@wirecat/cli-core 0.437.0 → 0.439.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 +35 -0
- package/dist/cli/bot/api.js +3 -1
- package/dist/cli/context.d.ts +1 -1
- package/dist/cli/context.js +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/messenger/doctor-command.js +2 -1
- package/dist/cli/messenger/report.d.ts +4 -1
- package/dist/cli/messenger/report.js +11 -2
- package/dist/cli/profile.d.ts +1 -0
- package/dist/cli/profile.js +2 -1
- package/dist/cli/program.d.ts +1 -1
- package/dist/cli/program.js +10 -4
- package/dist/cli/settings.d.ts +7 -0
- package/dist/cli/settings.js +5 -1
- package/dist/cli/shared-command-exports.d.ts +4 -0
- package/dist/runs/error-detail.js +1 -1
- package/dist/runs/run-context.d.ts +15 -7
- package/dist/runs/run-context.js +55 -14
- package/dist/runs/run.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,41 @@ 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
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## 0.439.0 — 26.10.2026
|
|
11
|
+
|
|
12
|
+
### Changed — may break callers
|
|
13
|
+
|
|
14
|
+
- **A run record keeps the command line as typed.** `argv` in `run.json` and `runs show` now holds the words the person
|
|
15
|
+
typed — a chat's title, a search, message text — so the history of what was run can be read back. A secret is
|
|
16
|
+
still always `[redacted]`: the value of an option or argument a CLI marks with the new `secretInput(...)` (exported
|
|
17
|
+
from `@wirecat/cli-core/cli`), one whose name says token, password, secret, credential, cookie, authorization or
|
|
18
|
+
API key, a password inside a URL, and a long opaque string. `bot api … --body` is marked secret, since a field the
|
|
19
|
+
API marks secret can ride in it. The new per-profile setting `record_input: false` (`config set record_input false`)
|
|
20
|
+
brings back the previous masking exactly. A configuration that will not load also masks, since it may be the one
|
|
21
|
+
that turned recording off. A CLI with its own `resolveSettings` may return `recordInput`; when it does not, typed
|
|
22
|
+
words are kept. `doctor reports create` still masks the run's words as before, since the report is sent to other
|
|
23
|
+
people. `config set` and `config show` list `record_input`, so a CLI's help snapshot changes. A test or document
|
|
24
|
+
that says a run never holds what was typed needs updating.
|
|
25
|
+
|
|
26
|
+
### Security
|
|
27
|
+
|
|
28
|
+
- **An error that quotes `--token=…` or `password=…` no longer keeps the value.** A kept error masked such a value
|
|
29
|
+
only when a space followed the `=` or `:`; Commander's `unknown option '--token=…'` kept it whole.
|
|
30
|
+
|
|
31
|
+
## 0.438.0 — 26.10.2026
|
|
32
|
+
|
|
33
|
+
### Security
|
|
34
|
+
|
|
35
|
+
- **A first word refused as a profile no longer reaches the run log.** When the first word could not be a profile
|
|
36
|
+
(`app "runs list"`, two words in one argument) or a profile lock refused it, the failed run's record kept it as
|
|
37
|
+
`profile` while its `argv` showed `[redacted]` — so whatever was typed first, possibly message text or a person's
|
|
38
|
+
name, was stored in the clear. Such a run now records no profile, as a run that names none does; a valid profile
|
|
39
|
+
the settings accepted is recorded as before. The lock's refusal quotes the refused word, so a kept error masks it,
|
|
40
|
+
and a bare first word with no command after it suggests `<tool> <word> account show` only when the word can be a
|
|
41
|
+
profile and the CLI has an `account show` command — that suggestion repeated the word unmasked in the kept error.
|
|
42
|
+
|
|
8
43
|
## 0.437.0 — 26.10.2026
|
|
9
44
|
|
|
10
45
|
### Added
|
package/dist/cli/bot/api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
|
+
import { secretInput } from "../../runs/run-context.js";
|
|
4
5
|
import { REMOTE_READ, REMOTE_WRITE } from "../effects.js";
|
|
5
6
|
import { apiFlagOf, apiOptionKey, readApiBody } from "./api-input.js";
|
|
6
7
|
export const generatedApiCommand = (api) => {
|
|
@@ -20,7 +21,8 @@ export const generatedApiCommand = (api) => {
|
|
|
20
21
|
command.addOption(parameter.required && parameter.in !== "body" ? option.makeOptionMandatory() : option);
|
|
21
22
|
}
|
|
22
23
|
if (operation.request) {
|
|
23
|
-
|
|
24
|
+
// A field the schema marks secret may ride in an inline body, and the flags never see it.
|
|
25
|
+
command.addOption(secretInput(new Option("--body <json>", "the request body as JSON; - reads it from stdin")));
|
|
24
26
|
command.option("--body-file <path>", "the request body from a JSON file; - is stdin");
|
|
25
27
|
}
|
|
26
28
|
annotate(command, {
|
package/dist/cli/context.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export interface BaseEnvironment {
|
|
|
62
62
|
handled?: (event: MessageEvent) => void;
|
|
63
63
|
/** Set by an MCP server on a command it runs for an agent (`runCommandForAgent`). */
|
|
64
64
|
agent?: AgentRun;
|
|
65
|
-
/** Set by `run()`: the words it was given, which a run keeps
|
|
65
|
+
/** Set by `run()`: the words it was given, which a run keeps as `record_input` says (`maskArgv`). */
|
|
66
66
|
argv?: readonly string[];
|
|
67
67
|
/**
|
|
68
68
|
* Set by `run()`: the rest of the command has no default deadline, and Ctrl-C, SIGTERM, a cancel or an explicit
|
package/dist/cli/context.js
CHANGED
|
@@ -86,6 +86,7 @@ export const baseContext = (command, resolveSettings) => {
|
|
|
86
86
|
program: rootOf(command),
|
|
87
87
|
...(environment.argv ? { argv: environment.argv } : {}),
|
|
88
88
|
env,
|
|
89
|
+
recordInput: settings.recordInput !== false,
|
|
89
90
|
}),
|
|
90
91
|
...(ranFor === undefined ? {} : { profile: ranFor }),
|
|
91
92
|
record: settings.record,
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { type CacheEvent, type DiagnosticEvent, type EventSink, providerErrorKey
|
|
|
25
25
|
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
|
+
export { secretInput } from "../runs/run-context.js";
|
|
28
29
|
export { currentRun, inRun, type RunScope, useAsProcessRun } from "../runs/scope.js";
|
|
29
30
|
export { LANGUAGES } from "../search/languages.js";
|
|
30
31
|
export { guardedApp, guardedPress, guardedStart, type Pressed } from "../sends/buttons.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -21,6 +21,7 @@ export { providerErrorKey, renderEvent, } from "../runs/events.js";
|
|
|
21
21
|
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
|
+
export { secretInput } from "../runs/run-context.js";
|
|
24
25
|
export { currentRun, inRun, useAsProcessRun } from "../runs/scope.js";
|
|
25
26
|
export { LANGUAGES } from "../search/languages.js";
|
|
26
27
|
export { guardedApp, guardedPress, guardedStart } from "../sends/buttons.js";
|
|
@@ -14,7 +14,7 @@ import { destroysNothing, LOCAL_WRITE, NO_EFFECTS, REMOTE_READ } from "../effect
|
|
|
14
14
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
15
15
|
import { unknownPermissionIssues } from "../permission-keys.js";
|
|
16
16
|
import { NONE_NAMED } from "../settings.js";
|
|
17
|
-
import { lineOf, nextStep } from "../steps.js";
|
|
17
|
+
import { lineOf, nextStep, rootOf } from "../steps.js";
|
|
18
18
|
import { recalledAccount } from "./accounts.js";
|
|
19
19
|
import { messengerContext } from "./context.js";
|
|
20
20
|
import { privateFiles, withSqliteSidecars } from "./private-files.js";
|
|
@@ -100,6 +100,7 @@ const reportCommand = (messenger) => {
|
|
|
100
100
|
sends: profile ? new SendJournal(sendsPathFor(app, profile, env)).entries() : [],
|
|
101
101
|
now,
|
|
102
102
|
home: env.HOME ?? env.USERPROFILE,
|
|
103
|
+
program: rootOf(this),
|
|
103
104
|
...(options.run === undefined ? {} : { runId: options.run }),
|
|
104
105
|
});
|
|
105
106
|
const path = resolve(options.output ?? reportFileName(app.command, now));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
1
2
|
import { type RunMetadata } from "../../runs/run.js";
|
|
2
3
|
import type { SendEntry } from "../../sends/journal.js";
|
|
3
4
|
export interface Report<Doctor extends object = Record<string, unknown>> {
|
|
@@ -29,7 +30,7 @@ export interface ReportContext {
|
|
|
29
30
|
* journal are built that way. Two things are added: every id becomes a label, and the home
|
|
30
31
|
* directory is hidden, since paths name the person's account on this machine.
|
|
31
32
|
*/
|
|
32
|
-
export declare const buildReport: <Doctor extends object, Context extends ReportContext = Record<never, never>>({ doctor, runsDir, runId, command, sends, now, home, context, eventFields, }: {
|
|
33
|
+
export declare const buildReport: <Doctor extends object, Context extends ReportContext = Record<never, never>>({ doctor, runsDir, runId, command, sends, now, home, context, eventFields, program, }: {
|
|
33
34
|
doctor: Doctor;
|
|
34
35
|
runsDir: string;
|
|
35
36
|
runId?: string;
|
|
@@ -41,6 +42,8 @@ export declare const buildReport: <Doctor extends object, Context extends Report
|
|
|
41
42
|
context?: Context;
|
|
42
43
|
/** Retain allowlisted recorded fields, including time and severity, for an existing consumer contract. */
|
|
43
44
|
eventFields?: "report" | "recorded";
|
|
45
|
+
/** The CLI's program, which tells a flag's value from a command word when the run's words are masked. */
|
|
46
|
+
program?: Command;
|
|
44
47
|
}) => Report<Doctor> & Omit<Context, keyof Report>;
|
|
45
48
|
/**
|
|
46
49
|
* Every field of a journal line that names a chat, a message or a write. A send's `operationId` is
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { CliError } from "@wirecat/cli-base";
|
|
4
|
+
import { Command } from "commander";
|
|
4
5
|
import { LOG_ATTRIBUTES } from "../../runs/log-attributes.js";
|
|
5
6
|
import { findRun, listRuns, readEvents } from "../../runs/run.js";
|
|
7
|
+
import { maskArgv } from "../../runs/run-context.js";
|
|
6
8
|
const RECENT_SENDS = 20;
|
|
7
9
|
/** Only these fields of a run event travel; a field added later is left out until it is named here. */
|
|
8
10
|
const EVENT_FIELDS = [
|
|
@@ -21,7 +23,7 @@ const EVENT_FIELDS = [
|
|
|
21
23
|
* journal are built that way. Two things are added: every id becomes a label, and the home
|
|
22
24
|
* directory is hidden, since paths name the person's account on this machine.
|
|
23
25
|
*/
|
|
24
|
-
export const buildReport = ({ doctor, runsDir, runId, command, sends, now = new Date(), home, context, eventFields = "report", }) => {
|
|
26
|
+
export const buildReport = ({ doctor, runsDir, runId, command, sends, now = new Date(), home, context, eventFields = "report", program, }) => {
|
|
25
27
|
const chosen = runId === undefined ? newestFailed(runsDir) : findRun(runsDir, runId);
|
|
26
28
|
if (runId !== undefined && !chosen)
|
|
27
29
|
throw new CliError("not_found", `no run ${runId} — \`${command} runs list\` names them`);
|
|
@@ -34,11 +36,18 @@ export const buildReport = ({ doctor, runsDir, runId, command, sends, now = new
|
|
|
34
36
|
...doctor,
|
|
35
37
|
...(account?.remembered ? { account: { ...account, remembered: label(account.remembered) } } : {}),
|
|
36
38
|
},
|
|
37
|
-
run: chosen
|
|
39
|
+
run: chosen
|
|
40
|
+
? {
|
|
41
|
+
metadata: maskedRun(chosen.metadata, program),
|
|
42
|
+
events: readEvents(chosen.dir).map(eventOf(label, eventFields)),
|
|
43
|
+
}
|
|
44
|
+
: null,
|
|
38
45
|
sends: sends.slice(-RECENT_SENDS).map((entry) => labelled(entry, label)),
|
|
39
46
|
};
|
|
40
47
|
return JSON.parse(hideHome(JSON.stringify(report), home));
|
|
41
48
|
};
|
|
49
|
+
// A report leaves the machine, so it masks the typed words as `record_input: false` would.
|
|
50
|
+
const maskedRun = (metadata, program = new Command()) => metadata.argv ? { ...metadata, argv: maskArgv(program, metadata.argv).argv } : metadata;
|
|
42
51
|
/**
|
|
43
52
|
* Every field of a journal line that names a chat, a message or a write. A send's `operationId` is
|
|
44
53
|
* its `sendId`, so one left raw would undo the other's label.
|
package/dist/cli/profile.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
/** How the command line names this profile: always the first word, since none is assumed. */
|
|
3
3
|
export declare const asFirstWord: (profile: string) => string;
|
|
4
|
+
export declare const isProfileName: (word: string) => boolean;
|
|
4
5
|
export declare const usableProfileName: (profile: string) => string;
|
|
5
6
|
/** The top-level commands that never read a profile; every other one is `<tool> <profile> …`. */
|
|
6
7
|
export declare const PROFILE_FREE: ReadonlySet<string>;
|
package/dist/cli/profile.js
CHANGED
|
@@ -7,8 +7,9 @@ export const asFirstWord = (profile) => `${profile} `;
|
|
|
7
7
|
* somewhere nobody would think to look.
|
|
8
8
|
*/
|
|
9
9
|
const PLAIN_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
10
|
+
export const isProfileName = (word) => PLAIN_NAME.test(word);
|
|
10
11
|
export const usableProfileName = (profile) => {
|
|
11
|
-
if (!
|
|
12
|
+
if (!isProfileName(profile)) {
|
|
12
13
|
throw new CliError("validation_error", `"${profile}" cannot be a profile name — letters, digits, dot, dash and underscore only, ` +
|
|
13
14
|
"starting with a letter or a digit");
|
|
14
15
|
}
|
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" | "permissionSources" | "configPath" | "savedPermissions" | "sources">>;
|
|
13
|
+
resolveSettings: (flags?: GlobalFlags, options?: ResolveOptions) => Pick<Settings, "profile" | "keepFailedRuns" | "keepRunsForDays" | "skillHint"> & Partial<Pick<Settings, "recordInput" | "permissions" | "permissionSources" | "configPath" | "savedPermissions" | "sources">>;
|
|
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
|
@@ -27,7 +27,7 @@ import { showInputFields } from "./input-fields.js";
|
|
|
27
27
|
import { MAX_BUFFERED_INPUT, provideInputPolicy } from "./input-policy.js";
|
|
28
28
|
import { checkJson, liftJson } from "./json-fields.js";
|
|
29
29
|
import { PreviewComplete, preview } from "./preview.js";
|
|
30
|
-
import { commandWords, liftProfile, sampleCommand, takesProfile, valuedOptions } from "./profile.js";
|
|
30
|
+
import { commandWords, isProfileName, liftProfile, sampleCommand, takesProfile, valuedOptions } from "./profile.js";
|
|
31
31
|
import { absolutePaths, relayTarget } from "./relay-route.js";
|
|
32
32
|
import { NoProfileHere, namedProfile, settingsFor, } from "./settings.js";
|
|
33
33
|
import { assertSharedContracts } from "./shared-contracts.js";
|
|
@@ -433,8 +433,11 @@ const runOnce = async (argv, definition, options) => {
|
|
|
433
433
|
// A bare word with nothing after it would make commander print help **on stdout**, which breaks
|
|
434
434
|
// the one contract this program has and says nothing about why.
|
|
435
435
|
if (profile !== undefined && rest.length === 0) {
|
|
436
|
+
const asProfile = commandAt(program, "account show") && isProfileName(profile)
|
|
437
|
+
? `, or \`${command} ${profile} account show\` if "${profile}" is your profile`
|
|
438
|
+
: "";
|
|
436
439
|
const message = `"${profile}" is not a command, so it was read as a profile name — and no command followed it. ` +
|
|
437
|
-
`Run \`${command} --help\` for the commands
|
|
440
|
+
`Run \`${command} --help\` for the commands${asProfile}.`;
|
|
438
441
|
const failure = new CliError("validation_error", message, { param: "profile" });
|
|
439
442
|
const settled = await settleFailure(failure, { definition, program, rest, argv, profile, options });
|
|
440
443
|
return report(streams, reporting, failed(definition, program, rest, failure, settled, debug));
|
|
@@ -571,7 +574,8 @@ const settleFailure = async (failure, state) => {
|
|
|
571
574
|
/**
|
|
572
575
|
* **Every failure is kept as a run** — a usage error, a configuration that will not load, a command
|
|
573
576
|
* that never opens a run — unless recording was turned off. One its own run already kept is skipped.
|
|
574
|
-
* Its
|
|
577
|
+
* Its words are kept as `record_input` says (`maskArgv`); a configuration that did not load masks them, since it may
|
|
578
|
+
* be the one that turned recording input off.
|
|
575
579
|
*/
|
|
576
580
|
const keepFailure = async (failure, { definition, program, rest, argv, profile, options }) => {
|
|
577
581
|
if (wasSettled(failure))
|
|
@@ -596,8 +600,10 @@ const keepFailure = async (failure, { definition, program, rest, argv, profile,
|
|
|
596
600
|
program,
|
|
597
601
|
argv,
|
|
598
602
|
env,
|
|
603
|
+
recordInput: settings ? settings.recordInput !== false : false,
|
|
599
604
|
}),
|
|
600
|
-
|
|
605
|
+
// Settings that did not load never accepted the word, which may be a refused name or somebody's words.
|
|
606
|
+
profile: settings ? namedProfile(settings) : undefined,
|
|
601
607
|
record: false,
|
|
602
608
|
keepFailed: settings?.keepFailedRuns ?? !rest.includes("--no-record"),
|
|
603
609
|
trace: false,
|
package/dist/cli/settings.d.ts
CHANGED
|
@@ -97,6 +97,11 @@ export interface Settings extends AISettings {
|
|
|
97
97
|
*/
|
|
98
98
|
commandTimeoutMs: number | undefined;
|
|
99
99
|
record: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* A run record keeps the words typed, secrets hidden; off masks every value that may be content. Optional so a CLI
|
|
102
|
+
* with settings of its own need not return it: left out, the words are kept.
|
|
103
|
+
*/
|
|
104
|
+
recordInput?: boolean;
|
|
100
105
|
/** A failed run is kept even unrecorded, unless recording was turned off by name. */
|
|
101
106
|
keepFailedRuns: boolean;
|
|
102
107
|
keepRunsForDays: number;
|
|
@@ -265,6 +270,7 @@ export declare const settingsFor: <T extends object = Record<never, never>>(app:
|
|
|
265
270
|
color: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
266
271
|
sender_colors: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
267
272
|
record: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
273
|
+
record_input: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
268
274
|
keep_runs_for_days: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
269
275
|
permissions: v.OptionalSchema<v.RecordSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, (issue: v.RegexIssue<string>) => string>]>, v.PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: v.PicklistIssue) => string>, (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
270
276
|
sends_per_hour: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
@@ -338,6 +344,7 @@ export declare const settingsFor: <T extends object = Record<never, never>>(app:
|
|
|
338
344
|
color: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
339
345
|
sender_colors: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
340
346
|
record: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
347
|
+
record_input: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
341
348
|
keep_runs_for_days: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
342
349
|
permissions: v.OptionalSchema<v.RecordSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, (issue: v.RegexIssue<string>) => string>]>, v.PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: v.PicklistIssue) => string>, (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
343
350
|
sends_per_hour: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
package/dist/cli/settings.js
CHANGED
|
@@ -56,6 +56,7 @@ const SHARED_PROFILE_ENTRIES = {
|
|
|
56
56
|
color: v.optional(flag),
|
|
57
57
|
sender_colors: v.optional(flag),
|
|
58
58
|
record: v.optional(flag),
|
|
59
|
+
record_input: v.optional(flag),
|
|
59
60
|
keep_runs_for_days: v.optional(count),
|
|
60
61
|
permissions: v.optional(permissionLevels),
|
|
61
62
|
sends_per_hour: v.optional(count),
|
|
@@ -168,7 +169,7 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
168
169
|
if (profile.value === lock)
|
|
169
170
|
return profile;
|
|
170
171
|
if (profile.from === "first word" || profile.from === PROFILE) {
|
|
171
|
-
throw new CliError("permission_error", `this process is locked to profile ${lock} (${PROFILE_LOCK}) — profile ${profile.value} is refused`);
|
|
172
|
+
throw new CliError("permission_error", `this process is locked to profile ${lock} (${PROFILE_LOCK}) — profile "${profile.value}" is refused`);
|
|
172
173
|
}
|
|
173
174
|
return { value: lock, from: PROFILE_LOCK };
|
|
174
175
|
};
|
|
@@ -231,6 +232,7 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
231
232
|
const searchCatchUp = fromLayers("search_catch_up", false);
|
|
232
233
|
const pictureSearch = fromLayers("picture_search", false);
|
|
233
234
|
const searchReranker = fromLayers("search_reranker", DEFAULT_RERANK_MODEL);
|
|
235
|
+
const recordInput = fromLayers("record_input", true);
|
|
234
236
|
const keepRunsForDays = fromLayers("keep_runs_for_days", DEFAULT_KEEP_RUNS_FOR_DAYS);
|
|
235
237
|
// Set where an agent runs unattended; the config file and `--permission` cannot undo it.
|
|
236
238
|
const readOnlyLock = readOnlyLockOf(app, env);
|
|
@@ -287,6 +289,7 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
287
289
|
? undefined
|
|
288
290
|
: (extension.parseDuration ?? parseDuration)(timeout.value, timeout.from === TIMEOUT ? TIMEOUT : "--timeout"),
|
|
289
291
|
record: record.value,
|
|
292
|
+
recordInput: recordInput.value,
|
|
290
293
|
keepFailedRuns: record.value || record.from === "default",
|
|
291
294
|
keepRunsForDays: keepRunsForDays.value,
|
|
292
295
|
...(readOnlyLock ? { readOnlyLock } : {}),
|
|
@@ -314,6 +317,7 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
314
317
|
pictureSearch: pictureSearch.from,
|
|
315
318
|
searchReranker: searchReranker.from,
|
|
316
319
|
record: record.from,
|
|
320
|
+
recordInput: recordInput.from,
|
|
317
321
|
keepRunsForDays: keepRunsForDays.from,
|
|
318
322
|
sendsPerHour: sendsPerHour.from,
|
|
319
323
|
requestsPerMinute: requestsPerMinute.from,
|
|
@@ -79,6 +79,7 @@ export declare const configCommand: (app: import("./entry.js").AppIdentity, conf
|
|
|
79
79
|
color: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
80
80
|
sender_colors: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
81
81
|
record: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
82
|
+
record_input: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
82
83
|
keep_runs_for_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
83
84
|
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").RegexAction<string, (issue: import("valibot").RegexIssue<string>) => string>]>, import("valibot").PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: import("valibot").PicklistIssue) => string>, (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
84
85
|
sends_per_hour: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
@@ -152,6 +153,7 @@ export declare const configCommand: (app: import("./entry.js").AppIdentity, conf
|
|
|
152
153
|
color: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
153
154
|
sender_colors: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
154
155
|
record: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
156
|
+
record_input: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
155
157
|
keep_runs_for_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
156
158
|
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").RegexAction<string, (issue: import("valibot").RegexIssue<string>) => string>]>, import("valibot").PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: import("valibot").PicklistIssue) => string>, (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
157
159
|
sends_per_hour: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
@@ -280,6 +282,7 @@ export declare const completeCommand: (messenger: import("./index.js").Messenger
|
|
|
280
282
|
color: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
281
283
|
sender_colors: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
282
284
|
record: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
285
|
+
record_input: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
283
286
|
keep_runs_for_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
284
287
|
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").RegexAction<string, (issue: import("valibot").RegexIssue<string>) => string>]>, import("valibot").PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: import("valibot").PicklistIssue) => string>, (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
285
288
|
sends_per_hour: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
@@ -353,6 +356,7 @@ export declare const completeCommand: (messenger: import("./index.js").Messenger
|
|
|
353
356
|
color: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
354
357
|
sender_colors: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
355
358
|
record: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
359
|
+
record_input: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
356
360
|
keep_runs_for_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
357
361
|
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").RegexAction<string, (issue: import("valibot").RegexIssue<string>) => string>]>, import("valibot").PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: import("valibot").PicklistIssue) => string>, (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
358
362
|
sends_per_hour: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
@@ -119,7 +119,7 @@ export const redactText = (text) => {
|
|
|
119
119
|
let out = home && home !== "/" ? text.split(home).join("~") : text;
|
|
120
120
|
out = out
|
|
121
121
|
.replace(/"[^"\n]*"|“[^”\n]*”|«[^»\n]*»/g, '"[redacted]"')
|
|
122
|
-
.replace(/\b(bearer|basic|token|key|password|secret)(\s*[:=]
|
|
122
|
+
.replace(/\b(bearer|basic|token|key|password|secret)(\s*[:=]\s*|\s+)[^\s'"]+/gi, "$1$2[redacted]")
|
|
123
123
|
.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "[email]")
|
|
124
124
|
.replace(/\+\d[\d\s()-]{6,}\d/g, "[number]")
|
|
125
125
|
.replace(/(^|[^\w-])\d{11,15}\b/g, "$1[number]")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
1
|
+
import type { Argument, Command, Option } from "commander";
|
|
2
2
|
import type { RunContext } from "./run.js";
|
|
3
3
|
export declare const REDACTED = "[redacted]";
|
|
4
4
|
/** Which coding agent started the process, and its session when the agent names one. Keys as written in run.json. */
|
|
@@ -17,24 +17,32 @@ export interface RunAgent {
|
|
|
17
17
|
*/
|
|
18
18
|
export declare const agentOf: (env: NodeJS.ProcessEnv) => RunAgent | undefined;
|
|
19
19
|
export interface MaskedArgv {
|
|
20
|
-
/** The words after the binary
|
|
20
|
+
/** The words after the binary; a secret is `[redacted]`, and with `keep` off so is every value that may be content. */
|
|
21
21
|
argv: string[];
|
|
22
22
|
/** The first word that names no command where one was expected. */
|
|
23
23
|
unknownCommand?: string;
|
|
24
24
|
}
|
|
25
|
+
/** Marks an option or argument whose value a run record never keeps, whatever the owner's `record_input`. */
|
|
26
|
+
export declare const secretInput: <T extends Option | Argument>(input: T) => T;
|
|
25
27
|
/**
|
|
26
|
-
* The command line
|
|
27
|
-
*
|
|
28
|
+
* The command line for a run record. With `keep` (the owner's `record_input`, on unless turned off) every word stays
|
|
29
|
+
* as typed except a secret: a value of an input marked `secretInput` or named for one (`--token`), and a word shaped
|
|
30
|
+
* like a credential. With `keep` off, command words, flag names and the profile stay; a value stays only when its
|
|
31
|
+
* shape cannot be somebody's words — a number short of a phone's length, a duration, a date, `true`, one of the
|
|
28
32
|
* option's declared choices — and its option or argument is not named for content (`--text`, `<phone>`). Anything
|
|
29
33
|
* else, a positional after `--` or an argument no command declares, is `[redacted]`: a chat is often typed by title.
|
|
30
34
|
*/
|
|
31
|
-
export declare const maskArgv: (program: Command, words: readonly string[]
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
export declare const maskArgv: (program: Command, words: readonly string[], { keep }?: {
|
|
36
|
+
keep?: boolean | undefined;
|
|
37
|
+
}) => MaskedArgv;
|
|
38
|
+
/** A run's context from the parsed program: `max messages list` and its words, or the word that named nothing. */
|
|
39
|
+
export declare const runContextOf: ({ bin, command, program, argv, env, recordInput, }: {
|
|
34
40
|
bin: string;
|
|
35
41
|
/** The run's own name, `messages list` or `mcp messages list`; empty when no command was recognised. */
|
|
36
42
|
command: string;
|
|
37
43
|
program?: Command;
|
|
38
44
|
argv?: readonly string[];
|
|
39
45
|
env: NodeJS.ProcessEnv;
|
|
46
|
+
/** The owner's `record_input`; left out, every value that may be content is masked. */
|
|
47
|
+
recordInput?: boolean;
|
|
40
48
|
}) => RunContext;
|
package/dist/runs/run-context.js
CHANGED
|
@@ -39,19 +39,55 @@ const sessionOf = (kind, env) => {
|
|
|
39
39
|
return (variable === undefined ? undefined : env[variable]?.trim()) || undefined;
|
|
40
40
|
};
|
|
41
41
|
const plainWord = (word) => /^[A-Za-z][A-Za-z0-9.-]{0,39}$/.test(word);
|
|
42
|
+
const secretInputs = new WeakSet();
|
|
43
|
+
/** Marks an option or argument whose value a run record never keeps, whatever the owner's `record_input`. */
|
|
44
|
+
export const secretInput = (input) => {
|
|
45
|
+
secretInputs.add(input);
|
|
46
|
+
return input;
|
|
47
|
+
};
|
|
48
|
+
const SECRET_WORDS = new Set([
|
|
49
|
+
"authorization",
|
|
50
|
+
"cookie",
|
|
51
|
+
"credential",
|
|
52
|
+
"credentials",
|
|
53
|
+
"passphrase",
|
|
54
|
+
"password",
|
|
55
|
+
"secret",
|
|
56
|
+
"token",
|
|
57
|
+
"apikey",
|
|
58
|
+
]);
|
|
59
|
+
// Undeclared flags carry no mark, so the name is the only signal; CLIs take secrets at a prompt (WC-SECR-5) and this
|
|
60
|
+
// catches the one typed anyway.
|
|
61
|
+
const secretNamed = (name) => {
|
|
62
|
+
const words = name
|
|
63
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
64
|
+
.split(/[\s_.-]+/)
|
|
65
|
+
.map((word) => word.toLowerCase());
|
|
66
|
+
return words.some((word, at) => SECRET_WORDS.has(word) || (word === "api" && words[at + 1] === "key"));
|
|
67
|
+
};
|
|
68
|
+
// A password inside a URL, or one unbroken run of 40 token characters, is a credential wherever it was typed.
|
|
69
|
+
const secretShaped = (word) => /:\/\/[^/@\s:]+:[^/@\s]+@/.test(word) || /(^|[^A-Za-z0-9_-])[A-Za-z0-9_-]{40,}($|[^A-Za-z0-9_-])/.test(word);
|
|
42
70
|
/**
|
|
43
|
-
* The command line
|
|
44
|
-
*
|
|
71
|
+
* The command line for a run record. With `keep` (the owner's `record_input`, on unless turned off) every word stays
|
|
72
|
+
* as typed except a secret: a value of an input marked `secretInput` or named for one (`--token`), and a word shaped
|
|
73
|
+
* like a credential. With `keep` off, command words, flag names and the profile stay; a value stays only when its
|
|
74
|
+
* shape cannot be somebody's words — a number short of a phone's length, a duration, a date, `true`, one of the
|
|
45
75
|
* option's declared choices — and its option or argument is not named for content (`--text`, `<phone>`). Anything
|
|
46
76
|
* else, a positional after `--` or an argument no command declares, is `[redacted]`: a chat is often typed by title.
|
|
47
77
|
*/
|
|
48
|
-
export const maskArgv = (program, words) => {
|
|
78
|
+
export const maskArgv = (program, words, { keep = false } = {}) => {
|
|
49
79
|
const argv = [];
|
|
50
80
|
let unknownCommand;
|
|
51
81
|
let current = program;
|
|
52
82
|
let positional = 0;
|
|
53
83
|
let profileSeen = false;
|
|
54
84
|
let lost = false;
|
|
85
|
+
const raw = (word) => (keep && !secretShaped(word) ? word : REDACTED);
|
|
86
|
+
const keptValue = (name, value, input) => keep
|
|
87
|
+
? (input && secretInputs.has(input)) || secretNamed(name)
|
|
88
|
+
? REDACTED
|
|
89
|
+
: raw(value)
|
|
90
|
+
: maskedValue(name, value, input?.argChoices);
|
|
55
91
|
const optionFor = (flag) => {
|
|
56
92
|
for (let at = current; at; at = at.parent) {
|
|
57
93
|
const found = at.options.find((option) => option.long === flag || option.short === flag);
|
|
@@ -65,32 +101,37 @@ export const maskArgv = (program, words) => {
|
|
|
65
101
|
if (word === "--") {
|
|
66
102
|
argv.push("--");
|
|
67
103
|
for (let after = at + 1; after < words.length; after++)
|
|
68
|
-
argv.push(
|
|
104
|
+
argv.push(raw(words[after]));
|
|
69
105
|
break;
|
|
70
106
|
}
|
|
71
107
|
if (word.startsWith("-") && word.length > 1 && !NUMBER.test(word)) {
|
|
72
108
|
const equals = word.indexOf("=");
|
|
73
109
|
const flag = equals > 0 ? word.slice(0, equals) : word;
|
|
74
110
|
if (!/^--?[A-Za-z0-9][A-Za-z0-9-]*$/.test(flag)) {
|
|
75
|
-
argv.push(
|
|
111
|
+
argv.push(raw(word));
|
|
76
112
|
continue;
|
|
77
113
|
}
|
|
78
114
|
const option = optionFor(flag);
|
|
79
115
|
const name = option?.attributeName() ?? flag.replace(/^-+/, "");
|
|
80
116
|
if (equals > 0) {
|
|
81
|
-
argv.push(`${flag}=${
|
|
117
|
+
argv.push(`${flag}=${keptValue(name, word.slice(equals + 1), option)}`);
|
|
82
118
|
continue;
|
|
83
119
|
}
|
|
84
120
|
argv.push(flag);
|
|
121
|
+
if (!option && keep && secretNamed(name) && words[at + 1] !== undefined && !words[at + 1]?.startsWith("-")) {
|
|
122
|
+
at += 1;
|
|
123
|
+
argv.push(REDACTED);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
85
126
|
if (!option || !(option.required || option.optional))
|
|
86
127
|
continue;
|
|
87
128
|
const takes = (next) => next !== undefined && (option.required || !next.startsWith("-") || NUMBER.test(next));
|
|
88
129
|
if (takes(words[at + 1])) {
|
|
89
130
|
at += 1;
|
|
90
|
-
argv.push(
|
|
131
|
+
argv.push(keptValue(name, words[at], option));
|
|
91
132
|
while (option.variadic && words[at + 1] !== undefined && !words[at + 1].startsWith("-")) {
|
|
92
133
|
at += 1;
|
|
93
|
-
argv.push(
|
|
134
|
+
argv.push(keptValue(name, words[at], option));
|
|
94
135
|
}
|
|
95
136
|
}
|
|
96
137
|
continue;
|
|
@@ -104,20 +145,20 @@ export const maskArgv = (program, words) => {
|
|
|
104
145
|
}
|
|
105
146
|
if (current === program && !profileSeen) {
|
|
106
147
|
profileSeen = true;
|
|
107
|
-
argv.push(PROFILE.test(word) ? word : REDACTED);
|
|
148
|
+
argv.push(keep ? raw(word) : PROFILE.test(word) ? word : REDACTED);
|
|
108
149
|
continue;
|
|
109
150
|
}
|
|
110
151
|
if (current.commands.length > 0 && current.registeredArguments.length === 0) {
|
|
111
152
|
lost = true;
|
|
112
153
|
unknownCommand = commandLike(word);
|
|
113
|
-
argv.push(unknownCommand);
|
|
154
|
+
argv.push(keep ? raw(word) : unknownCommand);
|
|
114
155
|
continue;
|
|
115
156
|
}
|
|
116
157
|
}
|
|
117
158
|
const declared = lost ? undefined : current.registeredArguments;
|
|
118
159
|
const argument = declared?.[Math.min(positional, declared.length - 1)];
|
|
119
160
|
positional += 1;
|
|
120
|
-
argv.push(argument ?
|
|
161
|
+
argv.push(argument ? keptValue(argument.name(), word, argument) : raw(word));
|
|
121
162
|
}
|
|
122
163
|
return { argv, ...(unknownCommand === undefined ? {} : { unknownCommand }) };
|
|
123
164
|
};
|
|
@@ -143,9 +184,9 @@ const safeShape = (value) => (NUMBER.test(value) && !/^\d{11,}$/.test(value)) ||
|
|
|
143
184
|
/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/.test(value) ||
|
|
144
185
|
value === "true" ||
|
|
145
186
|
value === "false";
|
|
146
|
-
/** A run's context from the parsed program: `max messages list` and its
|
|
147
|
-
export const runContextOf = ({ bin, command, program, argv, env, }) => {
|
|
148
|
-
const masked = program && argv ? maskArgv(program, argv) : undefined;
|
|
187
|
+
/** A run's context from the parsed program: `max messages list` and its words, or the word that named nothing. */
|
|
188
|
+
export const runContextOf = ({ bin, command, program, argv, env, recordInput = false, }) => {
|
|
189
|
+
const masked = program && argv ? maskArgv(program, argv, { keep: recordInput }) : undefined;
|
|
149
190
|
const agent = agentOf(env);
|
|
150
191
|
return {
|
|
151
192
|
cliCommand: command ? `${bin} ${command}` : bin,
|
package/dist/runs/run.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export interface RunMetadata {
|
|
|
49
49
|
traceId?: string;
|
|
50
50
|
/** The whole command with the program's name, `max messages list`; `command` stays the run's short name. */
|
|
51
51
|
cliCommand?: string;
|
|
52
|
-
/** The words after the program's name,
|
|
52
|
+
/** The words after the program's name, a secret `[redacted]`; with `record_input` off, any value that may be content too (`maskArgv`). */
|
|
53
53
|
argv?: string[];
|
|
54
54
|
/** The word that named no command, when that is why the run failed. */
|
|
55
55
|
unknownCommand?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.439.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",
|