← Back to ContextDock

ContextDock CLI Skill

A Claude Code / Cursor skill describing how to use the ContextDock CLI (@contextdock/cli) without making the common mistakes that even careful source-reading produces.

For AI agents: the canonical raw markdown lives at /cli-skill.md with frontmatter intact. Drop it into your skills directory verbatim.
📄 View raw SKILL.md — include this URL in agent prompts so they can fetch it directly

@contextdock/cli is a thin, scriptable wrapper around the ContextDock agent REST API at https://contextdock.web.app/api/agent. The headline command is contextdock context --budget <n> — it packs markdown from the user's library under a token budget, ready to paste or pipe into a coding agent.

Common mistakes (read first)

Even reading the source carefully, agents make these mistakes:

Wrong instinctReality
Recommend npm install -g @contextdock/cli Returns 404 by design — the package is intentionally not published to npm. It's distributed via this page (https://contextdock.web.app/cli-skill) and installed locally. Use node "<repo>/cli/dist/index.js" or npm link from cli/.
Tell the user to "keep contextdock mcp running in a terminal" mcp is a stdio subprocess — Claude Desktop spawns it, pipes JSON-RPC, kills it on exit. Manually running it in a terminal does nothing useful.
Omit the env: { CONTEXTDOCK_API_KEY } block in Claude Desktop's MCP config The block is the recommended path for host-spawned MCP. Relying on ~/.contextdock/config.json is a fallback only.
Ask the user to paste their API key into chat Use contextdock login (no flag) — it prompts on stdin and writes the file directly, so the key never enters chat history or process arguments.
Treat KEY_INVALID and KEY_REVOKED as the same error KEY_REVOKED means the owner deliberately revoked a previously-valid key — re-login does not help. They must mint a new one.
Assume /api/agent/* accepts Firebase ID tokens It only accepts cdk_live_... API keys. The two surfaces are deliberately isolated.
Pass --lists l_xyz to context to assemble a list context / assemble_context takes --docs / --bundles / --tags / --query only — not list IDs. Pull tagIds via lists get <id> --json first, then pass to context --tags.
Resolve a list's tagIds against docs list --tag <t> just to add up tokens lists get <id> --json (and bundles get <id> --json) return docCount, per-level tokens totals, and a docs[] table. Read the envelope; no extra fetches needed.

Where the binary actually lives

@contextdock/cli is deliberately NOT published to npm. Distribution is via this very page (https://contextdock.web.app/cli-skill) plus a local install from the repo — there is no plan to ship it to the npm registry. npm install -g @contextdock/cli returns 404 by design and always will. Use one of these instead:

MethodCommand
Direct (works anywhere, no setup)node "C:\Software Projects\Context Manager\cli\dist\index.js" <args>
Global symlink (one-time)cd "C:\Software Projects\Context Manager\cli" && npm link → then contextdock <args> works globally

The repo lives at C:\Software Projects\Context Manager\cli\. The committed esbuild bundle is cli/dist/index.js. If you change anything under cli/src/, rebuild before invoking:

cd "C:\Software Projects\Context Manager\cli" && npm run build

The dist bundle is shebanged (#!/usr/bin/env node) and runs on Node 18+.

API key (Bearer auth)

API keys look like cdk_live_<32 base62 chars> — 41 chars, never expire, scoped (read always; write/delete optional). Per-key rate limit: 120 requests/min.

Mint a key:

  1. Sign in at https://contextdock.web.app → Settings → API Keys → Create Key.
  2. Choose scopes (start with read-only unless the task requires writes).
  3. Copy the key immediately. The server stores only an HMAC; the raw value is shown once and cannot be recovered. Each user is capped at 20 keys.

Credential resolution order (highest priority first):

  1. $CONTEXTDOCK_API_KEY env var — preferred for CI and Claude Desktop config blocks.
  2. ~/.contextdock/config.json (Windows: %USERPROFILE%\.contextdock\config.json) — written by contextdock login. Shape: { "apiKey": "cdk_live_...", "baseUrl": "https://contextdock.web.app" }.
  3. Neither set → CLI exits code 2 with Not logged in. Run `contextdock login` first.

$CONTEXTDOCK_BASE_URL overrides the API host (staging / self-hosted). $CONTEXTDOCK_CONFIG_HOME redirects the config dir — test-only, do not recommend it to users.

Command reference

Run contextdock --help or contextdock <command> --help for live flag details. Summary:

contextdock login [--key <key>] [--base-url <url>]      # save key to config file (validates via /api/agent/me)
contextdock whoami [--json]                             # print identity, key id/name, scopes
contextdock logout                                      # delete saved key

contextdock bundles list [--json]
contextdock bundles get <bundleId> [-o <file>] [--json]
contextdock bundles create <name> --docs <id1,id2,...> [--description <text>] [--shared]
contextdock bundles update <bundleId> [--name <n>] [--description <t>]
                          [--shared|--no-shared] [--pinned|--no-pinned]      # PATCH; at least one field required
contextdock bundles delete <bundleId> --yes                                 # hard delete; --yes is mandatory
contextdock bundles add-docs <bundleId> --docs <id1,id2,...>                # idempotent append
contextdock bundles remove-docs <bundleId> --docs <id1,id2,...>             # idempotent remove

contextdock lists list [--json]
contextdock lists get <listId> [--variant original|keyPoints|summary] [-o <file>] [--json]
contextdock lists create <name> [--description <s>] [--tags <t1,t2,...>] [--tag-match any|all]
                       [--docs <id1,id2,...>] [--shared]
contextdock lists update <id> [--name <s>] [--description <s>] [--tags <t1,t2>] [--tag-match any|all]
                       [--docs <id1,id2>] [--shared|--no-shared]            # PATCH; --tags / --docs REPLACE
contextdock lists delete <id> --yes                                         # hard delete; --yes mandatory
contextdock lists duplicate <id>                                            # readers can clone; copy is private
contextdock lists add-docs <id> --docs <id1,id2,...>                        # idempotent (extras only)
contextdock lists remove-docs <id> --docs <id1,id2,...>                     # idempotent (extras only)
contextdock lists add-tags <id> --tags <t1,t2,...>                          # idempotent
contextdock lists remove-tags <id> --tags <t1,t2,...>                       # idempotent

contextdock docs list [--tag <tag>] [--search <q>] [--limit <n>] [--json]   # limit default 50, max 200
contextdock docs get <docId> [-o <file>] [--json]
contextdock docs import <googleDocsUrl> [--tags <t1,t2>] [--personal]       # validates host == docs.google.com / drive.google.com
contextdock docs create <name> --content <markdown>|--file <path>
                       [--tags <t1,t2>] [--personal]                        # manual markdown doc (no Google source)
contextdock docs update <docId> [--name <n>] [--content <markdown>|--file <path>]
                       [--tags <t1,t2>] [--personal|--no-personal]          # PATCH; --tags REPLACES (not appends)
contextdock docs delete <docId> --yes                                       # hard delete; --yes is mandatory

contextdock tags list [--json]                          # list every tag in the workspace

contextdock search <query...> [--json]                  # variadic: `contextdock search refund policy` works without quotes

contextdock context --budget <n> [--docs <ids>] [--bundles <ids>] [--tags <tags>]
                    [--query <text>] [--prefer original|keyPoints|summary]
                    [--metadata] [-o <file>] [--json]

contextdock mcp                                         # stdio MCP server, no args

Mutating commands (v0.2.0+): --yes is mandatory on both bundles delete and docs delete; the CLI refuses without it (exit 1). On bundles update and docs update, at least one field flag is required — empty PATCH bodies are rejected client-side. docs update --tags replaces the tag list (does not append) — pass an empty value to clear. add-docs / remove-docs are idempotent: re-adding an existing id is a no-op, removing a missing id is a no-op.

Output model — important for piping

Command flavorstdoutstderr
bundles get / lists get / docs get / context (default)Raw markdownStats line (e.g. Assembled 7 docs, 48201 / 50000 tokens)
Any of those with --jsonFull JSON envelope {data: ...}(silent)
Any of those with -o <file>(silent)Wrote N bytes to <file> + stats

This split makes piping safe:

contextdock context --budget 50000 --tags refunds > ctx.md   # only markdown lands in ctx.md

context requires at least one filter

--docs, --bundles, --tags, or --query. Without any, the CLI exits 1 with At least one filter is required: --docs, --bundles, --tags, or --query. --budget is always required (range 1000–125000 tokens; outside that range the API returns BUDGET_TOO_SMALL / BUDGET_EXCEEDED).

Lists are NOT bundles

A "list" is a separate primitive: a named collection of tagIds (dynamic — every doc carrying any/all of the listed tags is included on read) plus a small docIds extras array for ad-hoc additions. Bundles are static doc-id arrays. Use bundles when membership is hand-curated; use lists when membership should follow a tag rule and stay current.

Budgeting a bundle or list

bundles get <id> --json and lists get <id> --json both return an envelope enriched with docCount, a per-level tokens totals map, and a docs[] table — so you don't have to re-resolve membership against docs list --tag <t> just to add up tokens. Bundle entries are {id, title, tokens}; list entries also carry aiSummary and summary (each null when the level hasn't been generated). The envelope also carries a truncated boolean — true when the resolved member set exceeds the detail cap (default 200 docs), in which case docs[] is capped but docCount and tokens still reflect the full set.

# Pick the cheapest level that fits a budget — no content fetches, no membership resolution
contextdock lists get l_xyz --json | jq '.data.tokens'
# → { "original": 92000, "keyPoints": 41000, "summary": 12000, "aiSummary": 600 }

# Same shape on bundles
contextdock bundles get b_xyz --json | jq '.data.tokens.keyPoints'

To assemble a list under budget, read the list's tagIds, then hand them to context:

contextdock lists get l_xyz --json | jq -r '.data.tagIds | join(",")'
# → finance,ops
contextdock context --tags finance,ops --budget 100000 --prefer keyPoints

Watch the tag-match semantics gap: context --tags a,b always uses any semantics (union — docs carrying tag a OR tag b). Lists with tagMatch: "all" cannot be replicated through context --tags directly. The detail-envelope tokens totals already honor the list's tagMatch, so for budget planning the envelope is the right read. For actual assembly under tagMatch: "all", fetch the list's pre-assembled markdown via contextdock lists get l_xyz --variant <v> (the server applies the correct intersection).

MCP stdio: stdout is sacred — and mcp is not a daemon

contextdock mcp hands stdin/stdout to the MCP SDK for JSON-RPC framing. The command writes nothing to stdout itself — anything printed there corrupts the protocol and the host (Claude Desktop, Cursor, Claude Code) silently disconnects. Status messages, if any, must go to stderr. Do not add console.log, print, or process.stdout.write to the mcp command path — and warn the user if they're patching it.

Do NOT tell the user to "keep contextdock mcp running in a terminal". It is a stdio subprocess that the host (Claude Desktop / Cursor / etc.) spawns, pipes JSON-RPC through, and kills on exit. Running it manually in a terminal does nothing useful — there is no listener for it to talk to, and the user will sit watching a silent process. The only correct way to start it is to register it in the host's MCP config and let the host spawn it.

Claude Desktop config

Edit:

Because the CLI is not on npm (and won't be), point at node + the absolute path to dist/index.js:

{
  "mcpServers": {
    "contextdock": {
      "command": "node",
      "args": [
        "C:\\Software Projects\\Context Manager\\cli\\dist\\index.js",
        "mcp"
      ],
      "env": {
        "CONTEXTDOCK_API_KEY": "cdk_live_..."
      }
    }
  }
}

After npm link from the repo's cli/ directory, the simpler form works (the contextdock shim resolves to the local checkout):

{
  "mcpServers": {
    "contextdock": {
      "command": "contextdock",
      "args": ["mcp"],
      "env": { "CONTEXTDOCK_API_KEY": "cdk_live_..." }
    }
  }
}

The env block is strongly recommended even if ~/.contextdock/config.json exists. It pins the key to this MCP server entry (so revoking the file doesn't silently break Claude Desktop), survives moving the config between machines, and makes the wiring auditable in one place. The config file is the fallback, not the primary path for host-spawned subprocesses.

Restart Claude Desktop. Verify by asking it to "list my ContextDock bundles" — that exercises the list_bundles MCP tool end-to-end.

Errors & exit codes

Every CLI failure goes through CliError with one of five exit codes that mirror the API error taxonomy.

ExitMeaningTypical API code field
1Generic / validation / networkValidation failed, NO_FILTER_SPECIFIED, Network error: ...
2AuthKEY_INVALID, KEY_REVOKED, INSUFFICIENT_SCOPE, "Not logged in"
3Not foundDOC_NOT_FOUND, BUNDLE_NOT_FOUND
4Token budgetBUDGET_TOO_SMALL, BUDGET_EXCEEDED
5Rate limitRATE_LIMIT_EXCEEDED

Important distinctions:

When to ask the user

Ask the user to provide a key only when all of the following are true:

Even then, do not ask them to paste the key into chat. Tell them:

Mint a key at https://contextdock.web.app → Settings → API Keys → Create Key, copy it, then run contextdock login and paste at the prompt. The CLI writes it to ~/.contextdock/config.json for you.

contextdock login (no flags) reads the key off stdin so it never touches process arguments or shell history.

Do NOT


Source of truth: skills/contextdock-cli/SKILL.md in the ContextDock repo. The raw markdown is also served at /cli-skill.md.