@wirecat/cli-core 0.467.0 → 0.469.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 +38 -1
- package/dist/cli/bot/mcp.js +2 -1
- package/dist/cli/config-command.d.ts +4 -0
- package/dist/cli/config-command.js +5 -5
- package/dist/cli/effects.d.ts +9 -0
- package/dist/cli/effects.js +27 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/program.js +5 -2
- package/dist/cli/settings.d.ts +5 -0
- package/dist/cli/settings.js +12 -1
- package/dist/cli/skill-command.js +11 -3
- package/dist/cli/steps.d.ts +9 -0
- package/dist/cli/steps.js +16 -0
- package/dist/cli/store-settings.d.ts +6 -0
- package/dist/cli/store-settings.js +6 -0
- package/dist/mcp/agent.d.ts +2 -0
- package/dist/mcp/agent.js +1 -1
- package/dist/mcp/bot/server.d.ts +2 -0
- package/dist/mcp/bot/server.js +1 -0
- package/dist/mcp/server.js +2 -1
- package/dist/services/embeddings-maintenance.js +4 -6
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,44 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
|
|
|
5
5
|
minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
|
|
6
6
|
shape of this file.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 0.469.0 — 27.10.2026
|
|
9
|
+
|
|
10
|
+
### Changed — may break callers
|
|
11
|
+
|
|
12
|
+
- **cli-core needs `@wirecat/cli-base` 0.65.** The peer range moves from `>=0.64.0 <0.65.0` to `>=0.65.0 <0.66.0`. A
|
|
13
|
+
CLI that pins cli-base moves its pin with this release. cli-base 0.65 puts `serve` and `revoke` in the command
|
|
14
|
+
vocabulary, so `cli-dev lint` fails on a baseline entry that names them: remove `mcp serve`, `bot mcp serve`,
|
|
15
|
+
`mcp revoke` and `replies consents revoke` from a CLI's `lint/baseline/unknown-verb.json` (cli-core's own are gone).
|
|
16
|
+
Its `cli-dev audit` compares gcal only on the commands it already shares, and its handbook changes the rules on
|
|
17
|
+
deadlines, MCP profiles and what counts as a write; nothing cli-core calls changed.
|
|
18
|
+
|
|
19
|
+
## 0.468.0 — 27.10.2026
|
|
20
|
+
|
|
21
|
+
### Changed — may break callers
|
|
22
|
+
|
|
23
|
+
- **An MCP server offers a command that only fetches into the store through its read tool.** A command that reads
|
|
24
|
+
the provider and only keeps what it read in the owner's store (`effects: { remote: "read", local: "write" }`,
|
|
25
|
+
`FETCH`) — `contacts sync`, `metadata sync`, `chats members fetch`, a calendar's `sync` and `calendars list` — is
|
|
26
|
+
now `writes: false` in the catalogue and runs with `<cli>_read`; `<cli>_write` refuses it and names `_read` (owner
|
|
27
|
+
ruling, 2026-10-27; WC-CMD-17). These stay writes: a local edit (`changesStore`, `tags rebuild`), an authority
|
|
28
|
+
change (`grantsAuthority`, `recipients add`), a call to another service (`reachesOtherService`), a command that
|
|
29
|
+
saves files (named `download`, or with a folder to write into: `messages download`, `models download`,
|
|
30
|
+
`attachments extract --output-dir`), and anything that changes the provider. Permissions do not change: a
|
|
31
|
+
`readonly` level still refuses a fetch. New `fetchesOnly(command)` and `fetchesOnlyAt(root)`; `toolsFromCommands`
|
|
32
|
+
takes `fetchOnly`, and without it every write stays one (a CLI with its own server passes
|
|
33
|
+
`fetchOnly: fetchesOnlyAt(program)`). A CLI's `contracts/mcp-catalogue.json` changes where it has such commands.
|
|
34
|
+
- **A CLI that is not a messenger (`AppIdentity.messenger: false`) no longer offers messenger settings.** `config
|
|
35
|
+
set|unset` lists and takes neither `sends_per_hour`, `sender_colors`, `transcribe_with`, `search_catch_up` and
|
|
36
|
+
`read_other_bots` (`settingsFor(...).allSettings` leaves them out; a file that has one is still read) nor the store
|
|
37
|
+
settings of messages — `phone_countries`, `task_*` and `account_groups.<name>` — which it would have changed for
|
|
38
|
+
tg and MAX. `search_stemmers.*` stays. `config show` leaves the same profile settings out.
|
|
39
|
+
- **A model that is not downloaded names its download as the error's `next` step, only in a CLI that has the
|
|
40
|
+
command.** The `model.not-downloaded` error of a text model said `` `<cli> models download <id>` `` in its message,
|
|
41
|
+
also in a CLI without `models` (gcal). Its message is now `<id> is not downloaded on this machine`; the step
|
|
42
|
+
(`models.download`, or `models.download-image` for a picture model) comes as `error.next` with its `line`, the
|
|
43
|
+
profile in it, and only where the program has the command. Any error's `next` is now dropped when it names a
|
|
44
|
+
command the program lacks.
|
|
45
|
+
- **`skill show` offers `follow-ups` and `link-conversations` only in a messenger CLI.** Both work on chats.
|
|
9
46
|
|
|
10
47
|
## 0.467.0 — 27.10.2026
|
|
11
48
|
|
package/dist/cli/bot/mcp.js
CHANGED
|
@@ -3,7 +3,7 @@ import { CliError } from "@wirecat/cli-base";
|
|
|
3
3
|
import { annotate, describeProgram } from "@wirecat/cli-base/commands";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { environmentOf } from "../context.js";
|
|
6
|
-
import { changesAuthorityAt, LOCAL_READ, REMOTE_WRITE } from "../effects.js";
|
|
6
|
+
import { changesAuthorityAt, fetchesOnlyAt, LOCAL_READ, REMOTE_WRITE } from "../effects.js";
|
|
7
7
|
import { itemOf, renderAnswer } from "../envelope.js";
|
|
8
8
|
import { serverEntry } from "../messenger/mcp-command.js";
|
|
9
9
|
import { nextStep, rootOf } from "../steps.js";
|
|
@@ -37,6 +37,7 @@ export const botMcpCommand = (bot) => {
|
|
|
37
37
|
bot,
|
|
38
38
|
program: { run, commands: describeProgram(rootOf(this)) },
|
|
39
39
|
authority: changesAuthorityAt(rootOf(this)),
|
|
40
|
+
fetchOnly: fetchesOnlyAt(rootOf(this)),
|
|
40
41
|
settings: context.settings,
|
|
41
42
|
levels: () => bot.resolveSettings(this.optsWithGlobals(), { env: context.env, kind: "bot" }).permissions,
|
|
42
43
|
env: context.env,
|
|
@@ -2,6 +2,10 @@ import { Command } from "commander";
|
|
|
2
2
|
import { type AppIdentity } from "../ports/app.js";
|
|
3
3
|
import { type PermissionKeySource } from "./permission-keys.js";
|
|
4
4
|
import { type Configuration } from "./settings.js";
|
|
5
|
+
/**
|
|
6
|
+
* The settings in force and where each came from, and changing them. Printed whole: no field the
|
|
7
|
+
* configuration accepts can hold a secret — the schema has nowhere to put one.
|
|
8
|
+
*/
|
|
5
9
|
export declare const configCommand: (app: AppIdentity, config: Configuration, { permissionKey }?: {
|
|
6
10
|
permissionKey?: PermissionKeySource | undefined;
|
|
7
11
|
}) => Command;
|
|
@@ -9,13 +9,11 @@ import { itemOf, renderAnswer } from "./envelope.js";
|
|
|
9
9
|
import { knownBeside, knownPermissionKeys, unknownPermissionIssues, } from "./permission-keys.js";
|
|
10
10
|
import { fromFile, NONE_NAMED } from "./settings.js";
|
|
11
11
|
import { nextStep } from "./steps.js";
|
|
12
|
-
import { changeStoreSetting, isStoreSetting,
|
|
12
|
+
import { changeStoreSetting, isStoreSetting, offersStoreSetting, storeSettings, storeSettingsOf, } from "./store-settings.js";
|
|
13
13
|
/**
|
|
14
14
|
* The settings in force and where each came from, and changing them. Printed whole: no field the
|
|
15
15
|
* configuration accepts can hold a secret — the schema has nowhere to put one.
|
|
16
16
|
*/
|
|
17
|
-
/** `sender_colors` colours who wrote a message; a CLI without messages has nothing to colour. */
|
|
18
|
-
const shownSettings = (app, config) => app.messenger === false ? config.allSettings.filter((setting) => setting !== "sender_colors") : config.allSettings;
|
|
19
17
|
export const configCommand = (app, config, { permissionKey = config.permissions } = {}) => {
|
|
20
18
|
const command = new Command("config").description("the settings in force, and where each one came from");
|
|
21
19
|
annotate(command.command("show"), { effects: LOCAL_READ, examples: [`${app.command} personal config show`] })
|
|
@@ -38,7 +36,7 @@ export const configCommand = (app, config, { permissionKey = config.permissions
|
|
|
38
36
|
config_file: settings.configPath,
|
|
39
37
|
config_found: settings.configFound,
|
|
40
38
|
paths_overridden: overridden,
|
|
41
|
-
settings: [...
|
|
39
|
+
settings: [...config.allSettings, "command_timeout_ms"]
|
|
42
40
|
.filter((setting) => bot || setting !== "read_other_bots")
|
|
43
41
|
.map((setting) => sourced(settings, setting)),
|
|
44
42
|
store_settings: await storeSettings(env),
|
|
@@ -58,7 +56,7 @@ export const configCommand = (app, config, { permissionKey = config.permissions
|
|
|
58
56
|
effects: LOCAL_WRITE,
|
|
59
57
|
examples: [`${app.command} personal config ${action} limit${action === "set" ? " 20" : ""}`],
|
|
60
58
|
}))
|
|
61
|
-
.argument("<setting>", `one of: ${[...
|
|
59
|
+
.argument("<setting>", `one of: ${[...config.allSettings, ...storeSettingsOf(app)].join(", ")}`)
|
|
62
60
|
.option("--defaults", "change what every profile gets, rather than this profile")
|
|
63
61
|
.option("--personal", "only for personal accounts — the personal section of the file")
|
|
64
62
|
.option("--bot", "only for bots — the bot section of the file");
|
|
@@ -72,6 +70,8 @@ export const configCommand = (app, config, { permissionKey = config.permissions
|
|
|
72
70
|
const { defaults: everyone, personal, bot, } = this.opts();
|
|
73
71
|
const context = baseContext(this, (flags, options) => config.resolveSettings(flags, { ...options, needsProfile: false, withIssues: true }));
|
|
74
72
|
const { settings, renderer, env } = context;
|
|
73
|
+
if (isStoreSetting(setting) && !offersStoreSetting(app, setting))
|
|
74
|
+
throw new CliError("validation_error", `no setting called "${setting}" here — it belongs to the messenger CLIs; one of: ${[...config.allSettings, ...storeSettingsOf(app)].join(", ")}`);
|
|
75
75
|
if (isStoreSetting(setting)) {
|
|
76
76
|
if (everyone || personal || bot)
|
|
77
77
|
throw new CliError("validation_error", `${setting} is store-wide — --defaults, --personal and --bot do not apply`);
|
package/dist/cli/effects.d.ts
CHANGED
|
@@ -41,6 +41,15 @@ export declare const isLogin: (command: Command) => boolean;
|
|
|
41
41
|
export declare const destroysNothing: <T extends Command>(command: T) => T;
|
|
42
42
|
/** A local edit remains an edit even when it first fetches metadata from the messenger. */
|
|
43
43
|
export declare const changesStore: <T extends Command>(command: T) => T;
|
|
44
|
+
/**
|
|
45
|
+
* Reads the provider and only keeps what it read in the owner's store — a sync, a list that refreshes it. An MCP server
|
|
46
|
+
* offers it through the read tool (owner ruling, 2026-10-27; WC-CMD-17): it changes nothing at the provider or for
|
|
47
|
+
* other people. A local edit (`changesStore`), an authority change, a call to another service (`reachesOtherService`,
|
|
48
|
+
* a model download) and a command that saves files (a download, a folder to write into) stay writes.
|
|
49
|
+
*/
|
|
50
|
+
export declare const fetchesOnly: (command: Command) => boolean;
|
|
51
|
+
/** `fetchesOnly` by command path under `root`, as an MCP catalogue asks it; an unknown path is not one. */
|
|
52
|
+
export declare const fetchesOnlyAt: (root: Command) => (path: readonly string[]) => boolean;
|
|
44
53
|
/**
|
|
45
54
|
* Whether `<PREFIX>_READ_ONLY` refuses the command as a change to the store. Every write that stays on this machine
|
|
46
55
|
* is refused unless `destroysNothing` marks it, so a new one fails closed (WC-PERM-12). A fetch (`remote: "read"`)
|
package/dist/cli/effects.js
CHANGED
|
@@ -66,6 +66,33 @@ export const changesStore = (command) => {
|
|
|
66
66
|
STORE_WRITES.add(command);
|
|
67
67
|
return command;
|
|
68
68
|
};
|
|
69
|
+
/** A download or a folder to write into: files outside the store, which the owner places, not an agent. */
|
|
70
|
+
const savesFiles = (command) => command.name() === "download" ||
|
|
71
|
+
[...command.options.map((option) => option.attributeName()), ...command.registeredArguments.map((one) => one.name())]
|
|
72
|
+
.map((name) => name.toLowerCase())
|
|
73
|
+
.some((name) => name === "dir" || name.endsWith("dir") || name === "output");
|
|
74
|
+
/**
|
|
75
|
+
* Reads the provider and only keeps what it read in the owner's store — a sync, a list that refreshes it. An MCP server
|
|
76
|
+
* offers it through the read tool (owner ruling, 2026-10-27; WC-CMD-17): it changes nothing at the provider or for
|
|
77
|
+
* other people. A local edit (`changesStore`), an authority change, a call to another service (`reachesOtherService`,
|
|
78
|
+
* a model download) and a command that saves files (a download, a folder to write into) stay writes.
|
|
79
|
+
*/
|
|
80
|
+
export const fetchesOnly = (command) => {
|
|
81
|
+
const effects = metaOf(command).effects;
|
|
82
|
+
return (effects?.remote === "read" &&
|
|
83
|
+
effects.local === "write" &&
|
|
84
|
+
!STORE_WRITES.has(command) &&
|
|
85
|
+
!changesAuthority(command) &&
|
|
86
|
+
!OTHER_SERVICE.has(command) &&
|
|
87
|
+
!savesFiles(command));
|
|
88
|
+
};
|
|
89
|
+
/** `fetchesOnly` by command path under `root`, as an MCP catalogue asks it; an unknown path is not one. */
|
|
90
|
+
export const fetchesOnlyAt = (root) => (path) => {
|
|
91
|
+
let at = root;
|
|
92
|
+
for (const word of path)
|
|
93
|
+
at = at?.commands.find((one) => one.name() === word);
|
|
94
|
+
return at !== undefined && fetchesOnly(at);
|
|
95
|
+
};
|
|
69
96
|
/**
|
|
70
97
|
* Whether `<PREFIX>_READ_ONLY` refuses the command as a change to the store. Every write that stays on this machine
|
|
71
98
|
* is refused unless `destroysNothing` marks it, so a new one fails closed (WC-PERM-12). A fetch (`remote: "read"`)
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export { sharedCommandTemplate } from "./contract-templates.js";
|
|
|
63
63
|
export { type Closeable, withDeadline } from "./deadline.js";
|
|
64
64
|
export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
65
65
|
export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
|
|
66
|
-
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";
|
|
66
|
+
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, fetchesOnly, fetchesOnlyAt, 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";
|
|
67
67
|
export { type CliEntry, type EntryContext, type EntryIo, startCli } from "./entry.js";
|
|
68
68
|
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";
|
|
69
69
|
export { withAbort } from "./execution.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -56,7 +56,7 @@ export { sharedCommandTemplate } from "./contract-templates.js";
|
|
|
56
56
|
export { withDeadline } from "./deadline.js";
|
|
57
57
|
export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
58
58
|
export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
|
|
59
|
-
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";
|
|
59
|
+
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, fetchesOnly, fetchesOnlyAt, 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";
|
|
60
60
|
export { startCli } from "./entry.js";
|
|
61
61
|
export { answerSchema, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
62
62
|
export { withAbort } from "./execution.js";
|
package/dist/cli/program.js
CHANGED
|
@@ -31,7 +31,7 @@ import { commandWords, isProfileName, liftProfile, sampleCommand, takesProfile,
|
|
|
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";
|
|
34
|
-
import { commandAt, lineOf, withLines } from "./steps.js";
|
|
34
|
+
import { commandAt, failureSteps, lineOf, withLines } from "./steps.js";
|
|
35
35
|
/**
|
|
36
36
|
* The command tree, built fresh on each call. Commander is global state by default —
|
|
37
37
|
* `exitOverride` and the write hooks turn it into a value a test can drive.
|
|
@@ -659,11 +659,14 @@ const failed = ({ app }, program, rest, error, settled, debug) => {
|
|
|
659
659
|
? new CliError(told.code, told.message, { ...told.details, settlement_failed: true })
|
|
660
660
|
: told;
|
|
661
661
|
const effects = judgedEffects(path ? metaOf(commandAt(program, path) ?? program).effects : undefined);
|
|
662
|
-
const
|
|
662
|
+
const built = errorObjectOf(failure, {
|
|
663
663
|
...failureContext(app.errors),
|
|
664
664
|
targets: judgedTargets(program),
|
|
665
665
|
...(effects ? { effects } : {}),
|
|
666
666
|
});
|
|
667
|
+
const { next: _named, ...said } = built.error;
|
|
668
|
+
const steps = failureSteps(program, built.error);
|
|
669
|
+
const object = { ...built, error: { ...said, ...(steps.length > 0 ? { next: steps } : {}) } };
|
|
667
670
|
showInputFields(object, (path && commandAt(program, path)) || program);
|
|
668
671
|
const command = path || app.command;
|
|
669
672
|
return {
|
package/dist/cli/settings.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export declare const count: v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.
|
|
|
11
11
|
export declare const flag: v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>;
|
|
12
12
|
/** `record_input`: true, false, or "failed" — the words typed kept only in a run that failed. */
|
|
13
13
|
export declare const recordInputSetting: v.UnionSchema<[v.BooleanSchema<undefined>, v.LiteralSchema<"failed", undefined>], (issue: v.BaseIssue<unknown>) => string>;
|
|
14
|
+
/**
|
|
15
|
+
* Settings about messages, senders and bots. A CLI that is not a messenger reads none of them, so it neither lists nor
|
|
16
|
+
* takes them; a file that still has one is read as before.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MESSENGER_ONLY_SETTINGS: readonly string[];
|
|
14
19
|
/**
|
|
15
20
|
* **What one messenger adds to the file**, merged into the one strict schema — a field an adapter
|
|
16
21
|
* reads but the schema did not know would be refused as unknown. Each entry must be optional.
|
package/dist/cli/settings.js
CHANGED
|
@@ -82,6 +82,17 @@ const SHARED_PROFILE_ENTRIES = {
|
|
|
82
82
|
const BOT_ONLY_ENTRIES = {
|
|
83
83
|
read_other_bots: v.optional(v.union([flag, v.array(v.string(), plain("has to be a list of profile names"))], plain("has to be true, false or a list of profile names"))),
|
|
84
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Settings about messages, senders and bots. A CLI that is not a messenger reads none of them, so it neither lists nor
|
|
87
|
+
* takes them; a file that still has one is read as before.
|
|
88
|
+
*/
|
|
89
|
+
export const MESSENGER_ONLY_SETTINGS = [
|
|
90
|
+
"sender_colors",
|
|
91
|
+
"sends_per_hour",
|
|
92
|
+
"transcribe_with",
|
|
93
|
+
"search_catch_up",
|
|
94
|
+
"read_other_bots",
|
|
95
|
+
];
|
|
85
96
|
/** One program, one version: whether to look for a newer one is not a per-profile matter. */
|
|
86
97
|
const SHARED_DEFAULTS_ONLY = {
|
|
87
98
|
update_check: v.optional(flag),
|
|
@@ -151,7 +162,7 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
151
162
|
const schema = strictOf(configEntries);
|
|
152
163
|
const defaultsOnlyKeys = Object.keys(defaultsOnly);
|
|
153
164
|
const botOnlyKeys = Object.keys(BOT_ONLY_ENTRIES);
|
|
154
|
-
const allSettings = [...Object.keys(profileEntries), ...botOnlyKeys, ...defaultsOnlyKeys];
|
|
165
|
+
const allSettings = [...Object.keys(profileEntries), ...botOnlyKeys, ...defaultsOnlyKeys].filter((setting) => app.messenger !== false || !MESSENGER_ONLY_SETTINGS.includes(setting));
|
|
155
166
|
const PROFILE = envName(app, "PROFILE");
|
|
156
167
|
const PROFILE_LOCK = envName(app, "PROFILE_LOCK");
|
|
157
168
|
const TIMEOUT = envName(app, "TIMEOUT");
|
|
@@ -14,7 +14,7 @@ export const skillCommand = (app, skill) => {
|
|
|
14
14
|
// cli-base prints each answer bare; the envelope is cli-core's.
|
|
15
15
|
const renderer = { ...output.renderer, result: (value) => printItem(output, value) };
|
|
16
16
|
return { ...output, renderer, ...(env ? { env } : {}) };
|
|
17
|
-
}, SHARED_SKILLS);
|
|
17
|
+
}, app.messenger === false ? {} : SHARED_SKILLS);
|
|
18
18
|
// An installed skill is instructions every later agent follows.
|
|
19
19
|
const install = command.commands.find((sub) => sub.name() === "install");
|
|
20
20
|
if (install) {
|
|
@@ -23,10 +23,18 @@ export const skillCommand = (app, skill) => {
|
|
|
23
23
|
}
|
|
24
24
|
const show = command.commands.find((sub) => sub.name() === "show");
|
|
25
25
|
if (show)
|
|
26
|
-
annotate(show, {
|
|
26
|
+
annotate(show, {
|
|
27
|
+
examples: [
|
|
28
|
+
`${app.command} skill show`,
|
|
29
|
+
...(app.messenger === false ? [] : [`${app.command} skill show follow-ups`]),
|
|
30
|
+
],
|
|
31
|
+
});
|
|
27
32
|
return command;
|
|
28
33
|
};
|
|
29
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Skills this package ships for the messenger CLIs, printed by `skill show <name>`: `skills/` beside `dist/` and `src/`.
|
|
36
|
+
* Both work on chats, so a CLI without them offers neither.
|
|
37
|
+
*/
|
|
30
38
|
const SHARED_SKILLS = {
|
|
31
39
|
"follow-ups": new URL("../../skills/follow-ups/SKILL.md", import.meta.url),
|
|
32
40
|
"link-conversations": new URL("../../skills/link-conversations/SKILL.md", import.meta.url),
|
package/dist/cli/steps.d.ts
CHANGED
|
@@ -35,6 +35,15 @@ type Targets = NonNullable<StepInput["targets"]>;
|
|
|
35
35
|
* remotely.
|
|
36
36
|
*/
|
|
37
37
|
export declare const runnable: (root: Command, steps: readonly NextStep[] | undefined) => NextStep[];
|
|
38
|
+
/**
|
|
39
|
+
* The step a failure names, where its reason has one and this CLI has the command: a model that is not downloaded
|
|
40
|
+
* names its download only in a CLI with `models`. A step the error already carries is kept, also only if runnable.
|
|
41
|
+
*/
|
|
42
|
+
export declare const failureSteps: (root: Command, error: {
|
|
43
|
+
reason?: string;
|
|
44
|
+
details?: Record<string, unknown>;
|
|
45
|
+
next?: readonly NextStep[];
|
|
46
|
+
}) => NextStep[];
|
|
38
47
|
/** Each command step judged by its command's effects: safe to run unasked only when it writes nothing remotely. */
|
|
39
48
|
export declare const withSafety: (steps: readonly NextStep[], targets: Targets) => NextStep[];
|
|
40
49
|
/** Each command step with `line`, the whole command to type — only the CLI knows the profile, so MCP gets none. */
|
package/dist/cli/steps.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { STEPS as BASE_STEPS, defineSteps, MAX_NEXT_STEPS, stepOf, writes, } from "@wirecat/cli-base/standards";
|
|
2
|
+
import { IMAGE_MODELS } from "../images/picture/models.js";
|
|
2
3
|
import { typedCommand } from "../runs/scope.js";
|
|
3
4
|
import { judgedTargets } from "./effects.js";
|
|
4
5
|
/**
|
|
@@ -245,6 +246,21 @@ export const commandAt = (root, path) => {
|
|
|
245
246
|
export const runnable = (root, steps) => withSafety((steps ?? [])
|
|
246
247
|
.filter((step) => step.kind === "user_action" || commandAt(root, step.command) !== undefined)
|
|
247
248
|
.slice(0, MAX_NEXT_STEPS), judgedTargets(root));
|
|
249
|
+
/**
|
|
250
|
+
* The step a failure names, where its reason has one and this CLI has the command: a model that is not downloaded
|
|
251
|
+
* names its download only in a CLI with `models`. A step the error already carries is kept, also only if runnable.
|
|
252
|
+
*/
|
|
253
|
+
export const failureSteps = (root, error) => {
|
|
254
|
+
const model = error.details?.model;
|
|
255
|
+
const own = error.reason === "model.not-downloaded" && typeof model === "string"
|
|
256
|
+
? [
|
|
257
|
+
nextStep(IMAGE_MODELS.some((one) => one.id === model) ? "models.download-image" : "models.download", {
|
|
258
|
+
args: { model },
|
|
259
|
+
}),
|
|
260
|
+
]
|
|
261
|
+
: [];
|
|
262
|
+
return runnable(root, [...(error.next ?? []), ...own]);
|
|
263
|
+
};
|
|
248
264
|
/** Each command step judged by its command's effects: safe to run unasked only when it writes nothing remotely. */
|
|
249
265
|
export const withSafety = (steps, targets) => steps.map((step) => {
|
|
250
266
|
if (step.kind === "user_action")
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { AppIdentity } from "../ports/app.js";
|
|
2
2
|
/** Settings of the store file, not of a profile: the store is one file for every profile, tg and MAX. */
|
|
3
3
|
export declare const STORE_SETTINGS: string[];
|
|
4
|
+
/**
|
|
5
|
+
* The store settings a CLI offers. Phone countries, task rules and account groups belong to messages; a CLI that is
|
|
6
|
+
* not a messenger would change them for tg and MAX without reading them itself.
|
|
7
|
+
*/
|
|
8
|
+
export declare const storeSettingsOf: (app: Pick<AppIdentity, "messenger">) => string[];
|
|
9
|
+
export declare const offersStoreSetting: (app: Pick<AppIdentity, "messenger">, setting: string) => boolean;
|
|
4
10
|
export declare const isStoreSetting: (setting: string) => boolean;
|
|
5
11
|
export declare const changeStoreSetting: (app: AppIdentity, env: NodeJS.ProcessEnv, setting: string, value: string | undefined) => Promise<{
|
|
6
12
|
result: {
|
|
@@ -29,6 +29,12 @@ export const STORE_SETTINGS = [
|
|
|
29
29
|
FIRST_PASS,
|
|
30
30
|
];
|
|
31
31
|
const GROUP_PREFIX = `${ACCOUNT_GROUPS_SETTING}.`;
|
|
32
|
+
/**
|
|
33
|
+
* The store settings a CLI offers. Phone countries, task rules and account groups belong to messages; a CLI that is
|
|
34
|
+
* not a messenger would change them for tg and MAX without reading them itself.
|
|
35
|
+
*/
|
|
36
|
+
export const storeSettingsOf = (app) => app.messenger === false ? Object.keys(SCRIPTS) : [...STORE_SETTINGS, `${GROUP_PREFIX}<name>`];
|
|
37
|
+
export const offersStoreSetting = (app, setting) => app.messenger !== false || setting in SCRIPTS;
|
|
32
38
|
export const isStoreSetting = (setting) => setting in SCRIPTS ||
|
|
33
39
|
setting === PHONE_COUNTRIES ||
|
|
34
40
|
setting === TASK_CATEGORIES ||
|
package/dist/mcp/agent.d.ts
CHANGED
|
@@ -70,6 +70,8 @@ export interface CatalogueOptions {
|
|
|
70
70
|
permissions?: CommandPermissions | undefined;
|
|
71
71
|
/** Whether a command changes what a later run may do (`grantsAuthority`): execution requires `mcp.authority`. */
|
|
72
72
|
authority?: (path: readonly string[]) => boolean;
|
|
73
|
+
/** Whether a command only fetches into this machine (`fetchesOnlyAt`): offered as a read. Unset, every write stays one. */
|
|
74
|
+
fetchOnly?: (path: readonly string[]) => boolean;
|
|
73
75
|
/** Arguments left out of every tool, by name. */
|
|
74
76
|
omit?: readonly string[];
|
|
75
77
|
/** Offer `offline` on commands that read the messenger: answer from the copy on this machine. */
|
package/dist/mcp/agent.js
CHANGED
|
@@ -154,7 +154,7 @@ export const toolsFromCommands = (commands, options = {}) => {
|
|
|
154
154
|
path: info.path,
|
|
155
155
|
title: info.summary ?? info.description,
|
|
156
156
|
description: info.description,
|
|
157
|
-
writes: writes(info.effects),
|
|
157
|
+
writes: writes(info.effects) && options.fetchOnly?.(info.path) !== true,
|
|
158
158
|
input: schemaOf(fieldsOf(info, options)),
|
|
159
159
|
...(info.output ? { output: info.output } : {}),
|
|
160
160
|
}));
|
package/dist/mcp/bot/server.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface BotServerOptions {
|
|
|
16
16
|
keyOf?: (path: readonly string[]) => PermissionKey | null | undefined;
|
|
17
17
|
/** Whether a command changes what a later run may do; such a command requires `mcp.authority` to execute. */
|
|
18
18
|
authority?: (path: readonly string[]) => boolean;
|
|
19
|
+
/** Whether a command only fetches into this machine (`fetchesOnlyAt`): offered through the read tool. */
|
|
20
|
+
fetchOnly?: (path: readonly string[]) => boolean;
|
|
19
21
|
env: NodeJS.ProcessEnv;
|
|
20
22
|
skill?: URL;
|
|
21
23
|
}
|
package/dist/mcp/bot/server.js
CHANGED
|
@@ -33,6 +33,7 @@ export const createBotServer = (options) => {
|
|
|
33
33
|
readOnly: readOnlyLockOf(bot.app, env) !== undefined,
|
|
34
34
|
...(options.keyOf ? { keyOf: options.keyOf } : {}),
|
|
35
35
|
authority,
|
|
36
|
+
...(options.fetchOnly ? { fetchOnly: options.fetchOnly } : {}),
|
|
36
37
|
offline: true,
|
|
37
38
|
// Another bot's copy is this bot's to read only where `read_other_bots` says so.
|
|
38
39
|
...(settings.readOtherBots ? {} : { omit: ["all_bots", "bots"] }),
|
package/dist/mcp/server.js
CHANGED
|
@@ -8,7 +8,7 @@ import * as v from "valibot";
|
|
|
8
8
|
import { findCommand } from "../cli/command-contract.js";
|
|
9
9
|
import { environmentOf } from "../cli/context.js";
|
|
10
10
|
import { refuseWriteBeforeConnecting } from "../cli/early-refusal.js";
|
|
11
|
-
import { changesAuthorityAt, judgedTargets } from "../cli/effects.js";
|
|
11
|
+
import { changesAuthorityAt, fetchesOnlyAt, judgedTargets } from "../cli/effects.js";
|
|
12
12
|
import { itemOf } from "../cli/envelope.js";
|
|
13
13
|
import { DEFAULT_OUTPUT_BYTES } from "../cli/execution.js";
|
|
14
14
|
import { recalledAccount } from "../cli/messenger/accounts.js";
|
|
@@ -127,6 +127,7 @@ export const createServer = (command, context, messenger, sessionOptions) => {
|
|
|
127
127
|
readOnly: readOnlyLockOf(app, context.env) !== undefined,
|
|
128
128
|
keyOf,
|
|
129
129
|
authority,
|
|
130
|
+
fetchOnly: fetchesOnlyAt(root),
|
|
130
131
|
offline: true,
|
|
131
132
|
stdin,
|
|
132
133
|
}).map((one) => "sync_first" in one.input.entries && !syncAllowed(one.path)
|
|
@@ -19,10 +19,8 @@ const PER_SESSION = 8;
|
|
|
19
19
|
const PER_REQUEST = 256;
|
|
20
20
|
/** e5's tokenizer reads ~3.7 characters a token in Russian; 3 keeps the bound above the real count. */
|
|
21
21
|
const CHARS_PER_TOKEN_AT_LEAST = 3;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
model: id,
|
|
25
|
-
});
|
|
22
|
+
/** The download is the error's `next` step, named only by a CLI that has `models` (`failureSteps`). */
|
|
23
|
+
const notDownloaded = (id) => new CliError("not_found", `${id} is not downloaded on this machine`, { reason: "model.not-downloaded", model: id });
|
|
26
24
|
export const notBuiltError = (deps, chatId) => new CliError("not_found", `chat ${chatId} has no conversations yet — \`${typedCommand(deps.messenger.app.command, `conversations links rebuild --chat ${chatId}`)}\``);
|
|
27
25
|
export const embeddingTarget = (deps, choice = DEFAULT_TEXT_MODEL) => {
|
|
28
26
|
if (typeof choice !== "string") {
|
|
@@ -50,7 +48,7 @@ export const embeddingTarget = (deps, choice = DEFAULT_TEXT_MODEL) => {
|
|
|
50
48
|
perBatch: PER_SESSION,
|
|
51
49
|
open: async ({ threads, apart = false, } = {}) => {
|
|
52
50
|
if (!isTextModelInstalled(model, directory))
|
|
53
|
-
throw notDownloaded(
|
|
51
|
+
throw notDownloaded(model.id);
|
|
54
52
|
if (apart) {
|
|
55
53
|
const { openProcess } = await import("../embeddings/process.js");
|
|
56
54
|
return openProcess(model, directory, { threads: threads ?? defaultThreads() });
|
|
@@ -264,7 +262,7 @@ export const embeddingsMaintenance = (deps) => {
|
|
|
264
262
|
const account = await deps.account();
|
|
265
263
|
const target = embeddingTarget(deps, choice);
|
|
266
264
|
if (embed && !build && !target.installed)
|
|
267
|
-
throw notDownloaded(
|
|
265
|
+
throw notDownloaded(target.id);
|
|
268
266
|
const chatId = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
|
|
269
267
|
const scan = async () => ({
|
|
270
268
|
chats: (await readinessOf(deps, chatId, target.key)).sort(byOldestBuild),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.469.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",
|
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
"@types/node": "^22.20.5",
|
|
234
234
|
"@types/pngjs": "6.0.5",
|
|
235
235
|
"@vitest/coverage-v8": "^5.0.5",
|
|
236
|
-
"@wirecat/cli-base": "0.
|
|
236
|
+
"@wirecat/cli-base": "0.65.0",
|
|
237
237
|
"@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
|
|
238
238
|
"commander": "15.0.0",
|
|
239
239
|
"cspell": "10.4.0",
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
},
|
|
254
254
|
"peerDependencies": {
|
|
255
255
|
"@napi-rs/canvas": ">=0.1.69 <2",
|
|
256
|
-
"@wirecat/cli-base": ">=0.
|
|
256
|
+
"@wirecat/cli-base": ">=0.65.0 <0.66.0",
|
|
257
257
|
"commander": ">=15"
|
|
258
258
|
},
|
|
259
259
|
"bin": {
|