@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.
- package/.github/workflows/ci.yml +2 -3
- package/.gitleaks.toml +18 -0
- package/AGENTS.md +9 -15
- package/CHANGELOG.md +60 -0
- package/README.md +4 -3
- package/contracts/plan.json +2 -2
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +45 -31
- package/dist/commands/mcp.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/commands.md +29 -10
- package/docs/dev/TESTING.md +6 -3
- package/docs/dev/reference-clis.md +13 -7
- package/docs/dev/skills/release/fresh-audit.md +3 -3
- package/docs/dev/test-matrix.md +10 -12
- package/docs/security.md +4 -0
- package/docs/usage.md +8 -8
- package/kit/dist/new.js +16 -2
- package/lint/baseline/unknown-verb.json +1 -0
- package/package.json +3 -3
- package/scripts/audit.sh +2 -1
- package/scripts/test-matrix-untested.ts +4 -10
- package/src/commands/mcp.ts +61 -49
- package/src/mcp-http.test.ts +20 -19
- package/src/mcp.test.ts +3 -3
- package/src/program.test.ts +8 -2
- package/src/roots.test.ts +2 -7
- package/src/version.ts +1 -1
package/.github/workflows/ci.yml
CHANGED
|
@@ -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
|
-
#
|
|
33
|
-
#
|
|
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
|
-
##
|
|
9
|
+
## Design
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
|
43
|
-
agent, writes included: the profile's permissions decide what runs. See
|
|
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
|
|
package/contracts/plan.json
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -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
|
|
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: [
|
|
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 —
|
|
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
|
package/dist/commands/mcp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EACL,kBAAkB,EAClB,aAAa,EAEb,aAAa,EACb,MAAM,EACN,qBAAqB,EACrB,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,MAAM,GACP,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
package/docs/dev/TESTING.md
CHANGED
|
@@ -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
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
19
|
-
`registerProvider(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
the
|
|
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
|
|
9
|
-
`node_modules/@wirecat/cli-base/docs/standards/README.md`,
|
|
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.
|
package/docs/dev/test-matrix.md
CHANGED
|
@@ -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
|
-
**
|
|
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` | `--
|
|
24
|
-
| `mcp` | `--
|
|
25
|
-
| `mcp` | `--
|
|
26
|
-
| `mcp
|
|
27
|
-
| `mcp` |
|
|
28
|
-
| `mcp server start` |
|
|
29
|
-
| `mcp server start` | `--
|
|
30
|
-
| `mcp server
|
|
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).
|
|
33
|
-
only; it never loosens the profile.
|
|
32
|
+
[settings](configuration.md#permissions).
|
|
34
33
|
|
|
35
|
-
For ChatGPT and Claude in the browser,
|
|
36
|
-
`127.0.0.1` (port 8765, or
|
|
37
|
-
|
|
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 {
|
|
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 =
|
|
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.
|
|
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.
|
|
88
|
-
"@wirecat/cli-core": "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
|
-
|
|
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
|
]
|
package/src/commands/mcp.ts
CHANGED
|
@@ -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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
)
|
package/src/mcp-http.test.ts
CHANGED
|
@@ -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(
|
|
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
|
|
131
|
-
expect(await answer(["personal", "mcp", "
|
|
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(
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
169
|
-
const one = started(["personal", "mcp", "
|
|
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:
|
|
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: () => {} },
|
package/src/program.test.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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.
|
|
1
|
+
export const VERSION = "0.9.0"
|