@wirecat/cli-core 0.439.0 → 0.441.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 +49 -0
- package/README.md +2 -2
- package/dist/cli/command-runs.d.ts +3 -3
- package/dist/cli/command-runs.js +13 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/messenger/contacts-command.js +2 -1
- package/dist/cli/program.js +1 -1
- package/dist/runs/error-detail.d.ts +3 -0
- package/dist/runs/error-detail.js +5 -2
- package/dist/runs/run-context.d.ts +12 -5
- package/dist/runs/run-context.js +17 -7
- package/dist/services/tasks.js +20 -17
- package/package.json +1 -1
- package/parity.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,55 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.441.0 — 26.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`tasks list claimed_by:<holder>` lists the tasks a holder has claimed**, and `claimed_by:*` every claimed task.
|
|
15
|
+
A caller that wanted one agent's claims read every open task and filtered them itself. The filter matches live
|
|
16
|
+
claims only: a claim whose lease has lapsed holds nothing, as `tasks claim` and the list's own `claimed_by` field
|
|
17
|
+
already read it, so `NOT claimed_by:*` lists what no one is working on. A wildcard such as `claimed_by:claude-code:*`
|
|
18
|
+
matches by pattern.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`tasks claim` and `tasks release` work on a project task**, which belongs to no messenger account, as `tasks show`
|
|
23
|
+
and `tasks update` already did. They refused it as "no task for this account", so memo, which mounts these
|
|
24
|
+
commands, could not claim its own project tasks.
|
|
25
|
+
|
|
26
|
+
## 0.440.0 — 26.10.2026
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **A CLI's own command can keep its command line in its run record.** A command the CLI records itself with
|
|
31
|
+
`recorded` or `startRecording` kept no `argv` and no agent, since only cli-core's own commands built that part of the
|
|
32
|
+
record. `runContextOf` and the `RunContext` type are now exported from `@wirecat/cli-core/cli`. To adopt, pass
|
|
33
|
+
`context: runContextOf({ bin, command, program: rootOf(command), argv: environmentOf(command).argv, env,
|
|
34
|
+
recordInput: settings.recordInput !== false })` to the call; a CLI with its own program passes its own root command
|
|
35
|
+
and the words it parsed. Left out, `recordInput` masks every value that may be content. A CLI that records through
|
|
36
|
+
`commandRuns` under `run()` gets the command line with no change; its `settings()` may return `recordInput`, and when
|
|
37
|
+
it does not, the typed words are kept with secrets hidden.
|
|
38
|
+
- **`secretInput` also marks a whole command**: every word typed after it that is not an option is `[redacted]` in a
|
|
39
|
+
run record. For a command that refuses its arguments, such as one that asks for a phone number instead of taking it,
|
|
40
|
+
this hides what was typed without declaring an argument, so its usage line and contract stay the same.
|
|
41
|
+
|
|
42
|
+
### Changed — may break callers
|
|
43
|
+
|
|
44
|
+
- **`--record` help says what a run keeps now.** It read "ids and timings, never content", which stopped being true
|
|
45
|
+
in 0.439.0; it now reads "keep this run, with the command as typed and secrets hidden; record_input false leaves the
|
|
46
|
+
typed words out". Every CLI's help snapshot and `docs/commands.md` change with it. `recordRuns` no longer takes
|
|
47
|
+
`content`, which only that line used.
|
|
48
|
+
|
|
49
|
+
### Security
|
|
50
|
+
|
|
51
|
+
- **A phone number no longer reaches a run record.** Since 0.439.0 a run keeps the words as typed, so a number typed
|
|
52
|
+
after `contacts lookup`, which the command refuses, was kept in `argv` in the clear. The command is now marked with
|
|
53
|
+
`secretInput`, so every word typed after it is `[redacted]`. A phone number typed anywhere else, message text included, is replaced by `[redacted]` inside the word,
|
|
54
|
+
the rest kept: one written with its "+" and country code, or 11 to 15 digits in a row, the shapes a kept error
|
|
55
|
+
already masks. A positive id of 11 to 15 digits, such as an 11-digit Zoom meeting id or a time in milliseconds, is
|
|
56
|
+
hidden as well; a negative chat id is kept. A number with no "+" written in groups (`8 912 345-67-89`) is not
|
|
57
|
+
recognised.
|
|
58
|
+
|
|
10
59
|
## 0.439.0 — 26.10.2026
|
|
11
60
|
|
|
12
61
|
### Changed — may break callers
|
package/README.md
CHANGED
|
@@ -217,8 +217,8 @@ they deliberately differ. A rule changes in its own pull request, before the cod
|
|
|
217
217
|
- **The tool never picks an account.** Every account command names its profile — the first word in tg, max and
|
|
218
218
|
wa, an option such as `--account` or `--profile` in the others; without one, the command stops and lists the
|
|
219
219
|
choices — even when there is only one.
|
|
220
|
-
- **Run records keep
|
|
221
|
-
|
|
220
|
+
- **Run records keep the command as typed, never a secret.** A failure is kept by default, so `runs show` can explain
|
|
221
|
+
it; a token, a password or a phone number is hidden, and `record_input: false` leaves the typed words out too.
|
|
222
222
|
- **Tests that cannot touch real data.** A sandbox keeps every test away from the owner's store, and no test may
|
|
223
223
|
sleep. A coverage floor: 92% of lines, and at least half of every file.
|
|
224
224
|
- **Contract cases for every adapter.** `contractCases` turns the port's promises into cases any test runner can run,
|
|
@@ -6,6 +6,8 @@ import type { RequestEvent } from "../runs/events.js";
|
|
|
6
6
|
export interface SavedRunSettings {
|
|
7
7
|
record?: boolean | undefined;
|
|
8
8
|
keepRunsForDays?: number | undefined;
|
|
9
|
+
/** `record_input`: left out, the typed words are kept, secrets hidden. */
|
|
10
|
+
recordInput?: boolean | undefined;
|
|
9
11
|
}
|
|
10
12
|
export interface RunSettings {
|
|
11
13
|
record: boolean;
|
|
@@ -59,6 +61,4 @@ export declare const providerRequest: (operation: string, sink?: (event: Request
|
|
|
59
61
|
* on. Call it after the program's other `parseAsync` wrappers, so its own is the outermost and sees how the parse
|
|
60
62
|
* really ended; a refusal in an earlier `preAction` hook is then kept under the words read from the line.
|
|
61
63
|
*/
|
|
62
|
-
export declare const recordRuns: (program: Command, options: CommandRunOptions
|
|
63
|
-
content: string;
|
|
64
|
-
}) => void;
|
|
64
|
+
export declare const recordRuns: (program: Command, options: CommandRunOptions) => void;
|
package/dist/cli/command-runs.js
CHANGED
|
@@ -2,7 +2,9 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import { isCliError } from "@wirecat/cli-base";
|
|
3
3
|
import { startRecording, wasSettled } from "../runs/recording.js";
|
|
4
4
|
import { KEEP_RUNS_FOR_DAYS } from "../runs/run.js";
|
|
5
|
+
import { runContextOf } from "../runs/run-context.js";
|
|
5
6
|
import { environmentOf } from "./context.js";
|
|
7
|
+
import { rootOf } from "./profile.js";
|
|
6
8
|
/** `--record`/`--no-record` first, then the config; a failure is kept unless either says no (WC-LIFE-15). */
|
|
7
9
|
export const runSettings = (flag, saved = {}) => {
|
|
8
10
|
const record = flag ?? saved.record ?? false;
|
|
@@ -41,9 +43,10 @@ const tracker = (options) => {
|
|
|
41
43
|
const agent = options.agent ?? environment.agent !== undefined;
|
|
42
44
|
const streams = options.streams ?? environment.streams;
|
|
43
45
|
const env = options.env ?? environment.env;
|
|
46
|
+
const command = `${agent ? "mcp " : ""}${words.join(" ") || options.app.command}`;
|
|
44
47
|
recording = startRecording({
|
|
45
48
|
app: options.app,
|
|
46
|
-
command
|
|
49
|
+
command,
|
|
47
50
|
...(profile === undefined ? {} : { profile }),
|
|
48
51
|
record: settings.record,
|
|
49
52
|
keepFailed: settings.keepFailedRuns,
|
|
@@ -52,6 +55,14 @@ const tracker = (options) => {
|
|
|
52
55
|
format,
|
|
53
56
|
...(streams ? { streams } : {}),
|
|
54
57
|
...(env ? { env } : {}),
|
|
58
|
+
context: runContextOf({
|
|
59
|
+
bin: options.app.command,
|
|
60
|
+
command: words.length > 0 ? command : "",
|
|
61
|
+
program: rootOf(program),
|
|
62
|
+
...(environment.argv ? { argv: environment.argv } : {}),
|
|
63
|
+
env: env ?? process.env,
|
|
64
|
+
recordInput: saved.recordInput !== false,
|
|
65
|
+
}),
|
|
55
66
|
});
|
|
56
67
|
};
|
|
57
68
|
const finish = () => {
|
|
@@ -131,7 +142,7 @@ const isHelpExit = (error) => {
|
|
|
131
142
|
*/
|
|
132
143
|
export const recordRuns = (program, options) => {
|
|
133
144
|
program
|
|
134
|
-
.option("--record",
|
|
145
|
+
.option("--record", "keep this run, with the command as typed and secrets hidden; record_input false leaves the typed words out")
|
|
135
146
|
.option("--no-record", "do not keep this run, even if it fails");
|
|
136
147
|
program.hook("preAction", (_root, action) => {
|
|
137
148
|
const parse = parses.getStore();
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -24,8 +24,8 @@ export { repliesStatePathFor } from "../replies/state.js";
|
|
|
24
24
|
export { type CacheEvent, type DiagnosticEvent, type EventSink, providerErrorKey, type RequestEvent, renderEvent, type WarningEvent, } from "../runs/events.js";
|
|
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
|
-
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";
|
|
27
|
+
export { findRun, KEEP_RUNS_FOR_DAYS, listRuns, pruneRuns, type RunContext, type RunMetadata, readEvents, runsDirFor, runtime, startRun, } from "../runs/run.js";
|
|
28
|
+
export { runContextOf, secretInput } from "../runs/run-context.js";
|
|
29
29
|
export { currentRun, inRun, type RunScope, useAsProcessRun } from "../runs/scope.js";
|
|
30
30
|
export { LANGUAGES } from "../search/languages.js";
|
|
31
31
|
export { guardedApp, guardedPress, guardedStart, type Pressed } from "../sends/buttons.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -21,7 +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
|
+
export { runContextOf, secretInput } from "../runs/run-context.js";
|
|
25
25
|
export { currentRun, inRun, useAsProcessRun } from "../runs/scope.js";
|
|
26
26
|
export { LANGUAGES } from "../search/languages.js";
|
|
27
27
|
export { guardedApp, guardedPress, guardedStart } from "../sends/buttons.js";
|
|
@@ -3,6 +3,7 @@ import { annotate } from "@wirecat/cli-base/commands";
|
|
|
3
3
|
import { defineQuery } from "@wirecat/cli-base/query";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { REGISTRIES, registriesCover } from "../../botcheck/registries.js";
|
|
6
|
+
import { secretInput } from "../../runs/run-context.js";
|
|
6
7
|
import { levelFor } from "../../sends/permissions.js";
|
|
7
8
|
import { phoneOf } from "../../services/index.js";
|
|
8
9
|
import { dateWindow } from "../../services/moment.js";
|
|
@@ -276,7 +277,7 @@ export const contactsCommand = (messenger) => {
|
|
|
276
277
|
* ⚠ **The number is asked for or piped, never an argument**: argv is read by `ps` and kept by shell
|
|
277
278
|
* history, and a phone number is personal data.
|
|
278
279
|
*/
|
|
279
|
-
annotate(contacts.command("lookup"), { effects: REMOTE_READ, output: { entity: "contact_lookup" } })
|
|
280
|
+
annotate(secretInput(contacts.command("lookup")), { effects: REMOTE_READ, output: { entity: "contact_lookup" } })
|
|
280
281
|
.summary("find an account by a phone number")
|
|
281
282
|
.description("who has this phone number — asks for it, or reads it from stdin; never an argument")
|
|
282
283
|
// Commander's own refusal would repeat the number on stderr.
|
package/dist/cli/program.js
CHANGED
|
@@ -69,7 +69,7 @@ export const createProgram = ({ app, commands, configure, firstScreen }, { out,
|
|
|
69
69
|
.option("--full", `${found} with their whole text and every provider field`)
|
|
70
70
|
.option("--dry-run", "preview parsed arguments and permissions before running the action")
|
|
71
71
|
.option("--yes", "go ahead without the question an ask level puts before a write")
|
|
72
|
-
.option("--record",
|
|
72
|
+
.option("--record", "keep this run, with the command as typed and secrets hidden; record_input false leaves the typed words out")
|
|
73
73
|
.option("--no-record", "do not keep it, whatever the configuration says")
|
|
74
74
|
.showHelpAfterError();
|
|
75
75
|
for (const command of mounted)
|
|
@@ -34,6 +34,9 @@ export declare const framesOf: (error: unknown, limit?: number) => string[];
|
|
|
34
34
|
* or a token in the path as `:id` / `:token`. The host stays — which service refused is the point.
|
|
35
35
|
*/
|
|
36
36
|
export declare const endpointTemplate: (raw: string) => string;
|
|
37
|
+
/** A phone number as people type it with its country code, and one written as digits only without the "+". */
|
|
38
|
+
export declare const INTERNATIONAL_PHONE: string;
|
|
39
|
+
export declare const BARE_PHONE: string;
|
|
37
40
|
/**
|
|
38
41
|
* An error's sentence with what could be somebody's words or a secret taken out: anything in quotes, an email, a
|
|
39
42
|
* phone number, a bot token, a bearer or a long opaque string, a URL's query, the home directory. A second line of
|
|
@@ -109,6 +109,9 @@ export const endpointTemplate = (raw) => {
|
|
|
109
109
|
});
|
|
110
110
|
return `${scheme}${segments.join("/")}`.slice(0, 300);
|
|
111
111
|
};
|
|
112
|
+
/** A phone number as people type it with its country code, and one written as digits only without the "+". */
|
|
113
|
+
export const INTERNATIONAL_PHONE = /\+\d[\d\s()-]{6,}\d/.source;
|
|
114
|
+
export const BARE_PHONE = /(^|[^\w-])\d{11,15}\b/.source;
|
|
112
115
|
/**
|
|
113
116
|
* An error's sentence with what could be somebody's words or a secret taken out: anything in quotes, an email, a
|
|
114
117
|
* phone number, a bot token, a bearer or a long opaque string, a URL's query, the home directory. A second line of
|
|
@@ -121,8 +124,8 @@ export const redactText = (text) => {
|
|
|
121
124
|
.replace(/"[^"\n]*"|“[^”\n]*”|«[^»\n]*»/g, '"[redacted]"')
|
|
122
125
|
.replace(/\b(bearer|basic|token|key|password|secret)(\s*[:=]\s*|\s+)[^\s'"]+/gi, "$1$2[redacted]")
|
|
123
126
|
.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "[email]")
|
|
124
|
-
.replace(
|
|
125
|
-
.replace(
|
|
127
|
+
.replace(new RegExp(INTERNATIONAL_PHONE, "g"), "[number]")
|
|
128
|
+
.replace(new RegExp(BARE_PHONE, "g"), "$1[number]")
|
|
126
129
|
.replace(/\bbot\d+:[\w-]+/g, "bot[token]")
|
|
127
130
|
.replace(/\b\d{6,}:[\w-]{20,}\b/g, "[token]")
|
|
128
131
|
.replace(/(\?|&)[^\s#]*/g, "$1[redacted]")
|
|
@@ -22,12 +22,15 @@ export interface MaskedArgv {
|
|
|
22
22
|
/** The first word that names no command where one was expected. */
|
|
23
23
|
unknownCommand?: string;
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Marks an option or argument whose value a run record never keeps, whatever the owner's `record_input`; on a command,
|
|
27
|
+
* every word typed after it that is not an option.
|
|
28
|
+
*/
|
|
29
|
+
export declare const secretInput: <T extends Option | Argument | Command>(input: T) => T;
|
|
27
30
|
/**
|
|
28
31
|
* 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`),
|
|
30
|
-
* like a credential. With `keep` off, command words, flag names and the profile stay; a value stays only when its
|
|
32
|
+
* as typed except a secret: a value of an input marked `secretInput` or named for one (`--token`), a word shaped
|
|
33
|
+
* like a credential, and a phone number inside any word. With `keep` off, command words, flag names and the profile stay; a value stays only when its
|
|
31
34
|
* shape cannot be somebody's words — a number short of a phone's length, a duration, a date, `true`, one of the
|
|
32
35
|
* option's declared choices — and its option or argument is not named for content (`--text`, `<phone>`). Anything
|
|
33
36
|
* else, a positional after `--` or an argument no command declares, is `[redacted]`: a chat is often typed by title.
|
|
@@ -35,7 +38,11 @@ export declare const secretInput: <T extends Option | Argument>(input: T) => T;
|
|
|
35
38
|
export declare const maskArgv: (program: Command, words: readonly string[], { keep }?: {
|
|
36
39
|
keep?: boolean | undefined;
|
|
37
40
|
}) => MaskedArgv;
|
|
38
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* A run's context from the parsed program: `max messages list` and its words, or the word that named nothing. A CLI
|
|
43
|
+
* that calls `recorded` or `startRecording` for a command of its own passes this as `context`; without it the run
|
|
44
|
+
* keeps no command line and no agent.
|
|
45
|
+
*/
|
|
39
46
|
export declare const runContextOf: ({ bin, command, program, argv, env, recordInput, }: {
|
|
40
47
|
bin: string;
|
|
41
48
|
/** The run's own name, `messages list` or `mcp messages list`; empty when no command was recognised. */
|
package/dist/runs/run-context.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isContentKey } from "@wirecat/cli-base/standards";
|
|
2
|
+
import { BARE_PHONE, INTERNATIONAL_PHONE } from "./error-detail.js";
|
|
2
3
|
export const REDACTED = "[redacted]";
|
|
3
4
|
/**
|
|
4
5
|
* The agent is read from what each one sets in the processes it starts: Claude Code `CLAUDECODE` and
|
|
@@ -40,7 +41,10 @@ const sessionOf = (kind, env) => {
|
|
|
40
41
|
};
|
|
41
42
|
const plainWord = (word) => /^[A-Za-z][A-Za-z0-9.-]{0,39}$/.test(word);
|
|
42
43
|
const secretInputs = new WeakSet();
|
|
43
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Marks an option or argument whose value a run record never keeps, whatever the owner's `record_input`; on a command,
|
|
46
|
+
* every word typed after it that is not an option.
|
|
47
|
+
*/
|
|
44
48
|
export const secretInput = (input) => {
|
|
45
49
|
secretInputs.add(input);
|
|
46
50
|
return input;
|
|
@@ -67,10 +71,12 @@ const secretNamed = (name) => {
|
|
|
67
71
|
};
|
|
68
72
|
// A password inside a URL, or one unbroken run of 40 token characters, is a credential wherever it was typed.
|
|
69
73
|
const secretShaped = (word) => /:\/\/[^/@\s:]+:[^/@\s]+@/.test(word) || /(^|[^A-Za-z0-9_-])[A-Za-z0-9_-]{40,}($|[^A-Za-z0-9_-])/.test(word);
|
|
74
|
+
// Messaging CLIs promise a phone number reaches no log, so it goes even from text the record otherwise keeps.
|
|
75
|
+
const withoutPhones = (word) => word.replace(new RegExp(INTERNATIONAL_PHONE, "g"), REDACTED).replace(new RegExp(BARE_PHONE, "g"), `$1${REDACTED}`);
|
|
70
76
|
/**
|
|
71
77
|
* 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`),
|
|
73
|
-
* like a credential. With `keep` off, command words, flag names and the profile stay; a value stays only when its
|
|
78
|
+
* as typed except a secret: a value of an input marked `secretInput` or named for one (`--token`), a word shaped
|
|
79
|
+
* like a credential, and a phone number inside any word. With `keep` off, command words, flag names and the profile stay; a value stays only when its
|
|
74
80
|
* shape cannot be somebody's words — a number short of a phone's length, a duration, a date, `true`, one of the
|
|
75
81
|
* option's declared choices — and its option or argument is not named for content (`--text`, `<phone>`). Anything
|
|
76
82
|
* else, a positional after `--` or an argument no command declares, is `[redacted]`: a chat is often typed by title.
|
|
@@ -82,7 +88,7 @@ export const maskArgv = (program, words, { keep = false } = {}) => {
|
|
|
82
88
|
let positional = 0;
|
|
83
89
|
let profileSeen = false;
|
|
84
90
|
let lost = false;
|
|
85
|
-
const raw = (word) => (keep && !secretShaped(word) ? word : REDACTED);
|
|
91
|
+
const raw = (word) => (keep && !secretShaped(word) ? withoutPhones(word) : REDACTED);
|
|
86
92
|
const keptValue = (name, value, input) => keep
|
|
87
93
|
? (input && secretInputs.has(input)) || secretNamed(name)
|
|
88
94
|
? REDACTED
|
|
@@ -145,7 +151,7 @@ export const maskArgv = (program, words, { keep = false } = {}) => {
|
|
|
145
151
|
}
|
|
146
152
|
if (current === program && !profileSeen) {
|
|
147
153
|
profileSeen = true;
|
|
148
|
-
argv.push(keep ? raw(word) : PROFILE.test(word) ? word : REDACTED);
|
|
154
|
+
argv.push(keep ? raw(word) : PROFILE.test(word) && withoutPhones(word) === word ? word : REDACTED);
|
|
149
155
|
continue;
|
|
150
156
|
}
|
|
151
157
|
if (current.commands.length > 0 && current.registeredArguments.length === 0) {
|
|
@@ -158,7 +164,7 @@ export const maskArgv = (program, words, { keep = false } = {}) => {
|
|
|
158
164
|
const declared = lost ? undefined : current.registeredArguments;
|
|
159
165
|
const argument = declared?.[Math.min(positional, declared.length - 1)];
|
|
160
166
|
positional += 1;
|
|
161
|
-
argv.push(argument ? keptValue(argument.name(), word, argument) : raw(word));
|
|
167
|
+
argv.push(secretInputs.has(current) ? REDACTED : argument ? keptValue(argument.name(), word, argument) : raw(word));
|
|
162
168
|
}
|
|
163
169
|
return { argv, ...(unknownCommand === undefined ? {} : { unknownCommand }) };
|
|
164
170
|
};
|
|
@@ -184,7 +190,11 @@ const safeShape = (value) => (NUMBER.test(value) && !/^\d{11,}$/.test(value)) ||
|
|
|
184
190
|
/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/.test(value) ||
|
|
185
191
|
value === "true" ||
|
|
186
192
|
value === "false";
|
|
187
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* A run's context from the parsed program: `max messages list` and its words, or the word that named nothing. A CLI
|
|
195
|
+
* that calls `recorded` or `startRecording` for a command of its own passes this as `context`; without it the run
|
|
196
|
+
* keeps no command line and no agent.
|
|
197
|
+
*/
|
|
188
198
|
export const runContextOf = ({ bin, command, program, argv, env, recordInput = false, }) => {
|
|
189
199
|
const masked = program && argv ? maskArgv(program, argv, { keep: recordInput }) : undefined;
|
|
190
200
|
const agent = agentOf(env);
|
package/dist/services/tasks.js
CHANGED
|
@@ -15,17 +15,7 @@ export const TASK_QUERY = {
|
|
|
15
15
|
entity: "task",
|
|
16
16
|
backend: "sql",
|
|
17
17
|
fields: queryFieldsOf(taskEntity, {
|
|
18
|
-
exclude: [
|
|
19
|
-
"ref",
|
|
20
|
-
"description",
|
|
21
|
-
"project",
|
|
22
|
-
"account",
|
|
23
|
-
"priority",
|
|
24
|
-
"parent",
|
|
25
|
-
"claimed_by",
|
|
26
|
-
"claimed_until",
|
|
27
|
-
"close_suggestion",
|
|
28
|
-
],
|
|
18
|
+
exclude: ["ref", "description", "project", "account", "priority", "parent", "claimed_until", "close_suggestion"],
|
|
29
19
|
relations: {
|
|
30
20
|
close: {
|
|
31
21
|
type: "enum",
|
|
@@ -47,6 +37,7 @@ export const TASK_QUERY = {
|
|
|
47
37
|
chat: "chat:12345",
|
|
48
38
|
title: "title:*invoice*",
|
|
49
39
|
source_message: 'source_message:"msg:telegram/1/2/3"',
|
|
40
|
+
claimed_by: "claimed_by:session-1",
|
|
50
41
|
},
|
|
51
42
|
}),
|
|
52
43
|
};
|
|
@@ -84,6 +75,13 @@ const taskColumns = (groups, tags) => ({
|
|
|
84
75
|
: { sql: "t.source_group = ?", params: [groups.get(node.value) ?? ""] },
|
|
85
76
|
},
|
|
86
77
|
source_message: { type: "ref", sql: "t.source_locator" },
|
|
78
|
+
// A lapsed claim holds nothing, as tasks claim and the list's own claimed_by read it.
|
|
79
|
+
claimed_by: {
|
|
80
|
+
where: (node) => ({
|
|
81
|
+
sql: `t.claimed_by ${node.operator === "wildcard" ? "GLOB" : "="} ? AND t.claimed_until >= ?`,
|
|
82
|
+
params: [node.value, Date.now()],
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
87
85
|
reason: { type: "kw", sql: "t.close_reason_folded", folded: true },
|
|
88
86
|
close: { where: () => ({ sql: "t.close_suggested_as IS NOT NULL", params: [] }) },
|
|
89
87
|
assignee: {
|
|
@@ -343,16 +341,21 @@ export const tasksService = (deps, categories) => {
|
|
|
343
341
|
const { pair } = await store.tasks.unmerge(dup, by === "owner" ? "owner" : { bot: by });
|
|
344
342
|
return { pair, task: await cardOf(store, dup) };
|
|
345
343
|
}),
|
|
346
|
-
|
|
347
|
-
|
|
344
|
+
// A project task belongs to no messenger account, as show reads it.
|
|
345
|
+
claim: async (id, holder, forMs) => {
|
|
346
|
+
const store = await deps.store();
|
|
347
|
+
if (!(await store.tasks.projectTask(id)))
|
|
348
|
+
await owned(store, await deps.account(), id);
|
|
348
349
|
await store.tasks.claim(id, holder, new Date(Date.now() + forMs));
|
|
349
350
|
return cardOf(store, id);
|
|
350
|
-
}
|
|
351
|
-
release: (id, holder) =>
|
|
352
|
-
|
|
351
|
+
},
|
|
352
|
+
release: async (id, holder) => {
|
|
353
|
+
const store = await deps.store();
|
|
354
|
+
if (!(await store.tasks.projectTask(id)))
|
|
355
|
+
await owned(store, await deps.account(), id);
|
|
353
356
|
await store.tasks.release(id, holder);
|
|
354
357
|
return cardOf(store, id);
|
|
355
|
-
}
|
|
358
|
+
},
|
|
356
359
|
close: (id, { as, reason, by, agent, certainty, suggestOnly }) => inStore(async (store, account) => {
|
|
357
360
|
const existing = await owned(store, account, id);
|
|
358
361
|
const suggested = existing.state === "open" ? await store.tasks.closeSuggestion(id) : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.441.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",
|
package/parity.json
CHANGED
|
@@ -822,7 +822,7 @@
|
|
|
822
822
|
"meaning": "why a task was closed, kept with it — no-reply-needed, for example"
|
|
823
823
|
},
|
|
824
824
|
"--record": {
|
|
825
|
-
"meaning": "keep this run under `runs
|
|
825
|
+
"meaning": "keep this run under `runs`, with the command as typed and secrets hidden; record_input false leaves the typed words out",
|
|
826
826
|
"note": "max's own copy is worded differently until T6 moves the command onto the shared one (e13)"
|
|
827
827
|
},
|
|
828
828
|
"--remind-time": {
|