@wirecat/cli-template 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/.github/workflows/ci.yml +17 -0
  2. package/AGENTS.md +56 -2
  3. package/CHANGELOG.md +46 -0
  4. package/README.md +12 -4
  5. package/biome.json +2 -2
  6. package/contracts/mcp-tools.json +2 -2
  7. package/dist/bin/clidev.js +2 -2
  8. package/dist/bundle/{chunk-LU4A2T7Y.js → all-profiles-FS7AEOE7.js} +8 -5
  9. package/dist/bundle/{anthropic-ICTW7CNQ.js → anthropic-5RN7BGDI.js} +3 -3
  10. package/dist/bundle/{app-server-B6BCKXR7.js → app-server-WGTKZQKJ.js} +28 -29
  11. package/dist/bundle/{bun-sqlite-JE6FJFQT.js → bun-sqlite-WK2IGBGD.js} +2 -2
  12. package/dist/bundle/{chunk-AS2PJNQ2.js → chunk-2MWNE4L3.js} +1 -1
  13. package/dist/bundle/{chunk-2DAXUNTS.js → chunk-5HHIPDNW.js} +1 -1
  14. package/dist/bundle/{chunk-YOJLIVGO.js → chunk-5KAYQKBK.js} +1 -1
  15. package/dist/bundle/{chunk-62JRF2JA.js → chunk-6WOJIAYA.js} +1 -1
  16. package/dist/bundle/chunk-7EUX7AZV.js +141 -0
  17. package/dist/bundle/{chunk-7F5VMLVK.js → chunk-ADFSSIV4.js} +1 -1
  18. package/dist/bundle/{chunk-U6QD3J3F.js → chunk-ATHZEV2Z.js} +2 -2
  19. package/dist/bundle/{chunk-UT5TQ6MD.js → chunk-BPMKAZSS.js} +8 -8
  20. package/dist/bundle/{chunk-DZLHY4IA.js → chunk-C3GNOEJP.js} +1 -1
  21. package/dist/bundle/{chunk-PIJUGF5F.js → chunk-CTBHQRX6.js} +1 -1
  22. package/dist/bundle/{chunk-LFLSV353.js → chunk-DEHBTFIC.js} +1 -1
  23. package/dist/bundle/{chunk-NVNUUHT5.js → chunk-EA3TMI4A.js} +8 -8
  24. package/dist/bundle/{chunk-BEBD27QT.js → chunk-EILS4BTQ.js} +16 -18
  25. package/dist/bundle/{chunk-BVLPNNEL.js → chunk-ENDBOYQR.js} +14 -14
  26. package/dist/bundle/{chunk-6AW4DDK4.js → chunk-ETJXPB6A.js} +1 -1
  27. package/dist/bundle/{chunk-LATYBJ4T.js → chunk-FMO6F5Q2.js} +1 -1
  28. package/dist/bundle/{chunk-RJ7AS7UB.js → chunk-HTYLT6ME.js} +24 -7
  29. package/dist/bundle/{chunk-F34HGYWF.js → chunk-JFXWLQ2L.js} +1 -1
  30. package/dist/bundle/{chunk-IZKXJJ7V.js → chunk-JJZCPBH3.js} +29 -6
  31. package/dist/bundle/{chunk-G7W53MQP.js → chunk-LRHX62ZF.js} +2 -2
  32. package/dist/bundle/{chunk-SOYOOU2D.js → chunk-MBBOINK4.js} +2 -2
  33. package/dist/bundle/{chunk-BM5ZCDPQ.js → chunk-MEXT6XDP.js} +2 -2
  34. package/dist/bundle/{chunk-R2JKPHH5.js → chunk-NVPWZF67.js} +1 -1
  35. package/dist/bundle/{chunk-45244QQ6.js → chunk-OF56L2JU.js} +2 -2
  36. package/dist/bundle/{chunk-4PA6DJ7F.js → chunk-OGTRPW2K.js} +3 -3
  37. package/dist/bundle/{chunk-VXOR76ZL.js → chunk-ORV4EGNC.js} +1 -1
  38. package/dist/bundle/{chunk-TJJ2XSJP.js → chunk-QGOOPPEW.js} +2 -2
  39. package/dist/bundle/{chunk-NH6LZ3H3.js → chunk-RFJKNFVF.js} +3 -3
  40. package/dist/bundle/{chunk-R5RYPD23.js → chunk-RYOAQCVJ.js} +1 -1
  41. package/dist/bundle/{chunk-66VMJGV6.js → chunk-SKBRW5U3.js} +1 -1
  42. package/dist/bundle/{chunk-BWBZCYUN.js → chunk-TXHCL75B.js} +43 -6
  43. package/dist/bundle/{chunk-QXOSPYM3.js → chunk-VPCXYY46.js} +1 -1
  44. package/dist/bundle/{chunk-IOJ2DTCY.js → chunk-WDWP5N6M.js} +2 -0
  45. package/dist/bundle/{chunk-HYTHTSJP.js → chunk-WM5AMTRY.js} +4 -4
  46. package/dist/bundle/{chunk-ASHVHS52.js → chunk-YNROUY3R.js} +10 -10
  47. package/dist/bundle/{chunk-DQIBGCF3.js → chunk-Z5NL6YNU.js} +2 -2
  48. package/dist/bundle/{chunk-5SAF3XOL.js → chunk-ZSY4WAGI.js} +28 -21
  49. package/dist/bundle/clidev.js +3 -3
  50. package/dist/bundle/{command-contract-JMVMT5HT.js → command-contract-ZDU73EPH.js} +9 -9
  51. package/dist/bundle/{decode-NXB5UKMO.js → decode-QMLOGZ6R.js} +2 -2
  52. package/dist/bundle/{embed-IKZ24BMC.js → embed-PIR2LJR2.js} +4 -4
  53. package/dist/bundle/{embedder-GMQ24DOQ.js → embedder-6KBRPAEQ.js} +4 -4
  54. package/dist/bundle/{first-run-MDD4SIUK.js → first-run-JO3CMY3Z.js} +7 -7
  55. package/dist/bundle/{first-screen-376CXGXJ.js → first-screen-3T5IXTFL.js} +10 -10
  56. package/dist/bundle/{node-sqlite-73Z4SFOA.js → node-sqlite-LIK6OYNF.js} +2 -2
  57. package/dist/bundle/{oauth-EVTPAMZP.js → oauth-7FVF7HQQ.js} +4 -4
  58. package/dist/bundle/{open-46WZAPJ3.js → open-CRZDEATN.js} +2 -2
  59. package/dist/bundle/{openai-GWRDAHDU.js → openai-WAJ6WREQ.js} +11 -32
  60. package/dist/bundle/{program-B3CY3FP7.js → program-ZFFFPBRS.js} +2263 -300
  61. package/dist/bundle/{prompt-IVEYRLS5.js → prompt-QQIVHO3M.js} +3 -3
  62. package/dist/bundle/{recognize-WVEKHODM.js → recognize-4KLBZDEC.js} +2 -2
  63. package/dist/bundle/{remote-4K5VPMC7.js → remote-BABMZTHA.js} +5 -5
  64. package/dist/bundle/{rerank-V24UJZYJ.js → rerank-P3IQQJIL.js} +4 -4
  65. package/dist/bundle/{chunk-EMHCXXNJ.js → serve-25SSAKRZ.js} +6 -4
  66. package/dist/bundle/server-G4GCDMYI.js +52 -0
  67. package/dist/bundle/{chunk-PX2KP4QE.js → server-P4XUYY5A.js} +27 -7
  68. package/dist/bundle/{state-ZPYGECVP.js → state-QCQALQBJ.js} +3 -3
  69. package/dist/bundle/{store-6JQCLPQF.js → store-ZECPRGGH.js} +7 -7
  70. package/dist/bundle/store-maintenance-command-ECUO5G6L.js +42 -0
  71. package/dist/commands/comments.d.ts +3 -0
  72. package/dist/commands/comments.d.ts.map +1 -0
  73. package/dist/commands/comments.js +114 -0
  74. package/dist/commands/comments.js.map +1 -0
  75. package/dist/commands/session.d.ts +3 -0
  76. package/dist/commands/session.d.ts.map +1 -0
  77. package/dist/commands/session.js +96 -0
  78. package/dist/commands/session.js.map +1 -0
  79. package/dist/config.d.ts +2 -0
  80. package/dist/config.d.ts.map +1 -1
  81. package/dist/config.js +4 -2
  82. package/dist/config.js.map +1 -1
  83. package/dist/context.d.ts +83 -0
  84. package/dist/context.d.ts.map +1 -0
  85. package/dist/context.js +58 -0
  86. package/dist/context.js.map +1 -0
  87. package/dist/program.d.ts.map +1 -1
  88. package/dist/program.js +26 -5
  89. package/dist/program.js.map +1 -1
  90. package/dist/sample/comments.d.ts +61 -0
  91. package/dist/sample/comments.d.ts.map +1 -0
  92. package/dist/sample/comments.js +39 -0
  93. package/dist/sample/comments.js.map +1 -0
  94. package/dist/sample/transport.d.ts +42 -0
  95. package/dist/sample/transport.d.ts.map +1 -0
  96. package/dist/sample/transport.js +152 -0
  97. package/dist/sample/transport.js.map +1 -0
  98. package/dist/token.d.ts +16 -0
  99. package/dist/token.d.ts.map +1 -0
  100. package/dist/token.js +32 -0
  101. package/dist/token.js.map +1 -0
  102. package/dist/version.d.ts +1 -1
  103. package/dist/version.js +1 -1
  104. package/docs/commands.md +234 -12
  105. package/docs/configuration.md +1 -1
  106. package/docs/dev/reference-clis.md +3 -2
  107. package/docs/dev/test-matrix.md +36 -5
  108. package/docs/index.md +4 -3
  109. package/docs/security.md +4 -2
  110. package/kit/dist/new.js +6 -3
  111. package/lint/overrides.json +9 -1
  112. package/package.json +3 -4
  113. package/scripts/test-matrix-untested.ts +19 -0
  114. package/skills/clidev-cli/SKILL.md +1 -1
  115. package/src/commands/comments.ts +167 -0
  116. package/src/commands/session.ts +144 -0
  117. package/src/comments.test.ts +258 -0
  118. package/src/config.ts +5 -2
  119. package/src/context.ts +78 -0
  120. package/src/mcp.test.ts +71 -76
  121. package/src/program.test.ts +39 -17
  122. package/src/program.ts +30 -5
  123. package/src/sample/comments.ts +51 -0
  124. package/src/sample/transport.test.ts +117 -0
  125. package/src/sample/transport.ts +184 -0
  126. package/src/testing/fake-comments.ts +167 -0
  127. package/src/testing/harness.ts +51 -0
  128. package/src/token.ts +42 -0
  129. package/src/version.ts +1 -1
  130. package/dist/bundle/all-profiles-NVB2HT2S.js +0 -24
  131. package/dist/bundle/chunk-GUFPQZUA.js +0 -48
  132. package/dist/bundle/chunk-NL2DQPT7.js +0 -2
  133. package/dist/bundle/mcp-KHUZOJO6.js +0 -82
  134. package/dist/bundle/mcp-MZHWJXAE.js +0 -152
  135. package/dist/bundle/serve-PJWSOV7F.js +0 -19
  136. package/dist/bundle/server-2ZN7KZHZ.js +0 -40
  137. package/dist/bundle/server-5VYM7CCF.js +0 -53
  138. package/dist/bundle/store-maintenance-command-7XICIYHW.js +0 -42
  139. package/dist/commands/doctor.d.ts +0 -77
  140. package/dist/commands/doctor.d.ts.map +0 -1
  141. package/dist/commands/doctor.js +0 -74
  142. package/dist/commands/doctor.js.map +0 -1
  143. package/dist/commands/mcp.d.ts +0 -3
  144. package/dist/commands/mcp.d.ts.map +0 -1
  145. package/dist/commands/mcp.js +0 -97
  146. package/dist/commands/mcp.js.map +0 -1
  147. package/dist/mcp.d.ts +0 -29
  148. package/dist/mcp.d.ts.map +0 -1
  149. package/dist/mcp.js +0 -98
  150. package/dist/mcp.js.map +0 -1
  151. package/src/commands/doctor.ts +0 -86
  152. package/src/commands/mcp.ts +0 -130
  153. package/src/mcp-http.test.ts +0 -172
  154. package/src/mcp.ts +0 -140
@@ -57,6 +57,23 @@ jobs:
57
57
  - run: pnpm test
58
58
  - run: pnpm smoke:node
59
59
 
60
+ # The template's own check: a CLI made from it has no kit/. Needs the network for npm and pnpm installs.
61
+ kit-e2e:
62
+ if: github.repository == 'WireCatLabs/cli-template'
63
+ runs-on: ubuntu-latest
64
+ timeout-minutes: 30
65
+ steps:
66
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
67
+ with:
68
+ persist-credentials: false
69
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
70
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
71
+ with:
72
+ node-version: 24
73
+ cache: pnpm
74
+ - run: pnpm install --frozen-lockfile --ignore-scripts
75
+ - run: pnpm kit:e2e
76
+
60
77
  version:
61
78
  if: github.event_name == 'pull_request'
62
79
  runs-on: ubuntu-latest
package/AGENTS.md CHANGED
@@ -10,13 +10,67 @@ touches (`WC-<AREA>-<n>`) in the commit message and the pull request. The rules
10
10
 
11
11
  [docs/dev/DESIGN.md](docs/dev/DESIGN.md) holds the owner's answers to the
12
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.
13
+ what a command does, and write a new decision there. Learn how a command reaches a service from the sample below,
14
+ and the domain, the store and its commands from the reference CLI.
15
15
 
16
16
  <!-- cli-template:reference-cli:start -->
17
17
  The reference CLI is not chosen yet; see [Reference CLIs](docs/dev/reference-clis.md).
18
18
  <!-- cli-template:reference-cli:end -->
19
19
 
20
+ ## The sample: what to keep, what to replace
21
+
22
+ <!-- cspell:words gcal SECR -->
23
+
24
+ The template ships one working command group, `comments`, against a made-up service, Example Comments, whose address
25
+ never resolves. It shows every pattern a command that reaches a service needs, each tested against a strict fake of
26
+ the service. Build your first command on it: keep the patterns, replace the service. Delete the rest of the sample
27
+ once your own commands cover every pattern below; until then `pnpm test` keeps it working. A CLI made before the
28
+ sample has no `src/sample/`; read it in [cli-template](https://github.com/WireCatLabs/cli-template/tree/main/src).
29
+ Comments marked `SAMPLE:` in the code say what to replace.
30
+
31
+ Keep, the same for every service:
32
+
33
+ - **Token storage**, `src/token.ts`: cli-base `Credentials`, the environment variable, then the keyring, then a
34
+ private file; a run with moved folders never replaces the owner's entry (WC-SECR-4, WC-SECR-24).
35
+ - **Sign-in**, `src/commands/session.ts`: `session start | status | end | list`. The token is read hidden or from
36
+ stdin, never as an argument, and checked against the service before it is kept (WC-ACCT-12, WC-SECR-5). Replace
37
+ the `me` check with your service's. A service with OAuth signs in through cli-core's oauth module instead, as
38
+ [gcal-cli](https://github.com/WireCatLabs/gcal-cli) does (WC-SECR-22).
39
+ - **The command context**, `src/context.ts`: settings, the keyring and the run come from cli-core's `baseContext`
40
+ only, and every request runs inside its `run()`, with the `events` it gives passed to the transport. That is what
41
+ bounds a command by `--timeout` and records a run named after it (WC-LIFE-5, WC-LIFE-15).
42
+ - **Permissions**, `src/program.ts` and `src/config.ts`: the program hands cli-core the whole `configuration`, and
43
+ each command path is its own key (WC-PERM-22). A command that reaches the service another way, such as an
44
+ `api call`, also checks the key of the command it does the same as, with `alsoChecks`.
45
+ - **Writes**, `write` in `src/commands/comments.ts`: the guard asks or refuses by the command's key, the send journal
46
+ records the attempt, the answer carries `operation_id`, and a write is never retried: one that got no answer is
47
+ `outcome_unknown` (WC-PERM-13, WC-PERM-17, WC-OUT-10, WC-LIFE-9). `--dry-run` and `<PREFIX>_READ_ONLY` are
48
+ cli-core's; declare no `--dry-run` of your own.
49
+ - **MCP and doctor**, `src/program.ts`: cli-core's `appMcpCommand` and `appDoctorCommand`, never a copy of them.
50
+ Rewrite `name` and `untrusted` for your service (WC-MCP-11).
51
+ - **The test harness**, `src/testing/harness.ts`: the CLI through its own `run()`, with a memory keyring and the fake
52
+ in place of the network (WC-TEST-2, WC-TEST-3, WC-TEST-6).
53
+
54
+ Replace, your service's own:
55
+
56
+ - **The transport**, `src/sample/transport.ts`: the token in a header only, no redirects, a timeout, cli-core's
57
+ `Pacer`, cli-base's error codes and no retry (WC-SECR-6, WC-SECR-7, WC-OUT-20). Keep its shape; take the address,
58
+ the header and the error mapping from your service's documentation.
59
+ - **Wire shapes and the mapper**, `src/sample/comments.ts`: each field name from the documentation or a captured
60
+ answer, never from memory; no wire type leaves this file (WC-ARCH-3).
61
+ - **The commands**, `src/commands/comments.ts`: a list with `withPaging`, `withQuery` and `answerEntity`, so
62
+ `--json=a,b` and the MCP schema come from one schema (WC-CMD-19, WC-CMD-20); `show`; `create` and `update`.
63
+ - **The strict fake**, `src/testing/fake-comments.ts`: it refuses an unknown path, parameter or field, so a name typed
64
+ from memory fails a test instead of reaching the owner's account. Build yours from the documentation.
65
+ - **The tests**, `src/comments.test.ts` and `src/sample/transport.test.ts`. Keep each case for your commands: a deny
66
+ stops a read before any request, `--dry-run` sends nothing, a lost write is `outcome_unknown`, a recorded run is
67
+ named after its command, and the token reaches no output, run record or file.
68
+
69
+ Remove the sample only once your own commands and their tests cover each pattern above: the skeleton alone is below
70
+ the coverage floor, and the floor is never lowered (WC-TEST-11). To remove it, unmount `commentsCommand` in `src/program.ts` and delete `src/sample/`,
71
+ `src/commands/comments.ts`, `src/testing/fake-comments.ts` and their tests. Then run `pnpm generate`,
72
+ `pnpm exec cli-contract contracts/plan.json --update` and `pnpm test:matrix`.
73
+
20
74
  ## Template-owned files
21
75
 
22
76
  This CLI was made from [cli-template](https://github.com/WireCatLabs/cli-template). `template.json` names the files
package/CHANGELOG.md CHANGED
@@ -5,6 +5,52 @@ Notable changes to `@wirecat/cli-template`. One section per version, newest firs
5
5
 
6
6
  <!-- cspell:words clawhub gcal katex openclaw smol whatsapp -->
7
7
 
8
+ ## 0.16.0 — 06.11.2026
9
+
10
+ ### Added
11
+
12
+ - **`pnpm kit:e2e` runs in CI and checks more.** It makes a CLI with a short name and one with a long name, since a
13
+ rename changes line lengths, and runs `pnpm lint` in each besides `ci:quick` and `test:coverage`. A rename that
14
+ broke Biome's formatting passed it before. The `kit-e2e` job in `.github/workflows/ci.yml` runs it on every pull
15
+ request and merge, in this repository only: a CLI made from the template has no `kit/` and skips the job.
16
+
17
+ - **A working sample shows every pattern a command that reaches a service needs.** `comments list | show | create |
18
+ update` and `session start | status | end | list` reach Example Comments, a made-up service whose address never
19
+ resolves. The token is kept with cli-base's `Credentials`; every request runs inside cli-core's `baseContext` run
20
+ with its `events`; the transport keeps the token in a header, times out and never retries; writes pass the guard
21
+ and the send journal and answer with `operation_id`, and a lost write is `outcome_unknown`. The tests run against a
22
+ strict fake that refuses unknown paths, parameters and fields. `AGENTS.md` lists what to keep and what to replace,
23
+ pattern by pattern, with the rules each follows, and the code marks each replaceable part with `SAMPLE:`. Before,
24
+ the template had no sample, and agents copied the patterns from other CLIs, their bugs included.
25
+
26
+ ### Changed — may break callers
27
+
28
+ - **MCP and doctor are cli-core's.** `src/program.ts` mounts `appMcpCommand` and `appDoctorCommand`, as gcal-cli
29
+ does, in place of the template's own `src/mcp.ts`, `src/commands/mcp.ts` and `src/commands/doctor.ts`. `mcp` gains
30
+ `config`, `add`, `check`, `--profiles` and `--permission`; `doctor` gains `--online`, and its answer `store` and
31
+ `profile_lock`. A CLI made from an older skeleton swaps them the same way and removes `@modelcontextprotocol/server`
32
+ and `@modelcontextprotocol/client` from its dependencies.
33
+
34
+ ### Fixed
35
+
36
+ - **A new CLI passes `pnpm lint` from its first commit.** Renaming `clidev` changes line lengths, so Biome wraps some
37
+ lines differently, and the first commit's hook or CI failed on formatting. `new`'s next steps now run
38
+ `pnpm lint:fix` after `pnpm install`, and `kit:e2e` does the same before it runs `pnpm lint`.
39
+ - **The MCP server applies the permission table too (WC-PERM-23).** `src/mcp.ts` built its tools without it, so a
40
+ command a profile denies was offered to an agent as available and ran. It now passes `permissions` to
41
+ `toolsFromCommands` and to each tool call: the command is listed as unavailable and refused before it runs. A CLI
42
+ made from an older skeleton adds `permissions` in both places by hand.
43
+
44
+ ## 0.15.0 — 06.11.2026
45
+
46
+ ### Fixed
47
+
48
+ - **A `deny` in a profile stops a command again.** The skeleton handed cli-core only `resolveSettings`, and cli-core
49
+ checks a command's key only when it gets the permission table too, so a denied read still ran and `--dry-run`
50
+ previewed every command as allowed. `src/program.ts` now passes the whole `configuration`, and `src/config.ts`
51
+ declares `commandPermissions()`, so each command is checked against its own path. A CLI made from an older
52
+ skeleton makes the same two changes by hand.
53
+
8
54
  ## 0.14.0 — 06.11.2026
9
55
 
10
56
  ### Added
package/README.md CHANGED
@@ -5,8 +5,9 @@
5
5
 
6
6
  `clidev` is a command-line tool started from [cli-template](https://github.com/WireCatLabs/cli-template), the kit for
7
7
  new WireCat CLIs. It is a working skeleton: the shared root commands, the settings, the agent skill and the MCP
8
- server run, and every check of the WireCat standards passes. It does not connect to a service yet. Replace this page
9
- with the tool's own introduction once it does.
8
+ server run, and every check of the WireCat standards passes. Its one service is a sample, Example Comments, made up
9
+ to show how a command reaches a service; its address never resolves. Replace this page with the tool's own
10
+ introduction once the CLI reaches its real service.
10
11
 
11
12
  To start a new CLI from the template:
12
13
 
@@ -30,6 +31,8 @@ such as `personal` in `clidev personal config show`; none is ever assumed. `CLID
30
31
  | Command | What it does |
31
32
  | --- | --- |
32
33
  | `clidev doctor` | Checks this installation: version, paths, settings and profiles, without the network |
34
+ | `clidev personal session start` | Signs the profile in with the service's token, kept in the OS keyring |
35
+ | `clidev personal comments list` | The sample: lists, shows, creates and updates comments in Example Comments |
33
36
  | `clidev commands --json` | Prints every command as JSON, for agents and scripts |
34
37
  | `clidev personal config show` | Shows each setting and where it came from |
35
38
  | `clidev skill install` | Installs the agent skill for Claude Code, Codex, Gemini CLI and OpenClaw; `--for hermes` for Hermes Agent |
@@ -57,6 +60,9 @@ keeps the shared files in step later, so a CLI made today does not drift from on
57
60
  - **`upgrade`** merges each template change into your CLI file by file, and asks before it touches a file you changed.
58
61
  - **The skeleton runs.** The shared root commands, settings with permissions, the agent skill and the MCP server
59
62
  work before a line of your own code.
63
+ - **A sample shows every pattern.** `comments` reaches a made-up service the way a real command must: the token in
64
+ the keyring, every request inside cli-core's run, writes behind the guard, and a strict fake in the tests.
65
+ `AGENTS.md` lists what to keep and what to replace.
60
66
  - **Every check is wired in:** lint, types, tests with a coverage floor, command contracts, the docs checks and the
61
67
  release script.
62
68
 
@@ -66,7 +72,8 @@ The copy tool is described in [kit/README.md](kit/README.md).
66
72
 
67
73
  A profile is a name you choose for one account, such as `personal` or `work`. It is the first word of every command
68
74
  that uses an account. No profile is ever assumed: a command without one stops and lists the profiles on this
69
- computer. The skeleton has no sign-in yet; the CLI built from it adds one.
75
+ computer. `session start` signs a profile in; its token goes to the OS keyring, or to a private file where no
76
+ keyring works.
70
77
 
71
78
  ## Settings and permissions
72
79
 
@@ -80,7 +87,8 @@ The skeleton connects to no service, keeps no messages and has no commands of it
80
87
 
81
88
  ## Security
82
89
 
83
- `clidev` keeps its settings and state on this computer and sends nothing to any service yet. See
90
+ `clidev` keeps its settings and state on this computer, and its token in the OS keyring. Its only service is the
91
+ sample's, whose address never resolves. See
84
92
  [Security](docs/security.md), and report a vulnerability privately as [SECURITY.md](SECURITY.md) says.
85
93
 
86
94
  ## When it does not work
package/biome.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "extends": ["./config/wirecat/biome.base.json"],
6
6
  "overrides": [
7
7
  {
8
- "includes": ["src/**", "!src/mcp.ts", "!**/*.test.ts"],
8
+ "includes": ["src/**", "!**/*.test.ts"],
9
9
  "linter": {
10
10
  "rules": {
11
11
  "style": {
@@ -15,7 +15,7 @@
15
15
  "patterns": [
16
16
  {
17
17
  "group": ["@modelcontextprotocol/**"],
18
- "message": "only src/mcp.ts serves MCP; commands and services do not know the protocol"
18
+ "message": "cli-core's appMcpCommand serves MCP; commands and services do not know the protocol"
19
19
  }
20
20
  ]
21
21
  }
@@ -18,7 +18,7 @@
18
18
  "type": "object"
19
19
  },
20
20
  "command": {
21
- "description": "a command clidev_tools_search returned: \"runs list\"",
21
+ "description": "a command clidev_tools_search returned: \"session list\"",
22
22
  "minLength": 1,
23
23
  "type": "string"
24
24
  }
@@ -542,7 +542,7 @@
542
542
  "type": "object"
543
543
  },
544
544
  "command": {
545
- "description": "a command clidev_tools_search returned: \"runs list\"",
545
+ "description": "a command clidev_tools_search returned: \"session list\"",
546
546
  "minLength": 1,
547
547
  "type": "string"
548
548
  }
@@ -9,7 +9,7 @@ if (
9
9
  (words[0] === "--version" || words[0] === "-V") &&
10
10
  !Object.keys(process.env).some((key) => key.endsWith("_TEST_ARGV_LOG"))
11
11
  ) {
12
- process.stdout.write("0.14.0\n")
12
+ process.stdout.write("0.16.0\n")
13
13
  } else {
14
14
  // Before the bundle compiles: the bundle's own call comes after its chunks.
15
15
  module.enableCompileCache()
@@ -28,7 +28,7 @@ if (
28
28
  : next(specifier, context),
29
29
  })
30
30
  }
31
- const chunks = ["../bundle/all-profiles-NVB2HT2S.js","../bundle/anthropic-ICTW7CNQ.js","../bundle/app-server-B6BCKXR7.js","../bundle/bun-ILPS6AYA.js","../bundle/bun-sqlite-JE6FJFQT.js","../bundle/decode-NXB5UKMO.js","../bundle/dist-ESHIQU4M.js","../bundle/echarts-DRDGGHJT.js","../bundle/embed-IKZ24BMC.js","../bundle/embedder-GMQ24DOQ.js","../bundle/first-screen-376CXGXJ.js","../bundle/instructions-EOQOXY6V.js","../bundle/mcp-7HBLFGCW.js","../bundle/mcp-KHUZOJO6.js","../bundle/mcp-MZHWJXAE.js","../bundle/node-52WF2326.js","../bundle/node-P4E63KXA.js","../bundle/node-TLZSIHIJ.js","../bundle/node-sqlite-73Z4SFOA.js","../bundle/oauth-EVTPAMZP.js","../bundle/open-46WZAPJ3.js","../bundle/openai-GWRDAHDU.js","../bundle/process-YIQPKLZI.js","../bundle/program-B3CY3FP7.js","../bundle/prompt-IVEYRLS5.js","../bundle/recognize-WVEKHODM.js","../bundle/remote-4K5VPMC7.js","../bundle/rerank-V24UJZYJ.js","../bundle/serve-PJWSOV7F.js","../bundle/server-2ZN7KZHZ.js","../bundle/server-5VYM7CCF.js","../bundle/state-ZPYGECVP.js","../bundle/store-6JQCLPQF.js","../bundle/store-maintenance-command-7XICIYHW.js"]
31
+ const chunks = ["../bundle/all-profiles-FS7AEOE7.js","../bundle/anthropic-5RN7BGDI.js","../bundle/app-server-WGTKZQKJ.js","../bundle/bun-ILPS6AYA.js","../bundle/bun-sqlite-WK2IGBGD.js","../bundle/decode-QMLOGZ6R.js","../bundle/dist-ESHIQU4M.js","../bundle/echarts-DRDGGHJT.js","../bundle/embed-PIR2LJR2.js","../bundle/embedder-6KBRPAEQ.js","../bundle/first-screen-3T5IXTFL.js","../bundle/instructions-EOQOXY6V.js","../bundle/mcp-7HBLFGCW.js","../bundle/node-52WF2326.js","../bundle/node-P4E63KXA.js","../bundle/node-TLZSIHIJ.js","../bundle/node-sqlite-LIK6OYNF.js","../bundle/oauth-7FVF7HQQ.js","../bundle/open-CRZDEATN.js","../bundle/openai-WAJ6WREQ.js","../bundle/process-YIQPKLZI.js","../bundle/program-ZFFFPBRS.js","../bundle/prompt-QQIVHO3M.js","../bundle/recognize-4KLBZDEC.js","../bundle/remote-BABMZTHA.js","../bundle/rerank-P3IQQJIL.js","../bundle/serve-25SSAKRZ.js","../bundle/server-G4GCDMYI.js","../bundle/server-P4XUYY5A.js","../bundle/state-QCQALQBJ.js","../bundle/store-ZECPRGGH.js","../bundle/store-maintenance-command-ECUO5G6L.js"]
32
32
  if (((words) => words
33
33
  .filter((word) => !word.startsWith("-"))
34
34
  .slice(0, 2)
@@ -9,10 +9,14 @@ import {
9
9
  StdioServerTransport,
10
10
  callerOf,
11
11
  serveStdio
12
- } from "./chunk-F34HGYWF.js";
12
+ } from "./chunk-JFXWLQ2L.js";
13
13
  import {
14
14
  McpServer
15
15
  } from "./chunk-5D4AEUVA.js";
16
+ import "./chunk-ORV4EGNC.js";
17
+ import "./chunk-3WDYHOAF.js";
18
+ import "./chunk-WDWP5N6M.js";
19
+ import "./chunk-Y5E2WNWB.js";
16
20
  import {
17
21
  __name
18
22
  } from "./chunk-ZVVMGPBJ.js";
@@ -251,7 +255,7 @@ var serveAllProfilesOverStdio = /* @__PURE__ */ __name(async (options, io = {})
251
255
  }, "serveAllProfilesOverStdio");
252
256
  var serveAllProfilesOverHttp = /* @__PURE__ */ __name(async (options, http) => {
253
257
  const { build, close } = allProfilesServer(options);
254
- const { serveOverHttp } = await import("./serve-PJWSOV7F.js");
258
+ const { serveOverHttp } = await import("./serve-25SSAKRZ.js");
255
259
  const listening = await serveOverHttp(build, {
256
260
  ...http,
257
261
  appName: options.command,
@@ -271,11 +275,10 @@ var serveAllProfilesOverHttp = /* @__PURE__ */ __name(async (options, http) => {
271
275
  }, "close")
272
276
  };
273
277
  }, "serveAllProfilesOverHttp");
274
-
275
278
  export {
276
279
  CHILD_IDLE_MS,
277
- withProfile,
278
280
  allProfilesServer,
281
+ serveAllProfilesOverHttp,
279
282
  serveAllProfilesOverStdio,
280
- serveAllProfilesOverHttp
283
+ withProfile
281
284
  };
@@ -2,11 +2,11 @@ import { createRequire as __bundleRequire } from "node:module";
2
2
  const require = __bundleRequire(import.meta.url);
3
3
  import {
4
4
  checkedOptions
5
- } from "./chunk-R5RYPD23.js";
5
+ } from "./chunk-RYOAQCVJ.js";
6
6
  import {
7
7
  messagesFor,
8
8
  systemFor
9
- } from "./chunk-LATYBJ4T.js";
9
+ } from "./chunk-FMO6F5Q2.js";
10
10
  import {
11
11
  Anthropic
12
12
  } from "./chunk-ADLS6FVG.js";
@@ -15,7 +15,7 @@ import {
15
15
  optional,
16
16
  strictObject,
17
17
  string
18
- } from "./chunk-IOJ2DTCY.js";
18
+ } from "./chunk-WDWP5N6M.js";
19
19
  import "./chunk-Y5E2WNWB.js";
20
20
  import {
21
21
  __name
@@ -3,11 +3,10 @@ const require = __bundleRequire(import.meta.url);
3
3
  import {
4
4
  PER_RUN,
5
5
  envelope
6
- } from "./chunk-BEBD27QT.js";
7
- import "./chunk-BWBZCYUN.js";
6
+ } from "./chunk-EILS4BTQ.js";
8
7
  import {
9
8
  mcpUrlOf
10
- } from "./chunk-GUFPQZUA.js";
9
+ } from "./chunk-TXHCL75B.js";
11
10
  import {
12
11
  answeredWire
13
12
  } from "./chunk-VS5LGSTM.js";
@@ -17,20 +16,20 @@ import {
17
16
  registerCommands,
18
17
  runCommandForAgent,
19
18
  toolsFromCommands
20
- } from "./chunk-UT5TQ6MD.js";
19
+ } from "./chunk-BPMKAZSS.js";
21
20
  import {
22
21
  agentJson,
23
22
  skillResource
24
- } from "./chunk-HYTHTSJP.js";
23
+ } from "./chunk-WM5AMTRY.js";
25
24
  import {
26
25
  StdioServerTransport,
27
26
  serveStdio
28
- } from "./chunk-F34HGYWF.js";
29
- import "./chunk-IZKXJJ7V.js";
30
- import "./chunk-5SAF3XOL.js";
31
- import "./chunk-PIJUGF5F.js";
32
- import "./chunk-2DAXUNTS.js";
33
- import "./chunk-AS2PJNQ2.js";
27
+ } from "./chunk-JFXWLQ2L.js";
28
+ import "./chunk-JJZCPBH3.js";
29
+ import "./chunk-ZSY4WAGI.js";
30
+ import "./chunk-CTBHQRX6.js";
31
+ import "./chunk-5HHIPDNW.js";
32
+ import "./chunk-2MWNE4L3.js";
34
33
  import "./chunk-7Z4HR6GO.js";
35
34
  import {
36
35
  appInstructions
@@ -40,11 +39,11 @@ import {
40
39
  } from "./chunk-5D4AEUVA.js";
41
40
  import {
42
41
  mcpHttpMarker
43
- } from "./chunk-6AW4DDK4.js";
44
- import "./chunk-DZLHY4IA.js";
45
- import "./chunk-SOYOOU2D.js";
46
- import "./chunk-LATYBJ4T.js";
47
- import "./chunk-BM5ZCDPQ.js";
42
+ } from "./chunk-ETJXPB6A.js";
43
+ import "./chunk-C3GNOEJP.js";
44
+ import "./chunk-MBBOINK4.js";
45
+ import "./chunk-FMO6F5Q2.js";
46
+ import "./chunk-MEXT6XDP.js";
48
47
  import {
49
48
  DEFAULT_OUTPUT_BYTES,
50
49
  changesAuthorityAt,
@@ -54,25 +53,25 @@ import {
54
53
  readOnlyLockOf,
55
54
  readsOverMcpAt,
56
55
  rootOf2 as rootOf
57
- } from "./chunk-RJ7AS7UB.js";
58
- import "./chunk-VXOR76ZL.js";
59
- import "./chunk-U6QD3J3F.js";
56
+ } from "./chunk-HTYLT6ME.js";
57
+ import "./chunk-ORV4EGNC.js";
58
+ import "./chunk-ATHZEV2Z.js";
60
59
  import "./chunk-VOTEB3AN.js";
61
- import "./chunk-NVNUUHT5.js";
62
- import "./chunk-R2JKPHH5.js";
63
- import "./chunk-YOJLIVGO.js";
60
+ import "./chunk-EA3TMI4A.js";
61
+ import "./chunk-NVPWZF67.js";
62
+ import "./chunk-5KAYQKBK.js";
64
63
  import "./chunk-3WDYHOAF.js";
65
- import "./chunk-TJJ2XSJP.js";
66
- import "./chunk-NH6LZ3H3.js";
64
+ import "./chunk-QGOOPPEW.js";
65
+ import "./chunk-RFJKNFVF.js";
67
66
  import "./chunk-3MDNAYQA.js";
68
67
  import "./chunk-CGFQT3LQ.js";
69
- import "./chunk-DQIBGCF3.js";
70
- import "./chunk-45244QQ6.js";
71
- import "./chunk-62JRF2JA.js";
68
+ import "./chunk-Z5NL6YNU.js";
69
+ import "./chunk-OF56L2JU.js";
70
+ import "./chunk-6WOJIAYA.js";
72
71
  import {
73
72
  CliError,
74
73
  mcpErrorResult
75
- } from "./chunk-IOJ2DTCY.js";
74
+ } from "./chunk-WDWP5N6M.js";
76
75
  import "./chunk-Y5E2WNWB.js";
77
76
  import {
78
77
  __name
@@ -193,7 +192,7 @@ var serveAppOverStdio = /* @__PURE__ */ __name(async (command, context, options)
193
192
  var serveAppOverHttp = /* @__PURE__ */ __name(async (command, context, options, http) => {
194
193
  const { app } = options;
195
194
  const { build } = createAppServer(command, context, options);
196
- const { serveOverHttp } = await import("./serve-PJWSOV7F.js");
195
+ const { serveOverHttp } = await import("./serve-25SSAKRZ.js");
197
196
  const listening = await serveOverHttp(build, {
198
197
  ...http,
199
198
  appName: app.command,
@@ -3,8 +3,8 @@ const require = __bundleRequire(import.meta.url);
3
3
  import {
4
4
  loadVectorExtension,
5
5
  vectorExtension
6
- } from "./chunk-LFLSV353.js";
7
- import "./chunk-IOJ2DTCY.js";
6
+ } from "./chunk-DEHBTFIC.js";
7
+ import "./chunk-WDWP5N6M.js";
8
8
  import {
9
9
  importFrom
10
10
  } from "./chunk-Y5E2WNWB.js";
@@ -12,7 +12,7 @@ import {
12
12
  optional,
13
13
  saveConfigFile,
14
14
  string
15
- } from "./chunk-IOJ2DTCY.js";
15
+ } from "./chunk-WDWP5N6M.js";
16
16
  import {
17
17
  __name,
18
18
  __toESM
@@ -2,7 +2,7 @@ import { createRequire as __bundleRequire } from "node:module";
2
2
  const require = __bundleRequire(import.meta.url);
3
3
  import {
4
4
  CliError
5
- } from "./chunk-IOJ2DTCY.js";
5
+ } from "./chunk-WDWP5N6M.js";
6
6
  import {
7
7
  __name
8
8
  } from "./chunk-ZVVMGPBJ.js";
@@ -37,7 +37,7 @@ import {
37
37
  unknown,
38
38
  variant,
39
39
  visibleControls
40
- } from "./chunk-IOJ2DTCY.js";
40
+ } from "./chunk-WDWP5N6M.js";
41
41
  import {
42
42
  __name
43
43
  } from "./chunk-ZVVMGPBJ.js";
@@ -2,7 +2,7 @@ import { createRequire as __bundleRequire } from "node:module";
2
2
  const require = __bundleRequire(import.meta.url);
3
3
  import {
4
4
  resolvePaths
5
- } from "./chunk-IOJ2DTCY.js";
5
+ } from "./chunk-WDWP5N6M.js";
6
6
  import {
7
7
  __name
8
8
  } from "./chunk-ZVVMGPBJ.js";
@@ -0,0 +1,141 @@
1
+ import { createRequire as __bundleRequire } from "node:module";
2
+ const require = __bundleRequire(import.meta.url);
3
+ import {
4
+ CliError,
5
+ isCliError
6
+ } from "./chunk-WDWP5N6M.js";
7
+ import {
8
+ __name
9
+ } from "./chunk-ZVVMGPBJ.js";
10
+
11
+ // node_modules/.pnpm/@wirecat+cli-base@0.77.1_commander@15.0.0_esbuild@0.28.2_typescript@7.0.2_vitest@5.0.5/node_modules/@wirecat/cli-base/dist/http/body.js
12
+ var interrupted = /* @__PURE__ */ __name((signal) => isCliError(signal.reason) ? signal.reason : new CliError(signal.reason instanceof Error && signal.reason.name === "TimeoutError" ? "timeout" : "cancelled", "HTTP response read was interrupted"), "interrupted");
13
+ var checked = /* @__PURE__ */ __name((signal) => {
14
+ if (signal?.aborted)
15
+ throw interrupted(signal);
16
+ }, "checked");
17
+ var abortable = /* @__PURE__ */ __name(async (pending, signal) => {
18
+ if (!signal)
19
+ return pending;
20
+ let abort = /* @__PURE__ */ __name(() => {
21
+ }, "abort");
22
+ const stopped = new Promise((_, reject) => {
23
+ abort = /* @__PURE__ */ __name(() => reject(interrupted(signal)), "abort");
24
+ signal.addEventListener("abort", abort, { once: true });
25
+ if (signal.aborted)
26
+ abort();
27
+ });
28
+ try {
29
+ return await Promise.race([pending, stopped]);
30
+ } finally {
31
+ signal.removeEventListener("abort", abort);
32
+ }
33
+ }, "abortable");
34
+ var readJsonResponse = /* @__PURE__ */ __name(async (response, options) => {
35
+ const { maxBytes, signal } = options ?? {};
36
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1)
37
+ throw new CliError("validation_error", "maxBytes must be a positive safe integer");
38
+ if (signal !== void 0 && !(signal instanceof AbortSignal))
39
+ throw new CliError("validation_error", "signal must be an AbortSignal");
40
+ if (signal?.aborted) {
41
+ void response.body?.cancel().catch(() => {
42
+ });
43
+ throw interrupted(signal);
44
+ }
45
+ const length = response.headers.get("content-length");
46
+ if (length !== null && (!/^\d+$/.test(length) || Number(length) > maxBytes)) {
47
+ void response.body?.cancel().catch(() => {
48
+ });
49
+ throw new CliError("invalid_response", "HTTP response exceeds the byte budget");
50
+ }
51
+ if (!response.body)
52
+ throw new CliError("invalid_response", "HTTP response has no body");
53
+ if (response.body.locked)
54
+ throw new CliError("invalid_response", "HTTP response body is already in use");
55
+ const reader = response.body.getReader();
56
+ const chunks = [];
57
+ let bytes = 0;
58
+ try {
59
+ for (; ; ) {
60
+ checked(signal);
61
+ const part = await abortable(reader.read(), signal);
62
+ checked(signal);
63
+ if (part.done)
64
+ break;
65
+ if (!(part.value instanceof Uint8Array))
66
+ throw new CliError("invalid_response", "HTTP response is not bytes");
67
+ bytes += part.value.byteLength;
68
+ if (bytes > maxBytes)
69
+ throw new CliError("invalid_response", "HTTP response exceeds the byte budget");
70
+ if (part.value.byteLength)
71
+ chunks.push(new Uint8Array(part.value));
72
+ }
73
+ const body = new Uint8Array(bytes);
74
+ let offset = 0;
75
+ for (const chunk of chunks) {
76
+ body.set(chunk, offset);
77
+ offset += chunk.byteLength;
78
+ }
79
+ try {
80
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(body));
81
+ } catch {
82
+ throw new CliError("invalid_response", "HTTP response is not valid UTF-8 JSON");
83
+ }
84
+ } catch (error) {
85
+ checked(signal);
86
+ if (isCliError(error))
87
+ throw error;
88
+ throw new CliError("network_error", "HTTP response could not be read");
89
+ } finally {
90
+ void reader.cancel().catch(() => {
91
+ });
92
+ reader.releaseLock();
93
+ }
94
+ }, "readJsonResponse");
95
+
96
+ // node_modules/.pnpm/@wirecat+cli-base@0.77.1_commander@15.0.0_esbuild@0.28.2_typescript@7.0.2_vitest@5.0.5/node_modules/@wirecat/cli-base/dist/http/index.js
97
+ var parseRetryAfter = /* @__PURE__ */ __name((header, now) => {
98
+ if (!header)
99
+ return void 0;
100
+ const trimmed = header.trim();
101
+ if (/^\d+$/.test(trimmed))
102
+ return Number(trimmed) * 1e3;
103
+ const at = Date.parse(trimmed);
104
+ if (Number.isNaN(at))
105
+ return void 0;
106
+ const delta = at - now().getTime();
107
+ return delta > 0 ? delta : void 0;
108
+ }, "parseRetryAfter");
109
+ var parseRateLimitReset = /* @__PURE__ */ __name((header, now) => {
110
+ if (!header)
111
+ return void 0;
112
+ const seconds = Number(header.trim());
113
+ if (!Number.isFinite(seconds))
114
+ return void 0;
115
+ const delta = seconds * 1e3 - now().getTime();
116
+ return delta > 0 ? delta : void 0;
117
+ }, "parseRateLimitReset");
118
+ var providerWaitMs = /* @__PURE__ */ __name((headers, now) => parseRetryAfter(headers.get("retry-after"), now) ?? parseRateLimitReset(headers.get("x-ratelimit-reset"), now), "providerWaitMs");
119
+ var statusToCode = /* @__PURE__ */ __name((status) => {
120
+ if (status === 400 || status === 422)
121
+ return "validation_error";
122
+ if (status === 401)
123
+ return "authentication_error";
124
+ if (status === 403)
125
+ return "permission_error";
126
+ if (status === 404)
127
+ return "not_found";
128
+ if (status === 408)
129
+ return "timeout";
130
+ if (status === 429)
131
+ return "rate_limited";
132
+ if (status === 502 || status === 503 || status === 504)
133
+ return "provider_unavailable";
134
+ return "provider_error";
135
+ }, "statusToCode");
136
+
137
+ export {
138
+ readJsonResponse,
139
+ providerWaitMs,
140
+ statusToCode
141
+ };
@@ -2,7 +2,7 @@ import { createRequire as __bundleRequire } from "node:module";
2
2
  const require = __bundleRequire(import.meta.url);
3
3
  import {
4
4
  CliError
5
- } from "./chunk-IOJ2DTCY.js";
5
+ } from "./chunk-WDWP5N6M.js";
6
6
  import {
7
7
  importFrom
8
8
  } from "./chunk-Y5E2WNWB.js";
@@ -3,10 +3,10 @@ const require = __bundleRequire(import.meta.url);
3
3
  import {
4
4
  readSharedSection,
5
5
  writeSharedSection
6
- } from "./chunk-TJJ2XSJP.js";
6
+ } from "./chunk-QGOOPPEW.js";
7
7
  import {
8
8
  CliError
9
- } from "./chunk-IOJ2DTCY.js";
9
+ } from "./chunk-WDWP5N6M.js";
10
10
  import {
11
11
  __name
12
12
  } from "./chunk-ZVVMGPBJ.js";