tinycld CLI reference

The complete surface of the tinycld binary. For a task-oriented introduction — downloading it, logging in, and typical usage — see Command line tool.

This page documents the user-facing tinycld binary. It is unrelated to tinycld-pkg, the per-member developer tool that runs biome, tsc, and vitest, and to @tinycld/bootstrap, the scaffolder.

Global flags

Available on every command:

FlagDefaultMeaning
--output table|json|csvtableOutput format
--jsonShorthand for --output json
--context <name>current contextRun against a different saved server
--quietSuppress informational messages
--no-colorDisable colored output
--yesAnswer yes to prompts and skip interactive input
-h, --helpHelp for the command
-v, --versionVersion (root command only)

Without a TTY the tool disables color and never blocks on input, so no extra flags are needed to make it CI-safe.

Table output is column-aligned with a header row; CSV writes a header row then records; JSON is indented. JSON is a separate payload from the table, not a serialization of it — some commands deliberately expose more (or differently-named) fields than they print. Verify a command’s actual JSON before depending on a key.

Exit codes

0 on success, 1 on any error. There is no differentiated exit-code scheme — do not branch on specific non-zero values.

Errors are written to stderr as Error: <message>. Server-side failures render as server error (HTTP <status>): <server message>.

Core commands

Present in every build regardless of which packages the server has.

CommandArgumentsNotes
auth login<host>Device-grant login; saves a context and credentials
auth statusCurrent context, origin, user, and granted scopes
auth logoutRevokes the grant server-side, then clears the credential
context listSaved contexts; the current one is marked
context use<name>Switch the current context
context add<name> <origin>Save a context without logging in
context remove<name>Remove a context and its stored credentials
search<query>Federated search across installed packages
versionVersion, Go version, OS, and architecture
completionbash|zsh|fish|powershellShell completion script
help[command]Help for any command

search accepts --pkg <slug> (repeatable), --not <terms>, and --limit <n>. It understands the same query grammar as the in-app palette: pkg: prefixes scope the search and a leading hyphen excludes a term. Quote the query so the shell passes it as one argument; a query beginning with a hyphen needs a -- separator first.

Counts and partial-result warnings go to stderr, keeping stdout a clean document under --json.

A bare host in auth login or context add is normalized to https://, except localhost, 127.0.0.1, and ::1, which get http://.

Drive

CommandArgumentsFlags
drive ls[path]-l/--long, -a/--all
drive tree[path]--depth <n> (3), -a/--all
drive search<query>--limit, --offset
drive cat<path>
drive get<path> [dest]Folders download as a zip; asks before overwriting a local file (--yes skips)
drive put<local> [dest]-p/--parents, -r/--recursive
drive mkdir<path>-p/--parents
drive mv<src> <dst>
drive cp<src> <dst>
drive rm<path>--permanent (default is to trash)
drive trashLists trashed items
drive restore<path|id:…>
drive share<path>--user (repeatable), --role viewer|editor, --message
drive link create<path>--role viewer|commentor|editor, --expires <RFC3339>
drive link list<path>
drive link revoke<link-id>
drive versions<path>--restore <n>, --snapshot, --label
drive export<path> [dest]--to pdf|svg (pdf); asks before overwriting (--yes skips)
drive usageStorage usage

Every path argument also accepts id:<record-id> to bypass path resolution.

Mail

CommandArgumentsFlags
mail search[query]--mailbox, --limit, --offset, --from, --to, --subject, --has-words, --date-after, --date-before, --folder, --has-attachment, --not
mail list--folder (inbox), --mailbox, --limit (25), --page (1)
mail read<message|thread>--html, --raw, --no-mark
mail attachments<message>
mail download<message>--attachment N|all, --out <dir>
mail send--to, --cc, --bcc (repeatable), --subject, --body, --body-file, --attach, --mailbox, --from
mail reply<message|thread>--all, --body, --body-file, --attach, --from
mail draftSame flags as send, plus --message-id to update an existing draft
mail draft send<message|thread>
mail labelsLabels available to you
mail label add<thread> <label>
mail label remove<thread> <label>
mail mailboxesAddresses you can read and send as
mail statusPer-mailbox unread and folder counts
mail archive<thread…>
mail trash<thread…>
mail spam<thread…>
mail star<thread…>
mail unstar<thread…>
mail move<folder> <thread…>Folder is one of inbox, sent, drafts, trash, spam, archive
mail markread|unread <thread…>

--body-file - reads the message body from stdin. Folder, label, star, and read-state changes write the caller’s own per-user thread state, so they never affect other members of a shared mailbox.

Boards

Present when the Boards package is installed. The column group also answers to columns, and card to cards. Every view command aliases show, every remove aliases rm and delete, and list aliases ls.

CommandArgumentsFlags
boards list-a/--all (include archived)
boards view<board>-a/--all, --sprint <n|active|next|id|backlog>
boards archive<board>--unset to restore
boards remove<board>Deletes the board and everything on it
boards export<board>--format csv|json (csv), --out <file>; asks before overwriting (--yes skips)
boards import<file.json>--name, --hooks (write history and send notifications for imported cards). Accepts a Trello export or a board export
boards column show-b/--board (required)
boards column add<name>-b/--board (required), --index
boards column rename<list> <name>-b/--board (required)
boards column move<list> <index>-b/--board (required); zero-based, counted after the move
boards column category<list> backlog|todo|in_progress|done|canceled-b/--board (required)
boards column done<list>-b/--board (required), --unset. Shorthand for category <list> done
boards column wip<list> <limit>-b/--board (required); 0–999, 0 clears. A warning, never enforced
boards column remove<list>-b/--board (required) — deletes its cards
boards card view<id>Checklist, comments, links, and reactions
boards card add<title>-b/--board and -l/--list (both required), --description, --due, --start, --index, --reporter, --priority urgent|high|medium|low|none, --estimate <points>, --parent <card>, --sprint <n|active|next|id>
boards card edit<id>--title, --description, --due, --clear-due, --start, --clear-start, --reporter, --clear-reporter, --priority, --estimate (0 clears), --parent, --clear-parent, --sprint, --clear-sprint
boards card move<id>-l/--list, --index; -b/--board for a cross-board move, with --family move|unlink for its sub-tasks and --epic move|unlink for its epic
boards card copy<id>--title; copies the checklist, assignees, and labels, not attachments
boards card archive<id>--unset to restore
boards card remove<id>Permanent
boards card link<id> <other>Exactly one of --blocks, --related, --duplicates
boards card unlink<id> <other>Removes every link between the two
boards card link-pr<key> <url>Link a GitHub pull request to a card
boards card unlink-pr<key> <url>
boards card react<comment-id> <emoji>Emoji or a shorthand: thumbs_up, heart, rocket, eyes, …
boards card unreact<comment-id> <emoji>
boards sprint list<board>
boards sprint view<sprint>-b/--board
boards sprint create-b/--board (required), --name, --goal, --start, --end
boards sprint edit<sprint>-b/--board, --name, --goal, --start, --clear-start, --end, --clear-end
boards sprint start<sprint>-b/--board, --name, --goal, --start (today), --end (the board’s sprint length)
boards sprint complete<sprint>-b/--board, --unfinished next|new|backlog (required when cards remain), --next <sprint>
boards sprint delete<sprint>-b/--board; its cards return to the backlog. Aliases rm, remove
boards github list<board>Repositories attached to the board. Attaching one is done in the app

A <board> argument resolves by id, key, or name; a <sprint> by its number within the board, active, next, or id; a card <id> by id or key (OTTER-123). Dates are YYYY-MM-DD, or "YYYY-MM-DD HH:MM" for a due time. Mutating commands print nothing in table format — use --json or --output csv to get the written row back. Board membership and share links are read-only to the CLI — manage them in the app.

Contacts

Group alias: contact.

CommandArgumentsFlags
contacts list--favorites, --trashed, --limit
contacts search<query>--limit
contacts show<id>Alias: view
contacts add--first, --last, --email, --phone, --company, --title, --notes, --favorite. Aliases: new, create
contacts edit<id>Same field flags, plus --restore. Aliases: update, set
contacts rm<id>--permanent. Aliases: delete, remove
contacts export--out <file>; writes vCard to stdout by default. Asks before overwriting (--yes skips)
contacts import<file.vcf>Upserts on the vCard UID

rm is a soft delete. list --trashed finds trashed contacts and edit --restore brings one back; --permanent is the only hard delete.

Calendar

Group alias: cal.

CommandArgumentsFlags
calendar agenda--days (7), --calendar. Alias: next
calendar listAliases: ls, calendars. Includes a ROLE column, also present in --json as role (with kind)
calendar events--from, --to, --calendar
calendar show<id>Alias: view
calendar add--calendar, --title, --start, --end, --all-day, --location, --description, --guest (repeatable), --recurrence daily|weekly|monthly|yearly, --reminder <minutes>, --busy busy|free. Aliases: new, create
calendar rm<id>Aliases: delete, remove
calendar rsvp<id> yes|no|maybeRefused if you are not on the guest list
calendar export--calendar, --out <file>; asks before overwriting (--yes skips)
calendar import<file.ics>--calendar

Reading a calendar requires membership in any role; writing requires owner or editor. calendar list’s ROLE column is the only advance indication of which you have.

Text and Calc

Each contributes one command. Both alias comments to comment.

CommandArgumentsFlags
text comments<path>--add, --reply-to, --quote, --resolve, --reopen, --all
calc comments<path>--add, --reply-to, --cell, --sheet, --resolve, --reopen, --all

--all includes resolved threads. --cell uses one-based A1 notation, and a new spreadsheet comment also needs --sheet <id> (the sheet’s id, e.g. sheet1) — a reply inherits its thread’s anchor and refuses both flags. Documents and workbooks are Drive items, so tinycld drive handles creating, listing, downloading, and deleting them; their bodies are collaborative edit operations with no safe shell representation, which is why only comments are exposed here.

Authentication and scopes

Login uses the OAuth 2.1 Device Authorization Grant (RFC 8628) against the first-party public client tinycld-cli. The CLI requests a device code, prints the user code, opens the browser for approval, and polls for the token.

Access tokens refresh automatically when a request returns 401. A revoked or expired refresh token surfaces as authentication expired — run tinycld auth login. Grants are database rows checked per request, so revoking one from Settings → Personal → Connected apps takes effect on the terminal’s next call.

Scopes requested at login. Each package declares its own from its Go server (oauth.RegisterPackage in server/oauth_scopes.go), naming the collections and routes the scope governs and the label the consent screen shows:

ScopeGrants
profileIdentity for auth status
mail:read, mail:sendRead mail; send and reply
drive:read, drive:writeRead files; upload, move, share
contacts:read, contacts:writeRead contacts; add and edit
calendar:read, calendar:writeRead calendars; add and edit events
boards:read, boards:writeRead boards; edit lists and cards
text:read, text:writeRead document comments; post them
calc:read, calc:writeRead spreadsheet comments; post them

Only the scopes for installed packages appear in a given build.

How a package contributes commands

A package adds a command group by declaring a cli block in its manifest:

cli: {
    package: 'cli',                            // directory, relative to the member root
    module: 'tinycld.org/packages/mail/cli',   // Go module path
},

and exposing a single entry point from that Go module:

func Register(root *cobra.Command, c *client.Client)

There is deliberately no command list in the manifest — Cobra owns the command tree and generates --help from it. There is no scope list either: the package’s Go server registers its OAuth scopes with core (oauth.RegisterPackage), and the CLI’s device-flow login requests whatever the installed packages registered.

The generator (pnpm run packages:generate) emits cli/cli_extensions.go with the imports and Register calls, a cli/go.work covering each member’s cli/ directory, and cli/search_slugs.go listing searchable packages. All three are generated and gitignored — see Generated files.

A package whose cli.package directory is absent is skipped silently. The CLI module must not import tinycld.org/core; the few shared structs it needs are mirrored locally, with a comment naming the source.

Because the command tree is compiled, a server without a package has no trace of its commands — there is no runtime gating to configure.

Build and distribution

Built with Go and Cobra. To build locally, run the generator first:

cd tinycld && pnpm run packages:generate
cd cli && go build .

A local build reports its version as dev; releases stamp it via -ldflags "-X main.version=<v>".

Servers cross-compile the binary as a step of the package-build pipeline, with CGO_ENABLED=0 and -trimpath -ldflags "-s -w", for five targets: darwin/arm64, darwin/amd64, linux/amd64, linux/arm64, and windows/amd64. Artifacts land in <appDir>/cli-dist/. The step is best-effort: a target that fails to compile is logged and skipped, and never fails the package install.

There is no package registry, Homebrew tap, or auto-updater. The server distributes its own binary over two public endpoints — GET /api/cli/downloads lists the available platforms, and GET /api/cli/download/{platform} streams one. Both are public because a browser download link cannot carry an authorization header; {platform} is resolved by lookup against a fixed target list, so it cannot be used to traverse the filesystem.