{"_id":"@anyslate/cli","_rev":"9-03e5b7f489c994eafe347e4af06d6abc","name":"@anyslate/cli","dist-tags":{"latest":"0.4.1"},"versions":{"0.1.0":{"name":"@anyslate/cli","version":"0.1.0","keywords":["anyslate","mcp","claude-code","ai","memory","lifecycle-hooks","cli"],"license":"MIT","_id":"@anyslate/cli@0.1.0","maintainers":[{"name":"anyslate","email":"webmaster@anyslate.io"}],"homepage":"https://github.com/AnySlate/anyslate#readme","bugs":{"url":"https://github.com/AnySlate/anyslate/issues"},"bin":{"anyslate":"bin/anyslate.mjs"},"dist":{"shasum":"0cc3af2877a80c668a99079ca731313cb8dcaf0e","tarball":"https://registry.npmjs.org/@anyslate/cli/-/cli-0.1.0.tgz","fileCount":12,"integrity":"sha512-NSBsIRvKVMOP93nHBo6ATZ3RQNhIKR8puvRXG+MC0lETJVJZeizQAbKNh1fnUgoLLZy2d/YcscTFL2e2sSmadw==","signatures":[{"sig":"MEQCICtTomiMhqW4Nh6ptMDexA8Q2LlUukUkuuAB1MQn4FfzAiAdlaNMu5nZvbc2/WtvU9ACU8qo38LYjOp50rD7Bo+VtQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31202},"type":"module","engines":{"node":">=20.0.0"},"gitHead":"7132522c19a11b4307f901502af249660e16df75","scripts":{"test":"node --test 'src/__tests__/*.test.mjs'","publish:npm":"dotenv -e .env -- npm publish --access=public"},"_npmUser":{"name":"anyslate","email":"webmaster@anyslate.io"},"repository":{"url":"git+https://github.com/AnySlate/anyslate.git","type":"git","directory":"cli"},"_npmVersion":"11.9.0","description":"AnySlate CLI — lifecycle hooks + checkpoint / upload-artifact for AI memory capture. All hook subcommands submit to the Activity feed via the activity_submit MCP tool (Phase 14).","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"dotenv-cli":"^11.0.0"},"_npmOperationalInternal":{"tmp":"tmp/cli_0.1.0_1778408951460_0.04130809493410248","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@anyslate/cli","version":"0.2.0","keywords":["anyslate","mcp","claude-code","ai","memory","lifecycle-hooks","cli"],"license":"MIT","_id":"@anyslate/cli@0.2.0","maintainers":[{"name":"anyslate","email":"webmaster@anyslate.io"}],"homepage":"https://anyslate.io","bugs":{"url":"https://anyslate.io/support"},"bin":{"anyslate":"bin/anyslate.mjs"},"dist":{"shasum":"4e56b6c4d64c278122df3eba8a197cee5a13977c","tarball":"https://registry.npmjs.org/@anyslate/cli/-/cli-0.2.0.tgz","fileCount":18,"integrity":"sha512-lnF94k16t0wBvxgcD/aSwhChOJZI7Eh37MjOX4Cqdtx1WGZt+YX7ldymUKlr4XU9jDGfbZFWI7HH2YsSK5t5hA==","signatures":[{"sig":"MEQCIG82uxbC4T4kNj9ce/69zu8dMipy1TofhAOOTP9k4wGJAiA1tbHPYYQFfvbnwgYpxjojwhondiBZ51l0TuT2anTLxA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":121167},"type":"module","engines":{"node":">=20.0.0"},"gitHead":"3e49276dd07d63c7fe490298f0b7819f9d6c7197","scripts":{"test":"node --test 'src/__tests__/*.test.mjs'","publish:npm":"dotenv -e .env -- npm publish --access=public"},"_npmUser":{"name":"anyslate","email":"webmaster@anyslate.io"},"_npmVersion":"11.9.0","description":"AnySlate CLI - lifecycle hooks, git/CI capture, and manual checkpoints for AI memory. Validates its connection at login, diagnoses itself with `anyslate doctor`, and fails open without failing silent.","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"dotenv-cli":"^11.0.0"},"_npmOperationalInternal":{"tmp":"tmp/cli_0.2.0_1784385551773_0.32124950283231746","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@anyslate/cli","version":"0.3.0","keywords":["anyslate","mcp","claude-code","ai","memory","lifecycle-hooks","cli"],"license":"MIT","_id":"@anyslate/cli@0.3.0","maintainers":[{"name":"anyslate","email":"webmaster@anyslate.io"}],"homepage":"https://anyslate.io","bugs":{"url":"https://anyslate.io/support"},"bin":{"anyslate":"bin/anyslate.mjs"},"dist":{"shasum":"8f0b4a44ad54e08b52c18399acf6511c4a4c43c9","tarball":"https://registry.npmjs.org/@anyslate/cli/-/cli-0.3.0.tgz","fileCount":22,"integrity":"sha512-OCbJvan/hMZhW+HI1PQHZcvkNN3EFtcIezb6uxjKaTy/X9/EnFHy6t3d7ReWUj4RYyz0Dt7SZhqPAYGMqy/JSQ==","signatures":[{"sig":"MEUCIDHYGIOSDRcUUg4Rd7yUGmICpqrQ60oA8h96RDCVohAvAiEAkRLL8iwebAmxBn1QBJqrCt1F2xueRUp2gII41xIiPhU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":193664},"type":"module","engines":{"node":">=20.0.0"},"gitHead":"280f07a40eaa9a22a8df4e9f71d9c48268dbac87","scripts":{"test":"node --test 'src/__tests__/*.test.mjs'","publish:npm":"dotenv -e .env -- npm publish --access=public"},"_npmUser":{"name":"anyslate","email":"webmaster@anyslate.io"},"_npmVersion":"11.9.0","description":"AnySlate CLI - lifecycle hooks, git/CI capture, and manual checkpoints for AI memory. Validates its connection at login, diagnoses itself with `anyslate doctor`, and fails open without failing silent.","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"dotenv-cli":"^11.0.0"},"_npmOperationalInternal":{"tmp":"tmp/cli_0.3.0_1784389595851_0.6735651144270984","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@anyslate/cli","version":"0.3.1","keywords":["anyslate","mcp","claude-code","ai","memory","lifecycle-hooks","cli"],"license":"MIT","_id":"@anyslate/cli@0.3.1","maintainers":[{"name":"anyslate","email":"webmaster@anyslate.io"}],"homepage":"https://anyslate.io","bugs":{"url":"https://anyslate.io/support"},"bin":{"anyslate":"bin/anyslate.mjs"},"dist":{"shasum":"f84b4c32ad3de280d562eb3b5a27f7e7a193d432","tarball":"https://registry.npmjs.org/@anyslate/cli/-/cli-0.3.1.tgz","fileCount":22,"integrity":"sha512-efq22NWJbVaH7RXm43QSG3mR0C5o2j9dBfJOx405TPbJ94EOluFxAD5FxKIc0geO8HTzjbYfQz0dHevHJzeA8Q==","signatures":[{"sig":"MEUCIF+kkrXWb8NBRpO31BP9lfyv4Dyiai/be3YLYZZdOO3UAiEAzmfvf/JvMERHslArcB4ihQxXrjZYYnYlFPEViTdRX9Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":196358},"type":"module","engines":{"node":">=20.0.0"},"gitHead":"b32b28b6a67c0d5a4863dd45ca669e9216bd5c35","scripts":{"test":"node --test 'src/__tests__/*.test.mjs'","publish:npm":"dotenv -e .env -- npm publish --access=public"},"_npmUser":{"name":"anyslate","email":"webmaster@anyslate.io"},"_npmVersion":"11.9.0","description":"AnySlate CLI - lifecycle hooks, git/CI capture, and manual checkpoints for AI memory. Validates its connection at login, diagnoses itself with `anyslate doctor`, and fails open without failing silent.","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"dotenv-cli":"^11.0.0"},"_npmOperationalInternal":{"tmp":"tmp/cli_0.3.1_1784390980393_0.24251423240268122","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@anyslate/cli","version":"0.4.0","keywords":["anyslate","mcp","claude-code","ai","memory","lifecycle-hooks","cli"],"license":"MIT","_id":"@anyslate/cli@0.4.0","maintainers":[{"name":"anyslate","email":"webmaster@anyslate.io"}],"homepage":"https://anyslate.io","bugs":{"url":"https://anyslate.io/support"},"bin":{"anyslate":"bin/anyslate.mjs"},"dist":{"shasum":"7130c932d2504c510af9e91e1714d4a755bbd778","tarball":"https://registry.npmjs.org/@anyslate/cli/-/cli-0.4.0.tgz","fileCount":24,"integrity":"sha512-uX40FZmD4q1GxFSuYXXahIJFOAap0gZPLAVfGxvWHNvWzo5ZkRD4JzItsUvgZtniD+eMoTk0WmHLGhf/lTrtuQ==","signatures":[{"sig":"MEYCIQDk4/sXsQm2+lnIy9iaRHTTtsJ4rRBKPoW52VcCTuHSXAIhAP+is7ihK0An2fXatnCfKoqKqbhmfbupuMBUz/lQ5SmA","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":233235},"type":"module","engines":{"node":">=20.0.0"},"gitHead":"333e94d02166a35d996f9cec02742df0481d29ed","scripts":{"test":"node --test 'src/__tests__/*.test.mjs'","publish:npm":"dotenv -e .env -- npm publish --access=public"},"_npmUser":{"name":"anyslate","email":"webmaster@anyslate.io"},"_npmVersion":"11.9.0","description":"AnySlate CLI - lifecycle hooks, git/CI capture, and manual checkpoints for AI memory. Validates its connection at login, diagnoses itself with `anyslate doctor`, backs off and circuit-breaks rather than retrying, and fails open without failing silent.","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"dotenv-cli":"^11.0.0"},"_npmOperationalInternal":{"tmp":"tmp/cli_0.4.0_1785765945485_0.5073194679887065","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"_id":"@anyslate/cli@0.4.1","bin":{"anyslate":"bin/anyslate.mjs"},"bugs":{"url":"https://anyslate.io/support"},"dist":{"shasum":"f6354ae478b1dbbac575092ee8a3e98e258572d9","tarball":"https://registry.npmjs.org/@anyslate/cli/-/cli-0.4.1.tgz","fileCount":24,"integrity":"sha512-THFYibCSwSiOBoejx6ox/hsaFf/Ir7cc+b/0PcFCGqZAN85U4ljXXpi54ZOHsgc/p1nqftsF4is00608C8hf3Q==","signatures":[{"sig":"MEQCIFHNrNEOOQPxrGAdgqYiV9gfdDA36hwoQ6m/dS5Nj+pJAiBOXPZEEYrLifuIKXha3X2+wP7uvaj+FgFQS+37ueOO9g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDCEKKuttxxpmDxDu6Vetnir5M5dWXCtn+QXmu81NdGcQIhAJYnu63DVfxM81hNfIgUkwFeLQVP5UB35pcj1ac7vnjf"}],"unpackedSize":240250},"name":"@anyslate/cli","type":"module","engines":{"node":">=20.0.0"},"gitHead":"0b24dae3f35886e968571447c9883bc92d0ddcbe","license":"MIT","scripts":{"test":"node --test 'src/__tests__/*.test.mjs'","publish:npm":"dotenv -e .env -- npm publish --access=public"},"version":"0.4.1","_npmUser":{"name":"anyslate-org","email":"webmaster@anyslate.io"},"homepage":"https://anyslate.io","keywords":["anyslate","mcp","claude-code","ai","memory","lifecycle-hooks","cli"],"_npmVersion":"11.14.1","description":"AnySlate CLI - lifecycle hooks, git/CI capture, and manual checkpoints for AI memory. Validates its connection at login, diagnoses itself with `anyslate doctor`, backs off and circuit-breaks rather than retrying, and fails open without failing silent.","directories":{},"maintainers":[{"name":"anyslate-org","email":"webmaster@anyslate.io"},{"name":"marendev","email":"harikrishna@marensolutions.com"},{"name":"anasvp","email":"anasv.p@marensolutions.com"}],"_nodeVersion":"24.15.0","_hasShrinkwrap":false,"devDependencies":{"dotenv-cli":"^11.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cli_0.4.1_1788870968171_0.531230240804228"}}},"time":{"created":"2026-05-10T10:29:11.367Z","modified":"2026-09-08T12:36:08.560Z","0.1.0":"2026-05-10T10:29:11.628Z","0.2.0":"2026-07-18T14:39:11.904Z","0.3.0":"2026-07-18T15:46:35.993Z","0.3.1":"2026-07-18T16:09:40.540Z","0.4.0":"2026-08-03T14:05:45.632Z","0.4.1":"2026-09-08T12:36:08.269Z"},"bugs":{"url":"https://anyslate.io/support"},"license":"MIT","homepage":"https://anyslate.io","keywords":["anyslate","mcp","claude-code","ai","memory","lifecycle-hooks","cli"],"description":"AnySlate CLI - lifecycle hooks, git/CI capture, and manual checkpoints for AI memory. Validates its connection at login, diagnoses itself with `anyslate doctor`, backs off and circuit-breaks rather than retrying, and fails open without failing silent.","maintainers":[{"name":"anyslate-org","email":"webmaster@anyslate.io"},{"name":"marendev","email":"harikrishna@marensolutions.com"},{"name":"anasvp","email":"anasv.p@marensolutions.com"}],"readme":"<p align=\"center\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://mcp.anyslate.io/logo-full-dark.png\">\n    <img src=\"https://mcp.anyslate.io/logo-full-light.png\" alt=\"AnySlate\" width=\"280\">\n  </picture>\n</p>\n\n<h1 align=\"center\">@anyslate/cli</h1>\n\n<p align=\"center\">\n  <strong>Your AI tools remember what you did. Automatically.</strong><br>\n  Lifecycle hooks, git commits and CI runs → your AnySlate memory, without you lifting a finger.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@anyslate/cli\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@anyslate/cli?color=f45f5f&labelColor=252c3e\"></a>\n  <a href=\"https://nodejs.org\"><img alt=\"node\" src=\"https://img.shields.io/node/v/@anyslate/cli?color=f45f5f&labelColor=252c3e\"></a>\n  <img alt=\"dependencies\" src=\"https://img.shields.io/badge/runtime%20deps-0-f45f5f?labelColor=252c3e\">\n  <img alt=\"license\" src=\"https://img.shields.io/npm/l/@anyslate/cli?color=f45f5f&labelColor=252c3e\">\n</p>\n\n---\n\n## What is this?\n\n**AnySlate** gives your AI tools a long-term memory — a place where decisions, tasks and\nwork history live, so Claude, Cursor or Codex can pick up where you left off instead of\nstarting cold every session.\n\n**This CLI is how that memory gets filled in when no AI is watching.**\n\nAn AI can only record something if it decides to call a tool. But a lot of your real work\nhappens outside that: you commit code, CI deploys, you run the test suite, you make a call\nwhile staring at a terminal. None of it has an AI present to write it down.\n\nThis CLI closes that gap. Your tools call it for you:\n\n```\nClaude Code fires a hook  ─┐\ngit runs post-commit      ─┼─→  anyslate  ─→  AnySlate memory\nCI finishes a deploy      ─┘\n```\n\nThink of it like a fitness tracker for your engineering work. You don't tell a fitness\ntracker \"I just took 47 steps.\" You wear it, you walk, the number goes up. Same idea:\ninstall this once, paste one config block, and capture happens in the background forever.\n\n> **You will almost never type `anyslate` yourself.** It is plumbing that other tools call.\n> Setup takes about five minutes and ends with `anyslate doctor` confirming it works.\n\n## Quick start\n\n```bash\nnpm i -g @anyslate/cli     # 1. install\nanyslate login             # 2. sign in (opens your browser)\nanyslate doctor            # 3. confirm it works\n```\n\nThen paste the hook block into `~/.claude/settings.json` — see\n[Wiring into Claude Code](#wiring-into-claude-code) — and restart Claude Code. Done.\n\n## What actually gets captured\n\n| Trigger | Who calls the CLI | What lands in your memory |\n|---|---|---|\n| You open a Claude Code session | `SessionStart` hook | Session start + working directory; the AnySlate memory is created on first sight |\n| Claude edits `routes/auth.ts` | `PostToolUse` hook | `Edited routes/auth.ts` in the activity ledger |\n| Claude runs `npm test` | `PostToolUse` hook | Command + exit status in the activity ledger |\n| You `git commit` | git `post-commit` hook | Commit metadata and diff stats |\n| A teammate merges your PR | GitHub webhook (no CLI needed) | PR-merged event |\n| You close the laptop | `Stop` hook | Session-end marker |\n| **You want to record a decision** | **you typing `anyslate checkpoint`** | **Captured immediately, as a decision** |\n| **You want to attach a file** | **you typing `anyslate upload-artifact`** | **Stored, returns `cloud://artifact/<id>`** |\n\nThe first six rows are automatic. The last two are the rare moments you'd type something.\n\n### What hook captures write — read this before forming expectations\n\nHook captures land in a dedicated **`## Activity Ledger`** section of your memory page:\nwhich files were touched, which commands ran, what exit status they returned. That is the\nwhole contract. The ledger is capped (25 files / 15 commands per entry) and is built\ndeterministically — **no LLM runs in the hook path**, so background capture never bills\nagainst your AI quota.\n\nHook captures **do not** write to **Key Decisions** or **Open Tasks**. Those stay human-\nand LLM-authored. A hook firing on `Bash` has no decision inside it to extract, and the\nproduct no longer pretends otherwise.\n\nIf you want a decision recorded, say so:\n\n```bash\nanyslate checkpoint --note \"Decided: Redis for session cache, not memcached\"\n```\n\n### Approval: hook captures auto-promote\n\nEvery lifecycle hook — `Edit` / `Write` / `MultiEdit`, `Bash`, and the session start/stop\nmarkers — classifies **low-risk** and auto-promotes on its own, 30–90 seconds after\ncapture. An edit-heavy session does **not** build an approval queue.\n\nRows still wait for approval in **AI Memory → Activity** when they are genuinely riskier: a\ncapture aimed at a different session than the one that produced it, a decision that\nsupersedes an earlier one, a submission from an untrusted source, or one carrying\n`confidence < 0.8`.\n\n## Why bother\n\n- **You stop forgetting to checkpoint.** Capture happens whether you remember or not.\n- **You see what actually happened, not just what the AI claimed.** If the agent says it\n  implemented `TokenRefreshService` and the diff is whitespace, that discrepancy is\n  visible. You trust the AI because you can check, not because you have to assume.\n- **Your work follows you across tools.** Claude Code at home, Cursor at work, a terminal\n  in between — all feeding one memory. (Cursor and Windsurf feed it through the in-host\n  MCP server rather than this CLI — see the [FAQ](#faq).)\n- **Nothing is canonical until it is.** Anything pending can be rejected without ever\n  touching a memory page.\n\n## Install\n\n```bash\nnpm i -g @anyslate/cli\n# or run on demand without installing:\nnpx @anyslate/cli --help\n```\n\nRequires Node ≥ 20.\n\n> **macOS users hitting `EACCES` on global install:** don't use `sudo`. Configure a user-writable npm prefix instead:\n>\n> ```bash\n> mkdir -p ~/.npm-global\n> npm config set prefix '~/.npm-global'\n> echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc\n> source ~/.zshrc\n> npm install -g @anyslate/cli\n> ```\n\n## Authenticate\n\n```bash\nanyslate login\n```\n\nThat's it. `login` opens your browser, you approve the CLI on the AnySlate consent screen, and the credentials land in `~/.anyslate/cli.json` (mode `0600`). Nothing to copy, nothing to paste.\n\nWhat happens under the hood, in case you're the kind of person who wants to know before you run it: OAuth 2.1 authorization code with PKCE. The CLI reads every endpoint from the server's discovery documents (`/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource`) rather than assuming any path, registers itself once via Dynamic Client Registration and caches the resulting client id, binds a short-lived listener on `127.0.0.1` for the callback, and verifies the resulting token with one live request before writing anything.\n\nThe access token lasts **one hour**; a refresh token lasts **30 days**. The CLI refreshes automatically — five minutes ahead of expiry, and again if a call comes back `401` — so your hooks keep capturing without you thinking about it. Refresh tokens are single-use and rotate on every refresh; the new one is written to disk immediately.\n\nThen confirm the whole install:\n\n```bash\nanyslate doctor\n```\n\n### Signing in to a non-production environment\n\n```bash\nanyslate login --api-url https://anyslate-mcp-service-development.example.workers.dev\n```\n\n`--api-url` takes the service **root** (no `/mcp` suffix — see [the two URL conventions](#the-two-url-conventions---the-one-thing-people-get-wrong)). Every OAuth endpoint, and the OAuth `resource` identifier, is then read from *that* root's discovery documents. Dev and production issue different client ids, so the CLI caches one per root and reuses it.\n\nIf discovery fails, `login` says which host it tried and stops. It will not fall back to guessed endpoint paths.\n\n### Static tokens, for CI\n\nBrowsers are in short supply on a build agent. The token path is unchanged and stays supported:\n\n```bash\nanyslate login --token as_mcp_your_token_here\n# or, with no config file at all:\nexport ANYSLATE_MCP_TOKEN=as_mcp_your_token_here\n```\n\nMint the token in the AnySlate app: **Avatar (top-right) → API Tokens → Tokens tab → Create Token**. This is the same dialog on the desktop app and on the cloud app at `https://cloud.anyslate.io`; the dialog is titled *MCP Tokens*. MCP token minting is **available on every plan** - Free, Pro and Unlimited - with no token quota. Copy the token (it starts with `as_mcp_`; you only see it once).\n\n`login --token` verifies the token against the server before writing anything. If the host is unreachable, the URL isn't an AnySlate MCP service, or the token is unrecognised/revoked/expired, it prints the reason, **writes nothing, and exits non-zero**. See [`anyslate login`](#anyslate-login) for the `--force` / `--no-verify` escapes.\n\nStatic tokens never expire and are never refreshed. That is the trade: convenient for CI, and the reason the browser flow is the default everywhere else.\n\n### Signing out\n\n```bash\nanyslate logout\n```\n\nRevokes the session server-side, then removes the credentials from `~/.anyslate/cli.json`. Your `apiUrl`, `handle` and cached client registration are kept, so `anyslate login` afterwards is a one-liner.\n\n### Environment variables\n\n```bash\nexport ANYSLATE_MCP_TOKEN=as_mcp_your_token_here\nexport ANYSLATE_API_URL=https://mcp.anyslate.io   # optional; service ROOT, no /mcp\nexport ANYSLATE_HANDLE=mh_xxxxxxxx                # optional, scope to one capability handle\nexport ANYSLATE_DISABLE=1                         # optional, disables capture for this shell\nexport ANYSLATE_HOME=/path/to/dir                 # optional, overrides ~/.anyslate\nexport ANYSLATE_STDIN_TIMEOUT_MS=10000            # optional, stdin idle timeout (0 disables)\nexport ANYSLATE_HOOK_TIMEOUT_MS=5000              # optional, request budget for one hook\nexport ANYSLATE_MAX_CALLS_PER_MINUTE=60           # optional, local call ceiling (0 disables)\n```\n\nEnv vars override `~/.anyslate/cli.json`.\n\nThe bearer the CLI actually sends is resolved in this order, first hit wins:\n\n1. `ANYSLATE_MCP_TOKEN` — a static token from the environment. Never refreshed.\n2. The OAuth access token from `cli.json`, refreshed if it is expired or close to it.\n3. The static `mcp_token` in `cli.json`.\n\n`anyslate doctor` prints which of the three won, so an `ANYSLATE_MCP_TOKEN` you exported weeks ago and forgot cannot silently shadow a browser sign-in.\n\n`ANYSLATE_DISABLE=1` stops `hook` / `checkpoint` / `upload-artifact` from making any network call (they exit 0). `doctor` and `login` still run, so you can diagnose and set up while capture is off. Any value counts as \"on\" except empty, `0`, `false`, `no`, `off`.\n\nCapability handles are `mh_` + a 32-character id (e.g. `mh_V1StGXR8Z5jdHi6BmyT0aQx3nKpL7cWe`), minted alongside a scoped token in the **Handles** tab of the same dialog. **A handle id that isn't yours or doesn't exist is rejected server-side** - since 0.2.0 the CLI reports that as a failure instead of exiting 0 silently.\n\n**If your token was minted with a memory scope, it already carries that scope server-side and `ANYSLATE_HANDLE` / `--handle` is ignored.** Scoped tokens cannot be widened per-call by design. To capture workspace-wide, mint an unscoped token. `anyslate doctor` reports which case you're in.\n\n### When capture pauses itself\n\nSince 0.4.0 the CLI refuses to keep calling a server that is refusing it. Three things changed:\n\n- **No unbounded retries.** `checkpoint` and `upload-artifact` retry at most 3 times, with exponential backoff and jitter and a 15-second total sleep budget. `hook` does not retry at all: it fires on every tool call, so a retry there is a doubled request rate in exchange for one row in an activity feed.\n- **`Retry-After` is obeyed.** A `429` or `503` that names a wait pauses this machine for exactly that long. The CLI never sleeps through a long one - the process exits and the pause is honoured by whichever process runs next.\n- **A circuit breaker that outlives the process.** Every hook is a separate process, so an in-memory breaker would be worthless. After 3 consecutive `401`s - or one definitively dead credential, such as a refresh token the server has already replaced - capture pauses, the pause is recorded in `~/.anyslate/cli-guard.json`, and the CLI tells you once:\n\n```\nanyslate: capture paused until 2026-08-02T12:15:00.000Z — repeated authentication failures (invalid_grant). Run `anyslate login` to sign in again; no requests are sent until then.\n```\n\nAfter that one line, paused hooks are completely silent and make **no** network request. Repeated pauses escalate: 15 minutes, then 1 hour, 6 hours, 24 hours.\n\nThe pause is tied to the `apiUrl` + credential that earned it. `anyslate login` clears it immediately, and so does pointing `--api-url` somewhere else or swapping the token - fixing your setup is never punished by a wait. `anyslate doctor` always reports an active pause, and is itself never blocked by one.\n\n`ANYSLATE_MAX_CALLS_PER_MINUTE` is the last line of defence: a machine-wide ceiling (default 60/minute) on capture calls, so a runaway agent loop cannot become a load test. It is far above any real session; set it to `0` to disable.\n\n### The two URL conventions - the one thing people get wrong\n\nAnySlate ships two clients and they want **different** URLs. This trips up nearly everyone:\n\n| Client | Variable / flag | Wants | Example |\n|---|---|---|---|\n| **`@anyslate/mcp` bridge** (Claude Desktop, Cursor, Windsurf…) | `ANYSLATE_MCP_URL` | the **MCP endpoint** - *with* `/mcp` | `https://mcp.anyslate.io/mcp` |\n| **`@anyslate/cli`** (this package) | `--api-url` / `ANYSLATE_API_URL` | the **service root** - *without* `/mcp` | `https://mcp.anyslate.io` |\n\nThe CLI appends `/mcp` itself. Pasting the bridge's URL into `--api-url` used to produce `…/mcp/mcp`, a permanent `404 {\"error\":\"Not found\"}` that masks every other diagnosis - because the 404 fires before authentication, so a wrong URL makes a valid token look invalid.\n\n**Since 0.2.0 the CLI accepts either form and normalizes**, stripping any trailing `/mcp` segments (`/mcp`, `/mcp/mcp`, `/MCP/`, trailing slashes). Normalization happens on every read, so a `cli.json` written by an older version self-heals without re-running `login`. When it rewrites a value it tells you:\n\n```\nanyslate: apiUrl \"https://mcp.anyslate.io/mcp\" ends in /mcp — the CLI wants the service ROOT and appends /mcp itself.\nanyslate: using \"https://mcp.anyslate.io\". Run `anyslate login --api-url https://mcp.anyslate.io` to persist.\n```\n\n## Subcommands\n\n```\nanyslate hook <session-start|post-tool-use|stop> [--strict] [--session <id>] [--note <text>] [--host <hint>]\nanyslate checkpoint --note \"...\" [--kind milestone] [--session <id>] [--host <hint>] [--source <source>]\nanyslate upload-artifact --session <id> --kind <kind> [--file <path>] [--language <lang>] [--path-hint <path>]\nanyslate login [--api-url <URL>] [--no-browser] [--timeout <seconds>] [--handle <ID>]\nanyslate login --token <BEARER> [--handle <ID>] [--api-url <URL>] [--force] [--no-verify]\nanyslate logout [--local]\nanyslate doctor [--deep] [--refresh]\nanyslate version | help\n```\n\n### `anyslate doctor`\n\nThe one command whose job is to fail loudly. Run it after setup and any time capture seems dead. It exits non-zero if any check FAILs.\n\nIt checks, in order: which config layer won (env var vs `cli.json`) per key; that a credential is present; **which auth mode is in use** — OAuth or static token — and for OAuth how long the access token has left and whether a refresh token is stored; that the token *looks* like a token; that the URL parses and has the right shape (**before** any token verdict, because a wrong URL 404s ahead of auth and would otherwise be misread as a bad token); that the host is reachable and healthy; **whether refresh works**; that the token is valid (and, if not, whether it is unrecognised, revoked or expired); that the token carries `memory:write`; the effective handle scope; that `anyslate` is on the PATH a hook subprocess would get; and whether Claude Code's hooks are actually wired in `~/.claude/settings.json`.\n\nThe refresh check runs **after** reachability on purpose: a refresh against a host that is down fails for a reason that has nothing to do with your credentials, and reporting that as \"refresh is broken\" sends you hunting for the wrong bug. If the access token is still comfortably valid, `doctor` reports the refresh token as present but does not spend it — pass `--refresh` to force a real refresh round trip (which rotates the token).\n\nAn expired OAuth access token is a **WARN**, not a FAIL: access tokens are supposed to expire hourly. The FAIL you care about is `oauth-refresh`, which means renewal itself is broken and you need to run `anyslate login` again.\n\nReading the output:\n\n- **PASS** - checked and observed good.\n- **WARN** - works, but something is narrower or more fragile than you probably intend (e.g. a project-scoped token, or a config key coming from an env var you forgot you exported).\n- **FAIL** - this is why capture isn't working. Each FAIL prints the specific remedy.\n\n`doctor` is **not** side-effect-free: verifying the token updates its `last_used_at`, consumes rate-limit budget, and writes an audit-log row. `--deep` additionally submits a real `doctor_probe` activity, which is visible in your Activity feed - that's why it's opt-in. `--refresh` forces an OAuth refresh, which rotates your refresh token - also why it's opt-in.\n\n### `anyslate hook ...`\n\nReads a JSON event payload from stdin (Claude Code lifecycle event format). **Fails open by default** - a network blip, an invalid token or a server-side rejection logs to stderr and exits 0, so your Claude Code session is never broken. Pass `--strict` to exit 1 on failure instead (useful in CI smoke tests).\n\nBecause fail-open means you won't see stderr in normal use, every run is recorded to `~/.anyslate/cli-last-run.json` and a capped ring log at `~/.anyslate/cli-runs.ndjson` (both mode 0600, token redacted). After repeated consecutive failures the `SessionStart` hook surfaces a message in Claude Code itself telling you to run `anyslate doctor`. One-off failures stay quiet.\n\nKind mapping: `hook session-start` → `topic_shift`; `hook stop` → `conversation_end`; `hook post-tool-use` maps `Edit`/`Write`/`MultiEdit`/`Create` and `Bash`/`Shell`/`Run`/`GitCommit` → `task_completed`, everything else → `topic_shift`.\n\nHooks never emit `artifact_produced`. That kind is a contract — \"I stored an artifact, here is its id in `artifact_refs`\" — and the server rejects it without one. A hook observes a file touch; it stores nothing. The touched path is carried in the `files_touched` ledger field instead, so edits auto-promote as activity entries rather than piling up an approval queue.\n\n**Session identity is handled for you.** Claude Code's `session_id` is a UUID from its own namespace; AnySlate session ids are a different format entirely. The server resolves the incoming UUID against `conversation_id_external`, and **creates the AnySlate session on first sight** if there's no match, titling it from the host and working directory. You do **not** need to pass `--session` in the Claude Code hook config, and re-running under the same Claude Code session reuses the same AnySlate session. `--session` remains available for git hooks and CI, where you want to pin captures to one specific memory.\n\nFields read from the stdin payload: `session_id` (or `sessionId`), `tool_name` (or `toolName`), `tool_input` (or `toolInput`), `tool_response` (or `toolResponse`, JSON-stringified into `conversation_excerpt`), `transcript_path`, `client_checkpoint_id`. Anything else in the payload is ignored.\n\n**The activity ledger.** From `tool_input` and `tool_response` the hook derives three structured fields — `files_touched`, `commands_run` and `exit_status` — and sends them as first-class payload fields. They record what a tool call *did*, and they land in the activity/ledger section of the memory page. They are deliberately **not** folded into `notes`: `notes` is the prose the server mines for Decisions and Open Tasks, and turning shell commands into decisions would manufacture entries you never made. Fields with nothing to record are omitted rather than sent empty. No LLM runs anywhere on this path.\n\n### `anyslate checkpoint`\n\nUser-initiated, exits non-zero on failure - including on server-side rejections, not just network errors. Defaults `--kind milestone`, `--source api`. Allowed kinds: `topic_shift | decision_committed | task_completed | task_added | artifact_produced | milestone | conversation_end`.\n\n`--host <hint>` sets the advisory `host_hint`; `--source <source>` overrides the activity source.\n\nLow-risk items reach your memory page after a **30-second quiet period plus the next once-a-minute promoter run** - so 30-90 seconds, not instantly.\n\n### `anyslate upload-artifact`\n\nReads a file or stdin, calls the `upload_artifact` MCP tool. Prints `cloud://artifact/<id>` on stdout on success; on failure prints nothing to stdout, writes the reason to stderr and exits non-zero. Allowed kinds: `code_block | file_path | error_message | shell_command | config_snippet | url_reference | fenced_quote`. 5 MB content cap on both the `--file` and stdin paths.\n\nContent is read as UTF-8. Binary files are not supported and are refused rather than silently mangled.\n\n### `anyslate login`\n\nTwo paths, one config file. `--token` selects the static path; its absence selects the browser flow.\n\nBoth end the same way: one live request to `/mcp/auth/verify` that proves reachability, URL shape, token validity and scopes in a single round trip, then a `0600` write to `~/.anyslate/cli.json`. **On verification failure it writes nothing and exits non-zero.** Both preserve the `handle` you already had.\n\n**The sign-in target is explicit, never sticky.** Omitting `--api-url` always means\n**production**, even if your config currently points somewhere else — the stored value is\ndeliberately not inherited. If it does point elsewhere, the CLI says so and shows you the\nflag to use:\n\n```\nanyslate: signing in to production (https://mcp.anyslate.io).\nanyslate: your config points at https://…workers.dev — pass `--api-url https://…workers.dev` to sign in there instead.\n```\n\nThis matters because the target ends up in a URL printed to your terminal and opened in a\nbrowser. A short command should never quietly aim at a non-production host.\n\nA token missing the `memory:write` scope is a **warning**, not a block - but `anyslate hook` needs it, so heed it.\n\n**Browser flow flags**\n\n- `--api-url <root>` - which environment to sign in to. Defaults to production. Every OAuth endpoint comes from that root's discovery documents.\n- `--no-browser` - print the authorization URL instead of launching anything. For SSH sessions and containers. The URL is printed either way, so a browser that fails to appear never leaves you stuck.\n- `--timeout <seconds>` - how long to wait for the callback. Default `180`.\n- `--handle <ID>` - store a capability handle alongside the credentials.\n\n**Static-token flags**\n\n- `--force` - write anyway, with a warning. For when you know the server is temporarily down.\n- `--no-verify` - skip the probe entirely. For air-gapped or offline setup.\n\n**What gets stored.** The browser flow writes an `oauth` block: the client id, the access token, the refresh token, the absolute expiry, and the token endpoint and `resource` it discovered (cached so a background refresh costs one request instead of three, and bound to the root so switching environments re-discovers rather than reusing the wrong one). The static flow writes `mcp_token`, exactly as before. Neither path touches the other's keys.\n\n**Refresh.** Handled automatically by whichever command needs it — `hook`, `checkpoint`, `upload-artifact`, `doctor`. It happens five minutes before expiry, and once more on a `401`. Since hooks can fire in parallel, refreshes are serialized with a lock file and the config is replaced atomically, so two concurrent hooks cannot lose each other's rotated token. A hook **never** opens a browser: if refresh fails there, it logs the reason and exits 0 like any other failure.\n\n### `anyslate logout`\n\nRevokes the OAuth session at the server's revocation endpoint (refresh token first — it's the 30-day one), then removes `oauth` and `mcp_token` from `~/.anyslate/cli.json`.\n\nRevocation is best effort. If the host is unreachable the credentials are still cleared and you get a warning, because a `logout` that refuses to run offline is a `logout` you cannot use when you most need one.\n\n`apiUrl`, `handle` and the cached client registration survive. The registration is not a credential, and re-registering costs one of the ten Dynamic Client Registrations allowed per hour.\n\n- `--local` - skip revocation and only clear the local file. The server-side session then stays live until it expires.\n\nIf `ANYSLATE_MCP_TOKEN` is set in your environment, `logout` says so: it overrides the config, so capture keeps working until you unset it.\n\n## Wiring into Claude Code\n\nClaude Code is the only host today with a native lifecycle-hook surface. Edit `~/.claude/settings.json` (create it if missing) and **merge** this into any `hooks` block already there - don't replace the file:\n\n```json\n{\n  \"hooks\": {\n    \"SessionStart\": [\n      { \"hooks\": [ { \"type\": \"command\", \"command\": \"anyslate hook session-start\" } ] }\n    ],\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Edit|Write|MultiEdit|Bash\",\n        \"hooks\": [ { \"type\": \"command\", \"command\": \"anyslate hook post-tool-use\" } ]\n      }\n    ],\n    \"Stop\": [\n      { \"hooks\": [ { \"type\": \"command\", \"command\": \"anyslate hook stop\" } ] }\n    ]\n  }\n}\n```\n\nRestart Claude Code, then run `anyslate doctor` - it parses this file and tells you whether the wiring took.\n\n**If `anyslate` isn't found when the hook fires**, Claude Code spawned it with a minimal `PATH`. Use the absolute path (`command -v anyslate` prints it) or `npx --yes @anyslate/cli hook session-start`. `anyslate doctor` checks this explicitly.\n\n## Git capture\n\nRequires a global install (the template ships in the package). The guard on the first line means a missing template stops the recipe *before* it touches your hook file - it can never leave you with an empty one:\n\n```bash\n# from the repo root\nsrc=\"$(npm root -g)/@anyslate/cli/templates/git/post-commit\"\n[ -s \"$src\" ] && install -m 0755 \"$src\" .git/hooks/post-commit \\\n  || echo \"template not found - install @anyslate/cli globally first (npm i -g @anyslate/cli)\"\n```\n\nOptional, exported before the next commit:\n\n```bash\nexport ANYSLATE_SESSION=<anyslate_session_id>  # routes commits into a specific memory\nexport ANYSLATE_HOST=git_post_commit           # host_hint advisory\nexport ANYSLATE_QUIET=1                        # swallow CLI stderr\n```\n\n`ANYSLATE_SESSION` is read by **this git template only** (it passes `--session` through); the CLI itself does not read it. It has no effect on Claude Code hooks, which don't need it - see the session-identity note above.\n\nCommit metadata (`repo`, `branch`, `sha`, `subject`, `author`, `stats`) is JSON-encoded into the capture's `conversation_excerpt`. It is not a set of first-class queryable fields.\n\n## FAQ\n\n**Do I have to type `anyslate ...` regularly?**\nNo. After setup, your AI tools call it automatically. The only commands you might type yourself are `checkpoint`, `upload-artifact`, and `doctor` when something looks wrong.\n\n**Will this slow down my AI tool?**\nEach hook invocation is three sequential HTTPS round trips (`initialize`, `notifications/initialized`, `tools/call`) sharing one 15-second timeout budget. Typically well under a second. It is fail-open, so a network blip logs to stderr and exits cleanly rather than blocking your session.\n\n**What if I want to disable capture for one session?**\nSet `ANYSLATE_DISABLE=1` in the shell where you're running the AI tool. The CLI makes no network call and captures nothing for that shell.\n\nSetting `ANYSLATE_MCP_TOKEN=` (empty) does **not** disable capture - an empty string falls through to the token in `~/.anyslate/cli.json`, so capture keeps running. Earlier versions of this README claimed otherwise; that claim was wrong. Use `ANYSLATE_DISABLE=1`.\n\n**Where does the data live?**\nIn your AnySlate workspace. The CLI is a stateless client; it sends events to the AnySlate MCP service over HTTPS and stores nothing locally beyond your credentials in `~/.anyslate/cli.json` and the local run logs described under `anyslate hook` (all mode 0600). The run logs redact anything token-shaped before writing.\n\n**Do I need to log in again every hour?**\nNo. The one-hour lifetime is the access token's; the CLI renews it in the background from a 30-day refresh token, including inside hooks running unattended. You'll re-authenticate roughly monthly, or whenever you run `anyslate logout`.\n\n**I'm on a headless box / over SSH. Can I still use the browser flow?**\nYes - `anyslate login --no-browser` prints the URL, you open it on a machine that has a browser, and it redirects back to `127.0.0.1` on the port the CLI is listening on. That only works if the browser can reach that loopback address, so from a remote box you'll want an SSH tunnel - or just use `--token`, which is what it's there for.\n\n**Is the CLI adding dependencies to do OAuth?**\nNo. It has no runtime dependencies. PKCE, the loopback listener and the browser launch are all `node:` builtins, and there's a test that fails the build if an import ever points outside them.\n\n**Can I see what was captured?**\nYes - **AI Memory → Activity** shows every captured row with its status (pending / merged / rejected / failed), and failures under the **Failed** tab carry the reason. High-risk items wait for approval; low-risk items promote on their own in 30-90 seconds.\n\n**How do I undo a bad capture?**\nOpen the Activity panel, find the row, click **Reject**. Pre-promotion rejections never touch your memory pages. Post-promotion: open the memory and edit or delete the merged content.\n\n**Does this work with tools that aren't Claude Code?**\nAuto-firing lifecycle hooks are Claude Code only - it's the only host with a native hook surface. For Cursor / Windsurf / Cline / Claude Desktop / ChatGPT, capture happens **inside the host** via the AnySlate MCP server (`/anyslate-new`, `/anyslate-continue`). The CLI on the side handles git capture, CI capture, and manual checkpoints from anywhere a shell command can run.\n\n**What's the privacy story?**\nBearer-token auth, optional capability handles that scope a token to one project or topic, HTTPS-only transport, payload caps. Tokens are revocable from the same dialog you minted them in. `notes` is clipped at **4,000 characters** and `conversation_excerpt` at **16,000 characters** - both are string lengths, not bytes, and clipping applies to the `hook` path only (`checkpoint --note` is sent as given).\n\n**What if I'm offline?**\nHooks fail open - no error to your AI tool, just no capture for those events. There is no offline queue, so events missed while offline are not retroactively captured. The failure is recorded locally and surfaced by `anyslate doctor`.\n\n## Tests\n\n```bash\nnpm test    # node --test, no external deps\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}