@wirecat/cli-core 0.371.0 → 0.372.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 CHANGED
@@ -7,6 +7,31 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.372.0 — 20.10.2026
11
+
12
+ ### Added
13
+
14
+ - **Setup wizard helpers from `./cli`**: `AGENTS`, `type Agent`, `screenFor(context, quiet, steps)` and
15
+ `agentFor(context, given, pad, prompt?)`, for the `setup` command of a messenger CLI. `screenFor` prints the
16
+ numbered step headings (`[2/5] …`, details indented by 6) to stderr for a person, hides them under `--quiet`,
17
+ and keeps plain notes in a machine mode. `agentFor` asks which agent to install only at a terminal in the pretty
18
+ format and answers `none` otherwise; an unknown answer is a `validation_error`. Both take the minimum shape they
19
+ read (`format`, `streams.diagnostic`, `renderer.note`, `stdin`), so tg-cli's and whatsapp-cli's contexts fit as they
20
+ are. Pass the wizard's step count as `steps`, and the prompt's `input`, `output` and `signal` as `prompt`
21
+ (`secretInputOf(command)` plus the signal, or `{ input: context.stdin }`). No profile or agent is chosen for the
22
+ caller. This replaces the copies in both CLIs' `src/commands/setup.ts`.
23
+ - **`restartServers(app, script, env, spawn?)` from `./cli`** restarts the `serve` of every profile that is running,
24
+ with `<script> <profile> server restart`, and answers `{ restarted, left }`: the shape `upgradeCommand`'s
25
+ `afterUpdate` port returns, so `afterUpdate: () => restartServers(app, script, env, environment.spawn)` is the whole
26
+ host side. `script` is the new install's own entry point; `spawn` replaces the child process in a test. A serve started
27
+ by hand refuses the restart and lands in `left`. This replaces the copies in both CLIs' `src/commands/update.ts` and
28
+ `src/update.ts`.
29
+ - **`ARGV_LOG` and `argvLogSetup` from `./testing`**: the path `coverage/argv.jsonl` under the working directory, and
30
+ a vitest `globalSetup` function that clears it, for the test matrix that checks which commands and options a run
31
+ drove. A CLI's own `src/testing/argv-log.ts` can go. Vitest takes a package specifier as `globalSetup` only with a
32
+ named `setup` or a default export, so keep a one-line file: `export { argvLogSetup as default } from
33
+ "@wirecat/cli-core/testing"`.
34
+
10
35
  ## 0.371.0 — 20.10.2026
11
36
 
12
37
  ### Added
@@ -127,7 +127,8 @@ export { createProgram, type ProgramDefinition, type ProgramOptions, type RunOpt
127
127
  export { fieldsOf, projectFields } from "./result-fields.js";
128
128
  export { runsCommand } from "./runs/command.js";
129
129
  export { type Config, type Configuration, count, first, flag, fromFile, type GlobalFlags, NONE_NAMED, type ProfileKind, plain, type ResolveOptions, type Settings, type SettingsExtension, type SettingsExtensionContext, type Source, settingsFor, } from "./settings.js";
130
+ export { AGENTS, type Agent, agentFor, screenFor } from "./setup-screen.js";
130
131
  export { skillCommand } from "./skill-command.js";
131
132
  export { runnable, withSafety } from "./steps.js";
132
133
  export { changeStoreSetting, isStoreSetting, STORE_SETTINGS, storeSettings } from "./store-settings.js";
133
- export { type UpgradeContext, upgradeCommand } from "./upgrade-command.js";
134
+ export { restartServers, type UpgradeContext, upgradeCommand } from "./upgrade-command.js";
package/dist/cli/index.js CHANGED
@@ -122,7 +122,8 @@ export { createProgram, run } from "./program.js";
122
122
  export { fieldsOf, projectFields } from "./result-fields.js";
123
123
  export { runsCommand } from "./runs/command.js";
124
124
  export { count, first, flag, fromFile, NONE_NAMED, plain, settingsFor, } from "./settings.js";
125
+ export { AGENTS, agentFor, screenFor } from "./setup-screen.js";
125
126
  export { skillCommand } from "./skill-command.js";
126
127
  export { runnable, withSafety } from "./steps.js";
127
128
  export { changeStoreSetting, isStoreSetting, STORE_SETTINGS, storeSettings } from "./store-settings.js";
128
- export { upgradeCommand } from "./upgrade-command.js";
129
+ export { restartServers, upgradeCommand } from "./upgrade-command.js";
@@ -0,0 +1,25 @@
1
+ import { type Renderer, type RenderFormat, type Streams } from "@wirecat/cli-base";
2
+ import { type SecretInput } from "../terminal/prompt.js";
3
+ export declare const AGENTS: readonly ["none", "codex", "cursor", "claude", "gemini", "all"];
4
+ export type Agent = (typeof AGENTS)[number];
5
+ /**
6
+ * A person sees each step as a heading with its details indented under it. `--quiet` hides both, as it
7
+ * hides notes; a machine mode keeps the plain notes it always had, since stderr there is a log.
8
+ */
9
+ export declare const screenFor: (context: {
10
+ format: RenderFormat;
11
+ streams: Pick<Streams, "diagnostic">;
12
+ renderer: Pick<Renderer, "note">;
13
+ }, quiet: boolean, steps: number) => {
14
+ step: (index: number, title: string) => void;
15
+ detail: (text: string) => void;
16
+ title: (text: string) => void;
17
+ indent: number;
18
+ };
19
+ /** Asks only a person at a terminal; every other run gets "none", so no agent is ever installed unasked. */
20
+ export declare const agentFor: (context: {
21
+ format: RenderFormat;
22
+ stdin: NodeJS.ReadableStream & {
23
+ isTTY?: boolean;
24
+ };
25
+ }, given: Agent | undefined, pad: number, prompt?: Pick<SecretInput, "input" | "output" | "signal">) => Promise<Agent>;
@@ -0,0 +1,39 @@
1
+ import { CliError, indent } from "@wirecat/cli-base";
2
+ import { readSecret } from "../terminal/prompt.js";
3
+ import { inputPolicy } from "./input-policy.js";
4
+ export const AGENTS = ["none", "codex", "cursor", "claude", "gemini", "all"];
5
+ const DETAIL = 6;
6
+ /**
7
+ * A person sees each step as a heading with its details indented under it. `--quiet` hides both, as it
8
+ * hides notes; a machine mode keeps the plain notes it always had, since stderr there is a log.
9
+ */
10
+ export const screenFor = (context, quiet, steps) => {
11
+ const person = context.format === "pretty";
12
+ const say = (text) => {
13
+ if (!quiet)
14
+ context.streams.diagnostic(text);
15
+ };
16
+ return {
17
+ step: (index, title) => person ? say(`\n[${index}/${steps}] ${title}`) : context.renderer.note(`${index}/${steps} — ${title}`),
18
+ detail: (text) => (person ? say(indent(text, DETAIL)) : context.renderer.note(text)),
19
+ title: (text) => (person ? say(text) : context.renderer.note(text)),
20
+ indent: person ? DETAIL : 0,
21
+ };
22
+ };
23
+ /** Asks only a person at a terminal; every other run gets "none", so no agent is ever installed unasked. */
24
+ export const agentFor = async (context, given, pad, prompt = {}) => {
25
+ if (given)
26
+ return given;
27
+ if (context.format !== "pretty" || !context.stdin.isTTY || inputPolicy(context.stdin).noInput)
28
+ return "none";
29
+ const answer = (await readSecret(`${" ".repeat(pad)}Agent [codex/cursor/claude/gemini/all/none] (none): `, {
30
+ ...prompt,
31
+ echo: true,
32
+ }))
33
+ .trim()
34
+ .toLowerCase() || "none";
35
+ const agent = AGENTS.find((known) => known === answer);
36
+ if (agent === undefined)
37
+ throw new CliError("validation_error", `unknown agent — choose ${AGENTS.join(", ")} with --agent`);
38
+ return agent;
39
+ };
@@ -7,4 +7,13 @@ export interface UpgradeContext extends Omit<PackageUpgradePorts, "onInstall"> {
7
7
  format: RenderFormat;
8
8
  streams: Streams;
9
9
  }
10
+ /**
11
+ * A running serve keeps the code it started with; the update replaced the files under it. Each is restarted by
12
+ * the new `script` — `server restart` refuses a serve started by hand, which is then named in `left`.
13
+ * `spawn` replaces the child process, as in `UpdateEnvironment`.
14
+ */
15
+ export declare const restartServers: (app: AppIdentity, script: string, env: NodeJS.ProcessEnv, spawn?: (argv: string[]) => number) => {
16
+ restarted: string[];
17
+ left: string[];
18
+ };
10
19
  export declare const upgradeCommand: (app: AppIdentity, packageName: string, contextFor: (command: Command) => UpgradeContext) => Command;
@@ -1,9 +1,35 @@
1
+ import { spawnSync } from "node:child_process";
1
2
  import { CliError } from "@wirecat/cli-base";
2
3
  import { annotate } from "@wirecat/cli-base/commands";
3
4
  import { Command } from "commander";
5
+ import { servingProfiles } from "../background/lock.js";
4
6
  import { upgradePackage } from "../services/package-upgrade.js";
5
7
  import { grantsAuthority, LOCAL_WRITE } from "./effects.js";
6
8
  import { itemOf, renderAnswer, snakeKeys } from "./envelope.js";
9
+ import { usableProfileName } from "./profile.js";
10
+ /**
11
+ * A running serve keeps the code it started with; the update replaced the files under it. Each is restarted by
12
+ * the new `script` — `server restart` refuses a serve started by hand, which is then named in `left`.
13
+ * `spawn` replaces the child process, as in `UpdateEnvironment`.
14
+ */
15
+ export const restartServers = (app, script, env, spawn) => {
16
+ const restarted = [];
17
+ const left = [];
18
+ for (const profile of servingProfiles(app, env)) {
19
+ try {
20
+ usableProfileName(profile);
21
+ }
22
+ catch {
23
+ continue;
24
+ }
25
+ const argv = [script, profile, "server", "restart"];
26
+ const code = spawn
27
+ ? spawn([process.execPath, ...argv])
28
+ : (spawnSync(process.execPath, argv, { env, shell: false, stdio: ["ignore", 2, 2] }).status ?? 1);
29
+ (code === 0 ? restarted : left).push(profile);
30
+ }
31
+ return { restarted, left };
32
+ };
7
33
  export const upgradeCommand = (app, packageName, contextFor) => grantsAuthority(annotate(new Command("upgrade"), {
8
34
  effects: LOCAL_WRITE,
9
35
  mcp: false,
@@ -0,0 +1,4 @@
1
+ /** Where each test run records which commands and options it drove (`pnpm test:matrix`). */
2
+ export declare const ARGV_LOG: string;
3
+ /** A vitest `globalSetup`: one run, one log. */
4
+ export declare const argvLogSetup: () => void;
@@ -0,0 +1,9 @@
1
+ import { mkdirSync, rmSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /** Where each test run records which commands and options it drove (`pnpm test:matrix`). */
4
+ export const ARGV_LOG = join(process.cwd(), "coverage", "argv.jsonl");
5
+ /** A vitest `globalSetup`: one run, one log. */
6
+ export const argvLogSetup = () => {
7
+ mkdirSync(join(process.cwd(), "coverage"), { recursive: true });
8
+ rmSync(ARGV_LOG, { force: true });
9
+ };
@@ -1,3 +1,4 @@
1
+ export { ARGV_LOG, argvLogSetup } from "./argv-log.js";
1
2
  export { type ContractCase, type ContractOptions, contractCases, OPTIONAL_METHODS } from "./contract.js";
2
3
  export { type FakeAdapter, fakeAdapter } from "./fake.js";
3
4
  export { type McpStdio, type McpStdioStreams, type McpToolResult, mcpOverStdio } from "./mcp-stdio.js";
package/dist/kit/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { ARGV_LOG, argvLogSetup } from "./argv-log.js";
1
2
  export { contractCases, OPTIONAL_METHODS } from "./contract.js";
2
3
  export { fakeAdapter } from "./fake.js";
3
4
  export { mcpOverStdio } from "./mcp-stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.371.0",
3
+ "version": "0.372.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",