@wirecat/cli-base 0.55.0 → 0.56.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/CHANGELOG.md +53 -0
- package/README.md +2 -1
- package/dist/dev/lint.d.ts +10 -0
- package/dist/dev/lint.js +25 -0
- package/dist/dev/main.d.ts +7 -0
- package/dist/dev/main.js +57 -2
- package/dist/mcp/index.d.ts +19 -0
- package/dist/mcp/index.js +40 -6
- package/dist/release/fold.d.ts +4 -1
- package/dist/release/fold.js +6 -3
- package/dist/skill/index.d.ts +10 -0
- package/dist/skill/index.js +49 -11
- package/dist/standards/lint.js +6 -0
- package/dist/standards/vocabulary.js +1 -0
- package/docs/standards/README.md +1 -1
- package/docs/standards/architecture.md +2 -2
- package/docs/standards/kit-surface.md +1 -1
- package/docs/standards/lifecycle.md +33 -0
- package/docs/standards/new-cli.md +4 -2
- package/docs/standards/releases.md +3 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,59 @@ Versions before 0.8.0 are in the [git tags](https://github.com/WireCatLabs/cli-b
|
|
|
8
8
|
Every entry says what changed as a caller sees it, why, and what to watch for — the rules are
|
|
9
9
|
[`docs/dev/CONVENTIONS.md`](docs/dev/CONVENTIONS.md#the-changelog).
|
|
10
10
|
|
|
11
|
+
## 0.56.0 — 25.10.2026
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`<cli> skill show --for claude|agents` prints the copy installed for that agent** instead of this version's
|
|
16
|
+
SKILL.md, as text or `{ name, target, path, installed, content }` with `--json`. A host could show only the bundled
|
|
17
|
+
copy and `skill status`'s version numbers, so it could not show what `skill install` would change; now it reads
|
|
18
|
+
both and compares them. It reads only, fails with `not_found` and the install command when the agent has no copy,
|
|
19
|
+
and refuses a shipped skill by name, which is never installed.
|
|
20
|
+
|
|
21
|
+
- **`removeStdioEntry` and `stdioEntryStatus` in `@wirecat/cli-base/mcp`** undo and read what `installStdioEntry`
|
|
22
|
+
writes. A CLI could add itself to Codex or Claude Code, but not take itself out or say whether it is there, so a
|
|
23
|
+
host such as WireCat Desktop had to read each client's configuration on its own. `removeStdioEntry(client, name)`
|
|
24
|
+
runs the client's own `mcp remove`, for Claude Code with `--scope user` (the scope `add` uses), so a project's entry
|
|
25
|
+
with the same name stays; a name the client does not have returns `removed: false`. `stdioEntryStatus(client,
|
|
26
|
+
name)` returns `registered`, `missing`, or `unavailable` when the client is not installed. Both go through the
|
|
27
|
+
client's `mcp get` first and refuse a name that is not letters, digits, `_` or `-`.
|
|
28
|
+
|
|
29
|
+
- **Every CLI keeps a run log: `WC-LIFE-15` and the `missing-run-log` lint rule**
|
|
30
|
+
([Lifecycle](docs/standards/lifecycle.md#wc-life-15--keep-a-run-log-through-cli-core-and-read-it-back-with-runs)).
|
|
31
|
+
The handbook described what a log may hold but never required one, and drive, memo and zm shipped without a run
|
|
32
|
+
log. `cli-dev lint` now fails a CLI whose command tree has no `runs list` or `runs show`, or no global `--record` or
|
|
33
|
+
`--no-record`. With `--cli`, it also runs the built CLI once with an unknown flag, in a new empty home with none of
|
|
34
|
+
the caller's environment but `PATH`, and fails when `runs list --json` then shows no failed run. A CLI without
|
|
35
|
+
`runs` gets the finding on its next cli-base bump: register cli-core's `runsCommand(app)`, or baseline the
|
|
36
|
+
finding with `cli-dev lint --write-baseline` until it does. The weekly cross-CLI audit lists it too.
|
|
37
|
+
|
|
38
|
+
- **`<cli> skill status` says whether the installed SKILL.md is current, for each agent.** It reads
|
|
39
|
+
`~/.claude/skills/<appName>/SKILL.md` and `~/.agents/skills/<appName>/SKILL.md` and prints, per place, the absolute
|
|
40
|
+
path, the version installed there (`null` when there is none) and a `state`: `current`, `stale` (older than the
|
|
41
|
+
CLI), `missing` or `unversioned`. It writes nothing. A desktop app or a setup script could tell this only by reading
|
|
42
|
+
the files itself. `skillStatus(app, env)` returns the same list, and the daily skill hint now decides from it, with
|
|
43
|
+
the same outcome as before. A CLI on cli-core gets the command when cli-core picks up this release.
|
|
44
|
+
|
|
45
|
+
- **The collection noun `tables`: "the tables of the local store, with their kind, row count and size".** cli-core's
|
|
46
|
+
`store tables list` uses it to answer which tables the shared archive holds, so a desktop app or an agent can see
|
|
47
|
+
them without opening the file. `copies` keeps its narrower meaning, the tables a repair kept aside.
|
|
48
|
+
|
|
49
|
+
## 0.55.1 — 25.10.2026
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **`WC-ARCH-21` names the call that exists** ([Architecture](docs/standards/architecture.md)): a CLI registers its provider with `store.accounts.registerProvider(entry)`. The rule named a `registerProvider` import from `@wirecat/cli-core/store`, which cli-core 0.421.0 does not export.
|
|
54
|
+
|
|
55
|
+
- **`cli-dev changelog restore` no longer stops a release when the newest tag has no section of its own**
|
|
56
|
+
([releases standard](docs/standards/releases.md#wc-rel-15--fold-a-late-pending-section-into-the-unpublished-version-before-publishing)).
|
|
57
|
+
A raise that renamed the previous section instead of dating `## Unreleased` leaves a tag whose changelog has no
|
|
58
|
+
`## <version> — ` heading, and the command threw on every later `bin/release`. Nothing was released under that
|
|
59
|
+
heading, so there is nothing to restore: the command now prints why on stderr and changes nothing.
|
|
60
|
+
`restoreReleased` from `@wirecat/cli-base/release` returns `undefined` in that case instead of throwing; a
|
|
61
|
+
changelog with no section for the tagged version still throws. A CLI that skipped the restore for this case can
|
|
62
|
+
drop the skip and pin this version.
|
|
63
|
+
|
|
11
64
|
## 0.55.0 — 25.10.2026
|
|
12
65
|
|
|
13
66
|
### Added
|
package/README.md
CHANGED
|
@@ -180,7 +180,8 @@ operation and never falls back to `any`. 64-bit integers stay exact.
|
|
|
180
180
|
|
|
181
181
|
### Built for agents and for keeping installs current
|
|
182
182
|
|
|
183
|
-
- **`skill show` and `skill install`** give a coding agent the CLI's own guide, versioned with the CLI
|
|
183
|
+
- **`skill show` and `skill install`** give a coding agent the CLI's own guide, versioned with the CLI; `skill status`
|
|
184
|
+
says whether each installed copy matches it.
|
|
184
185
|
- **MCP setup** for Codex and Claude Code, with a read-only handshake probe.
|
|
185
186
|
- **Shell completion** from the command tree, answered from local data only — a shell asks on every Tab.
|
|
186
187
|
- **Update checks that never update unasked.** Which package manager installed the CLI, the command that updates it,
|
package/dist/dev/lint.d.ts
CHANGED
|
@@ -21,7 +21,17 @@ export interface LintContext {
|
|
|
21
21
|
* `log_attributes` (`logAttributesJsonSchema(allow)`) is the CLI's log allow-list, for `log-attribute-key`.
|
|
22
22
|
*/
|
|
23
23
|
schemas?: Readonly<Record<string, unknown>>;
|
|
24
|
+
/** What `cli-dev lint --cli` saw when it made the built CLI fail once in an empty home; absent with `--commands`. */
|
|
25
|
+
runLog?: RunLogProbe;
|
|
24
26
|
}
|
|
27
|
+
export interface RunLogProbe {
|
|
28
|
+
kept: boolean;
|
|
29
|
+
/** Why no failed run was found, when none was. */
|
|
30
|
+
detail?: string;
|
|
31
|
+
}
|
|
32
|
+
/** The run log's reader and its switches; what `runsCommand` and cli-core's shared program add. */
|
|
33
|
+
export declare const RUN_LOG_COMMANDS: readonly ["runs list", "runs show"];
|
|
34
|
+
export declare const RUN_LOG_FLAGS: readonly ["--record", "--no-record"];
|
|
25
35
|
/** The schemas cli-base itself defines; every CLI's lint checks them too. */
|
|
26
36
|
export declare const baseSchemas: () => Record<string, unknown>;
|
|
27
37
|
/** Splits an example the way a shell would for the quoting examples use: '…', "…" and a backslash. */
|
package/dist/dev/lint.js
CHANGED
|
@@ -25,6 +25,9 @@ export const kindOf = (option) => {
|
|
|
25
25
|
};
|
|
26
26
|
export const summaryOf = (command) => (command.summary ?? command.description.split("\n")[0] ?? "").trim();
|
|
27
27
|
const label = (command) => command.path.join(" ");
|
|
28
|
+
/** The run log's reader and its switches; what `runsCommand` and cli-core's shared program add. */
|
|
29
|
+
export const RUN_LOG_COMMANDS = ["runs list", "runs show"];
|
|
30
|
+
export const RUN_LOG_FLAGS = ["--record", "--no-record"];
|
|
28
31
|
/** The `--schemas` entry holding the CLI's log allow-list; its keys are camelCase, so `key-casing` skips it. */
|
|
29
32
|
const LOG_ATTRIBUTES = "log_attributes";
|
|
30
33
|
/** The schemas cli-base itself defines; every CLI's lint checks them too. */
|
|
@@ -395,6 +398,28 @@ const CHECKS = {
|
|
|
395
398
|
message: `"${key}" names content or a secret, which no log may carry`,
|
|
396
399
|
}));
|
|
397
400
|
},
|
|
401
|
+
"missing-run-log": (document, _vocabulary, context) => {
|
|
402
|
+
const paths = new Set(walk(document.commands).map(label));
|
|
403
|
+
const flags = new Set(document.global_options.flatMap(optionNames));
|
|
404
|
+
return [
|
|
405
|
+
...RUN_LOG_COMMANDS.filter((path) => !paths.has(path)).map((path) => ({
|
|
406
|
+
target: path,
|
|
407
|
+
message: `no \`${path}\`: register cli-core's \`runsCommand(app)\`, so a person can read what a run did`,
|
|
408
|
+
})),
|
|
409
|
+
...RUN_LOG_FLAGS.filter((flag) => !flags.has(flag)).map((flag) => ({
|
|
410
|
+
target: flag,
|
|
411
|
+
message: `no global ${flag}: run the program through cli-core's \`run\`, or add the flag at the root`,
|
|
412
|
+
})),
|
|
413
|
+
...(context.runLog && !context.runLog.kept
|
|
414
|
+
? [
|
|
415
|
+
{
|
|
416
|
+
target: "runs",
|
|
417
|
+
message: `a failing command left no failed run: ${context.runLog.detail ?? "none listed"}`,
|
|
418
|
+
},
|
|
419
|
+
]
|
|
420
|
+
: []),
|
|
421
|
+
];
|
|
422
|
+
},
|
|
398
423
|
};
|
|
399
424
|
export const lintRuleIds = () => Object.keys(CHECKS);
|
|
400
425
|
/** Every violation, one per rule and target, sorted. Generated commands keep the names of the API they mirror. */
|
package/dist/dev/main.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RunLogProbe } from "./lint.js";
|
|
1
2
|
export interface DevIo {
|
|
2
3
|
cwd: string;
|
|
3
4
|
/** The clock an override's `review_after` is judged by. */
|
|
@@ -5,4 +6,10 @@ export interface DevIo {
|
|
|
5
6
|
out: (text: string) => void;
|
|
6
7
|
err: (text: string) => void;
|
|
7
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Makes the built CLI fail once on an unknown flag and asks its own `runs list --json` whether that failure was
|
|
11
|
+
* kept. The CLI sees a new empty home and nothing else from this environment: a stray `*_STATE_DIR` or
|
|
12
|
+
* `XDG_STATE_HOME` would point it at the owner's real runs.
|
|
13
|
+
*/
|
|
14
|
+
export declare const probeRunLog: (cwd: string, bin: string, environment?: NodeJS.ProcessEnv) => RunLogProbe;
|
|
8
15
|
export declare const main: (argv: readonly string[], io: DevIo) => Promise<number>;
|
package/dist/dev/main.js
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
* reads and writes under `cwd` and reports through `out` and `err`; `main` returns the exit code.
|
|
4
4
|
*/
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
7
8
|
import { isAbsolute, join, relative } from "node:path";
|
|
8
9
|
import { pathToFileURL } from "node:url";
|
|
9
10
|
import { parseArgs } from "node:util";
|
|
10
11
|
import spawn from "cross-spawn";
|
|
11
12
|
import * as v from "valibot";
|
|
12
13
|
import { parseCommandsDocument } from "../commands/document.js";
|
|
14
|
+
import { taggedSection } from "../release/fold.js";
|
|
13
15
|
import { changelogProblems, docsProblems, JOURNAL_IDS, markdownFiles, packageVersion, structureProblems, versionScript, } from "../release/index.js";
|
|
14
16
|
import { AUDIT_BLOCKS, defineParityGroups, PARITY_GROUPS, REQUIRED_ROOTS } from "../standards/audit.js";
|
|
15
17
|
import { defineReasons } from "../standards/errors.js";
|
|
@@ -19,7 +21,7 @@ import { VOCABULARY } from "../standards/vocabulary.js";
|
|
|
19
21
|
import { auditClis, scorecardTable, snakeKeys } from "./audit.js";
|
|
20
22
|
import { readCapabilities } from "./capabilities.js";
|
|
21
23
|
import { changelogChange, landChange, landFold, newestTag, processShell, restoreChange } from "./land.js";
|
|
22
|
-
import { baselineOf, judge, lintCommands } from "./lint.js";
|
|
24
|
+
import { baselineOf, judge, lintCommands, RUN_LOG_COMMANDS, walk } from "./lint.js";
|
|
23
25
|
import { nextVersion } from "./next-version.js";
|
|
24
26
|
import { slowTests } from "./slow-tests.js";
|
|
25
27
|
import { matrixPage, matrixRows, parseArgvLog } from "./test-matrix.js";
|
|
@@ -123,6 +125,10 @@ const changelog = (args, io) => {
|
|
|
123
125
|
if (action === "restore") {
|
|
124
126
|
const shell = processShell(io.cwd);
|
|
125
127
|
const tag = newestTag(shell, relative(io.cwd, file));
|
|
128
|
+
if (!taggedSection(tag.text, tag.version)) {
|
|
129
|
+
io.err(`v${tag.version}'s changelog has no section of its own; nothing to restore`);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
126
132
|
const change = (text) => restoreChange(text, tag.text, tag.version, values.unreleased);
|
|
127
133
|
if (values.land) {
|
|
128
134
|
const landed = landChange({
|
|
@@ -233,6 +239,52 @@ const commandsJson = (io, cli, file, commandArgs = ["commands", "--json"]) => {
|
|
|
233
239
|
throw new Error(`${bin} commands --json failed: ${run.error?.message ?? String(run.stderr).trim().split("\n")[0]}`);
|
|
234
240
|
return JSON.parse(String(run.stdout));
|
|
235
241
|
};
|
|
242
|
+
const PROBE_FLAG = "--cli-dev-run-log-probe";
|
|
243
|
+
/**
|
|
244
|
+
* Makes the built CLI fail once on an unknown flag and asks its own `runs list --json` whether that failure was
|
|
245
|
+
* kept. The CLI sees a new empty home and nothing else from this environment: a stray `*_STATE_DIR` or
|
|
246
|
+
* `XDG_STATE_HOME` would point it at the owner's real runs.
|
|
247
|
+
*/
|
|
248
|
+
export const probeRunLog = (cwd, bin, environment = process.env) => {
|
|
249
|
+
const home = mkdtempSync(join(tmpdir(), "cli-dev-run-log-"));
|
|
250
|
+
const env = {
|
|
251
|
+
PATH: environment.PATH ?? "",
|
|
252
|
+
HOME: home,
|
|
253
|
+
USERPROFILE: home,
|
|
254
|
+
APPDATA: join(home, "AppData", "Roaming"),
|
|
255
|
+
LOCALAPPDATA: join(home, "AppData", "Local"),
|
|
256
|
+
CI: "true",
|
|
257
|
+
...(environment.SystemRoot ? { SystemRoot: environment.SystemRoot } : {}),
|
|
258
|
+
};
|
|
259
|
+
const script = /\.[cm]?js$/.test(bin);
|
|
260
|
+
const call = (args) => spawn.sync(script ? process.execPath : bin, [...(script ? [bin] : []), ...args], {
|
|
261
|
+
cwd,
|
|
262
|
+
env,
|
|
263
|
+
encoding: "utf8",
|
|
264
|
+
timeout: 60_000,
|
|
265
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
266
|
+
});
|
|
267
|
+
try {
|
|
268
|
+
const failed = call([PROBE_FLAG]);
|
|
269
|
+
if (failed.status === 0)
|
|
270
|
+
return { kept: false, detail: `\`${PROBE_FLAG}\` did not fail, so nothing could be kept` };
|
|
271
|
+
const listed = call(["runs", "list", "--json"]);
|
|
272
|
+
if (listed.status !== 0)
|
|
273
|
+
return { kept: false, detail: `\`runs list --json\` exited ${listed.status ?? listed.error?.message}` };
|
|
274
|
+
let items;
|
|
275
|
+
try {
|
|
276
|
+
items = JSON.parse(String(listed.stdout)).items;
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return { kept: false, detail: "`runs list --json` did not print JSON" };
|
|
280
|
+
}
|
|
281
|
+
const kept = Array.isArray(items) && items.some((item) => item?.status === "failed");
|
|
282
|
+
return kept ? { kept } : { kept, detail: `\`runs list --json\` shows no failed run after \`${PROBE_FLAG}\`` };
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
rmSync(home, { recursive: true, force: true });
|
|
286
|
+
}
|
|
287
|
+
};
|
|
236
288
|
const readBaseline = (dir, label, problems) => {
|
|
237
289
|
const baseline = {};
|
|
238
290
|
if (existsSync(dir))
|
|
@@ -311,9 +363,12 @@ const lintRun = (args, io) => {
|
|
|
311
363
|
io.err(`${values.schemas}: a JSON object of JSON Schemas by name`);
|
|
312
364
|
return 2;
|
|
313
365
|
}
|
|
366
|
+
const paths = new Set(walk(parsed.document.commands).map((command) => command.path.join(" ")));
|
|
367
|
+
const hasRuns = RUN_LOG_COMMANDS.every((path) => paths.has(path));
|
|
314
368
|
const violations = lintCommands(parsed.document, VOCABULARY, LINT_RULES, {
|
|
315
369
|
raw,
|
|
316
370
|
schemas: schemas,
|
|
371
|
+
...(values.cli && hasRuns ? { runLog: probeRunLog(io.cwd, values.cli) } : {}),
|
|
317
372
|
});
|
|
318
373
|
const severity = new Map(LINT_RULES.map((rule) => [rule.id, rule.severity]));
|
|
319
374
|
if (values["write-baseline"]) {
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -14,6 +14,25 @@ export interface CommandResult {
|
|
|
14
14
|
}
|
|
15
15
|
export type RunCommand = (file: string, args: string[]) => CommandResult;
|
|
16
16
|
export declare const setupArguments: (client: McpClient, name: string, entry: StdioEntry) => string[];
|
|
17
|
+
/** `unavailable`: the client is not installed here, or could not be started. */
|
|
18
|
+
export type StdioEntryState = "registered" | "missing" | "unavailable";
|
|
19
|
+
/** Whether the client has a server by this name; reads only, through the client's own `mcp get`. */
|
|
20
|
+
export declare const stdioEntryStatus: (client: McpClient, name: string, { run }?: {
|
|
21
|
+
run?: RunCommand;
|
|
22
|
+
}) => {
|
|
23
|
+
client: McpClient;
|
|
24
|
+
name: string;
|
|
25
|
+
state: StdioEntryState;
|
|
26
|
+
};
|
|
27
|
+
export declare const removeArguments: (client: McpClient, name: string) => string[];
|
|
28
|
+
/** Takes out what `installStdioEntry` added; a name the client does not have is left alone and reported. */
|
|
29
|
+
export declare const removeStdioEntry: (client: McpClient, name: string, { run }?: {
|
|
30
|
+
run?: RunCommand;
|
|
31
|
+
}) => {
|
|
32
|
+
client: McpClient;
|
|
33
|
+
name: string;
|
|
34
|
+
removed: boolean;
|
|
35
|
+
};
|
|
17
36
|
export declare const installStdioEntry: (client: McpClient, name: string, entry: StdioEntry, { run }?: {
|
|
18
37
|
run?: RunCommand;
|
|
19
38
|
}) => {
|
package/dist/mcp/index.js
CHANGED
|
@@ -10,26 +10,60 @@ const realRun = (file, args) => {
|
|
|
10
10
|
return { status: result.status, error: result.error, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
11
11
|
};
|
|
12
12
|
const executable = (client) => (client === "codex" ? "codex" : "claude");
|
|
13
|
+
const presentIn = (client, name, run) => {
|
|
14
|
+
const state = lookUp(client, name, run);
|
|
15
|
+
if (state === "unavailable")
|
|
16
|
+
throw new Error(`${executable(client)} is not installed or could not be started`);
|
|
17
|
+
return state === "registered";
|
|
18
|
+
};
|
|
13
19
|
export const setupArguments = (client, name, entry) => {
|
|
14
20
|
const variables = Object.entries(entry.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
|
|
15
21
|
if (client === "codex")
|
|
16
22
|
return ["mcp", "add", name, ...variables, "--", entry.command, ...entry.args];
|
|
17
23
|
return ["mcp", "add", "--scope", "user", name, ...variables, "--", entry.command, ...entry.args];
|
|
18
24
|
};
|
|
19
|
-
|
|
25
|
+
const checkName = (name) => {
|
|
20
26
|
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name))
|
|
21
27
|
throw new Error("the MCP name must be letters, digits, _ or -");
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
};
|
|
29
|
+
const lookUp = (client, name, run) => {
|
|
24
30
|
const file = executable(client);
|
|
25
31
|
const existing = run(file, ["mcp", "get", name, ...(client === "codex" ? ["--json"] : [])]);
|
|
26
32
|
if (existing.error)
|
|
27
|
-
|
|
33
|
+
return "unavailable";
|
|
28
34
|
if (existing.status === 0)
|
|
29
|
-
|
|
30
|
-
if (
|
|
35
|
+
return "registered";
|
|
36
|
+
if (!/No MCP server named/i.test(existing.stdout + existing.stderr)) {
|
|
31
37
|
throw new Error(`${file} could not check its MCP configuration`);
|
|
32
38
|
}
|
|
39
|
+
return "missing";
|
|
40
|
+
};
|
|
41
|
+
/** Whether the client has a server by this name; reads only, through the client's own `mcp get`. */
|
|
42
|
+
export const stdioEntryStatus = (client, name, { run = realRun } = {}) => {
|
|
43
|
+
checkName(name);
|
|
44
|
+
return { client, name, state: lookUp(client, name, run) };
|
|
45
|
+
};
|
|
46
|
+
export const removeArguments = (client, name) =>
|
|
47
|
+
// Claude Code without a scope removes from whichever scope holds the name, which may be a project's own entry.
|
|
48
|
+
client === "codex" ? ["mcp", "remove", name] : ["mcp", "remove", "--scope", "user", name];
|
|
49
|
+
/** Takes out what `installStdioEntry` added; a name the client does not have is left alone and reported. */
|
|
50
|
+
export const removeStdioEntry = (client, name, { run = realRun } = {}) => {
|
|
51
|
+
checkName(name);
|
|
52
|
+
if (!presentIn(client, name, run))
|
|
53
|
+
return { client, name, removed: false };
|
|
54
|
+
const file = executable(client);
|
|
55
|
+
const removed = run(file, removeArguments(client, name));
|
|
56
|
+
if (removed.status !== 0 || removed.error)
|
|
57
|
+
throw new Error(`${file} could not remove ${name}`);
|
|
58
|
+
return { client, name, removed: true };
|
|
59
|
+
};
|
|
60
|
+
export const installStdioEntry = (client, name, entry, { run = realRun } = {}) => {
|
|
61
|
+
checkName(name);
|
|
62
|
+
if (!entry.command || entry.args.some((arg) => arg.includes("\0")))
|
|
63
|
+
throw new Error("invalid MCP command");
|
|
64
|
+
const file = executable(client);
|
|
65
|
+
if (presentIn(client, name, run))
|
|
66
|
+
throw new Error(`${name} is already configured in ${file}; remove it there first`);
|
|
33
67
|
const args = setupArguments(client, name, entry);
|
|
34
68
|
const added = run(file, args);
|
|
35
69
|
if (added.status !== 0 || added.error)
|
package/dist/release/fold.d.ts
CHANGED
|
@@ -13,12 +13,15 @@ export declare const foldUnreleased: (text: string, version: string, unreleased?
|
|
|
13
13
|
* when no heading repeats there. The caller makes sure the version is not on npm.
|
|
14
14
|
*/
|
|
15
15
|
export declare const mergeRepeatedHeadings: (text: string, version: string) => string | undefined;
|
|
16
|
+
/** The `## <version> — ` section of a changelog, or `undefined` when it has none. */
|
|
17
|
+
export declare const taggedSection: (text: string, version: string) => string | undefined;
|
|
16
18
|
/**
|
|
17
19
|
* Puts the section of `version`, the newest tagged one, back to its text in that tag. A pull request
|
|
18
20
|
* checked before the version was dated or tagged and merged after it adds its entry there with no
|
|
19
21
|
* guard to see it, though that code ships only in the next version: each such entry moves to the unreleased
|
|
20
22
|
* section, after its entries under the same `###` heading. `undefined` when the section matches the tag the
|
|
21
|
-
* way the changelog guard compares them
|
|
23
|
+
* way the changelog guard compares them, or when the tag's changelog has no section for `version`: nothing was
|
|
24
|
+
* released under that heading, so there is nothing to restore.
|
|
22
25
|
*/
|
|
23
26
|
export declare const restoreReleased: (text: string, tagText: string, version: string, unreleased?: string) => {
|
|
24
27
|
text: string;
|
package/dist/release/fold.js
CHANGED
|
@@ -85,18 +85,21 @@ const entries = (section) => [...section.headings].flatMap(([heading, lines]) =>
|
|
|
85
85
|
}
|
|
86
86
|
return found;
|
|
87
87
|
});
|
|
88
|
+
/** The `## <version> — ` section of a changelog, or `undefined` when it has none. */
|
|
89
|
+
export const taggedSection = (text, version) => text.split(/\n(?=## )/).find((block) => block.startsWith(`## ${version} — `));
|
|
88
90
|
/**
|
|
89
91
|
* Puts the section of `version`, the newest tagged one, back to its text in that tag. A pull request
|
|
90
92
|
* checked before the version was dated or tagged and merged after it adds its entry there with no
|
|
91
93
|
* guard to see it, though that code ships only in the next version: each such entry moves to the unreleased
|
|
92
94
|
* section, after its entries under the same `###` heading. `undefined` when the section matches the tag the
|
|
93
|
-
* way the changelog guard compares them
|
|
95
|
+
* way the changelog guard compares them, or when the tag's changelog has no section for `version`: nothing was
|
|
96
|
+
* released under that heading, so there is nothing to restore.
|
|
94
97
|
*/
|
|
95
98
|
export const restoreReleased = (text, tagText, version, unreleased = "Unreleased") => {
|
|
96
99
|
const dated = (block) => block.startsWith(`## ${version} — `);
|
|
97
|
-
const tagged = tagText
|
|
100
|
+
const tagged = taggedSection(tagText, version);
|
|
98
101
|
if (!tagged)
|
|
99
|
-
|
|
102
|
+
return undefined;
|
|
100
103
|
const blocks = text.split(/\n(?=## )/);
|
|
101
104
|
const index = blocks.findIndex(dated);
|
|
102
105
|
if (index < 0)
|
package/dist/skill/index.d.ts
CHANGED
|
@@ -25,6 +25,16 @@ export declare const installSkill: (app: SkillApp, skill: URL, { targets, env }?
|
|
|
25
25
|
targets?: readonly SkillTarget[];
|
|
26
26
|
env?: Env;
|
|
27
27
|
}) => string[];
|
|
28
|
+
export type SkillState = "current" | "stale" | "missing" | "unversioned";
|
|
29
|
+
export interface SkillTargetStatus {
|
|
30
|
+
target: SkillTarget;
|
|
31
|
+
path: string;
|
|
32
|
+
/** The `metadata.version` of the installed copy; `null` when there is none or it has no version. */
|
|
33
|
+
installed: string | null;
|
|
34
|
+
state: SkillState;
|
|
35
|
+
}
|
|
36
|
+
/** Each target's installed copy against this CLI's version; reads only, and an unreadable file is missing. */
|
|
37
|
+
export declare const skillStatus: (app: SkillApp, env?: Env) => SkillTargetStatus[];
|
|
28
38
|
/** What the command writes to and where home is, for this invocation — the host's own output resolution. */
|
|
29
39
|
export type SkillEnvironment = (command: Command) => {
|
|
30
40
|
renderer: Renderer;
|
package/dist/skill/index.js
CHANGED
|
@@ -88,6 +88,21 @@ export const installSkill = (app, skill, { targets = SKILL_TARGETS, env = proces
|
|
|
88
88
|
return path;
|
|
89
89
|
});
|
|
90
90
|
};
|
|
91
|
+
/** Each target's installed copy against this CLI's version; reads only, and an unreadable file is missing. */
|
|
92
|
+
export const skillStatus = (app, env = process.env) => SKILL_TARGETS.map((target) => {
|
|
93
|
+
const path = skillPath(target, app.appName, env);
|
|
94
|
+
let content;
|
|
95
|
+
try {
|
|
96
|
+
content = readFileSync(path, "utf8");
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return { target, path, installed: null, state: "missing" };
|
|
100
|
+
}
|
|
101
|
+
const installed = skillVersion(content);
|
|
102
|
+
if (installed === undefined)
|
|
103
|
+
return { target, path, installed: null, state: "unversioned" };
|
|
104
|
+
return { target, path, installed, state: isNewer(app.version, installed) ? "stale" : "current" };
|
|
105
|
+
});
|
|
91
106
|
/**
|
|
92
107
|
* `more`: skills a library ships for every CLI built on it, by name — `skill show <name>` prints one with
|
|
93
108
|
* `{{command}}` turned into this CLI's command. They are printed, not installed: two CLIs would install
|
|
@@ -102,13 +117,32 @@ export const skillCommand = (app, skill, environment, more = {}) => {
|
|
|
102
117
|
const named = Object.keys(more);
|
|
103
118
|
const show = annotate(command.command("show"), { effects: { remote: "none", local: "read" } })
|
|
104
119
|
.summary("print the tool's instructions for AI agents")
|
|
105
|
-
.description(`print SKILL.md — \`${app.command} skill install\` puts it where Claude Code, Codex and Gemini CLI look for it`);
|
|
120
|
+
.description(`print SKILL.md — \`${app.command} skill install\` puts it where Claude Code, Codex and Gemini CLI look for it; --for prints the copy already there`);
|
|
106
121
|
if (named.length > 0)
|
|
107
122
|
show.argument("[name]", `one of the skills shipped for a task: ${named.join(", ")}`);
|
|
123
|
+
show.addOption(new Option("--for <agents>", "print the copy installed for these agents instead of this version's").choices(SKILL_TARGETS));
|
|
108
124
|
show.action(function (given) {
|
|
109
125
|
// Without a declared argument Commander passes the options here, not a name.
|
|
110
126
|
const name = typeof given === "string" ? given : undefined;
|
|
111
|
-
const { renderer, streams } = environment(this);
|
|
127
|
+
const { renderer, streams, env } = environment(this);
|
|
128
|
+
const { for: target } = this.opts();
|
|
129
|
+
if (target !== undefined) {
|
|
130
|
+
if (name !== undefined)
|
|
131
|
+
throw new CliError("validation_error", `only ${app.appName} is installed; leave out "${name}" with --for`);
|
|
132
|
+
const path = skillPath(target, app.appName, env ?? process.env);
|
|
133
|
+
let content;
|
|
134
|
+
try {
|
|
135
|
+
content = readFileSync(path, "utf8").trimEnd();
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
throw new CliError("not_found", `no skill is installed for ${target} at ${path} — \`${app.command} skill install --for ${target}\` writes it`);
|
|
139
|
+
}
|
|
140
|
+
if (machine(this))
|
|
141
|
+
renderer.result({ name: app.appName, target, path, installed: skillVersion(content) ?? null, content });
|
|
142
|
+
else
|
|
143
|
+
streams.data(content);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
112
146
|
const source = name === undefined ? skill : more[name];
|
|
113
147
|
if (!source) {
|
|
114
148
|
throw new CliError("validation_error", `no skill "${name}" — there are: ${named.join(", ")}`);
|
|
@@ -134,20 +168,24 @@ export const skillCommand = (app, skill, environment, more = {}) => {
|
|
|
134
168
|
renderer.result({ name: app.appName, version: app.version, written });
|
|
135
169
|
renderer.success(`${app.appName} ${app.version} skill installed: ${written.join(", ")}`);
|
|
136
170
|
});
|
|
171
|
+
annotate(command.command("status"), {
|
|
172
|
+
effects: { remote: "none", local: "read" },
|
|
173
|
+
examples: [`${app.command} skill status`, `${app.command} skill status --json`],
|
|
174
|
+
})
|
|
175
|
+
.summary("whether the installed instructions for AI agents match this version")
|
|
176
|
+
.description(`for each place \`${app.command} skill install\` writes to: the path, the version installed there, and whether it is current, stale, missing or unversioned`)
|
|
177
|
+
.action(function () {
|
|
178
|
+
const { renderer, env } = environment(this);
|
|
179
|
+
const targets = skillStatus(app, env ?? process.env);
|
|
180
|
+
renderer.result({ name: app.appName, version: app.version, targets });
|
|
181
|
+
});
|
|
137
182
|
return command;
|
|
138
183
|
};
|
|
139
184
|
const isAgent = (env) => Boolean(env.AI_AGENT || env.CLAUDECODE);
|
|
140
185
|
const needsInstall = (app, env) => {
|
|
141
|
-
const
|
|
142
|
-
try {
|
|
143
|
-
return [skillVersion(readFileSync(skillPath(target, app.appName, env), "utf8"))];
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
return [];
|
|
147
|
-
}
|
|
148
|
-
});
|
|
186
|
+
const states = skillStatus(app, env).map(({ state }) => state);
|
|
149
187
|
// A location never installed to is a choice (`--for claude`); only no copy at all, or an old one, is worth a line.
|
|
150
|
-
return
|
|
188
|
+
return (states.every((state) => state === "missing") || states.some((state) => state === "stale" || state === "unversioned"));
|
|
151
189
|
};
|
|
152
190
|
/**
|
|
153
191
|
* One line for an agent whose copy of SKILL.md is missing or older than this CLI, at most once a
|
package/dist/standards/lint.js
CHANGED
|
@@ -110,6 +110,12 @@ export const LINT_RULES = defineLintRules([
|
|
|
110
110
|
scope: "key",
|
|
111
111
|
summary: "no log attribute key on the allow-list names message text, a token or a phone number",
|
|
112
112
|
},
|
|
113
|
+
{
|
|
114
|
+
id: "missing-run-log",
|
|
115
|
+
severity: "high",
|
|
116
|
+
scope: "group",
|
|
117
|
+
summary: "the CLI keeps a failed run and reads it back: `runs list`, `runs show`, global `--record` and `--no-record`",
|
|
118
|
+
},
|
|
113
119
|
]);
|
|
114
120
|
/** `lint/baseline/<rule-id>.json`: today's violations of one rule, by target. It may only shrink. */
|
|
115
121
|
export const baselineSchema = v.array(text);
|
|
@@ -216,6 +216,7 @@ const collections = [
|
|
|
216
216
|
["tags", "labels the owner puts on things"],
|
|
217
217
|
["searches", "the owner's saved searches"],
|
|
218
218
|
["copies", "tables kept aside by a repair"],
|
|
219
|
+
["tables", "the tables of the local store, with their kind, row count and size"],
|
|
219
220
|
["attachments", "files of stored messages"],
|
|
220
221
|
["tasks", "what waits on the owner"],
|
|
221
222
|
["members", "people in a chat"],
|
package/docs/standards/README.md
CHANGED
|
@@ -33,7 +33,7 @@ the four steps that need a person. A new CLI is ready for its first release when
|
|
|
33
33
|
| [Accounts](accounts.md) | `ACCT` | 11 | The explicit profile, reads across accounts, scope flags, linked identities |
|
|
34
34
|
| [Permissions](permissions.md) | `PERM` | 21 | Levels, keys, defaults, hard limits, the write guard and its journal |
|
|
35
35
|
| [Architecture](architecture.md) | `ARCH` | 17 | Layers, dependency direction, vendor types, what goes in which package |
|
|
36
|
-
| [Lifecycle](lifecycle.md) | `LIFE` |
|
|
36
|
+
| [Lifecycle](lifecycle.md) | `LIFE` | 14 | Opening late, who closes, one-shot exits, long-running commands, the `serve` relay, resumable syncs, the run log |
|
|
37
37
|
| [Store](store.md) | `STORE` | 14 | Store areas, account scope, migrations, test isolation |
|
|
38
38
|
| [Data model](data-model.md) | `DATA` | 7 | Core entities, entities and source records, `external_id`, field names, links and duplicates |
|
|
39
39
|
| [Testing](testing.md) | `TEST` | 17 | Sandbox, no network, no real waits, leak tests, coverage, contracts, test matrix, parity |
|
|
@@ -206,8 +206,8 @@ at start-up and the `serve` relay are in [Lifecycle](lifecycle.md); the store's
|
|
|
206
206
|
|
|
207
207
|
### WC-ARCH-21 · A CLI registers its provider in the store; an unknown provider's rows are skipped, never guessed
|
|
208
208
|
|
|
209
|
-
Every run, a CLI writes its provider's entry — `{ name, kind, aliases, command }` — with
|
|
210
|
-
`@wirecat/cli-core/store
|
|
209
|
+
Every run, a CLI writes its provider's entry — `{ name, kind, aliases, command }` — with
|
|
210
|
+
`store.accounts.registerProvider(entry)` on the store from `openStore()` (`@wirecat/cli-core/store`). cli-core reads its built-in providers plus the stored entries when the store opens, so
|
|
211
211
|
every other CLI knows the new provider's kind without a cli-core release. A row whose provider no entry names is left
|
|
212
212
|
out of search and listed under `skipped`, with the hint to run that CLI once.
|
|
213
213
|
|
|
@@ -18,7 +18,7 @@ The list is what the template's `src/` imports today, type imports included.
|
|
|
18
18
|
|---|---|
|
|
19
19
|
| `@wirecat/cli-base` | `catchCrashes`, `CliError`, `configFilePath`, `mcpErrorResult`, `resolvePaths` |
|
|
20
20
|
| `@wirecat/cli-base/commands` | `annotate`, `CommandInfo`, `describeProgram` |
|
|
21
|
-
| `@wirecat/cli-core/cli` | `AppIdentity`, `changesAuthorityAt`, `commandsCommand`, `configCommand`, `Configuration`, `createProgram`, `environmentOf`, `GlobalFlags`, `itemOf`, `LOCAL_READ`, `outputFor`, `ProgramDefinition`, `ProgramOptions`, `readOnlyLockOf`, `REMOTE_WRITE`, `renderAnswer`, `rootOf`, `run`, `RunOptions`, `settingsFor`, `skillCommand`, `upgradeCommand` |
|
|
21
|
+
| `@wirecat/cli-core/cli` | `AppIdentity`, `changesAuthorityAt`, `commandsCommand`, `configCommand`, `Configuration`, `createProgram`, `environmentOf`, `GlobalFlags`, `itemOf`, `LOCAL_READ`, `outputFor`, `ProgramDefinition`, `ProgramOptions`, `readOnlyLockOf`, `REMOTE_WRITE`, `renderAnswer`, `rootOf`, `run`, `RunOptions`, `runsCommand`, `settingsFor`, `skillCommand`, `upgradeCommand` |
|
|
22
22
|
| `@wirecat/cli-core/mcp` | `McpCommand`, `registerCommands`, `runCommandForAgent`, `toolsFromCommands` |
|
|
23
23
|
| `@wirecat/cli-core/sends` | `Level` |
|
|
24
24
|
| `@wirecat/cli-core/update` | `appUpdate` |
|
|
@@ -14,6 +14,16 @@ A "one-shot" command does one job and ends: most commands. A "long-running" comm
|
|
|
14
14
|
`lifetime: "persistent"` (it runs until it is stopped, such as `serve` or `watch`) or `lifetime: "interactive"` (it
|
|
15
15
|
waits for a person, such as a login).
|
|
16
16
|
|
|
17
|
+
A "run" is one command's record: what it was, how it ended, and its events, never content. The rules on what a
|
|
18
|
+
run's events may hold are [WC-SECR-1](security.md#wc-secr-1--private-data-and-secrets-never-reach-a-log-a-fixture-or-a-committed-document)
|
|
19
|
+
and [WC-SECR-2](security.md#wc-secr-2--a-log-keeps-only-known-attribute-keys-and-hides-content-keys).
|
|
20
|
+
|
|
21
|
+
### Who follows these rules today
|
|
22
|
+
|
|
23
|
+
- **WC-LIFE-15:** tg, max, wa and the template. drive, memo and zm do not keep runs yet; each is adding them on a
|
|
24
|
+
`feat/runs` branch (2026-10-25). zm's `resolveSettings` also returns `keepFailedRuns: false`, which turns off the
|
|
25
|
+
failure log cli-core's `run` would keep.
|
|
26
|
+
|
|
17
27
|
## One-shot commands
|
|
18
28
|
|
|
19
29
|
### WC-LIFE-1 · The code that opens a resource closes it, so a one-shot command exits when it has answered
|
|
@@ -183,6 +193,29 @@ provider's limit as often as it ends. These three rules make the next run carry
|
|
|
183
193
|
failed is a different question, answered by `recordRun` and `failingNotice` in cli-core's schedules.
|
|
184
194
|
- **Applies to:** every CLI with a sync. **Since:** cli-base 0.44.0. **Rulings:** none.
|
|
185
195
|
|
|
196
|
+
## Runs
|
|
197
|
+
|
|
198
|
+
### WC-LIFE-15 · Keep a run log through cli-core, and read it back with `runs`
|
|
199
|
+
|
|
200
|
+
A failed command is always kept as a run, unless the person turned recording off with `--no-record` or
|
|
201
|
+
`record: false` in the config. `--record` keeps every run of a command that does its work inside cli-core's run
|
|
202
|
+
(`context.run`, or `startRecording` in a program of its own). A run is a folder `<state>/runs/<day>/<run_id>/` with
|
|
203
|
+
`run.json` and `events.jsonl`, one log record per line. `runs list` and `runs show` read it.
|
|
204
|
+
|
|
205
|
+
- A CLI that runs its program through cli-core's `run` gets the failure log and `--record`/`--no-record` from it,
|
|
206
|
+
and adds `runsCommand(app)` to its commands. Its configuration is `settingsFor(app)`, or a `resolveSettings` that
|
|
207
|
+
returns the owner's `record` and `keep_runs_for_days` as `keepFailedRuns` and `keepRunsForDays`, never fixed values.
|
|
208
|
+
- A CLI with a program of its own opens each command's run with `startRecording` from `@wirecat/cli-core/cli`,
|
|
209
|
+
ends it with `succeed` or `fail`, adds `--record` and `--no-record` at the root and registers `runsCommand(app)`.
|
|
210
|
+
|
|
211
|
+
- **Why:** when a command fails, a person or an agent needs to see what it did, in the same place and shape in every
|
|
212
|
+
CLI. Without the rule, three CLIs shipped with no run log, and one switched cli-core's off.
|
|
213
|
+
- **Enforced by:** `missing-run-log`.
|
|
214
|
+
- **Example:** `tg --bogus` fails, `tg runs list` shows that run as `failed`, and `tg runs show <run_id>` shows its
|
|
215
|
+
events.
|
|
216
|
+
- **Exceptions:** none.
|
|
217
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.56.0. **Rulings:** none.
|
|
218
|
+
|
|
186
219
|
## Sources
|
|
187
220
|
|
|
188
221
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
@@ -50,7 +50,8 @@ Give the agent `docs/dev/DESIGN.md` next. Every example passes a profile explici
|
|
|
50
50
|
The reference CLI is the one to compare with when a step is unclear.
|
|
51
51
|
|
|
52
52
|
The copy already holds every file this handbook asks for: the required root commands (`commands`, `doctor`,
|
|
53
|
-
`skill show`, `skill install`, `mcp`, `upgrade`) and `config` from cli-core, the
|
|
53
|
+
`skill show`, `skill install`, `mcp`, `upgrade`) and `config` from cli-core, the run log and its `runs` commands
|
|
54
|
+
([WC-LIFE-15](lifecycle.md#wc-life-15--keep-a-run-log-through-cli-core-and-read-it-back-with-runs)), the MCP server with a tool for every
|
|
54
55
|
command, writes included, the community presets in `config/wirecat/`, the TypeScript, Biome and vitest setup,
|
|
55
56
|
`contracts/`, `lint/`, the test matrix, the user pages, `CHANGELOG.md`, `SECURITY.md`, `bin/release`, and the
|
|
56
57
|
`pr.yml`, `ci.yml`, `main.yml`, `standards.yml`, `changelog-guard.yml`, `monthly.yml` and `release.yml` workflows.
|
|
@@ -83,7 +84,8 @@ All of these pass on `main`, before the first release:
|
|
|
83
84
|
|
|
84
85
|
- `pnpm standards:check`
|
|
85
86
|
- `pnpm typecheck`, `pnpm lint` and `pnpm test` with coverage
|
|
86
|
-
- `cli-dev lint` against the built CLI
|
|
87
|
+
- `cli-dev lint` against the built CLI. It also makes the CLI fail once in an empty home and checks that
|
|
88
|
+
`runs list` shows the failed run
|
|
87
89
|
- `cli-dev audit --for <cli> --lint <cli>=lint` with no new finding, run with the command trees of the CLIs in
|
|
88
90
|
the same parity groups. `--lint` judges the CLI's lint rules by its own `lint/` folder. What it shares with the
|
|
89
91
|
other CLIs is judged by cli-base's `audit/baseline`; the pull request that adds the CLI to a parity group adds its
|
|
@@ -173,7 +173,9 @@ on the [CI](ci.md) page.
|
|
|
173
173
|
section, through a pull request. Then, while npm does not have the version, `bin/release` runs
|
|
174
174
|
`cli-dev changelog fold --land --version <x.y.z>`. It folds the pending section into the dated one, and merges any
|
|
175
175
|
repeated `###` heading, through a pull request.
|
|
176
|
-
- **Exceptions:** only the newest tag is compared; an older section that differs from its tag stays as it is.
|
|
176
|
+
- **Exceptions:** only the newest tag is compared; an older section that differs from its tag stays as it is. A
|
|
177
|
+
newest tag whose changelog has no section of its own has nothing to restore: the command says so and changes
|
|
178
|
+
nothing.
|
|
177
179
|
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
178
180
|
|
|
179
181
|
### WC-REL-16 · The package ships only the files it names
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-base",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"description": "The parts every command line tool needs: output modes, terminal rendering, error model, exit codes, credentials, clocks",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|