@wirecat/cli-core 0.347.0 → 0.348.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 +54 -0
- package/dist/cli/bot/copy.d.ts +1 -1
- package/dist/cli/bot/copy.js +1 -1
- package/dist/cli/embedding-choice.js +2 -1
- package/dist/cli/envelope.d.ts +4 -0
- package/dist/cli/envelope.js +4 -3
- package/dist/cli/guide.js +4 -0
- package/dist/cli/help/first-screen.js +13 -1
- package/dist/cli/help/sections.d.ts +4 -3
- package/dist/cli/help/sections.js +5 -10
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/messenger/backfill-command.js +6 -6
- package/dist/cli/messenger/context.d.ts +3 -3
- package/dist/cli/messenger/context.js +44 -17
- package/dist/cli/messenger/doctor-command.js +3 -3
- package/dist/cli/messenger/flooded.js +1 -1
- package/dist/cli/messenger/mcp-command.js +1 -1
- package/dist/cli/messenger/messages-delete-command.js +2 -1
- package/dist/cli/messenger/observed.js +21 -7
- package/dist/cli/messenger/schedule-command.js +4 -3
- package/dist/cli/messenger/serve-relay.js +1 -0
- package/dist/cli/messenger/store-maintenance-command.js +3 -3
- package/dist/cli/messenger/stored.js +36 -30
- package/dist/cli/program.js +10 -9
- package/dist/cli/runs/command.js +3 -2
- package/dist/cli/steps.d.ts +4 -0
- package/dist/cli/steps.js +9 -0
- package/dist/cli/store-settings.js +4 -3
- package/dist/domain/models.d.ts +11 -1
- package/dist/kit/contract.d.ts +2 -2
- package/dist/kit/contract.js +25 -0
- package/dist/kit/fake.d.ts +1 -0
- package/dist/kit/fake.js +15 -16
- package/dist/mcp/meeting-preparation.d.ts +1 -0
- package/dist/mcp/meeting-project-candidates.d.ts +1 -0
- package/dist/mcp/meeting-series-inventory.d.ts +1 -0
- package/dist/mcp/tool.js +2 -0
- package/dist/mcp/tools/messages-photo.js +2 -1
- package/dist/meetings/mcp/passage.d.ts +1 -0
- package/dist/meetings/mcp/project-evidence.d.ts +1 -0
- package/dist/meetings/mcp/search.d.ts +1 -0
- package/dist/meetings/reads.d.ts +1 -0
- package/dist/ports/messenger.d.ts +10 -2
- package/dist/runs/recording.js +1 -0
- package/dist/runs/scope.d.ts +7 -0
- package/dist/runs/scope.js +5 -0
- package/dist/services/archive.js +2 -1
- package/dist/services/chats.js +3 -2
- package/dist/services/conversations.js +2 -1
- package/dist/services/deps.js +2 -1
- package/dist/services/embeddings-maintenance.js +3 -2
- package/dist/services/index.d.ts +1 -1
- package/dist/services/search-all-meetings.d.ts +3 -4
- package/dist/services/search-all.d.ts +8 -1
- package/dist/services/search-all.js +1 -1
- package/dist/speech/transcribe.js +2 -1
- package/dist/store/chats.d.ts +2 -2
- package/dist/store/sqlite/chats.d.ts +2 -2
- package/dist/store/sqlite/chats.js +7 -7
- package/dist/store/types.d.ts +2 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,60 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.348.0 — 19.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **A next step in `--json` output and in an error carries `line`, the whole command with the profile**:
|
|
15
|
+
`"line": "max personal mcp config"` beside `"command": "mcp config"`. A copied `command` alone failed with "name a
|
|
16
|
+
profile". MCP answers leave it out; `command` is unchanged.
|
|
17
|
+
- **`searchAll(…, { app, hintCommand })`**: the tool a missing-model hint names, apart from `app.command`. memo passes
|
|
18
|
+
tg's identity for `group:` lists but has no `models` command; `hintCommand: null` names the tools that have one
|
|
19
|
+
(tg, max or wa) instead of `tg models text download`. `searchAllIncludingMeetings` and `searchAllWithMeetings`
|
|
20
|
+
take it too (`SearchAllMessenger`).
|
|
21
|
+
- **`commandsText(program, describeProgram(program), scope?)` from `./cli`**: the `<tool> commands` text list by
|
|
22
|
+
section, for a tool that builds its own commander program and keeps its own `commands` command (zm, drive, memo).
|
|
23
|
+
cli-core's groups land in their section on such a program too; before, only `createProgram` put them there.
|
|
24
|
+
- **A messenger whose history is in the store can save the history pushed during any command, not only `serve`
|
|
25
|
+
and `watch`.** `PassiveUpdates` may now also have `listenHistory(onBatch, signal)`, optional even in an adapter
|
|
26
|
+
typed as `PassiveUpdates`: it hands on the same `HistoryBatch`es as `feed`, re-keys included, from a connection that
|
|
27
|
+
does not watch, without connecting. A one-shot command and an MCP session then save them through `feed`'s own
|
|
28
|
+
code, and `close` stops it with `listen` (both within 5 s in all) before it waits for the saves. Without it, a
|
|
29
|
+
WhatsApp history push that came during such a command was acknowledged and then lost. An adapter without it
|
|
30
|
+
behaves as before. Its run record is an `adapter.listenHistory` request whose response counts `batches` and each
|
|
31
|
+
part's items (`rekeys`, `chats`, `messages`, …); a failure is a `listen_failed` warning for `history.listen`. The
|
|
32
|
+
contract kit checks it as it checks `listen` when the adapter has it, `OPTIONAL_METHODS` lists it, and
|
|
33
|
+
`fakeAdapter(seed, { listenHistory: true })` has it.
|
|
34
|
+
|
|
35
|
+
### Changed — may break callers
|
|
36
|
+
|
|
37
|
+
- **Peer `@wirecat/cli-base` is `>=0.47.0 <0.48.0`** (was `>=0.45.0 <0.47.0`): 0.47.0 is the first whose strict
|
|
38
|
+
`nextStepSchema` accepts `line`, so a tool on an older cli-base would refuse cli-core's own answers in its schema
|
|
39
|
+
checks. Bump cli-base with cli-core.
|
|
40
|
+
- **Saving a chat keeps a `title`, `unreadCount`, `lastMessageAt` or `participantsCount` the update leaves out**, the
|
|
41
|
+
rule #1596 set for `providerMetadata` keys and `parentChatId`: left out keeps the stored value, `null` stores none,
|
|
42
|
+
a value replaces it. So a push that knows only part of a chat, such as WhatsApp's notice that a group left its
|
|
43
|
+
community, sends `{ id, kind, parentChatId: null }` and no longer erases the rest. `saveChats`, `Delta.chats` and
|
|
44
|
+
`HistoryBatch.chats` take the new `ChatUpdate` type, a `Chat` with those four fields optional; a full `Chat` still
|
|
45
|
+
fits, and every adapter today sends all four, so none has to change. To clear a field, send `null`, never leave it
|
|
46
|
+
out. Code that reads `HistoryBatch.chats` or `Delta.chats` now sees the four fields as possibly absent.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **The first screen names only the option topics the tool has.** `help options` was described as "output, offline,
|
|
51
|
+
timeouts, dry run" on every program; a tool with its own program and none of `--json`, `--offline`, `--timeout`,
|
|
52
|
+
`--dry-run` (memo, zm, drive) now reads "the options every command takes", and one with some names those.
|
|
53
|
+
- **A guide step may ask for help.** `checkGuide` refused `chat messages list --help` and `-h` ("no option --help")
|
|
54
|
+
and `chat messages --help` ("needs one of: …"); a step with the help flag now only needs its command path to exist.
|
|
55
|
+
- **A command's own help settings survive the `<profile>` usage line.** `createProgram` replaced each command's help
|
|
56
|
+
configuration when it set the usage line, so tg's `setup --help` lost its "Global Options" block
|
|
57
|
+
(`showGlobalOptions`). It now adds the usage line to what the command set.
|
|
58
|
+
- **Hints in errors and notes name the profile**: "the store is behind this build — `max personal store migrate`"
|
|
59
|
+
instead of `max store migrate`, which fails with "name a profile" when copied. Covers about 30 hints (store, runs,
|
|
60
|
+
schedules, sync, models, conversations, serve, MCP's photo and transcribe tools). `typedCommand(tool, words)`
|
|
61
|
+
(run scope) and `lineOf(command, words)` build them; a run's scope now carries its `profile`. Consumers' tests
|
|
62
|
+
that match these messages change on bump.
|
|
63
|
+
|
|
10
64
|
## 0.347.0 — 18.10.2026
|
|
11
65
|
|
|
12
66
|
### Added
|
package/dist/cli/bot/copy.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { BotEvent } from "./port.js";
|
|
|
9
9
|
export declare const botCopy: (provider: Provider) => {
|
|
10
10
|
accountOf: (botId: string) => AccountKey;
|
|
11
11
|
keep: (botId: string, messages: readonly Message[], via: IngestedVia, warn: (message: string) => void, senders?: readonly PersonFacts[]) => Promise<boolean>;
|
|
12
|
-
/** Only a chat the messenger has just described in full: the store
|
|
12
|
+
/** Only a chat the messenger has just described in full: the store takes its title and counts as given. */
|
|
13
13
|
keepChat: (botId: string, chat: Chat, warn: (message: string) => void) => Promise<void>;
|
|
14
14
|
/** Messages the bot deleted, kept as tombstones: reads and search stop returning them. */
|
|
15
15
|
forget: (botId: string, chatId: string, messageIds: string[], warn: (message: string) => void) => Promise<boolean>;
|
package/dist/cli/bot/copy.js
CHANGED
|
@@ -57,7 +57,7 @@ export const botCopy = (provider) => {
|
|
|
57
57
|
await store.people.savePeople(accountOf(botId), [...senders]);
|
|
58
58
|
}, warn);
|
|
59
59
|
},
|
|
60
|
-
/** Only a chat the messenger has just described in full: the store
|
|
60
|
+
/** Only a chat the messenger has just described in full: the store takes its title and counts as given. */
|
|
61
61
|
keepChat: async (botId, chat, warn) => {
|
|
62
62
|
if (CHAT_ID.test(chat.id))
|
|
63
63
|
await quietly((store) => store.chats.saveChats(accountOf(botId), [chat]), warn);
|
|
@@ -4,6 +4,7 @@ import { embeddingKeys, endpointKeyName } from "../embeddings/keys.js";
|
|
|
4
4
|
import { DEFAULT_TEXT_MODEL } from "../embeddings/models.js";
|
|
5
5
|
import { isLocal, remoteModel } from "../embeddings/remote.js";
|
|
6
6
|
import { readOnlyLockOf, readOnlyRefusal } from "../ports/app.js";
|
|
7
|
+
import { typedCommand } from "../runs/scope.js";
|
|
7
8
|
/** A local model id, or a remote model with its key from `models text key set`. */
|
|
8
9
|
export const embeddingChoice = ({ model, provider, baseUrl, dims, concurrency }, app, settings, env, { needKey = true, keyring } = {}) => {
|
|
9
10
|
const flagsChooseProvider = (provider !== undefined && provider !== (settings.embeddingProvider ?? "local")) ||
|
|
@@ -30,7 +31,7 @@ export const embeddingChoice = ({ model, provider, baseUrl, dims, concurrency },
|
|
|
30
31
|
const keyName = baseUrl === undefined ? "openai" : endpointKeyName(baseUrl);
|
|
31
32
|
const key = embeddingKeys(app, env, keyring).read(keyName)?.key;
|
|
32
33
|
if (!key && needKey && baseUrl === undefined) {
|
|
33
|
-
throw new CliError("authentication_error", `no OpenAI key — \`${app.command
|
|
34
|
+
throw new CliError("authentication_error", `no OpenAI key — \`${typedCommand(app.command, "models text key set openai")}\`, or OPENAI_API_KEY`);
|
|
34
35
|
}
|
|
35
36
|
return { remote, ...(key ? { apiKey: key } : {}), ...(concurrency ? { concurrency } : {}) };
|
|
36
37
|
};
|
package/dist/cli/envelope.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export declare const answerSchema: (item?: v.GenericSchema) => v.UnionSchema<[v.
|
|
|
44
44
|
readonly step: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "dotted lower-case words, such as `page.next`">]>;
|
|
45
45
|
readonly command: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>;
|
|
46
46
|
readonly args: v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>;
|
|
47
|
+
readonly line: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>, undefined>;
|
|
47
48
|
readonly why: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>;
|
|
48
49
|
readonly safe_to_auto_execute: v.BooleanSchema<undefined>;
|
|
49
50
|
}, undefined>, v.StrictObjectSchema<{
|
|
@@ -58,6 +59,7 @@ export declare const answerSchema: (item?: v.GenericSchema) => v.UnionSchema<[v.
|
|
|
58
59
|
args: {
|
|
59
60
|
[x: string]: unknown;
|
|
60
61
|
};
|
|
62
|
+
line?: string | undefined;
|
|
61
63
|
why: string;
|
|
62
64
|
safe_to_auto_execute: boolean;
|
|
63
65
|
} | {
|
|
@@ -90,6 +92,7 @@ export declare const answerSchema: (item?: v.GenericSchema) => v.UnionSchema<[v.
|
|
|
90
92
|
readonly step: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "dotted lower-case words, such as `page.next`">]>;
|
|
91
93
|
readonly command: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>;
|
|
92
94
|
readonly args: v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>;
|
|
95
|
+
readonly line: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>, undefined>;
|
|
93
96
|
readonly why: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>;
|
|
94
97
|
readonly safe_to_auto_execute: v.BooleanSchema<undefined>;
|
|
95
98
|
}, undefined>, v.StrictObjectSchema<{
|
|
@@ -104,6 +107,7 @@ export declare const answerSchema: (item?: v.GenericSchema) => v.UnionSchema<[v.
|
|
|
104
107
|
args: {
|
|
105
108
|
[x: string]: unknown;
|
|
106
109
|
};
|
|
110
|
+
line?: string | undefined;
|
|
107
111
|
why: string;
|
|
108
112
|
safe_to_auto_execute: boolean;
|
|
109
113
|
} | {
|
package/dist/cli/envelope.js
CHANGED
|
@@ -3,9 +3,9 @@ import { metaOf } from "@wirecat/cli-base/commands";
|
|
|
3
3
|
import { itemEnvelopeSchema, jsonlOf, listEnvelopeSchema, MAX_NEXT_STEPS, } from "@wirecat/cli-base/standards";
|
|
4
4
|
import * as v from "valibot";
|
|
5
5
|
import { currentRun } from "../runs/scope.js";
|
|
6
|
-
import { setExitCode } from "./context.js";
|
|
6
|
+
import { environmentOf, setExitCode } from "./context.js";
|
|
7
7
|
import { withoutProviderRefs } from "./provider-refs.js";
|
|
8
|
-
import { nextStep, rootOf, runnable, stepLine, withSafety } from "./steps.js";
|
|
8
|
+
import { nextStep, rootOf, runnable, stepLine, withLines, withSafety } from "./steps.js";
|
|
9
9
|
/** Every answer: a list or one thing, with items of `item` — any value where the command declares no entity. */
|
|
10
10
|
export const answerSchema = (item = v.unknown()) => v.union([listEnvelopeSchema(item), itemEnvelopeSchema(item)]);
|
|
11
11
|
const capped = (steps) => (steps.length > 0 ? { next: steps.slice(0, MAX_NEXT_STEPS) } : {});
|
|
@@ -131,7 +131,8 @@ export const renderAnswer = (command, { renderer, format, streams }, body, view)
|
|
|
131
131
|
if (!isBody(body))
|
|
132
132
|
refuse(body);
|
|
133
133
|
const root = rootOf(command);
|
|
134
|
-
const
|
|
134
|
+
const runnableSteps = runnable(root, body.next);
|
|
135
|
+
const next = environmentOf(command).agent ? runnableSteps : withLines(root, runnableSteps);
|
|
135
136
|
const kept = withoutProviderRefs({ ...body, ...(next.length > 0 ? { next } : {}) });
|
|
136
137
|
if (next.length === 0)
|
|
137
138
|
delete kept.next;
|
package/dist/cli/guide.js
CHANGED
|
@@ -211,6 +211,10 @@ export const checkStep = (program, run) => {
|
|
|
211
211
|
}
|
|
212
212
|
if (at === program)
|
|
213
213
|
return [`no command "${rest[0] ?? ""}" — steps leave out the profile`];
|
|
214
|
+
const help = at._getHelpOption();
|
|
215
|
+
const beforeDashes = rest.slice(index, rest.includes("--") ? rest.indexOf("--") : undefined);
|
|
216
|
+
if (help && beforeDashes.some((word) => word === help.long || word === help.short))
|
|
217
|
+
return [];
|
|
214
218
|
if (at.commands.length > 0)
|
|
215
219
|
return [`needs one of: ${at.commands.map((one) => one.name()).join(", ")}`];
|
|
216
220
|
for (const command of chain)
|
|
@@ -17,13 +17,25 @@ export const WIRECAT_TOOLS = [
|
|
|
17
17
|
const WIDTH = 100;
|
|
18
18
|
const widest = (texts) => texts.reduce((most, text) => Math.max(most, text.length), 0);
|
|
19
19
|
const block = (title, rows, column) => rows.length === 0 ? [] : ["", `${title}:`, ...rows.map(([left, right]) => ` ${left.padEnd(column)}${right}`)];
|
|
20
|
+
const TOPICS = [
|
|
21
|
+
["--json", "output"],
|
|
22
|
+
["--offline", "offline"],
|
|
23
|
+
["--timeout", "timeouts"],
|
|
24
|
+
["--dry-run", "dry run"],
|
|
25
|
+
];
|
|
26
|
+
/** Only the topics this program has options for: memo has no --offline. */
|
|
27
|
+
const optionTopics = (program) => {
|
|
28
|
+
const longs = new Set(program.options.map((option) => option.long));
|
|
29
|
+
const topics = TOPICS.filter(([long]) => longs.has(long)).map(([, topic]) => topic);
|
|
30
|
+
return topics.length > 0 ? topics.join(", ") : "the options every command takes";
|
|
31
|
+
};
|
|
20
32
|
/** Only commands the tool has: `guide` and `setup` are not in every tool. */
|
|
21
33
|
const pointers = (program, cli, start, profile) => {
|
|
22
34
|
const has = (path) => commandAt(program, path) !== undefined;
|
|
23
35
|
const next = [
|
|
24
36
|
has("commands") ? [`${cli} commands`, "every command, by section"] : undefined,
|
|
25
37
|
has("guide") ? [`${cli} guide`, "scenarios: command sequences for common tasks"] : undefined,
|
|
26
|
-
[`${cli} help options`,
|
|
38
|
+
[`${cli} help options`, optionTopics(program)],
|
|
27
39
|
].filter((row) => row !== undefined);
|
|
28
40
|
const rows = [
|
|
29
41
|
...(start ? [["Start here:", start, profile ? "(any profile name works)" : ""]] : []),
|
|
@@ -13,7 +13,8 @@ export type HelpSection = Exclude<keyof typeof SECTIONS, "other">;
|
|
|
13
13
|
* fields of `commands --json`, which must not change.
|
|
14
14
|
*/
|
|
15
15
|
export declare const inSection: <T extends Command>(command: T, section: HelpSection) => T;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The nearest section on the command or above it, then cli-core's own for a top-level group of that name; `other`
|
|
18
|
+
* when neither is set. Looked up when listed, so a tool with its own program needs no install step.
|
|
19
|
+
*/
|
|
17
20
|
export declare const sectionOf: (command: Command) => keyof typeof SECTIONS;
|
|
18
|
-
/** Puts each of cli-core's groups mounted on `program` in its section, unless the tool already chose one. */
|
|
19
|
-
export declare const sectionCoreGroups: (program: Command) => void;
|
|
@@ -15,10 +15,13 @@ export const inSection = (command, section) => {
|
|
|
15
15
|
sections.set(command, section);
|
|
16
16
|
return command;
|
|
17
17
|
};
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* The nearest section on the command or above it, then cli-core's own for a top-level group of that name; `other`
|
|
20
|
+
* when neither is set. Looked up when listed, so a tool with its own program needs no install step.
|
|
21
|
+
*/
|
|
19
22
|
export const sectionOf = (command) => {
|
|
20
23
|
for (let at = command; at; at = at.parent) {
|
|
21
|
-
const section = sections.get(at);
|
|
24
|
+
const section = sections.get(at) ?? (at.parent && !at.parent.parent ? CORE_SECTIONS[at.name()] : undefined);
|
|
22
25
|
if (section)
|
|
23
26
|
return section;
|
|
24
27
|
}
|
|
@@ -67,11 +70,3 @@ const CORE_SECTIONS = {
|
|
|
67
70
|
store: "setup",
|
|
68
71
|
upgrade: "setup",
|
|
69
72
|
};
|
|
70
|
-
/** Puts each of cli-core's groups mounted on `program` in its section, unless the tool already chose one. */
|
|
71
|
-
export const sectionCoreGroups = (program) => {
|
|
72
|
-
for (const command of program.commands) {
|
|
73
|
-
const section = CORE_SECTIONS[command.name()];
|
|
74
|
-
if (section && !sections.has(command))
|
|
75
|
-
inSection(command, section);
|
|
76
|
-
}
|
|
77
|
-
};
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -58,6 +58,7 @@ export { type AnswerOutput, answerSchema, type Body, type ItemBody, itemAnswer,
|
|
|
58
58
|
export { withAbort } from "./execution.js";
|
|
59
59
|
export { checkGuide, checkStep, type GuideOptions, type GuideReference, type GuideStep, guideCommand, loadScenarios, type Scenario, } from "./guide.js";
|
|
60
60
|
export { type CommandHelp, commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, type NextChoice, nextUsually, } from "./help/command-help.js";
|
|
61
|
+
export { commandsText } from "./help/commands-text.js";
|
|
61
62
|
export { type FirstScreen, type FirstScreenLine, firstScreen, installHelp, WIRECAT_TOOLS, } from "./help/first-screen.js";
|
|
62
63
|
export { type HelpSection, inSection, SECTIONS } from "./help/sections.js";
|
|
63
64
|
export { bufferedInput, type InputPolicy, inputPolicy, MAX_BUFFERED_INPUT, MAX_SECRET_INPUT, provideInputPolicy, } from "./input-policy.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -53,6 +53,7 @@ export { answerSchema, itemAnswer, itemOf, jsonlRecords, judgedAnswer, listOf, m
|
|
|
53
53
|
export { withAbort } from "./execution.js";
|
|
54
54
|
export { checkGuide, checkStep, guideCommand, loadScenarios, } from "./guide.js";
|
|
55
55
|
export { commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, nextUsually, } from "./help/command-help.js";
|
|
56
|
+
export { commandsText } from "./help/commands-text.js";
|
|
56
57
|
export { firstScreen, installHelp, WIRECAT_TOOLS, } from "./help/first-screen.js";
|
|
57
58
|
export { inSection, SECTIONS } from "./help/sections.js";
|
|
58
59
|
export { bufferedInput, inputPolicy, MAX_BUFFERED_INPUT, MAX_SECRET_INPUT, provideInputPolicy, } from "./input-policy.js";
|
|
@@ -17,7 +17,7 @@ import { destroysNothing, FETCH, LOCAL_READ, LOCAL_WRITE } from "../effects.js";
|
|
|
17
17
|
import { itemOf, listOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
18
18
|
import { listQuery, pagedList, pageOf, typedQuery, withQuery } from "../list-query.js";
|
|
19
19
|
import { withPaging } from "../paging.js";
|
|
20
|
-
import { nextStep } from "../steps.js";
|
|
20
|
+
import { lineOf, nextStep } from "../steps.js";
|
|
21
21
|
import { finishJob, jobDetails, jobStarted, jobsDir, listJobs, readJob, removeJob, saveJob, spawnDetached, stateOf, tracedJob, updateJob, } from "./backfill-jobs.js";
|
|
22
22
|
import { messengerContext, refuseLocalWrite } from "./context.js";
|
|
23
23
|
import { reviewPoint } from "./mention-points.js";
|
|
@@ -60,7 +60,7 @@ export const syncCommand = (messenger) => {
|
|
|
60
60
|
throw new CliError("validation_error", "--dry-run prices one chat; not with --all");
|
|
61
61
|
if (all && messenger.history === "store") {
|
|
62
62
|
throw new CliError("validation_error", `--all walks the messenger's chat list, and ${messenger.name ?? messenger.app.command} pushes its chats ` +
|
|
63
|
-
`instead — keep \`${
|
|
63
|
+
`instead — keep \`${lineOf(this, "serve")}\` running${messenger.historyOnDemand ? ", or name one chat" : ""}`);
|
|
64
64
|
}
|
|
65
65
|
if (since !== undefined && last !== undefined) {
|
|
66
66
|
throw new CliError("validation_error", "give --since-time or --last, not both: how far back the fetch goes");
|
|
@@ -91,7 +91,7 @@ export const syncCommand = (messenger) => {
|
|
|
91
91
|
const answer = await context.withServices((services) => services.archive.estimate(named, { limit, pageSize, pauseMs }));
|
|
92
92
|
renderAnswer(this, context, itemOf(snakeKeys(answer)));
|
|
93
93
|
if (answer.missing === null) {
|
|
94
|
-
context.renderer.note(`nothing held of this chat to measure by — \`${
|
|
94
|
+
context.renderer.note(`nothing held of this chat to measure by — \`${lineOf(this, `messages sync ${chat} --limit ${pageSize}`)}\` gives the estimate something to go on`);
|
|
95
95
|
}
|
|
96
96
|
else if (answer.missing > 0)
|
|
97
97
|
context.renderer.note("an estimate: provider waits (FloodWait) come on top");
|
|
@@ -167,10 +167,10 @@ export const syncCommand = (messenger) => {
|
|
|
167
167
|
}));
|
|
168
168
|
if (tasks?.held)
|
|
169
169
|
context.renderer.note(`the first sync left ${tasks.held} possible task(s) in ${tasks.heldChats ?? 0} chat(s) unopened ` +
|
|
170
|
-
`(config task_first_pass_per_chat) — ask the owner, then \`${
|
|
170
|
+
`(config task_first_pass_per_chat) — ask the owner, then \`${lineOf(this, "tasks scan <chat>")}\``);
|
|
171
171
|
if (toReview > 0)
|
|
172
172
|
context.renderer.note(`${shown} mention${toReview === 1 ? "" : "s"} found since the last review — ` +
|
|
173
|
-
`\`${
|
|
173
|
+
`\`${lineOf(this, "mentions list --new")}\``);
|
|
174
174
|
}
|
|
175
175
|
catch (error) {
|
|
176
176
|
if (jobId) {
|
|
@@ -327,7 +327,7 @@ const launchJob = (command, context, messenger, { chat, argv, limit, pageSize, l
|
|
|
327
327
|
const dir = jobsDir(app, context.env);
|
|
328
328
|
const running = listJobs(dir).find((job) => job.profile === context.profile && job.chat === chat && stateOf(job) === "running");
|
|
329
329
|
if (running) {
|
|
330
|
-
throw new CliError("validation_error", `job ${running.id} is already fetching ${chat} (PID ${running.pid}) — \`${app.command} store jobs show ${running.id}\``);
|
|
330
|
+
throw new CliError("validation_error", `job ${running.id} is already fetching ${chat} (PID ${running.pid}) — \`${app.command} ${context.profile} store jobs show ${running.id}\``);
|
|
331
331
|
}
|
|
332
332
|
const now = new Date();
|
|
333
333
|
const id = `${now.toISOString().replace(/[-:]/g, "").slice(0, 15)}-${randomBytes(3).toString("hex")}`;
|
|
@@ -230,9 +230,9 @@ export interface MessengerContext extends BaseContext {
|
|
|
230
230
|
* the reads answer saved to the store. `close` closes the connection and the store it opened. One
|
|
231
231
|
* function, so a command and an MCP tool call save and record exactly alike.
|
|
232
232
|
*
|
|
233
|
-
* A `history: "store"` messenger with `listen` saves what arrives for as long as a connection that
|
|
234
|
-
* `watch` is open: what it queued while nothing was connected is delivered once and cannot be fetched again.
|
|
235
|
-
* connection opened with `listen: true` is left to its `watch`, which
|
|
233
|
+
* A `history: "store"` messenger with `listen` or `listenHistory` saves what arrives for as long as a connection that
|
|
234
|
+
* does not `watch` is open: what it queued while nothing was connected is delivered once and cannot be fetched again.
|
|
235
|
+
* A connection opened with `listen: true` is left to its `watch` and `feed`, which save the same.
|
|
236
236
|
*/
|
|
237
237
|
export declare const connected: (connection: MessengerAdapter, { app, provider, name, deletedWithoutChat, pace, history, savedChatId, }: Pick<Messenger, "app" | "provider" | "name" | "deletedWithoutChat" | "pace" | "history" | "savedChatId">, { settings, env, renderer }: Pick<BaseContext, "settings" | "env" | "renderer">, events: EventSink, store?: LazyStore, { listen: watching }?: Pick<ConnectOptions, "listen">) => {
|
|
238
238
|
adapter: MessengerAdapter;
|
|
@@ -114,24 +114,24 @@ const settled = async (pending, ms) => {
|
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
/**
|
|
117
|
-
* Runs
|
|
117
|
+
* Runs one passive listen until `stop`, which answers whether it ended in time. A listen that fails warns and
|
|
118
118
|
* never fails the command; one that fails once the connection is `closing` is the close, not news. It still listens
|
|
119
119
|
* while the connection closes, so what arrives then is saved too.
|
|
120
120
|
*/
|
|
121
|
-
const listened = (
|
|
122
|
-
if (!adapter.listen)
|
|
123
|
-
return undefined;
|
|
121
|
+
const listened = ({ run, operation, what }, events, warn) => {
|
|
124
122
|
const abort = new AbortController();
|
|
125
123
|
let closing = false;
|
|
126
124
|
const ended = Promise.resolve()
|
|
127
|
-
.then(() =>
|
|
125
|
+
.then(() => run(abort.signal))
|
|
128
126
|
.catch((error) => {
|
|
129
127
|
if (closing)
|
|
130
128
|
return;
|
|
131
|
-
events({ event: "warning", code: "listen_failed", operation
|
|
132
|
-
warn(
|
|
129
|
+
events({ event: "warning", code: "listen_failed", operation });
|
|
130
|
+
warn(`${what} arriving during this command are not saved: ${error instanceof Error ? error.message : String(error)}`);
|
|
133
131
|
});
|
|
134
132
|
return {
|
|
133
|
+
operation,
|
|
134
|
+
what,
|
|
135
135
|
closing: () => {
|
|
136
136
|
closing = true;
|
|
137
137
|
},
|
|
@@ -141,6 +141,27 @@ const listened = (adapter, events, warn) => {
|
|
|
141
141
|
},
|
|
142
142
|
};
|
|
143
143
|
};
|
|
144
|
+
/** The passive listens a connection of a `history: "store"` messenger runs: the ones its adapter has. */
|
|
145
|
+
const passivesOf = (adapter) => [
|
|
146
|
+
...(adapter.listen
|
|
147
|
+
? [
|
|
148
|
+
{
|
|
149
|
+
run: (signal) => adapter.listen?.(() => { }, signal),
|
|
150
|
+
operation: "messages.listen",
|
|
151
|
+
what: "messages",
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
: []),
|
|
155
|
+
...(adapter.listenHistory
|
|
156
|
+
? [
|
|
157
|
+
{
|
|
158
|
+
run: (signal) => adapter.listenHistory?.(() => { }, signal),
|
|
159
|
+
operation: "history.listen",
|
|
160
|
+
what: "history batches",
|
|
161
|
+
},
|
|
162
|
+
]
|
|
163
|
+
: []),
|
|
164
|
+
];
|
|
144
165
|
/** A list that cannot be read stays as it is, which refuses every send: the warning says why. */
|
|
145
166
|
const rekeyRecipients = async (app, profile, env, warn, from, to) => {
|
|
146
167
|
try {
|
|
@@ -155,9 +176,9 @@ const rekeyRecipients = async (app, profile, env, warn, from, to) => {
|
|
|
155
176
|
* the reads answer saved to the store. `close` closes the connection and the store it opened. One
|
|
156
177
|
* function, so a command and an MCP tool call save and record exactly alike.
|
|
157
178
|
*
|
|
158
|
-
* A `history: "store"` messenger with `listen` saves what arrives for as long as a connection that
|
|
159
|
-
* `watch` is open: what it queued while nothing was connected is delivered once and cannot be fetched again.
|
|
160
|
-
* connection opened with `listen: true` is left to its `watch`, which
|
|
179
|
+
* A `history: "store"` messenger with `listen` or `listenHistory` saves what arrives for as long as a connection that
|
|
180
|
+
* does not `watch` is open: what it queued while nothing was connected is delivered once and cannot be fetched again.
|
|
181
|
+
* A connection opened with `listen: true` is left to its `watch` and `feed`, which save the same.
|
|
161
182
|
*/
|
|
162
183
|
export const connected = (connection, { app, provider, name, deletedWithoutChat, pace, history, savedChatId, }, { settings, env, renderer }, events, store = lazyStore(env, app.command), { listen: watching = false } = {}) => {
|
|
163
184
|
const self = connection.self();
|
|
@@ -185,20 +206,26 @@ export const connected = (connection, { app, provider, name, deletedWithoutChat,
|
|
|
185
206
|
rekeyed: (from, to) => rekeyRecipients(app, settings.profile, env, renderer.warn, from, to),
|
|
186
207
|
...(history === "store" ? { namesFromStore: savedChatId ? { savedChatId } : {} } : {}),
|
|
187
208
|
});
|
|
188
|
-
const
|
|
209
|
+
const passives = history === "store" && self !== null && !watching
|
|
210
|
+
? passivesOf(saving).map((passive) => listened(passive, events, renderer.warn))
|
|
211
|
+
: [];
|
|
189
212
|
return {
|
|
190
213
|
adapter: saving,
|
|
191
214
|
close: async () => {
|
|
192
|
-
passive
|
|
215
|
+
for (const passive of passives)
|
|
216
|
+
passive.closing();
|
|
193
217
|
try {
|
|
194
218
|
await connection.close();
|
|
195
219
|
}
|
|
196
220
|
finally {
|
|
197
221
|
try {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
222
|
+
const ended = await Promise.all(passives.map((passive) => passive.stop()));
|
|
223
|
+
passives.forEach(({ operation, what }, at) => {
|
|
224
|
+
if (ended[at])
|
|
225
|
+
return;
|
|
226
|
+
events({ event: "warning", code: "store_not_written", operation });
|
|
227
|
+
renderer.warn(`not saved to the local store: the messenger was still handing on ${what} at exit`);
|
|
228
|
+
});
|
|
202
229
|
if (!(await settled(pending, SAVES_WAIT_MS))) {
|
|
203
230
|
events({ event: "warning", code: "store_not_written", operation: "close" });
|
|
204
231
|
renderer.warn("not saved to the local store: the last messages were still being written at exit");
|
|
@@ -252,7 +279,7 @@ const warnUnserved = async (messenger, profile, { env, renderer }, store) => {
|
|
|
252
279
|
const newest = (await store.chats.chatStats(account))
|
|
253
280
|
.map((one) => one.newestAt)
|
|
254
281
|
.reduce((latest, at) => (at !== null && (latest === null || at > latest) ? at : latest), null);
|
|
255
|
-
renderer.warn(`no \`${messenger.app.command} serve\` keeps profile "${profile}" up to date — ` +
|
|
282
|
+
renderer.warn(`no \`${messenger.app.command} ${profile} serve\` keeps profile "${profile}" up to date — ` +
|
|
256
283
|
(newest === null ? "the local store holds no messages yet" : `the newest stored message is from ${newest}`));
|
|
257
284
|
};
|
|
258
285
|
/** The words of a command below the program, `["messages", "list"]` — the profile word is not one of them. */
|
|
@@ -11,7 +11,7 @@ import { storePath } from "../../store/path.js";
|
|
|
11
11
|
import { baseContext, environmentOf, outputFor } from "../context.js";
|
|
12
12
|
import { destroysNothing, LOCAL_WRITE, REMOTE_READ } from "../effects.js";
|
|
13
13
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
14
|
-
import { nextStep } from "../steps.js";
|
|
14
|
+
import { lineOf, nextStep } from "../steps.js";
|
|
15
15
|
import { recalledAccount } from "./accounts.js";
|
|
16
16
|
import { messengerContext } from "./context.js";
|
|
17
17
|
import { privateFiles, withSqliteSidecars } from "./private-files.js";
|
|
@@ -61,7 +61,7 @@ const reportCommand = (messenger) => {
|
|
|
61
61
|
.description("what a problem report holds; writes nothing")
|
|
62
62
|
.action(function () {
|
|
63
63
|
const output = outputFor(this);
|
|
64
|
-
const create =
|
|
64
|
+
const create = lineOf(this, "doctor report create");
|
|
65
65
|
renderAnswer(this, output, itemOf({ includes: INCLUDES, excludes: EXCLUDES, ...(app.issues ? { send_to: app.issues } : {}) }, { next: [nextStep("doctor.report-create", { args: {} })] }), () => [
|
|
66
66
|
`A problem report is one file for the author of ${app.command}.`,
|
|
67
67
|
"",
|
|
@@ -71,7 +71,7 @@ const reportCommand = (messenger) => {
|
|
|
71
71
|
`It never holds: ${EXCLUDES.join(", ")}.`,
|
|
72
72
|
"",
|
|
73
73
|
`Create one: ${create}`,
|
|
74
|
-
`About a given run: ${create} --run <id> (ids: ${
|
|
74
|
+
`About a given run: ${create} --run <id> (ids: ${lineOf(this, "runs list")})`,
|
|
75
75
|
].join("\n"));
|
|
76
76
|
});
|
|
77
77
|
destroysNothing(annotate(report.command("create"), { effects: LOCAL_WRITE }))
|
|
@@ -5,7 +5,7 @@ import { providerErrorKey } from "../../runs/events.js";
|
|
|
5
5
|
import { holdEnd } from "../../sends/flood.js";
|
|
6
6
|
const LONG_LIVED = new Set(["self", "newSendId", "close", "formatMarkdown", "formatHtml"]);
|
|
7
7
|
/** Neither held nor paced, as they listen for as long as the run does; what ends them is still remembered. */
|
|
8
|
-
const LISTENING = new Set(["watch", "feed", "listen"]);
|
|
8
|
+
const LISTENING = new Set(["watch", "feed", "listen", "listenHistory"]);
|
|
9
9
|
const BLOCKING = new Set(["frozen", "limited", "banned"]);
|
|
10
10
|
/** Shorter waits for the pace pass unannounced; a person at a terminal would not notice them. */
|
|
11
11
|
const ANNOUNCE_WAIT_MS = 5_000;
|
|
@@ -216,7 +216,7 @@ const VERSION_MANAGER = /[\\/](\.nvm|nvm|\.fnm|fnm|fnm_multishells|\.volta|volta
|
|
|
216
216
|
export const serverEntry = (app, { profile, flags = {}, execPath, scriptPath, env, }) => {
|
|
217
217
|
if (installerOf(scriptPath) === "npx") {
|
|
218
218
|
throw new CliError("validation_error", `this ${app.command} runs from npx's cache, which gets cleared, and the path would stop working — ` +
|
|
219
|
-
`install it globally, then run \`${app.command} mcp config\` again`);
|
|
219
|
+
`install it globally, then run \`${app.command} ${profile} mcp config\` again`);
|
|
220
220
|
}
|
|
221
221
|
const names = [
|
|
222
222
|
...["CONFIG_DIR", "STATE_DIR", "CACHE_DIR"].map((name) => envName(app, name)),
|
|
@@ -5,6 +5,7 @@ import { DELETE_AT_ONCE } from "../../services/index.js";
|
|
|
5
5
|
import { environmentOf } from "../context.js";
|
|
6
6
|
import { ownerOnlyOption, REMOTE_WRITE } from "../effects.js";
|
|
7
7
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
8
|
+
import { lineOf } from "../steps.js";
|
|
8
9
|
import { messengerContext } from "./context.js";
|
|
9
10
|
/**
|
|
10
11
|
* The guard asks first at the default level, `ask`; `--allow-dangerous` is the yes. For everyone only
|
|
@@ -20,7 +21,7 @@ export const deleteCommand = (messenger) => annotate(new Command("delete"), { ef
|
|
|
20
21
|
const everyone = this.opts().forEveryone === true;
|
|
21
22
|
if (environmentOf(this).agent && (everyone || messenger.deleteForMe === false))
|
|
22
23
|
throw new CliError("permission_error", `${everyone ? "" : `${messenger.name ?? messenger.provider} deletes only for everyone, and `}an agent never ` +
|
|
23
|
-
`deletes for everyone — the owner can run \`${
|
|
24
|
+
`deletes for everyone — the owner can run \`${lineOf(this, "messages delete --for-everyone")}\``, { permission: "messages.delete", sent: false });
|
|
24
25
|
const context = messengerContext(this, messenger);
|
|
25
26
|
const deleted = await context.withServices((services) => services.messages.delete({
|
|
26
27
|
chat,
|
|
@@ -32,15 +32,15 @@ export const observed = (messenger, events) => {
|
|
|
32
32
|
throw error;
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
-
const counted = async (operation,
|
|
35
|
+
const counted = async (operation, onItem, work, count) => {
|
|
36
36
|
const counts = {};
|
|
37
|
-
await timed(operation, {}, () => work((
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
await timed(operation, {}, () => work((item) => {
|
|
38
|
+
count(item, counts);
|
|
39
|
+
onItem(item);
|
|
40
40
|
}), () => ({ counts }));
|
|
41
41
|
};
|
|
42
42
|
const mint = messenger.newSendId;
|
|
43
|
-
const { chats, history, contact, around, watch, listen } = messenger;
|
|
43
|
+
const { chats, history, contact, around, watch, listen, listenHistory } = messenger;
|
|
44
44
|
// A method not listed here is timed under its own name, with no ids until a line here names them.
|
|
45
45
|
return throughWrapper(messenger, {
|
|
46
46
|
self: () => messenger.self(),
|
|
@@ -84,15 +84,29 @@ export const observed = (messenger, events) => {
|
|
|
84
84
|
}, () => messenger.send(chatId, text, options), (sent) => ({ ids: { message_id: sent.message.id } })),
|
|
85
85
|
...(watch
|
|
86
86
|
? {
|
|
87
|
-
watch: (onEvent, signal, onReady) => counted("messages.watch", onEvent, (counting) => watch.call(messenger, counting, signal, onReady)),
|
|
87
|
+
watch: (onEvent, signal, onReady) => counted("messages.watch", onEvent, (counting) => watch.call(messenger, counting, signal, onReady), byEvent),
|
|
88
88
|
}
|
|
89
89
|
: {}),
|
|
90
90
|
...(listen
|
|
91
91
|
? {
|
|
92
|
-
listen: (onEvent, signal) => counted("adapter.listen", onEvent, (counting) => listen.call(messenger, counting, signal)),
|
|
92
|
+
listen: (onEvent, signal) => counted("adapter.listen", onEvent, (counting) => listen.call(messenger, counting, signal), byEvent),
|
|
93
|
+
}
|
|
94
|
+
: {}),
|
|
95
|
+
...(listenHistory
|
|
96
|
+
? {
|
|
97
|
+
listenHistory: (onBatch, signal) => counted("adapter.listenHistory", onBatch, (counting) => listenHistory.call(messenger, counting, signal), byPart),
|
|
93
98
|
}
|
|
94
99
|
: {}),
|
|
95
100
|
logout: () => timed("session.logout", {}, () => messenger.logout()),
|
|
96
101
|
close: () => messenger.close(),
|
|
97
102
|
}, (name, call) => (...args) => timed(`adapter.${name}`, {}, () => call(...args)));
|
|
98
103
|
};
|
|
104
|
+
const byEvent = (event, counts) => {
|
|
105
|
+
counts[event.event] = (counts[event.event] ?? 0) + 1;
|
|
106
|
+
};
|
|
107
|
+
const byPart = (batch, counts) => {
|
|
108
|
+
counts.batches = (counts.batches ?? 0) + 1;
|
|
109
|
+
for (const [part, items] of Object.entries(batch))
|
|
110
|
+
if (Array.isArray(items) && items.length > 0)
|
|
111
|
+
counts[part] = (counts[part] ?? 0) + items.length;
|
|
112
|
+
};
|
|
@@ -13,6 +13,7 @@ import { storePath } from "../../store/path.js";
|
|
|
13
13
|
import { environmentOf, setExitCode } from "../context.js";
|
|
14
14
|
import { grantsAuthority, LOCAL_READ, LOCAL_WRITE, REMOTE_WRITE } from "../effects.js";
|
|
15
15
|
import { itemOf, listOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
16
|
+
import { lineOf } from "../steps.js";
|
|
16
17
|
import { messengerContext } from "./context.js";
|
|
17
18
|
/** Each run of an agent is a paid model call that reads chats; keeping the store current is `serve`'s job. */
|
|
18
19
|
const AGENT_MINIMUM_MINUTES = 60;
|
|
@@ -84,7 +85,7 @@ export const scheduleCommand = (messenger) => {
|
|
|
84
85
|
say(scheduleAnswer(schedule, { file, timer: timer().where(schedule.name), replaced }), [
|
|
85
86
|
`${replaced ? "Replaced" : "Added"} ${schedule.name}: ${describeWhen(schedule.when)}.`,
|
|
86
87
|
`It runs: ${schedule.kind === "agent" ? schedule.argv.join(" ") : `${app.command} ${schedule.argv.join(" ")}`}`,
|
|
87
|
-
`Output: ${join(dirname(file), "runs")} · now: ${
|
|
88
|
+
`Output: ${join(dirname(file), "runs")} · now: ${lineOf(self, `schedule run ${schedule.name}`)} · stop: ${lineOf(self, `schedule remove ${schedule.name}`)}`,
|
|
88
89
|
]);
|
|
89
90
|
};
|
|
90
91
|
return { context, system, state, say, timer, environment, save };
|
|
@@ -172,7 +173,7 @@ export const scheduleCommand = (messenger) => {
|
|
|
172
173
|
});
|
|
173
174
|
renderAnswer(this, context, answer, () => {
|
|
174
175
|
if (rows.length === 0) {
|
|
175
|
-
context.renderer.note(`no schedules — \`${
|
|
176
|
+
context.renderer.note(`no schedules — \`${lineOf(this, "schedule command|agent")}\` adds one`);
|
|
176
177
|
return "";
|
|
177
178
|
}
|
|
178
179
|
return rows
|
|
@@ -210,7 +211,7 @@ export const scheduleCommand = (messenger) => {
|
|
|
210
211
|
const { state, say, timer } = prepare(this);
|
|
211
212
|
const file = scheduleFile(state, scheduleName(name));
|
|
212
213
|
if (!existsSync(file))
|
|
213
|
-
throw new CliError("not_found", `no schedule ${name} — \`${
|
|
214
|
+
throw new CliError("not_found", `no schedule ${name} — \`${lineOf(this, "schedule list")}\``);
|
|
214
215
|
await timer().remove(name);
|
|
215
216
|
rmSync(file, { force: true });
|
|
216
217
|
say({ name, removed: true }, [`Removed ${name}.`]);
|
|
@@ -11,6 +11,7 @@ export const lentConnection = (connection, watchers) => throughWrapper(connectio
|
|
|
11
11
|
close: async () => { },
|
|
12
12
|
feed: undefined,
|
|
13
13
|
listen: undefined,
|
|
14
|
+
listenHistory: undefined,
|
|
14
15
|
...(connection.watch && watchers
|
|
15
16
|
? {
|
|
16
17
|
// A watcher that throws (its output limit, a closed reader) ends its own watch, never serve's.
|