@plotio/cli 0.2.1 → 0.3.0

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.
Files changed (3) hide show
  1. package/README.md +88 -155
  2. package/dist/plot.js +57 -25
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,7 +3,9 @@
3
3
  `plot`: read Plot pages, follow a meeting while it happens, and post back, from
4
4
  any agent with a shell (Claude Code, Codex CLI, Amp, OpenCode) or a script.
5
5
 
6
- Design and reasoning: `plans/agent-cli-and-mcp.md` at the repo root.
6
+ Plot records conversations — meetings, calls, voice notes — as shared pages: a
7
+ transcript of who said what, plus topics Plot's own model keeps up to date as
8
+ people talk.
7
9
 
8
10
  ## Install
9
11
 
@@ -12,183 +14,114 @@ npx @plotio/cli help agents # no install
12
14
  npm install -g @plotio/cli # or install the `plot` binary
13
15
  ```
14
16
 
15
- Node 22 or later (the repo pins 24 in `.nvmrc`). In this repo,
16
- `pnpm --filter @plotio/cli start <command>` runs the TypeScript directly (no
17
- `--` before the command: pnpm passes it through, and `plot` would read it as
18
- the command).
17
+ Node 22 or later.
18
+
19
+ **An agent should run `plot help agents` first.** It is the whole guide, and it
20
+ is the one that matches the version installed. `plot help <command>` lists a
21
+ command's options; everything below is the map, not a second copy of it.
19
22
 
20
23
  ## Getting access
21
24
 
22
- Two kinds of credential, both `plot_agent_sk_…` tokens that Plot stores only
23
- as a hash:
25
+ Two kinds of credential, both `plot_agent_sk_…` tokens that Plot stores only as
26
+ a hash. Each is kept once redeemed, so later shells need nothing.
24
27
 
25
- - **A page.** Someone picks "Copy agent instructions" on a page, which gives
26
- a one-time `temp_code_…` (ten minutes, single use).
28
+ - **A page.** Someone picks "Copy agent instructions" on a page, which gives a
29
+ one-time `temp_code_…` (ten minutes, single use).
27
30
  `plot open temp_code_… --agent "Claude Code"` redeems it for a token that
28
31
  reads that one page (24 hours from the last read, a week at most).
29
- - **A workspace.** `plot login --agent "Claude Code"` signs in the way Claude
30
- Code does: OAuth authorization code with PKCE (S256), client id `plot-cli`,
31
- against the authorization server the API names in
32
- `/.well-known/oauth-authorization-server`. So `--api` at a local or preview
33
- deployment signs in on that deployment's own web app, never production's.
34
- The code is exchanged at the token endpoint, `/v1/me` names the workspace,
35
- and the token is stored as that API's workspace grant.
36
-
37
- One verifier and state make two authorize URLs that differ only in
38
- `redirect_uri`, and the server binds a code to the one it was approved with:
39
- a loopback one (`http://127.0.0.1:<port>/callback`) and the out-of-band
40
- `urn:ietf:wg:oauth:2.0:oob`, for which the consent page shows the code
41
- instead of redirecting. The loopback URL is only ever opened, never printed,
42
- because a browser on any other machine would be redirected into a dead port.
43
-
44
- - **On your own machine.** The browser opens on the loopback URL; approve
45
- and it calls the terminal back by itself. Nothing to paste.
46
- - **Browser elsewhere (SSH, another computer).** The terminal prints the
47
- out-of-band URL under "A browser tab should have opened. If it did not, or
48
- it is on another machine, open this URL:". Approve there, and paste the
49
- code the page shows at `Paste code here if prompted:`. Whichever arrives
50
- first wins, within ten minutes. `--no-browser` prints only that URL and
51
- does not listen at all.
52
- - **An agent or a sandbox (no TTY).** Nothing listens and nothing waits.
53
- `plot login` prints the out-of-band URL and
54
- `After approving, run: plot login --code <code>`, saves a pending login
55
- (API base, verifier, state, agent name, time) in
56
- `$XDG_CONFIG_HOME/plot/pending-login.json` at `0600`, and exits 0. The agent
57
- hands the URL to its person, who approves and sends the code back;
58
- `plot login --code <code>` exchanges it, stores the grant, deletes the
59
- pending file and prints `Connected to <workspace>`. A pending login lasts
60
- ten minutes, and `--code` works at a terminal too.
32
+ - **A workspace.** `plot login --agent "Claude Code"` signs in with OAuth in a
33
+ browser, against the authorization server the API itself names — so `--api`
34
+ at a local or preview deployment signs in on that deployment's own web app,
35
+ never production's.
36
+
37
+ `plot login` ends three ways, and what it can see decides which:
38
+
39
+ - **This machine's browser.** It opens, you approve, and it hands the terminal
40
+ the credential itself. Nothing to paste.
41
+ - **A browser somewhere else** (SSH, another computer). Approve at the URL the
42
+ terminal prints, then paste the code that page shows. Ten minutes.
43
+ `--no-browser` prints that URL and listens for nothing.
44
+ - **An agent or a sandbox** (no TTY). Nothing waits: `plot login` prints the URL
45
+ and exits 0. Hand the URL to your person; they approve and send back the code
46
+ the page showed. `plot login --code <code>` finishes the login, within ten
47
+ minutes, and prints the workspace.
61
48
 
62
49
  ## Commands
63
50
 
64
- | Command | Does |
65
- | ------------------------------------------------- | ----------------------------------------------------------------------------- |
66
- | `plot open <temp_code_…> [--agent <name>]` | Redeem a handoff code and store the page token |
67
- | `plot login [--agent] [--no-browser]` | Connect a workspace: sign in and approve in the browser |
68
- | `plot login --code <code>` | Finish a login started without a terminal, with the code the browser showed |
69
- | `plot logout [<page>] [--all]` | Revoke a credential on the server, then forget it |
70
- | `plot whoami [<page>] [--json]` | What the credential acts for, its scope and expiry |
71
- | `plot pages [--limit <n>] [--next <c>] [--json]` | Pages the workspace grant can read, newest first |
72
- | `plot read <page> [--json]` | The page as Markdown (topics included), or as JSON; stores the updates cursor |
73
- | `plot watch <page> [--level …] [options]` | Wait for the next update, print it, store the cursor |
74
- | `plot topics <page>` | Topics as JSON, with the quotes behind each point |
75
- | `plot search <query> [--limit] [--next] [--json]` | Search every page the workspace grant can read |
76
- | `plot post <page> [--branch <id>] [--body <t>]` | Post a message (body from `--body`, else stdin), idempotent per run |
77
- | `plot mcp-config [--host <h>] [--write] [<page>]` | The MCP server block for a host, pointing at `<api>/mcp` |
78
- | `plot help agents` | The guide for an AI agent |
51
+ | Command | Does |
52
+ | ----------------------------------------------------------- | ----------------------------------------------------------------------------- |
53
+ | `plot open <temp_code_…> [--agent <name>]` | Redeem a handoff code and store the page token |
54
+ | `plot login [--agent] [--no-browser]` | Connect a workspace: sign in and approve in the browser |
55
+ | `plot login --code <code>` | Finish a login started without a terminal, with the code the browser showed |
56
+ | `plot logout [<page>] [--all]` | Revoke a credential on the server, then forget it |
57
+ | `plot whoami [<page>] [--json]` | What the credential acts for, its scope and expiry |
58
+ | `plot pages [--limit <n>] [--next <c>] [--json]` | Pages the workspace grant can read, newest first |
59
+ | `plot read <page> [--json]` | The page as Markdown (topics included), or as JSON; stores the updates cursor |
60
+ | `plot watch <page> [--level …] [options]` | Wait for the next update, print it, store the cursor |
61
+ | `plot topics <page>` | Topics as JSON, with the quotes behind each point |
62
+ | `plot search <query> [--limit] [--next] [--json]` | Search every page the workspace grant can read |
63
+ | `plot post <page> [--branch <id>] [--body <t>]` | Post a message (body from `--body`, else stdin), idempotent per run |
64
+ | `plot mcp-config [--host <h>] [--token] [--write] [<page>]` | The MCP server block for a host: the `<api>/mcp` URL, signed in with OAuth |
65
+ | `plot help agents` | The guide for an AI agent |
79
66
 
80
67
  `<page>` is a page id or a page URL (`https://www.plot.io/p/<id>`).
81
68
 
82
- Every command takes `--api <url>`, `--token <token>` and `-h`/`--help`.
83
- `plot help <command>` lists a command's options.
84
-
85
- ### `watch`
86
-
87
- One command is one turn: it blocks until something qualifies, prints only
88
- what is new since the last `watch` of that page, stores the new cursor and
89
- exits. Levels: `substantial` (default, when Plot's topic model completed a
90
- run), `every` (any new words), `topics` (a topic changed). Each request asks
91
- the server to hold it for up to 240 seconds (`wait=240`, kept under the 300
92
- seconds Node's fetch waits for response headers); where the server
93
- answers at once instead (Convex reached directly, in local dev and previews),
94
- `watch` pauses `--interval` seconds (default 3) and asks again.
95
- `--timeout <seconds>` gives up, cursor unchanged; keep it under your shell
96
- tool's own timeout. `--from-start` and `--cursor` override the stored cursor,
97
- and `--partial` includes words still being spoken (marked `interim`).
98
-
99
- An update is `{notice, page, level, lines, topics, room, lastRunAt, cursor}`:
100
- `lines` carry `authorName`, `text` and the flags `continues`, `revised`,
101
- `removed`, `interim`; `topics` carry their `points` with source quotes;
102
- `room` is who is present. `plot read` also stores the cursor (from its
103
- `X-Plot-Updates-Cursor` header), so a `watch` after a `read` brings only what
104
- was said after it. A stored cursor the server refuses (`bad_cursor`,
105
- `wrong_cursor_page`) is dropped and the watch starts over once.
106
-
107
- ### `pages` and `search`
108
-
109
- Both page with an opaque `next`; keep passing `--next` until the output has
110
- no "More:" line, since a short batch does not mean the end. A page token on
111
- either is refused (`wrong_scope`): they need `plot login`.
112
-
113
- ### `post`
114
-
115
- Each run generates one idempotency `id` and reuses it if the connection drops
116
- or a gateway answers 502 to 504, so the message lands once. A refusal is never
117
- retried: `read_only` means the person who granted access cannot post there.
118
-
119
- ### `mcp-config`
120
-
121
- Hosts: `claude-code` (default, `.mcp.json`), `cursor` (`~/.cursor/mcp.json`),
122
- `vscode` (`.vscode/mcp.json`), `zed` (its `settings.json`) and
123
- `claude-desktop` (a connector added in its settings, OAuth only). The
124
- `Authorization` header names the `PLOT_TOKEN` environment variable in the
125
- host's own syntax (`${PLOT_TOKEN}` for Claude Code, `${env:PLOT_TOKEN}` for
126
- Cursor and VS Code) rather than holding the token, because `.mcp.json` and
127
- `.vscode/mcp.json` are usually committed. The token itself (the stored
128
- workspace one, or `<page>`'s) is printed to stderr as an `export` line to run
129
- where the host starts. `--inline-token` puts the token in the header instead
130
- (and makes a file it writes `0600`); Zed cannot read a variable there, so its
131
- block always holds the token. `--no-token` leaves the header out so the host
132
- signs in with OAuth. `--write` merges the block into the host's file, keeping
133
- every other server, and refuses a file whose top level is not a JSON object.
134
-
135
- ## Environment
136
-
137
- | Variable | Meaning |
138
- | ----------------- | ------------------------------------------------------------------------- |
139
- | `PLOT_API_URL` | API base. Default `https://agents.plot.io`; a deployment's `.convex.site` |
140
- | | host for local dev and previews. `--api` wins over it. |
141
- | `PLOT_TOKEN` | Use this token instead of the store (a sandbox or CI job). |
142
- | `PLOT_AGENT_NAME` | Default `--agent`. Inside Claude Code the default is "Claude Code". |
143
- | `XDG_CONFIG_HOME` | Where the credential store lives (below). |
69
+ Every command takes `--api <url>`, `--token <token>`, `--json` and `-h`/`--help`
70
+ (except `mcp-config`, where `--token` is a switch taking no value).
144
71
 
145
- Token resolution for a page command: `--token`, then `PLOT_TOKEN`, then the
146
- page's stored grant, then the stored workspace grant. A stored token the
147
- server calls expired or revoked is removed and the next one tried.
72
+ `watch` is one update per run: it blocks until something qualifies at its
73
+ `--level`, prints only what is new since the last `watch` or `read` of that
74
+ page, stores the cursor and exits — so the next run continues where it left
75
+ off. Keep `--timeout` under your shell tool's own. See `plot help watch`.
148
76
 
149
- ## Where credentials live
77
+ ## `mcp-config`
150
78
 
151
- `$XDG_CONFIG_HOME/plot/credentials.json`, by default
152
- `~/.config/plot/credentials.json`, mode `0600` in a `0700` directory. Keyed by
153
- API base, so production's and a preview's tokens never collide:
79
+ By default the block is the URL and nothing else, which is the standard way to
80
+ reach a remote MCP server: the host gets a 401 pointing at Plot's OAuth
81
+ metadata, registers itself, and has you sign in the first time it connects.
154
82
 
155
83
  ```json
156
84
  {
157
- "https://agents.plot.io": {
158
- "workspace": {
159
- "token": "plot_agent_sk_…",
160
- "workspace": "Acme",
161
- "expiresAt": "…"
162
- },
163
- "pages": {
164
- "<pageId>": {
165
- "token": "plot_agent_sk_…",
166
- "expiresAt": "…",
167
- "title": "…",
168
- "cursor": "…"
169
- }
170
- }
85
+ "mcpServers": {
86
+ "plot": { "type": "http", "url": "https://agents.plot.io/mcp" }
171
87
  }
172
88
  }
173
89
  ```
174
90
 
175
- A page entry can hold just a `cursor` when that page is watched through the
176
- workspace grant. `plot logout --all` revokes and removes everything for the
177
- current API base.
91
+ Hosts: `claude-code` (default, `.mcp.json`), `cursor`, `vscode`, `zed` and
92
+ `claude-desktop`. `--write` merges the block into that host's file, keeping
93
+ every other server. For Claude Code,
94
+ `claude mcp add --transport http --scope project plot <api>/mcp` is equivalent.
178
95
 
179
- ## Development
96
+ `--token` adds a bearer header instead, for where OAuth cannot be used — a page
97
+ grant, which has no OAuth flow, or a sandbox or CI job with no browser. The
98
+ header names `PLOT_TOKEN` rather than holding the token, since these files are
99
+ usually committed. See `plot help mcp-config`.
180
100
 
181
- ```bash
182
- pnpm --filter @plotio/cli test # node --test, no network
183
- pnpm --filter @plotio/cli type-check
184
- pnpm --filter @plotio/cli lint
185
- pnpm --filter @plotio/cli build # dist/plot.js, one bundled file
186
- ```
101
+ ## Environment
187
102
 
188
- `dist/` is not committed; `prepack` runs the build, so `pnpm pack` and
189
- `pnpm publish` from a clean tree still ship `dist/plot.js`.
103
+ | Variable | Meaning |
104
+ | ----------------- | ---------------------------------------------------------------------------- |
105
+ | `PLOT_API_URL` | API base. Default `https://agents.plot.io`; `https://agents-staging.plot.io` |
106
+ | | for staging; a deployment's `.convex.site` host for local dev and previews. |
107
+ | | `--api` wins over it. |
108
+ | `PLOT_TOKEN` | Use this token instead of the store (a sandbox or CI job). |
109
+ | `PLOT_AGENT_NAME` | Default `--agent`. Inside Claude Code the default is "Claude Code". |
110
+ | `XDG_CONFIG_HOME` | Where the credential store lives (below). |
111
+
112
+ Token resolution for a page command: `--token`, then `PLOT_TOKEN`, then the
113
+ page's stored grant, then the stored workspace grant. A stored token the server
114
+ calls expired or revoked is removed and the next one tried.
190
115
 
191
- All HTTP lives in `src/api.ts`. The build bundles with esbuild rather than
192
- emitting with `tsc`, because `@plot/shared` (token shapes) is a private
193
- workspace package: the bundle inlines it, so the
194
- published package has no dependencies at all.
116
+ ## Where credentials live
117
+
118
+ `$XDG_CONFIG_HOME/plot/credentials.json`, by default
119
+ `~/.config/plot/credentials.json`, mode `0600` in a `0700` directory. It is
120
+ keyed by API base, so production's tokens and a preview's never collide — and
121
+ so changing `--api` or `PLOT_API_URL` changes which credential is in play. It
122
+ also holds each page's last `watch` cursor, which is how a new shell resumes.
123
+ `plot logout --all` revokes and removes everything for the current API base.
124
+
125
+ A `plot login` with no TTY leaves a `pending-login.json` beside it, same mode,
126
+ holding what `--code` needs to finish the login. It is deleted on success and
127
+ is useless after ten minutes.
package/dist/plot.js CHANGED
@@ -21,7 +21,7 @@ Run "plot --help" for usage.`, 2);
21
21
  };
22
22
 
23
23
  // src/version.ts
24
- var VERSION = "0.2.1";
24
+ var VERSION = "0.3.0";
25
25
 
26
26
  // src/api.ts
27
27
  var DEFAULT_API_URL = "https://agents.plot.io";
@@ -454,6 +454,13 @@ Write back (only if the person who gave you access can post):
454
454
  <page> is a page id or a link like https://www.plot.io/p/<id>.
455
455
 
456
456
  ${DATA_NOT_INSTRUCTIONS}
457
+ Connect an MCP host (Claude Code, Cursor, VS Code, Zed) instead:
458
+ plot mcp-config --host <host> The host's config block: the URL only. The
459
+ host signs in to Plot itself (OAuth).
460
+ plot mcp-config --token A bearer header instead, for a host with no
461
+ browser to sign in with (a sandbox, CI).
462
+ plot mcp-config <page> A page grant, which always needs the header.
463
+
457
464
  Keep tokens out of files you commit. "plot logout" gives a credential up.`;
458
465
  var COMMAND_HELP = {
459
466
  open: `plot open <temp_code_\u2026> [--agent <name>]
@@ -531,20 +538,25 @@ appears as the person who granted access, marked as posted via your agent.
531
538
  Needs a member who can post; a viewer's grant is refused (read_only). Each
532
539
  run sends one idempotency id, so a retry after a dropped connection posts
533
540
  once.`,
534
- "mcp-config": `plot mcp-config [--host <host>] [--write] [--no-token]
541
+ "mcp-config": `plot mcp-config [--host <host>] [--write] [--token]
535
542
  [--inline-token] [<page>]
536
543
 
537
- Prints the MCP server config for a host, pointing at <api>/mcp with a bearer
538
- header where the host supports headers. Hosts: claude-code (default),
539
- cursor, vscode, zed, claude-desktop. --write merges it into the host's
540
- config file instead.
541
- The header names the PLOT_TOKEN environment variable rather than holding the
542
- token, because .mcp.json and .vscode/mcp.json are usually committed; the
543
- token (the stored workspace one, or <page>'s) is printed separately for you
544
- to export where the host runs. Zed cannot read an environment variable
545
- there, so its block always holds the token.
546
- --inline-token Put the token itself in the header
547
- --no-token Leave the header out, so the host signs in with OAuth`
544
+ Prints the MCP server config for a host: just the URL, <api>/mcp. The host
545
+ signs in to Plot with OAuth the first time it connects (it opens the
546
+ browser and keeps the token itself), so no token is read or printed. Hosts:
547
+ claude-code (default), cursor, vscode, zed, claude-desktop (a connector in
548
+ its own settings). --write merges it into the host's config file instead,
549
+ keeping every other server.
550
+
551
+ --token is for when OAuth cannot be used: a page grant, which has no OAuth
552
+ flow (a <page> argument implies --token), or a sandbox or CI job with no
553
+ browser to sign in with. It adds a bearer header that names the PLOT_TOKEN
554
+ environment variable rather than holding the token, because .mcp.json and
555
+ .vscode/mcp.json are usually committed; the token (the stored workspace one,
556
+ or <page>'s) is printed separately for you to export where the host runs.
557
+ Zed cannot read an environment variable there, so its block holds the token.
558
+ --token Add a bearer header instead of leaving sign-in to OAuth
559
+ --inline-token Put the token itself in the header (implies --token)`
548
560
  };
549
561
 
550
562
  // src/login.ts
@@ -1334,20 +1346,26 @@ async function mcpConfig(rt) {
1334
1346
  );
1335
1347
  return;
1336
1348
  }
1349
+ const pageRef = rt.positionals[0];
1350
+ if (rt.positionals.some((p) => p.startsWith("plot_agent_sk_"))) {
1351
+ throw new UsageError(
1352
+ `In mcp-config, --token takes no value: it uses ${TOKEN_ENV} or the stored login. Set ${TOKEN_ENV} instead of passing the token.`
1353
+ );
1354
+ }
1355
+ const pageId = pageRef ? parsePageRef(pageRef) : void 0;
1356
+ const wantToken = rt.values.token === true || rt.values["inline-token"] === true || !!pageId;
1357
+ if (pageId && rt.values.token !== true && rt.values["inline-token"] !== true) {
1358
+ rt.ctx.err(
1359
+ "A page grant has no OAuth flow for a host to run, so this block carries a bearer token (as with --token)."
1360
+ );
1361
+ }
1337
1362
  let token;
1338
- if (!rt.values["no-token"]) {
1339
- const pageId = rt.positionals[0] ? parsePageRef(rt.positionals[0]) : void 0;
1363
+ if (wantToken) {
1340
1364
  const entry = await originEntry(rt);
1341
- const candidates = tokenCandidates({
1342
- flag: stringFlag(rt, "token"),
1343
- env: rt.ctx.env,
1344
- entry,
1345
- pageId
1346
- });
1347
- token = candidates[0]?.token;
1365
+ token = tokenCandidates({ env: rt.ctx.env, entry, pageId })[0]?.token;
1348
1366
  if (!token) {
1349
- rt.ctx.err(
1350
- 'No stored credential, so no Authorization header: the host will sign in with OAuth. Run "plot login" first to use a token instead.'
1367
+ throw new CliError(
1368
+ pageId ? `No stored credential for that page. Run "plot open <temp_code_\u2026>" (or "plot login") first, or set ${TOKEN_ENV}.` : `No stored credential for --token. Run "plot login" first, or set ${TOKEN_ENV}. Without --token the host signs in with OAuth itself.`
1351
1369
  );
1352
1370
  }
1353
1371
  }
@@ -1371,9 +1389,21 @@ async function mcpConfig(rt) {
1371
1389
  ].join("\n")
1372
1390
  );
1373
1391
  };
1392
+ const oauthHint = () => {
1393
+ if (token) return;
1394
+ if (host === "claude-code" && !rt.values.write) {
1395
+ rt.ctx.err(
1396
+ `Or: claude mcp add --transport http --scope project plot ${url}`
1397
+ );
1398
+ }
1399
+ rt.ctx.err(
1400
+ "The host will ask you to sign in to Plot the first time it connects."
1401
+ );
1402
+ };
1374
1403
  if (!rt.values.write) {
1375
1404
  rt.ctx.out(JSON.stringify(block, null, 2));
1376
1405
  rt.ctx.err(`Add this to ${spec.where}.${credentialNote}`);
1406
+ oauthHint();
1377
1407
  exportHint();
1378
1408
  return;
1379
1409
  }
@@ -1418,6 +1448,7 @@ async function mcpConfig(rt) {
1418
1448
  });
1419
1449
  if (inline && !created) await chmod2(file, 384);
1420
1450
  rt.ctx.out(`Wrote the "plot" server to ${file}.${credentialNote}`);
1451
+ oauthHint();
1421
1452
  exportHint();
1422
1453
  }
1423
1454
 
@@ -1620,7 +1651,8 @@ var COMMANDS = {
1620
1651
  options: {
1621
1652
  host: { type: "string" },
1622
1653
  write: { type: "boolean" },
1623
- "no-token": { type: "boolean" },
1654
+ // A switch here, not the global --token <token>: see mcpConfig.ts.
1655
+ token: { type: "boolean" },
1624
1656
  "inline-token": { type: "boolean" }
1625
1657
  }
1626
1658
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plotio/cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Read Plot pages, follow a meeting as it happens, and post back, from any agent with a shell.",
5
5
  "type": "module",
6
6
  "license": "MIT",