@wirecat/cli-core 0.421.0 → 0.423.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 +29 -1
- package/dist/analysis/settings.d.ts +0 -6
- package/dist/analysis/settings.js +2 -5
- package/dist/cli/analysis-choice.d.ts +0 -1
- package/dist/cli/analysis-choice.js +9 -22
- package/dist/cli/bot/context.js +1 -1
- package/dist/cli/commands-command.js +1 -1
- package/dist/cli/config-command.d.ts +0 -4
- package/dist/cli/config-command.js +36 -26
- package/dist/cli/config-issues.d.ts +52 -0
- package/dist/cli/config-issues.js +187 -0
- package/dist/cli/config-keys.js +4 -28
- package/dist/cli/embedding-choice.d.ts +1 -2
- package/dist/cli/embedding-choice.js +2 -4
- package/dist/cli/first-run.d.ts +0 -16
- package/dist/cli/first-run.js +1 -6
- package/dist/cli/index.d.ts +3 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/messenger/context.js +1 -1
- package/dist/cli/messenger/conversations-command.js +5 -9
- package/dist/cli/messenger/doctor-command.js +38 -4
- package/dist/cli/messenger/inbox.js +2 -6
- package/dist/cli/messenger/private-people-command.js +1 -2
- package/dist/cli/messenger/review.js +2 -5
- package/dist/cli/permission-keys.d.ts +9 -2
- package/dist/cli/permission-keys.js +21 -6
- package/dist/cli/preview.d.ts +3 -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.d.ts +1 -1
- package/dist/cli/program.js +18 -10
- package/dist/cli/settings.d.ts +21 -117
- package/dist/cli/settings.js +107 -114
- package/dist/cli/shared-command-exports.d.ts +4 -232
- package/dist/mcp/surface.js +9 -3
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/models/settings.d.ts +1 -7
- package/dist/models/settings.js +1 -18
- package/dist/ports/app.d.ts +6 -0
- package/dist/search/account-list.js +8 -7
- 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/package.json +1 -1
|
@@ -6,10 +6,8 @@ import { isLocal, remoteModel } from "../embeddings/remote.js";
|
|
|
6
6
|
import { readOnlyLockOf, readOnlyRefusal } from "../ports/app.js";
|
|
7
7
|
import { typedCommand } from "../runs/scope.js";
|
|
8
8
|
/** A local model id, or a remote model with its key from `models keys set`. */
|
|
9
|
-
export const embeddingChoice = ({ model,
|
|
10
|
-
|
|
11
|
-
throw new CliError("validation_error", "give --embedding-provider or its legacy --provider spelling, not both");
|
|
12
|
-
provider = embeddingProvider ?? provider;
|
|
9
|
+
export const embeddingChoice = ({ model, embeddingProvider, baseUrl, dims, concurrency }, app, settings, env, { needKey = true, keyring } = {}) => {
|
|
10
|
+
let provider = embeddingProvider;
|
|
13
11
|
const flagsChooseProvider = (provider !== undefined && provider !== (settings.embeddingProvider ?? "local")) ||
|
|
14
12
|
(baseUrl !== undefined && baseUrl.replace(/\/+$/, "") !== settings.embeddingBaseUrl?.replace(/\/+$/, ""));
|
|
15
13
|
provider ??= flagsChooseProvider ? "openai" : (settings.embeddingProvider ?? "local");
|
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";
|
|
@@ -51,6 +52,7 @@ export type { CommandAvailability, DiscoveredCommand } from "./command-availabil
|
|
|
51
52
|
export { commandContract, type JsonSchema, SCHEMA_DIALECT } from "./command-contract.js";
|
|
52
53
|
export { CONTRACT } from "./commands-command.js";
|
|
53
54
|
export { refuseUnknownKey } from "./config-command.js";
|
|
55
|
+
export type { ConfigIssue, ConfigIssueKind } from "./config-issues.js";
|
|
54
56
|
export { loadSnakeCaseConfig } from "./config-keys.js";
|
|
55
57
|
export { type AgentRun, type BaseContext, type BaseEnvironment, baseContext, environmentOf, outputFor, provide, secretInputOf, } from "./context.js";
|
|
56
58
|
export { addContractInfo, type ContractEvidenceOptions, contractEvidence } from "./contract-evidence.js";
|
|
@@ -62,7 +64,7 @@ export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNoth
|
|
|
62
64
|
export { type CliEntry, type EntryContext, type EntryIo, startCli } from "./entry.js";
|
|
63
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";
|
|
64
66
|
export { withAbort } from "./execution.js";
|
|
65
|
-
export { askSetupQuestions, asksAtStart, type FirstRun, type FirstRunIo,
|
|
67
|
+
export { askSetupQuestions, asksAtStart, type FirstRun, type FirstRunIo, setupAtStart, systemLanguage, } from "./first-run.js";
|
|
66
68
|
export { checkGuide, checkStep, type GuideOptions, type GuideReference, type GuideStep, loadScenarios, MEMO_SCENARIOS, type Scenario, shellProblems, } from "./guide.js";
|
|
67
69
|
export { type CommandHelp, commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, type NextChoice, nextUsually, } from "./help/command-help.js";
|
|
68
70
|
export { commandsText } from "./help/commands-text.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";
|
|
@@ -355,7 +355,7 @@ export const messengerContext = (command, messenger, resolving = {}) => {
|
|
|
355
355
|
const { profile } = base.settings;
|
|
356
356
|
refuseDenied(command, base.settings);
|
|
357
357
|
refuseLacking(messenger, pathOf(command));
|
|
358
|
-
refuseUnknownPermissionKeys(command, base.settings
|
|
358
|
+
refuseUnknownPermissionKeys(command, base.settings, app.command, messenger.permissionKey);
|
|
359
359
|
// Read when warning, not now: serve swaps the renderer's methods for log records after this.
|
|
360
360
|
const warn = (text, warning) => base.renderer.warn(text, warning);
|
|
361
361
|
const accountFor = () => {
|
|
@@ -37,8 +37,8 @@ const conversationBuildCommand = (messenger) => boundOptions(reachesOtherService
|
|
|
37
37
|
.option("--chat <chat>", messenger.chatArgument)
|
|
38
38
|
.option("--analyze", "link batches using the configured analysis provider; requires --chat and remembers consent for this chat/provider")
|
|
39
39
|
.addOption(new Option("--analysis-provider <provider>", "analysis: agent, openai or anthropic"))
|
|
40
|
-
.option("--model <model>", "analysis model; overrides
|
|
41
|
-
.option("--base-url <url>", "analysis API endpoint; overrides
|
|
40
|
+
.option("--model <model>", "analysis model; overrides models.analysis.model")
|
|
41
|
+
.option("--base-url <url>", "analysis API endpoint; overrides models.analysis.base_url")
|
|
42
42
|
.option("--size <n>", "analysis answer messages per batch, 10–200; default 50", positiveCount("--size"))
|
|
43
43
|
.option("--max-tokens <n>", "analysis input/output reservation cap per run; default 100000", positiveCount("--max-tokens")), { chunks: false }).action(async function () {
|
|
44
44
|
const options = this.optsWithGlobals();
|
|
@@ -93,7 +93,6 @@ const conversationBuildCommand = (messenger) => boundOptions(reachesOtherService
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
if (options.analysisProvider !== undefined ||
|
|
96
|
-
options.provider !== undefined ||
|
|
97
96
|
options.model !== undefined ||
|
|
98
97
|
options.baseUrl !== undefined ||
|
|
99
98
|
options.size !== undefined ||
|
|
@@ -133,10 +132,7 @@ const embeddingClearCommand = (messenger) => withModelOptions(destroysNothing(an
|
|
|
133
132
|
const chat = chatOf(options);
|
|
134
133
|
const context = messengerContext(this, messenger);
|
|
135
134
|
refuseLocalWrite(context, messenger.app.command, EMBED_KEY);
|
|
136
|
-
const given = options.model !== undefined ||
|
|
137
|
-
options.embeddingProvider !== undefined ||
|
|
138
|
-
options.provider !== undefined ||
|
|
139
|
-
options.baseUrl !== undefined;
|
|
135
|
+
const given = options.model !== undefined || options.embeddingProvider !== undefined || options.baseUrl !== undefined;
|
|
140
136
|
const model = given ? choiceOf(options, messenger, context, { needKey: false }) : undefined;
|
|
141
137
|
const cleared = await context.withServices((services) => services.embeddings.clear(chat, model));
|
|
142
138
|
renderAnswer(this, context, itemOf(cleared), () => `${cleared.cleared} vectors dropped\n`);
|
|
@@ -379,14 +375,14 @@ export const conversationsCommand = (messenger) => {
|
|
|
379
375
|
.option("--chat <chat>", "revoke only this chat's consents; defaults to every chat")
|
|
380
376
|
.addOption(new Option("--consent-provider <identity>", "exact provider identity from consents list; defaults to every provider"))
|
|
381
377
|
.action(async function () {
|
|
382
|
-
const { chat,
|
|
378
|
+
const { chat, consentProvider } = this.opts();
|
|
383
379
|
const context = messengerContext(this, messenger);
|
|
384
380
|
refuseLocalWrite(context, messenger.app.command, LINKS_KEY);
|
|
385
381
|
await context.withStore(async (store, account) => {
|
|
386
382
|
const chatId = chat === undefined
|
|
387
383
|
? undefined
|
|
388
384
|
: (await servicesFor(storedDeps(messenger, store, account, context.guard)).conversations.batchStatus(chat, BATCH_SIZE.default)).chat;
|
|
389
|
-
await analysisConsents(store, account).revoke(chatId, consentProvider
|
|
385
|
+
await analysisConsents(store, account).revoke(chatId, consentProvider);
|
|
390
386
|
});
|
|
391
387
|
renderAnswer(this, context, itemOf({ revoked: true }));
|
|
392
388
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { dirname, resolve } from "node:path";
|
|
2
|
-
import { writeSecurely } from "@wirecat/cli-base";
|
|
3
|
+
import { configFilePath, resolvePaths, writeSecurely } from "@wirecat/cli-base";
|
|
3
4
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
import { engineStates } from "../../attachments/extract.js";
|
|
@@ -11,6 +12,8 @@ import { storePath } from "../../store/path.js";
|
|
|
11
12
|
import { baseContext, environmentOf, outputFor } from "../context.js";
|
|
12
13
|
import { destroysNothing, LOCAL_WRITE, NO_EFFECTS, REMOTE_READ } from "../effects.js";
|
|
13
14
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
15
|
+
import { unknownPermissionIssues } from "../permission-keys.js";
|
|
16
|
+
import { NONE_NAMED } from "../settings.js";
|
|
14
17
|
import { lineOf, nextStep } from "../steps.js";
|
|
15
18
|
import { recalledAccount } from "./accounts.js";
|
|
16
19
|
import { messengerContext } from "./context.js";
|
|
@@ -126,14 +129,45 @@ const diagnose = async (command, messenger) => {
|
|
|
126
129
|
platform: process.platform,
|
|
127
130
|
arch: process.arch,
|
|
128
131
|
};
|
|
132
|
+
// Every problem in the file is reported, not refused: doctor is what a person runs when nothing else will.
|
|
129
133
|
let context;
|
|
130
134
|
try {
|
|
131
|
-
context = baseContext(command, messenger.resolveSettings);
|
|
135
|
+
context = baseContext(command, (flags, options) => messenger.resolveSettings(flags, { ...options, needsProfile: false, readOnly: true, withIssues: true }));
|
|
132
136
|
}
|
|
133
137
|
catch (error) {
|
|
134
|
-
|
|
138
|
+
// Not the file: a profile lock, a flag or an environment variable the settings refuse.
|
|
139
|
+
const path = configFilePath(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).config);
|
|
140
|
+
return {
|
|
141
|
+
report: {
|
|
142
|
+
cli,
|
|
143
|
+
config: { path, found: existsSync(path), issues: [] },
|
|
144
|
+
settings: { error: messageOf(error) },
|
|
145
|
+
store: snakeKeys(await storeSummary(env)),
|
|
146
|
+
runs: { directory: runsDirFor(app, env), kept: listRuns(runsDirFor(app, env)).length },
|
|
147
|
+
},
|
|
148
|
+
};
|
|
135
149
|
}
|
|
136
150
|
const { settings, renderer } = context;
|
|
151
|
+
const config = {
|
|
152
|
+
path: settings.configPath,
|
|
153
|
+
found: settings.configFound,
|
|
154
|
+
issues: [
|
|
155
|
+
...(settings.configIssues ?? []),
|
|
156
|
+
...unknownPermissionIssues(command, settings, app.command, messenger.permissionKey),
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
if (settings.sources.profile === NONE_NAMED)
|
|
160
|
+
return {
|
|
161
|
+
renderer,
|
|
162
|
+
report: {
|
|
163
|
+
cli,
|
|
164
|
+
profile: null,
|
|
165
|
+
config,
|
|
166
|
+
store: snakeKeys(await storeSummary(env)),
|
|
167
|
+
readers: engineStates(),
|
|
168
|
+
runs: { directory: runsDirFor(app, env), kept: listRuns(runsDirFor(app, env)).length },
|
|
169
|
+
},
|
|
170
|
+
};
|
|
137
171
|
const { profile } = settings;
|
|
138
172
|
const account = recalledAccount(app, provider, profile, env);
|
|
139
173
|
const store = storePath(env);
|
|
@@ -151,7 +185,7 @@ const diagnose = async (command, messenger) => {
|
|
|
151
185
|
report: {
|
|
152
186
|
cli,
|
|
153
187
|
profile,
|
|
154
|
-
config
|
|
188
|
+
config,
|
|
155
189
|
account: { remembered: account?.account ?? null },
|
|
156
190
|
// A session file on disk says nothing about whether the messenger still accepts it.
|
|
157
191
|
login: { state: "not checked" },
|
|
@@ -2,7 +2,6 @@ import { CliError } from "@wirecat/cli-base";
|
|
|
2
2
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { renderMessages } from "../../render/messages.js";
|
|
5
|
-
import { kindsOf } from "../../services/inbox.js";
|
|
6
5
|
import { modelWith } from "../../speech/hearing.js";
|
|
7
6
|
import { REMOTE_READ } from "../effects.js";
|
|
8
7
|
import { renderAnswer } from "../envelope.js";
|
|
@@ -44,15 +43,12 @@ export const inboxCommand = (messenger) => annotate(new Command("inbox"), {
|
|
|
44
43
|
.option(...MODEL_OPTION)
|
|
45
44
|
.addHelpText("after", "To mark a chat read, use `chats mark-read <chat>`. The other side sees it.")
|
|
46
45
|
.action(async function (query) {
|
|
47
|
-
const { new: fresh, all,
|
|
46
|
+
const { new: fresh, all, transcribe, model, } = this.opts();
|
|
48
47
|
const hearWith = modelWith(transcribe, model);
|
|
49
48
|
const context = messengerContext(this, messenger);
|
|
50
49
|
const { app } = messenger;
|
|
51
50
|
const { settings, renderer, format, env } = context;
|
|
52
|
-
const { since, meta, kinds
|
|
53
|
-
if (kind !== undefined && queriedKinds !== undefined)
|
|
54
|
-
throw new CliError("validation_error", "give kind: or its legacy --kind spelling, not both");
|
|
55
|
-
const kinds = queriedKinds ?? (kind === undefined ? undefined : kindsOf(kind));
|
|
51
|
+
const { since, meta, kinds } = catchUpQuery(query, INBOX_QUERY, "inbox");
|
|
56
52
|
if (fresh && since !== undefined)
|
|
57
53
|
throw new CliError("validation_error", "--new keeps its own point — not with a date in the query");
|
|
58
54
|
const saved = fresh && since === undefined
|
|
@@ -65,8 +65,7 @@ export const privatePeopleCommands = (messenger) => {
|
|
|
65
65
|
command.action(async function (person, id) {
|
|
66
66
|
const context = messengerContext(this, messenger);
|
|
67
67
|
refuseLocalWrite(context, messenger.app.command, `contacts.notes.${name}`);
|
|
68
|
-
const { textFile
|
|
69
|
-
const file = textFile ?? legacyFile;
|
|
68
|
+
const { textFile: file, revision } = this.opts();
|
|
70
69
|
const text = file === undefined || file === "-" ? await readAll(context.stdin) : await readFile(file, "utf8");
|
|
71
70
|
const version = Number(revision);
|
|
72
71
|
if (name === "edit" && (!Number.isSafeInteger(version) || version < 1))
|
|
@@ -3,7 +3,7 @@ import { annotate } from "@wirecat/cli-base/commands";
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { parseDuration } from "../../domain/duration.js";
|
|
5
5
|
import { renderMessages } from "../../render/messages.js";
|
|
6
|
-
import {
|
|
6
|
+
import { REVIEW_DAYS, reviewStart, UNANSWERED_HOURS } from "../../services/inbox.js";
|
|
7
7
|
import { modelWith } from "../../speech/hearing.js";
|
|
8
8
|
import { REMOTE_READ } from "../effects.js";
|
|
9
9
|
import { renderAnswer } from "../envelope.js";
|
|
@@ -45,9 +45,7 @@ export const reviewCommand = (messenger) => annotate(new Command("review"), {
|
|
|
45
45
|
const hearWith = modelWith(options.transcribe, options.model);
|
|
46
46
|
const context = messengerContext(this, messenger);
|
|
47
47
|
const { settings, renderer, format, env } = context;
|
|
48
|
-
const { since: from, chat, meta, kinds
|
|
49
|
-
if (options.kind !== undefined && queriedKinds !== undefined)
|
|
50
|
-
throw new CliError("validation_error", "give kind: or its legacy --kind spelling, not both");
|
|
48
|
+
const { since: from, chat, meta, kinds } = catchUpQuery(query, REVIEW_QUERY, "review");
|
|
51
49
|
if (options.new && (from !== undefined || options.unanswered !== undefined)) {
|
|
52
50
|
// An open question stays open: moving past it would drop it from the next review.
|
|
53
51
|
throw new CliError("validation_error", "--new keeps its own point — not with a date or --unanswered");
|
|
@@ -62,7 +60,6 @@ export const reviewCommand = (messenger) => annotate(new Command("review"), {
|
|
|
62
60
|
: undefined;
|
|
63
61
|
const since = from ?? saved?.first ?? reviewStart();
|
|
64
62
|
const hours = options.unanswered === undefined ? undefined : unansweredHours(options.unanswered);
|
|
65
|
-
const kinds = queriedKinds ?? (options.kind === undefined ? undefined : kindsOf(options.kind));
|
|
66
63
|
let hearing;
|
|
67
64
|
const transcribe = options.transcribe === true;
|
|
68
65
|
const { found, reader } = await context.withServices(async (services, connect) => {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import { type PermissionKey } from "../sends/permissions.js";
|
|
3
|
+
import { type ConfigIssue } from "./config-issues.js";
|
|
4
|
+
import type { Settings } from "./settings.js";
|
|
3
5
|
export type PermissionKeyOf = (command: Command) => PermissionKey | null | undefined;
|
|
4
6
|
/**
|
|
5
7
|
* The keys `permissions` may name: every command of the program, every write key, and each of their
|
|
@@ -9,5 +11,10 @@ export declare const knownPermissionKeys: (command: Command, keyOf?: PermissionK
|
|
|
9
11
|
/** The known keys one level under the unknown key's parent, to name in the refusal. */
|
|
10
12
|
export declare const knownBeside: (key: PermissionKey, known: ReadonlySet<PermissionKey>) => PermissionKey[];
|
|
11
13
|
export declare const unknownPermissionKeys: (levels: Readonly<Record<PermissionKey, unknown>>, known: ReadonlySet<PermissionKey>) => PermissionKey[];
|
|
12
|
-
/**
|
|
13
|
-
export declare const
|
|
14
|
+
/** Every unknown permission key saved in the file, in every section and profile, with the unset that removes it. */
|
|
15
|
+
export declare const unknownPermissionIssues: (command: Command, settings: Pick<Settings, "configPath" | "savedPermissions" | "permissions" | "permissionSources" | "profile">, tool: string, keyOf?: PermissionKeyOf) => ConfigIssue[];
|
|
16
|
+
/**
|
|
17
|
+
* A saved key no command checks would do nothing, so the command refuses to run and names the unset that removes
|
|
18
|
+
* it. A `--permission` key is checked too: it is no file's, so the flag is the fix.
|
|
19
|
+
*/
|
|
20
|
+
export declare const refuseUnknownPermissionKeys: (command: Command, settings: Pick<Settings, "configPath" | "savedPermissions" | "permissions" | "permissionSources" | "profile">, tool: string, keyOf?: PermissionKeyOf) => void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { CEILING_KEY, keyForCommand, WRITE_KEYS } from "../sends/permissions.js";
|
|
3
|
+
import { issueMessage, permissionIssues, publicIssue } from "./config-issues.js";
|
|
3
4
|
const pathOf = (command) => {
|
|
4
5
|
const words = [];
|
|
5
6
|
for (let at = command; at?.parent; at = at.parent)
|
|
@@ -42,10 +43,24 @@ export const knownBeside = (key, known) => {
|
|
|
42
43
|
.sort();
|
|
43
44
|
};
|
|
44
45
|
export const unknownPermissionKeys = (levels, known) => Object.keys(levels).filter((key) => key !== CEILING_KEY && !known.has(key));
|
|
45
|
-
/**
|
|
46
|
-
export const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
/** Every unknown permission key saved in the file, in every section and profile, with the unset that removes it. */
|
|
47
|
+
export const unknownPermissionIssues = (command, settings, tool, keyOf) => {
|
|
48
|
+
const saved = settings.savedPermissions ??
|
|
49
|
+
Object.keys(settings.permissions)
|
|
50
|
+
.filter((key) => key !== CEILING_KEY && settings.permissionSources[key] !== "flag")
|
|
51
|
+
.map((key) => ({ key, at: ["profiles", settings.profile, "permissions", key] }));
|
|
52
|
+
return permissionIssues(saved, knownPermissionKeys(command, keyOf), { file: settings.configPath, tool }).map(publicIssue);
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* A saved key no command checks would do nothing, so the command refuses to run and names the unset that removes
|
|
56
|
+
* it. A `--permission` key is checked too: it is no file's, so the flag is the fix.
|
|
57
|
+
*/
|
|
58
|
+
export const refuseUnknownPermissionKeys = (command, settings, tool, keyOf) => {
|
|
59
|
+
const issues = unknownPermissionIssues(command, settings, tool, keyOf);
|
|
60
|
+
if (issues.length > 0)
|
|
61
|
+
throw new CliError("configuration_error", issueMessage(issues), { permission: issues[0]?.key });
|
|
62
|
+
const flagged = Object.keys(settings.permissions).filter((key) => settings.permissionSources[key] === "flag");
|
|
63
|
+
const [key] = unknownPermissionKeys(Object.fromEntries(flagged.map((one) => [one, true])), knownPermissionKeys(command, keyOf));
|
|
64
|
+
if (key !== undefined)
|
|
65
|
+
throw new CliError("validation_error", `--permission ${key} names no command`, { permission: key });
|
|
51
66
|
};
|
package/dist/cli/preview.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import { type
|
|
2
|
+
import { type PermissionKey } from "../sends/permissions.js";
|
|
3
|
+
import type { Settings } from "./settings.js";
|
|
3
4
|
export declare class PreviewComplete extends Error {
|
|
4
5
|
}
|
|
5
|
-
export declare const preview: (command: Command,
|
|
6
|
+
export declare const preview: (command: Command, settings: Pick<Settings, "permissions" | "permissionSources" | "configPath" | "savedPermissions" | "profile">, tool: string, keyOf?: (path: readonly string[]) => PermissionKey | null | undefined) => {
|
|
6
7
|
preview: boolean;
|
|
7
8
|
path: string[];
|
|
8
9
|
permission: {
|
package/dist/cli/preview.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
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 { keyForCommand, levelFor, readKeysForCommand, WRITE_KEYS
|
|
4
|
+
import { keyForCommand, levelFor, readKeysForCommand, WRITE_KEYS } from "../sends/permissions.js";
|
|
5
5
|
import { commandPathOf } from "./command-contract.js";
|
|
6
6
|
import { refuseUnknownPermissionKeys } from "./permission-keys.js";
|
|
7
7
|
export class PreviewComplete extends Error {
|
|
8
8
|
}
|
|
9
|
-
export const preview = (command,
|
|
9
|
+
export const preview = (command, settings, tool, keyOf) => {
|
|
10
10
|
const path = commandPathOf(command);
|
|
11
|
-
|
|
11
|
+
const { permissions } = settings;
|
|
12
|
+
refuseUnknownPermissionKeys(command, settings, tool, keyOf && ((one) => keyOf(commandPathOf(one))));
|
|
12
13
|
const key = keyForCommand(path);
|
|
13
14
|
const level = key ? levelFor(permissions, key).level : "allow";
|
|
14
15
|
for (const source of readKeysForCommand(path)) {
|
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.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface ProgramDefinition {
|
|
|
10
10
|
/** The CLI's own settings, for keeping a failure that happened before its command could; plain ones without. */
|
|
11
11
|
configuration?: {
|
|
12
12
|
permissionKey?: (path: readonly string[]) => string | null | undefined;
|
|
13
|
-
resolveSettings: (flags?: GlobalFlags, options?: ResolveOptions) => Pick<Settings, "profile" | "keepFailedRuns" | "keepRunsForDays" | "skillHint"> & Partial<Pick<Settings, "permissions">>;
|
|
13
|
+
resolveSettings: (flags?: GlobalFlags, options?: ResolveOptions) => Pick<Settings, "profile" | "keepFailedRuns" | "keepRunsForDays" | "skillHint"> & Partial<Pick<Settings, "permissions" | "permissionSources" | "configPath" | "savedPermissions">>;
|
|
14
14
|
};
|
|
15
15
|
configure?: (program: Command) => void;
|
|
16
16
|
/** The tool's pitch and "what you can do" lines on `--help` and a bare run. */
|
package/dist/cli/program.js
CHANGED
|
@@ -26,7 +26,7 @@ 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
28
|
import { PreviewComplete, preview } from "./preview.js";
|
|
29
|
-
import {
|
|
29
|
+
import { commandWords, liftProfile, PROFILE_FREE, sampleCommand, valuedOptions } from "./profile.js";
|
|
30
30
|
import { absolutePaths, relayTarget } from "./relay-route.js";
|
|
31
31
|
import { settingsFor } from "./settings.js";
|
|
32
32
|
import { assertSharedContracts } from "./shared-contracts.js";
|
|
@@ -37,11 +37,15 @@ import { commandAt, lineOf, withLines } from "./steps.js";
|
|
|
37
37
|
*/
|
|
38
38
|
export const createProgram = ({ app, commands, configure, firstScreen }, { out, err } = {}) => {
|
|
39
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";
|
|
40
44
|
program
|
|
41
45
|
.name(app.command)
|
|
42
46
|
.usage("[profile] [options] <command>")
|
|
43
47
|
.description(`${app.description}\n\n` +
|
|
44
|
-
`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` +
|
|
45
49
|
`\`${app.envPrefix}_PROFILE\` says the same thing for a whole shell session.\n` +
|
|
46
50
|
"No profile is assumed: without one, a command that uses an account stops and lists the profiles here.")
|
|
47
51
|
.version(app.version, "-V, --version")
|
|
@@ -49,21 +53,21 @@ export const createProgram = ({ app, commands, configure, firstScreen }, { out,
|
|
|
49
53
|
.option("--json", "machine-readable output: one JSON value on stdout; --json=id,text keeps only those fields")
|
|
50
54
|
.option("--jsonl", "machine-readable output: one JSON object per line, for streaming and jq")
|
|
51
55
|
.option("--quiet", "diagnostics off; a failure is still said")
|
|
52
|
-
.option("--trace",
|
|
56
|
+
.option("--trace", `the connection's own log lines on stderr — never ${content}`)
|
|
53
57
|
.option("--debug", "on a failure, its whole record — cause and stack — in place of the short line")
|
|
54
58
|
.option("--timeout <duration>", "give up on the whole command after this — 30s, 2m, 500ms")
|
|
55
59
|
.option("--offline", "answer from what was recorded and never connect; fails if nothing was")
|
|
56
60
|
.option("--no-input", "never prompt or open interactive login; piped input remains available")
|
|
57
61
|
.option("--max-input-bytes <bytes>", "maximum buffered input bytes (default: 16777216)")
|
|
58
62
|
.option("--max-output-bytes <bytes>", "maximum machine output bytes (default: 4194304; 0 disables)")
|
|
59
|
-
.option("--brief",
|
|
60
|
-
.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`)
|
|
61
65
|
.option("--dry-run", "preview parsed arguments and permissions before running the action")
|
|
62
66
|
.option("--yes", "go ahead without the question an ask level puts before a write")
|
|
63
|
-
.option("--record",
|
|
67
|
+
.option("--record", `keep this run — ids and timings, never ${content}`)
|
|
64
68
|
.option("--no-record", "do not keep it, whatever the configuration says")
|
|
65
69
|
.showHelpAfterError();
|
|
66
|
-
for (const command of
|
|
70
|
+
for (const command of mounted)
|
|
67
71
|
program.addCommand(command);
|
|
68
72
|
configure?.(program);
|
|
69
73
|
assertSharedContracts(program);
|
|
@@ -375,9 +379,13 @@ const runOnce = async (argv, definition, options) => {
|
|
|
375
379
|
// No permission governs housekeeping like `skill install`, so no profile's settings change its preview.
|
|
376
380
|
needsProfile: keyForCommand(path) !== null,
|
|
377
381
|
});
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
382
|
+
const previewed = preview(action, {
|
|
383
|
+
permissions: resolved.permissions ?? {},
|
|
384
|
+
permissionSources: resolved.permissionSources ?? {},
|
|
385
|
+
configPath: resolved.configPath ?? "",
|
|
386
|
+
profile: resolved.profile,
|
|
387
|
+
...(resolved.savedPermissions ? { savedPermissions: resolved.savedPermissions } : {}),
|
|
388
|
+
}, command, definition.configuration?.permissionKey);
|
|
381
389
|
renderAnswer(action, outputFor(action), itemOf(snakeKeys(previewed)));
|
|
382
390
|
throw new PreviewComplete();
|
|
383
391
|
}
|