@wirecat/cli-core 0.425.0 → 0.427.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 +45 -0
- package/dist/cli/commands-command.js +1 -1
- package/dist/cli/effects.d.ts +4 -0
- package/dist/cli/effects.js +5 -1
- package/dist/cli/guide.d.ts +1 -1
- package/dist/cli/guide.js +33 -5
- package/dist/cli/help/command-help.js +12 -7
- package/dist/cli/help/first-screen.js +2 -2
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.js +3 -2
- package/dist/cli/meeting-preparation-command.js +2 -2
- package/dist/cli/meeting-series-inventory-command.js +2 -2
- package/dist/cli/messenger/models-command.js +3 -2
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/store-maintenance-command.js +9 -47
- package/dist/cli/messenger/store-tables-command.d.ts +5 -0
- package/dist/cli/messenger/store-tables-command.js +59 -0
- package/dist/cli/profile.d.ts +7 -0
- package/dist/cli/profile.js +19 -0
- package/dist/cli/program.js +12 -11
- package/dist/cli/store-info.d.ts +12 -0
- package/dist/cli/store-info.js +72 -0
- package/dist/entities/index.d.ts +54 -5
- package/dist/entities/index.js +3 -1
- package/dist/entities/store-maintenance.d.ts +48 -4
- package/dist/entities/store-maintenance.js +16 -3
- package/dist/entities/store-table.d.ts +8 -0
- package/dist/entities/store-table.js +9 -0
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/store/compatibility.d.ts +28 -0
- package/dist/store/compatibility.js +41 -0
- package/dist/store/exports.d.ts +1 -0
- package/dist/store/exports.js +1 -0
- package/dist/store/sqlite/store-tables.d.ts +43 -0
- package/dist/store/sqlite/store-tables.js +111 -0
- package/dist/store/store.d.ts +1 -0
- package/dist/store/store.js +1 -0
- package/package.json +3 -3
- package/parity.json +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,51 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.427.0 — 25.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Every CLI on cli-core gets `skill status` and `skill show --for claude|agents`** from cli-base 0.56.0. `skill
|
|
15
|
+
status` says, for each agent, whether its installed SKILL.md matches this version; `skill show --for` prints the copy
|
|
16
|
+
installed there, so a host can show what `skill install` would change.
|
|
17
|
+
- **`store info` for every WireCat tool, and whether this tool can read the store.** `storeInfo(env)` returns the answer for a tool that builds its own program (drive, memo); `storeInfoCommand()` is the `info` command and `sharedStoreCommand()` a `store` group holding it, for a tool on `createProgram` with no `store` of its own (zm). `schema` gains `reason` (`missing`, `older`, `current`, `newer` — this tool may still write it — or `too_new`) and `upgrade`: `{ version, min_compatible }` the file will be at after this tool's next run, or `null`, so a caller can name every other tool whose `speaks` is below that minimum before it updates. `writable` keeps saying whether this tool can open the file. The answer has `clients`, each tool that opened the store with its `version`, cli-core `core`, `speaks` and `seen_at`. A missing store answers with this tool's `schema` too, and a file that does not open with `schema.speaks`.
|
|
18
|
+
- **`store tables list` lists every table of the archive, and `store info` describes the file itself.** WireCat
|
|
19
|
+
Desktop's Storage page showed totals but not which tables the archive holds, its migrations or the last backup, and
|
|
20
|
+
the app may not open the file. `store tables list [query...]` answers each table's `name`, `kind` (`data`,
|
|
21
|
+
`search_index`, `search_index_part`, `internal`) and `rows`, read-only; a search index's rows stay `null`, since
|
|
22
|
+
counting them scans the whole index. `--full` adds `bytes`, the pages a table and its indexes hold, which reads the
|
|
23
|
+
whole file: under a second when it is in memory, ten seconds to two minutes when not, on a 3 GB store. Under Bun,
|
|
24
|
+
whose SQLite has no `dbstat`, `--full` says so. `store info` gains `journal_mode`, `pages` (`size`, `count`, and
|
|
25
|
+
`free`, the pages a `VACUUM` would give back), `migrations` (every applied version with its `min_compatible` and
|
|
26
|
+
`applied_at`) and `backup`, the time, file and check from the `backups/last.json` marker a backup job leaves beside
|
|
27
|
+
the store (`null` without one). The resource word `tables` comes from cli-base
|
|
28
|
+
([WireCatLabs/cli-base#334](https://github.com/WireCatLabs/cli-base/pull/334)).
|
|
29
|
+
|
|
30
|
+
### Changed — may break callers
|
|
31
|
+
|
|
32
|
+
- **cli-core needs `@wirecat/cli-base` 0.56.** The peer range moves from `>=0.53.0 <0.54.0` to `>=0.56.0 <0.57.0`. A
|
|
33
|
+
CLI that pins cli-base moves its pin with this release. cli-base 0.54 and 0.55 change the release lock and the
|
|
34
|
+
changelog guard, which a CLI adopts through community's standards sync, and 0.56 adds the `missing-run-log` lint
|
|
35
|
+
rule: a CLI without `runs` fails `cli-dev lint` until it registers `runsCommand(app)`.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **`store info` opens the store read-only.** It no longer sets the journal mode, and a store too new for this tool answers with its schema and empty `rows` instead of failing to count tables it may have renamed.
|
|
40
|
+
|
|
41
|
+
## 0.426.0 — 25.10.2026
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **`profileFree(command)` and `takesProfile(command)` from `/cli`: a CLI marks the commands that read no profile, and help asks its own command tree.** A marked command, and every command under it, gets no `personal` in its help example and no `<profile>` in its usage line: `drive doctor`, `drive api list`, not `drive personal doctor`. `PROFILE_FREE` (`commands`, `complete`, `guide`, `help`, `skill`, `upgrade`) still counts for every CLI. drive and zm mark their own housekeeping commands to use it.
|
|
46
|
+
- **`storeChangeRefusal(app)` and `previewFlag(program)` from `/cli`.**
|
|
47
|
+
|
|
48
|
+
### Changed — may break callers
|
|
49
|
+
|
|
50
|
+
- **Help and examples that cli-core writes no longer speak of messages to a CLI that is not a messenger, or show a command that does something unasked.** The first screen's "Works with" says zm and drive are "searchable with your other WireCat data", in every CLI (tg, max and wa included). `guide`'s example names the CLI's first scenario (`tg guide catch-up --json`) in place of `<name>`, and is left out when the CLI has none. `models download` (the messenger `models` command) says its size and examples the default text model only, not `parakeet-v3`. `meetings scheduled` and `meetings prepare` example `--dry-run` where the program has it. `commands`' argument example is `commands info` for a CLI that is not a messenger. The `<PREFIX>_READ_ONLY` refusal of a store change says "change the local store" for such a CLI; tg, max and wa keep "change the message store".
|
|
51
|
+
- **"Availability: available for this profile" is shown only where a profile is in play.** A CLI whose first screen has `profile: false` (memo), and a command that reads no profile, show the line only when the command is unavailable, with its reason.
|
|
52
|
+
- **`createProgram` puts no `<profile>` in the usage line of a CLI whose first screen has `profile: false`**, nor of a `profileFree` command.
|
|
53
|
+
- **A guide scenario step may start with a profile word** (`tg personal chats list`), so scenarios can name the profile as the command line requires. `checkStep` skips a first word that is not a command and is a usable profile name; its "steps leave out the profile" refusal is gone.
|
|
54
|
+
|
|
10
55
|
## 0.425.0 — 25.10.2026
|
|
11
56
|
|
|
12
57
|
### Added
|
|
@@ -28,7 +28,7 @@ export const commandsCommand = (app) => {
|
|
|
28
28
|
reason: "output.one-object",
|
|
29
29
|
});
|
|
30
30
|
})
|
|
31
|
-
.argument("[path...]",
|
|
31
|
+
.argument("[path...]", `one command path, for example: ${app.messenger === false ? "commands info" : "search messages"}; inspect other groups in separate calls`)
|
|
32
32
|
.action(function (path) {
|
|
33
33
|
const root = rootOf(this);
|
|
34
34
|
const output = outputFor(this);
|
package/dist/cli/effects.d.ts
CHANGED
|
@@ -48,6 +48,10 @@ export declare const changesStore: <T extends Command>(command: T) => T;
|
|
|
48
48
|
*/
|
|
49
49
|
export declare const isStoreChange: (command: Command) => boolean;
|
|
50
50
|
export declare const STORE_CHANGE_REFUSAL = "change the message store";
|
|
51
|
+
/** ` --dry-run` where the program has it, so an example of a command that calls a provider calls nothing. */
|
|
52
|
+
export declare const previewFlag: (program: Command) => string;
|
|
53
|
+
/** What a read-only refusal says the command would do: the store holds more than messages outside a messenger. */
|
|
54
|
+
export declare const storeChangeRefusal: (app: Pick<AppIdentity, "messenger">) => string;
|
|
51
55
|
/** Under `<PREFIX>_READ_ONLY`, refuses a change to the store — for a caller outside `run()`, an MCP tool. */
|
|
52
56
|
export declare const refuseStoreChange: (app: AppIdentity, env: NodeJS.ProcessEnv) => void;
|
|
53
57
|
/**
|
package/dist/cli/effects.js
CHANGED
|
@@ -79,11 +79,15 @@ export const isStoreChange = (command) => {
|
|
|
79
79
|
!changesAuthority(command));
|
|
80
80
|
};
|
|
81
81
|
export const STORE_CHANGE_REFUSAL = "change the message store";
|
|
82
|
+
/** ` --dry-run` where the program has it, so an example of a command that calls a provider calls nothing. */
|
|
83
|
+
export const previewFlag = (program) => program.options.some((option) => option.long === "--dry-run") ? " --dry-run" : "";
|
|
84
|
+
/** What a read-only refusal says the command would do: the store holds more than messages outside a messenger. */
|
|
85
|
+
export const storeChangeRefusal = (app) => app.messenger === false ? "change the local store" : STORE_CHANGE_REFUSAL;
|
|
82
86
|
/** Under `<PREFIX>_READ_ONLY`, refuses a change to the store — for a caller outside `run()`, an MCP tool. */
|
|
83
87
|
export const refuseStoreChange = (app, env) => {
|
|
84
88
|
const lock = readOnlyLockOf(app, env);
|
|
85
89
|
if (lock)
|
|
86
|
-
throw readOnlyRefusal(lock,
|
|
90
|
+
throw readOnlyRefusal(lock, storeChangeRefusal(app));
|
|
87
91
|
};
|
|
88
92
|
const pathOf = (command) => {
|
|
89
93
|
const words = [];
|
package/dist/cli/guide.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export declare const loadScenarios: (directory: string) => Promise<{
|
|
|
49
49
|
}>;
|
|
50
50
|
/**
|
|
51
51
|
* `<tool> guide` lists the tool's scenarios and the ones it works with; `<tool> guide <name>` prints one: the goal,
|
|
52
|
-
* each step as a command and why, and what to look at. The files are
|
|
52
|
+
* each step as a command and why, and what to look at. The files are parsed when the command runs, never when the
|
|
53
53
|
* program is built. `checkGuide` validates them in the tool's CI.
|
|
54
54
|
*/
|
|
55
55
|
export declare const guideCommand: (app: Pick<AppIdentity, "command">, options: GuideOptions) => Command;
|
package/dist/cli/guide.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
import { environmentOf, outputFor } from "./context.js";
|
|
7
7
|
import { NO_EFFECTS } from "./effects.js";
|
|
8
8
|
import { itemOf, listOf, renderAnswer } from "./envelope.js";
|
|
9
|
-
import { PROFILE_FREE } from "./profile.js";
|
|
9
|
+
import { commandWords, PROFILE_FREE, usableProfileName } from "./profile.js";
|
|
10
10
|
/** memo's cross-tool scenarios: the one source of the titles every tool lists, and memo's files must carry. */
|
|
11
11
|
export const MEMO_SCENARIOS = {
|
|
12
12
|
personBrief: { tool: "memo", name: "person-brief", title: "One person across every tool" },
|
|
@@ -79,6 +79,19 @@ export const loadScenarios = async (directory) => {
|
|
|
79
79
|
problems: parsed.flatMap(({ problems }) => problems),
|
|
80
80
|
};
|
|
81
81
|
};
|
|
82
|
+
/** Only the file names, for the example: building the program parses no scenario. */
|
|
83
|
+
const firstScenarioName = (directory) => {
|
|
84
|
+
try {
|
|
85
|
+
return readdirSync(directory)
|
|
86
|
+
.filter((file) => file.endsWith(".md") && file.toLowerCase() !== "readme.md")
|
|
87
|
+
.map((file) => basename(file, ".md"))
|
|
88
|
+
.filter((name) => NAME.test(name))
|
|
89
|
+
.sort()[0];
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
82
95
|
const guides = new WeakMap();
|
|
83
96
|
const rows = (pairs) => {
|
|
84
97
|
const width = pairs.reduce((most, [left]) => Math.max(most, left.length), 0) + 2;
|
|
@@ -110,13 +123,14 @@ const scenarioText = (cli, scenario, profile) => [
|
|
|
110
123
|
].join("\n");
|
|
111
124
|
/**
|
|
112
125
|
* `<tool> guide` lists the tool's scenarios and the ones it works with; `<tool> guide <name>` prints one: the goal,
|
|
113
|
-
* each step as a command and why, and what to look at. The files are
|
|
126
|
+
* each step as a command and why, and what to look at. The files are parsed when the command runs, never when the
|
|
114
127
|
* program is built. `checkGuide` validates them in the tool's CI.
|
|
115
128
|
*/
|
|
116
129
|
export const guideCommand = (app, options) => {
|
|
130
|
+
const first = firstScenarioName(options.directory);
|
|
117
131
|
const command = annotate(new Command("guide"), {
|
|
118
132
|
effects: NO_EFFECTS,
|
|
119
|
-
examples: [`${app.command} guide`, `${app.command} guide
|
|
133
|
+
examples: [`${app.command} guide`, ...(first ? [`${app.command} guide ${first} --json`] : [])],
|
|
120
134
|
})
|
|
121
135
|
.description("scenarios: the commands for a common task, in order, and why each")
|
|
122
136
|
.argument("[name]", "one scenario; without it, the list")
|
|
@@ -242,6 +256,18 @@ const ownerOf = (chain, flag) => {
|
|
|
242
256
|
}
|
|
243
257
|
return undefined;
|
|
244
258
|
};
|
|
259
|
+
const isCommandWord = (program, word) => word !== undefined && commandWords(program).has(word);
|
|
260
|
+
const isProfileWord = (word) => {
|
|
261
|
+
if (word === undefined || word.startsWith("-"))
|
|
262
|
+
return false;
|
|
263
|
+
try {
|
|
264
|
+
usableProfileName(word);
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
245
271
|
/** What is wrong with one step on this program: the path, each option and its value, the arguments. Never runs it. */
|
|
246
272
|
export const checkStep = (program, run) => {
|
|
247
273
|
const [tool, ...rest] = words(run);
|
|
@@ -249,7 +275,8 @@ export const checkStep = (program, run) => {
|
|
|
249
275
|
return [`starts with "${tool ?? ""}", not ${program.name()}`];
|
|
250
276
|
const chain = [program];
|
|
251
277
|
let at = program;
|
|
252
|
-
|
|
278
|
+
// The first word may be the profile, as the command line takes it: `tg personal chats list`.
|
|
279
|
+
let index = isCommandWord(program, rest[0]) || !isProfileWord(rest[0]) ? 0 : 1;
|
|
253
280
|
for (; index < rest.length; index++) {
|
|
254
281
|
const next = at.commands.find((one) => one.name() === rest[index] || one.aliases().includes(rest[index] ?? ""));
|
|
255
282
|
if (!next)
|
|
@@ -257,8 +284,9 @@ export const checkStep = (program, run) => {
|
|
|
257
284
|
chain.unshift(next);
|
|
258
285
|
at = next;
|
|
259
286
|
}
|
|
287
|
+
// A mistyped command reads like a profile word; when nothing follows that is a command, name the first word.
|
|
260
288
|
if (at === program)
|
|
261
|
-
return [`no command "${rest[0] ?? ""}"
|
|
289
|
+
return [`no command "${rest[0] ?? ""}"`];
|
|
262
290
|
const help = at._getHelpOption();
|
|
263
291
|
const beforeDashes = rest.slice(index, rest.includes("--") ? rest.indexOf("--") : undefined);
|
|
264
292
|
if (help && beforeDashes.some((word) => word === help.long || word === help.short))
|
|
@@ -2,7 +2,7 @@ import { metaOf } from "@wirecat/cli-base/commands";
|
|
|
2
2
|
import { writes } from "@wirecat/cli-base/standards";
|
|
3
3
|
import { commandPathOf, outputOf } from "../command-contract.js";
|
|
4
4
|
import { environmentOf } from "../context.js";
|
|
5
|
-
import {
|
|
5
|
+
import { takesProfile } from "../profile.js";
|
|
6
6
|
import { commandAt } from "../steps.js";
|
|
7
7
|
import { short } from "./commands-text.js";
|
|
8
8
|
/** Argument names onto the kinds `domain/references.ts` names; a plural takes the same kind. */
|
|
@@ -177,7 +177,7 @@ export const EXAMPLE_PROFILE = "personal";
|
|
|
177
177
|
const withProfile = (command, example) => {
|
|
178
178
|
const root = rootOf(command);
|
|
179
179
|
const top = commandPathOf(command)[0];
|
|
180
|
-
if (!profiled.has(root) || top === undefined ||
|
|
180
|
+
if (!profiled.has(root) || top === undefined || !takesProfile(command))
|
|
181
181
|
return example;
|
|
182
182
|
const prefix = `${root.name()} ${top}`;
|
|
183
183
|
return example === prefix || example.startsWith(`${prefix} `)
|
|
@@ -203,11 +203,16 @@ export const commandHelpText = (command) => {
|
|
|
203
203
|
...metaOf(command),
|
|
204
204
|
path: commandPathOf(command),
|
|
205
205
|
});
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
// "Available for this profile" says nothing where no profile is in play: a tool without profiles, or a command
|
|
207
|
+
// that reads none.
|
|
208
|
+
const profileMatters = profiled.has(rootOf(command)) && takesProfile(command);
|
|
209
|
+
const access = !availability
|
|
210
|
+
? []
|
|
211
|
+
: availability.available
|
|
212
|
+
? profileMatters
|
|
213
|
+
? rows("Availability:", ["available for this profile"])
|
|
214
|
+
: []
|
|
215
|
+
: rows("Availability:", [`unavailable — ${availability.reason}`]);
|
|
211
216
|
return [
|
|
212
217
|
"",
|
|
213
218
|
...access,
|
|
@@ -25,12 +25,12 @@ export const WIRECAT_TOOLS = [
|
|
|
25
25
|
{
|
|
26
26
|
command: "zm",
|
|
27
27
|
package: "@wirecat/zoom-cli",
|
|
28
|
-
does: "Zoom meetings: transcripts and summaries, searchable with your
|
|
28
|
+
does: "Zoom meetings: transcripts and summaries, searchable with your other WireCat data",
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
command: "drive",
|
|
32
32
|
package: "@wirecat/drive-cli",
|
|
33
|
-
does: "Google Drive and Docs as local text, searchable with your
|
|
33
|
+
does: "Google Drive and Docs as local text, searchable with your other WireCat data",
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
command: "memo",
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export { sharedCommandTemplate } from "./contract-templates.js";
|
|
|
60
60
|
export { type Closeable, withDeadline } from "./deadline.js";
|
|
61
61
|
export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
62
62
|
export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
|
|
63
|
-
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, } from "./effects.js";
|
|
63
|
+
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, previewFlag, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, storeChangeRefusal, } from "./effects.js";
|
|
64
64
|
export { type CliEntry, type EntryContext, type EntryIo, startCli } from "./entry.js";
|
|
65
65
|
export { type AnswerOutput, answerSchema, type Body, type ItemBody, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, type ListBody, type ListInput, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
66
66
|
export { withAbort } from "./execution.js";
|
|
@@ -100,7 +100,7 @@ export { type Saving, stored } from "./messenger/stored.js";
|
|
|
100
100
|
export { MODEL_CATALOGUE_QUERY, type ModelCatalogueOptions } from "./model-catalogue-command.js";
|
|
101
101
|
export { window, withPaging } from "./paging.js";
|
|
102
102
|
export { knownBeside, knownPermissionKeys, type PermissionKeyOf, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
|
|
103
|
-
export { asFirstWord, commandWords, liftProfile, refuseCommandName, rootOf, usableProfileName } from "./profile.js";
|
|
103
|
+
export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
|
|
104
104
|
export { createProgram, type ProgramDefinition, type ProgramOptions, type RunOptions, run } from "./program.js";
|
|
105
105
|
export { fieldsOf, projectFields } from "./result-fields.js";
|
|
106
106
|
export { type Config, type Configuration, count, first, flag, fromFile, type GlobalFlags, NONE_NAMED, namedProfile, type ProfileKind, plain, type ResolveOptions, type Settings, type SettingsExtension, type SettingsExtensionContext, type Source, settingsFor, } from "./settings.js";
|
|
@@ -109,5 +109,6 @@ export { AGENTS, type Agent, agentFor, screenFor } from "./setup-screen.js";
|
|
|
109
109
|
export { accountCommand, addMeetingBatchCommand, addMeetingPreparationCommand, addMeetingProjectCandidatesCommand, addMeetingSeriesInventoryCommand, attachmentsCommand, botAdminsCommand, botCommand, botMcpCommand, botMembersCommand, botMessagesCommand, callsCommand, chatsCommand, commandsCommand, completeCommand, configCommand, contactsCommand, conversationsCommand, deleteCommand, doctorCommand, editCommand, floodCommand, forwardCommand, generatedApiCommand, guideCommand, imagesCommand, inboxCommand, markReadCommand, mcpCommand, mentionsCommand, messagesCommand, metadataCommand, modelCatalogueCommand, modelsCommand, pinCommand, pollsCommand, reactionsCommand, recipientsCommand, repliesCommand, reviewCommand, runsCommand, scheduleCommand, searchCommand, searchesCommand, sendCommand, sendsCommand, serveCommand, serverCommand, skillCommand, starCommands, statsCommand, stickersCommand, storeCommand, tagsCommand, tasksCommand, tasksStatsCommand, topicsCommand, unpinCommand, upgradeCommand, watchCommand, } from "./shared-command-exports.js";
|
|
110
110
|
export { assertSharedContracts, customizeSharedCommand, hasSharedContract, overrideSharedContract, type SharedContractOverride, sharedContractOverrides, sharedContracts, } from "./shared-contracts.js";
|
|
111
111
|
export { runnable, withSafety } from "./steps.js";
|
|
112
|
+
export { type StoreInfo, sharedStoreCommand, storeInfo, storeInfoCommand } from "./store-info.js";
|
|
112
113
|
export { changeStoreSetting, isStoreSetting, STORE_SETTINGS, storeSettings } from "./store-settings.js";
|
|
113
114
|
export { restartServers, type UpgradeContext } from "./upgrade-command.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -53,7 +53,7 @@ export { sharedCommandTemplate } from "./contract-templates.js";
|
|
|
53
53
|
export { withDeadline } from "./deadline.js";
|
|
54
54
|
export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
55
55
|
export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
|
|
56
|
-
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, } from "./effects.js";
|
|
56
|
+
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, previewFlag, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, storeChangeRefusal, } from "./effects.js";
|
|
57
57
|
export { startCli } from "./entry.js";
|
|
58
58
|
export { answerSchema, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
59
59
|
export { withAbort } from "./execution.js";
|
|
@@ -91,7 +91,7 @@ export { stored } from "./messenger/stored.js";
|
|
|
91
91
|
export { MODEL_CATALOGUE_QUERY } from "./model-catalogue-command.js";
|
|
92
92
|
export { window, withPaging } from "./paging.js";
|
|
93
93
|
export { knownBeside, knownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
|
|
94
|
-
export { asFirstWord, commandWords, liftProfile, refuseCommandName, rootOf, usableProfileName } from "./profile.js";
|
|
94
|
+
export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
|
|
95
95
|
export { createProgram, run } from "./program.js";
|
|
96
96
|
export { fieldsOf, projectFields } from "./result-fields.js";
|
|
97
97
|
export { count, first, flag, fromFile, NONE_NAMED, namedProfile, plain, settingsFor, } from "./settings.js";
|
|
@@ -100,5 +100,6 @@ export { AGENTS, agentFor, screenFor } from "./setup-screen.js";
|
|
|
100
100
|
export { accountCommand, addMeetingBatchCommand, addMeetingPreparationCommand, addMeetingProjectCandidatesCommand, addMeetingSeriesInventoryCommand, attachmentsCommand, botAdminsCommand, botCommand, botMcpCommand, botMembersCommand, botMessagesCommand, callsCommand, chatsCommand, commandsCommand, completeCommand, configCommand, contactsCommand, conversationsCommand, deleteCommand, doctorCommand, editCommand, floodCommand, forwardCommand, generatedApiCommand, guideCommand, imagesCommand, inboxCommand, markReadCommand, mcpCommand, mentionsCommand, messagesCommand, metadataCommand, modelCatalogueCommand, modelsCommand, pinCommand, pollsCommand, reactionsCommand, recipientsCommand, repliesCommand, reviewCommand, runsCommand, scheduleCommand, searchCommand, searchesCommand, sendCommand, sendsCommand, serveCommand, serverCommand, skillCommand, starCommands, statsCommand, stickersCommand, storeCommand, tagsCommand, tasksCommand, tasksStatsCommand, topicsCommand, unpinCommand, upgradeCommand, watchCommand, } from "./shared-command-exports.js";
|
|
101
101
|
export { assertSharedContracts, customizeSharedCommand, hasSharedContract, overrideSharedContract, sharedContractOverrides, sharedContracts, } from "./shared-contracts.js";
|
|
102
102
|
export { runnable, withSafety } from "./steps.js";
|
|
103
|
+
export { sharedStoreCommand, storeInfo, storeInfoCommand } from "./store-info.js";
|
|
103
104
|
export { changeStoreSetting, isStoreSetting, STORE_SETTINGS, storeSettings } from "./store-settings.js";
|
|
104
105
|
export { restartServers } from "./upgrade-command.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
3
|
-
import { REMOTE_READ } from "./effects.js";
|
|
3
|
+
import { previewFlag, REMOTE_READ } from "./effects.js";
|
|
4
4
|
import { communicationOptions } from "./meeting-communications.js";
|
|
5
5
|
import { readMeetingPreparation } from "./meeting-preparation-read.js";
|
|
6
6
|
const integer = (value) => {
|
|
@@ -48,6 +48,6 @@ export const addMeetingPreparationCommand = (program, deps) => {
|
|
|
48
48
|
origin: "handwritten",
|
|
49
49
|
effects: REMOTE_READ,
|
|
50
50
|
output: { entity: "meeting_preparation" },
|
|
51
|
-
examples: [`${program.name()} meetings prepare 123 --json`],
|
|
51
|
+
examples: [`${program.name()} meetings prepare 123${previewFlag(program)} --json`],
|
|
52
52
|
});
|
|
53
53
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
3
|
-
import { REMOTE_READ } from "./effects.js";
|
|
3
|
+
import { previewFlag, REMOTE_READ } from "./effects.js";
|
|
4
4
|
import { readMeetingSeriesInventory } from "./meeting-series-inventory-read.js";
|
|
5
5
|
const integer = (value) => {
|
|
6
6
|
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value)))
|
|
@@ -32,6 +32,6 @@ export const addMeetingSeriesInventoryCommand = (program, deps) => {
|
|
|
32
32
|
effects: REMOTE_READ,
|
|
33
33
|
offline: false,
|
|
34
34
|
output: { entity: "meeting_series_inventory" },
|
|
35
|
-
examples: [`${program.name()} meetings scheduled --json`],
|
|
35
|
+
examples: [`${program.name()} meetings scheduled${previewFlag(program)} --json`],
|
|
36
36
|
});
|
|
37
37
|
};
|
|
@@ -165,9 +165,10 @@ export const modelsCommand = (messenger) => {
|
|
|
165
165
|
reachesOtherService(annotate(models.command("download"), {
|
|
166
166
|
effects: FETCH,
|
|
167
167
|
output: { entity: "model" },
|
|
168
|
-
examples: [`${tool} models download
|
|
168
|
+
examples: [`${tool} models download ${DEFAULT_TEXT_MODEL}`],
|
|
169
169
|
}))
|
|
170
|
-
.
|
|
170
|
+
.summary("download and verify one speech or embedding model")
|
|
171
|
+
.description("download and verify one speech or embedding model: tens to hundreds of MB, each size in models list")
|
|
171
172
|
.argument("<model>", "a model id from models list")
|
|
172
173
|
.option("--accept-terms", "accept the model's licence terms, for a model that has its own")
|
|
173
174
|
.action(async function (id, options) {
|
|
@@ -3,9 +3,9 @@ import type { CacheDatabase } from "../../store/driver.js";
|
|
|
3
3
|
import type { Messenger } from "./context.js";
|
|
4
4
|
/** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
|
|
5
5
|
export declare const storeSummary: (env: NodeJS.ProcessEnv) => Promise<{
|
|
6
|
-
error?: undefined;
|
|
7
6
|
path: string;
|
|
8
7
|
exists: boolean;
|
|
8
|
+
error?: undefined;
|
|
9
9
|
} | {
|
|
10
10
|
path: string;
|
|
11
11
|
exists: boolean;
|
|
@@ -7,31 +7,27 @@ import { holdersOf } from "../../background/processes.js";
|
|
|
7
7
|
import { RULES_VERSION } from "../../conversations/link.js";
|
|
8
8
|
import { syncsOneChat } from "../../ports/traits.js";
|
|
9
9
|
import { isSealed, sealFile, unsealFile } from "../../sealed.js";
|
|
10
|
-
import {
|
|
10
|
+
import { fileSchemaOf, SPEAKS, writableBy } from "../../store/compatibility.js";
|
|
11
|
+
import { migrate } from "../../store/migrations.js";
|
|
11
12
|
import { openCache } from "../../store/open.js";
|
|
12
13
|
import { storePath } from "../../store/path.js";
|
|
13
14
|
import { deleteCopy, repairStore } from "../../store/repair.js";
|
|
14
|
-
import { drainNoteIndex, fillSearchIndex, fillStems, involvementStoreOver, noteIndexState,
|
|
15
|
+
import { drainNoteIndex, fillSearchIndex, fillStems, involvementStoreOver, noteIndexState, openStore, orphanPointers, pendingNormalization, resetAttachmentWords, resetNoteIndex, resetSearchIndex, resetStems, searchIndexState, stemmerCache, stemsState, } from "../../store/store.js";
|
|
15
16
|
import { environmentOf, outputFor } from "../context.js";
|
|
16
17
|
import { destroysNothing, LOCAL_READ, LOCAL_WRITE } from "../effects.js";
|
|
17
18
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
18
19
|
import { lineOf, nextStep } from "../steps.js";
|
|
20
|
+
import { storeInfoCommand } from "../store-info.js";
|
|
19
21
|
import { answerOf } from "./ask.js";
|
|
20
22
|
import { ENCRYPT_OPTION, passwordOf } from "./password.js";
|
|
21
23
|
import { servingProfiles } from "./serve-command.js";
|
|
22
|
-
|
|
24
|
+
import { storeTablesCommand } from "./store-tables-command.js";
|
|
23
25
|
const SEARCH_INDEXES = ["messages_fts", "chats_fts", "identities_fts"];
|
|
24
26
|
/** Contentless: it has no table to be compared with, only its own structure to check. */
|
|
25
27
|
const WORD_INDEX = "message_words";
|
|
26
|
-
/** An empty file is normal: `openStore` creates it before the first migration runs. */
|
|
27
28
|
const schemaOf = (database) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
? database.prepare("SELECT version, min_compatible FROM schema_migrations ORDER BY version DESC LIMIT 1").get()
|
|
31
|
-
: undefined;
|
|
32
|
-
const version = Number(row?.version ?? 0);
|
|
33
|
-
const minCompatible = Number(row?.min_compatible ?? 0);
|
|
34
|
-
return { version, minCompatible, writable: version <= SPEAKS || minCompatible <= SPEAKS };
|
|
29
|
+
const file = fileSchemaOf(database);
|
|
30
|
+
return { ...file, writable: writableBy(file) };
|
|
35
31
|
};
|
|
36
32
|
const count = (database, table) => Number(database.prepare(`SELECT count(*) AS n FROM ${table}`).get()?.n ?? 0);
|
|
37
33
|
/** Before version 6 the column does not exist, and a file behind this build is not migrated here. */
|
|
@@ -49,16 +45,6 @@ const reading = async (path, read) => {
|
|
|
49
45
|
database.close();
|
|
50
46
|
}
|
|
51
47
|
};
|
|
52
|
-
/** For a command that only reads: the connection refuses writes, and opening sets no journal mode. */
|
|
53
|
-
const readingOnly = async (path, read) => {
|
|
54
|
-
const { database } = await openSqlite(path, { readOnly: true });
|
|
55
|
-
try {
|
|
56
|
-
return read(database);
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
database.close();
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
48
|
/** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
|
|
63
49
|
export const storeSummary = async (env) => {
|
|
64
50
|
const path = storePath(env);
|
|
@@ -84,7 +70,7 @@ export const storeSummary = async (env) => {
|
|
|
84
70
|
/** A maintenance answer: one item, snake_case, its hints as steps where a registered one fits. */
|
|
85
71
|
const result = (command, value, next = []) => renderAnswer(command, outputFor(command), itemOf(snakeKeys(value), { next }));
|
|
86
72
|
export const storeMaintenanceCommands = (messenger) => [
|
|
87
|
-
|
|
73
|
+
storeInfoCommand(),
|
|
88
74
|
checkCommand(messenger),
|
|
89
75
|
migrateCommand(messenger),
|
|
90
76
|
reindexCommand(messenger),
|
|
@@ -94,6 +80,7 @@ export const storeMaintenanceCommands = (messenger) => [
|
|
|
94
80
|
repairCommand(messenger),
|
|
95
81
|
resetCommand(messenger),
|
|
96
82
|
copiesCommand(),
|
|
83
|
+
storeTablesCommand(messenger),
|
|
97
84
|
];
|
|
98
85
|
/** Renaming tables under a running `serve` would pull them from under its statements. */
|
|
99
86
|
const refuseWhileServing = (command, messenger) => {
|
|
@@ -162,31 +149,6 @@ const copiesCommand = () => new Command("copies").description("the tables `store
|
|
|
162
149
|
const deleted = await reading(path, (database) => deleteCopy(database, name));
|
|
163
150
|
result(this, { path, deleted });
|
|
164
151
|
}));
|
|
165
|
-
const infoCommand = () => annotate(new Command("info"), { output: { entity: "store_info" }, effects: LOCAL_READ })
|
|
166
|
-
.summary("the local archive's format, size and contents")
|
|
167
|
-
.description("the store file: where it is, its size, its schema and how many rows it holds, by provider, chat kind, account, year (UTC) and attachment kind, over every account; changes nothing")
|
|
168
|
-
.action(async function () {
|
|
169
|
-
const path = storePath(environmentOf(this).env ?? process.env);
|
|
170
|
-
if (!existsSync(path)) {
|
|
171
|
-
result(this, { path, exists: false });
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const read = readingOnly(path, (database) => {
|
|
175
|
-
const { version, minCompatible, writable } = schemaOf(database);
|
|
176
|
-
return {
|
|
177
|
-
path,
|
|
178
|
-
exists: true,
|
|
179
|
-
bytes: { file: bytesOf(path), wal: bytesOf(`${path}-wal`) },
|
|
180
|
-
schema: { version, minCompatible, speaks: SPEAKS, writable },
|
|
181
|
-
rows: version > 0 ? rowCounts(database) : {},
|
|
182
|
-
pendingNormalization: pendingIfKnown(database),
|
|
183
|
-
wordIndex: searchIndexState(database) ?? null,
|
|
184
|
-
stemIndex: stemsState(database) ?? null,
|
|
185
|
-
...(version > 0 ? storeContents(database) : {}),
|
|
186
|
-
};
|
|
187
|
-
});
|
|
188
|
-
result(this, await read.catch((error) => ({ path, exists: true, opens: false, error: messageOf(error) })));
|
|
189
|
-
});
|
|
190
152
|
/**
|
|
191
153
|
* Reports and never repairs. The per-chat completeness is what tells "nothing was said" from "not
|
|
192
154
|
* fetched" (NEED-399 A). Times are the chat's newest message by the messenger's list against the
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type QueryRegistry } from "@wirecat/cli-base/query";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import type { Messenger } from "./context.js";
|
|
4
|
+
export declare const STORE_TABLES_QUERY: QueryRegistry;
|
|
5
|
+
export declare const storeTablesCommand: (messenger: Messenger) => Command;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { CliError } from "@wirecat/cli-base";
|
|
3
|
+
import { annotate } from "@wirecat/cli-base/commands";
|
|
4
|
+
import { queryFieldsOf } from "@wirecat/cli-base/query";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { storeTable } from "../../entities/store-table.js";
|
|
7
|
+
import { storePath } from "../../store/path.js";
|
|
8
|
+
import { readStoreTables } from "../../store/store.js";
|
|
9
|
+
import { environmentOf, outputFor } from "../context.js";
|
|
10
|
+
import { LOCAL_READ } from "../effects.js";
|
|
11
|
+
import { listOf, renderAnswer } from "../envelope.js";
|
|
12
|
+
import { queryTerms, typedQuery, withQuery } from "../list-query.js";
|
|
13
|
+
const PATH = "store tables list";
|
|
14
|
+
export const STORE_TABLES_QUERY = {
|
|
15
|
+
entity: "store_table",
|
|
16
|
+
backend: "native",
|
|
17
|
+
fields: queryFieldsOf(storeTable, {
|
|
18
|
+
native: ["name", "kind"],
|
|
19
|
+
examples: { name: "name:messages", kind: "kind:data" },
|
|
20
|
+
}).map((field) => field.native
|
|
21
|
+
? { ...field, operators: field.operators.filter((operator) => operator === "term" || operator === "phrase") }
|
|
22
|
+
: field),
|
|
23
|
+
};
|
|
24
|
+
const filterOf = (words) => {
|
|
25
|
+
const terms = queryTerms(words, STORE_TABLES_QUERY, PATH);
|
|
26
|
+
const name = terms.get("name");
|
|
27
|
+
const kind = terms.get("kind");
|
|
28
|
+
return { ...(name === undefined ? {} : { name }), ...(kind === undefined ? {} : { kind }) };
|
|
29
|
+
};
|
|
30
|
+
export const storeTablesCommand = (messenger) => {
|
|
31
|
+
const tables = new Command("tables").description("the tables of the local archive");
|
|
32
|
+
withQuery(annotate(tables.command("list"), {
|
|
33
|
+
effects: LOCAL_READ,
|
|
34
|
+
examples: [
|
|
35
|
+
`${messenger.app.command} store tables list`,
|
|
36
|
+
`${messenger.app.command} store tables list kind:data`,
|
|
37
|
+
`${messenger.app.command} store tables list --full --json`,
|
|
38
|
+
],
|
|
39
|
+
}), STORE_TABLES_QUERY)
|
|
40
|
+
.summary("every table of the local archive, with its kind and row count")
|
|
41
|
+
.description("every table of the local archive: its kind (data, search index, part of a search index, internal) and its " +
|
|
42
|
+
"rows, a search index's rows not counted; --full adds each table's size on disk, which reads the whole file " +
|
|
43
|
+
"(about ten seconds for 3 GB); changes nothing")
|
|
44
|
+
.action(async function (words) {
|
|
45
|
+
const filter = filterOf(words);
|
|
46
|
+
const { full } = this.optsWithGlobals();
|
|
47
|
+
const path = storePath(environmentOf(this).env ?? process.env);
|
|
48
|
+
if (!existsSync(path))
|
|
49
|
+
throw new CliError("not_found", `no store at ${path}`);
|
|
50
|
+
const items = await readStoreTables(path, { filter, sizes: full === true });
|
|
51
|
+
renderAnswer(this, outputFor(this), listOf(PATH, {
|
|
52
|
+
items,
|
|
53
|
+
limit: Math.max(1, items.length),
|
|
54
|
+
has_more: false,
|
|
55
|
+
args: words.length ? { query: typedQuery(words) } : {},
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
return tables;
|
|
59
|
+
};
|
package/dist/cli/profile.d.ts
CHANGED
|
@@ -4,6 +4,13 @@ export declare const asFirstWord: (profile: string) => string;
|
|
|
4
4
|
export declare const usableProfileName: (profile: string) => string;
|
|
5
5
|
/** The top-level commands that never read a profile; every other one is `<tool> <profile> …`. */
|
|
6
6
|
export declare const PROFILE_FREE: ReadonlySet<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Marks a command, and every command under it, as one that reads no profile, so help shows it without one:
|
|
9
|
+
* `drive doctor`, not `drive personal doctor`. Which commands those are is the CLI's to say.
|
|
10
|
+
*/
|
|
11
|
+
export declare const profileFree: <T extends Command>(command: T) => T;
|
|
12
|
+
/** Whether the command line puts a profile before this command: not for `PROFILE_FREE` or a `profileFree` one. */
|
|
13
|
+
export declare const takesProfile: (command: Command) => boolean;
|
|
7
14
|
/**
|
|
8
15
|
* Every word that means a command at the top level. Taken off the built program rather than
|
|
9
16
|
* written out, because a list written out goes stale the day a command is added — and `help` is in
|
package/dist/cli/profile.js
CHANGED
|
@@ -16,6 +16,25 @@ export const usableProfileName = (profile) => {
|
|
|
16
16
|
};
|
|
17
17
|
/** The top-level commands that never read a profile; every other one is `<tool> <profile> …`. */
|
|
18
18
|
export const PROFILE_FREE = new Set(["commands", "complete", "guide", "help", "skill", "upgrade"]);
|
|
19
|
+
const freeOfProfile = new WeakSet();
|
|
20
|
+
/**
|
|
21
|
+
* Marks a command, and every command under it, as one that reads no profile, so help shows it without one:
|
|
22
|
+
* `drive doctor`, not `drive personal doctor`. Which commands those are is the CLI's to say.
|
|
23
|
+
*/
|
|
24
|
+
export const profileFree = (command) => {
|
|
25
|
+
freeOfProfile.add(command);
|
|
26
|
+
return command;
|
|
27
|
+
};
|
|
28
|
+
/** Whether the command line puts a profile before this command: not for `PROFILE_FREE` or a `profileFree` one. */
|
|
29
|
+
export const takesProfile = (command) => {
|
|
30
|
+
let top;
|
|
31
|
+
for (let at = command; at?.parent; at = at.parent) {
|
|
32
|
+
if (freeOfProfile.has(at))
|
|
33
|
+
return false;
|
|
34
|
+
top = at;
|
|
35
|
+
}
|
|
36
|
+
return top !== undefined && !PROFILE_FREE.has(top.name());
|
|
37
|
+
};
|
|
19
38
|
/**
|
|
20
39
|
* Every word that means a command at the top level. Taken off the built program rather than
|
|
21
40
|
* written out, because a list written out goes stale the day a command is added — and `help` is in
|
package/dist/cli/program.js
CHANGED
|
@@ -18,7 +18,7 @@ import { commandAvailability } from "./command-availability.js";
|
|
|
18
18
|
import { commandPathOf } from "./command-contract.js";
|
|
19
19
|
import { outputFor, provide } from "./context.js";
|
|
20
20
|
import { refuseWriteBeforeConnecting } from "./early-refusal.js";
|
|
21
|
-
import { changesAuthorityAt, changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTargets,
|
|
21
|
+
import { changesAuthorityAt, changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTargets, storeChangeRefusal, } from "./effects.js";
|
|
22
22
|
import { itemOf, renderAnswer, snakeKeys } from "./envelope.js";
|
|
23
23
|
import { byteCount, DEFAULT_COMMAND_MS, DEFAULT_OUTPUT_BYTES, execution } from "./execution.js";
|
|
24
24
|
import { setupAtStart } from "./first-run.js";
|
|
@@ -27,7 +27,7 @@ import { showInputFields } from "./input-fields.js";
|
|
|
27
27
|
import { MAX_BUFFERED_INPUT, provideInputPolicy } from "./input-policy.js";
|
|
28
28
|
import { checkJson, liftJson } from "./json-fields.js";
|
|
29
29
|
import { PreviewComplete, preview } from "./preview.js";
|
|
30
|
-
import { commandWords, liftProfile,
|
|
30
|
+
import { commandWords, liftProfile, sampleCommand, takesProfile, valuedOptions } from "./profile.js";
|
|
31
31
|
import { absolutePaths, relayTarget } from "./relay-route.js";
|
|
32
32
|
import { NoProfileHere, namedProfile, settingsFor, } from "./settings.js";
|
|
33
33
|
import { assertSharedContracts } from "./shared-contracts.js";
|
|
@@ -74,14 +74,15 @@ export const createProgram = ({ app, commands, configure, firstScreen }, { out,
|
|
|
74
74
|
assertSharedContracts(program);
|
|
75
75
|
installHelp(program, { pitch: app.description, ...firstScreen });
|
|
76
76
|
// Commander builds a usage line from the command tree, where the profile is not a word.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
if (firstScreen?.profile !== false)
|
|
78
|
+
for (const top of program.commands)
|
|
79
|
+
forEachCommand(top, (command) => command.configureHelp({
|
|
80
|
+
...command.configureHelp(),
|
|
81
|
+
commandUsage: (shown) => {
|
|
82
|
+
const usage = new Help().commandUsage(shown);
|
|
83
|
+
return takesProfile(shown) ? `${app.command} <profile>${usage.slice(app.command.length)}` : usage;
|
|
84
|
+
},
|
|
85
|
+
}));
|
|
85
86
|
// Depth-first: Commander does not pass `configureOutput` down to a command added with
|
|
86
87
|
// `addCommand`, so `tg messages --help` would write to the real terminal.
|
|
87
88
|
if (out || err) {
|
|
@@ -284,7 +285,7 @@ const runOnce = async (argv, definition, options) => {
|
|
|
284
285
|
if (lock && isLogin(action) && !flags.dryRun)
|
|
285
286
|
throw readOnlyRefusal(lock, "log in or out");
|
|
286
287
|
if (lock && isStoreChange(action) && !flags.dryRun)
|
|
287
|
-
throw readOnlyRefusal(lock,
|
|
288
|
+
throw readOnlyRefusal(lock, storeChangeRefusal(definition.app));
|
|
288
289
|
refuseWriteBeforeConnecting(action, {
|
|
289
290
|
app: definition.app,
|
|
290
291
|
env: commandEnv,
|