@wirecat/cli-template 0.7.0 → 0.9.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.
@@ -29,9 +29,8 @@ jobs:
29
29
  platform-run: |
30
30
  node dist/bin/clidev.js commands --json >/dev/null
31
31
  node dist/bin/clidev.js doctor --json >/dev/null
32
- # The skeleton keeps no local state of its own; its config file and run records are cli-core's. A CLI that keeps
33
- # its own (a store, a sync cursor, any file it writes) adds a contracts:upgrade script and removes this skip
34
- # (WC-TEST-14).
32
+ # contracts:upgrade needs local state of the CLI's own and a published version to upgrade from (WC-TEST-14).
33
+ # Once a CLI has both, it adds the script and removes this skip; see docs/dev/TESTING.md.
35
34
  skip: |
36
35
  contracts:upgrade
37
36
  checks: |
package/.gitleaks.toml CHANGED
@@ -3,3 +3,21 @@ useDefault = true
3
3
 
4
4
  [allowlist]
5
5
  paths = ['''pnpm-lock\.yaml''']
6
+
7
+ # gitleaks' own rules know Google API keys (AIza…) only. Google calls its OAuth tokens opaque, so these prefixes are
8
+ # the observed format, not a promised one: ya29. in Apigee's sample access token, 1// in the sample refresh token of
9
+ # the web-server OAuth guide.
10
+ [[rules]]
11
+ id = "google-oauth-access-token"
12
+ description = "Google OAuth 2.0 access token"
13
+ regex = '''\b(ya29\.[A-Za-z0-9._-]{20,})'''
14
+ secretGroup = 1
15
+ keywords = ["ya29."]
16
+
17
+ [[rules]]
18
+ id = "google-oauth-refresh-token"
19
+ description = "Google OAuth 2.0 refresh token"
20
+ regex = '''(?:^|[^A-Za-z0-9/])(1//[A-Za-z0-9_-]{30,})'''
21
+ secretGroup = 1
22
+ entropy = 3.5
23
+ keywords = ["1//"]
package/AGENTS.md CHANGED
@@ -6,20 +6,12 @@ file and over every other document in this repository. The handbook ships inside
6
6
  touches (`WC-<AREA>-<n>`) in the commit message and the pull request. The rules of this CLI alone are in
7
7
  [CLAUDE.md](CLAUDE.md).
8
8
 
9
- ## Before building anything
9
+ ## Design
10
10
 
11
- This CLI was made from [cli-template](https://github.com/WireCatLabs/cli-template). Its first task is the design:
12
-
13
- 1. Read [docs/dev/DESIGN.md](docs/dev/DESIGN.md). If the seven answers are not there, ask the owner the
14
- [design interview](docs/dev/skills/design-interview/SKILL.md) and write them down.
15
- 2. Learn from the reference CLI below. Copy its patterns for the provider, the store and the commands; the
16
- template has no sample code on purpose.
17
- 3. Ask the further questions in the interview that the reference CLI does not answer. Build only after the owner
18
- has read `DESIGN.md`.
19
- 4. When the first command reaches the service, rewrite `README.md`, `docs/index.md` and the description in
20
- `skills/clidev-cli/SKILL.md` to say what the tool does and who it is for. Rewrite the MCP server's instructions
21
- in `src/mcp.ts` too: name the kinds of outside text the CLI carries, from the untrusted-text answer in
22
- `DESIGN.md` (WC-MCP-11).
11
+ [docs/dev/DESIGN.md](docs/dev/DESIGN.md) holds the owner's answers to the
12
+ [design interview](docs/dev/skills/design-interview/SKILL.md) and the decisions made since. Read it before changing
13
+ what a command does, and write a new decision there. Learn the patterns for the provider, the store and the commands
14
+ from the reference CLI below; the template has no sample code on purpose.
23
15
 
24
16
  <!-- cli-template:reference-cli:start -->
25
17
  The reference CLI is not chosen yet; see [Reference CLIs](docs/dev/reference-clis.md).
@@ -27,5 +19,7 @@ The reference CLI is not chosen yet; see [Reference CLIs](docs/dev/reference-cli
27
19
 
28
20
  ## Template-owned files
29
21
 
30
- `template.json` names the files `npx @wirecat/cli-template upgrade` keeps in step with the template. Change one of
31
- them here only when this CLI needs to differ; `upgrade` asks before it touches your change.
22
+ This CLI was made from [cli-template](https://github.com/WireCatLabs/cli-template). `template.json` names the files
23
+ `npx @wirecat/cli-template upgrade` keeps in step with the template. Change one of them here only when this CLI needs
24
+ to differ; `upgrade` asks before it touches your change. Text that holds for this CLI alone goes into the pages it
25
+ owns, such as `DESIGN.md`, `CLAUDE.md` and `README.md`, not into a template-owned page.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,66 @@ Notable changes to `@wirecat/cli-template`. One section per version, newest firs
5
5
 
6
6
  <!-- cspell:words katex smol whatsapp -->
7
7
 
8
+ ## 0.9.0 — 27.10.2026
9
+
10
+ ### Changed — may break callers
11
+
12
+ - **The MCP server is `clidev <profile> mcp serve`; `mcp` alone only shows its commands** (WC-CMD-3). `--http`,
13
+ `--port` and `--public-url` move to `mcp serve`, and `mcp --revoke` is its own command, `mcp revoke`, still refused
14
+ under `CLIDEV_READ_ONLY`. There is no alias: a client set up with `claude mcp add clidev -- clidev personal mcp`
15
+ gets the help and exits 2; set it up again with `… mcp serve`. `mcp server start` runs `mcp serve --http`.
16
+ - **The skeleton runs on cli-core 0.463.0 and cli-base 0.63.0**, the cli-base line cli-core 0.463 accepts; a CLI that
17
+ pins cli-base moves both pins together. `skill show`, `skill status` and `skill install --json` answer in the
18
+ one-thing envelope, `{ item, meta }`, so a script reads their fields under `item`.
19
+ - **`mcp server start` no longer offers `--permission`.** cli-core added it to every CLI and passed it to a server
20
+ that refused it; now it is refused before anything starts. The profile's `permissions` still make the server
21
+ stricter.
22
+
23
+ A CLI made from an older template keeps its own `src/commands/mcp.ts`, since `src/` is a seed: copy the template's
24
+ file, and change `claude mcp add` lines in its docs to end in `mcp serve`.
25
+
26
+ ### Security
27
+
28
+ - **`.gitleaks.toml` catches Google OAuth tokens.** gitleaks' own rules know Google API keys only, so an access token
29
+ (`ya29.…`) or a refresh token (`1//…`) with no keyword beside it went through. Two rules now catch them. A CLI takes
30
+ them with `upgrade`; scan its history once with `gitleaks git` after that, since CI scans all of it.
31
+
32
+ ### Fixed
33
+
34
+ - **A command other than `mcp serve` no longer loads the MCP library.** `src/commands/mcp.ts` imported `portOf` and
35
+ `publicUrlOf` from `@wirecat/cli-core/mcp` at the top, so `--version` and `doctor` loaded the MCP server SDK too
36
+ (WC-LIFE-6). `mcp serve --http` now imports them when it runs.
37
+ - `docs/security.md` says what `mcp serve --http` listens on, what it keeps of each browser login, and that
38
+ `mcp revoke` ends them.
39
+ - **`pnpm audit:commands` prints the CLI's own scorecard lines**, not the last CLI's total, which was zoom-cli's. It
40
+ fails when the scorecard has no line for the CLI. Whether a new finding fails the run did not change.
41
+ - **`AGENTS.md` keeps only what holds for the CLI's whole life.** The one-time first task (interview, reference CLI,
42
+ rewrite the README and the MCP instructions) moved into the `DESIGN.md` that `new` writes, which the CLI owns and
43
+ deletes the section from when it is done. `upgrade` drops the old text from `AGENTS.md`.
44
+ - **`new` writes a `files` list that ships only what the CLI runs from**: `dist`, `skills`, `README.md`,
45
+ `CHANGELOG.md` and `LICENSE`. It copied the template's own list, so a new CLI published its `src/` with tests and
46
+ fakes, `scripts/`, `.github/`, `bin/release`, `config/`, `lint/` and `template.json` (WC-REL-16). `package.json` is
47
+ a seed, so a CLI made before this trims its list by hand; `pnpm smoke` then checks the package still runs.
48
+ - `docs/dev/TESTING.md` and `ci.yml` give both reasons `contracts:upgrade` is skipped: it needs local state of the
49
+ CLI's own and a published version to upgrade from (WC-TEST-14).
50
+ - `docs/dev/reference-clis.md` shows the provider call cli-core has, `store.accounts.registerProvider(entry)`.
51
+ `@wirecat/cli-core/store` exports no `registerProvider` function.
52
+
53
+ ## 0.8.0 — 27.10.2026
54
+
55
+ ### Fixed
56
+
57
+ - The fresh-audit prompt lets the auditor read the cli-base workflow files the CLI's workflows pin (WC-CI-8), as
58
+ the handbook's New CLI page now says.
59
+
60
+ ### Removed
61
+
62
+ - **`mcp --permission` is gone.** cli-core 0.449.0 checks a `--permission` key against the messenger's names
63
+ (`messages`, `drafts`, `chats` and the like), so in a CLI made from this template it refused every value, its own
64
+ command paths included. It returns when cli-core can check a CLI's own permission names. Until then the profile's
65
+ `permissions` are the way to make the MCP server stricter. `mcp server start` still shows a `--permission` option,
66
+ which cli-core adds; the server it starts refuses it.
67
+
8
68
  ## 0.7.0 — 27.10.2026
9
69
 
10
70
  ### Changed — may break callers
package/README.md CHANGED
@@ -33,14 +33,15 @@ such as `personal` in `clidev personal config show`; none is ever assumed. `CLID
33
33
  | `clidev commands --json` | Prints every command as JSON, for agents and scripts |
34
34
  | `clidev personal config show` | Shows each setting and where it came from |
35
35
  | `clidev skill install` | Installs the agent skill for Claude Code, Codex, Gemini CLI and OpenClaw; `--for hermes` for Hermes Agent |
36
- | `clidev personal mcp` | Serves the profile's commands to an AI agent over MCP; `--http` for agents in the browser |
36
+ | `clidev personal mcp serve` | Serves the profile's commands to an AI agent over MCP; `--http` for agents in the browser |
37
37
  | `clidev runs list` | Lists recorded runs, newest first: every command that failed is kept |
38
38
  | `clidev upgrade --check` | Says whether a newer version is on npm |
39
39
 
40
40
  ## For AI agents
41
41
 
42
- `clidev skill show` prints the rules an agent follows with this tool. `clidev personal mcp` serves every command to an
43
- agent, writes included: the profile's permissions decide what runs. See [MCP and permissions](docs/usage.md#agents).
42
+ `clidev skill show` prints the rules an agent follows with this tool. `clidev personal mcp serve` serves every
43
+ command to an agent, writes included: the profile's permissions decide what runs. See
44
+ [MCP and permissions](docs/usage.md#agents).
44
45
 
45
46
  ## Why this template
46
47
 
@@ -36,7 +36,7 @@
36
36
  },
37
37
  {
38
38
  "id": "missing-explicit-profile",
39
- "args": ["mcp"],
39
+ "args": ["mcp", "serve"],
40
40
  "exit": 2,
41
41
  "stream": "stderr",
42
42
  "kind": "json",
@@ -44,7 +44,7 @@
44
44
  }
45
45
  ],
46
46
  "mcp": {
47
- "args": ["personal", "mcp"],
47
+ "args": ["personal", "mcp", "serve"],
48
48
  "snapshot": "mcp-tools.json"
49
49
  }
50
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAyBnC,eAAO,MAAM,UAAU,QAAO,OA2ExB,CAAA"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAwFnC,eAAO,MAAM,UAAU,QAAO,OAuBzB,CAAA"}
@@ -1,60 +1,38 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { annotate, describeProgram } from "@wirecat/cli-base/commands";
3
- import { changesAuthorityAt, environmentOf, httpTokenFile, itemOf, MCP_HTTP_DEFAULT_PORT, mcpServerCommand, outputFor, REMOTE_WRITE, refuseUnderReadOnly, renderAnswer, rootOf, } from "@wirecat/cli-core/cli";
4
- import { portOf, publicUrlOf } from "@wirecat/cli-core/mcp";
3
+ import { changesAuthorityAt, environmentOf, httpTokenFile, itemOf, LOCAL_READ, LOCAL_WRITE, MCP_HTTP_DEFAULT_PORT, mcpServerCommand, outputFor, REMOTE_WRITE, refuseUnderReadOnly, renderAnswer, rootOf, } from "@wirecat/cli-core/cli";
5
4
  import { Command } from "commander";
6
5
  import { configuration } from "../config.js";
7
6
  import { IDENTITY } from "../identity.js";
7
+ const SETUP = `claude mcp add ${IDENTITY.command} -- ${IDENTITY.command} personal mcp serve`;
8
8
  const settingsOf = (command) => {
9
9
  const flags = command.optsWithGlobals();
10
10
  const env = environmentOf(command).env ?? process.env;
11
11
  const { profile } = configuration.resolveSettings(flags, { env });
12
12
  return { flags, env, profile, tokenFile: httpTokenFile(IDENTITY, profile, env) };
13
13
  };
14
- const withPermission = (command) => command.option("--permission <key=level>", "make a permission stricter for this server only, like messages.send=deny; it never loosens the profile; repeat for more keys", (value, previous = []) => [...previous, value]);
15
- export const mcpCommand = () => withPermission(annotate(new Command("mcp"), {
14
+ const serveCommand = () => annotate(new Command("serve"), {
16
15
  effects: REMOTE_WRITE,
17
16
  lifetime: "persistent",
18
17
  mcp: false,
19
- examples: [`${IDENTITY.command} personal mcp`],
18
+ examples: [
19
+ `${IDENTITY.command} personal mcp serve`,
20
+ `${IDENTITY.command} personal mcp serve --http --public-url https://laptop.example.ts.net`,
21
+ ],
20
22
  })
21
23
  .summary("serve this profile's commands to an AI agent")
22
- .description(`serve this profile's commands to an agent over MCP, on stdin and stdout, writes included; the profile's permissions decide — \`claude mcp add ${IDENTITY.command} -- ${IDENTITY.command} personal mcp\``))
24
+ .description(`serve this profile's commands to an agent over MCP, on stdin and stdout, writes included; the profile's permissions decide — \`${SETUP}\``)
23
25
  .option("--http", "serve over HTTP on 127.0.0.1 for ChatGPT and Claude in the browser, behind your tunnel; the profile's permissions decide")
24
26
  .option("--port <port>", `the local port for --http (default ${MCP_HTTP_DEFAULT_PORT})`)
25
27
  .option("--public-url <url>", "the tunnel's https address the browser apps use, e.g. https://<name>.ts.net")
26
- .option("--revoke", "forget every login given to a browser app; each must log in again")
27
- .addCommand(mcpServerCommand({
28
- example: `${IDENTITY.command} personal`,
29
- target: (self) => {
30
- const { env, profile, tokenFile } = settingsOf(self);
31
- return {
32
- invocation: `${IDENTITY.command} ${profile}`,
33
- profile,
34
- envPrefix: IDENTITY.envPrefix,
35
- env,
36
- tokenFile,
37
- argv: [profile, "mcp", "--http"],
38
- version: IDENTITY.version,
39
- };
40
- },
41
- answer: (self, item, lines) => renderAnswer(self, outputFor(self), itemOf(item), () => lines.join("\n")),
42
- }))
43
28
  .action(async function () {
44
29
  const { flags, env, profile, tokenFile } = settingsOf(this);
45
- if (flags.revoke) {
46
- refuseUnderReadOnly(this, "mcp --revoke");
47
- const { revokeAll } = await import("@wirecat/cli-core/mcp");
48
- revokeAll(tokenFile);
49
- renderAnswer(this, outputFor(this), itemOf({ revoked: true, profile }));
50
- return;
51
- }
52
30
  if (!flags.http && (flags.json || flags.jsonl))
53
31
  throw new CliError("validation_error", "MCP over stdio controls its own output; leave out --json and --jsonl");
54
32
  const environment = environmentOf(this);
55
33
  const run = environment.runCommand;
56
34
  if (!run)
57
- throw new Error("mcp runs inside the program's run(), which provides runCommand");
35
+ throw new Error("mcp serve runs inside the program's run(), which provides runCommand");
58
36
  const program = rootOf(this);
59
37
  const options = {
60
38
  profile,
@@ -68,6 +46,7 @@ export const mcpCommand = () => withPermission(annotate(new Command("mcp"), {
68
46
  const { serveMcp, serveMcpOverHttp } = await import("../mcp.js");
69
47
  if (!flags.http)
70
48
  return serveMcp(options);
49
+ const { portOf, publicUrlOf } = await import("@wirecat/cli-core/mcp");
71
50
  const { renderer } = outputFor(this);
72
51
  await serveMcpOverHttp(options, {
73
52
  publicUrl: publicUrlOf(flags.publicUrl, {
@@ -80,4 +59,39 @@ export const mcpCommand = () => withPermission(annotate(new Command("mcp"), {
80
59
  note: (line) => renderer.note(line),
81
60
  });
82
61
  });
62
+ const revokeCommand = () => annotate(new Command("revoke"), {
63
+ effects: LOCAL_WRITE,
64
+ mcp: false,
65
+ examples: [`${IDENTITY.command} personal mcp revoke`],
66
+ })
67
+ .summary("forget every login given to a browser app")
68
+ .description("forget every login `mcp serve --http` gave a browser app; each must log in again")
69
+ .action(async function () {
70
+ refuseUnderReadOnly(this, "mcp revoke");
71
+ const { profile, tokenFile } = settingsOf(this);
72
+ const { revokeAll } = await import("@wirecat/cli-core/mcp");
73
+ revokeAll(tokenFile);
74
+ renderAnswer(this, outputFor(this), itemOf({ revoked: true, profile }));
75
+ });
76
+ export const mcpCommand = () => annotate(new Command("mcp"), { effects: LOCAL_READ, mcp: false })
77
+ .summary("serve this profile's commands to an AI agent")
78
+ .description(`serve this profile's commands to an agent over MCP — \`${SETUP}\``)
79
+ .addCommand(serveCommand())
80
+ .addCommand(revokeCommand())
81
+ .addCommand(mcpServerCommand({
82
+ example: `${IDENTITY.command} personal`,
83
+ target: (self) => {
84
+ const { env, profile, tokenFile } = settingsOf(self);
85
+ return {
86
+ invocation: `${IDENTITY.command} ${profile}`,
87
+ profile,
88
+ envPrefix: IDENTITY.envPrefix,
89
+ env,
90
+ tokenFile,
91
+ argv: [profile, "mcp", "serve", "--http"],
92
+ version: IDENTITY.version,
93
+ };
94
+ },
95
+ answer: (self, item, lines) => renderAnswer(self, outputFor(self), itemOf(item), () => lines.join("\n")),
96
+ }));
83
97
  //# sourceMappingURL=mcp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"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"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.7.0";
1
+ export declare const VERSION = "0.9.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = "0.7.0";
1
+ export const VERSION = "0.9.0";
2
2
  //# sourceMappingURL=version.js.map
package/docs/commands.md CHANGED
@@ -120,29 +120,49 @@ clidev skill status
120
120
 
121
121
  ## `clidev mcp`
122
122
 
123
- serve this profile's commands to an agent over MCP, on stdin and stdout, writes included; the profile's permissions decide — `claude mcp add clidev -- clidev personal mcp`
123
+ serve this profile's commands to an agent over MCP — `claude mcp add clidev -- clidev personal mcp serve`
124
+
125
+ ### `clidev mcp serve`
126
+
127
+ serve this profile's commands to an agent over MCP, on stdin and stdout, writes included; the profile's permissions decide — `claude mcp add clidev -- clidev personal mcp serve`
124
128
 
125
129
  **Changes something in the service.**
126
130
 
127
131
  ```sh
128
- clidev mcp [options]
132
+ clidev mcp serve [options]
129
133
  ```
130
134
 
131
135
  | Option | What it does |
132
136
  |---|---|
133
- | `--permission <key=level>` | make a permission stricter for this server only, like messages.send=deny; it never loosens the profile; repeat for more keys. |
134
137
  | `--http` | serve over HTTP on 127.0.0.1 for ChatGPT and Claude in the browser, behind your tunnel; the profile's permissions decide. |
135
138
  | `--port <port>` | the local port for --http (default 8765). |
136
139
  | `--public-url <url>` | the tunnel's https address the browser apps use, e.g. https://<name>.ts.net. |
137
- | `--revoke` | forget every login given to a browser app; each must log in again. |
140
+
141
+ **Returns:** serve this profile's commands to an AI agent
142
+
143
+ **Example:** `clidev personal mcp serve`, `clidev personal mcp serve --http --public-url https://laptop.example.ts.net`
144
+
145
+ ### `clidev mcp revoke`
146
+
147
+ forget every login `mcp serve --http` gave a browser app; each must log in again
148
+
149
+ **Changes something on this computer only.**
150
+
151
+ ```sh
152
+ clidev mcp revoke
153
+ ```
154
+
155
+ **Returns:** forget every login given to a browser app
156
+
157
+ **Example:** `clidev personal mcp revoke`
138
158
 
139
159
  ### `clidev mcp server`
140
160
 
141
- start, stop and check `mcp --http` in the background; status shows the login code for a browser app
161
+ start, stop and check `mcp serve --http` in the background; status shows the login code for a browser app
142
162
 
143
163
  #### `clidev mcp server start`
144
164
 
145
- start `mcp --http` apart from this shell and answer once it listens, with its login code
165
+ start `mcp serve --http` apart from this shell and answer once it listens, with its login code
146
166
 
147
167
  **Changes something on this computer only.**
148
168
 
@@ -153,8 +173,7 @@ clidev mcp server start [options]
153
173
  | Option | What it does |
154
174
  |---|---|
155
175
  | `--port <port>` | the local port (default 8765). |
156
- | `--public-url <url>` | the tunnel's https address the browser apps use, e.g. https://<name>.ts.net. |
157
- | `--permission <key=level>` | make a permission stricter for this server only; repeat for more keys. |
176
+ | `--public-url <url>` | the tunnel's https address the browser apps use, e.g. https://<name>.ts.net or https://<name>.ts.net/tg. |
158
177
 
159
178
  **Returns:** start the HTTP MCP server in the background
160
179
 
@@ -162,7 +181,7 @@ clidev mcp server start [options]
162
181
 
163
182
  #### `clidev mcp server stop`
164
183
 
165
- stop `mcp --http` — only one that `mcp server start` started
184
+ stop `mcp serve --http` — only one that `mcp server start` started
166
185
 
167
186
  **Changes something on this computer only.**
168
187
 
@@ -176,7 +195,7 @@ clidev mcp server stop
176
195
 
177
196
  #### `clidev mcp server status`
178
197
 
179
- whether `mcp --http` runs, on which port and address, the login code for a new browser app, and the apps logged in
198
+ whether `mcp serve --http` runs, on which port and address, the login code for a new browser app, and the apps logged in
180
199
 
181
200
  ```sh
182
201
  clidev mcp server status
@@ -12,8 +12,11 @@ How the tests run, and what they never touch. The rules are the handbook's
12
12
  `scripts/test-matrix-untested.ts`.
13
13
  - `pnpm contracts:check` runs the built CLI against `contracts/plan.json`: help, discovery, errors and the MCP tool
14
14
  list. `pnpm exec cli-contract contracts/plan.json --update` rewrites the snapshots; review the diff.
15
- - `pnpm contracts:upgrade` does not exist yet: the skeleton keeps no local state of its own (its config file and run
16
- records are cli-core's), and `.github/workflows/ci.yml` skips the step. Add it, and remove the skip, as soon as the
17
- CLI writes state of its own: a store, a sync cursor or any other file.
15
+ - `pnpm contracts:upgrade` does not exist yet, and `.github/workflows/ci.yml` skips the step. It needs both local
16
+ state of the CLI's own (a store, a sync cursor, any other file its code writes and reads back) and a published
17
+ version to upgrade from; the skeleton has neither, and the config file and run records are cli-core's
18
+ ([WC-TEST-14](../../node_modules/@wirecat/cli-base/docs/standards/testing.md)). Once the CLI keeps such state and its
19
+ first version is on npm, add the script, as
20
+ [drive-cli does](https://github.com/WireCatLabs/drive-cli/blob/150b105569a98fbd0d4b66349305ad84128adfda/package.json#L37), and remove the skip.
18
21
  - `pnpm smoke` installs the packed package into a temporary folder and runs it offline.
19
22
  - `pnpm audit:commands` audits the command tree beside the latest published WireCat CLIs.
@@ -15,10 +15,16 @@ A messenger CLI sets `messenger: true` in `src/identity.ts`, and `new --category
15
15
  category keeps the template's `messenger: false`, so the shared help and `commands` do not speak of chats, messages
16
16
  and senders. `config` still lists cli-core's messenger settings, such as `sends_per_hour` and `read_other_bots`.
17
17
 
18
- cli-core keeps a provider registry (WC-ARCH-21). A CLI whose provider is not built into cli-core calls
19
- `registerProvider(store, { name, kind, aliases, command })` from `@wirecat/cli-core/store` on every run that opens
20
- the shared store. `kind` is one of `messages`, `mail`, `notes`, `documents`, `meetings` or `events`. Until the CLI
21
- has run once on a computer, `search` there leaves its accounts out and lists them under `skipped`. The reference CLIs
22
- do not show this call: cli-core already knows their providers, from its [seed list](https://github.com/WireCatLabs/cli-core/blob/6d8f415a6ba3124679741f57d3c22ee0f236e8bf/src/domain/providers.ts#L17-L29) and, for Drive, its
23
- [document providers](https://github.com/WireCatLabs/cli-core/blob/6d8f415a6ba3124679741f57d3c22ee0f236e8bf/src/domain/providers.ts#L50) (cli-core 0.439.0). `grep -rl registerProvider` over a clone of each of the six finds no call. The skeleton opens no store and has no provider, so it registers nothing; the call comes with
24
- the CLI's first store code.
18
+ cli-core keeps a provider registry (WC-ARCH-21). A CLI whose provider is not built into cli-core writes its entry on
19
+ every run that opens the shared store: `await store.accounts.registerProvider({ name, kind, aliases, command })`, on the
20
+ store that `openStore` from `@wirecat/cli-core/store` returns
21
+ ([`AccountsStore`](https://github.com/WireCatLabs/cli-core/blob/185718d3212cda9bb026e09809760979fe31a1e6/src/store/accounts.ts#L40-L41),
22
+ cli-core 0.459.0). `@wirecat/cli-core/store` does not export a `registerProvider` function. `kind` is one of
23
+ `messages`, `mail`, `notes`, `documents`, `meetings` or `events`. Until the CLI has run once on a computer, `search`
24
+ there leaves its accounts out and lists them under `skipped`. Of the reference CLIs, none makes this call: cli-core
25
+ already knows their providers, from its
26
+ [seed list](https://github.com/WireCatLabs/cli-core/blob/185718d3212cda9bb026e09809760979fe31a1e6/src/domain/providers.ts#L17-L29)
27
+ and, for Drive, its
28
+ [document providers](https://github.com/WireCatLabs/cli-core/blob/185718d3212cda9bb026e09809760979fe31a1e6/src/domain/providers.ts#L50).
29
+ [gcal-cli](https://github.com/WireCatLabs/gcal-cli/blob/14e27c45e6dc204cde8adcd9e4fa6aa069745335/src/store.ts) does. The skeleton opens no store and
30
+ has no provider, so it registers nothing; the call comes with the CLI's first store code.
@@ -5,9 +5,9 @@ agent's context. Give it as written.
5
5
 
6
6
  ## Prompt
7
7
 
8
- You audit a WireCat CLI repository, in the current folder. Use only two sources: the handbook, starting at
9
- `node_modules/@wirecat/cli-base/docs/standards/README.md`, and the repository itself. Do not read plans, journals
10
- or earlier reviews. Run `pnpm install` first if `node_modules` is missing.
8
+ You audit a WireCat CLI repository, in the current folder. Use only three sources: the handbook, starting at
9
+ `node_modules/@wirecat/cli-base/docs/standards/README.md`, the repository itself, and the cli-base workflow files its
10
+ own workflows pin (WC-CI-8). Do not read plans, journals or earlier reviews. Run `pnpm install` first if `node_modules` is missing.
11
11
 
12
12
  Check the repository against every handbook page that applies to it: the code, the docs, the configs and the
13
13
  workflows. You may run the package scripts. Change nothing.
@@ -7,7 +7,7 @@ ran it · ⛔ not tested offline, with the reason and where it is checked instea
7
7
  Measured from the test run (`coverage/argv.jsonl`), not searched for — see
8
8
  [TESTING.md](TESTING.md) for how, and for the states and failures that cut across commands.
9
9
 
10
- **51 ✅ · 7 ⛔ · 0 ❌** — 18 commands, 40 options.
10
+ **50 ✅ · 6 ⛔ · 0 ❌** — 19 commands, 37 options.
11
11
 
12
12
  | Command | Option | | Note |
13
13
  |---|---|---|---|
@@ -19,17 +19,15 @@ Measured from the test run (`coverage/argv.jsonl`), not searched for — see
19
19
  | `skill install` | | ✅ | |
20
20
  | `skill install` | `--for` | ✅ | |
21
21
  | `skill status` | | ✅ | |
22
- | `mcp` | | ✅ | |
23
- | `mcp` | `--permission` | ✅ | |
24
- | `mcp` | `--http` | ✅ | |
25
- | `mcp` | `--port` | ✅ | |
26
- | `mcp` | `--public-url` | ✅ | |
27
- | `mcp` | `--revoke` | ✅ | |
28
- | `mcp server start` | | ⛔ | starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/ |
29
- | `mcp server start` | `--port` | ⛔ | starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/ |
30
- | `mcp server start` | `--public-url` | ⛔ | starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/ |
31
- | `mcp server start` | `--permission` | ⛔ | starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/ |
32
- | `mcp server stop` | | ⛔ | starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/ |
22
+ | `mcp serve` | | ✅ | |
23
+ | `mcp serve` | `--http` | ✅ | |
24
+ | `mcp serve` | `--port` | ✅ | |
25
+ | `mcp serve` | `--public-url` | ✅ | |
26
+ | `mcp revoke` | | ✅ | |
27
+ | `mcp server start` | | ⛔ | starts a detached `mcp serve --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/ |
28
+ | `mcp server start` | `--port` | ⛔ | starts a detached `mcp serve --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/ |
29
+ | `mcp server start` | `--public-url` | ⛔ | starts a detached `mcp serve --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/ |
30
+ | `mcp server stop` | | ⛔ | starts a detached `mcp serve --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/ |
33
31
  | `mcp server status` | | ✅ | |
34
32
  | `upgrade` | | ✅ | |
35
33
  | `upgrade` | `--check` | ✅ | |
package/docs/security.md CHANGED
@@ -6,6 +6,10 @@ What WireCat CLIs share — the keyring, permissions, MCP and what never reaches
6
6
  `clidev` keeps its settings in `config.json` and its state in the folders `clidev doctor` shows. It sends nothing
7
7
  to any service yet.
8
8
 
9
+ `clidev personal mcp serve --http` listens on `127.0.0.1` only, for browser apps that reach it through your own
10
+ tunnel. Each app logs in with a one-time code, and the server keeps a hash of each login it gives, in the state
11
+ folder, readable by you only. `clidev personal mcp revoke` forgets every one of them.
12
+
9
13
  A run record keeps the command line as you typed it, so `clidev runs show` tells you what was run. A secret is still
10
14
  stored as `[redacted]`: an option named like a token or password, a password in a link, a long opaque string. A
11
15
  phone number is replaced by `[redacted]` inside the word you typed, message text included: one written with its "+" and
package/docs/usage.md CHANGED
@@ -15,25 +15,25 @@ command without one stops with exit code 2 and lists the profiles on this comput
15
15
 
16
16
  ## Output for scripts
17
17
 
18
- Every command except `mcp` on stdin and stdout takes `--json`, and prints one JSON value on stdout. Errors are JSON on stderr, with a code to branch
18
+ Every command except `mcp serve` on stdin and stdout takes `--json`, and prints one JSON value on stdout. Errors are JSON on stderr, with a code to branch
19
19
  on. The [command reference](commands.md) lists the codes.
20
20
 
21
21
  ## Agents
22
22
 
23
23
  `clidev skill install` puts the tool's skill where Claude Code, Codex, Gemini CLI and OpenClaw look for it.
24
24
  `clidev skill install --for hermes` also adds that folder to Hermes Agent's own settings, so a bare `skill install`
25
- leaves Hermes out. `clidev personal mcp` serves the profile's commands over MCP, on stdin and stdout:
25
+ leaves Hermes out. `clidev personal mcp serve` serves the profile's commands over MCP, on stdin and stdout:
26
26
 
27
27
  ```sh
28
- claude mcp add clidev -- clidev personal mcp
28
+ claude mcp add clidev -- clidev personal mcp serve
29
29
  ```
30
30
 
31
31
  The server offers every command, the ones that write included. The profile's permissions decide what runs; see
32
- [settings](configuration.md#permissions). `--permission <key>=deny` makes one permission key stricter for this server
33
- only; it never loosens the profile.
32
+ [settings](configuration.md#permissions).
34
33
 
35
- For ChatGPT and Claude in the browser, `clidev personal mcp --http --public-url https://laptop.example.ts.net` serves on
36
- `127.0.0.1` (port 8765, or `--port`) behind your own tunnel. A browser app logs in with a one-time code that the
37
- command prints; `clidev personal mcp --revoke` ends every login. `clidev personal mcp server start` runs the same
34
+ For ChatGPT and Claude in the browser,
35
+ `clidev personal mcp serve --http --public-url https://laptop.example.ts.net` serves on `127.0.0.1` (port 8765, or
36
+ `--port`) behind your own tunnel. A browser app logs in with a one-time code that the command prints;
37
+ `clidev personal mcp revoke` ends every login. `clidev personal mcp server start` runs the same
38
38
  server in the background, `mcp server status` shows its port, the login code and the apps logged in, and
39
39
  `mcp server stop` ends it.
package/kit/dist/new.js CHANGED
@@ -5,7 +5,7 @@ import { createInterface } from "node:readline/promises";
5
5
  import { promisify } from "node:util";
6
6
  import { ask, choiceList, INTERVIEW, labelOf, NAME_QUESTIONS, parseAnswer } from "./interview.js";
7
7
  import { isRecord, MANIFEST, sha256 } from "./manifest.js";
8
- import { PACKED_AS, renderTemplate } from "./source.js";
8
+ import { renderTemplate } from "./source.js";
9
9
  const run = promisify(execFile);
10
10
  const REFERENCES = {
11
11
  messenger: ["whatsapp-cli", "tg-cli", "max-cli"],
@@ -107,6 +107,18 @@ export const designDoc = (names, answers) => {
107
107
  `Written by \`npx @wirecat/cli-template new\`. The agent reads it first, asks the further questions the reference CLI`,
108
108
  "does not answer, and starts building only after the owner has read it.",
109
109
  "",
110
+ "## First task",
111
+ "",
112
+ "Delete this section once every step is done (WC-DOC-3).",
113
+ "",
114
+ "1. If an answer below is missing, ask the owner the [design interview](skills/design-interview/SKILL.md).",
115
+ "2. Learn from the reference CLI below: its provider, its store and its commands.",
116
+ "3. Ask the further questions in the interview that the reference CLI does not answer, and write the answers here.",
117
+ " Build only after the owner has read this page.",
118
+ "4. When the first command reaches the service, rewrite `README.md`, `docs/index.md` and the description in",
119
+ ` \`skills/${names.name}/SKILL.md\` to say what the tool does and who it is for. Rewrite the MCP server's`,
120
+ " instructions in `src/mcp.ts` too: name the kinds of outside text the CLI carries (WC-MCP-11).",
121
+ "",
110
122
  "## Answers",
111
123
  "",
112
124
  ...rows,
@@ -137,6 +149,7 @@ const removeKit = (command) => command
137
149
  .split(" && ")
138
150
  .filter((part) => !/\bkit[:/]/.test(part))
139
151
  .join(" && ");
152
+ const CLI_FILES = ["dist", "skills", "/README.md", "CHANGELOG.md", "LICENSE"];
140
153
  export const cliPackageJson = (text, names) => {
141
154
  const pkg = JSON.parse(text);
142
155
  if (!isRecord(pkg))
@@ -149,8 +162,9 @@ export const cliPackageJson = (text, names) => {
149
162
  ...Object.fromEntries(Object.entries(bin).filter(([, value]) => !isKit(value))),
150
163
  [names.command]: `dist/bin/${names.command}.js`,
151
164
  };
165
+ // The template's own list ships the skeleton's sources for `new` to copy; a CLI ships what runs (WC-REL-16).
152
166
  if (Array.isArray(pkg.files))
153
- next.files = pkg.files.filter((entry) => !isKit(entry) && !Object.values(PACKED_AS).includes(entry));
167
+ next.files = CLI_FILES;
154
168
  if (isRecord(pkg.scripts)) {
155
169
  next.scripts = Object.fromEntries(Object.entries(pkg.scripts)
156
170
  .filter((entry) => typeof entry[1] === "string" && !entry[0].startsWith("kit:"))
@@ -0,0 +1 @@
1
+ ["mcp revoke", "mcp serve"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-template",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Start a new WireCat CLI: a working skeleton on cli-base and cli-core, copied by new and kept current by upgrade",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",
@@ -84,8 +84,8 @@
84
84
  },
85
85
  "dependencies": {
86
86
  "@modelcontextprotocol/server": "2.4.0",
87
- "@wirecat/cli-base": "0.62.0",
88
- "@wirecat/cli-core": "0.449.0",
87
+ "@wirecat/cli-base": "0.63.0",
88
+ "@wirecat/cli-core": "0.463.0",
89
89
  "commander": "15.0.0"
90
90
  },
91
91
  "devDependencies": {
package/scripts/audit.sh CHANGED
@@ -21,4 +21,5 @@ for cli in tg max wa memo drive zm; do
21
21
  done
22
22
  "$root/node_modules/.bin/cli-dev" audit --cli "$root/dist/bin/$command.js" "${others[@]}" --for "$command" \
23
23
  --lint "$command=$root/lint" >"$scratch/scorecard.txt" || { cat "$scratch/scorecard.txt"; exit 1; }
24
- tail -n 1 "$scratch/scorecard.txt"
24
+ awk -v cli="$command" 'NR == 1 || $1 == cli' "$scratch/scorecard.txt"
25
+ grep -q "^$command *total " "$scratch/scorecard.txt" || { echo "audit: no scorecard row for $command" >&2; exit 1; }
@@ -12,29 +12,23 @@ export const UNTESTED: { command: string; option?: string; reason: string }[] =
12
12
  {
13
13
  command: "mcp server start",
14
14
  reason:
15
- "starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/",
15
+ "starts a detached `mcp serve --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/",
16
16
  },
17
17
  {
18
18
  command: "mcp server start",
19
19
  option: "--port",
20
20
  reason:
21
- "starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/",
21
+ "starts a detached `mcp serve --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/",
22
22
  },
23
23
  {
24
24
  command: "mcp server start",
25
25
  option: "--public-url",
26
26
  reason:
27
- "starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/",
28
- },
29
- {
30
- command: "mcp server start",
31
- option: "--permission",
32
- reason:
33
- "starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/",
27
+ "starts a detached `mcp serve --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/",
34
28
  },
35
29
  {
36
30
  command: "mcp server stop",
37
31
  reason:
38
- "starts a detached `mcp --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/",
32
+ "starts a detached `mcp serve --http` from the built entry; cli-core tests mcpServerCommand, and the background run was checked by hand against dist/",
39
33
  },
40
34
  ]
@@ -6,6 +6,8 @@ import {
6
6
  type GlobalFlags,
7
7
  httpTokenFile,
8
8
  itemOf,
9
+ LOCAL_READ,
10
+ LOCAL_WRITE,
9
11
  MCP_HTTP_DEFAULT_PORT,
10
12
  mcpServerCommand,
11
13
  outputFor,
@@ -14,7 +16,6 @@ import {
14
16
  renderAnswer,
15
17
  rootOf,
16
18
  } from "@wirecat/cli-core/cli"
17
- import { portOf, publicUrlOf } from "@wirecat/cli-core/mcp"
18
19
  import { Command } from "commander"
19
20
  import { configuration } from "../config.js"
20
21
  import { IDENTITY } from "../identity.js"
@@ -23,9 +24,10 @@ interface McpFlags extends GlobalFlags {
23
24
  http?: boolean
24
25
  port?: string
25
26
  publicUrl?: string
26
- revoke?: boolean
27
27
  }
28
28
 
29
+ const SETUP = `claude mcp add ${IDENTITY.command} -- ${IDENTITY.command} personal mcp serve`
30
+
29
31
  const settingsOf = (command: Command) => {
30
32
  const flags = command.optsWithGlobals<McpFlags>()
31
33
  const env = environmentOf(command).env ?? process.env
@@ -33,65 +35,33 @@ const settingsOf = (command: Command) => {
33
35
  return { flags, env, profile, tokenFile: httpTokenFile(IDENTITY, profile, env) }
34
36
  }
35
37
 
36
- const withPermission = (command: Command) =>
37
- command.option(
38
- "--permission <key=level>",
39
- "make a permission stricter for this server only, like messages.send=deny; it never loosens the profile; repeat for more keys",
40
- (value: string, previous: string[] = []) => [...previous, value],
41
- )
42
-
43
- export const mcpCommand = (): Command =>
44
- withPermission(
45
- annotate(new Command("mcp"), {
46
- effects: REMOTE_WRITE,
47
- lifetime: "persistent",
48
- mcp: false,
49
- examples: [`${IDENTITY.command} personal mcp`],
50
- })
51
- .summary("serve this profile's commands to an AI agent")
52
- .description(
53
- `serve this profile's commands to an agent over MCP, on stdin and stdout, writes included; the profile's permissions decide — \`claude mcp add ${IDENTITY.command} -- ${IDENTITY.command} personal mcp\``,
54
- ),
55
- )
38
+ const serveCommand = (): Command =>
39
+ annotate(new Command("serve"), {
40
+ effects: REMOTE_WRITE,
41
+ lifetime: "persistent",
42
+ mcp: false,
43
+ examples: [
44
+ `${IDENTITY.command} personal mcp serve`,
45
+ `${IDENTITY.command} personal mcp serve --http --public-url https://laptop.example.ts.net`,
46
+ ],
47
+ })
48
+ .summary("serve this profile's commands to an AI agent")
49
+ .description(
50
+ `serve this profile's commands to an agent over MCP, on stdin and stdout, writes included; the profile's permissions decide — \`${SETUP}\``,
51
+ )
56
52
  .option(
57
53
  "--http",
58
54
  "serve over HTTP on 127.0.0.1 for ChatGPT and Claude in the browser, behind your tunnel; the profile's permissions decide",
59
55
  )
60
56
  .option("--port <port>", `the local port for --http (default ${MCP_HTTP_DEFAULT_PORT})`)
61
57
  .option("--public-url <url>", "the tunnel's https address the browser apps use, e.g. https://<name>.ts.net")
62
- .option("--revoke", "forget every login given to a browser app; each must log in again")
63
- .addCommand(
64
- mcpServerCommand({
65
- example: `${IDENTITY.command} personal`,
66
- target: (self) => {
67
- const { env, profile, tokenFile } = settingsOf(self)
68
- return {
69
- invocation: `${IDENTITY.command} ${profile}`,
70
- profile,
71
- envPrefix: IDENTITY.envPrefix,
72
- env,
73
- tokenFile,
74
- argv: [profile, "mcp", "--http"],
75
- version: IDENTITY.version,
76
- }
77
- },
78
- answer: (self, item, lines) => renderAnswer(self, outputFor(self), itemOf(item), () => lines.join("\n")),
79
- }),
80
- )
81
58
  .action(async function (this: Command) {
82
59
  const { flags, env, profile, tokenFile } = settingsOf(this)
83
- if (flags.revoke) {
84
- refuseUnderReadOnly(this, "mcp --revoke")
85
- const { revokeAll } = await import("@wirecat/cli-core/mcp")
86
- revokeAll(tokenFile)
87
- renderAnswer(this, outputFor(this), itemOf({ revoked: true, profile }))
88
- return
89
- }
90
60
  if (!flags.http && (flags.json || flags.jsonl))
91
61
  throw new CliError("validation_error", "MCP over stdio controls its own output; leave out --json and --jsonl")
92
62
  const environment = environmentOf(this)
93
63
  const run = environment.runCommand
94
- if (!run) throw new Error("mcp runs inside the program's run(), which provides runCommand")
64
+ if (!run) throw new Error("mcp serve runs inside the program's run(), which provides runCommand")
95
65
  const program = rootOf(this)
96
66
  const options = {
97
67
  profile,
@@ -104,6 +74,7 @@ export const mcpCommand = (): Command =>
104
74
  // Loaded here, not at the top: every other command would otherwise pay for the MCP SDK.
105
75
  const { serveMcp, serveMcpOverHttp } = await import("../mcp.js")
106
76
  if (!flags.http) return serveMcp(options)
77
+ const { portOf, publicUrlOf } = await import("@wirecat/cli-core/mcp")
107
78
  const { renderer } = outputFor(this)
108
79
  await serveMcpOverHttp(options, {
109
80
  publicUrl: publicUrlOf(flags.publicUrl, {
@@ -116,3 +87,44 @@ export const mcpCommand = (): Command =>
116
87
  note: (line) => renderer.note(line),
117
88
  })
118
89
  })
90
+
91
+ const revokeCommand = (): Command =>
92
+ annotate(new Command("revoke"), {
93
+ effects: LOCAL_WRITE,
94
+ mcp: false,
95
+ examples: [`${IDENTITY.command} personal mcp revoke`],
96
+ })
97
+ .summary("forget every login given to a browser app")
98
+ .description("forget every login `mcp serve --http` gave a browser app; each must log in again")
99
+ .action(async function (this: Command) {
100
+ refuseUnderReadOnly(this, "mcp revoke")
101
+ const { profile, tokenFile } = settingsOf(this)
102
+ const { revokeAll } = await import("@wirecat/cli-core/mcp")
103
+ revokeAll(tokenFile)
104
+ renderAnswer(this, outputFor(this), itemOf({ revoked: true, profile }))
105
+ })
106
+
107
+ export const mcpCommand = (): Command =>
108
+ annotate(new Command("mcp"), { effects: LOCAL_READ, mcp: false })
109
+ .summary("serve this profile's commands to an AI agent")
110
+ .description(`serve this profile's commands to an agent over MCP — \`${SETUP}\``)
111
+ .addCommand(serveCommand())
112
+ .addCommand(revokeCommand())
113
+ .addCommand(
114
+ mcpServerCommand({
115
+ example: `${IDENTITY.command} personal`,
116
+ target: (self) => {
117
+ const { env, profile, tokenFile } = settingsOf(self)
118
+ return {
119
+ invocation: `${IDENTITY.command} ${profile}`,
120
+ profile,
121
+ envPrefix: IDENTITY.envPrefix,
122
+ env,
123
+ tokenFile,
124
+ argv: [profile, "mcp", "serve", "--http"],
125
+ version: IDENTITY.version,
126
+ }
127
+ },
128
+ answer: (self, item, lines) => renderAnswer(self, outputFor(self), itemOf(item), () => lines.join("\n")),
129
+ }),
130
+ )
@@ -95,7 +95,10 @@ it("serves the three tools behind the browser login, and shows the server in mcp
95
95
  const port = await freePort()
96
96
  const base = new URL(`http://127.0.0.1:${port}`)
97
97
  const stop = new AbortController()
98
- const serving = started(["personal", "mcp", "--http", "--public-url", base.href, "--port", String(port)], stop.signal)
98
+ const serving = started(
99
+ ["personal", "mcp", "serve", "--http", "--public-url", base.href, "--port", String(port)],
100
+ stop.signal,
101
+ )
99
102
  await vi.waitFor(() => expect(serving.err.join("\n")).toContain("serving on"))
100
103
  const codes = () => [...serving.err.join("\n").matchAll(/login code for a new browser app: (\S+)/g)].map((m) => m[1])
101
104
  const client = await loggedInClient(base, () => codes()[0] ?? "")
@@ -121,36 +124,34 @@ it("serves the three tools behind the browser login, and shows the server in mcp
121
124
  expect(await answer(["personal", "mcp", "server", "status", "--json"])).toMatchObject({ running: false })
122
125
  })
123
126
 
124
- it("refuses mcp --http without the tunnel's address", async () => {
125
- const one = started(["personal", "mcp", "--http", "--json"])
127
+ it("refuses mcp serve --http without the tunnel's address", async () => {
128
+ const one = started(["personal", "mcp", "serve", "--http", "--json"])
126
129
  expect(await one.code).not.toBe(0)
127
130
  expect(one.err.join("\n")).toContain("--public-url")
128
131
  })
129
132
 
130
- it("forgets every browser login on --revoke", async () => {
131
- expect(await answer(["personal", "mcp", "--revoke", "--json"])).toEqual({ revoked: true, profile: "personal" })
132
- })
133
-
134
- it("reads --permission through the profile's settings, refusing one that is not a command path and level", async () => {
135
- const one = started(["personal", "mcp", "--permission", "messages.send", "--json"])
136
- expect(await one.code).toBe(2)
137
- expect(one.err.join("\n")).toContain("--permission takes a command path and level")
133
+ it("forgets every browser login on mcp revoke", async () => {
134
+ expect(await answer(["personal", "mcp", "revoke", "--json"])).toEqual({ revoked: true, profile: "personal" })
138
135
  })
139
136
 
140
137
  it("keeps the login code out of a background server's log, and refuses a port in use", async () => {
141
138
  const port = await freePort()
142
139
  const url = `http://127.0.0.1:${port}`
143
140
  const stop = new AbortController()
144
- const background = started(["personal", "mcp", "--http", "--public-url", url, "--port", String(port)], stop.signal, {
145
- ...process.env,
146
- CLIDEV_MCP_HTTP: "personal",
147
- })
141
+ const background = started(
142
+ ["personal", "mcp", "serve", "--http", "--public-url", url, "--port", String(port)],
143
+ stop.signal,
144
+ {
145
+ ...process.env,
146
+ CLIDEV_MCP_HTTP: "personal",
147
+ },
148
+ )
148
149
  try {
149
150
  await vi.waitFor(() => expect(background.err.join("\n")).toContain("serving on"))
150
151
  expect(background.err.join("\n")).toContain(
151
152
  "a new login code for a browser app: `clidev personal mcp server status`",
152
153
  )
153
- const second = started(["work", "mcp", "--http", "--public-url", url, "--port", String(port), "--json"])
154
+ const second = started(["work", "mcp", "serve", "--http", "--public-url", url, "--port", String(port), "--json"])
154
155
  expect(await second.code).not.toBe(0)
155
156
  expect(second.err.join("\n")).toContain(`port ${port} is in use`)
156
157
  } finally {
@@ -160,12 +161,12 @@ it("keeps the login code out of a background server's log, and refuses a port in
160
161
  })
161
162
 
162
163
  it("refuses --json for MCP over stdio", async () => {
163
- const one = started(["personal", "mcp", "--json"])
164
+ const one = started(["personal", "mcp", "serve", "--json"])
164
165
  expect(await one.code).toBe(2)
165
166
  expect(one.err.join("\n")).toContain("MCP over stdio controls its own output")
166
167
  })
167
168
 
168
- it("refuses --revoke under the read-only lock", async () => {
169
- const one = started(["personal", "mcp", "--revoke", "--json"], undefined, { ...process.env, CLIDEV_READ_ONLY: "1" })
169
+ it("refuses mcp revoke under the read-only lock", async () => {
170
+ const one = started(["personal", "mcp", "revoke", "--json"], undefined, { ...process.env, CLIDEV_READ_ONLY: "1" })
170
171
  expect(await one.code).toBe(5)
171
172
  })
package/src/mcp.test.ts CHANGED
@@ -47,7 +47,7 @@ it("finds writes as well as reads", async () => {
47
47
  const client = await connect()
48
48
  const found = structured(await client.callTool({ name: "clidev_tools_search", arguments: {} }))
49
49
  expect(found.items).toContainEqual(expect.objectContaining({ writes: true }))
50
- expect(found.items).not.toContainEqual(expect.objectContaining({ command: "mcp" }))
50
+ expect(found.items).not.toContainEqual(expect.objectContaining({ command: expect.stringMatching(/^mcp\b/) }))
51
51
  })
52
52
 
53
53
  it("runs a command through the CLI and answers with its result", async () => {
@@ -76,9 +76,9 @@ it("serves until the command is stopped", async () => {
76
76
  await expect(serving).resolves.toBeUndefined()
77
77
  })
78
78
 
79
- it("clidev <profile> mcp starts the server and ends on a signal", async () => {
79
+ it("clidev <profile> mcp serve starts the server and ends on a signal", async () => {
80
80
  const stop = new AbortController()
81
- const serving = run(["personal", "mcp"], {
81
+ const serving = run(["personal", "mcp", "serve"], {
82
82
  signal: stop.signal,
83
83
  tty: false,
84
84
  streams: { data: () => {}, diagnostic: () => {} },
@@ -127,12 +127,18 @@ it("skill show prints the packaged skill", async () => {
127
127
  expect(out).toContain(readFileSync(new URL("../skills/clidev-cli/SKILL.md", import.meta.url), "utf8").split("\n")[0])
128
128
  })
129
129
 
130
- it("mcp without a profile stops and says so", async () => {
131
- const { code, err } = await ran(["mcp", "--json"])
130
+ it("mcp serve without a profile stops and says so", async () => {
131
+ const { code, err } = await ran(["mcp", "serve", "--json"])
132
132
  expect(code).toBe(2)
133
133
  expect(err).toContain("validation_error")
134
134
  })
135
135
 
136
+ it("mcp alone only names its commands", async () => {
137
+ const { code, err } = await ran(["personal", "mcp", "--json"])
138
+ expect(code).toBe(2)
139
+ expect(err).toContain("--help")
140
+ })
141
+
136
142
  it("upgrade --check installs nothing when npm cannot be reached", async () => {
137
143
  const { code, out } = await ran(["upgrade", "--check", "--json"])
138
144
  expect(code).toBe(0)
package/src/roots.test.ts CHANGED
@@ -77,18 +77,13 @@ it("skill status and skill show --for read what skill install wrote", async () =
77
77
  expect((await ran(["skill", "install", "--for", "claude", "--json"])).code).toBe(0)
78
78
  const status = await ran(["skill", "status", "--json"])
79
79
  expect(status.code).toBe(0)
80
- // cli-core #2059 wraps skill answers in `{ item, meta }`; accept both until this CLI pins the release that ships it.
81
- const answer = (out: string) => {
82
- const body = JSON.parse(out)
83
- return body.item ?? body
84
- }
85
- expect(answer(status.out).targets).toEqual([
80
+ expect(JSON.parse(status.out).item.targets).toEqual([
86
81
  expect.objectContaining({ target: "claude", state: "current" }),
87
82
  expect.objectContaining({ target: "agents", installed: null, state: "missing" }),
88
83
  ])
89
84
  const shown = await ran(["skill", "show", "--for", "claude", "--json"])
90
85
  expect(shown.code).toBe(0)
91
- expect(answer(shown.out)).toMatchObject({
86
+ expect(JSON.parse(shown.out).item).toMatchObject({
92
87
  target: "claude",
93
88
  content: expect.stringContaining("name: clidev-cli"),
94
89
  })
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.7.0"
1
+ export const VERSION = "0.9.0"