@anyslate/cli 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,57 +1,119 @@
1
- # @anyslate/cli
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://mcp.anyslate.io/logo-full-dark.png">
4
+ <img src="https://mcp.anyslate.io/logo-full-light.png" alt="AnySlate" width="280">
5
+ </picture>
6
+ </p>
2
7
 
3
- The bridge that lets your AI tools quietly tell AnySlate what you've been working on - without you having to remember to checkpoint anything.
8
+ <h1 align="center">@anyslate/cli</h1>
4
9
 
5
- ## What this is
10
+ <p align="center">
11
+ <strong>Your AI tools remember what you did. Automatically.</strong><br>
12
+ Lifecycle hooks, git commits and CI runs → your AnySlate memory, without you lifting a finger.
13
+ </p>
6
14
 
7
- A small command-line tool that runs on your machine. Your AI tools (Claude Code, git, CI) call it on your behalf as you work. It records what's happening into your AnySlate **Activity feed** - file edits, shell commands, commits - automatically and continuously.
15
+ <p align="center">
16
+ <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>
17
+ <a href="https://nodejs.org"><img alt="node" src="https://img.shields.io/node/v/@anyslate/cli?color=f45f5f&labelColor=252c3e"></a>
18
+ <img alt="dependencies" src="https://img.shields.io/badge/runtime%20deps-0-f45f5f?labelColor=252c3e">
19
+ <img alt="license" src="https://img.shields.io/npm/l/@anyslate/cli?color=f45f5f&labelColor=252c3e">
20
+ </p>
8
21
 
9
- Think of it like a Fitbit, but for your AI work. You don't tell a Fitbit "I just took 47 steps." You wear it, walk around, the count updates. Same shape here. You install this once, paste a small config snippet into your AI tool's settings, and from that point on, capture happens in the background while you work.
22
+ ---
10
23
 
11
- **You almost never run a CLI command yourself.** The tool exists to be invisible plumbing that other tools call. The setup below takes 5 minutes and ends with `anyslate doctor` telling you it works.
24
+ ## What is this?
12
25
 
13
- ## Where it fits in your workflow
26
+ **AnySlate** gives your AI tools a long-term memory — a place where decisions, tasks and
27
+ work history live, so Claude, Cursor or Codex can pick up where you left off instead of
28
+ starting cold every session.
14
29
 
15
- Most days you'll never type `anyslate` yourself. Here's what actually happens:
30
+ **This CLI is how that memory gets filled in when no AI is watching.**
16
31
 
17
- | Trigger | Who calls the CLI | What lands |
32
+ An AI can only record something if it decides to call a tool. But a lot of your real work
33
+ happens outside that: you commit code, CI deploys, you run the test suite, you make a call
34
+ while staring at a terminal. None of it has an AI present to write it down.
35
+
36
+ This CLI closes that gap. Your tools call it for you:
37
+
38
+ ```
39
+ Claude Code fires a hook ─┐
40
+ git runs post-commit ─┼─→ anyslate ─→ AnySlate memory
41
+ CI finishes a deploy ─┘
42
+ ```
43
+
44
+ Think of it like a fitness tracker for your engineering work. You don't tell a fitness
45
+ tracker "I just took 47 steps." You wear it, you walk, the number goes up. Same idea:
46
+ install this once, paste one config block, and capture happens in the background forever.
47
+
48
+ > **You will almost never type `anyslate` yourself.** It is plumbing that other tools call.
49
+ > Setup takes about five minutes and ends with `anyslate doctor` confirming it works.
50
+
51
+ ## Quick start
52
+
53
+ ```bash
54
+ npm i -g @anyslate/cli # 1. install
55
+ anyslate login # 2. sign in (opens your browser)
56
+ anyslate doctor # 3. confirm it works
57
+ ```
58
+
59
+ Then paste the hook block into `~/.claude/settings.json` — see
60
+ [Wiring into Claude Code](#wiring-into-claude-code) — and restart Claude Code. Done.
61
+
62
+ ## What actually gets captured
63
+
64
+ | Trigger | Who calls the CLI | What lands in your memory |
18
65
  |---|---|---|
19
- | You open a Claude Code session | Claude Code's `SessionStart` hook | Session-start activity; the AnySlate session is created on first sight |
20
- | Claude edits `routes/auth.ts` | Claude Code's `PostToolUse` hook | "Edited routes/auth.ts" in the activity ledger (**awaits your approval** - see below) |
21
- | Claude runs `npm test` | Claude Code's `PostToolUse` hook | Command + output in the activity ledger |
22
- | You `git commit` | Git's `post-commit` hook | Commit metadata + diff stats captured |
23
- | Teammate merges your PR | GitHub webhook (no CLI needed) | PR-merged event captured |
24
- | You close the laptop | Claude Code's `Stop` hook | Session-end marker captured |
25
- | **You manually want to checkpoint a decision** | **You typing `anyslate checkpoint ...`** | **Captured immediately** |
26
- | **You want to upload a file as an artifact** | **You typing `anyslate upload-artifact ...`** | **File stored, returns `cloud://artifact/<id>`** |
66
+ | You open a Claude Code session | `SessionStart` hook | Session start + working directory; the AnySlate memory is created on first sight |
67
+ | Claude edits `routes/auth.ts` | `PostToolUse` hook | `Edited routes/auth.ts` in the activity ledger |
68
+ | Claude runs `npm test` | `PostToolUse` hook | Command + exit status in the activity ledger |
69
+ | You `git commit` | git `post-commit` hook | Commit metadata and diff stats |
70
+ | A teammate merges your PR | GitHub webhook (no CLI needed) | PR-merged event |
71
+ | You close the laptop | `Stop` hook | Session-end marker |
72
+ | **You want to record a decision** | **you typing `anyslate checkpoint`** | **Captured immediately, as a decision** |
73
+ | **You want to attach a file** | **you typing `anyslate upload-artifact`** | **Stored, returns `cloud://artifact/<id>`** |
27
74
 
28
- The first six rows are automatic - that's what the lifecycle hooks do. The bottom two are the rare moments where you'd actually type something yourself.
75
+ The first six rows are automatic. The last two are the rare moments you'd type something.
29
76
 
30
- ### What hook captures actually write - read this before you form expectations
77
+ ### What hook captures write read this before forming expectations
31
78
 
32
- Hook captures land in a dedicated **`## Activity Ledger`** section of your memory page: which files were touched, which commands ran, what exit status they returned. That is the whole contract. The ledger is capped (25 files / 15 commands per entry) and is built deterministically - no LLM runs in the hook path, so capture never bills against your AI quota.
79
+ Hook captures land in a dedicated **`## Activity Ledger`** section of your memory page:
80
+ which files were touched, which commands ran, what exit status they returned. That is the
81
+ whole contract. The ledger is capped (25 files / 15 commands per entry) and is built
82
+ deterministically — **no LLM runs in the hook path**, so background capture never bills
83
+ against your AI quota.
33
84
 
34
- Hook captures **do not** write to the **Decisions** or **Open Tasks** sections. Those stay human- and LLM-authored, and are populated by `anyslate checkpoint`, by the in-host MCP flow (`/anyslate-new`, `/anyslate-continue`), or by you editing the page. A hook firing on `Bash` has no decision in it to extract, and the product no longer pretends otherwise.
85
+ Hook captures **do not** write to **Key Decisions** or **Open Tasks**. Those stay human-
86
+ and LLM-authored. A hook firing on `Bash` has no decision inside it to extract, and the
87
+ product no longer pretends otherwise.
35
88
 
36
- If you want a decision recorded, type it:
89
+ If you want a decision recorded, say so:
37
90
 
38
91
  ```bash
39
92
  anyslate checkpoint --note "Decided: Redis for session cache, not memcached"
40
93
  ```
41
94
 
42
- ### Approval: file edits wait, shell commands don't
43
-
44
- The classifier treats `Edit` / `Write` / `MultiEdit` as **high-risk** - those rows stay pending in **AI Memory → Activity** until you approve them. `Bash` and session lifecycle events classify **low-risk** and auto-promote on their own.
95
+ ### Approval: hook captures auto-promote
45
96
 
46
- This is deliberate, and it means a Claude Code session that edits a lot of files produces a queue you have to work through. Budget a few seconds a day for it, or approve in bulk.
97
+ Every lifecycle hook `Edit` / `Write` / `MultiEdit`, `Bash`, and the session start/stop
98
+ markers — classifies **low-risk** and auto-promotes on its own, 30–90 seconds after
99
+ capture. An edit-heavy session does **not** build an approval queue.
47
100
 
48
- ## What you get back
101
+ Rows still wait for approval in **AI Memory → Activity** when they are genuinely riskier: a
102
+ capture aimed at a different session than the one that produced it, a decision that
103
+ supersedes an earlier one, a submission from an untrusted source, or one carrying
104
+ `confidence < 0.8`.
49
105
 
50
- After the setup is in place:
106
+ ## Why bother
51
107
 
52
108
  - **You stop forgetting to checkpoint.** Capture happens whether you remember or not.
53
- - **AnySlate sees what actually happened, not just what the AI claimed.** Git knows what files really changed. The hooks know what commands really ran.
54
- - **Your work follows you across tools.** Claude Code at home, Cursor at work, terminal in between - all feed the same memory. (Cursor and Windsurf feed it through the in-host MCP server, not this CLI - see the FAQ.)
109
+ - **You see what actually happened, not just what the AI claimed.** If the agent says it
110
+ implemented `TokenRefreshService` and the diff is whitespace, that discrepancy is
111
+ visible. You trust the AI because you can check, not because you have to assume.
112
+ - **Your work follows you across tools.** Claude Code at home, Cursor at work, a terminal
113
+ in between — all feeding one memory. (Cursor and Windsurf feed it through the in-host
114
+ MCP server rather than this CLI — see the [FAQ](#faq).)
115
+ - **Nothing is canonical until it is.** Anything pending can be rejected without ever
116
+ touching a memory page.
55
117
 
56
118
  ## Install
57
119
 
@@ -75,22 +137,56 @@ Requires Node ≥ 20.
75
137
 
76
138
  ## Authenticate
77
139
 
78
- Mint an MCP 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.
140
+ ```bash
141
+ anyslate login
142
+ ```
143
+
144
+ That'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.
145
+
146
+ What 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.
147
+
148
+ The 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.
149
+
150
+ Then confirm the whole install:
151
+
152
+ ```bash
153
+ anyslate doctor
154
+ ```
155
+
156
+ ### Signing in to a non-production environment
157
+
158
+ ```bash
159
+ anyslate login --api-url https://anyslate-mcp-service-development.example.workers.dev
160
+ ```
161
+
162
+ `--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.
163
+
164
+ If discovery fails, `login` says which host it tried and stops. It will not fall back to guessed endpoint paths.
165
+
166
+ ### Static tokens, for CI
79
167
 
80
- Copy the token (it starts with `as_mcp_`; you only see it once), then:
168
+ Browsers are in short supply on a build agent. The token path is unchanged and stays supported:
81
169
 
82
170
  ```bash
83
171
  anyslate login --token as_mcp_your_token_here
172
+ # or, with no config file at all:
173
+ export ANYSLATE_MCP_TOKEN=as_mcp_your_token_here
84
174
  ```
85
175
 
86
- `login` 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.
176
+ Mint 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).
87
177
 
88
- Then confirm the whole install:
178
+ `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.
179
+
180
+ Static 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.
181
+
182
+ ### Signing out
89
183
 
90
184
  ```bash
91
- anyslate doctor
185
+ anyslate logout
92
186
  ```
93
187
 
188
+ Revokes 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.
189
+
94
190
  ### Environment variables
95
191
 
96
192
  ```bash
@@ -104,6 +200,14 @@ export ANYSLATE_STDIN_TIMEOUT_MS=10000 # optional, stdin idle timeout
104
200
 
105
201
  Env vars override `~/.anyslate/cli.json`.
106
202
 
203
+ The bearer the CLI actually sends is resolved in this order, first hit wins:
204
+
205
+ 1. `ANYSLATE_MCP_TOKEN` — a static token from the environment. Never refreshed.
206
+ 2. The OAuth access token from `cli.json`, refreshed if it is expired or close to it.
207
+ 3. The static `mcp_token` in `cli.json`.
208
+
209
+ `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.
210
+
107
211
  `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`.
108
212
 
109
213
  Capability 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.
@@ -134,8 +238,10 @@ anyslate: using "https://mcp.anyslate.io". Run `anyslate login --api-url https:/
134
238
  anyslate hook <session-start|post-tool-use|stop> [--strict] [--session <id>] [--note <text>] [--host <hint>]
135
239
  anyslate checkpoint --note "..." [--kind milestone] [--session <id>] [--host <hint>] [--source <source>]
136
240
  anyslate upload-artifact --session <id> --kind <kind> [--file <path>] [--language <lang>] [--path-hint <path>]
241
+ anyslate login [--api-url <URL>] [--no-browser] [--timeout <seconds>] [--handle <ID>]
137
242
  anyslate login --token <BEARER> [--handle <ID>] [--api-url <URL>] [--force] [--no-verify]
138
- anyslate doctor [--deep]
243
+ anyslate logout [--local]
244
+ anyslate doctor [--deep] [--refresh]
139
245
  anyslate version | help
140
246
  ```
141
247
 
@@ -143,7 +249,11 @@ anyslate version | help
143
249
 
144
250
  The 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.
145
251
 
146
- It checks, in order: which config layer won (env var vs `cli.json`) per key; that a token is present; 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; 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`.
252
+ It 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`.
253
+
254
+ The 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).
255
+
256
+ An 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.
147
257
 
148
258
  Reading the output:
149
259
 
@@ -151,7 +261,7 @@ Reading the output:
151
261
  - **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).
152
262
  - **FAIL** - this is why capture isn't working. Each FAIL prints the specific remedy.
153
263
 
154
- `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.
264
+ `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.
155
265
 
156
266
  ### `anyslate hook ...`
157
267
 
@@ -185,14 +295,52 @@ Content is read as UTF-8. Binary files are not supported and are refused rather
185
295
 
186
296
  ### `anyslate login`
187
297
 
188
- Verifies, then writes `~/.anyslate/cli.json` with mode `0600`. Idempotent - preserves the apiUrl/handle if you only update the token (the preserved apiUrl is re-normalized and re-verified).
298
+ Two paths, one config file. `--token` selects the static path; its absence selects the browser flow.
299
+
300
+ Both 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.
301
+
302
+ **The sign-in target is explicit, never sticky.** Omitting `--api-url` always means
303
+ **production**, even if your config currently points somewhere else — the stored value is
304
+ deliberately not inherited. If it does point elsewhere, the CLI says so and shows you the
305
+ flag to use:
306
+
307
+ ```
308
+ anyslate: signing in to production (https://mcp.anyslate.io).
309
+ anyslate: your config points at https://…workers.dev — pass `--api-url https://…workers.dev` to sign in there instead.
310
+ ```
311
+
312
+ This matters because the target ends up in a URL printed to your terminal and opened in a
313
+ browser. A short command should never quietly aim at a non-production host.
314
+
315
+ A token missing the `memory:write` scope is a **warning**, not a block - but `anyslate hook` needs it, so heed it.
316
+
317
+ **Browser flow flags**
189
318
 
190
- Verification is a single request that proves reachability, URL shape, token existence, revocation, expiry and scopes in one round trip. **On failure it writes nothing and exits non-zero.** Two escapes:
319
+ - `--api-url <root>` - which environment to sign in to. Defaults to production. Every OAuth endpoint comes from that root's discovery documents.
320
+ - `--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.
321
+ - `--timeout <seconds>` - how long to wait for the callback. Default `180`.
322
+ - `--handle <ID>` - store a capability handle alongside the credentials.
323
+
324
+ **Static-token flags**
191
325
 
192
326
  - `--force` - write anyway, with a warning. For when you know the server is temporarily down.
193
327
  - `--no-verify` - skip the probe entirely. For air-gapped or offline setup.
194
328
 
195
- A token missing the `memory:write` scope is a **warning**, not a block - but `anyslate hook` needs it, so heed it.
329
+ **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.
330
+
331
+ **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.
332
+
333
+ ### `anyslate logout`
334
+
335
+ Revokes 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`.
336
+
337
+ Revocation 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.
338
+
339
+ `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.
340
+
341
+ - `--local` - skip revocation and only clear the local file. The server-side session then stays live until it expires.
342
+
343
+ If `ANYSLATE_MCP_TOKEN` is set in your environment, `logout` says so: it overrides the config, so capture keeps working until you unset it.
196
344
 
197
345
  ## Wiring into Claude Code
198
346
 
@@ -258,7 +406,16 @@ Set `ANYSLATE_DISABLE=1` in the shell where you're running the AI tool. The CLI
258
406
  Setting `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`.
259
407
 
260
408
  **Where does the data live?**
261
- In 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 token in `~/.anyslate/cli.json` and the local run logs described under `anyslate hook` (all mode 0600).
409
+ In 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.
410
+
411
+ **Do I need to log in again every hour?**
412
+ No. 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`.
413
+
414
+ **I'm on a headless box / over SSH. Can I still use the browser flow?**
415
+ Yes - `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.
416
+
417
+ **Is the CLI adding dependencies to do OAuth?**
418
+ No. 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.
262
419
 
263
420
  **Can I see what was captured?**
264
421
  Yes - **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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anyslate/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "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.",
5
5
  "type": "module",
6
6
  "bin": {