@wirecat/cli-core 0.420.0 → 0.422.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 +23 -0
- package/dist/cli/bot/context.d.ts +3 -2
- package/dist/cli/bot/context.js +15 -0
- package/dist/cli/commands-command.js +1 -1
- package/dist/cli/config-command.d.ts +0 -4
- package/dist/cli/config-command.js +5 -4
- package/dist/cli/first-run.d.ts +0 -16
- package/dist/cli/first-run.js +1 -6
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +2 -2
- package/dist/cli/messenger/context.js +3 -6
- package/dist/cli/permission-keys.d.ts +2 -0
- package/dist/cli/permission-keys.js +10 -3
- package/dist/cli/preview.d.ts +2 -2
- package/dist/cli/preview.js +4 -3
- package/dist/cli/profile.d.ts +2 -0
- package/dist/cli/profile.js +10 -0
- package/dist/cli/program.js +14 -14
- package/dist/cli/settings.d.ts +0 -2
- package/dist/cli/settings.js +1 -5
- package/dist/mcp/surface.js +9 -7
- package/dist/mcp/tool.d.ts +1 -3
- package/dist/mcp/tool.js +1 -2
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/parity/manifest.js +0 -4
- package/dist/ports/app.d.ts +6 -0
- package/dist/search/dictionary.d.ts +3 -6
- package/dist/search/dictionary.js +2 -1
- package/dist/search/languages.d.ts +28 -0
- package/dist/search/languages.js +7 -0
- package/dist/search/stem.d.ts +5 -4
- package/dist/search/stem.js +3 -2
- package/dist/sends/index.d.ts +1 -1
- package/dist/sends/index.js +1 -1
- package/dist/sends/permissions.d.ts +0 -13
- package/dist/sends/permissions.js +15 -142
- package/package.json +1 -1
- package/dist/cli/moved.d.ts +0 -18
- package/dist/cli/moved.js +0 -72
- package/dist/sends/permission-aliases.d.ts +0 -5
- package/dist/sends/permission-aliases.js +0 -50
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.422.0 — 25.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`AppIdentity.messenger`: set it to `false` in a CLI that is not a messenger** (a notes, files or calendar CLI from cli-template). The shared program, `commands`, `config` and MCP text then stop speaking of chats and messages: the profile example in the program description names the CLI's own first command, `commands info`'s example is `<cmd> commands info commands`, `--brief` and `--full` describe "found items", `--trace` and `--record` say "never content", and `config show` and the `config set`/`unset` list leave out `sender_colors`. Unset means a messenger: tg, max and wa print exactly what they did. A CLI that sets it refreshes its contract snapshots once.
|
|
15
|
+
- **The MCP `_tools_search`, `_read` and `_write` tools speak neutrally when the catalogue has no `messages` commands**: task words "list", "show", "change a setting", one of the CLI's own reading commands as the example, and "creates, edits, deletes" for writes. No setting is needed; a messenger's catalogue is unchanged.
|
|
16
|
+
|
|
17
|
+
### Changed — may break callers
|
|
18
|
+
|
|
19
|
+
- **The languages search stems are one table, `LANGUAGES`, and each entry now names its Hunspell dictionary** (`dictionary: "dictionary-ru"`). `LANGUAGES` from `/cli` keeps its other fields. `LATIN_STEMMERS` is now `english, spanish, none`, in the order the error for an unknown stemmer already printed, so the `allowed` list of a `stemmer.invalid` error comes in that order too. A caller that compared that list in order sorts it first.
|
|
20
|
+
|
|
21
|
+
## 0.421.0 — 25.10.2026
|
|
22
|
+
|
|
23
|
+
### Changed — may break callers
|
|
24
|
+
|
|
25
|
+
- **Old command names and old permission keys are no longer recognised.** A retired command path (`contacts profile`, `bot store fetch`, `search mail` and the rest) gets the ordinary "unknown command" answer, on the command line and over MCP, with no pointer to a replacement. A saved permission key is read only as written: nothing maps an old key to a new one, and `chats.mute` no longer covers `chats unmute`. Removed with it: `movedCommandError` from `/cli`; `PERMISSIONS`, `Permission`, `permissionFor` and `assertStatsPermissionsCurrent` from `/sends`; the `permission` field of an MCP tool definition; and the `skipMovedPermissions` option of `resolveSettings`. A CLI that imports one of them drops the import; nothing replaces them.
|
|
26
|
+
- **Bot commands and `--dry-run` refuse a saved permission key that no command checks**, as messenger commands already did. They stop with a `configuration_error` that names the key and the `config unset` that removes it: "permissions.bot.store.fetch names no command — remove it with \`tg sales config unset --bot permissions.bot.store.fetch\`". `config show`, `set` and `unset` still run, so the file can be repaired. Before, a bot command did not look at the keys at all, and an old key was quietly renamed or ignored.
|
|
27
|
+
- **`messages sync` and `bot messages sync` are checked as `messages.sync` and `bot.messages.sync`**, their own paths. A level on `messages` or `bot.messages` still applies to them; `commands --json` now reports the longer key.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **A deny saved on the sync command itself now stops it.** `permissions.bot.messages.sync: deny` was ignored, because `bot messages sync` was checked under `bot.messages`; `permissions.messages.sync: deny` could not be saved at all and was refused as naming no command. Both are now honoured on the command line, in MCP discovery and by the MCP server. Bot commands also refuse a denied read (`bot.messages: deny` stops `bot messages list` and `bot messages sync`) before they connect, as messenger commands already did.
|
|
32
|
+
|
|
10
33
|
## 0.420.0 — 25.10.2026
|
|
11
34
|
|
|
12
35
|
### Changed — may break callers
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import { SendJournal } from "../../sends/journal.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type Level, type PermissionKey } from "../../sends/permissions.js";
|
|
4
4
|
import { RecipientList } from "../../sends/recipients.js";
|
|
5
|
+
import type { Settings } from "../settings.js";
|
|
5
6
|
import type { BotAdapter, BotChatRef, BotConnectOptions, BotMessenger } from "./port.js";
|
|
6
7
|
import { ChatRegistry } from "./registry.js";
|
|
7
8
|
import { BotTokenStore } from "./token.js";
|
|
@@ -9,7 +10,7 @@ import { BotTokenStore } from "./token.js";
|
|
|
9
10
|
export declare const botWords: (bot: BotMessenger, profile: string) => string;
|
|
10
11
|
/** What a bot command needs, resolved once: the bot's settings, its token, its lists. */
|
|
11
12
|
export declare const botContext: (command: Command, bot: BotMessenger) => {
|
|
12
|
-
settings:
|
|
13
|
+
settings: Settings;
|
|
13
14
|
renderer: import("@wirecat/cli-base").Renderer;
|
|
14
15
|
format: import("@wirecat/cli-base").RenderFormat;
|
|
15
16
|
color: boolean;
|
package/dist/cli/bot/context.js
CHANGED
|
@@ -2,16 +2,30 @@ import { CliError } from "@wirecat/cli-base";
|
|
|
2
2
|
import { pickChat } from "../../resolve.js";
|
|
3
3
|
import { sendGuard } from "../../sends/guard.js";
|
|
4
4
|
import { SendJournal } from "../../sends/journal.js";
|
|
5
|
+
import { levelFor, readKeysForCommand } from "../../sends/permissions.js";
|
|
5
6
|
import { RecipientList } from "../../sends/recipients.js";
|
|
6
7
|
import { readSecret } from "../../terminal/prompt.js";
|
|
8
|
+
import { commandPathOf } from "../command-contract.js";
|
|
7
9
|
import { baseContext, secretInputOf } from "../context.js";
|
|
8
10
|
import { terminalAsker } from "../messenger/ask.js";
|
|
11
|
+
import { refuseUnknownPermissionKeys } from "../permission-keys.js";
|
|
9
12
|
import { asFirstWord } from "../profile.js";
|
|
10
13
|
import { botCopy } from "./copy.js";
|
|
11
14
|
import { botFiles, ChatRegistry } from "./registry.js";
|
|
12
15
|
import { BotTokenStore } from "./token.js";
|
|
13
16
|
/** How the owner types this bot — `max sales bot` — so a refusal names the command that fixes it. */
|
|
14
17
|
export const botWords = (bot, profile) => `${bot.app.command} ${asFirstWord(profile)}bot`;
|
|
18
|
+
/** What a messenger command refuses before it connects, for a bot: an unknown key, then a denied read. */
|
|
19
|
+
const refuseBotPermissions = (command, bot, settings) => {
|
|
20
|
+
refuseUnknownPermissionKeys(command, settings.permissions, `${bot.app.command} ${asFirstWord(settings.profile)}config unset --bot`);
|
|
21
|
+
for (const key of readKeysForCommand(commandPathOf(command))) {
|
|
22
|
+
const { level, key: named } = levelFor(settings.permissions, key);
|
|
23
|
+
if (level !== "deny")
|
|
24
|
+
continue;
|
|
25
|
+
throw new CliError("permission_error", `bot profile ${settings.profile} denies ${key} (permissions.${named} is deny, from the ` +
|
|
26
|
+
`${settings.permissionSources[named ?? ""] ?? "default"})`, { permission: key });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
15
29
|
/** What a bot command needs, resolved once: the bot's settings, its token, its lists. */
|
|
16
30
|
export const botContext = (command, bot) => {
|
|
17
31
|
const base = baseContext(command, (flags, options) => bot.resolveSettings(flags, { ...options, kind: "bot" }));
|
|
@@ -21,6 +35,7 @@ export const botContext = (command, bot) => {
|
|
|
21
35
|
const tokens = bot.tokenStore?.(command, profile) ??
|
|
22
36
|
new BotTokenStore({ app: bot.app, profile, env, ...(keyring ? { keyring } : {}) });
|
|
23
37
|
const words = botWords(bot, profile);
|
|
38
|
+
refuseBotPermissions(command, bot, settings);
|
|
24
39
|
const registry = bot.registry?.(command, profile) ?? new ChatRegistry(bot.app, profile, env);
|
|
25
40
|
const recipients = () => new RecipientList(files.recipients, words);
|
|
26
41
|
const journal = () => new SendJournal(files.journal, { service: bot.app.command, version: bot.app.version });
|
|
@@ -59,7 +59,7 @@ export const commandsCommand = (app) => {
|
|
|
59
59
|
});
|
|
60
60
|
command.addCommand(annotate(new Command("info"), {
|
|
61
61
|
effects: NO_EFFECTS,
|
|
62
|
-
examples: [`${app.command} commands info messages list`],
|
|
62
|
+
examples: [`${app.command} commands info ${app.messenger === false ? "commands" : "messages list"}`],
|
|
63
63
|
})
|
|
64
64
|
.description(DISCOVERY_DESCRIPTIONS.info)
|
|
65
65
|
.argument("[path...]", "one command path; without one, export schemas, error reasons and explicit overrides")
|
|
@@ -2,10 +2,6 @@ import { Command } from "commander";
|
|
|
2
2
|
import { type AppIdentity } from "../ports/app.js";
|
|
3
3
|
import { type PermissionKeyOf } 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
|
-
*/
|
|
9
5
|
export declare const configCommand: (app: AppIdentity, config: Configuration, { permissionKey }?: {
|
|
10
6
|
permissionKey?: PermissionKeyOf;
|
|
11
7
|
}) => Command;
|
|
@@ -15,6 +15,8 @@ import { changeStoreSetting, isStoreSetting, STORE_SETTINGS, storeSettings } fro
|
|
|
15
15
|
* The settings in force and where each came from, and changing them. Printed whole: no field the
|
|
16
16
|
* configuration accepts can hold a secret — the schema has nowhere to put one.
|
|
17
17
|
*/
|
|
18
|
+
/** `sender_colors` colours who wrote a message; a CLI without messages has nothing to colour. */
|
|
19
|
+
const shownSettings = (app, config) => app.messenger === false ? config.allSettings.filter((setting) => setting !== "sender_colors") : config.allSettings;
|
|
18
20
|
export const configCommand = (app, config, { permissionKey } = {}) => {
|
|
19
21
|
const command = new Command("config").description("the settings in force, and where each one came from");
|
|
20
22
|
annotate(command.command("show"), { effects: LOCAL_READ, examples: [`${app.command} personal config show`] })
|
|
@@ -32,7 +34,7 @@ export const configCommand = (app, config, { permissionKey } = {}) => {
|
|
|
32
34
|
config_file: settings.configPath,
|
|
33
35
|
config_found: settings.configFound,
|
|
34
36
|
paths_overridden: overridden,
|
|
35
|
-
settings: [...config
|
|
37
|
+
settings: [...shownSettings(app, config), "command_timeout_ms"]
|
|
36
38
|
.filter((setting) => bot || setting !== "read_other_bots")
|
|
37
39
|
.map((setting) => sourced(settings, setting, config.schema)),
|
|
38
40
|
store_settings: await storeSettings(env),
|
|
@@ -49,13 +51,13 @@ export const configCommand = (app, config, { permissionKey } = {}) => {
|
|
|
49
51
|
}))
|
|
50
52
|
.argument("<setting>",
|
|
51
53
|
// default_profile is only removed now: `config unset` still takes it, `config set` refuses it.
|
|
52
|
-
`one of: ${[...config.
|
|
54
|
+
`one of: ${[...shownSettings(app, config).filter((one) => action === "unset" || one !== "default_profile"), ...STORE_SETTINGS, "account_groups.<name>"].join(", ")}`)
|
|
53
55
|
.option("--defaults", "change what every profile gets, rather than this profile")
|
|
54
56
|
.option("--personal", "only for personal accounts — the personal section of the file")
|
|
55
57
|
.option("--bot", "only for bots — the bot section of the file");
|
|
56
58
|
if (action === "set")
|
|
57
59
|
sub
|
|
58
|
-
.argument("<value>", "a number, true or false
|
|
60
|
+
.argument("<value>", "a number, true or false; for a group tg:work,max:Alice,email:me@x")
|
|
59
61
|
.description("save a setting to the configuration file");
|
|
60
62
|
else
|
|
61
63
|
sub.description("remove a setting from the configuration file");
|
|
@@ -64,7 +66,6 @@ export const configCommand = (app, config, { permissionKey } = {}) => {
|
|
|
64
66
|
const context = baseContext(this, (flags, options) => config.resolveSettings(flags, {
|
|
65
67
|
...options,
|
|
66
68
|
needsProfile: !everyone && setting !== "default_profile" && !isStoreSetting(setting),
|
|
67
|
-
skipMovedPermissions: true,
|
|
68
69
|
}));
|
|
69
70
|
const { settings, renderer, env } = context;
|
|
70
71
|
if (isStoreSetting(setting)) {
|
package/dist/cli/first-run.d.ts
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
import { type Installer } from "@wirecat/cli-base/update";
|
|
2
2
|
import { type Question } from "./setup-answers.js";
|
|
3
3
|
/** The languages cli-core stems and has a dictionary for; each is one stemmer of its alphabet. */
|
|
4
|
-
export declare const LANGUAGES: readonly [{
|
|
5
|
-
readonly name: "russian";
|
|
6
|
-
readonly code: "ru";
|
|
7
|
-
readonly label: "Russian";
|
|
8
|
-
readonly script: "cyrillic";
|
|
9
|
-
}, {
|
|
10
|
-
readonly name: "english";
|
|
11
|
-
readonly code: "en";
|
|
12
|
-
readonly label: "English";
|
|
13
|
-
readonly script: "latin";
|
|
14
|
-
}, {
|
|
15
|
-
readonly name: "spanish";
|
|
16
|
-
readonly code: "es";
|
|
17
|
-
readonly label: "Spanish";
|
|
18
|
-
readonly script: "latin";
|
|
19
|
-
}];
|
|
20
4
|
export interface FirstRunIo {
|
|
21
5
|
ask: (prompt: string) => Promise<string>;
|
|
22
6
|
say: (text: string) => void;
|
package/dist/cli/first-run.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { accessSync, constants, realpathSync } from "node:fs";
|
|
3
3
|
import { delimiter, join } from "node:path";
|
|
4
4
|
import { installerOf, updateCommand } from "@wirecat/cli-base/update";
|
|
5
|
+
import { LANGUAGES } from "../search/languages.js";
|
|
5
6
|
import { DEFAULT_STEMMERS } from "../search/stem.js";
|
|
6
7
|
import { openCache } from "../store/open.js";
|
|
7
8
|
import { storePath } from "../store/path.js";
|
|
@@ -11,12 +12,6 @@ import { readSecret } from "../terminal/prompt.js";
|
|
|
11
12
|
import { WIRECAT_TOOLS } from "./help/first-screen.js";
|
|
12
13
|
import { buildStems } from "./messenger/store-maintenance-command.js";
|
|
13
14
|
import { QUESTIONS, readSetupAnswers, unanswered, writeSetupAnswers, } from "./setup-answers.js";
|
|
14
|
-
/** The languages cli-core stems and has a dictionary for; each is one stemmer of its alphabet. */
|
|
15
|
-
export const LANGUAGES = [
|
|
16
|
-
{ name: "russian", code: "ru", label: "Russian", script: "cyrillic" },
|
|
17
|
-
{ name: "english", code: "en", label: "English", script: "latin" },
|
|
18
|
-
{ name: "spanish", code: "es", label: "Spanish", script: "latin" },
|
|
19
|
-
];
|
|
20
15
|
/** Commands that run without first-run questions: they serve agents, help, or open the store themselves. */
|
|
21
16
|
const SKIPPED = new Set(["help", "guide", "completion", "mcp", "serve", "server", "update", "store"]);
|
|
22
17
|
/**
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export { CORE_LOG_EVENTS } from "../runs/log-events.js";
|
|
|
26
26
|
export { crashOf, type Recording, type RecordingOptions, recorded, startRecording, wasSettled, } from "../runs/recording.js";
|
|
27
27
|
export { findRun, KEEP_RUNS_FOR_DAYS, listRuns, pruneRuns, type RunMetadata, readEvents, runsDirFor, runtime, startRun, } from "../runs/run.js";
|
|
28
28
|
export { currentRun, inRun, type RunScope, useAsProcessRun } from "../runs/scope.js";
|
|
29
|
+
export { LANGUAGES } from "../search/languages.js";
|
|
29
30
|
export { guardedApp, guardedPress, guardedStart, type Pressed } from "../sends/buttons.js";
|
|
30
31
|
export { guardedClose, guardedCreatePoll, guardedVote } from "../sends/polls.js";
|
|
31
32
|
export type { InboxReader } from "../services/inbox.js";
|
|
@@ -62,7 +63,7 @@ export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNoth
|
|
|
62
63
|
export { type CliEntry, type EntryContext, type EntryIo, startCli } from "./entry.js";
|
|
63
64
|
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";
|
|
64
65
|
export { withAbort } from "./execution.js";
|
|
65
|
-
export { askSetupQuestions, asksAtStart, type FirstRun, type FirstRunIo,
|
|
66
|
+
export { askSetupQuestions, asksAtStart, type FirstRun, type FirstRunIo, setupAtStart, systemLanguage, } from "./first-run.js";
|
|
66
67
|
export { checkGuide, checkStep, type GuideOptions, type GuideReference, type GuideStep, loadScenarios, MEMO_SCENARIOS, type Scenario, shellProblems, } from "./guide.js";
|
|
67
68
|
export { type CommandHelp, commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, type NextChoice, nextUsually, } from "./help/command-help.js";
|
|
68
69
|
export { commandsText } from "./help/commands-text.js";
|
|
@@ -96,7 +97,6 @@ export type { Running, ServerOptions, ServerProcess, ServerSystem } from "./mess
|
|
|
96
97
|
export { storeSummary } from "./messenger/store-maintenance-command.js";
|
|
97
98
|
export { type Saving, stored } from "./messenger/stored.js";
|
|
98
99
|
export { MODEL_CATALOGUE_QUERY, type ModelCatalogueOptions } from "./model-catalogue-command.js";
|
|
99
|
-
export { movedCommandError } from "./moved.js";
|
|
100
100
|
export { window, withPaging } from "./paging.js";
|
|
101
101
|
export { knownBeside, knownPermissionKeys, type PermissionKeyOf, unknownPermissionKeys } from "./permission-keys.js";
|
|
102
102
|
export { asFirstWord, commandWords, liftProfile, refuseCommandName, rootOf, usableProfileName } from "./profile.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export { CORE_LOG_EVENTS } from "../runs/log-events.js";
|
|
|
22
22
|
export { crashOf, recorded, startRecording, wasSettled, } from "../runs/recording.js";
|
|
23
23
|
export { findRun, KEEP_RUNS_FOR_DAYS, listRuns, pruneRuns, readEvents, runsDirFor, runtime, startRun, } from "../runs/run.js";
|
|
24
24
|
export { currentRun, inRun, useAsProcessRun } from "../runs/scope.js";
|
|
25
|
+
export { LANGUAGES } from "../search/languages.js";
|
|
25
26
|
export { guardedApp, guardedPress, guardedStart } from "../sends/buttons.js";
|
|
26
27
|
export { guardedClose, guardedCreatePoll, guardedVote } from "../sends/polls.js";
|
|
27
28
|
export { INBOX_CHATS, newIn, REVIEW_CHATS, reviewIn, unreadIn } from "../services/inbox.js";
|
|
@@ -55,7 +56,7 @@ export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNoth
|
|
|
55
56
|
export { startCli } from "./entry.js";
|
|
56
57
|
export { answerSchema, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
57
58
|
export { withAbort } from "./execution.js";
|
|
58
|
-
export { askSetupQuestions, asksAtStart,
|
|
59
|
+
export { askSetupQuestions, asksAtStart, setupAtStart, systemLanguage, } from "./first-run.js";
|
|
59
60
|
export { checkGuide, checkStep, loadScenarios, MEMO_SCENARIOS, shellProblems, } from "./guide.js";
|
|
60
61
|
export { commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, nextUsually, } from "./help/command-help.js";
|
|
61
62
|
export { commandsText } from "./help/commands-text.js";
|
|
@@ -87,7 +88,6 @@ export { STEMS_EVERY_MS, STEMS_SLICE_MS, stemFills } from "./messenger/serve-ste
|
|
|
87
88
|
export { storeSummary } from "./messenger/store-maintenance-command.js";
|
|
88
89
|
export { stored } from "./messenger/stored.js";
|
|
89
90
|
export { MODEL_CATALOGUE_QUERY } from "./model-catalogue-command.js";
|
|
90
|
-
export { movedCommandError } from "./moved.js";
|
|
91
91
|
export { window, withPaging } from "./paging.js";
|
|
92
92
|
export { knownBeside, knownPermissionKeys, unknownPermissionKeys } from "./permission-keys.js";
|
|
93
93
|
export { asFirstWord, commandWords, liftProfile, refuseCommandName, rootOf, usableProfileName } from "./profile.js";
|
|
@@ -3,12 +3,12 @@ import { servingProfiles } from "../../background/lock.js";
|
|
|
3
3
|
import { FloodMemory, floodPathFor } from "../../sends/flood.js";
|
|
4
4
|
import { ceilingRefusal, guardFor } from "../../sends/guard.js";
|
|
5
5
|
import { DEFAULT_PACE, Pacer, pacePathFor } from "../../sends/pace.js";
|
|
6
|
-
import {
|
|
6
|
+
import { CEILING_KEY, levelFor, readKeysForCommand } from "../../sends/permissions.js";
|
|
7
7
|
import { RecipientList, recipientsPathFor } from "../../sends/recipients.js";
|
|
8
8
|
import { OFFLINE, servicesFor } from "../../services/index.js";
|
|
9
9
|
import { openStore } from "../../store/store.js";
|
|
10
10
|
import { baseContext, environmentOf } from "../context.js";
|
|
11
|
-
import {
|
|
11
|
+
import { refuseUnknownPermissionKeys } from "../permission-keys.js";
|
|
12
12
|
import { asFirstWord } from "../profile.js";
|
|
13
13
|
import { rootOf } from "../steps.js";
|
|
14
14
|
import { recalledAccount, rememberAccount } from "./accounts.js";
|
|
@@ -325,7 +325,6 @@ const pathOf = (command) => {
|
|
|
325
325
|
};
|
|
326
326
|
/** `deny` stops a read too, before anything connects: what the profile may not see is never fetched. */
|
|
327
327
|
const refuseDenied = (command, settings) => {
|
|
328
|
-
assertStatsPermissionsCurrent(pathOf(command), settings.permissions);
|
|
329
328
|
for (const key of readKeysForCommand(pathOf(command))) {
|
|
330
329
|
const { level, key: named } = levelFor(settings.permissions, key);
|
|
331
330
|
if (level !== "deny")
|
|
@@ -356,9 +355,7 @@ export const messengerContext = (command, messenger, resolving = {}) => {
|
|
|
356
355
|
const { profile } = base.settings;
|
|
357
356
|
refuseDenied(command, base.settings);
|
|
358
357
|
refuseLacking(messenger, pathOf(command));
|
|
359
|
-
|
|
360
|
-
throw new CliError("configuration_error", `permissions.${key} names no command — remove it with \`${app.command} ${asFirstWord(profile)}config unset permissions.${key}\``, { permission: key });
|
|
361
|
-
}
|
|
358
|
+
refuseUnknownPermissionKeys(command, base.settings.permissions, `${app.command} ${asFirstWord(profile)}config unset`, messenger.permissionKey);
|
|
362
359
|
// Read when warning, not now: serve swaps the renderer's methods for log records after this.
|
|
363
360
|
const warn = (text, warning) => base.renderer.warn(text, warning);
|
|
364
361
|
const accountFor = () => {
|
|
@@ -9,3 +9,5 @@ export declare const knownPermissionKeys: (command: Command, keyOf?: PermissionK
|
|
|
9
9
|
/** The known keys one level under the unknown key's parent, to name in the refusal. */
|
|
10
10
|
export declare const knownBeside: (key: PermissionKey, known: ReadonlySet<PermissionKey>) => PermissionKey[];
|
|
11
11
|
export declare const unknownPermissionKeys: (levels: Readonly<Record<PermissionKey, unknown>>, known: ReadonlySet<PermissionKey>) => PermissionKey[];
|
|
12
|
+
/** A saved key no command checks would do nothing, so the command refuses to run; `unset` names the fix. */
|
|
13
|
+
export declare const refuseUnknownPermissionKeys: (command: Command, levels: Readonly<Record<PermissionKey, unknown>>, unset: string, keyOf?: PermissionKeyOf) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { CEILING_KEY, keyForCommand, WRITE_KEYS } from "../sends/permissions.js";
|
|
3
3
|
const pathOf = (command) => {
|
|
4
4
|
const words = [];
|
|
@@ -20,12 +20,12 @@ const everyCommand = (command) => [command, ...command.commands.flatMap(everyCom
|
|
|
20
20
|
export const knownPermissionKeys = (command, keyOf = (one) => keyForCommand(pathOf(one)), extra = []) => {
|
|
21
21
|
const known = new Set();
|
|
22
22
|
const add = (key) => {
|
|
23
|
-
key = canonicalPermissionKey(key);
|
|
24
23
|
for (let parts = key.split("."); parts.length > 0; parts = parts.slice(0, -1))
|
|
25
24
|
known.add(parts.join("."));
|
|
26
25
|
};
|
|
27
26
|
for (const one of everyCommand(rootOf(command))) {
|
|
28
|
-
const
|
|
27
|
+
const own = keyOf(one);
|
|
28
|
+
const key = own === undefined ? keyForCommand(pathOf(one)) : own;
|
|
29
29
|
if (key)
|
|
30
30
|
add(key);
|
|
31
31
|
}
|
|
@@ -42,3 +42,10 @@ export const knownBeside = (key, known) => {
|
|
|
42
42
|
.sort();
|
|
43
43
|
};
|
|
44
44
|
export const unknownPermissionKeys = (levels, known) => Object.keys(levels).filter((key) => key !== CEILING_KEY && !known.has(key));
|
|
45
|
+
/** A saved key no command checks would do nothing, so the command refuses to run; `unset` names the fix. */
|
|
46
|
+
export const refuseUnknownPermissionKeys = (command, levels, unset, keyOf) => {
|
|
47
|
+
const [key] = unknownPermissionKeys(levels, knownPermissionKeys(command, keyOf));
|
|
48
|
+
if (key === undefined)
|
|
49
|
+
return;
|
|
50
|
+
throw new CliError("configuration_error", `permissions.${key} names no command — remove it with \`${unset} permissions.${key}\``, { permission: key });
|
|
51
|
+
};
|
package/dist/cli/preview.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import { type Level } from "../sends/permissions.js";
|
|
2
|
+
import { type Level, type PermissionKey } from "../sends/permissions.js";
|
|
3
3
|
export declare class PreviewComplete extends Error {
|
|
4
4
|
}
|
|
5
|
-
export declare const preview: (command: Command, permissions: Readonly<Record<string, Level
|
|
5
|
+
export declare const preview: (command: Command, permissions: Readonly<Record<string, Level>>, unset: string, keyOf?: (path: readonly string[]) => PermissionKey | null | undefined) => {
|
|
6
6
|
preview: boolean;
|
|
7
7
|
path: string[];
|
|
8
8
|
permission: {
|
package/dist/cli/preview.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { metaOf } from "@wirecat/cli-base/commands";
|
|
3
3
|
import { writes as writesAnything } from "@wirecat/cli-base/standards";
|
|
4
|
-
import {
|
|
4
|
+
import { keyForCommand, levelFor, readKeysForCommand, WRITE_KEYS, } from "../sends/permissions.js";
|
|
5
5
|
import { commandPathOf } from "./command-contract.js";
|
|
6
|
+
import { refuseUnknownPermissionKeys } from "./permission-keys.js";
|
|
6
7
|
export class PreviewComplete extends Error {
|
|
7
8
|
}
|
|
8
|
-
export const preview = (command, permissions) => {
|
|
9
|
+
export const preview = (command, permissions, unset, keyOf) => {
|
|
9
10
|
const path = commandPathOf(command);
|
|
11
|
+
refuseUnknownPermissionKeys(command, permissions, unset, keyOf && ((one) => keyOf(commandPathOf(one))));
|
|
10
12
|
const key = keyForCommand(path);
|
|
11
13
|
const level = key ? levelFor(permissions, key).level : "allow";
|
|
12
|
-
assertStatsPermissionsCurrent(path, permissions);
|
|
13
14
|
for (const source of readKeysForCommand(path)) {
|
|
14
15
|
if (levelFor(permissions, source).level === "deny")
|
|
15
16
|
throw new CliError("permission_error", `this profile does not permit ${source}`, { permission: source });
|
package/dist/cli/profile.d.ts
CHANGED
|
@@ -28,3 +28,5 @@ export declare const valuedOptions: (program: Command) => Set<string>;
|
|
|
28
28
|
export declare const refuseCommandName: (profile: string, words: Set<string>, command: string) => void;
|
|
29
29
|
/** The root of the command tree, from anywhere inside it. */
|
|
30
30
|
export declare const rootOf: (command: Command) => Command;
|
|
31
|
+
/** A command this CLI really has, for an example: the first that takes a profile, down to its first leaf. */
|
|
32
|
+
export declare const sampleCommand: (commands: readonly Command[]) => string | undefined;
|
package/dist/cli/profile.js
CHANGED
|
@@ -59,3 +59,13 @@ export const rootOf = (command) => {
|
|
|
59
59
|
root = root.parent;
|
|
60
60
|
return root;
|
|
61
61
|
};
|
|
62
|
+
/** A command this CLI really has, for an example: the first that takes a profile, down to its first leaf. */
|
|
63
|
+
export const sampleCommand = (commands) => {
|
|
64
|
+
const words = [];
|
|
65
|
+
let command = commands.find((one) => !PROFILE_FREE.has(one.name()));
|
|
66
|
+
while (command) {
|
|
67
|
+
words.push(command.name());
|
|
68
|
+
command = command.commands.find((one) => one.name() !== "help");
|
|
69
|
+
}
|
|
70
|
+
return words.length ? words.join(" ") : undefined;
|
|
71
|
+
};
|
package/dist/cli/program.js
CHANGED
|
@@ -25,9 +25,8 @@ import { installHelp } from "./help/first-screen.js";
|
|
|
25
25
|
import { showInputFields } from "./input-fields.js";
|
|
26
26
|
import { MAX_BUFFERED_INPUT, provideInputPolicy } from "./input-policy.js";
|
|
27
27
|
import { checkJson, liftJson } from "./json-fields.js";
|
|
28
|
-
import { movedCommandError } from "./moved.js";
|
|
29
28
|
import { PreviewComplete, preview } from "./preview.js";
|
|
30
|
-
import { commandWords, liftProfile, PROFILE_FREE, valuedOptions } from "./profile.js";
|
|
29
|
+
import { asFirstWord, commandWords, liftProfile, PROFILE_FREE, sampleCommand, valuedOptions } from "./profile.js";
|
|
31
30
|
import { absolutePaths, relayTarget } from "./relay-route.js";
|
|
32
31
|
import { settingsFor } from "./settings.js";
|
|
33
32
|
import { assertSharedContracts } from "./shared-contracts.js";
|
|
@@ -38,11 +37,15 @@ import { commandAt, lineOf, withLines } from "./steps.js";
|
|
|
38
37
|
*/
|
|
39
38
|
export const createProgram = ({ app, commands, configure, firstScreen }, { out, err } = {}) => {
|
|
40
39
|
const program = new Command();
|
|
40
|
+
const mounted = commands();
|
|
41
|
+
const messenger = app.messenger !== false;
|
|
42
|
+
const found = messenger ? "found messages" : "found items";
|
|
43
|
+
const content = messenger ? "message content" : "content";
|
|
41
44
|
program
|
|
42
45
|
.name(app.command)
|
|
43
46
|
.usage("[profile] [options] <command>")
|
|
44
47
|
.description(`${app.description}\n\n` +
|
|
45
|
-
`The first word is the profile whenever it is not a command — \`${app.command} personal chats list\`.\n` +
|
|
48
|
+
`The first word is the profile whenever it is not a command — \`${app.command} personal ${messenger ? "chats list" : (sampleCommand(mounted) ?? "<command>")}\`.\n` +
|
|
46
49
|
`\`${app.envPrefix}_PROFILE\` says the same thing for a whole shell session.\n` +
|
|
47
50
|
"No profile is assumed: without one, a command that uses an account stops and lists the profiles here.")
|
|
48
51
|
.version(app.version, "-V, --version")
|
|
@@ -50,21 +53,21 @@ export const createProgram = ({ app, commands, configure, firstScreen }, { out,
|
|
|
50
53
|
.option("--json", "machine-readable output: one JSON value on stdout; --json=id,text keeps only those fields")
|
|
51
54
|
.option("--jsonl", "machine-readable output: one JSON object per line, for streaming and jq")
|
|
52
55
|
.option("--quiet", "diagnostics off; a failure is still said")
|
|
53
|
-
.option("--trace",
|
|
56
|
+
.option("--trace", `the connection's own log lines on stderr — never ${content}`)
|
|
54
57
|
.option("--debug", "on a failure, its whole record — cause and stack — in place of the short line")
|
|
55
58
|
.option("--timeout <duration>", "give up on the whole command after this — 30s, 2m, 500ms")
|
|
56
59
|
.option("--offline", "answer from what was recorded and never connect; fails if nothing was")
|
|
57
60
|
.option("--no-input", "never prompt or open interactive login; piped input remains available")
|
|
58
61
|
.option("--max-input-bytes <bytes>", "maximum buffered input bytes (default: 16777216)")
|
|
59
62
|
.option("--max-output-bytes <bytes>", "maximum machine output bytes (default: 4194304; 0 disables)")
|
|
60
|
-
.option("--brief",
|
|
61
|
-
.option("--full",
|
|
63
|
+
.option("--brief", `${found} as reference, title, time, ${messenger ? "sender" : "author"} and excerpt only`)
|
|
64
|
+
.option("--full", `${found} with their whole text and every provider field`)
|
|
62
65
|
.option("--dry-run", "preview parsed arguments and permissions before running the action")
|
|
63
66
|
.option("--yes", "go ahead without the question an ask level puts before a write")
|
|
64
|
-
.option("--record",
|
|
67
|
+
.option("--record", `keep this run — ids and timings, never ${content}`)
|
|
65
68
|
.option("--no-record", "do not keep it, whatever the configuration says")
|
|
66
69
|
.showHelpAfterError();
|
|
67
|
-
for (const command of
|
|
70
|
+
for (const command of mounted)
|
|
68
71
|
program.addCommand(command);
|
|
69
72
|
configure?.(program);
|
|
70
73
|
assertSharedContracts(program);
|
|
@@ -377,7 +380,9 @@ const runOnce = async (argv, definition, options) => {
|
|
|
377
380
|
needsProfile: keyForCommand(path) !== null,
|
|
378
381
|
});
|
|
379
382
|
const permissions = "permissions" in resolved ? resolved.permissions : {};
|
|
380
|
-
|
|
383
|
+
const unset = `${command} ${asFirstWord(resolved.profile)}config unset${path[0] === "bot" ? " --bot" : ""}`;
|
|
384
|
+
const previewed = preview(action, permissions, unset, definition.configuration?.permissionKey);
|
|
385
|
+
renderAnswer(action, outputFor(action), itemOf(snakeKeys(previewed)));
|
|
381
386
|
throw new PreviewComplete();
|
|
382
387
|
}
|
|
383
388
|
// Not inside the scheduled run itself: its own failure is already on its way to last.json.
|
|
@@ -421,11 +426,6 @@ const runOnce = async (argv, definition, options) => {
|
|
|
421
426
|
const settled = await settleFailure(failure, { definition, program, rest, profile, options });
|
|
422
427
|
return report(streams, reporting, failed(definition, program, rest, failure, settled, debug));
|
|
423
428
|
}
|
|
424
|
-
const failure = movedCommandError(program, rest);
|
|
425
|
-
if (failure !== undefined) {
|
|
426
|
-
const settled = await settleFailure(failure, { definition, program, rest, profile, options });
|
|
427
|
-
return report(streams, reporting, failed(definition, program, rest, failure, settled, debug));
|
|
428
|
-
}
|
|
429
429
|
try {
|
|
430
430
|
await control.race(() => program.parseAsync(rest, { from: "user" }));
|
|
431
431
|
const code = exit.code ?? (process.exitCode === undefined ? 0 : Number(process.exitCode));
|
package/dist/cli/settings.d.ts
CHANGED
|
@@ -140,8 +140,6 @@ export interface ResolveOptions {
|
|
|
140
140
|
needsProfile?: boolean;
|
|
141
141
|
/** Read the config file without writing it: no default file. For a dry run. */
|
|
142
142
|
readOnly?: boolean;
|
|
143
|
-
/** Skip moved permission keys instead of refusing them, so `config set` and `unset` can fix one. */
|
|
144
|
-
skipMovedPermissions?: boolean;
|
|
145
143
|
}
|
|
146
144
|
/** Where `profile` came from when none was named and the command needs none. */
|
|
147
145
|
export declare const NONE_NAMED = "none named";
|
package/dist/cli/settings.js
CHANGED
|
@@ -4,7 +4,6 @@ import * as v from "valibot";
|
|
|
4
4
|
import { AI_ENTRIES, resolveAISettings } from "../analysis/settings.js";
|
|
5
5
|
import { parseDuration } from "../domain/duration.js";
|
|
6
6
|
import { envName, readOnlyLockOf } from "../ports/app.js";
|
|
7
|
-
import { canonicalPermissionKey } from "../sends/permission-aliases.js";
|
|
8
7
|
import { assertOverridesTighten, CEILING_KEY, LEVELS, layerPermissions, permissionOverrides, RESOURCES, } from "../sends/permissions.js";
|
|
9
8
|
import { storePath } from "../store/path.js";
|
|
10
9
|
import { sharedConfigPath } from "../store/shared-config.js";
|
|
@@ -78,7 +77,6 @@ const SHARED_DEFAULTS_ONLY = {
|
|
|
78
77
|
update_check: v.optional(flag),
|
|
79
78
|
skill_hint: v.optional(flag),
|
|
80
79
|
};
|
|
81
|
-
const withoutMoved = (levels) => levels && Object.fromEntries(Object.entries(levels).filter(([key]) => canonicalPermissionKey(key) === key));
|
|
82
80
|
/** Where `profile` came from when none was named and the command needs none. */
|
|
83
81
|
export const NONE_NAMED = "none named";
|
|
84
82
|
/** A name for a command that speaks for no account; never chosen for one that does. */
|
|
@@ -232,9 +230,7 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
232
230
|
: { value: false, from: "default" };
|
|
233
231
|
const fileLayers = layers.map(([from, scope]) => [
|
|
234
232
|
from,
|
|
235
|
-
|
|
236
|
-
? withoutMoved(scope?.permissions)
|
|
237
|
-
: scope?.permissions,
|
|
233
|
+
scope?.permissions,
|
|
238
234
|
]);
|
|
239
235
|
const overrides = permissionOverrides(flags.permission);
|
|
240
236
|
assertOverridesTighten(overrides, layerPermissions(fileLayers).levels);
|
package/dist/mcp/surface.js
CHANGED
|
@@ -2,7 +2,6 @@ import { toJsonSchema, toStandardJsonSchema } from "@valibot/to-json-schema";
|
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
3
|
import * as v from "valibot";
|
|
4
4
|
import { answerSchema, listOf } from "../cli/envelope.js";
|
|
5
|
-
import { movedPath, movedPointer } from "../cli/moved.js";
|
|
6
5
|
import { ENTITIES } from "../entities/index.js";
|
|
7
6
|
import { agentArguments } from "./text.js";
|
|
8
7
|
import { answered, commandOf, entryRunner, failed, inputOf, outputSchemaOf, READ, syncAllowedFor, UNTRUSTED, WRITE, } from "./tool.js";
|
|
@@ -63,10 +62,16 @@ export const registerCommands = (server, prefix, commands, errors,
|
|
|
63
62
|
/** Throws the refusal of a command left out because the lock or a level stops it, so the agent learns why. */
|
|
64
63
|
withheld) => {
|
|
65
64
|
const byNormal = new Map(Object.keys(commands).map((command) => [normal(command), command]));
|
|
65
|
+
// No AppIdentity reaches here; only a messenger's catalogue has `messages` commands.
|
|
66
|
+
const messenger = Object.keys(commands).some((command) => command.split(" ")[0] === "messages");
|
|
67
|
+
const tasks = messenger ? '"unread", "send message", "chat members"' : '"list", "show", "change a setting"';
|
|
68
|
+
const sample = messenger
|
|
69
|
+
? "messages list"
|
|
70
|
+
: (Object.entries(commands).find(([, one]) => !one.writes)?.[0] ?? Object.keys(commands)[0] ?? "");
|
|
66
71
|
server.registerTool(`${prefix}_tools_search`, {
|
|
67
72
|
outputSchema: toStandardJsonSchema(v.looseObject({})),
|
|
68
73
|
title: "Find a command",
|
|
69
|
-
description: `Find the command for a task —
|
|
74
|
+
description: `Find the command for a task — ${tasks} — before calling ` +
|
|
70
75
|
`${prefix}_read or ${prefix}_write with it. Each match gives its command, whether it writes, its arguments and the shape of its answer. ` +
|
|
71
76
|
"With no words, lists every command and title, without arguments.",
|
|
72
77
|
inputSchema: toStandardJsonSchema(v.strictObject({ query: v.optional(v.pipe(v.string(), v.description("words that describe the task"))) })),
|
|
@@ -103,9 +108,6 @@ withheld) => {
|
|
|
103
108
|
const one = command === undefined ? undefined : commands[command];
|
|
104
109
|
if (command === undefined || !one) {
|
|
105
110
|
withheld?.(normal(asked));
|
|
106
|
-
const moved = movedPath(normal(asked).split(" "), (path) => byNormal.has(path));
|
|
107
|
-
if (moved !== undefined)
|
|
108
|
-
throw new CliError("validation_error", `no command "${asked}" here — ${movedPointer(moved)}`);
|
|
109
111
|
const near = nearest(commands, asked);
|
|
110
112
|
throw new CliError("validation_error", `no command "${asked}" here${near.length ? ` — did you mean ${near.map((name) => `"${name}"`).join(", ")}?` : ""}; ${prefix}_tools_search finds one`);
|
|
111
113
|
}
|
|
@@ -126,7 +128,7 @@ withheld) => {
|
|
|
126
128
|
}
|
|
127
129
|
};
|
|
128
130
|
const call = v.strictObject({
|
|
129
|
-
command: v.pipe(v.string(), v.minLength(1), v.description(`a command ${prefix}_tools_search returned: "
|
|
131
|
+
command: v.pipe(v.string(), v.minLength(1), v.description(`a command ${prefix}_tools_search returned: "${sample}"`)),
|
|
130
132
|
arguments: v.optional(v.pipe(v.record(v.string(), v.unknown()), v.description("that command's arguments"))),
|
|
131
133
|
});
|
|
132
134
|
server.registerTool(`${prefix}_read`, {
|
|
@@ -140,7 +142,7 @@ withheld) => {
|
|
|
140
142
|
server.registerTool(`${prefix}_write`, {
|
|
141
143
|
outputSchema: toStandardJsonSchema(ANSWER),
|
|
142
144
|
title: "Run a writing command",
|
|
143
|
-
description: `Run one command that changes something — sends, edits, deletes, joins — with the arguments ${prefix}_tools_search showed. ${UNTRUSTED}`,
|
|
145
|
+
description: `Run one command that changes something — ${messenger ? "sends, edits, deletes, joins" : "creates, edits, deletes"} — with the arguments ${prefix}_tools_search showed. ${UNTRUSTED}`,
|
|
144
146
|
inputSchema: toStandardJsonSchema(call),
|
|
145
147
|
annotations: WRITE,
|
|
146
148
|
}, runner(true));
|
package/dist/mcp/tool.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { type EntityName } from "../entities/index.js";
|
|
|
10
10
|
import { type AppErrors } from "../errors/reasons.js";
|
|
11
11
|
import type { MessengerAdapter } from "../ports/messenger.js";
|
|
12
12
|
import type { SendGuard } from "../sends/guard.js";
|
|
13
|
-
import { type
|
|
13
|
+
import { type PermissionKey } from "../sends/permissions.js";
|
|
14
14
|
import { type Services } from "../services/index.js";
|
|
15
15
|
import type { AccountKey, MessageStore } from "../store/store.js";
|
|
16
16
|
import type { MessengerSession } from "./session.js";
|
|
@@ -50,8 +50,6 @@ interface Tool<S extends Input> {
|
|
|
50
50
|
/** What it touches, where its hints cannot say: a read of the messenger kept in the store is not a write there. */
|
|
51
51
|
effects?: Effects;
|
|
52
52
|
_meta?: Record<string, unknown>;
|
|
53
|
-
/** A write, and what the profile's `allow` must name for it to be offered. */
|
|
54
|
-
permission?: Permission;
|
|
55
53
|
/** Writes what the owner keeps, as its command (`isStoreChange`): `<PREFIX>_READ_ONLY` refuses it. */
|
|
56
54
|
changesStore?: true;
|
|
57
55
|
/** The CLI command it answers as, where its name does not spell it: `chats send-as`. Its level is read from it. */
|
package/dist/mcp/tool.js
CHANGED
|
@@ -12,7 +12,7 @@ import { failureContext } from "../errors/reasons.js";
|
|
|
12
12
|
import { partialOutcome, startRecording } from "../runs/recording.js";
|
|
13
13
|
import { newRunId } from "../runs/run.js";
|
|
14
14
|
import { inRun } from "../runs/scope.js";
|
|
15
|
-
import {
|
|
15
|
+
import { keyForCommand, levelFor, readKeysForCommand } from "../sends/permissions.js";
|
|
16
16
|
import { onlineDeps, storeModeDeps } from "../services/deps.js";
|
|
17
17
|
import { servicesFor } from "../services/index.js";
|
|
18
18
|
import { agentJson } from "./text.js";
|
|
@@ -178,7 +178,6 @@ const callRunner = ({ messenger, session, withStore, withServices, defaults, aro
|
|
|
178
178
|
definition.check?.(args);
|
|
179
179
|
if (definition.changesStore)
|
|
180
180
|
refuseStoreChange(messenger.app, defaults.env);
|
|
181
|
-
assertStatsPermissionsCurrent(path, defaults.settings.permissions ?? {});
|
|
182
181
|
refuseLacking(messenger, path);
|
|
183
182
|
for (const permission of readKeysForCommand(path)) {
|
|
184
183
|
if (levelFor(defaults.settings.permissions ?? {}, permission).level === "deny")
|