@wirecat/cli-core 0.427.0 → 0.429.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,29 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.429.0 — 25.10.2026
11
+
12
+ ### Added
13
+
14
+ - **`@wirecat/cli-core/cli/start` gives a bin `startCli` without the rest of `./cli`**, and **a bare `--version` or `-V` is answered before the program loads**. Importing `./cli` for `startCli` loaded about 1,000 files before the program even ran; `./cli/start` loads about 50 (`check:dist` keeps it under 80). The version is `app.version` and a newline on stdout, exit 0 — what Commander printed. Nothing else runs for it: no SQLite swap, no crash handler, and no notice a tool's `run` prints. `--version` with any other word (`--version --json`, `personal --version`) goes through the program as before. `startCli` and `run()` load the first-run questions, the store and the consent prompt only when a command asks them: the program module alone now loads about 320 files, not 850 (the `./cli` barrel a CLI imports still loads them all).
15
+ - **`store reset --dry-run` lists what a reset would empty and deletes nothing**: the store's files, their bytes,
16
+ every data table with its row count (search indexes are left out: they are rebuilt from those tables), whether a
17
+ backup comes first, and `in_use` — whether a serve or another process has the file open now, which the real reset
18
+ refuses (`null` where the system cannot say). It reads without a write lock, so it answers while a sync runs, and
19
+ it runs under `<PREFIX>_READ_ONLY`. A desktop app shows the list in its confirmation dialog.
20
+
21
+ ## 0.428.0 — 25.10.2026
22
+
23
+ ### Changed — may break callers
24
+
25
+ - **cli-core needs `@wirecat/cli-base` 0.57.** The peer range moves from `>=0.56.0 <0.57.0` to `>=0.57.0 <0.58.0`:
26
+ cli-base 0.57 saves `config.json` owner-only (`0600`), which the new `doctor` check below expects. A CLI that pins
27
+ cli-base moves its pin with this release.
28
+
29
+ ### Security
30
+
31
+ - **`doctor` reports a `config.json` or config folder that other users can read, with the `chmod` that fixes it.** The first config file is created `0600` in a `0700` folder, but cli-base up to 0.56 rewrote it as `0644` on the first `config set`, and older versions created the folder without `0700`, so on a machine used for a while both are often open. `files.problems` now lists the config file (wants `0600`) and its folder (wants `0700`), and stderr prints `to make it private: chmod 600 '<path>'`; nothing is changed on disk. The folder is checked even when `<APP>_CONFIG_DIR` names it, because the credential file falls back to it. Unlike before, `files` is also in the answer with no profile named and when the settings are refused. Windows is not checked, as before. Until cli-core moves to the cli-base release that saves the config `0600`, `config set` makes the file `0644` again and `doctor` will keep naming it.
32
+
10
33
  ## 0.427.0 — 25.10.2026
11
34
 
12
35
  ### Added
@@ -2,6 +2,7 @@ import { type ChildProcess, type SpawnOptions } from "node:child_process";
2
2
  import type { ErrorReason } from "@wirecat/cli-base/standards";
3
3
  import type { Command } from "commander";
4
4
  import { type AppIdentity } from "../ports/app.js";
5
+ export type { AppIdentity };
5
6
  /** What `startCli` hands the CLI's own program: the process, wired once. */
6
7
  export interface EntryContext {
7
8
  /** `process.argv` without the runtime and the script. */
package/dist/cli/entry.js CHANGED
@@ -1,10 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { CliError, catchCrashes } from "@wirecat/cli-base";
3
- import { readOnlyLockOf } from "../ports/app.js";
3
+ import { envName, readOnlyLockOf } from "../ports/app.js";
4
4
  import { ensureSqlite } from "../sqlite-runtime.js";
5
- import { readSecret } from "../terminal/prompt.js";
6
- import { commandPathOf } from "./command-contract.js";
7
- import { setupAtStart } from "./first-run.js";
5
+ import { logVersion } from "./argv-log.js";
8
6
  const processIo = () => ({
9
7
  argv: process.argv,
10
8
  env: process.env,
@@ -26,6 +24,8 @@ const processIo = () => ({
26
24
  export const startCli = async (entry) => {
27
25
  const io = { ...processIo(), ...entry.io };
28
26
  const args = io.argv.slice(2);
27
+ if (args.length === 1 && (args[0] === "--version" || args[0] === "-V"))
28
+ return printVersion(entry.app, io);
29
29
  const tty = io.stdout.isTTY === true;
30
30
  const release = catchCrashes({
31
31
  cli: entry.app.command,
@@ -62,6 +62,10 @@ export const startCli = async (entry) => {
62
62
  // The first-run questions write the shared config; a dry run writes nothing.
63
63
  if (command.optsWithGlobals().dryRun === true)
64
64
  return;
65
+ const [{ setupAtStart }, { commandPathOf }] = await Promise.all([
66
+ import("./first-run.js"),
67
+ import("./command-contract.js"),
68
+ ]);
65
69
  await setupAtStart({
66
70
  command: entry.app.command,
67
71
  env: io.env,
@@ -75,6 +79,7 @@ export const startCli = async (entry) => {
75
79
  ask: async (question, command) => {
76
80
  if (headless(command) || io.stdin.isTTY !== true)
77
81
  return false;
82
+ const { readSecret } = await import("../terminal/prompt.js");
78
83
  const answer = await readSecret(`${question} [y/N] `, {
79
84
  input: io.stdin,
80
85
  output: io.stderr,
@@ -112,6 +117,16 @@ export const startCli = async (entry) => {
112
117
  else if (code !== undefined)
113
118
  io.setExitCode(code);
114
119
  };
120
+ /**
121
+ * Answered before the program loads, which is most of a start: an agent asks for the version to learn whether the
122
+ * tool is there at all. Only the bare flag — anything else on the line goes through the program as before.
123
+ */
124
+ const printVersion = (app, io) => {
125
+ const argvLog = io.env[envName(app, "TEST_ARGV_LOG")];
126
+ if (argvLog)
127
+ logVersion(argvLog);
128
+ io.stdout.write(`${app.version}\n`);
129
+ };
115
130
  /** Help and the version were printed already; help for a missing command is a usage mistake, as in `run()`. */
116
131
  const commanderExit = (error) => {
117
132
  if (!(error instanceof Error))
@@ -4,13 +4,8 @@ import { delimiter, join } from "node:path";
4
4
  import { installerOf, updateCommand } from "@wirecat/cli-base/update";
5
5
  import { LANGUAGES } from "../search/languages.js";
6
6
  import { DEFAULT_STEMMERS } from "../search/stem.js";
7
- import { openCache } from "../store/open.js";
8
7
  import { storePath } from "../store/path.js";
9
8
  import { sharedConfigPath } from "../store/shared-config.js";
10
- import { openStore } from "../store/store.js";
11
- import { readSecret } from "../terminal/prompt.js";
12
- import { WIRECAT_TOOLS } from "./help/first-screen.js";
13
- import { buildStems } from "./messenger/store-maintenance-command.js";
14
9
  import { QUESTIONS, readSetupAnswers, unanswered, writeSetupAnswers, } from "./setup-answers.js";
15
10
  /** Commands that run without first-run questions: they serve agents, help, or open the store themselves. */
16
11
  const SKIPPED = new Set(["help", "guide", "completion", "mcp", "serve", "server", "update", "store"]);
@@ -86,6 +81,8 @@ const suggested = (env, contentLanguages) => {
86
81
  return (own.length ? own : LANGUAGES.filter(({ code }) => code === "en" || code === system)).map(({ name }) => name);
87
82
  };
88
83
  const askLanguages = async ({ command, env, io, contentLanguages }) => {
84
+ // The store and its maintenance are most of cli-core; every command runs this file, few ever ask.
85
+ const { openStore } = await import("../store/store.js");
89
86
  const store = await openStore({ env });
90
87
  let rebuild = false;
91
88
  try {
@@ -117,6 +114,10 @@ const askLanguages = async ({ command, env, io, contentLanguages }) => {
117
114
  }
118
115
  if (!rebuild)
119
116
  return;
117
+ const [{ openCache }, { buildStems }] = await Promise.all([
118
+ import("../store/open.js"),
119
+ import("./messenger/store-maintenance-command.js"),
120
+ ]);
120
121
  const database = await openCache(storePath(env));
121
122
  try {
122
123
  buildStems(database, (note) => io.say(` ${note}`), { force: true });
@@ -128,6 +129,7 @@ const askLanguages = async ({ command, env, io, contentLanguages }) => {
128
129
  const askTools = async (run) => {
129
130
  const { command, env, io } = run;
130
131
  const installed = run.installed ?? ((name) => onPath(name, env));
132
+ const { WIRECAT_TOOLS } = await import("./help/first-screen.js");
131
133
  const others = WIRECAT_TOOLS.filter((tool) => tool.command !== command);
132
134
  const missing = others.filter((tool) => !installed(tool.command));
133
135
  io.say("Other WireCat tools — they share this store, so one search finds everything:");
@@ -213,7 +215,7 @@ export const setupAtStart = async (start) => {
213
215
  contentLanguages: start.contentLanguages,
214
216
  all: start.path[0] === "setup",
215
217
  io: {
216
- ask: (prompt) => readSecret(prompt, { input, echo: true }),
218
+ ask: async (prompt) => (await import("../terminal/prompt.js")).readSecret(prompt, { input, echo: true }),
217
219
  say: (text) => process.stderr.write(`${text}\n`),
218
220
  },
219
221
  }).catch((error) => {
@@ -137,13 +137,16 @@ const diagnose = async (command, messenger) => {
137
137
  catch (error) {
138
138
  // Not the file: a profile lock, a flag or an environment variable the settings refuse.
139
139
  const path = configFilePath(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).config);
140
+ const files = privateFiles(configFiles(path));
140
141
  return {
142
+ fixes: fixesOf(files),
141
143
  report: {
142
144
  cli,
143
145
  config: { path, found: existsSync(path), issues: [] },
144
146
  settings: { error: messageOf(error) },
145
147
  store: snakeKeys(await storeSummary(env)),
146
148
  runs: { directory: runsDirFor(app, env), kept: listRuns(runsDirFor(app, env)).length },
149
+ files: filesReport(files),
147
150
  },
148
151
  };
149
152
  }
@@ -156,9 +159,12 @@ const diagnose = async (command, messenger) => {
156
159
  ...unknownPermissionIssues(command, settings, app.command, messenger.permissionKey),
157
160
  ],
158
161
  };
159
- if (settings.sources.profile === NONE_NAMED)
162
+ const own = configFiles(settings.configPath);
163
+ if (settings.sources.profile === NONE_NAMED) {
164
+ const files = privateFiles(own);
160
165
  return {
161
166
  renderer,
167
+ fixes: fixesOf(files),
162
168
  report: {
163
169
  cli,
164
170
  profile: null,
@@ -166,22 +172,30 @@ const diagnose = async (command, messenger) => {
166
172
  store: snakeKeys(await storeSummary(env)),
167
173
  readers: engineStates(),
168
174
  runs: { directory: runsDirFor(app, env), kept: listRuns(runsDirFor(app, env)).length },
175
+ files: filesReport(files),
169
176
  },
170
177
  };
178
+ }
171
179
  const { profile } = settings;
172
180
  const account = recalledAccount(app, provider, profile, env);
173
181
  const store = storePath(env);
174
182
  const sends = sendsPathFor(app, profile, env);
175
183
  const flood = floodPathFor(app, profile, env);
176
184
  const files = privateFiles({
177
- files: [...withSqliteSidecars(store), sends, flood],
185
+ files: [...own.files, ...withSqliteSidecars(store), sends, flood],
178
186
  // A folder the owner chose with MESSAGING_STORE may rightly be shared, like the home folder itself.
179
- dirs: [...(env.MESSAGING_STORE ? [] : [dirname(store)]), dirname(sends), dirname(flood), runsDirFor(app, env)],
187
+ dirs: [
188
+ ...own.dirs,
189
+ ...(env.MESSAGING_STORE ? [] : [dirname(store)]),
190
+ dirname(sends),
191
+ dirname(flood),
192
+ runsDirFor(app, env),
193
+ ],
180
194
  });
181
195
  return {
182
196
  renderer,
183
197
  profile,
184
- fixes: files.checked ? files.problems.map((problem) => problem.fix) : [],
198
+ fixes: fixesOf(files),
185
199
  report: {
186
200
  cli,
187
201
  profile,
@@ -195,15 +209,20 @@ const diagnose = async (command, messenger) => {
195
209
  flood: floodState(new FloodMemory(flood)),
196
210
  runs: { directory: runsDirFor(app, env), kept: listRuns(runsDirFor(app, env)).length },
197
211
  // The messenger's own block keeps the keys it gives: some of them are file paths.
198
- files: files.checked
199
- ? { ...files, problems: files.problems.map(({ path, mode, want }) => ({ path, mode, want })) }
200
- : files,
212
+ files: filesReport(files),
201
213
  [provider]: await (messenger.diagnose?.(command, context) ?? Promise.resolve({})).catch((error) => ({
202
214
  error: messageOf(error),
203
215
  })),
204
216
  },
205
217
  };
206
218
  };
219
+ /**
220
+ * The config folder is checked even when `<APP>_CONFIG_DIR` names it: it is the tool's own folder, and the
221
+ * credential file falls back to it when there is no keyring.
222
+ */
223
+ const configFiles = (path) => ({ files: [path], dirs: [dirname(path)] });
224
+ const fixesOf = (files) => (files.checked ? files.problems.map((problem) => problem.fix) : []);
225
+ const filesReport = (files) => files.checked ? { ...files, problems: files.problems.map(({ path, mode, want }) => ({ path, mode, want })) } : files;
207
226
  /** What the messenger asked this profile to wait for, still in force — what the next command would be refused. */
208
227
  export const floodState = (memory) => {
209
228
  const { deadlines, sendBlock } = memory.read();
@@ -12,7 +12,7 @@ import { migrate } from "../../store/migrations.js";
12
12
  import { openCache } from "../../store/open.js";
13
13
  import { storePath } from "../../store/path.js";
14
14
  import { deleteCopy, repairStore } from "../../store/repair.js";
15
- import { drainNoteIndex, fillSearchIndex, fillStems, involvementStoreOver, noteIndexState, openStore, orphanPointers, pendingNormalization, resetAttachmentWords, resetNoteIndex, resetSearchIndex, resetStems, searchIndexState, stemmerCache, stemsState, } from "../../store/store.js";
15
+ import { drainNoteIndex, fillSearchIndex, fillStems, involvementStoreOver, noteIndexState, openSqlite, openStore, orphanPointers, pendingNormalization, resetAttachmentWords, resetNoteIndex, resetSearchIndex, resetStems, searchIndexState, stemmerCache, stemsState, } from "../../store/store.js";
16
16
  import { environmentOf, outputFor } from "../context.js";
17
17
  import { destroysNothing, LOCAL_READ, LOCAL_WRITE } from "../effects.js";
18
18
  import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
@@ -45,6 +45,16 @@ const reading = async (path, read) => {
45
45
  database.close();
46
46
  }
47
47
  };
48
+ /** For a command that only reads: the connection refuses writes, and opening sets no journal mode. */
49
+ const readingOnly = async (path, read) => {
50
+ const { database } = await openSqlite(path, { readOnly: true });
51
+ try {
52
+ return read(database);
53
+ }
54
+ finally {
55
+ database.close();
56
+ }
57
+ };
48
58
  /** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
49
59
  export const storeSummary = async (env) => {
50
60
  const path = storePath(env);
@@ -630,22 +640,30 @@ const quiesce = async (path, messenger, then, warn, waitMs = STORE_LOCK_WAIT_MS)
630
640
  }
631
641
  };
632
642
  /**
633
- * For a store this build cannot migrate: a backup beside it, then an empty store at this build's
634
- * schema. The backup is made before anything is deleted, and its path is said before the delete.
643
+ * For a store this build cannot migrate, or one the owner wants empty: a backup beside it, then an empty
644
+ * store at this build's schema. The backup is made before anything is deleted, and its path is said before
645
+ * the delete. A new file rather than `DELETE FROM` every table: deleted rows would stay readable in free
646
+ * pages until a VACUUM, and every table comes back in this build's shape.
635
647
  */
636
648
  const resetCommand = (messenger) => annotate(new Command("reset"), { output: { entity: "store_reset" }, effects: LOCAL_WRITE })
637
649
  .summary("back up the local archive and replace it with an empty one")
638
650
  .description("back the store up beside itself, then delete it and start an empty one at this build's schema; asks first, or --yes")
639
651
  .option("--no-backup", "delete the store without backing it up first")
640
- .action(async function ({ backup: keep }) {
652
+ .option("--dry-run", "list every table it would empty, with its rows, and delete nothing")
653
+ .action(async function ({ backup: keep, dryRun }) {
641
654
  const { renderer } = outputFor(this);
642
655
  const { command } = messenger.app;
643
- const path = storePath(environmentOf(this).env ?? process.env);
656
+ const env = environmentOf(this).env ?? process.env;
657
+ const path = storePath(env);
644
658
  if (!existsSync(path)) {
645
659
  result(this, { path, exists: false, reset: false });
646
660
  renderer.note(`no store at ${path} — nothing to reset; the first \`${command}\` command that reads a chat creates it`);
647
661
  return;
648
662
  }
663
+ if (dryRun) {
664
+ result(this, await resetPreview(path, messenger, env, keep));
665
+ return;
666
+ }
649
667
  refuseWhileServing(this, messenger);
650
668
  if (this.optsWithGlobals().yes !== true) {
651
669
  const what = `this deletes the store at ${path} and starts an empty one, ${keep ? "after a backup beside it" : "with no backup"}`;
@@ -674,7 +692,7 @@ const resetCommand = (messenger) => annotate(new Command("reset"), { output: { e
674
692
  });
675
693
  renderer.note(`backed the store up to ${backup}`);
676
694
  }
677
- for (const suffix of ["", "-wal", "-shm"])
695
+ for (const suffix of STORE_PARTS)
678
696
  rmSync(`${path}${suffix}`, { force: true });
679
697
  await (await openStore({ path, command })).close();
680
698
  const schema = await reading(path, (database) => schemaOf(database).version);
@@ -683,5 +701,43 @@ const resetCommand = (messenger) => annotate(new Command("reset"), { output: { e
683
701
  ? `the store is empty at schema ${schema}; \`${command} store restore ${backup}\` puts the old one back once a build can read it`
684
702
  : `the store is empty at schema ${schema}; there is no backup`);
685
703
  });
704
+ const STORE_PARTS = ["", "-wal", "-shm"];
705
+ /**
706
+ * What a reset would delete, read without a write lock so it answers while a sync runs. `in_use` is what the
707
+ * real run would refuse over now; `null` where this system cannot say who has the file open.
708
+ */
709
+ const resetPreview = async (path, messenger, env, keep) => {
710
+ const files = STORE_PARTS.map((suffix) => `${path}${suffix}`).filter((file) => existsSync(file));
711
+ const holders = holdersOf(files.map((file) => realpathSync(file)));
712
+ const serving = servingProfiles(messenger.app, env).length > 0;
713
+ const read = await readingOnly(path, (database) => ({
714
+ schema: schemaOf(database).version,
715
+ tables: emptiedTables(database),
716
+ }));
717
+ return {
718
+ path,
719
+ exists: true,
720
+ reset: false,
721
+ dryRun: true,
722
+ files,
723
+ bytes: files.reduce((sum, file) => sum + bytesOf(file), 0),
724
+ schema: read.schema,
725
+ tables: read.tables,
726
+ backupFirst: keep,
727
+ inUse: serving || (holders === undefined ? null : holders.length > 0),
728
+ };
729
+ };
730
+ /** The tables that hold data; a search index and its shadow tables are rebuilt from them, so they are left out. */
731
+ const emptiedTables = (database) => {
732
+ const all = database
733
+ .prepare("SELECT name, sql FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
734
+ .all()
735
+ .map((row) => ({ name: String(row.name), virtual: /^\s*CREATE\s+VIRTUAL/i.test(String(row.sql)) }));
736
+ const indexes = all.filter((table) => table.virtual).map((table) => `${table.name}_`);
737
+ return all
738
+ .filter((table) => !table.virtual && table.name !== "schema_migrations")
739
+ .filter((table) => !indexes.some((prefix) => table.name.startsWith(prefix)))
740
+ .map((table) => ({ name: table.name, rows: count(database, `"${table.name}"`) }));
741
+ };
686
742
  const isoOf = (value) => new Date(Number(value)).toISOString();
687
743
  const messageOf = (error) => (error instanceof Error ? error.message : String(error));
@@ -4,8 +4,8 @@ export declare const generatedApiCommand: (api: import("./bot/api.js").ApiComman
4
4
  export declare const botCommand: (bot: import("./index.js").BotMessenger) => import("commander").Command;
5
5
  export declare const botMcpCommand: (bot: import("./index.js").BotMessenger) => import("commander").Command;
6
6
  export declare const botMessagesCommand: (bot: import("./index.js").BotMessenger) => import("commander").Command;
7
- export declare const commandsCommand: (app: import("./index.js").AppIdentity) => import("commander").Command;
8
- export declare const configCommand: (app: import("./index.js").AppIdentity, config: {
7
+ export declare const commandsCommand: (app: import("./entry.js").AppIdentity) => import("commander").Command;
8
+ export declare const configCommand: (app: import("./entry.js").AppIdentity, config: {
9
9
  resolveSettings: (flags?: import("./settings.js").GlobalFlags, options?: import("./settings.js").ResolveOptions) => import("./settings.js").Settings & object;
10
10
  configuredProfiles: (options?: import("./settings.js").ResolveOptions) => string[];
11
11
  changeSetting: (path: string, { profile, setting, value, kind, root, }: {
@@ -172,7 +172,7 @@ export declare const configCommand: (app: import("./index.js").AppIdentity, conf
172
172
  }, args_2?: {
173
173
  permissionKey?: import("./permission-keys.js").PermissionKeyOf;
174
174
  } | undefined) => import("commander").Command;
175
- export declare const guideCommand: (app: Pick<import("./index.js").AppIdentity, "command">, options: import("./guide.js").GuideOptions) => import("commander").Command;
175
+ export declare const guideCommand: (app: Pick<import("./entry.js").AppIdentity, "command">, options: import("./guide.js").GuideOptions) => import("commander").Command;
176
176
  export declare const addMeetingBatchCommand: (program: import("commander").Command, deps: {
177
177
  reader: import("../meetings/batch.js").MeetingBatchReader;
178
178
  input: (path: string, signal?: AbortSignal) => Promise<string>;
@@ -410,7 +410,7 @@ export declare const tasksCommand: (messenger: import("./index.js").Messenger) =
410
410
  export declare const tasksStatsCommand: (messenger: import("./index.js").Messenger) => import("commander").Command;
411
411
  export declare const topicsCommand: (messenger: import("./index.js").Messenger) => import("commander").Command;
412
412
  export declare const watchCommand: (messenger: import("./index.js").Messenger) => import("commander").Command;
413
- export declare const runsCommand: (app: import("./index.js").AppIdentity) => import("commander").Command;
414
- export declare const skillCommand: (app: import("./index.js").AppIdentity, skill: URL) => import("commander").Command;
415
- export declare const upgradeCommand: (app: import("./index.js").AppIdentity, packageName: string, contextFor: (command: import("commander").Command) => import("./upgrade-command.js").UpgradeContext) => import("commander").Command;
416
- export declare const modelCatalogueCommand: (app: import("./index.js").AppIdentity, options: import("./model-catalogue-command.js").ModelCatalogueOptions) => import("commander").Command;
413
+ export declare const runsCommand: (app: import("./entry.js").AppIdentity) => import("commander").Command;
414
+ export declare const skillCommand: (app: import("./entry.js").AppIdentity, skill: URL) => import("commander").Command;
415
+ export declare const upgradeCommand: (app: import("./entry.js").AppIdentity, packageName: string, contextFor: (command: import("commander").Command) => import("./upgrade-command.js").UpgradeContext) => import("commander").Command;
416
+ export declare const modelCatalogueCommand: (app: import("./entry.js").AppIdentity, options: import("./model-catalogue-command.js").ModelCatalogueOptions) => import("commander").Command;
@@ -9773,6 +9773,20 @@ export declare const ENTITIES: {
9773
9773
  readonly path: v.StringSchema<undefined>;
9774
9774
  readonly exists: v.LiteralSchema<false, undefined>;
9775
9775
  readonly reset: v.LiteralSchema<false, undefined>;
9776
+ }, undefined>, v.StrictObjectSchema<{
9777
+ readonly path: v.StringSchema<undefined>;
9778
+ readonly exists: v.LiteralSchema<true, undefined>;
9779
+ readonly reset: v.LiteralSchema<false, undefined>;
9780
+ readonly dry_run: v.LiteralSchema<true, undefined>;
9781
+ readonly files: v.ArraySchema<v.StringSchema<undefined>, undefined>;
9782
+ readonly bytes: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
9783
+ readonly schema: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
9784
+ readonly tables: v.ArraySchema<v.StrictObjectSchema<{
9785
+ readonly name: v.StringSchema<undefined>;
9786
+ readonly rows: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
9787
+ }, undefined>, undefined>;
9788
+ readonly backup_first: v.BooleanSchema<undefined>;
9789
+ readonly in_use: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
9776
9790
  }, undefined>, v.StrictObjectSchema<{
9777
9791
  readonly path: v.StringSchema<undefined>;
9778
9792
  readonly exists: v.LiteralSchema<true, undefined>;
@@ -346,6 +346,20 @@ export declare const storeReset: v.UnionSchema<[v.StrictObjectSchema<{
346
346
  readonly path: v.StringSchema<undefined>;
347
347
  readonly exists: v.LiteralSchema<false, undefined>;
348
348
  readonly reset: v.LiteralSchema<false, undefined>;
349
+ }, undefined>, v.StrictObjectSchema<{
350
+ readonly path: v.StringSchema<undefined>;
351
+ readonly exists: v.LiteralSchema<true, undefined>;
352
+ readonly reset: v.LiteralSchema<false, undefined>;
353
+ readonly dry_run: v.LiteralSchema<true, undefined>;
354
+ readonly files: v.ArraySchema<v.StringSchema<undefined>, undefined>;
355
+ readonly bytes: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
356
+ readonly schema: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
357
+ readonly tables: v.ArraySchema<v.StrictObjectSchema<{
358
+ readonly name: v.StringSchema<undefined>;
359
+ readonly rows: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
360
+ }, undefined>, undefined>;
361
+ readonly backup_first: v.BooleanSchema<undefined>;
362
+ readonly in_use: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
349
363
  }, undefined>, v.StrictObjectSchema<{
350
364
  readonly path: v.StringSchema<undefined>;
351
365
  readonly exists: v.LiteralSchema<true, undefined>;
@@ -147,6 +147,18 @@ export const storeRestore = v.strictObject({
147
147
  });
148
148
  export const storeReset = v.union([
149
149
  v.strictObject({ ...absent.entries, reset: v.literal(false) }),
150
+ v.strictObject({
151
+ path: text,
152
+ exists: v.literal(true),
153
+ reset: v.literal(false),
154
+ dry_run: v.literal(true),
155
+ files: v.array(text),
156
+ bytes: count,
157
+ schema: count,
158
+ tables: v.array(v.strictObject({ name: text, rows: count })),
159
+ backup_first: v.boolean(),
160
+ in_use: v.nullable(v.boolean()),
161
+ }),
150
162
  v.strictObject({
151
163
  path: text,
152
164
  exists: v.literal(true),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.427.0",
3
+ "version": "0.429.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",
@@ -48,6 +48,10 @@
48
48
  "types": "./dist/cli/index.d.ts",
49
49
  "default": "./dist/cli/index.js"
50
50
  },
51
+ "./cli/start": {
52
+ "types": "./dist/cli/entry.d.ts",
53
+ "default": "./dist/cli/entry.js"
54
+ },
51
55
  "./sends": {
52
56
  "types": "./dist/sends/index.d.ts",
53
57
  "default": "./dist/sends/index.js"
@@ -213,7 +217,7 @@
213
217
  "@types/node": "^22.20.4",
214
218
  "@types/pngjs": "6.0.5",
215
219
  "@vitest/coverage-v8": "^5.0.2",
216
- "@wirecat/cli-base": "0.56.0",
220
+ "@wirecat/cli-base": "0.57.0",
217
221
  "@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
218
222
  "commander": "15.0.0",
219
223
  "cspell": "10.3.6",
@@ -234,7 +238,7 @@
234
238
  },
235
239
  "peerDependencies": {
236
240
  "@napi-rs/canvas": ">=0.1.69 <2",
237
- "@wirecat/cli-base": ">=0.56.0 <0.57.0",
241
+ "@wirecat/cli-base": ">=0.57.0 <0.58.0",
238
242
  "commander": ">=15"
239
243
  },
240
244
  "bin": {
package/parity.json CHANGED
@@ -4611,6 +4611,7 @@
4611
4611
  "store reset": {
4612
4612
  "in": "all",
4613
4613
  "options": {
4614
+ "--dry-run": "all",
4614
4615
  "--no-backup": "all"
4615
4616
  }
4616
4617
  },