CLI reference

Reference

CLI reference

sharedoc reaches the same workspace you see in the browser — projects, files, threads, channels, DMs, notifications, sync — from a terminal or an agent. 151 commands, each one call to the hub's API with the same rules as the web app. This page is built from the CLI's own command table, so it matches the version you install.

For agents

sharedoc help --json prints this whole table as JSON, sharedoc <command> --help prints one entry, and /docs/cli serves it as plain text. /skill.md adds the etiquette.

Install and log in#

Install
curl -fsSL https://sharedoc.brightmotion.io/install.sh | sh
sharedoc login
sharedoc whoami

Headless (CI, a container, an agent's own identity): skip login and set SHAREDOC_TOKEN to a token from Settings → CLI access, or one minted with sharedoc agents token. sharedoc update updates an installed CLI and restarts the sync service onto the new build.

Conventions#

  • Shape. sharedoc <noun> <verb> [args] [flags], flat. A project argument takes the slug or the display name in any case; a typo gets a did you mean.
  • Scope. --workspace and --project choose where a command acts. Inside a connected folder the folder decides; with one workspace or one project, that one.
  • JSON. --json prints exactly one JSON document on stdout, errors included, and nothing else. Agents should always pass it.
  • Never a prompt. Destructive commands need --yes. Where a command has --dry-run it checks permissions and previews the result without writing.
  • Attribution. --as-agent claude-code --as-model opus-5 marks a write as an agent's: Maya · via claude-code (opus-5).
  • Retries. Posting verbs take an --id so a retry can't double-post; --if-version makes a write fail (exit 4) rather than overwrite someone else's change.

Errors#

Under --json an error is one object with a stable code, a message, and often a hint for the next command:

stdout, exit 3
{
  "error": {
    "code": "not_found",
    "message": "no project 'lumen-ap' you can open — did you mean 'lumen-app'?",
    "hint": "sharedoc projects list --json"
  }
}

Exit codes#

CodeNameMeaning
0oksuccess
1usagebad arguments/flags
2authnot logged in, token revoked, or write scope required
3not_foundworkspace/project/file/thread not found
4conflictoverlapping edits under --strict, or a stale --if-version (412) — nothing was written; re-read and retry
5rate_limitedslow down; retry after the hinted seconds
6serverserver or network error
7timeout--wait ran out with the draft set still open — ask again later

Global flags#

These work on every command.

--jsonPrint exactly one JSON document on stdout (errors too). Agents: always use this.
--workspace valueWorkspace slug/id/name to act in (default: the connected folder, else your only workspace)
--project valueProject slug (default: the connected folder, else your only project in the workspace)
--host valueServer base URL (default: $SHAREDOC_HTTP, else the host you logged in to)
--as-agent valueAttribute writes to an agent, e.g. 'claude-code' (also $SHAREDOC_AGENT)
--as-model valueThe model behind --as-agent, e.g. 'opus-5' — shown as 'via claude-code (opus-5)' (also $SHAREDOC_MODEL)
--limit nMax rows for list commands (default 50, max 1000)
--cursor valueContinue a list from a previous nextCursor
--yesSkip confirmation for destructive commands (agents are non-interactive)
--dry-runShow what would happen; write nothing
--quietNo footer / hints on stderr
--allow-cwd-envKeep SHAREDOC_* that bun loaded from the working directory's .env (the installed CLI never loads it)

Environment and files#

NameWhat
SHAREDOC_TOKENA CLI token to use instead of the saved login. Never read from a .env in the current folder, so a repo can't inject one; the same goes for every SHAREDOC_* variable here. Export them in your shell instead.
SHAREDOC_HTTPThe server's base URL (default: the one you logged in to).
SHAREDOC_AGENT · SHAREDOC_MODELDefaults for --as-agent and --as-model.
SHAREDOC_TELEMETRY=0 · DO_NOT_TRACK=1Turn off the usage report the CLI sends after each command (the command's name, flag names without values, exit code, duration, version, OS). sharedoc telemetry off does the same for this machine.
~/.config/share-doc/config.jsonSaved logins, one per workspace, and the server they belong to (mode 0600). %APPDATA% on Windows; $XDG_CONFIG_HOME is honoured.
~/.config/share-doc/sync.jsonThe sync set: every folder ⇄ project pair, and the chat mirror. Edit it with sync add / sync rm.
~/.share-doc/The installed CLI bundle, which the sync service runs.

Commands#

By area. writes needs a write-scope token.

Identity & scope 5Workspaces & projects 19People 30Files 17Threads (doc comments) 12Channels & DMs 36Search & events 6Sync 22Self-description 5

Identity & scope

Sign in: opens the browser to authorize this device (AgentHog-style loopback + one-time code). With only a pre-browser credentials.json on the machine it migrates that token into config.json instead, no browser — asking which workspace it should act in (or --workspace).

sharedoc login [flags]
Flags
--forceRe-login even if already logged in (revokes the stored tokens first)
--no-browserPrint the URL and paste the code instead (implied over SSH)
--no-waitPrint the URL to approve and exit — the first of two steps, for an agent that cannot answer a prompt: the human approves, the page shows a code, and --code finishes
--code valueFinish a --no-wait login with the sd_code_… the authorize page showed (single-use, 5 minutes)
--scope valueToken scope · one of read, write · default write
--host valueServer to log in to (default: production)
--workspace valueMigrating credentials.json: the workspace the token acts in by default (required under --json or without a terminal when it sees several)
Examples
sharedoc login
sharedoc login --no-browser
sharedoc login --no-wait --json            # an agent: send the human the url …
sharedoc login --code sd_code_… --json     # … and redeem the code they paste back
sharedoc login --workspace brightmotion --json   # migrate credentials.json headlessly
sharedoc-dev login --host http://localhost:3273

--json{ loggedIn, apiUrl, config, verified, tokenName, projects, logins:[{workspace,workspaceId,slug,scope}], migrated?, from? } (--no-wait: { loggedIn:false, apiUrl, url, codeTtlSeconds, next })

APIGET /cli/auth → POST /api/v1/cli/exchange (migration: GET /api/v1/me)

Who and where am I: user, token, scope, workspaces, projects, server, credential source.

sharedoc whoami
Examples
sharedoc whoami --json

--json{ api, source, legacy, logins:[{workspace,workspaceId,slug,user,token:{name,prefix,scope},workspaces,projects:n,error}] } (source = the file actually read, or "SHAREDOC_TOKEN env"; legacy = it was credentials.json)

APIGET /api/v1/me

Revoke the stored tokens server-side and forget them locally.

sharedoc logout

APIDELETE /api/v1/tokens/self

List your CLI tokens (name, prefix, scope, last used).

sharedoc tokens list

--json{ tokens:[{id,name,prefix,scope,workspaceIds,lastUsedAt,createdAt,revoked}] }

APIGET /api/v1/tokens

Revoke one token by id.

sharedoc tokens revoke <id> [flags]
Arguments
<id>token id from `tokens list`
Flags
--yesrequired

APIDELETE /api/v1/tokens/:id

Workspaces & projects

Workspaces you belong to, with your role.

sharedoc workspaces list

--json{ workspaces:[{id,slug,name,role,projects:n}] }

APIGET /api/v1/workspaces

Create a workspace, inbox and general channel. Requires an account-wide write token from Settings → CLI access.

sharedoc workspaces create <name> [flags]
Arguments
<name>display name
Flags
--slug valueexplicit stable slug (1–60 lowercase letters, numbers or hyphens)
--if-not-existsreuse --slug only when ownership, workspace and settings match; repair missing seed files

--json{ workspace, created }

APIPOST /api/v1/workspaces

Projects across saved logins, deduplicated and sorted by slug/id. Pages default to 50; continue with --cursor and the same filters.

sharedoc projects list [flags]
Flags
--visibility valueprivate or workspace
--query valuename or slug contains this text
Examples
sharedoc projects list --workspace justin --json

--json{ projects:[{id,slug,name,workspace,role,visibility,kind,updatedAt}], nextCursor }

APIGET /api/v1/projects

Create a project. It opens on a seeded sharedoc-instructions.md; the folder to sync comes next (sync add).

sharedoc projects create <name> [flags]
Arguments
<name>display name (the slug is derived)
Flags
--slug valueexplicit stable slug (1–60 lowercase letters, numbers or hyphens)
--if-not-existsreuse --slug only when ownership, workspace and settings match; repair missing seed files
--workspace valueworkspace slug — asked for on a terminal when you have several; required under --json then
--privatemembers only (default: everyone in the workspace can open it)
--no-channelno chat channel for this project
Examples
sharedoc projects create "Acme Diligence" --workspace brightmotion --json

--json{ project:{id,slug,name,workspace,visibility,channel,url,seeded[]}, created, complete, seedErrors[] }

APIPOST /api/v1/projects

The statuses a doc may carry (its front-matter `status`): the workspace's list, or with --project that project's effective list — its own, or the workspace's when it inherits (`inherited: true`). Each has a key (what the file holds), a label, one of nine colours and an optional description (when to set it).

sharedoc statuses list [flags]
Flags
--project valuea project's effective list (default: the workspace's)
--field valuewhich property's list · default status
Examples
sharedoc statuses list
sharedoc statuses list --project sharedoc --json

--json{ workspace, project, field, inherited, revision, canEdit, presets:[{key,label,color,description,position}] }

APIGET /api/v1/statuses?project=|workspace=[&field=]

Add a status to the workspace's list (owner/admin) or, with --project, to that project's own list (project owner or workspace admin). A project that inherits gets its own list, starting from a copy of the workspace's.

sharedoc statuses add <key> [flags]
Arguments
<key>lowercase letters, digits and dashes: ready-for-agent
Flags
--label valuewhat chips show · default from the key: Ready for agent
--color valuecolour name · one of gray, red, orange, yellow, green, teal, blue, purple, pink · default gray
--description valuewhen to set it — shown in the status picker
--position nwhere in the list (0 = first) · default last
--project valuethe project's own list instead of the workspace's
--field valuewhich property's list · default status
Examples
sharedoc statuses add ready-for-agent --color purple --description "fully specified; an agent may start"
sharedoc statuses add triage --color orange --project sharedoc

--json{ workspace, project, field, inherited, revision, changed, presets, vanished:[{key,docs}] }

APIPUT /api/v1/statuses

Remove a status from a list. Docs that still say it are not rewritten: they show it as unknown (`invalid: ["unknown-status"]`), and the output says how many.

sharedoc statuses rm <key> [flags]
Arguments
<key>the status key
Flags
--project valuethe project's own list
--field valuewhich property's list · default status
Examples
sharedoc statuses rm triage --project sharedoc

--json{ workspace, project, field, inherited, revision, changed, presets, vanished:[{key,docs}] }

APIPUT /api/v1/statuses

Rename a status key (label kept unless --label). Docs are not rewritten: ones with the old key show it as unknown until edited.

sharedoc statuses mv <key> <new-key> [flags]
Arguments
<key>the current key
<new-key>the new key
Flags
--label valuea new label as well
--project valuethe project's own list
--field valuewhich property's list · default status
Examples
sharedoc statuses mv review ready-for-review --label "Ready for review"

--json{ workspace, project, field, inherited, revision, changed, presets, vanished:[{key,docs}] }

APIPUT /api/v1/statuses

Change a status's label, colour, description or position.

sharedoc statuses edit <key> [flags]
Arguments
<key>the status key
Flags
--label valuewhat chips show
--color valuecolour name · one of gray, red, orange, yellow, green, teal, blue, purple, pink
--description valuewhen to set it ("" clears it)
--position nmove it (0 = first)
--project valuethe project's own list
--field valuewhich property's list · default status
Examples
sharedoc statuses edit blocked --color red --position 0

--json{ workspace, project, field, inherited, revision, changed, presets, vanished:[{key,docs}] }

APIPUT /api/v1/statuses

Drop a project's own list so it uses the workspace's again.

sharedoc statuses inherit [flags]
Flags
--project valuethe project (required)
--field valuewhich property's list · default status
Examples
sharedoc statuses inherit --project sharedoc

--json{ workspace, project, field, inherited: true, changed, presets, vanished:[{key,docs}] }

APIPUT /api/v1/statuses { project, inherit: true }

One command from a folder to a link: create a project named after the folder, push its contents once, and print a join link.

sharedoc share [dir] [flags]
Arguments
[dir]the folder (default .). Use --resume for interrupted setup. A git repository root needs --force (prefer docs/)
Flags
--slug valueexplicit stable slug (1–60 lowercase letters, numbers or hyphens)
--if-not-existsreuse --slug only when ownership, workspace and settings match; repair missing seed files
--resumeresume this folder’s saved share setup without creating another project or link
--no-channelno project chat channel
--label valuelabel on the join link
--name valueproject name (default: the folder name)
--workspace valueworkspace slug (asked on a terminal when you have several; required under --json then)
--role valuewhat the link grants · one of editor, commenter, viewer · default editor
--expires valuelink lifetime: 7d, 24h, 30d, never · default 7d
--privatemembers only
--syncalso add the folder to the sync set so it keeps syncing (sharedoc sync install runs it)
--forceshare a git repository root anyway
--yesskip the warning for a folder of more than 50 files or 20 MB (it is sent either way; the list is printed first; lockfiles, build output and what .sharedocignore lists are skipped)
Examples
sharedoc share ~/deals/acme
sharedoc share ./brand-board --role viewer --expires 30d --json

--json{ project, pushed:{pulled,pushed,merged,conflicts}, link, url, sync }

APIPOST /api/v1/projects · PUT /api/v1/files/content · POST /api/v1/projects/:slug/share-links

Inspect IDs, access, counts and settings URL.

sharedoc projects show <slug>
Arguments
<slug>stable slug

APIGET /api/v1/projects/:slug

Rename the display name; IDs, slugs and links stay the same.

sharedoc projects rename <slug> <value>
Arguments
<slug>stable slug
<value>new display name

APIPATCH /api/v1/projects/:slug

Change project access and reconcile its channel. --dry-run explains the access change.

sharedoc projects visibility <slug> <value>
Arguments
<slug>stable slug
<value>private or workspace

APIPATCH /api/v1/projects/:slug

Delete a project for good: its files (archived ones too), comments, channel, members, links and history. Posts elsewhere that only carry its label stay and lose the label; members' synced folders keep their local copies. Owners and workspace admins. Requires --yes; --dry-run says what would go.

sharedoc projects delete <slug> [flags]
Arguments
<slug>stable slug
Flags
--yesrequired
--dry-runpreview what goes
Examples
sharedoc projects delete old-experiment --dry-run
sharedoc projects delete old-experiment --yes

--json{ deleted, project, removed:{files,archivedFiles,bytes,members,threads,messages,connections}, storage:{deleted,failed} }

APIDELETE /api/v1/projects/:slug?confirm=<slug>

Delete a workspace for good: every project and file in it, its channels, DMs and messages, its members' seats and invitations. Its agents are retired, its Slack connections disconnected, and CLI tokens that only opened it are revoked. People's synced folders keep their local copies. Only the owner. Requires --yes; --dry-run says what would go.

sharedoc workspaces delete <slug> [flags]
Arguments
<slug>stable slug
Flags
--yesrequired
--dry-runpreview what goes
Examples
sharedoc workspaces delete my-mistake --dry-run
sharedoc workspaces delete my-mistake --yes

--json{ deleted, workspace, removed:{projects,files,archivedFiles,bytes,members,agents,channels,messages,slackConnections}, storage:{deleted,failed}, agentsRemoved, tokensRevoked, slackDisconnected, slackRevoked }

APIDELETE /api/v1/workspaces/:slug?confirm=<slug>

Inspect IDs, access, counts and settings URL.

sharedoc workspaces show <slug>
Arguments
<slug>stable slug

APIGET /api/v1/workspaces/:slug

Rename the display name; IDs, slugs and links stay the same.

sharedoc workspaces rename <slug> <value>
Arguments
<slug>stable slug
<value>new display name

APIPATCH /api/v1/workspaces/:slug

Set the workspace's icon from a PNG, JPEG or WebP (up to 2 MB; the hub crops it to a centred square), or --remove it to go back to the initials square. Owners and admins. It shows in the sidebar's workspace picker and switcher, the settings header and the CLI login page; in the web app the same people get Add an icon under General in the workspace settings, with a crop to drag and zoom.

sharedoc workspaces icon <slug> [image] [flags]
Arguments
<slug>stable slug
[image]the picture; omit with --remove
Flags
--removeclear the icon instead
Examples
sharedoc workspaces icon acme ./logo.png
sharedoc workspaces icon acme --remove

--json{ workspace, icon, version? }

APIPUT|DELETE /api/v1/workspaces/:slug/icon

People

Members of a workspace (or the explicit members of a project with --project), with each one's status. `kind` is `person` or `bot` (an agent identity).

sharedoc members list

--json{ members:[{id,name,email,kind,role,invitedBy,joinedAt,lastActiveAt,status:{emoji,text,expiresAt,paused}|null}], invites:[{id,email,role,projectRole,workspaceRole,project,invitedBy,createdAt}] }

APIGET /api/v1/members

The workspace's agents — bots an admin created so an agent can act as itself, each with a name, who controls it, whether it is paused, who may DM it, and (for admins and its owner) its tokens. --mine lists the ones you control, across workspaces.

sharedoc agents list [flags]
Flags
--minethe agents you control, in every workspace
Examples
sharedoc agents list
sharedoc agents list --mine --json

--json{ workspace, canManage, agents:[{id,name,kind:"bot",avatar,paused,dmsFrom:"owner"|"workspace",owner:{id,name,email},workspace:{id,slug,name},createdAt,tokens:[{id,name,prefix,lastUsedAt,createdAt,revoked}]}] }

APIGET /api/v1/agents?workspace=|mine=1

Create an agent (workspace owner/admin): a member of its own with the name given, controlled by you unless --owner names another member. It cannot invite, share, change settings or mint tokens; mint its first token with `agents token`. The name is how docs assign work to it (`assignee: Night shift`), so it may not contain '@' — and renaming it later leaves the docs that name it unassigned, with a warning, until someone picks again.

sharedoc agents create <name...> [flags]
Arguments
<name…>its display name, e.g. "Night shift" — what a doc's assignee says; 1–60 characters, no @
Flags
--owner valuewho controls it: an email, name or user id in the workspace · default you
--dms valuewho may DM it: owner (only whoever controls it) or workspace (anyone in it) · one of owner, workspace · default owner
Examples
sharedoc agents create Night shift
sharedoc agents create "Night shift" --owner [email protected] --json

--json{ agent:{id,name,owner,workspace,paused,dmsFrom,tokens:[]} }

APIPOST /api/v1/agents

Mint a write token for an agent, bound to its workspace — shown once; pass it to the agent as SHAREDOC_TOKEN. Its calls then write as the agent (add --as-agent / --as-model there for the 'via' chip). Admins and whoever controls it.

sharedoc agents token <agent> [flags]
Arguments
<agent>its name or id (`agents list`)
Flags
--name valuea label for the token · default <agent> token
Examples
sharedoc agents token "Night shift" --name "cron on the server" --json

--json{ token, id, prefix, name, scope:"write", agent:{id,name}, workspace }

APIPOST /api/v1/agents/:id/tokens

Revoke one of an agent's tokens. Admins and whoever controls it.

sharedoc agents revoke <agent> <token-id> [flags]
Arguments
<agent>its name or id
<token-id>from `agents list --json` (tokens[].id)
Flags
--yesrequired
Examples
sharedoc agents revoke "Night shift" 6f1c… --yes

--json{ ok, id }

APIDELETE /api/v1/agents/:id/tokens/:tokenId

Pause an agent: every call with its tokens is refused, by name, until `agents resume`. Its seat and tokens are kept. Admins and whoever controls it.

sharedoc agents pause <agent>
Arguments
<agent>its name or id
Examples
sharedoc agents pause "Night shift"

--json{ agent }

APIPATCH /api/v1/agents/:id { paused: true }

Resume a paused agent.

sharedoc agents resume <agent>
Arguments
<agent>its name or id
Examples
sharedoc agents resume "Night shift"

--json{ agent }

APIPATCH /api/v1/agents/:id { paused: false }

Choose who may DM an agent: `owner` — only whoever controls it, the default — or `workspace`, anyone in it. Anyone else's message (or reply, or huddle) in a DM or group with it is refused by name and never reaches it — the web shows them an auto-reply instead — and their @-mentions, replies in threads it follows and doc comments post without notifying it. Another agent with the same controller always gets through. Admins and whoever controls it.

sharedoc agents dms <agent> <who>
Arguments
<agent>its name or id
<who>owner or workspace
Examples
sharedoc agents dms "Night shift" workspace
sharedoc agents dms "Night shift" owner --json

--json{ agent }

APIPATCH /api/v1/agents/:id { dmsFrom }

Set an agent's profile photo from a PNG, JPEG or WebP (up to 2 MB; the hub crops it to a centred square), or --remove it to go back to the robot disc. Admins and whoever controls it. In the web app the same people get a camera button on its profile and its Agents row, with a crop to drag and zoom.

sharedoc agents photo <agent> [image] [flags]
Arguments
<agent>its name or id
[image]the picture; omit with --remove
Flags
--removeclear the photo instead
Examples
sharedoc agents photo "Night shift" ./owl.png
sharedoc agents photo "Night shift" --remove

--json{ agent, avatar, version? }

APIPUT|DELETE /api/v1/agents/:id/avatar

Delete an agent (workspace owner/admin): its tokens are revoked and it leaves the workspace and its projects; what it wrote stays, signed "<name> (removed)".

sharedoc agents rm <agent> [flags]
Arguments
<agent>its name or id
Flags
--yesrequired
Examples
sharedoc agents rm "Night shift" --yes

--json{ ok, id, name, tokensRevoked }

APIDELETE /api/v1/agents/:id

Show your status: the emoji and text next to your name on every message and sidebar row.

sharedoc status
Examples
sharedoc status
sharedoc status --json

--json{ status:{emoji,text,expiresAt,paused}|null }

APIGET /api/v1/me/status

Set your status. It stays until you clear it, or clears itself after --for or at --until. --pause also snoozes your notifications for as long as it lasts.

sharedoc status set [text...] [flags]
Arguments
[text…]the status line, up to 100 characters (optional when --emoji is given)
Flags
--emoji valueone emoji, or a :shortcode: like :calendar: (default 💬)
--for valueclear after this long: 30m, 1h, 1h30m, 2d (1 minute to 7 days)
--until valueclear at: eod (midnight tonight, your time zone), eow (midnight ending Friday), a date like 2026-09-28 (the start of that day, your time zone), or an ISO date-time with its offset
--pausealso pause notifications until the status clears
Examples
sharedoc status set "done for the day, started early" --emoji 🌅 --until eod --pause
sharedoc status set "in a meeting" --emoji :calendar: --for 1h
sharedoc status set "on vacation" --emoji 🌴 --until 2026-09-28

--json{ status:{emoji,text,expiresAt,paused}, snoozeUntil? }

APIPUT /api/v1/me/status

Clear your status. If it paused your notifications, that snooze ends too (unless you changed the snooze since).

sharedoc status clear
Examples
sharedoc status clear

--json{ status:null, snoozeUntil? }

APIDELETE /api/v1/me/status

Add people who are already in the workspace to a project (the way into a private one). Someone already on it keeps their role; someone not in the workspace is refused — invite them with `members invite`. Owners and editors may add.

sharedoc members add <who...> [flags]
Arguments
<who…>one or more emails, names or user ids (comma or space separated)
Flags
--project valueproject slug (default: the connected folder's)
--role valueproject role for the people added · one of viewer, commenter, editor · default editor
Examples
sharedoc members add [email protected] [email protected] --project severance-plan --role commenter
sharedoc members add [email protected] --project severance-plan --dry-run --json

--json{ project, role, added:[{id,name,email,role}], already:[{id,name,email,role}], dryRun? }

APIPOST /api/v1/projects/:slug/members

Invite people by email to the workspace, or to one project (--project, or the connected folder's). Nobody's existing role changes: results show the workspace and project role each person actually holds. A still-pending invitation comes back as `pending` and is not re-mailed — `invites resend <id>` does that.

sharedoc members invite <email...> [flags]
Arguments
<email…>one or more emails (comma or space separated)
Flags
--project-role valuewith --project: what they may do there · one of viewer, commenter, editor · default editor
--workspace-role valuethe seat someone new gets in the workspace. guest = only the projects they are put on (the default with --project; member without). member needs you to be a member; admin, a workspace owner/admin · one of guest, member, admin · default guest with --project, else member
--role valueold spelling of --workspace-role · one of guest, member, admin
--project valuealso put them on this project (someone already on it keeps their role)
--dry-runsay what would happen to each address; writes and mails nothing
Examples
sharedoc members invite [email protected] --project acme --project-role commenter --workspace-role guest
sharedoc members invite [email protected] --project acme --dry-run --json

--json{ workspace, project, role, workspaceRole, projectRole, dryRun?, results:[{email,kind:"added"|"already"|"invited"|"pending",workspaceRole,projectRole?,invitationId?,emailSent,emailError?}] }

APIPOST /api/v1/members/invite

Change an explicit member's role on a project (--project; the project owner, or a workspace owner/admin who can open it — a private project only its members can) or in the workspace (--workspace; owners/admins). The owner's role never changes; nobody is added.

sharedoc members role <who> <role> [flags]
Arguments
<who>email, name or user id
<role>project: viewer|commenter|editor · workspace: guest|member|admin
Flags
--project valuea project role
--workspace valuea workspace role (instead of --project)
--dry-runshow before → after; change nothing
Examples
sharedoc members role [email protected] viewer --project severance-plan
sharedoc members role [email protected] guest --workspace acme --dry-run

--json{ project|workspace, member:{id,name,email}, before, role, changed, dryRun? }

APIPATCH /api/v1/projects/:slug/members/:who · PATCH /api/v1/workspaces/:slug/members/:who

Take someone's explicit membership off a project (--project), or remove them from the workspace and every project in it (--workspace). If they can still open the project through the workspace, the result says so. The owner cannot be removed. Requires --yes.

sharedoc members remove <who> [flags]
Arguments
<who>email, name or user id
Flags
--project valueoff this project
--workspace valueout of this workspace (instead of --project)
--yesrequired
--dry-runpreview, including access they keep
Examples
sharedoc members remove [email protected] --project severance-plan --yes

--json{ project, member, role, removed, stillHasAccess:{role,through}|null, warning } | { workspace, member, role, removed, projects }

APIDELETE /api/v1/projects/:slug/members/:who · DELETE /api/v1/workspaces/:slug/members/:who

Pending email invitations — a project's (--project) or the workspace's — with the id `invites resend|revoke` take. (`invite links` lists share links.)

sharedoc invites list [flags]
Flags
--project valueproject slug (default: the connected folder's)
--workspace valueevery pending invitation in the workspace you can see
Examples
sharedoc invites list --project acme

--json{ project|workspace, invites:[{id,email,project,projectRole,workspaceRole,invitedBy,invitedById,createdAt,lastSentAt,sends,resendableAt,canManage}] }

APIGET /api/v1/invites

Mail a pending invitation again — at most once every 10 minutes and 5 times in all. Whoever sent it, the project owner, or a workspace owner/admin who can open the project. The invitation stays as it was; `delivery` says whether the mail went.

sharedoc invites resend <id> [flags]
Arguments
<id>invitation id from `invites list`
Flags
--project valuethe project it is for (an id from another project is not found)
--dry-runsay whether it could be sent now

--json{ invitation, resent, delivery:{sent,error} } | { dryRun, invitation, wouldSend, blocked }

APIPOST /api/v1/invites/:id/resend

Withdraw a pending invitation so it no longer lets anyone in. Removes nobody — use `members remove`. Requires --yes.

sharedoc invites revoke <id> [flags]
Arguments
<id>invitation id from `invites list`
Flags
--project valuethe project it is for
--yesrequired
--dry-runpreview

--json{ invitation, revoked }

APIDELETE /api/v1/invites/:id

Revoke a join link; anyone who already joined stays. With --file/--folder: set that path to Restricted (people who joined through its link lose that access; direct grants and memberships stay).

sharedoc invite revoke [id] [flags]
Arguments
[id]link id from `invite links` (project links)
Flags
--project valueproject slug (default: the connected folder's)
--file valuethe file to restrict
--folder valuethe folder to restrict

--json{ project, link, revoked:true } | { project, path, link, losesAccess }

APIDELETE /api/v1/projects/:slug/share-links/:id · PUT /api/v1/resources/link {audience:restricted}

Re-copy a file/folder's active link (the same secret, nothing rotated). Managers only; Restricted returns the ordinary URL.

sharedoc invite copy [flags]
Flags
--file valuethe file
--folder valuethe folder
--project valueproject slug (default: the connected folder's)

--json{ project, path, link, url, secret }

APIPOST /api/v1/resources/link/copy

Rotate a file/folder's link: the old one stops working for everyone who joined through it, a new one is minted with the same settings. Requires --yes; --dry-run lists who loses access.

sharedoc invite replace [flags]
Flags
--file valuethe file
--folder valuethe folder
--project valueproject slug
--yesrequired
--dry-runpreview

--json{ project, path, link, url, replaced:true, losesAccess }

APIPOST /api/v1/resources/link/replace

Who can open one file or folder and why: your own effective role; for a project owner or workspace admin also the direct grants, inherited folder grants, pending invitations and the link state.

sharedoc access list <path> [flags]
Arguments
<path>project-relative path (a file with an extension, else a folder; or --file/--folder)
Flags
--project valueproject slug (default: the connected folder's)
--file valuetreat as a file
--folder valuetreat as a folder
Examples
sharedoc access list clients/acme --project deals
sharedoc access list clients/acme/brief.md --json

--json{ project, path, kind, role, capabilities:{read,comment,write,manage}, sources, canManage, aclRevision, people?, inherited?, invitations?, link?, linkRecipients? }

APIGET /api/v1/resources/access

Give a person (an existing account) viewer, commenter or editor access to one file or folder. Adds to their membership access, never lowers it.

sharedoc access set <path> <email> [flags]
Arguments
<path>file or folder
<email>their account email
Flags
--role valuewhat they may do · one of viewer, commenter, editor · default viewer
--project valueproject slug
--dry-runpreview

--json{ grant:{id,user,role,path,kind}, created, aclRevision }

APIPUT /api/v1/resources/grants

Take a direct grant away (by email or grant id). Reports the access they still have through a parent folder or membership. Requires --yes.

sharedoc access remove <path> <who> [flags]
Arguments
<path>file or folder
<who>email or grant id
Flags
--project valueproject slug
--yesrequired
--dry-runpreview

--json{ revoked, aclRevision, removed, stillHasAccessVia }

APIDELETE /api/v1/resources/grants/:id

Invite an email to one file or folder: an existing account is granted at once; anyone else gets a pending invitation (7 days) they redeem with that verified email. No email is sent unless --notify.

sharedoc access invite <path> <email> [flags]
Arguments
<path>file or folder
<email>who
Flags
--role valuewhat they may do · one of viewer, commenter, editor · default viewer
--notifysend the invitation email
--project valueproject slug
--dry-runpreview

--json{ action:granted|invited, grant?|invitation?, emailSent, aclRevision }

APIPOST /api/v1/resources/invitations

Files and folders shared with you file-by-file (outside your project memberships), with role and URL.

sharedoc access shared

--json{ items:[{project,projectName,workspace,path,kind,role,source,grantedAt,url}] }

APIGET /api/v1/shared-with-me

Files

Files in a project (any project you can open — no connected folder needed).

sharedoc files list [project] [path] [flags]
Arguments
[project]project slug or name (any case), or `_inbox` for this workspace's inbox (or --project)
[path]folder prefix
Flags
--kind valuefilter · one of markdown, blob
--since valueonly files changed since (24h, 7d, ISO)
--depth none folder level instead of the whole tree — adds folders[] · default unlimited
--status valueonly markdown docs with this status (repeat, or comma-separate, for any of them)
--assignee valueonly docs assigned to them: me, an email, or an agent's name
--unassignedonly markdown docs nobody is assigned to
Examples
sharedoc files list homa-legal
sharedoc files list homa-legal --depth 1 --json
sharedoc files list _inbox --since 1h --json
sharedoc files list sharedoc --status blocked --assignee me

--json{ project, path?, folders?:[{name,path,count}], files:[{path,name,kind,size,mime,version,updatedAt,thumbUrl,statuses,assignee:{id,name,email}|null,meta,invalid}] }

APIGET /api/v1/files?project=&status=&assignee=&unassigned=1

Print a file: markdown as text (latest revision), blobs as raw bytes. --thumb gives the small version of a picture or a video poster; --out writes to a file.

sharedoc files cat <project> <path> [flags]
Arguments
<project>project slug
<path>file path inside the project
Flags
--version na specific revision (markdown only)
--thumbthe 640px thumbnail (images) or poster (video) instead of the original — read this first
--out valuewrite the bytes to this file instead of stdout
--with-mediamarkdown only: after the text, list every image and media link resolved to a project path (same as `files links`)
Examples
sharedoc files cat homa-legal contract-v3.md
sharedoc files cat homa-legal media/shot.png --thumb --out /tmp/shot.webp
sharedoc files cat _inbox 2026-09/ab12cd-scan.pdf > scan.pdf

APIGET /api/v1/files/content

Revisions of a markdown file.

sharedoc files history <project> <path>
Arguments
<project>project slug
<path>file path

--json{ revisions:[{version,size,contentHash,authorUserId,author:{id,name,email}|null,viaAgent,source,summary,createdAt}] }

APIGET /api/v1/files/history

Write a file. Markdown is 3-way merged into the live doc against --base, never overwritten.

sharedoc files put <project> <path> [flags]
Arguments
<project>project slug or name, any case (or `<project>/<path>` as one argument)
<path>file path inside the project
Flags
--from valueread the bytes from a local file, or - for stdin (default: stdin when piped)
--message valuerevision summary, shown in `files history`
--base nthe version you read before editing — makes this a merge, not an overwrite
--strictrefuse (409 → exit 4) instead of merging overlapping edits
Examples
sharedoc files put homa-legal contract-v3.md --from ./contract.md --base 7 --message "counsel edits"
cat plan.md | sharedoc files put homa-legal notes/plan.md --json
sharedoc files put homa-legal assets/site.png --from ./site.png

--json{ path, version, kind, merged, failedHunks, conflict }

APIPUT /api/v1/files/content?project=&path=&base=&source=cli&strict=&message=

Set a markdown doc's statuses — the `status` list in its YAML front-matter, edited in place (comments and other keys kept; the block is added when missing and removed when nothing is left). Keys you set or add must be in the project's statuses (`statuses list`); any key can be removed. Order: --set/--clear, then --remove, then --add. One revision, source `meta`.

sharedoc files status <project> <path> [flags]
Arguments
<project>project slug (or `<project>/<path>` as one argument)
<path>the .md doc
Flags
--add valueadd a status (repeat, or comma-separate)
--remove valueremove a status (repeat, or comma-separate)
--set valuereplace the whole list (comma-separated)
--clearremove every status
--if-version nthe version you read (files list --json): if the doc moved on, nothing is written — 412 → exit 4
--message valuerevision summary (default: "status → …")
--replace-unreadablethe block does not parse (409): replace it with a clean one
Examples
sharedoc files status sharedoc issues/x.md --add ready-for-agent
sharedoc files status sharedoc issues/x.md --remove ready-for-agent --add in-progress --if-version 7 --json || echo "someone else claimed it"
sharedoc files status sharedoc issues/x.md --set done
sharedoc files status sharedoc issues/x.md --clear

--json{ project, path, version, changed, statuses, assignee:{id,name,email}|null, meta, invalid }

APIPOST /api/v1/files/meta (If-Match: <version>)

Set (or clear) a markdown doc's assignee — the `assignee` line in its front-matter: a person's email, or an agent's name (`assignee: Night shift`). `me` is you, or the agent whose token this is; `none` clears it. They must be able to open the project; they get a notification unless it is you. An agent's name is matched whatever its case, and only an agent is matched by name — a person is named by email.

sharedoc files assign <project> <path> <who> [flags]
Arguments
<project>project slug (or `<project>/<path>` as one argument)
<path>the .md doc
<who>an email, an agent's name, `agent:<id>`, `me`, or `none`
Flags
--if-version nthe version you read: 412 → exit 4 when the doc moved on
--message valuerevision summary (default: "assignee → …")
Examples
sharedoc files assign sharedoc issues/x.md me
sharedoc files assign sharedoc issues/x.md [email protected]
sharedoc files assign sharedoc issues/x.md "Night shift"
sharedoc files assign sharedoc issues/x.md none

--json{ project, path, version, changed, statuses, assignee:{id,name,email}|null, meta, invalid }

APIPOST /api/v1/files/meta (If-Match: <version>)

Markdown docs across every project you can open in a workspace, found by their front-matter properties — the overnight runner's question ("what is ready for an agent?") in one call. Filters AND together; archived docs are left out unless asked. Newest change first; pages of 50 (--limit up to 500), `nextCursor` continues, --all follows every page. Someone who reaches a project only through a shared file sees just that file, and never who it is assigned to.

sharedoc docs list [flags]
Flags
--status valuehas this status (repeat, or comma-separate, for any of them)
--assignee valueassigned to me, an email, an agent's name, or a user id (comma-separate for any of them)
--project valueonly these projects (repeat, or comma-separate)
--unassignednobody is assigned
--has-statushas at least one status
--path valuepath starts with this (e.g. issues/)
--archivedarchived docs only
--include-archivedlive and archived docs
--sort valueupdated (newest first) or path (by project, then path) · one of updated, path · default updated
--allfollow nextCursor to the end and print one answer (stops at a page boundary past 10 000 docs; nextCursor continues)
Examples
sharedoc docs list --status ready-for-agent --json
sharedoc docs list --assignee me --has-status
sharedoc docs list --status blocked --status in-progress --project sharedoc --project kid-learn
sharedoc docs list --status ready-for-agent --all --json | jq -c '.docs[]'

--json{ workspace, docs:[{id,project,path,name,statuses,assignee:{id,name,email}|null,meta,invalid,version,updatedAt,updatedBy:{id,name}|null,archived,openThreads}], nextCursor }

APIGET /api/v1/docs?workspace=&status=&assignee=&project=&unassigned=1&hasStatus=1&archived=0|1|all&sort=updated|path&limit=&cursor=

The same list with any filter tree: `{ and|or: [...] }` groups (4 deep, 50 conditions) of `{ field, op, value }`. Fields: status (contains, in, isEmpty, notEmpty), assignee (eq, in, isEmpty, notEmpty; 'me', an email or an id), project (eq, in), archived (eq, in), path (eq, in, startsWith), updatedAt (gt, lt; ISO, or 24h / 7d / 3m ago — m is months). A filter that never names archived matches live docs only; naming it anywhere (even in one or-branch) turns that off for the whole filter. Unsupported fields or ops are refused with the reason (exit 1).

sharedoc docs query [flags]
Flags
--filter valuethe filter tree as JSON
--filter-file valueread the filter tree from a file, or - for stdin
--sort valuekeys, comma-separated, - for descending: -updatedAt (default), path, project, assignee — or a JSON list of { field, direction, nulls }
--fields valueonly these row keys (comma-separated; id always comes back)
--allfollow nextCursor to the end (stops at a page boundary past 10 000 docs; nextCursor continues)
Examples
sharedoc docs query --filter '{"and":[{"field":"status","op":"contains","value":"blocked"},{"field":"assignee","op":"eq","value":"me"}]}' --json
sharedoc docs query --filter '{"or":[{"field":"status","op":"isEmpty"},{"field":"assignee","op":"isEmpty"}]}' --sort project,path
sharedoc docs query --filter '{"field":"updatedAt","op":"gt","value":"7d"}' --fields path,statuses --all --json

--json{ workspace, docs:[…as docs list…], nextCursor }

APIPOST /api/v1/docs/query { workspace, filter?, sort?, fields?, limit? (≤ 500), cursor? }

Move or rename a file (its revisions, threads and comments follow it).

sharedoc files mv <project> <from> <to> [flags]
Arguments
<project>project slug
<from>current path
<to>new path
Flags
--to-project valuemove it into another project as well
Examples
sharedoc files mv homa-legal draft.md contracts/v3.md
sharedoc files mv homa-legal scan.pdf scan.pdf --to-project homa-archive

--json{ project, path }

APIPOST /api/v1/files/move

Archive a doc — "closed", like an issue: off the tree and search, still readable at its URL with its comments and history; `files reopen` brings it back. Editing it from anywhere reopens it too. Requires --yes.

sharedoc files archive <project> <path> [flags]
Arguments
<project>project slug
<path>file path
Flags
--yesrequired — the CLI never prompts
Examples
sharedoc files archive homa-legal old-draft.md --yes

--json{ ok: true, archived: true }

APIPOST /api/v1/files/archive

Reopen an archived doc in place (history, comments and URL intact).

sharedoc files reopen <project> <path>
Arguments
<project>project slug
<path>file path
Examples
sharedoc files reopen homa-legal old-draft.md

--json{ ok: true, archived: false }

APIPOST /api/v1/files/reopen

Alias of `files archive` (kept for scripts). Requires --yes.

sharedoc files rm <project> <path> [flags]
Arguments
<project>project slug
<path>file path
Flags
--yesrequired — the CLI never prompts
Examples
sharedoc files rm homa-legal old-draft.md --yes

--json{ ok: true, archived: true }

APIPOST /api/v1/files/archive

Alias of `files reopen`.

sharedoc files restore <project> <path>
Arguments
<project>project slug
<path>file path
Examples
sharedoc files restore homa-legal old-draft.md

--json{ ok: true, archived: false }

APIPOST /api/v1/files/reopen

Unified diff between two revisions (default: the previous one → the latest).

sharedoc files diff <project> <path> [flags]
Arguments
<project>project slug
<path>file path
Flags
--from nolder version (from `files history`)
--to nnewer version
Examples
sharedoc files diff homa-legal contract-v3.md
sharedoc files diff homa-legal contract-v3.md --from 5 --to 7 --json

--json{ diff }

APIGET /api/v1/files/diff

A short-lived signed download URL for a blob (share it, curl it, feed it to a tool).

sharedoc files url <project> <path> [flags]
Arguments
<project>project slug
<path>file path
Flags
--ttl nseconds the URL stays valid · default 900
Examples
sharedoc files url _inbox 2026-09/ab12cd-scan.pdf --ttl 900 --json

--json{ url, expiresAt }

APIGET /api/v1/files/url

Threads (doc comments)

Threads you can see: one doc with --doc, one project, or everything (open by default).

sharedoc threads list [project] [flags]
Arguments
[project]project slug (or --project; omit for every project)
Flags
--doc valueonly threads anchored to this doc, as <project>/<path> or a path with --project
--status valuewhich threads · one of open, resolved, all · default open
--mineonly threads you started, replied to, or are mentioned in
--unreadonly threads with messages you have not read
Examples
sharedoc threads list --doc homa-legal/contract-v3.md --json
sharedoc threads list --status all --mine

--json{ threads:[{id,projectSlug,anchorKind,docPath,anchor,status,title,createdBy:{id,name},lastMessageAt,messageCount,followerCount,rootSnippet,viaAgent}], nextCursor }

APIGET /api/v1/threads

One thread: anchor, status, followers, every message, and the references they carry.

sharedoc thread show <id>
Arguments
<id>thread id from `threads list`
Examples
sharedoc thread show 2b7df1a0-… --json
sharedoc thread show 2b7df1a0-…

--json{ thread, messages:[{id,author:{id,name},viaAgent,kind,bodyMd,editedAt,deletedAt,createdAt,reactions:[{emoji,userIds,others}]}], references, followers }

APIGET /api/v1/threads/:id

Start a doc-anchored thread (this is what a comment is). Quote the text you mean.

sharedoc thread create [flags]
Flags
--doc valuethe doc to anchor to, as <project>/<path> (or a path with --project)
--quote valueexact text from the doc to anchor to
--lines valueline range instead of a quote, e.g. 12-20
--body valuemarkdown body (§8 tags: @name, [[project/path.md#L1-L9]], [[thread:id]])
--body-file valueread the body from a file, or - for stdin
--attach valuefile(s) to upload to _inbox and tag in the body, comma-separated: photo.jpg,clip.mp4
--id valueclient-generated uuid so a retry cannot double-post (else one is generated and printed)
--channel valuepost into a channel instead of a doc (`general`, `#general`, a project slug, or an id)
--project valuewith --channel: label the post with the project it is about (`none` for no label)
Examples
sharedoc thread create --doc homa-legal/contract-v3.md --quote "the parties agree" --body "Is this the signed wording? @Emily" --json
sharedoc thread create --doc homa-legal/contract-v3.md --lines 12-20 --body-file review.md

--json{ thread, message, references, unresolved }

APIPOST /api/v1/threads

Reply in an existing thread — prefer this over opening a new one.

sharedoc thread reply <id> [flags]
Arguments
<id>thread id
Flags
--body valuemarkdown body (or --body-file / stdin)
--body-file valueread the body from a file, or - for stdin
--attach valuefile(s) to upload to _inbox and tag in the body, comma-separated: photo.jpg,clip.mp4
--id valueclient-generated uuid for the new message (idempotent retries)
--draftsave it in the human's composer, unsent (it becomes a set of one if they have text there)
--variant valuepropose a version instead of posting — repeat for each; the human picks one
--variants-file valueversions as a JSON array [{ "label", "body" }] from a file, or - for stdin
--note valuewith versions: what you read and how they differ
--at valueschedule it instead: ISO, 'tomorrow 9:00', 'mon 9:00' (this machine's zone)
--in valueschedule it this far ahead: 90m, 2h, 1d
--notifywith a draft or versions: also notify the human (push)
Examples
sharedoc thread reply 2b7df1a0-… --body "Agreed — see [[homa-legal/appendix-b.md#L4-L9]]" --json
cat notes.md | sharedoc thread reply 2b7df1a0-…

--json{ message, references, unresolved }

APIPOST /api/v1/threads/:id/messages

Change which project one of your own channel posts or DMs says it is about (or `none` to clear it). Moves the label only — not the post.

sharedoc thread relabel <id> <project>
Arguments
<id>thread id
<project>project slug, or `none`
Examples
sharedoc thread relabel 2b7df1a0-… sharedoc
sharedoc thread relabel 2b7df1a0-… none

--json{ thread }

APIPATCH /api/v1/threads/:id

Mark a thread resolved (only when you were asked to — resolving hides it).

sharedoc thread resolve <id>
Arguments
<id>thread id
Examples
sharedoc thread resolve 2b7df1a0-…

--json{ thread }

APIPATCH /api/v1/threads/:id

Reopen a resolved thread.

sharedoc thread reopen <id>
Arguments
<id>thread id
Examples
sharedoc thread reopen 2b7df1a0-…

--json{ thread }

APIPATCH /api/v1/threads/:id

Follow a thread (you are followed in automatically when you post or are mentioned).

sharedoc thread follow <id>
Arguments
<id>thread id
Examples
sharedoc thread follow 2b7df1a0-…

--json{ thread }

APIPATCH /api/v1/threads/:id

Stop following a thread.

sharedoc thread unfollow <id>
Arguments
<id>thread id
Examples
sharedoc thread unfollow 2b7df1a0-…

--json{ thread }

APIPATCH /api/v1/threads/:id

Rewrite one of your own messages (references are re-derived).

sharedoc thread edit <message-id> [flags]
Arguments
<message-id>message id from `thread show`
Flags
--body valuethe new markdown body (or --body-file / stdin)
--body-file valueread the body from a file, or - for stdin
Examples
sharedoc thread edit 9c01a8b2-… --body "Fixed: @Emily-Ross owns this"

--json{ message, references, unresolved }

APIPATCH /api/v1/messages/:id

Tombstone one of your own messages. Requires --yes.

sharedoc thread delete <message-id> [flags]
Arguments
<message-id>message id from `thread show`
Flags
--yesrequired — the CLI never prompts
Examples
sharedoc thread delete 9c01a8b2-… --yes

--json{ ok: true }

APIDELETE /api/v1/messages/:id

Add (or with --remove, take back) an emoji reaction on a message.

sharedoc react <message-id> <emoji> [flags]
Arguments
<message-id>message id from `thread show`
<emoji>a single emoji, e.g. 👍
Flags
--removeremove your reaction instead of adding it
Examples
sharedoc react 9c01a8b2-… 👍
sharedoc react 9c01a8b2-… 👍 --remove --json

--json{ reactions:[{emoji,userIds,others:[{key,name,avatar}]}] }

APIPOST /api/v1/messages/:id/reactions · DELETE …?emoji=

Channels & DMs

Channels and DMs you can see, with unread posts and mention counts.

sharedoc channels list [flags]
Flags
--archivedarchived channels instead
Examples
sharedoc channels list
sharedoc channels list --json

--json{ conversations:[{id,kind,name,topic,isPrivate,projectSlug,memberCount,messageCount,lastMessageAt,me:{joined,notifyLevel,lastReadAt},unread,mentions,members?}] }

APIGET /api/v1/conversations

Audio huddles in the channels and DMs you can see, newest first: who started it, how long, who joined. Joining one needs a browser.

sharedoc huddles list [flags]
Flags
--liveonly huddles going on right now
Examples
sharedoc huddles list
sharedoc huddles list --live --json

--json{ huddles:[{id,conversationId,conversation:{kind,name},threadId,messageId,startedBy:{id,name},startedAt,endedAt,durationSec,participants:[{userId,name}],live:[{userId,name,muted}],count}] }

APIGET /api/v1/huddles

Start a huddle in a channel or DM — posts the announcement and notifies as usual; joining the call needs a browser.

sharedoc huddle start <channel>
Arguments
<channel>channel name or conversation id

APIPOST /api/v1/conversations/:id/huddle

Create a channel (public by default). A project made with its channel toggle on already has #<slug>; --project gives one to a project that has none.

sharedoc channels create [name] [flags]
Arguments
[name]channel name (lowercase, dashes); defaults to the project slug with --project
Flags
--project valueattach the channel to a project (its members can see it)
--privatemembers only
--topic valueone line shown in the header
--members valuecomma-separated emails/names to add
Examples
sharedoc channels create launch --topic 'Sept launch' --members [email protected]

--json{ conversation, created }

APIPOST /api/v1/conversations

Add workspace members to a channel you are in — the only way into a private one. A project's channel follows its project: use `members add --project` there.

sharedoc channels add <name> <who...>
Arguments
<name>channel name
<who…>one or more emails, names or user ids (comma or space separated)
Examples
sharedoc channels add plans [email protected] [email protected]

--json{ conversation, added, members:[{id,name,email,role,notifyLevel}] }

APIPOST /api/v1/conversations/:id/members

Join a public channel. You are in #general already, and in a project's channel when it has one — a project can be made without one, in which case its posts are labels on #general.

sharedoc channels join <name>
Arguments
<name>channel name

APIPOST /api/v1/conversations/:id/join

Leave a channel.

sharedoc channels leave <name>
Arguments
<name>channel name

APIPOST /api/v1/conversations/:id/leave

Archive a channel (hidden for everyone; posts stay). Requires --yes.

sharedoc channels archive <name> [flags]
Arguments
<name>channel name
Flags
--yesrequired

APIPATCH /api/v1/conversations/:id

Delete a channel and every post in it — permanent, unlike archive. A project channel deleted this way stays gone. Requires --yes.

sharedoc channels delete <name> [flags]
Arguments
<name>channel name
Flags
--yesrequired

--json{ deleted:true, id, name }

APIDELETE /api/v1/conversations/:id

Invite another workspace into one of your channels, by the email of someone there (owners and admins of this workspace). They pick which of their workspaces joins and what it is called there; nobody gets a seat in yours. Open mode: everyone in it sees every post. --hub: each workspace sees your posts to everyone and its own threads, never another's. The mode is fixed once the first workspace joins. Not for DMs, project channels, #general, Slack-linked channels or the Talk channel.

sharedoc channel share <channel> [flags]
Arguments
<channel>channel name or id
Flags
--email valuewho to invite (required)
--hubshare in hub mode (only before any workspace has joined)
Examples
sharedoc channel share acme --email [email protected]
sharedoc channel share support --email [email protected] --hub

--json{ invitation:{id,email,mode,expiresAt}, created, emailSent }

APIPOST /api/v1/conversations/:id/invitations

Invitations to shared channels sent to you, each with the workspaces you could connect it to. With a channel: as its host's owner or admin, who is invited and which workspaces are in it or waiting for their admin.

sharedoc channel invites [channel]
Arguments
[channel]a channel you host (omit for the invitations sent to you)
Examples
sharedoc channel invites
sharedoc channel invites acme --json

--json{ invitations:[{id,channel,host:{id,name},invitedBy,mode,expiresAt,workspaces:[{id,slug,name,role,direct,taken}]}] } · { mode, refusal, invitations:[{id,email,mode,expiresAt}], workspaces:[{id,name,status}] }

APIGET /api/v1/shared-channels/invitations · GET /api/v1/conversations/:id/invitations

Accept an invitation to a shared channel into one of your workspaces (--workspace; one you are a member of, not a guest in). It is private there unless --open. As an owner or admin there it connects at once; otherwise it waits for one of them (`channel approve`).

sharedoc channel accept <id> [flags]
Arguments
<id>invitation id from `channel invites`
Flags
--name valuethe channel's name in your workspace (default: the host's)
--openevery member of your workspace can find and join it (default: only people added)
Examples
sharedoc channel accept 3f1c… --workspace cockpit --name brightmotion
sharedoc channel accept 3f1c… --workspace cockpit --open

--json{ status:"connected"|"pending", workspace, name, conversation }

APIPOST /api/v1/shared-channels/invitations/:id/accept

As an owner or admin of --workspace: let in a shared channel a member accepted (or --decline it). With no channel, list what is waiting.

sharedoc channel approve [channel] [flags]
Arguments
[channel]its name here, or its id
Flags
--declineturn it down instead
Examples
sharedoc channel approve
sharedoc channel approve brightmotion --workspace cockpit

--json{ status, workspace, name?, conversation? }

APIGET /api/v1/shared-channels?workspace= · POST /api/v1/shared-channels/:id/approve|decline

End a shared channel for one workspace (owners and admins). Without --from: your workspace (--workspace) leaves a channel it joined. With --from: as the host, remove that workspace (its name or id, from `channel invites <channel>`). The side that goes loses the channel and its history, its own posts included; the host keeps everything, and a system post says who went. Requires --yes.

sharedoc channel disconnect <channel> [flags]
Arguments
<channel>channel name or id
Flags
--from valueas the host: the workspace to remove
--yesrequired
Examples
sharedoc channel disconnect brightmotion --workspace cockpit --yes
sharedoc channel disconnect acme --from Acme --yes

--json{ status:"left"|"removed", workspace }

APIPOST /api/v1/shared-channels/:id/leave · POST /api/v1/shared-channels/:id/remove

Read a channel or DM: posts newest-last with reply counts; every id is a thread id (`thread show` / `thread reply`).

sharedoc channel history <name> [flags]
Arguments
<name>channel name, project slug, or conversation id (a DM id from `dm`)
Flags
--since valueonly posts since (24h, 7d, ISO)
--project valueonly posts labelled with this project
--limit nposts per page · default 30
--cursor valuecontinue from a previous nextCursor
Examples
sharedoc channel history general
sharedoc channel history general --project sharedoc
sharedoc channel history homa-legal --since 24h --json

--json{ conversation, threads:[{id,projectSlug,root:{id,authorName,viaAgent,bodyMd,createdAt,reactions},replyCount,lastReplyAt,participants}], nextCursor }

APIGET /api/v1/conversations/:id/messages

Post in a channel (the same as `thread create --channel`). Read the channel first.

sharedoc channel post <name> [flags]
Arguments
<name>channel name, project slug, or conversation id
Flags
--body valuemarkdown body (§8 tags: @name, #channel, [[project/path.md]], [[thread:id]])
--body-file valueread the body from a file, or - for stdin
--attach valuefile(s) to upload to _inbox and tag in the body, comma-separated: photo.jpg,clip.mp4
--id valueclient-generated uuid so a retry cannot double-post
--project valuethe project this post is about (a workspace-visible one) — optional but encouraged; defaults to the connected folder you are in, `none` opts out
--draftsave it in the human's composer, unsent (it becomes a set of one if they have text there)
--variant valuepropose a version instead of posting — repeat for each; the human picks one
--variants-file valueversions as a JSON array [{ "label", "body" }] from a file, or - for stdin
--note valuewith versions: what you read and how they differ
--at valueschedule it instead: ISO, 'tomorrow 9:00', 'mon 9:00' (this machine's zone)
--in valueschedule it this far ahead: 90m, 2h, 1d
--notifywith a draft or versions: also notify the human (push)
Examples
sharedoc channel post general --body 'Deployed v0.10 — notes in [[ops/deploys.md]]' --project sharedoc --as-agent claude-code --as-model opus-5
sharedoc channel post general --body 'The whiteboard from today' --attach ./whiteboard.jpg

--json{ thread, message, references, unresolved, created }

APIPOST /api/v1/conversations/:id/messages

Message one or more people directly (opens the DM once, reuses it after), or `me` for a note to self. Without a body: just open it and print its id. An agent takes DMs only from whoever controls it unless they set `agents dms <agent> workspace`; anyone else is refused with a 403 naming who it takes them from.

sharedoc dm <user...> [flags]
Arguments
<user…>email, display name, or user id — several for a group DM, `me` for a note to self
Flags
--body valuemarkdown body (or --body-file / stdin)
--body-file valueread the body from a file, or - for stdin
--attach valuefile(s) to upload to _inbox and tag in the body, comma-separated: photo.jpg,clip.mp4
--id valueclient-generated uuid for the message
--project valuethe project this message is about — defaults to the connected folder you are in, `none` opts out
--draftsave it in the human's composer, unsent (it becomes a set of one if they have text there)
--variant valuepropose a version instead of posting — repeat for each; the human picks one
--variants-file valueversions as a JSON array [{ "label", "body" }] from a file, or - for stdin
--note valuewith versions: what you read and how they differ
--at valueschedule it instead: ISO, 'tomorrow 9:00', 'mon 9:00' (this machine's zone)
--in valueschedule it this far ahead: 90m, 2h, 1d
--notifywith a draft or versions: also notify the human (push)
Examples
sharedoc dm [email protected] --body 'Can you look at [[homa-legal/contract-v3.md]] today?'
sharedoc dm me --body 'the staging token is in 1Password'
sharedoc dm emily --json

--json{ conversation, thread, message, references, unresolved, created }

APIPOST /api/v1/conversations {kind:dm} → POST /api/v1/conversations/:id/messages

Your drafts, the variant sets waiting for you to choose, scheduled and failed messages. Private: nobody else sees any of it.

sharedoc drafts list [flags]
Flags
--draftsonly composer drafts
--setsonly variant sets waiting to be chosen
--scheduledonly scheduled messages
--failedonly scheduled messages that could not be sent
--resolvedsets settled in the last 30 days and what was chosen
Examples
sharedoc drafts list
sharedoc drafts list --sets --json
sharedoc drafts list --resolved

--json{ drafts:[{id,status,conversationId,threadId,bodyMd,viaAgent,variantId,sendAt,error,target:{kind,name,members,threadSnippet}}], sets:[{id,status,note,viaAgent,proposedSendAt,chosenLabel,chosenEdited,messageId,variants:[{id,label,bodyMd}],target}] }

APIGET /api/v1/drafts?workspace=&status=

Draft a message for your human instead of posting it: one body goes into their composer (unless it has text in it), several become a set of versions they choose from. Nothing is sent.

sharedoc draft create [to...] [flags]
Arguments
[to…]a channel (`general`, `#general`, an id), or @people for a DM / group — omit with --thread
Flags
--thread valuea reply in this thread (channel or DM threads only)
--body valueone body (or --body-file / stdin)
--body-file valueread the body from a file, or - for stdin
--variant valueone version — repeat for each (labelled A, B, C)
--variants-file valuea JSON array [{ "label": "short", "body": "…" }, …] from a file, or - for stdin — use this for multi-line markdown
--note valuewith variants: what you read and how the versions differ
--at valueschedule it: ISO time, 'tomorrow 9:00', 'mon 9:00' (this machine's zone). With variants: the time you suggest
--in valueschedule it this far ahead: 90m, 2h, 1d
--project valuethe project a new post is about (as on `channel post`)
--notifyalso send the human a notification with push (for a long run they walked away from)
--wait valuewith variants: block until they choose (default 10m, 0 = no limit); exit 0 chosen · 4 dismissed · 7 still open
--id valueclient-generated uuid so a retry cannot create twice
Examples
sharedoc draft create sharedoc --thread 2b7df1a0-… --variants-file - --note "Colin asked when the deploy lands; B commits to a date" < replies.json
sharedoc draft create general --body 'Deployed v0.30 — notes in [[ops/deploys.md]]'
sharedoc draft create @colin --body 'Can we move standup?' --at 'mon 9:00'

--json{ draft, created } | { set, variants, busy, created } (--wait adds outcome)

APIPOST /api/v1/drafts

One draft, scheduled message or variant (a set id works too).

sharedoc draft show <id>
Arguments
<id>draft or set id from `drafts list`

--json{ draft, set }

APIGET /api/v1/drafts/:id

Change a draft's text or a variant's label, schedule, reschedule or unschedule it.

sharedoc draft edit <id> [flags]
Arguments
<id>draft id
Flags
--body valuethe new text (or --body-file)
--body-file valueread the new text from a file, or - for stdin
--label valuea variant's label
--at valueschedule / reschedule
--in valueschedule this far ahead
--unscheduleback into the composer (refused if it has text in it)
Examples
sharedoc draft edit 9c01a8b2-… --in 2h
sharedoc draft edit 9c01a8b2-… --unschedule

--json{ draft, set }

APIPATCH /api/v1/drafts/:id

Send a draft or scheduled message now, as a post by you. For a person at a terminal — agents: never send for the human.

sharedoc draft send <id>
Arguments
<id>draft id

--json{ thread, message, references, unresolved, created, draftId }

APIPOST /api/v1/drafts/:id/send

Throw a draft away. Requires --yes.

sharedoc draft delete <id> [flags]
Arguments
<id>draft id
Flags
--yesrequired

--json{ ok, deleted, set }

APIDELETE /api/v1/drafts/:id

A variant set: its versions, and — once the human has chosen — which one won and whether they edited it. --wait blocks until they decide.

sharedoc draft set show <set-id> [flags]
Arguments
<set-id>set id from `draft create` or `drafts list --sets`
Flags
--wait valueblock until it is chosen or dismissed (default 10m, 0 = no limit); exit 0 chosen · 4 dismissed/failed · 7 still open
Examples
sharedoc draft set show 51c0…
sharedoc draft set show 51c0… --wait 30m --json

--json{ set:{id,status,note,chosenVariantId,chosenLabel,chosenEdited,messageId,resolvedAt,variants}, variants }

APIGET /api/v1/draft-sets/:id?wait=

Close a variant set without sending anything. Requires --yes.

sharedoc draft set dismiss <set-id> [flags]
Arguments
<set-id>set id
Flags
--yesrequired

--json{ set }

APIPOST /api/v1/draft-sets/:id/dismiss

Add a version to an open set (six at most). The human sees it appear.

sharedoc draft variant add <set-id> [flags]
Arguments
<set-id>set id
Flags
--body valuethe text (or --body-file / stdin)
--body-file valueread it from a file, or - for stdin
--label valuewhat makes this one different: short, formal, with the numbers

--json{ variant, set }

APIPOST /api/v1/draft-sets/:id/variants

Take a version out of its set (a set left empty is dismissed). Requires --yes.

sharedoc draft variant rm <variant-id> [flags]
Arguments
<variant-id>variant id
Flags
--yesrequired

--json{ ok, deleted, set }

APIDELETE /api/v1/drafts/:id

A person's choice from a terminal: put a version in the composer (the default), --send it, or schedule it with --at / --in. Agents: never pick for the human.

sharedoc draft pick <variant-id> [flags]
Arguments
<variant-id>variant id from `draft set show`
Flags
--sendsend it now, as it is
--at valueschedule it
--in valueschedule it this far ahead

--json{ draft, set } | { thread, message, … }

APIPOST /api/v1/drafts/:id/use · POST …/send · PATCH …{sendAt}

Connect a Slack workspace to your account: opens Settings → Slack's Add to Slack in your browser (it must be signed in to share-doc as you) and waits until Slack hands over access. Choose which conversations come in on that page. Running it again for a connected workspace reconnects it and keeps its links.

sharedoc slack connect [flags]
Flags
--workspace valuethe share-doc workspace the Slack conversations go into (default: your first)
--no-browserprint the page to open instead of opening it
--timeout nseconds to wait · default 300
--dry-runprint the page and stop
Examples
sharedoc slack connect
sharedoc slack connect --workspace brightmotion

--json{ connection:{id,team:{id,name,icon},slackUser:{id,handle,avatar},scopes,status,links,…}, created }

APIGET /api/v1/slack/connections (then the browser: /settings/slack/connect → Slack → /api/v1/slack/oauth/callback)

Your Slack connections: the Slack workspace, your Slack handle, how many conversations are linked, status (ok; revoked: reconnect; disconnected), when the last event arrived. Never a token.

sharedoc slack list
Examples
sharedoc slack list
sharedoc slack list --json

--json{ connections:[{id,workspaceId,team:{id,name,icon},slackUser:{id,handle,avatar},scopes,status,readSync,autoLinkDms,lastEventAt,lastPostAt,lastError,links,createdAt,updatedAt}] }

APIGET /api/v1/slack/connections

How your Slack connections are doing: per connection its status, last event, last post, last error and switches; per linked conversation read only or two-way, health, how far its history has come in, its newest message and your Slack read cursor.

sharedoc slack status [connection]
Arguments
[connection]connection id or Slack workspace name (default: all of them)
Examples
sharedoc slack status
sharedoc slack status Brightmotion --json

--json{ connections:[{ connection:{…}, links:[{…as slack links}] }] }

APIGET /api/v1/slack/connections, then GET /api/v1/slack/connections/:id per connection

The files on a message that came from Slack (they stay in Slack; the hub fetches them for you, with the same access as the message). Without --index: list them, numbered from 1, with a `gone` flag for one Slack no longer serves. With --index n: download file n to --out, or to its own name in the working directory (refused if that exists, unless --force). Message ids and their slackFiles are on `thread show --json`.

sharedoc slack file <message-id> [flags]
Arguments
<message-id>a message id from `thread show --json`
Flags
--index ndownload this file (1-based, as listed)
--out valuewrite it here (overwrites); default: the file's name in the working directory
--forceoverwrite an existing file of that name in the working directory
--dry-runsay where it would go; download nothing
Examples
sharedoc slack file 5f0c…
sharedoc slack file 5f0c… --json
sharedoc slack file 5f0c… --index 1 --out /tmp/shot.png

--json{ files:[{id,name,mime,size,width,height,kind:image|video|pdf|file,url,thumbUrl,permalink,gone}] } | with --index: { path, bytes, name, mime }

APIGET /api/v1/slack/files?message=<id>, then GET /api/v1/slack/files/:id

People in a connected Slack workspace you can start a DM with (from share-doc's copy of its member list; bots and deactivated accounts left out). Matches the name, @handle or email; share-doc teammates are marked and listed first. Use a handle or the Slack id with `slack dm`.

sharedoc slack people [query] [flags]
Arguments
[query]part of a name, handle or email
Flags
--team valuewhich Slack workspace (name or id), when you have more than one
--limit nat most this many · default 50
Examples
sharedoc slack people colin
sharedoc slack people --team Brightmotion --json

--json{ connection, team, people:[{id,name,handle,avatar,teammate:{id,name}|null}] }

APIGET /api/v1/slack/connections/:id/people?q=

Start a Slack DM (one person) or group DM (two to eight) from share-doc, as you, and bring it in as a private share-doc conversation; one that is already linked just opens, one you removed comes back with its history. Nothing is sent to Slack until you post in it (`channel post <conversation-id>`).

sharedoc slack dm <person> [flags]
Arguments
<person>one or more: @handle, name, or Slack id (U…) — see `slack people`
Flags
--team valuewhich Slack workspace (name or id), when you have more than one
--dry-runresolve the people and stop
Examples
sharedoc slack dm @colin
sharedoc slack dm @colin @dana --json

--json{ change:existing|restored|added, conversationId, workspace, name, link }

APIPOST /api/v1/slack/connections/:id/dm {users}

Disconnect a Slack workspace: Slack revokes the token and share-doc deletes it, and the conversations it brought in are archived with their history (their composers close). Connecting the same Slack account again lets you tick them back in. Nothing changes in Slack. Requires --yes; --dry-run lists what would be archived. --forget then removes a disconnected connection from your list (its archived conversations stay).

sharedoc slack disconnect [connection] [flags]
Arguments
[connection]connection id or Slack workspace name (default: your only one)
Flags
--yesrequired
--forgetremove it from your list (disconnects it first if needed)
--dry-runlist what would be archived
Examples
sharedoc slack disconnect Brightmotion --dry-run
sharedoc slack disconnect Brightmotion --yes

--json{ connection, archived:[link], slackRevoked, slackError } | { forgotten }

APIDELETE /api/v1/slack/connections/:id [?forget=1]

The change log (who did what, when) from a cursor; --follow streams.

sharedoc events tail [flags]
Flags
--cursor nstart after this event id (default: last 50)
--kinds valuecomma-separated kinds, e.g. doc.updated,member.added
--followkeep streaming (SSE); output becomes one event per line (NDJSON with --json)

--json{ events:[{id,kind,workspace,project,actor,viaAgent,subject,payload,createdAt}], nextCursor }

APIGET /api/v1/events

Your notifications (mentions, DMs, replies in threads you follow, new threads on docs you follow). Unread only by default.

sharedoc unread [flags]
Flags
--allinclude what you have already read
--kind valueone kind · one of mention, dm, reply, thread, channel, invite, system, reminder
--watchprint what is unread, then keep streaming new ones (SSE) until Ctrl-C
Examples
sharedoc unread
sharedoc unread --watch --json

--json{ notifications:[{id,kind,title,snippet,threadId,conversationId,conversationName,projectSlug,docPath,actor,viaAgent,readAt,createdAt}], unread, mentions, nextCursor }

APIGET /api/v1/notifications

Mark notifications read by id.

sharedoc unread read <id...>
Arguments
<id…>ids from `unread`

--json{ read:n }

APIPOST /api/v1/notifications/read

Mark everything read (needs --yes), or everything in one conversation / before a time.

sharedoc unread ack [flags]
Flags
--conversation valueonly this channel / DM
--before valueonly notifications up to this ISO time
--yesrequired for a blanket ack

--json{ read:n }

APIPOST /api/v1/notifications/read

Show or change your notification preferences (spec §3.8): level, thread replies, doc follows, email timing, snooze, working hours.

sharedoc notify prefs [flags]
Flags
--set valuekey=value[,key=value] — keys: email browser pushAlways level threadReplies docFollows inboxDrops invites emailMode awayMinutes digestHour markRead snoozeUntil (ISO or null) hoursDays (every|weekdays) hoursStart hoursEnd (minutes from midnight) timezone (IANA)
Examples
sharedoc notify prefs
sharedoc notify prefs --set level=all,emailMode=never
sharedoc notify prefs --set snoozeUntil=2026-09-04T17:00:00Z
sharedoc notify prefs --set hoursDays=weekdays,hoursStart=540,hoursEnd=1080

--json{ prefs:{email,browser,pushAlways,level,threadReplies,docFollows,inboxDrops,invites,emailMode,awayMinutes,digestHour,markRead,snoozeUntil,hoursDays,hoursStart,hoursEnd,timezone} }

APIGET|PUT /api/v1/notify/prefs

Sync

Two-way sync a local directory with a project (long-running by default).

sharedoc connect <project> <dir> [flags]
Arguments
<project>project slug
<dir>local folder (empty folder = pulls everything). Dotfiles, node_modules, lockfiles, __pycache__/, build output at the top (dist/, build/, out/, target/, coverage/) and what the folder's .sharedocignore lists are never added; .gitignore is not read
Flags
--oncereconcile once and exit — pull remote, push local, print a summary
--resetmove the folder aside into .share-doc/backup-<ts>/ and re-seed from the hub (never deletes)
--strictoverlapping edits are a conflict, not a merge: nothing is applied, --once exits 4
--no-commentsdo not write <doc>.comments.md next to docs that have open threads (on by default; the sidecar is read-only and never pushed)
--forcego ahead on a folder the sync set already covers, or on a git repository root (pair docs/ instead — a root pair uploads README and source)
--yesfirst sync of a folder past 50 files or 20 MB: skip the warning, and send it at once even in a workspace with large files turned off (which otherwise holds it for approval)
--folder valuemap the local folder to this folder inside the project instead of the whole project (a shared folder's recipient can connect just that folder)
--sync-default valuewhat this connection syncs when no folder rule says otherwise (v0.40) · one of inherit, on, off · default inherit
--dry-runsay what would be mapped: no registration, no scan, no transfer
Examples
sharedoc connect homa-legal ~/deals/homa
sharedoc connect homa-legal ./homa --once --json
sharedoc connect homa-legal ./homa --once --strict   # exit 4 on a conflict
sharedoc connect homa-legal ./homa --reset

--json{ pulled, pushed, merged, conflicts } (--once only)

APIPOST /api/v1/connections (register) · GET …/policy · PUT /api/v1/files/content (push) · Hocuspocus + GET /api/v1/files (pull)

Add a folder ⇄ project pair, or channels to mirror, to this machine's sync set (~/.config/share-doc/sync.json).

sharedoc sync add [project] [dir] [flags]
Arguments
[project]project slug or name, any case (with <dir>)
[dir]local folder to keep in sync with it
Flags
--strictthis pair: overlapping edits are a conflict, not a merge
--folder valuemap the folder to this folder inside the project (default: the whole project)
--sync-default valuethe connection's default: inherit (use folder defaults), on (all unless excluded), off (none until selected) · one of inherit, on, off · default inherit
--forcepair a git repository root anyway (refused otherwise — pair its docs/ folder)
--yesfirst sync past 50 files or 20 MB: skip the warning, and send it at once even in a workspace with large files turned off (which otherwise holds it for approval)
--channel valuemirror these channels, comma-separated: general,ops
--channelsmirror every channel you can see (project channels included)
--dmsmirror DMs and group DMs too (off unless asked)
--dir valueroot folder for the channel mirror · default ~/sharedoc/chat
--history valuehow far back to backfill: 30d, 12h, all · default 30d
--workspace valuethe workspace this pair or the mirror runs in — stored in sync.json; needed when the saved login is one token that sees several workspaces
Examples
sharedoc sync add homa-legal ~/deals/homa
sharedoc sync add ops ~/ops --strict
sharedoc sync add --channel general,homa-legal
sharedoc sync add --channels --dms --dir ~/chat
sharedoc sync add --channels --workspace brightmotion   # a login with no workspace on it

--json{ pair:{project,dir,strict,workspace?}, path, firstPush?:{files,bytes,ignored} } | { channels:{dir,include,exclude,dms,history,workspace?}, path }

Remove a folder pair (<dir>), one channel (#name) or the whole channel mirror (channels) from the set, and disconnect the pair's connection on the hub. Never touches files — local or on the server: the way out for a paired folder that went `missing` when you meant to stop syncing it.

sharedoc sync rm <what>
Arguments
<what><dir> | #channel | channels
Examples
sharedoc sync rm ~/deals/homa
sharedoc sync rm #random
sharedoc sync rm channels

--json{ removed, path }

A paired folder is gone (`missing` in `sync status`) and you did mean to delete everything in it: archive on the server every file its connection syncs, as one batch (`sync undo` reopens it), then unpair it as `sync rm` does. The running sync never reads a vanished folder as deletes. Requires --yes; refuses a folder that is still there.

sharedoc sync archive-missing <dir> [flags]
Arguments
<dir>the paired folder, as in `sync list`
Flags
--yesrequired
--dry-runlist what it would archive
Examples
sharedoc sync archive-missing ~/old-notes --dry-run
sharedoc sync archive-missing ~/old-notes --yes

--json{ project, dir, batch, archived:[path], failed:[{path,error}], disconnect } | { dryRun:true, project, dir, archive:[path] }

The sync set as configured: pairs, channel rules, resolved paths.

sharedoc sync list

--json{ projects:[{project,dir,strict}], channels:{dir,include,exclude,dms,history}|null, path }

The set as it is right now: per pair state/files/last push/pull/error, the channel mirror, whether `sync run` is up (and the service manager's view: launchd on macOS, systemd on Linux).

sharedoc sync status

--json{ running:{pid,mode,since}|null, service:{manager,installed,loaded,running,pid}|null, pairs:[{project,dir,state,files,lastPushAt,lastPullAt,lastError,counts,held,heldDeletes:{batch,since,count,paths}|null,approve}], channels:{dir,state,conversations,threads,lastEventAt,lastError}|null } (state `missing`: the folder is gone — nothing syncs or is archived until it is back)

THE process: every pair and the channel mirror, live, until Ctrl-C (one lock per machine). --once reconciles everything and exits.

sharedoc sync run [flags]
Flags
--oncereconcile every pair, backfill channels, print one table, exit (4 if a strict pair conflicted, 6 if a pair could not run)
--stricttreat every pair as strict for this run
--serviceservice mode: under launchd, timestamps and a log at ~/Library/Logs/sharedoc/sync.log (rotated at 5 MB); under systemd, plain lines to the journal
Examples
sharedoc sync run
sharedoc sync run --once --json

--json{ pairs:[{project,dir,pulled,pushed,merged,conflicts,error}], channels:{dir,conversations,threads,error}|null } (--once only)

Run `sync run` as a user service — launchd on macOS, systemd --user on Linux — that starts at login and is restarted if it dies. Idempotent. Waits (up to 20 s) for every pair and the channel mirror to report live or an error before printing; `settled: false` means check `sync status` in a moment.

sharedoc sync install
Examples
sharedoc sync install
sharedoc sync status

--json{ unit, program:{argv,via}, service:{manager,installed,loaded,running,pid}, log, journal, linger, settled, pairs:[{project,dir,state,lastError}], channels:{dir,state,conversations,lastError}|null, gated:[dir] }

Stop and remove the user service (launchd / systemd). The set and every folder stay.

sharedoc sync uninstall

--json{ removed, unit }

Tail the service log: ~/Library/Logs/sharedoc/sync.log on macOS, the journal (journalctl --user -u sharedoc-sync) on Linux.

sharedoc sync logs [flags]
Flags
--followkeep following, like tail -f
--lines nhow many lines · default 200
--which valuemacOS: sync (ours) or launchd (what launchd captured before the logger was up); the journal holds both on Linux · one of sync, launchd
Examples
sharedoc sync logs
sharedoc sync logs --follow

The hub's inventory of machine connections ("connections" in Settings): status, last seen vs last synced, whether settings are applied. Owners see their local paths; project managers a coarse row per machine. `sync list` is this machine's local set.

sharedoc sync endpoints [flags]
Flags
--project valueone project's connections (default: the connected folder's project, else every connection you own)
--allevery connection you own across projects
Examples
sharedoc sync endpoints --project homa-legal
sharedoc sync endpoints --all --json

--json{ project?, canManage?, syncRevision?, connections:[{id,project,device,owner,own,root,mode,defaultMode,state,status,live,lastSeenAt,lastSuccessAt,configRevision,appliedRevision,applied,dir?,host?,lastError?,rules?,thisMachine}] }

APIGET /api/v1/connections?project=

Pause a connection: transfers stop, folder rules are kept, local edits wait. The running sync notices within 30 s.

sharedoc sync pause [connection-id] [flags]
Arguments
[connection-id]from `sync endpoints` (default: the connected folder's own connection)
Flags
--dry-runpreview

--json{ connection, before }

APIPATCH /api/v1/connections/:id {state:paused}

Approve what a connection is waiting on. --deletes: the files its folder deleted in bulk (more than 25 at once), held rather than archived on the server — this archives them. Otherwise, in a workspace with large files turned off (workspace settings → Storage; on, the default, nothing waits): its big first push (a `sync add` past 50 files or 20 MB without --yes), or with --file / --held the big files it holds over the workspace's ask-first size. Only on the person's say-so — `sync status` and the log say what is waiting.

sharedoc sync approve [connection-id] [flags]
Arguments
[connection-id]from `sync endpoints`, or the paired folder's path (default: the connected folder's own connection)
Flags
--deletesthe bulk delete it is holding
--file valuea held file's project path (repeatable)
--heldevery file it is holding
--dry-runpreview

--json{ connection, before, approved } | --deletes: { connection, before, decision, heldDeletes:{batch,since,count,paths} }

APIPOST /api/v1/connections/:id/approve

Keep the files a connection's folder deleted in bulk and is holding: nothing is archived, and the folder pulls them back from the server. (Putting the files back does the same.)

sharedoc sync discard [connection-id] [flags]
Arguments
[connection-id]from `sync endpoints`, or the paired folder's path
Flags
--deletesrequired: the held deletes are what is discarded
--dry-runpreview

--json{ connection, before, decision, heldDeletes }

APIPOST /api/v1/connections/:id/discard

Reopen every file one burst of a folder's deletes archived on the server, in one call — by default the newest burst with anything still archived (last 30 days). --list shows the bursts. The folder, if still paired, pulls them back.

sharedoc sync undo [connection-id] [flags]
Arguments
[connection-id]from `sync endpoints`, or the paired folder's path
Flags
--batch valuewhich burst (from --list, or the log's "undo with" line)
--listthe bursts, newest first
--dry-runlist what it would reopen
Examples
sharedoc sync undo 3f2a9c1e
sharedoc sync undo ~/kid-learn/docs --list

--json{ batch, reopened:[path], skipped:[{path,reason}] } | --list: { connection, batches:[{batch,count,stillArchived,firstAt,lastAt}] }

APIPOST /api/v1/connections/:id/undo · GET /api/v1/connections/:id/archives

Resume a paused connection: it re-reads its settings and reconciles.

sharedoc sync resume [connection-id] [flags]
Arguments
[connection-id]from `sync endpoints`
Flags
--dry-runpreview

--json{ connection, before }

APIPATCH /api/v1/connections/:id {state:active}

Disconnect a connection on the hub: its credential is revoked and any running sync stops; local files, cloud files and membership stay. Requires --yes. Reconnecting is a new `sync add` / `connect`.

sharedoc sync disconnect [connection-id] [flags]
Arguments
[connection-id]from `sync endpoints`
Flags
--yesrequired
--dry-runpreview what is kept

--json{ connection, before } | { dryRun:true, before, after, keeps }

APIPOST /api/v1/connections/:id/disconnect

What one connection syncs, folder by folder: list the tree with each folder's rule, effective state and why; `set <path> --mode on|off|inherit` changes a rule (a narrower On beats an ancestor Off); `--default` sets the connection default; `--clear-children <path>` removes overrides under a folder.

sharedoc sync folders [connection-id] [set <path>] [flags]
Arguments
[connection-id]from `sync endpoints` (default: the connected folder's)
[set <path>]the folder to change (project-relative)
Flags
--mode valuewith `set`: on | off | inherit (inherit removes the rule) · one of on, off, inherit
--default valuethe connection default: inherit = use folder defaults · on = all folders unless excluded · off = no folders until selected · one of inherit, on, off
--clear-children valueremove every explicit rule strictly under this folder
--if-match valueconfig revision the change is based on (a stale revision is a conflict, exit 4)
--dry-runpreview the impact (files, bytes) without saving
Examples
sharedoc sync folders 1a2b3c4d
sharedoc sync folders 1a2b3c4d set media --mode off
sharedoc sync folders 1a2b3c4d set media/previews --mode on
sharedoc sync folders 1a2b3c4d --default off --dry-run

--json{ connection, policy, rules, folders:[{path,rule,projectDefault,effective:{selected,source,at},mixed,counts}], impact:{selected,selectedBytes,excluded,excludedBytes,total,totalBytes} }

APIGET | PUT /api/v1/connections/:id/folder-rules · PATCH /api/v1/connections/:id

A project's folder sync defaults (what connections that use folder defaults do with each folder, future folders included). Without --sync-default: list. The root is on|off; other folders may inherit.

sharedoc folders settings [path] [flags]
Arguments
[path]project-relative folder ("" or omitted = the root)
Flags
--project valueproject slug (default: the connected folder's)
--sync-default valueset this folder's default · one of on, off, inherit
--dry-runpreview
Examples
sharedoc folders settings --project homa-legal
sharedoc folders settings media --project homa-legal --sync-default off

--json{ project, canManage, syncRevision, defaults:{path:mode}, folders:[…], impact }

APIGET | PUT /api/v1/projects/:slug/sync-defaults

Your devices (one per ShareDoc installation) with their connections, grouped by machine.

sharedoc devices list

--json{ devices:[{id,label,platform,clientVersion,lastSeenAt,connections:[…]}], unmanaged:[…] }

APIGET /api/v1/devices

Rename a device's label; its identity and connections are unchanged.

sharedoc devices rename <device-id> <label>
Arguments
<device-id>from `devices list`
<label>new label

--json{ device:{id,label} }

APIPATCH /api/v1/devices/:id

Disconnect every connection a device holds for you (never erases its disk). Requires --yes; --dry-run lists the affected projects first.

sharedoc devices disconnect <device-id> [flags]
Arguments
<device-id>from `devices list`
Flags
--yesrequired
--dry-runlist what would be disconnected

--json{ device, disconnected:[{id,project,dir}], count }

APIPOST /api/v1/devices/:id/disconnect

Self-description

This help. --json dumps the full command table for agents.

sharedoc help [flags]
Flags
--jsonmachine-readable

The live data contract: entities, fields, enums, and the command table.

sharedoc schema [entity]
Arguments
[entity]one entity, e.g. events, documents, tokens

APIGET /api/v1/schema

Print the CLI version.

sharedoc version

Turn usage reporting on or off, or show whether it is on. After each command the CLI tells the server it is logged in to the command's name, the names of the flags given (never their values or any arguments), the exit code, the duration, the CLI version, OS and architecture. Nothing about files, paths, projects or messages. Also off with SHAREDOC_TELEMETRY=0 or DO_NOT_TRACK=1.

sharedoc telemetry [on|off]
Arguments
[on|off]turn it on or off; omit to show the current state
Examples
sharedoc telemetry
sharedoc telemetry off
SHAREDOC_TELEMETRY=0 sharedoc files list acme

--json{ enabled, decidedBy, file? }

Update this CLI in place: runs the installer from the server it targets, then restarts the sync service.

sharedoc update [flags]
Flags
--no-restartleave the sync service (launchd / systemd) running the old bundle
Examples
sharedoc update
sharedoc update --dry-run

--json{ server, installer, bundle, running?, changed, before:{path,exists,size,sha256}, after:{…}, service:{manager,installed,loaded,running,pid,restarted,error}|null }