@wirecat/cli-base 0.73.0 → 0.75.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
@@ -8,6 +8,35 @@ 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.75.0 — 02.11.2026
12
+
13
+ ### Fixed
14
+
15
+ - **A list whose provider filters it no longer advertises `OR` in its help.** `fieldsHelp`'s `Syntax:` line on a
16
+ `backend: "native"` list now says only what such a list takes: `field:value` terms joined by AND, and the phrase,
17
+ `word*` and range forms only when one of its fields takes them. `OR`, `NOT` and `( )` were listed before, and the
18
+ list refused them. A negatable field keeps its `Leave out:` line. The MCP `query` description already said this
19
+ (0.73.0). A `sql` list's line is unchanged.
20
+
21
+ - **`<cli> upgrade` no longer fails with `MODULE_NOT_FOUND` after the install.** pnpm deletes the folder the old
22
+ version runs from, and a chunk the command imported after the install was gone. The bundle's loader now loads every
23
+ chunk before an upgrade starts, as it does for a server.
24
+
25
+ ### Added
26
+
27
+ - **`--full-history`**, a mode switch: keep a chat's whole history from now on, past a messenger's history limits
28
+ (cli-core's `messages sync`).
29
+
30
+ ## 0.74.0 — 02.11.2026
31
+
32
+ ### Added
33
+
34
+ - **`--compact`, a shared output switch for one line per item (WC-OUT-24).** The vocabulary lists it with role
35
+ `output`, and `COMPACT_FLAG` from `@wirecat/cli-base/standards` holds its name, so a CLI builds its option from
36
+ that one place. The rule says what the text keeps from the JSON answer: the reference first, the text last and cut
37
+ with `…(+N)`, and `page`, `missing`, `next` and `meta.complete` as lines that start with `# `. cli-core's runner
38
+ draws it; a CLI on its own `run()` has nothing to change until it offers the switch.
39
+
11
40
  ## 0.73.0 — 02.11.2026
12
41
 
13
42
  ### Added
@@ -26,6 +26,11 @@ export declare const LOADER_MARK = "// cli-dev bundle loader\n";
26
26
  * written into the loader as source, so it uses nothing from outside.
27
27
  */
28
28
  export declare const isServer: (words: readonly string[]) => boolean;
29
+ /**
30
+ * `upgrade`: the package manager replaces the files this process runs from, and pnpm deletes the old folder, so a chunk
31
+ * the command imports after the install is gone (MODULE_NOT_FOUND). It loads every chunk before it starts.
32
+ */
33
+ export declare const isUpgrade: (words: readonly string[]) => boolean;
29
34
  /** The bin that replaces the compiled one: `name` is the bundle's entry, `chunks` what a server preloads. */
30
35
  export declare const loaderSource: (name: string, version: string, chunks: readonly string[]) => string;
31
36
  export declare const bundle: (options: BundleOptions) => Promise<BundleResult>;
@@ -177,6 +177,14 @@ export const isServer = (words) => {
177
177
  .slice(0, 4)
178
178
  .some((word, at) => word === "serve" || word === "watch" || (word === "mcp" && plain[at + 1] === undefined));
179
179
  };
180
+ /**
181
+ * `upgrade`: the package manager replaces the files this process runs from, and pnpm deletes the old folder, so a chunk
182
+ * the command imports after the install is gone (MODULE_NOT_FOUND). It loads every chunk before it starts.
183
+ */
184
+ export const isUpgrade = (words) => words
185
+ .filter((word) => !word.startsWith("-"))
186
+ .slice(0, 2)
187
+ .includes("upgrade");
180
188
  /** The bin that replaces the compiled one: `name` is the bundle's entry, `chunks` what a server preloads. */
181
189
  export const loaderSource = (name, version, chunks) => `#!/usr/bin/env node
182
190
  ${LOADER_MARK}import * as module from "node:module"
@@ -202,8 +210,9 @@ if (
202
210
  : next(specifier, context),
203
211
  })
204
212
  }
205
- const running = import(${JSON.stringify(`../bundle/${name}.js`)})
206
213
  const chunks = ${JSON.stringify(chunks)}
214
+ if ((${isUpgrade.toString()})(words)) await Promise.allSettled(chunks.map((chunk) => import(chunk)))
215
+ const running = import(${JSON.stringify(`../bundle/${name}.js`)})
207
216
  // After the server answers its first request, so it starts no later.
208
217
  if (serves) setTimeout(() => void Promise.allSettled(chunks.map((chunk) => import(chunk))), 2000).unref()
209
218
  await running
@@ -1,6 +1,19 @@
1
1
  export const QUERY_SYNTAX = 'field:value · "a phrase" · word* · AND OR NOT · ( ) · [a TO b] · > >= < <=';
2
2
  const shownOf = (registry) => registry.backend === "native" ? registry.fields.filter((field) => field.native) : registry.fields;
3
3
  const leftOut = (fields) => fields.filter((field) => field.negatable).map((field) => field.name);
4
+ const has = (fields, operator) => fields.some((field) => field.operators.includes(operator));
5
+ // A provider takes only field:value joined by AND, so a native list advertises no OR and no grouping.
6
+ const syntaxOf = (shown, backend) => {
7
+ if (backend !== "native")
8
+ return QUERY_SYNTAX;
9
+ return [
10
+ "field:value",
11
+ ...(has(shown, "phrase") ? ['"a phrase"'] : []),
12
+ ...(has(shown, "wildcard") ? ["word*"] : []),
13
+ "terms joined by AND",
14
+ ...(has(shown, "range") ? ["[a TO b] · > >= < <="] : []),
15
+ ].join(" · ");
16
+ };
4
17
  const kindOf = (field) => field.values ? `${field.type}: ${field.values.join("|")}` : `${field.type}: ${field.operators.join(", ")}`;
5
18
  /** The Fields section of a list's help: one line per field it filters by, its defaults, the syntax and the guide. */
6
19
  export const fieldsHelp = (registry, guide) => {
@@ -17,7 +30,7 @@ export const fieldsHelp = (registry, guide) => {
17
30
  ...(negatable.length > 0
18
31
  ? [`Leave out: -${negatable[0]}:value or NOT ${negatable[0]}:value, for ${negatable.join(", ")}`]
19
32
  : []),
20
- `Syntax: ${QUERY_SYNTAX}`,
33
+ `Syntax: ${syntaxOf(shown, registry.backend)}`,
21
34
  ...(guide ? [`Guide: ${guide}`] : []),
22
35
  ].join("\n");
23
36
  };
@@ -1,3 +1,4 @@
1
+ import { COMPACT_FLAG } from "./output.js";
1
2
  export const HOW_FLAGS = [
2
3
  {
3
4
  name: "--limit",
@@ -84,6 +85,12 @@ export const HOW_FLAGS = [
84
85
  type: "switch",
85
86
  meaning: "each item as its reference, title, time and excerpt only",
86
87
  },
88
+ {
89
+ name: COMPACT_FLAG,
90
+ role: "output",
91
+ type: "switch",
92
+ meaning: "one line per item, built from the same answer as --json, with its paging and next steps as # lines",
93
+ },
87
94
  {
88
95
  name: "--chart-kind",
89
96
  value: "<messages|active|membership>",
@@ -275,6 +282,7 @@ export const HOW_FLAGS = [
275
282
  meaning: "refuse if what it names has changed since it was read",
276
283
  replaces: ["--expected"],
277
284
  },
285
+ { name: "--full-history", role: "mode", type: "switch", meaning: "keep a chat's whole history, past its limits" },
278
286
  {
279
287
  name: "--for-everyone",
280
288
  role: "mode",
@@ -1,6 +1,6 @@
1
1
  export { EFFECT_LEVELS, type EffectLevel, type Effects, effectsSchema, writes } from "./effects.js";
2
2
  export { BASE_REASONS, COMMON_DETAILS, CRASH_CODE, CRASH_REASON, defineReasons, type ErrorObject, type ErrorReason, errorObjectSchema, } from "./errors.js";
3
3
  export { defineLogEvents, isContentKey, LOG_ATTRIBUTE_KEYS, LOG_EVENTS, LOG_SEVERITIES, type LogEvent, type LogRecord, type LogSeverity, logAttributesJsonSchema, logEventProblems, logRecordSchema, traceIdOf, } from "./log.js";
4
- export { clockMeta, type Issue, type ItemEnvelope, isoTime, issueSchema, itemEnvelopeSchema, jsonlEndSchema, jsonlItemSchema, jsonlOf, type ListEnvelope, listEnvelopeSchema, localTime, MAX_NEXT_STEPS, type Meta, type Missing, metaSchema, missingSchema, type NextStep, nextStepSchema, type Page, pageSchema, reference, type Total, totalSchema, } from "./output.js";
4
+ export { COMPACT_FLAG, clockMeta, type Issue, type ItemEnvelope, isoTime, issueSchema, itemEnvelopeSchema, jsonlEndSchema, jsonlItemSchema, jsonlOf, type ListEnvelope, listEnvelopeSchema, localTime, MAX_NEXT_STEPS, type Meta, type Missing, metaSchema, missingSchema, type NextStep, nextStepSchema, type Page, pageSchema, reference, type Total, totalSchema, } from "./output.js";
5
5
  export { fill, jsonSchemaOf, RegistryError } from "./registry.js";
6
6
  export { defineSteps, SAME_COMMAND, STEPS, type StepDefinition, type StepInput, type StepTarget, stepOf, } from "./steps.js";
@@ -1,6 +1,6 @@
1
1
  export { EFFECT_LEVELS, effectsSchema, writes } from "./effects.js";
2
2
  export { BASE_REASONS, COMMON_DETAILS, CRASH_CODE, CRASH_REASON, defineReasons, errorObjectSchema, } from "./errors.js";
3
3
  export { defineLogEvents, isContentKey, LOG_ATTRIBUTE_KEYS, LOG_EVENTS, LOG_SEVERITIES, logAttributesJsonSchema, logEventProblems, logRecordSchema, traceIdOf, } from "./log.js";
4
- export { clockMeta, isoTime, issueSchema, itemEnvelopeSchema, jsonlEndSchema, jsonlItemSchema, jsonlOf, listEnvelopeSchema, localTime, MAX_NEXT_STEPS, metaSchema, missingSchema, nextStepSchema, pageSchema, reference, totalSchema, } from "./output.js";
4
+ export { COMPACT_FLAG, clockMeta, isoTime, issueSchema, itemEnvelopeSchema, jsonlEndSchema, jsonlItemSchema, jsonlOf, listEnvelopeSchema, localTime, MAX_NEXT_STEPS, metaSchema, missingSchema, nextStepSchema, pageSchema, reference, totalSchema, } from "./output.js";
5
5
  export { fill, jsonSchemaOf, RegistryError } from "./registry.js";
6
6
  export { defineSteps, SAME_COMMAND, STEPS, stepOf, } from "./steps.js";
@@ -1,6 +1,8 @@
1
1
  import * as v from "valibot";
2
2
  /** At most two next steps per answer: the one to take, then one alternative. */
3
3
  export declare const MAX_NEXT_STEPS = 2;
4
+ /** The switch for one line per item (WC-OUT-24). Its name is still the owner's to settle, so it lives in one place. */
5
+ export declare const COMPACT_FLAG = "--compact";
4
6
  export declare const isoTime: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
5
7
  /** The same moment on the wall clock of a zone, with its offset: `2026-11-02T16:03:00.000+01:00` (WC-TIME-2). */
6
8
  export declare const localTime: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "an ISO 8601 time with its offset">]>;
@@ -3,6 +3,8 @@ import { errorCodes } from "../errors.js";
3
3
  import { dottedId, text } from "./registry.js";
4
4
  /** At most two next steps per answer: the one to take, then one alternative. */
5
5
  export const MAX_NEXT_STEPS = 2;
6
+ /** The switch for one line per item (WC-OUT-24). Its name is still the owner's to settle, so it lives in one place. */
7
+ export const COMPACT_FLAG = "--compact";
6
8
  export const isoTime = v.pipe(v.string(), v.isoTimestamp("an ISO 8601 time in UTC"), v.endsWith("Z", "an ISO 8601 time in UTC, ending in `Z`"));
7
9
  /** The same moment on the wall clock of a zone, with its offset: `2026-11-02T16:03:00.000+01:00` (WC-TIME-2). */
8
10
  export const localTime = v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}$/, "an ISO 8601 time with its offset"));
@@ -30,7 +30,7 @@ the four steps that need a person. A new CLI is ready for its first release when
30
30
  | [Toolchain](toolchain.md) | `TOOL` | 20 | TypeScript, Biome, grit plugins, size caps, import cycles, spelling, Markdown, Go |
31
31
  | [Layout](layout.md) | `LAY` | 10 | Repository layout, entry points, exports, file size, one job per file, splitting a unit, the bundled bin |
32
32
  | [Commands](commands.md) | `CMD` | 29 | Paths, vocabulary, flags, arguments, effects, lifetime, query, help, external references, joining `wcat` |
33
- | [Output](output.md) | `OUT` | 23 | Data on stdout only, envelopes, errors, exit codes, partial answers, projection |
33
+ | [Output](output.md) | `OUT` | 24 | Data on stdout only, envelopes, errors, exit codes, partial answers, projection |
34
34
  | [MCP](mcp.md) | `MCP` | 13 | The MCP tools, how they come from commands, what each permission shows |
35
35
  | [Accounts](accounts.md) | `ACCT` | 12 | The explicit profile, reads across accounts, scope flags, linked identities, sign-in commands |
36
36
  | [Permissions](permissions.md) | `PERM` | 22 | Levels, keys, defaults, hard limits, the write guard and its journal |
@@ -165,8 +165,9 @@ catalogue and parity manifest stay in cli-core
165
165
 
166
166
  - **Why:** a script that works with one CLI works with the next.
167
167
  - **Enforced by:** review only.
168
- - **Example:** `--json` and `--jsonl` choose machine output. `--quiet` turns diagnostics off, but a failure is still
169
- said. `-v` and `-vv` add detail, `-V` prints the version, `--trace` writes request diagnostics, `--debug` adds the
168
+ - **Example:** `--json` and `--jsonl` choose machine output, `--compact` one line per item
169
+ ([WC-OUT-24](output.md#wc-out-24--offer-a-compact-text-form-built-from-the-same-answer)). `--quiet` turns
170
+ diagnostics off, but a failure is still said. `-v` and `-vv` add detail, `-V` prints the version, `--trace` writes request diagnostics, `--debug` adds the
170
171
  whole failure record. `--timeout <duration>` bounds the command. `--no-input` never prompts. `--dry-run` previews.
171
172
  `--yes` answers the question an `ask` permission level puts before a write.
172
173
  - **Exceptions:** none.
@@ -176,8 +177,8 @@ catalogue and parity manifest stay in cli-core
176
177
 
177
178
  - **Why:** a prompt in a pipe or under an agent waits for ever.
178
179
  - **Enforced by:** review only.
179
- - **Example:** `--no-input`, `--json`, `--jsonl`, a `CI` variable or a stdout that is not a terminal each switch prompts
180
- and browser logins off. Data piped on stdin for an explicit command still works. Buffered stdin stops at
180
+ - **Example:** `--no-input`, `--json`, `--jsonl`, `--compact`, a `CI` variable or a stdout that is not a terminal
181
+ each switch prompts and browser logins off. Data piped on stdin for an explicit command still works. Buffered stdin stops at
181
182
  `--max-input-bytes` (16 MiB by default).
182
183
  - **Exceptions:** none. `--yes` confirms a write; it never lifts a `deny`
183
184
  ([WC-PERM-10](permissions.md#wc-perm-10--a-flag-confirms-it-never-grants)).
@@ -288,6 +288,21 @@ Three things follow from it:
288
288
  - **Exceptions:** none.
289
289
  - **Applies to:** all CLIs. **Since:** cli-base 0.43.1. **Rulings:** none.
290
290
 
291
+ ### WC-OUT-24 · Offer a compact text form built from the same answer
292
+
293
+ - **Why:** an agent pays for every key, quote and repeated chat reference it reads. One line per item says the same
294
+ thing in about half the bytes, with no script to format it.
295
+ - **Enforced by:** `cli-core: src/cli/compact.test.ts`.
296
+ - **Example:** `tg personal messages list 111 --compact` prints one line per item, columns joined by ` · `, the
297
+ reference first and the free text last. When every reference shares a prefix, a header line names it once and the
298
+ rows print only the tail. Text is kept on one line, with control and direction characters made visible, and cut at
299
+ 400 characters with `…(+N)`; `--full` keeps it whole. What JSON says beside the items is kept as lines that start
300
+ with `# `: `# more:` from `page`, `# missing:` per `missing` entry, `# next:` per next step and `# complete: false`.
301
+ A row never starts with `# `, so no message text can pass for one of them. The footer goes to stdout with the
302
+ rows. `--compact` is refused with `--json`, `--jsonl` and `--brief`, never prompts, and exits as JSON would.
303
+ - **Exceptions:** MCP tools answer JSON.
304
+ - **Applies to:** CLIs that run on cli-core's `run()`. **Since:** cli-base 0.74.0. **Rulings:** NEED-2131.
305
+
291
306
  ## Sources
292
307
 
293
308
  These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-base",
3
- "version": "0.73.0",
3
+ "version": "0.75.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",