@wirecat/cli-template 0.1.4 → 0.1.6

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 (51) hide show
  1. package/.github/workflows/ci.yml +2 -1
  2. package/.github/workflows/main.yml +2 -1
  3. package/.github/workflows/monthly.yml +1 -1
  4. package/AGENTS.md +2 -0
  5. package/CHANGELOG.md +77 -0
  6. package/README.md +34 -6
  7. package/bin/release +68 -6
  8. package/biome.json +23 -1
  9. package/config/parity-groups.json +6 -0
  10. package/contracts/help.snap +2 -2
  11. package/contracts/mcp-tools.json +2 -2
  12. package/contracts/plan.json +1 -1
  13. package/cspell.json +3 -7
  14. package/dist/commands/doctor.d.ts +25 -4
  15. package/dist/commands/doctor.d.ts.map +1 -1
  16. package/dist/commands/doctor.js +26 -19
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/mcp.d.ts.map +1 -1
  19. package/dist/mcp.js +6 -2
  20. package/dist/mcp.js.map +1 -1
  21. package/dist/version.d.ts +1 -1
  22. package/dist/version.js +1 -1
  23. package/docs/commands.md +6 -6
  24. package/docs/configuration.md +1 -1
  25. package/docs/dev/TESTING.md +2 -0
  26. package/docs/dev/reference-clis.md +6 -2
  27. package/docs/dev/skills/release/SKILL.md +1 -1
  28. package/docs/index.md +3 -3
  29. package/docs/installation.md +1 -1
  30. package/docs/security.md +1 -1
  31. package/docs/troubleshooting.md +2 -2
  32. package/docs/usage.md +1 -1
  33. package/kit/dist/main.js +1 -1
  34. package/kit/dist/manifest.js +18 -30
  35. package/kit/dist/new.js +9 -7
  36. package/kit/dist/rename.js +1 -0
  37. package/kit/dist/source.js +7 -4
  38. package/package.json +7 -7
  39. package/scripts/commands.ts +1 -1
  40. package/scripts/docs-rules.ts +15 -0
  41. package/scripts/smoke.ts +26 -7
  42. package/skills/clidev-cli/SKILL.md +3 -3
  43. package/src/commands/doctor.ts +36 -19
  44. package/src/mcp.test.ts +0 -1
  45. package/src/mcp.ts +7 -2
  46. package/src/program.test.ts +26 -5
  47. package/src/version.ts +1 -1
  48. package/vitest.config.ts +11 -3
  49. package/kit/README.md +0 -136
  50. package/kit/tsconfig.json +0 -17
  51. package/kit/tsconfig.test.json +0 -5
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
2
2
  import { join } from "node:path"
3
3
  import { expect, it } from "vitest"
4
+ import { doctorReport } from "./commands/doctor.js"
4
5
  import { IDENTITY } from "./identity.js"
5
6
  import { run } from "./program.js"
6
7
 
@@ -25,20 +26,40 @@ it("lists the required root commands and config", async () => {
25
26
  it("doctor answers without a config file", async () => {
26
27
  const { code, out } = await ran(["doctor", "--json"])
27
28
  expect(code).toBe(0)
28
- expect(json(out).item).toMatchObject({ config: { exists: false }, profiles: [], read_only: null })
29
+ expect(json(out).item).toMatchObject({ config: { found: false, issues: [] }, profiles: [], read_only: null })
29
30
  })
30
31
 
31
- it("doctor reports an unreadable config as a field", async () => {
32
+ const withConfig = async (text: string, check: (config: unknown) => void) => {
32
33
  const dir = process.env.CLIDEV_CONFIG_DIR ?? ""
33
34
  mkdirSync(dir, { recursive: true })
34
- writeFileSync(join(dir, "config.json"), "not json")
35
+ writeFileSync(join(dir, "config.json"), text)
35
36
  try {
36
37
  const { code, out } = await ran(["doctor", "--json"])
37
38
  expect(code).toBe(0)
38
- expect(json(out).item.config).toMatchObject({ exists: true, error: expect.any(String) })
39
+ check(json(out).item.config)
39
40
  } finally {
40
41
  rmSync(join(dir, "config.json"))
41
42
  }
43
+ }
44
+
45
+ it("doctor reports invalid JSON as an issue", async () => {
46
+ await withConfig('{\n "defaults": {\n', (config) =>
47
+ expect(config).toMatchObject({ found: true, issues: [{ kind: "invalid_json", line: 3 }] }),
48
+ )
49
+ })
50
+
51
+ it("doctor reports an unknown setting with the command that removes it", async () => {
52
+ await withConfig('{ "defaults": { "limt": 3 } }', (config) =>
53
+ expect(config).toMatchObject({
54
+ found: true,
55
+ issues: [{ kind: "unknown_setting", key: "limt", fix: "clidev config unset --defaults limt" }],
56
+ }),
57
+ )
58
+ })
59
+
60
+ it("doctor reports settings an environment variable breaks, and still answers", () => {
61
+ const report = doctorReport({ ...process.env, CLIDEV_EMBEDDING_DIMS: "many" })
62
+ expect(report).toMatchObject({ config: { issues: [] }, settings: { error: expect.any(String) }, profiles: null })
42
63
  })
43
64
 
44
65
  it("config show names the profile it was given", async () => {
@@ -65,7 +86,7 @@ it("upgrade --check installs nothing when npm cannot be reached", async () => {
65
86
  expect(json(out).item).toMatchObject({ current: IDENTITY.version, latest: null, updated: false })
66
87
  })
67
88
 
68
- it("keeps a failed command as a run, and runs list, show and path read it back (WC-LIFE-15)", async () => {
89
+ it("keeps a failed command as a run, and runs list, show and path read it back", async () => {
69
90
  expect((await ran(["--no-such-flag"])).code).toBe(2)
70
91
  const listed = await ran(["runs", "list", "--limit", "5", "--page", "1", "--json"])
71
92
  expect(listed.code).toBe(0)
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.1.4"
1
+ export const VERSION = "0.1.6"
package/vitest.config.ts CHANGED
@@ -10,10 +10,18 @@ export default defineConfig({
10
10
  globalSetup: ["src/testing/argv-log.ts"],
11
11
  coverage: {
12
12
  provider: "v8",
13
- include: ["src/**/*.ts"],
14
- exclude: ["src/**/*.test.ts", "src/testing/**", "src/bin/**"],
13
+ include: ["src/**/*.ts", "kit/src/**/*.ts"],
14
+ exclude: [
15
+ "src/**/*.test.ts",
16
+ // Test setup: the sandbox and the argv log run only under vitest.
17
+ "src/testing/**",
18
+ // The entry point: one line handing argv to run(), which the suite drives directly.
19
+ "src/bin/**",
20
+ // The kit's entry point runs only as a separate process, in kit/test/package.test.ts.
21
+ "kit/src/main.ts",
22
+ ],
15
23
  reporter: ["text-summary", "json-summary"],
16
- thresholds: { lines: 90, statements: 90, functions: 80, branches: 70 },
24
+ thresholds: { lines: 90, statements: 90, functions: 80, branches: 70, perFile: { lines: 50 } },
17
25
  },
18
26
  },
19
27
  })
package/kit/README.md DELETED
@@ -1,136 +0,0 @@
1
- # kit — `new` and `upgrade`
2
-
3
- The copy tool behind `npx @wirecat/cli-template`. It is the package's `cli-template` bin
4
- (`kit/dist/main.js`, built from `kit/src` by `tsc -p kit`). It is never copied into a new CLI.
5
-
6
- ```sh
7
- npx @wirecat/cli-template new cal-cli --command cal --package @wirecat/cal-cli --owner WireCatLabs \
8
- --category notes --content-lang en,ru --sign-in token --api official-docs --filters time,person \
9
- --never-store tokens --untrusted messages,links
10
- npx @wirecat/cli-template upgrade --dry-run
11
- ```
12
-
13
- ## `new <name>`
14
-
15
- `new` copies the files of its own package version into `./<name>`. It renames the placeholders, rewrites
16
- `package.json`, runs `git init` and writes `template.json`, `docs/dev/DESIGN.md` and the reference CLI into
17
- `AGENTS.md`. It then prints the four steps that need a person. It does not install packages, commit or push.
18
-
19
- In a terminal it asks for every missing answer. Without a terminal, a missing answer is an error that lists the
20
- choices. Nothing is guessed.
21
-
22
- | Flag | Answers (several are comma-separated) |
23
- |---|---|
24
- | `<name>` | the folder and repository name |
25
- | `--command` | the command word |
26
- | `--package` | the npm package name |
27
- | `--owner` | the GitHub owner |
28
- | `--category` | `messenger`, `mail`, `notes`, `files`, `calendar`, `meetings` |
29
- | `--content-lang` | language codes, several: `en,ru` |
30
- | `--sign-in` | `oauth`, `token`, `session`, `none` |
31
- | `--api` | `official-client`, `official-docs`, `unofficial` |
32
- | `--filters` | several of `time`, `person`, `container`, `text`, `status` |
33
- | `--never-store` | several of `tokens`, `codes`, `secret-links`, `content` |
34
- | `--untrusted` | several of `messages`, `files`, `links`, `names`, or `none` alone |
35
-
36
- Every question except the names and `--content-lang` also takes `other:<your answer>`.
37
-
38
- `new` writes the content languages into `contentLanguages` in `src/identity.ts`; on the first run, cli-core ticks them
39
- in advance in the search-language question, and the person confirms. `DESIGN.md` records them too, with the
40
- speech-model order a messenger definition should use and the warnings for languages that have no stemmer or speech
41
- model in cli-core. `new` writes no search or model setting.
42
-
43
- ## `upgrade`
44
-
45
- Run it in a CLI made from the template, with a clean git working tree. For each template-owned file that the
46
- template changed since the recorded version, it asks one of four things:
47
-
48
- - **apply** merges the change with `git merge-file`. The base is the recorded version, downloaded with `npm pack`.
49
- "Ours" is the CLI's file and "theirs" is this version. Both template versions are renamed first, so the command
50
- word never conflicts.
51
- - **replace** takes the template's file as it is.
52
- - **skip** keeps the CLI's file. It is asked again only when the template changes that file again.
53
- - **diff** shows the template's change.
54
-
55
- Without a terminal, `--apply`, `--replace` or `--skip` sets the answer for every file. `--dry-run` lists what each
56
- file would get and writes nothing. Seeds and `package.json` are never touched.
57
-
58
- A conflict leaves git markers, and `template.json` keeps the new state under `pending`. Resolve the markers and run
59
- `upgrade` again: it checks that the markers are gone and then moves to the new version.
60
-
61
- ## `template.json` in this repository
62
-
63
- ```json
64
- {
65
- "files": [".github/", "bin/release", "AGENTS.md", "docs/meta.json"],
66
- "seeds": ["src/", "README.md", "docs/", "config/wirecat/"],
67
- "placeholders": {
68
- "command": "clidev", "name": "clidev-cli", "package": "@wirecat/clidev-cli", "repository": "WireCatLabs/clidev-cli"
69
- }
70
- }
71
- ```
72
-
73
- - `files` lists the template-owned files that `upgrade` replays. `seeds` lists the files copied once and never
74
- touched again; `config/wirecat/` goes there, because community's installer owns it. An entry ending in `/` is a
75
- folder. The most specific entry wins: an exact path beats a folder, a deeper folder beats a shallower one, and a
76
- seed wins a tie. Only listed files are copied. A listed file missing from the package stops `new`.
77
- - `template.json` and `kit/` are never copied, whatever the lists say. The kit's tests run inside `pnpm test`, so
78
- the configs name `kit/`: `new` and `upgrade` drop every `"kit/…"` entry from `vitest.config.ts`, `biome.json`,
79
- `cspell.json`, `tsconfig*.json` and `.markdownlint-cli2.jsonc`. Do not add a kit-only workflow: `.github/` is
80
- template-owned and would carry it into every new CLI.
81
- - `README.md` and `CHANGELOG.md` describe the template, so `new` writes short ones for the new CLI.
82
- - `config/parity-groups.json` puts the template in parity groups with the published CLIs, so `scripts/audit.sh`
83
- compares its `commands`, `upgrade`, `config` and `skill` paths with theirs. It is in neither list, so a new CLI does
84
- not get it: the file replaces cli-base's `PARITY_GROUPS`, and a new CLI joins those groups instead (step 4 of
85
- `new`'s output).
86
- - `docs/dev/DESIGN.md` is always a seed: it holds the CLI's interview answers.
87
- - `package.json` is always a seed. `new` rewrites it: `name` becomes `--package`, `version` becomes `0.0.0`, and
88
- `repository`, `homepage` and `bugs` point at `<owner>/<name>`. It also drops every `bin`, `files` entry and script
89
- that points into `kit/`, and the `&& pnpm kit:…` parts of other scripts. `src/version.ts` gets `VERSION = "0.0.0"`.
90
- - `placeholders` names the words `new` and `upgrade` replace. The command word is replaced as a whole word, so
91
- `clidev` changes but `clidevs` and cli-base's `cli-dev` do not. Its upper-case form changes too: `CLIDEV_HOME`
92
- becomes `CAL_HOME`. Followed by a colon and a space it is a key and stays: `clidev: CLIDEV` becomes `clidev: CAL`.
93
- `name` (`clidev-cli`) is the repository name: `skills/clidev-cli/` becomes `skills/cal-cli/`. The word `clidev`
94
- cannot appear in prose for any other meaning. Paths are renamed the same way: `src/bin/clidev.ts` becomes
95
- `src/bin/cal.ts`.
96
- - Each template version is renamed with its own `placeholders`, so `upgrade` from 0.1.0 or 0.1.1, which used `app`,
97
- merges cleanly. A key missing from `placeholders` means its `app` value (`app`, `app-cli`, `@wirecat/app-cli`,
98
- `WireCatLabs/app-cli`): those versions left `name` out.
99
- - The version is `package.json`'s, never a second copy.
100
- - `AGENTS.md` can hold the block that `new` fills with the reference CLI:
101
-
102
- ```md
103
- <!-- cli-template:reference-cli:start -->
104
- <!-- cli-template:reference-cli:end -->
105
- ```
106
-
107
- Without the block, `new` appends one.
108
-
109
- ## `template.json` in a CLI made from it
110
-
111
- `new` writes this shape and `upgrade` keeps it current:
112
-
113
- ```json
114
- {
115
- "template": "@wirecat/cli-template",
116
- "version": "0.1.0",
117
- "names": { "name": "cal-cli", "command": "cal", "package": "@wirecat/cal-cli", "owner": "WireCatLabs" },
118
- "files": { ".github/workflows/ci.yml": "<sha256 of the renamed template file at this version>" },
119
- "seeds": ["src/", "README.md", "docs/", "config/wirecat/"]
120
- }
121
- ```
122
-
123
- ## What the package needs from the repository
124
-
125
- - `"bin": { "cli-template": "kit/dist/main.js" }`, and `kit/dist` in `files`. No ignore file inside `kit/`: npm
126
- applies it and drops `kit/dist` from the package. npm also never packs `pnpm-lock.yaml`, so it is not a seed; the
127
- new CLI makes its own on `pnpm install`.
128
- - Every template-owned file and seed in `files`. npm drops a nested `.gitignore` and any `.npmrc` when it packs.
129
- When it installs a package, it renames `.gitignore` to `.npmignore`. So the package ships the root `.gitignore` as
130
- `gitignore`, and `new` and `upgrade` read it back as `.gitignore`. The test in `kit/test/package.test.ts` runs the
131
- bin from a tarball installed with `npm install` to catch that.
132
- - `kit:build` (`tsc -p kit`, then the copy to `gitignore`) run by `build`, because `bin/release` and CI pack with
133
- `--ignore-scripts`. Node does not strip types under `node_modules`, so the bin must be JavaScript.
134
- - `pnpm kit:e2e` packs this repository, installs the tarball with npm, runs `new` and then `pnpm install` and
135
- `pnpm ci:quick` in the new CLI. It needs the network and takes a few minutes, so it is not part of `pnpm test`.
136
- - `kit/test/**/*.test.ts` in vitest's `include`; `kit/dist` is ignored by the root `**/dist` rules of Biome and git.
package/kit/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2023",
4
- "lib": ["ES2023"],
5
- "module": "NodeNext",
6
- "moduleResolution": "NodeNext",
7
- "types": ["node"],
8
- "strict": true,
9
- "noUncheckedIndexedAccess": true,
10
- "verbatimModuleSyntax": true,
11
- "isolatedModules": true,
12
- "skipLibCheck": true,
13
- "rootDir": "src",
14
- "outDir": "dist"
15
- },
16
- "include": ["src/**/*.ts"]
17
- }
@@ -1,5 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": { "noEmit": true, "rootDir": "." },
4
- "include": ["src/**/*.ts", "test/**/*.ts"]
5
- }