@wirecat/cli-core 0.405.0 → 0.406.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 +24 -1
- package/dist/botcheck/reasons.d.ts +1 -1
- package/dist/cli/analysis-choice.js +1 -1
- package/dist/cli/answer-entity.d.ts +5 -0
- package/dist/cli/answer-entity.js +25 -0
- package/dist/cli/command-contract.js +2 -5
- package/dist/cli/commands-command.js +4 -1
- package/dist/cli/effects.js +1 -1
- package/dist/cli/embedding-choice.d.ts +1 -1
- package/dist/cli/embedding-choice.js +2 -2
- package/dist/cli/help/command-help.js +2 -2
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-fields.js +2 -2
- package/dist/cli/meeting-project-candidates-command.js +0 -1
- package/dist/cli/meeting-series-inventory-command.js +0 -1
- package/dist/cli/messenger/admin-commands.js +14 -14
- package/dist/cli/messenger/archive-commands.js +2 -2
- package/dist/cli/messenger/attachments-command.js +7 -8
- package/dist/cli/messenger/backfill-command.js +1 -1
- package/dist/cli/messenger/chat-answers.js +3 -3
- package/dist/cli/messenger/chat-lists.js +3 -3
- package/dist/cli/messenger/chats-command.js +8 -8
- package/dist/cli/messenger/chats-members-command.js +1 -1
- package/dist/cli/messenger/contacts-command.js +10 -7
- package/dist/cli/messenger/context.d.ts +11 -4
- package/dist/cli/messenger/context.js +6 -0
- package/dist/cli/messenger/conversation-examples.js +7 -6
- package/dist/cli/messenger/conversations-command.d.ts +0 -4
- package/dist/cli/messenger/conversations-command.js +130 -135
- package/dist/cli/messenger/counters-command.js +4 -2
- package/dist/cli/messenger/doctor-command.js +8 -4
- package/dist/cli/messenger/gaps-command.js +2 -2
- package/dist/cli/messenger/hearing-command.d.ts +1 -1
- package/dist/cli/messenger/hearing-command.js +1 -1
- package/dist/cli/messenger/images-command.js +1 -1
- package/dist/cli/messenger/inbox.js +1 -3
- package/dist/cli/messenger/lacks.d.ts +1 -0
- package/dist/cli/messenger/lacks.js +1 -1
- package/dist/cli/messenger/mcp-command.js +4 -4
- package/dist/cli/messenger/messages-comments-command.js +10 -5
- package/dist/cli/messenger/metadata-command.js +5 -5
- package/dist/cli/messenger/models-command.js +101 -90
- package/dist/cli/messenger/private-people-command.js +5 -6
- package/dist/cli/messenger/replies-edit-command.js +0 -1
- package/dist/cli/messenger/review.js +1 -3
- package/dist/cli/messenger/serve-relay.d.ts +1 -0
- package/dist/cli/messenger/stats-command.js +1 -1
- package/dist/cli/messenger/store-maintenance-command.d.ts +2 -2
- package/dist/cli/messenger/store-maintenance-command.js +4 -4
- package/dist/cli/messenger/stored.js +5 -0
- package/dist/cli/messenger/tags-command.js +4 -10
- package/dist/cli/messenger/tasks-command.js +3 -3
- package/dist/cli/messenger/transcribe-command.js +1 -1
- package/dist/cli/moved.d.ts +2 -0
- package/dist/cli/moved.js +39 -1
- package/dist/cli/permission-keys.js +2 -0
- package/dist/cli/settings.js +1 -1
- package/dist/cli/steps.js +4 -4
- package/dist/cli/store-settings.js +1 -1
- package/dist/cli/upgrade-command.js +0 -1
- package/dist/domain/models.d.ts +3 -3
- package/dist/domain/task-categories.js +1 -1
- package/dist/embeddings/remote.js +1 -1
- package/dist/entities/member.d.ts +2 -2
- package/dist/entities/member.js +2 -2
- package/dist/mcp/agent.d.ts +1 -0
- package/dist/mcp/agent.js +1 -1
- package/dist/mcp/tools/attachments.js +1 -1
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/ports/app.d.ts +1 -1
- package/dist/ports/messenger.d.ts +6 -1
- package/dist/search/task-similarity.js +1 -1
- package/dist/sends/guard.js +2 -2
- package/dist/sends/permission-aliases.d.ts +5 -0
- package/dist/sends/permission-aliases.js +49 -0
- package/dist/sends/permissions.js +33 -25
- package/dist/services/admin.js +7 -7
- package/dist/services/chats.js +1 -1
- package/dist/services/conversations.d.ts +1 -1
- package/dist/services/conversations.js +1 -1
- package/dist/services/counters.js +1 -1
- package/dist/services/embeddings-maintenance.js +2 -2
- package/dist/services/embeddings-search.js +1 -1
- package/dist/services/embeddings.d.ts +4 -4
- package/dist/services/messages-search.d.ts +1 -1
- package/dist/services/messages-search.js +2 -2
- package/dist/services/metadata.js +2 -2
- package/dist/services/notes-search.js +3 -3
- package/dist/services/search-catchup.js +1 -1
- package/dist/speech/install.d.ts +4 -2
- package/dist/speech/install.js +16 -4
- package/dist/speech/local.js +1 -1
- package/dist/speech/transcribe.js +1 -1
- package/dist/store/search-index.d.ts +1 -1
- package/dist/store/sqlite/attachment-texts.d.ts +1 -1
- package/dist/store/sqlite/attachment-texts.js +1 -1
- package/dist/store/sqlite/batches.js +1 -1
- package/dist/store/sqlite/involvements.d.ts +1 -1
- package/dist/store/sqlite/note-index.d.ts +2 -2
- package/dist/store/sqlite/note-index.js +2 -2
- package/dist/store/sqlite/search-index.d.ts +1 -1
- package/dist/store/sqlite/search-index.js +1 -1
- package/dist/store/sqlite/stems-fill.d.ts +3 -3
- package/dist/store/sqlite/stems-fill.js +3 -3
- package/dist/store/sqlite/stems.d.ts +1 -1
- package/dist/store/sqlite/task-pairs.d.ts +1 -1
- package/dist/store/sqlite/task-pairs.js +1 -1
- package/package.json +1 -1
- package/parity.json +130 -139
- package/skills/follow-ups/SKILL.md +5 -5
- package/skills/link-conversations/SKILL.md +8 -8
|
@@ -30,94 +30,121 @@ import { readAll } from "./stdin.js";
|
|
|
30
30
|
* `conversations`: the threads inside a group chat, found by rules over the stored messages — replies,
|
|
31
31
|
* mentions, one sender's messages in a row. From the store alone; nothing is built until asked (phase 3).
|
|
32
32
|
*/
|
|
33
|
+
const conversationBuildCommand = (messenger) => boundOptions(reachesOtherService(annotate(new Command("rebuild"), { output: { entity: "conversation_build" }, effects: REMOTE_WRITE }))
|
|
34
|
+
.summary("build conversation links from stored messages")
|
|
35
|
+
.description("find a chat's conversations in what the store holds, replacing the last build; without --chat, every " +
|
|
36
|
+
"chat that changed since its build and every group never built; never asks the messenger")
|
|
37
|
+
.option("--chat <chat>", messenger.chatArgument)
|
|
38
|
+
.option("--analyze", "link batches using the configured analysis provider; requires --chat and remembers consent for this chat/provider")
|
|
39
|
+
.addOption(new Option("--analysis-provider <provider>", "analysis: agent, openai or anthropic"))
|
|
40
|
+
.option("--model <model>", "analysis model; overrides analysis_model")
|
|
41
|
+
.option("--base-url <url>", "analysis API endpoint; overrides analysis_base_url")
|
|
42
|
+
.option("--size <n>", "analysis answer messages per batch, 10–200; default 50", positiveCount("--size"))
|
|
43
|
+
.option("--max-tokens <n>", "analysis input/output reservation cap per run; default 100000", positiveCount("--max-tokens")), { chunks: false }).action(async function () {
|
|
44
|
+
const options = this.optsWithGlobals();
|
|
45
|
+
const { chat, maxChats } = options;
|
|
46
|
+
const context = messengerContext(this, messenger);
|
|
47
|
+
if (options.analyze) {
|
|
48
|
+
if (!chat)
|
|
49
|
+
throw new CliError("validation_error", "--analyze requires --chat");
|
|
50
|
+
refuseLocalWrite(context, messenger.app.command, LINKS_KEY);
|
|
51
|
+
refuseLocalWrite(context, messenger.app.command, "conversations.links.rebuild");
|
|
52
|
+
const target = analysisChoice(options, messenger.app, context.settings, context.env, context.keyring);
|
|
53
|
+
if (!isLocal(target.baseUrl))
|
|
54
|
+
refuseUnderReadOnly(this, `send chat text to ${new URL(target.baseUrl).host}`);
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
context.track({ close: async () => controller.abort() });
|
|
57
|
+
const maxTokens = options.maxTokens ?? 100_000;
|
|
58
|
+
const size = options.size ?? BATCH_SIZE.default;
|
|
59
|
+
const result = await context.withStore(async (store, account) => {
|
|
60
|
+
const services = servicesFor({
|
|
61
|
+
...storedDeps(messenger, store, account, context.guard),
|
|
62
|
+
env: context.env,
|
|
63
|
+
profile: context.profile,
|
|
64
|
+
});
|
|
65
|
+
const status = await services.conversations.batchStatus(chat, size);
|
|
66
|
+
const consents = analysisConsents(store, account);
|
|
67
|
+
const identity = providerIdentity(target);
|
|
68
|
+
if (status.messages && !(await consents.has(status.chat, identity))) {
|
|
69
|
+
const what = `${status.messages} messages, ${status.characters} characters (about ${status.tokensEstimate} message tokens, plus prompt and batch context), go to ${target.model} at ${target.baseUrl}; this run reserves at most ${maxTokens} input/output tokens. Consent is remembered for this chat and provider until revoked`;
|
|
70
|
+
if (!options.yes) {
|
|
71
|
+
const answer = await askOwner(this, `${what}. Allow analysis? [y/N] `);
|
|
72
|
+
if (answer === null)
|
|
73
|
+
throw new CliError("confirmation_required", `${what} — add --yes`);
|
|
74
|
+
if (!/^y(es)?$/i.test(answer.trim()))
|
|
75
|
+
throw new CliError("cancelled", "cancelled — nothing was sent");
|
|
76
|
+
}
|
|
77
|
+
await consents.remember(status.chat, identity);
|
|
78
|
+
}
|
|
79
|
+
await services.conversations.build(chat);
|
|
80
|
+
const request = openAnalysis(target, controller.signal);
|
|
81
|
+
return runAnalysis(services.conversations, chat, async (...args) => {
|
|
82
|
+
if (!(await consents.has(status.chat, identity)))
|
|
83
|
+
throw new CliError("permission_error", "analysis consent was revoked; no further batch was sent");
|
|
84
|
+
return request(...args);
|
|
85
|
+
}, {
|
|
86
|
+
model: target.model,
|
|
87
|
+
command: messenger.app.command,
|
|
88
|
+
size,
|
|
89
|
+
maxTokens,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
renderAnswer(this, context, itemOf(snakeKeys(result)));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (options.analysisProvider !== undefined ||
|
|
96
|
+
options.provider !== undefined ||
|
|
97
|
+
options.model !== undefined ||
|
|
98
|
+
options.baseUrl !== undefined ||
|
|
99
|
+
options.size !== undefined ||
|
|
100
|
+
options.maxTokens !== undefined)
|
|
101
|
+
throw new CliError("validation_error", "analysis options require --analyze");
|
|
102
|
+
if (chat === undefined) {
|
|
103
|
+
const refreshed = await context.withServices((services) => services.embeddings.refresh({
|
|
104
|
+
embed: false,
|
|
105
|
+
...(maxChats ? { maxChats } : {}),
|
|
106
|
+
progress: (note) => context.renderer.note(note),
|
|
107
|
+
}));
|
|
108
|
+
renderRefreshed(this, context, refreshed);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const built = await context.withServices((services) => services.conversations.build(chat));
|
|
112
|
+
renderAnswer(this, context, itemOf(snakeKeys(built)), () => `${built.messages} messages → ${built.conversations} conversations, ${built.links} links (rules v${built.rulesVersion})\n`);
|
|
113
|
+
});
|
|
114
|
+
const embeddingStatusCommand = (messenger) => withModelOptions(annotate(new Command("status"), { output: { entity: "conversation_embedding_status" }, effects: LOCAL_READ })
|
|
115
|
+
.summary("which conversation vectors are ready or stale")
|
|
116
|
+
.description("how many chunks of a chat have a vector of the model, how many are left, and what is left costs")
|
|
117
|
+
.option("--chat <chat>", messenger.chatArgument)).action(async function () {
|
|
118
|
+
const options = this.optsWithGlobals();
|
|
119
|
+
const chat = chatOf(options);
|
|
120
|
+
const context = messengerContext(this, messenger);
|
|
121
|
+
const model = choiceOf(options, messenger, context, { needKey: false });
|
|
122
|
+
const status = await context.withServices((services) => services.embeddings.status(chat, model));
|
|
123
|
+
renderAnswer(this, context, itemOf(snakeKeys(status)), () => `${status.embedded} of ${status.chunks} chunks embedded with ${status.model}; ${status.left} left` +
|
|
124
|
+
(status.estimateSeconds === null ? "" : `, ${minutes(status.estimateSeconds)}`) +
|
|
125
|
+
(status.priceAtMost ? `, at most ${dollars(status.priceAtMost.usd)}` : "") +
|
|
126
|
+
"\n");
|
|
127
|
+
});
|
|
128
|
+
const embeddingClearCommand = (messenger) => withModelOptions(destroysNothing(annotate(new Command("clear"), { output: { entity: "conversation_change" }, effects: LOCAL_WRITE }))
|
|
129
|
+
.summary("remove stored conversation vectors")
|
|
130
|
+
.description("drop a chat's vectors, or only one model's; messages and conversations are never touched")
|
|
131
|
+
.option("--chat <chat>", messenger.chatArgument)).action(async function () {
|
|
132
|
+
const options = this.optsWithGlobals();
|
|
133
|
+
const chat = chatOf(options);
|
|
134
|
+
const context = messengerContext(this, messenger);
|
|
135
|
+
refuseLocalWrite(context, messenger.app.command, EMBED_KEY);
|
|
136
|
+
const given = options.model !== undefined ||
|
|
137
|
+
options.embeddingProvider !== undefined ||
|
|
138
|
+
options.provider !== undefined ||
|
|
139
|
+
options.baseUrl !== undefined;
|
|
140
|
+
const model = given ? choiceOf(options, messenger, context, { needKey: false }) : undefined;
|
|
141
|
+
const cleared = await context.withServices((services) => services.embeddings.clear(chat, model));
|
|
142
|
+
renderAnswer(this, context, itemOf(cleared), () => `${cleared.cleared} vectors dropped\n`);
|
|
143
|
+
});
|
|
33
144
|
export const conversationsCommand = (messenger) => {
|
|
34
145
|
const conversations = new Command("conversations")
|
|
35
146
|
.summary("group stored messages into conversations")
|
|
36
147
|
.description("the conversations inside a chat, found in the stored messages by replies, mentions and who wrote next");
|
|
37
|
-
boundOptions(reachesOtherService(annotate(conversations.command("build"), { output: { entity: "conversation_build" }, effects: REMOTE_WRITE }))
|
|
38
|
-
.summary("build conversation links from stored messages")
|
|
39
|
-
.description("find a chat's conversations in what the store holds, replacing the last build; without --chat, every " +
|
|
40
|
-
"chat that changed since its build and every group never built; never asks the messenger")
|
|
41
|
-
.option("--chat <chat>", messenger.chatArgument)
|
|
42
|
-
.option("--analyze", "link batches using the configured analysis provider; requires --chat and remembers consent for this chat/provider")
|
|
43
|
-
.addOption(new Option("--analysis-provider <provider>", "analysis: agent, openai or anthropic").conflicts("provider"))
|
|
44
|
-
.addOption(new Option("--provider <provider>", "compatibility alias for --analysis-provider")
|
|
45
|
-
.hideHelp()
|
|
46
|
-
.conflicts("analysisProvider"))
|
|
47
|
-
.option("--model <model>", "analysis model; overrides analysis_model")
|
|
48
|
-
.option("--base-url <url>", "analysis API endpoint; overrides analysis_base_url")
|
|
49
|
-
.option("--size <n>", "analysis answer messages per batch, 10–200; default 50", positiveCount("--size"))
|
|
50
|
-
.option("--max-tokens <n>", "analysis input/output reservation cap per run; default 100000", positiveCount("--max-tokens")), { chunks: false }).action(async function () {
|
|
51
|
-
const options = this.optsWithGlobals();
|
|
52
|
-
const { chat, maxChats } = options;
|
|
53
|
-
const context = messengerContext(this, messenger);
|
|
54
|
-
if (options.analyze) {
|
|
55
|
-
if (!chat)
|
|
56
|
-
throw new CliError("validation_error", "--analyze requires --chat");
|
|
57
|
-
refuseLocalWrite(context, messenger.app.command, LINKS_KEY);
|
|
58
|
-
refuseLocalWrite(context, messenger.app.command, "conversations.build");
|
|
59
|
-
const target = analysisChoice(options, messenger.app, context.settings, context.env, context.keyring);
|
|
60
|
-
if (!isLocal(target.baseUrl))
|
|
61
|
-
refuseUnderReadOnly(this, `send chat text to ${new URL(target.baseUrl).host}`);
|
|
62
|
-
const controller = new AbortController();
|
|
63
|
-
context.track({ close: async () => controller.abort() });
|
|
64
|
-
const maxTokens = options.maxTokens ?? 100_000;
|
|
65
|
-
const size = options.size ?? BATCH_SIZE.default;
|
|
66
|
-
const result = await context.withStore(async (store, account) => {
|
|
67
|
-
const services = servicesFor({
|
|
68
|
-
...storedDeps(messenger, store, account, context.guard),
|
|
69
|
-
env: context.env,
|
|
70
|
-
profile: context.profile,
|
|
71
|
-
});
|
|
72
|
-
const status = await services.conversations.batchStatus(chat, size);
|
|
73
|
-
const consents = analysisConsents(store, account);
|
|
74
|
-
const identity = providerIdentity(target);
|
|
75
|
-
if (status.messages && !(await consents.has(status.chat, identity))) {
|
|
76
|
-
const what = `${status.messages} messages, ${status.characters} characters (about ${status.tokensEstimate} message tokens, plus prompt and batch context), go to ${target.model} at ${target.baseUrl}; this run reserves at most ${maxTokens} input/output tokens. Consent is remembered for this chat and provider until revoked`;
|
|
77
|
-
if (!options.yes) {
|
|
78
|
-
const answer = await askOwner(this, `${what}. Allow analysis? [y/N] `);
|
|
79
|
-
if (answer === null)
|
|
80
|
-
throw new CliError("confirmation_required", `${what} — add --yes`);
|
|
81
|
-
if (!/^y(es)?$/i.test(answer.trim()))
|
|
82
|
-
throw new CliError("cancelled", "cancelled — nothing was sent");
|
|
83
|
-
}
|
|
84
|
-
await consents.remember(status.chat, identity);
|
|
85
|
-
}
|
|
86
|
-
await services.conversations.build(chat);
|
|
87
|
-
const request = openAnalysis(target, controller.signal);
|
|
88
|
-
return runAnalysis(services.conversations, chat, async (...args) => {
|
|
89
|
-
if (!(await consents.has(status.chat, identity)))
|
|
90
|
-
throw new CliError("permission_error", "analysis consent was revoked; no further batch was sent");
|
|
91
|
-
return request(...args);
|
|
92
|
-
}, {
|
|
93
|
-
model: target.model,
|
|
94
|
-
command: messenger.app.command,
|
|
95
|
-
size,
|
|
96
|
-
maxTokens,
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
renderAnswer(this, context, itemOf(snakeKeys(result)));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (options.analysisProvider !== undefined ||
|
|
103
|
-
options.provider !== undefined ||
|
|
104
|
-
options.model !== undefined ||
|
|
105
|
-
options.baseUrl !== undefined ||
|
|
106
|
-
options.size !== undefined ||
|
|
107
|
-
options.maxTokens !== undefined)
|
|
108
|
-
throw new CliError("validation_error", "analysis options require --analyze");
|
|
109
|
-
if (chat === undefined) {
|
|
110
|
-
const refreshed = await context.withServices((services) => services.embeddings.refresh({
|
|
111
|
-
embed: false,
|
|
112
|
-
...(maxChats ? { maxChats } : {}),
|
|
113
|
-
progress: (note) => context.renderer.note(note),
|
|
114
|
-
}));
|
|
115
|
-
renderRefreshed(this, context, refreshed);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const built = await context.withServices((services) => services.conversations.build(chat));
|
|
119
|
-
renderAnswer(this, context, itemOf(snakeKeys(built)), () => `${built.messages} messages → ${built.conversations} conversations, ${built.links} links (rules v${built.rulesVersion})\n`);
|
|
120
|
-
});
|
|
121
148
|
withQuery(annotate(conversations.command("list"), { effects: LOCAL_READ })
|
|
122
149
|
.summary("stored conversations in one chat")
|
|
123
150
|
.description("a chat's conversations, the newest first: when, how many messages, how many people")
|
|
@@ -187,7 +214,7 @@ export const conversationsCommand = (messenger) => {
|
|
|
187
214
|
const { model: id, chats, unbuiltGroups, } = await context.withServices((services) => services.embeddings.readiness({ ...(options.chat === undefined ? {} : { chat: options.chat }), model }));
|
|
188
215
|
renderAnswer(this, context, readinessList({ model: id, chats, ...(unbuiltGroups === undefined ? {} : { unbuiltGroups }) }), () => chats.map((one) => `${readinessLine(one, id)}\n`).join(""));
|
|
189
216
|
if (chats.length === 0 && !unbuiltGroups && context.format === "pretty")
|
|
190
|
-
context.renderer.note("no chat has conversations yet — `conversations
|
|
217
|
+
context.renderer.note("no chat has conversations yet — `conversations links rebuild --chat <chat>`");
|
|
191
218
|
});
|
|
192
219
|
const batches = conversations
|
|
193
220
|
.command("batches")
|
|
@@ -204,7 +231,7 @@ export const conversationsCommand = (messenger) => {
|
|
|
204
231
|
renderAnswer(this, context, itemOf(snakeKeys(status)), () => `${status.messages} messages to answer, about ${status.batches} batches, ` +
|
|
205
232
|
`${status.characters} characters (about ${status.tokensEstimate} tokens)\n`);
|
|
206
233
|
});
|
|
207
|
-
sizeOption(annotate(batches.command("
|
|
234
|
+
sizeOption(annotate(batches.command("show"), { output: { entity: "conversation_batch" }, effects: LOCAL_READ })
|
|
208
235
|
.summary("get the next batch of messages to link")
|
|
209
236
|
.description("the next window to answer, with the messages before it; message text goes to stdout only")
|
|
210
237
|
.requiredOption("--chat <chat>", messenger.chatArgument)).action(async function () {
|
|
@@ -228,9 +255,8 @@ export const conversationsCommand = (messenger) => {
|
|
|
228
255
|
.join("")}`);
|
|
229
256
|
context.renderer.note(`${batch.remaining.messages} messages left after this batch`);
|
|
230
257
|
});
|
|
231
|
-
const links = conversations
|
|
232
|
-
|
|
233
|
-
.description("your agent's answers: which earlier message each message of a batch answers");
|
|
258
|
+
const links = conversations.command("links").description("message links and the conversations derived from them");
|
|
259
|
+
links.addCommand(conversationBuildCommand(messenger));
|
|
234
260
|
readsStdin(annotate(links.command("add"), { output: { entity: "conversation_change" }, effects: LOCAL_WRITE }), {
|
|
235
261
|
name: "answer",
|
|
236
262
|
description: '{ "model", "skill"?, "answers": [{ "message", "parent", "confidence" }] }',
|
|
@@ -239,7 +265,7 @@ export const conversationsCommand = (messenger) => {
|
|
|
239
265
|
.summary("save an agent's conversation-link answers")
|
|
240
266
|
.description('store your agent\'s answer to a batch, read as JSON from stdin: { "model", "answers": [{ "message", ' +
|
|
241
267
|
'"parent", "confidence" }] }; all or nothing')
|
|
242
|
-
.requiredOption("--batch <id>", "the batch id `conversations batches
|
|
268
|
+
.requiredOption("--batch <id>", "the batch id `conversations batches show` printed")
|
|
243
269
|
.action(async function () {
|
|
244
270
|
const { batch } = this.opts();
|
|
245
271
|
const context = messengerContext(this, messenger);
|
|
@@ -252,8 +278,7 @@ export const conversationsCommand = (messenger) => {
|
|
|
252
278
|
.summary("remove an agent's saved conversation-link answers")
|
|
253
279
|
.description("drop your agent's answers for a chat, or only one model's; messages are never touched")
|
|
254
280
|
.requiredOption("--chat <chat>", messenger.chatArgument)
|
|
255
|
-
.addOption(new Option("--answer-model <model>", "only the stored answers this model gave")
|
|
256
|
-
.addOption(new Option("--model <model>", "compatibility alias for --answer-model").hideHelp().conflicts("answerModel"))
|
|
281
|
+
.addOption(new Option("--answer-model <model>", "only the stored answers this model gave"))
|
|
257
282
|
.action(async function () {
|
|
258
283
|
const { chat, model, answerModel } = this.opts();
|
|
259
284
|
const context = messengerContext(this, messenger);
|
|
@@ -308,36 +333,9 @@ export const conversationsCommand = (messenger) => {
|
|
|
308
333
|
next: done.skipped ? [nextStep("conversations.build", { args: { chat } })] : [],
|
|
309
334
|
}), () => `${done.embedded} chunks embedded with ${done.model}${done.skipped ? `, ${done.skipped} changed since the build` : ""}\n`);
|
|
310
335
|
});
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
.option("--chat <chat>", messenger.chatArgument)).action(async function () {
|
|
315
|
-
const options = this.optsWithGlobals();
|
|
316
|
-
const chat = chatOf(options);
|
|
317
|
-
const context = messengerContext(this, messenger);
|
|
318
|
-
const model = choiceOf(options, messenger, context, { needKey: false });
|
|
319
|
-
const status = await context.withServices((services) => services.embeddings.status(chat, model));
|
|
320
|
-
renderAnswer(this, context, itemOf(snakeKeys(status)), () => `${status.embedded} of ${status.chunks} chunks embedded with ${status.model}; ${status.left} left` +
|
|
321
|
-
(status.estimateSeconds === null ? "" : `, ${minutes(status.estimateSeconds)}`) +
|
|
322
|
-
(status.priceAtMost ? `, at most ${dollars(status.priceAtMost.usd)}` : "") +
|
|
323
|
-
"\n");
|
|
324
|
-
});
|
|
325
|
-
withModelOptions(destroysNothing(annotate(embed.command("clear"), { output: { entity: "conversation_change" }, effects: LOCAL_WRITE }))
|
|
326
|
-
.summary("remove stored conversation vectors")
|
|
327
|
-
.description("drop a chat's vectors, or only one model's; messages and conversations are never touched")
|
|
328
|
-
.option("--chat <chat>", messenger.chatArgument)).action(async function () {
|
|
329
|
-
const options = this.optsWithGlobals();
|
|
330
|
-
const chat = chatOf(options);
|
|
331
|
-
const context = messengerContext(this, messenger);
|
|
332
|
-
refuseLocalWrite(context, messenger.app.command, EMBED_KEY);
|
|
333
|
-
const given = options.model !== undefined ||
|
|
334
|
-
options.embeddingProvider !== undefined ||
|
|
335
|
-
options.provider !== undefined ||
|
|
336
|
-
options.baseUrl !== undefined;
|
|
337
|
-
const model = given ? choiceOf(options, messenger, context, { needKey: false }) : undefined;
|
|
338
|
-
const cleared = await context.withServices((services) => services.embeddings.clear(chat, model));
|
|
339
|
-
renderAnswer(this, context, itemOf(cleared), () => `${cleared.cleared} vectors dropped\n`);
|
|
340
|
-
});
|
|
336
|
+
const embeddings = new Command("embeddings").description("stored conversation vectors");
|
|
337
|
+
embeddings.addCommand(embeddingStatusCommand(messenger)).addCommand(embeddingClearCommand(messenger));
|
|
338
|
+
conversations.addCommand(embeddings);
|
|
341
339
|
boundOptions(destroysNothing(annotate(conversations.command("rebuild"), {
|
|
342
340
|
output: { entity: "conversation_preparation" },
|
|
343
341
|
effects: LOCAL_WRITE,
|
|
@@ -373,13 +371,13 @@ export const conversationsCommand = (messenger) => {
|
|
|
373
371
|
has_more: false,
|
|
374
372
|
}));
|
|
375
373
|
});
|
|
376
|
-
annotate(consents.command("
|
|
374
|
+
annotate(consents.command("remove"), {
|
|
375
|
+
effects: LOCAL_WRITE,
|
|
376
|
+
output: { entity: "analysis_consent_revocation" },
|
|
377
|
+
})
|
|
377
378
|
.description("forget analysis permissions so the next analysis asks again")
|
|
378
379
|
.option("--chat <chat>", "revoke only this chat's consents; defaults to every chat")
|
|
379
|
-
.addOption(new Option("--consent-provider <identity>", "exact provider identity from consents list; defaults to every provider")
|
|
380
|
-
.addOption(new Option("--provider <identity>", "compatibility alias for --consent-provider")
|
|
381
|
-
.hideHelp()
|
|
382
|
-
.conflicts("consentProvider"))
|
|
380
|
+
.addOption(new Option("--consent-provider <identity>", "exact provider identity from consents list; defaults to every provider"))
|
|
383
381
|
.action(async function () {
|
|
384
382
|
const { chat, provider, consentProvider } = this.opts();
|
|
385
383
|
const context = messengerContext(this, messenger);
|
|
@@ -406,11 +404,8 @@ const chatOf = ({ chat }) => {
|
|
|
406
404
|
return chat;
|
|
407
405
|
};
|
|
408
406
|
const withModelOptions = (command) => command
|
|
409
|
-
.option("--model <model>", "local: a model id from `models
|
|
410
|
-
.addOption(new Option("--embedding-provider <provider>", "embedding provider: local or openai; flags override profile settings")
|
|
411
|
-
.addOption(new Option("--provider <provider>", "compatibility alias for --embedding-provider")
|
|
412
|
-
.hideHelp()
|
|
413
|
-
.conflicts("embeddingProvider"))
|
|
407
|
+
.option("--model <model>", "local: a model id from `models list` (default: e5-small); remote: the provider's model")
|
|
408
|
+
.addOption(new Option("--embedding-provider <provider>", "embedding provider: local or openai; flags override profile settings"))
|
|
414
409
|
.option("--base-url <url>", "a server with OpenAI's /v1/embeddings: Gemini, Jina, or Ollama and LM Studio on this machine")
|
|
415
410
|
.option("--dims <n>", "remote: the vector size — needed with --base-url; shortens an OpenAI model's", positiveCount("--dims"));
|
|
416
411
|
/** How much one run over many chats may do: the owner's machine, not a background job, pays for it. */
|
|
@@ -488,7 +483,7 @@ const consent = async (command, status, baseUrl, { yes, maxTokens }) => {
|
|
|
488
483
|
throw new CliError("cancelled", "cancelled — nothing was sent");
|
|
489
484
|
};
|
|
490
485
|
const LINKS_KEY = "conversations.links";
|
|
491
|
-
const EMBED_KEY = "conversations.
|
|
486
|
+
const EMBED_KEY = "conversations.embeddings";
|
|
492
487
|
const dollars = (usd) => (usd < 0.0001 ? "under $0.0001" : `$${usd.toFixed(4)}`);
|
|
493
488
|
const minutes = (seconds) => (seconds < 90 ? `about ${seconds} s` : `about ${Math.round(seconds / 60)} min`);
|
|
494
489
|
/** The agent's JSON, shaped enough for the store to check the rest against the batch. */
|
|
@@ -524,7 +519,7 @@ export const linksCommand = (messenger) => annotate(new Command("links"), {
|
|
|
524
519
|
return;
|
|
525
520
|
}
|
|
526
521
|
if (found.links.length === 0) {
|
|
527
|
-
context.renderer.note("no links: it starts a conversation, or the chat is not built — `conversations
|
|
522
|
+
context.renderer.note("no links: it starts a conversation, or the chat is not built — `conversations links rebuild`");
|
|
528
523
|
}
|
|
529
524
|
context.streams.data(found.links
|
|
530
525
|
.map((link) => `${link.chosen ? "→" : " "} ${link.parentId ?? "(starts)"} ${link.source} ${link.kind} ` +
|
|
@@ -538,7 +533,7 @@ const itemLine = (one) => `${one.id} ${one.started_at.slice(0, 16).replace("T",
|
|
|
538
533
|
const hitLine = ({ summary, chunk, score, stale, locator }) => `${score === null ? " — " : score.toFixed(3)} ${line(summary)} ` +
|
|
539
534
|
`(messages ${chunk.firstMessageId}–${chunk.lastMessageId})${locator ? ` ${locator}` : ""}${stale ? " stale" : ""}`;
|
|
540
535
|
const readinessLine = ({ chat, state, graph, pending, vectors }, model) => graph === null
|
|
541
|
-
? `${chat} ${state} — \`conversations
|
|
536
|
+
? `${chat} ${state} — \`conversations links rebuild --chat ${chat}\``
|
|
542
537
|
: `${chat} ${state} built ${graph.builtAt.slice(0, 16).replace("T", " ")}, rules v${graph.rulesVersion}` +
|
|
543
538
|
`${graph.outdatedRules ? " (outdated)" : ""} · not in the build: ${pending.new} new, ${pending.edited} edited, ` +
|
|
544
539
|
`${pending.deleted} deleted · ${model}: ${vectors.current} current, ${vectors.stale} stale, ` +
|
|
@@ -9,7 +9,7 @@ import { countersAnswer, refreshAnswer, refuseJsonl } from "./stats-answers.js";
|
|
|
9
9
|
export const countersCommand = (messenger) => {
|
|
10
10
|
const parent = new Command("counters").description("per-counter observations and bounded remote refresh");
|
|
11
11
|
for (const operation of ["show", "refresh"]) {
|
|
12
|
-
const command = new Command(operation)
|
|
12
|
+
const command = new Command(operation === "refresh" ? "sync" : operation)
|
|
13
13
|
.summary("refresh message counters from the messenger")
|
|
14
14
|
.description(operation === "show"
|
|
15
15
|
? "show saved counter values and their observation freshness"
|
|
@@ -22,6 +22,8 @@ export const countersCommand = (messenger) => {
|
|
|
22
22
|
.option("--selection <json>", "pinned counter-targets selection from counters show; conflicts with query and scope")
|
|
23
23
|
.option("--counters <names>", "distinct views,reactions,comments fields; all three by default")
|
|
24
24
|
.option("--limit <n>", "messages, 1–100; 20 by default", positiveCount("--limit"));
|
|
25
|
+
if (operation === "refresh")
|
|
26
|
+
command;
|
|
25
27
|
if (operation === "show")
|
|
26
28
|
command.option("--max-age <duration>", "maximum fresh observation age; 24h by default");
|
|
27
29
|
annotate(command, operation === "refresh"
|
|
@@ -36,7 +38,7 @@ export const countersCommand = (messenger) => {
|
|
|
36
38
|
const context = messengerContext(this, messenger), options = this.opts();
|
|
37
39
|
refuseJsonl(context.format);
|
|
38
40
|
if (operation === "refresh" && !options.dryRun)
|
|
39
|
-
refuseLocalWrite(context, messenger.app.command, "stats.messages.counters.
|
|
41
|
+
refuseLocalWrite(context, messenger.app.command, "stats.messages.counters.sync");
|
|
40
42
|
const query = {
|
|
41
43
|
...options,
|
|
42
44
|
limit: options.limit ?? 20,
|
|
@@ -9,7 +9,7 @@ import { FloodMemory, floodPathFor } from "../../sends/flood.js";
|
|
|
9
9
|
import { SendJournal, sendsPathFor } from "../../sends/journal.js";
|
|
10
10
|
import { storePath } from "../../store/path.js";
|
|
11
11
|
import { baseContext, environmentOf, outputFor } from "../context.js";
|
|
12
|
-
import { destroysNothing, LOCAL_WRITE, REMOTE_READ } from "../effects.js";
|
|
12
|
+
import { destroysNothing, LOCAL_WRITE, NO_EFFECTS, REMOTE_READ } from "../effects.js";
|
|
13
13
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
14
14
|
import { lineOf, nextStep } from "../steps.js";
|
|
15
15
|
import { recalledAccount } from "./accounts.js";
|
|
@@ -57,11 +57,15 @@ const INCLUDES = [
|
|
|
57
57
|
const EXCLUDES = ["message text", "chat titles", "names", "phone numbers", "the session", "the app credentials"];
|
|
58
58
|
const reportCommand = (messenger) => {
|
|
59
59
|
const { app } = messenger;
|
|
60
|
-
const report =
|
|
60
|
+
const report = new Command("reports").description("problem reports");
|
|
61
|
+
annotate(report.command("show"), {
|
|
62
|
+
effects: NO_EFFECTS,
|
|
63
|
+
examples: [`${app.command} doctor reports show`],
|
|
64
|
+
})
|
|
61
65
|
.description("what a problem report holds; writes nothing")
|
|
62
66
|
.action(function () {
|
|
63
67
|
const output = outputFor(this);
|
|
64
|
-
const create = lineOf(this, "doctor
|
|
68
|
+
const create = lineOf(this, "doctor reports create");
|
|
65
69
|
renderAnswer(this, output, itemOf({ includes: INCLUDES, excludes: EXCLUDES, ...(app.issues ? { send_to: app.issues } : {}) }, { next: [nextStep("doctor.report-create", { args: {} })] }), () => [
|
|
66
70
|
`A problem report is one file for the author of ${app.command}.`,
|
|
67
71
|
"",
|
|
@@ -76,7 +80,7 @@ const reportCommand = (messenger) => {
|
|
|
76
80
|
});
|
|
77
81
|
destroysNothing(annotate(report.command("create"), {
|
|
78
82
|
effects: LOCAL_WRITE,
|
|
79
|
-
examples: [`${app.command} personal doctor
|
|
83
|
+
examples: [`${app.command} personal doctor reports create --output ./problem-report.json`],
|
|
80
84
|
}))
|
|
81
85
|
.description("write a problem report to a file, and say where to send it")
|
|
82
86
|
.option("--run <id>", "the run the report is about; the newest failed one if not given")
|
|
@@ -21,7 +21,7 @@ const count = (value) => {
|
|
|
21
21
|
};
|
|
22
22
|
export const gapsCommand = (messenger) => {
|
|
23
23
|
const group = new Command("gaps").description("inspect recorded interior coverage gaps and explicitly fetch them");
|
|
24
|
-
annotate(group.command("
|
|
24
|
+
annotate(group.command("show"), { output: { entity: "store_gap_plan" }, effects: LOCAL_READ })
|
|
25
25
|
.description("local coverage plan; missing message ids alone do not imply missing history")
|
|
26
26
|
.argument("<chat>", messenger.chatArgument)
|
|
27
27
|
.action(async function (chat) {
|
|
@@ -71,7 +71,7 @@ export const gapsCommand = (messenger) => {
|
|
|
71
71
|
if (prepare) {
|
|
72
72
|
validateCatchUpBounds(preparation);
|
|
73
73
|
refuseLocalWrite(context, messenger.app.command, "conversations.links");
|
|
74
|
-
refuseLocalWrite(context, messenger.app.command, "conversations.
|
|
74
|
+
refuseLocalWrite(context, messenger.app.command, "conversations.embeddings");
|
|
75
75
|
}
|
|
76
76
|
if (args.background) {
|
|
77
77
|
const planned = await context.withServices((services) => services.gaps.plan(chat));
|
|
@@ -2,7 +2,7 @@ import type { Message } from "../../domain/models.js";
|
|
|
2
2
|
import { type Hearing } from "../../speech/hearing.js";
|
|
3
3
|
import type { Messenger, MessengerContext, ReadConnection } from "./context.js";
|
|
4
4
|
export declare const TRANSCRIBE_OPTION: readonly ["--transcribe", "turn voice messages not heard yet into text — by the messenger where it can, else by a model on this machine; can take minutes"];
|
|
5
|
-
export declare const MODEL_OPTION: readonly ["--model <model>", "which downloaded speech model hears them, with --transcribe; `models
|
|
5
|
+
export declare const MODEL_OPTION: readonly ["--model <model>", "which downloaded speech model hears them, with --transcribe; `models list` shows them"];
|
|
6
6
|
/**
|
|
7
7
|
* Kept transcripts on every read; new ones with `--transcribe`, using the read's connection when
|
|
8
8
|
* supplied. It is released before local recognition. `undefined` when there is no voice message, and then no file is opened.
|
|
@@ -6,7 +6,7 @@ export const TRANSCRIBE_OPTION = [
|
|
|
6
6
|
];
|
|
7
7
|
export const MODEL_OPTION = [
|
|
8
8
|
"--model <model>",
|
|
9
|
-
"which downloaded speech model hears them, with --transcribe; `models
|
|
9
|
+
"which downloaded speech model hears them, with --transcribe; `models list` shows them",
|
|
10
10
|
];
|
|
11
11
|
/**
|
|
12
12
|
* Kept transcripts on every read; new ones with `--transcribe`, using the read's connection when
|
|
@@ -27,7 +27,7 @@ export const imagesCommand = (messenger) => {
|
|
|
27
27
|
.action(async function () {
|
|
28
28
|
const { chat, limit = DEFAULT_LIMIT } = this.opts();
|
|
29
29
|
const context = messengerContext(this, messenger);
|
|
30
|
-
refuseLocalWrite(context, messenger.app.command, "conversations.
|
|
30
|
+
refuseLocalWrite(context, messenger.app.command, "conversations.embeddings");
|
|
31
31
|
const controller = new AbortController();
|
|
32
32
|
context.track({ close: async () => controller.abort() });
|
|
33
33
|
const done = await context.withServices((services) => services.pictures.embed({ ...(chat === undefined ? {} : { chat }), limit, signal: controller.signal }));
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
3
|
-
import { Command
|
|
3
|
+
import { Command } from "commander";
|
|
4
4
|
import { renderMessages } from "../../render/messages.js";
|
|
5
|
-
import { CHAT_KINDS } from "../../services/chats.js";
|
|
6
5
|
import { kindsOf } from "../../services/inbox.js";
|
|
7
6
|
import { modelWith } from "../../speech/hearing.js";
|
|
8
7
|
import { REMOTE_READ } from "../effects.js";
|
|
@@ -41,7 +40,6 @@ export const inboxCommand = (messenger) => annotate(new Command("inbox"), {
|
|
|
41
40
|
.option("--new", "what arrived since the last check, each message once — for scheduled runs")
|
|
42
41
|
.option("--limit <n>", "at most this many per chat, the newest", positiveCount("--limit"))
|
|
43
42
|
.option("--all", "muted and archived chats too — left out unless they mention you or reply to you")
|
|
44
|
-
.addOption(new Option("--kind <kinds>", `compatibility spelling for kind:; ${CHAT_KINDS.join(", ")}`).hideHelp())
|
|
45
43
|
.option(...TRANSCRIBE_OPTION)
|
|
46
44
|
.option(...MODEL_OPTION)
|
|
47
45
|
.addHelpText("after", "To mark a chat read, use `chats mark-read <chat>`. The other side sees it.")
|
|
@@ -9,6 +9,7 @@ type Need = readonly [method: keyof MessengerAdapter, what: string];
|
|
|
9
9
|
export declare const COMMAND_NEEDS: Readonly<Record<string, Need>>;
|
|
10
10
|
interface Lacking {
|
|
11
11
|
channelComments?: boolean;
|
|
12
|
+
readsComments?: boolean;
|
|
12
13
|
lacks?: readonly (keyof MessengerAdapter)[];
|
|
13
14
|
never?: readonly (keyof MessengerAdapter)[];
|
|
14
15
|
history?: "server" | "store";
|
|
@@ -30,7 +30,7 @@ export const lackedNeed = (messenger, path) => {
|
|
|
30
30
|
const need = COMMAND_NEEDS[path];
|
|
31
31
|
if (!need)
|
|
32
32
|
return undefined;
|
|
33
|
-
if (path === "messages comments" && messenger?.channelComments === false)
|
|
33
|
+
if (path === "messages comments" && (messenger?.readsComments ?? messenger?.channelComments) === false)
|
|
34
34
|
return need;
|
|
35
35
|
if (!messenger?.lacks?.includes(need[0]) && !messenger?.never?.includes(need[0]))
|
|
36
36
|
return undefined;
|
|
@@ -74,9 +74,9 @@ export const mcpCommand = (messenger) => {
|
|
|
74
74
|
// Pasted into a file, so a person gets the client's own JSON, indented; a script finds it under `config`.
|
|
75
75
|
renderAnswer(this, context, itemOf({ config: entry.config }, { next: entry.nodeBound ? [nextStep("mcp.config", { args: {} })] : [] }), () => JSON.stringify(entry.config, null, 2));
|
|
76
76
|
}));
|
|
77
|
-
command.addCommand(withFlags(grantsAuthority(annotate(new Command("
|
|
77
|
+
command.addCommand(withFlags(grantsAuthority(annotate(new Command("add"), {
|
|
78
78
|
effects: LOCAL_WRITE,
|
|
79
|
-
examples: [`${app.command} personal mcp
|
|
79
|
+
examples: [`${app.command} personal mcp add codex --allow-writes`],
|
|
80
80
|
}))
|
|
81
81
|
.description("add this profile's local MCP server to Codex or Claude Code")
|
|
82
82
|
.argument("<client>", "codex or claude-code")
|
|
@@ -108,9 +108,9 @@ export const mcpCommand = (messenger) => {
|
|
|
108
108
|
throw new CliError("validation_error", error instanceof Error ? error.message : "MCP setup failed");
|
|
109
109
|
}
|
|
110
110
|
}));
|
|
111
|
-
command.addCommand(withFlags(annotate(new Command("
|
|
111
|
+
command.addCommand(withFlags(annotate(new Command("check"), {
|
|
112
112
|
effects: LOCAL_READ,
|
|
113
|
-
examples: [`${app.command} personal mcp
|
|
113
|
+
examples: [`${app.command} personal mcp check`],
|
|
114
114
|
}).description("check this profile's local MCP handshake and tool list")).action(async function () {
|
|
115
115
|
const context = messengerContext(this, messenger);
|
|
116
116
|
const { settings, env } = context;
|
|
@@ -10,15 +10,20 @@ import { messengerContext } from "./context.js";
|
|
|
10
10
|
import { COMMENTS_QUERY, cursorOf, historyWindow } from "./history-query.js";
|
|
11
11
|
import { messageItems, viewOf } from "./message-items.js";
|
|
12
12
|
export const commentsCommand = (messenger) => {
|
|
13
|
+
const otherPosts = messenger.readsComments === true && messenger.channelComments === false;
|
|
13
14
|
const command = annotate(new Command("comments"), {
|
|
14
|
-
examples: [
|
|
15
|
+
examples: [
|
|
16
|
+
`${messenger.app.command} personal messages comments "${otherPosts ? "Announcements" : "News channel"}" 101 --limit 20`,
|
|
17
|
+
],
|
|
15
18
|
effects: REMOTE_READ,
|
|
16
19
|
})
|
|
17
|
-
.summary("comments on a channel post")
|
|
18
|
-
.description(
|
|
20
|
+
.summary(otherPosts ? "comments on a post" : "comments on a channel post")
|
|
21
|
+
.description(otherPosts
|
|
22
|
+
? "the comments received under a post, oldest to newest"
|
|
23
|
+
: "the comments under a channel post, oldest to newest; they live in its discussion group")
|
|
19
24
|
.addHelpText("after", queryHelp(COMMENTS_QUERY))
|
|
20
|
-
.argument("<chat>", `the channel: ${messenger.chatArgument}`)
|
|
21
|
-
.argument("<post>", "the post's message id in the channel")
|
|
25
|
+
.argument("<chat>", `the ${otherPosts ? "chat" : "channel"}: ${messenger.chatArgument}`)
|
|
26
|
+
.argument("<post>", otherPosts ? "the post's message id in the chat" : "the post's message id in the channel")
|
|
22
27
|
.argument("[query...]", "where to start: id<42")
|
|
23
28
|
.option("--limit <n>", "how many", positiveCount("--limit"))
|
|
24
29
|
.option("--cursor <cursor>", "go on from where the last page stopped; page.next gives it")
|
|
@@ -8,10 +8,10 @@ import { messengerContext, refuseLocalWrite } from "./context.js";
|
|
|
8
8
|
export const metadataCommand = (messenger) => {
|
|
9
9
|
const tool = messenger.app.command;
|
|
10
10
|
const command = new Command("metadata").description("cached group/channel descriptions for local automatic tags");
|
|
11
|
-
annotate(command.command("
|
|
11
|
+
annotate(command.command("show"), {
|
|
12
12
|
output: { entity: "chat_metadata" },
|
|
13
13
|
effects: LOCAL_READ,
|
|
14
|
-
examples: [`${tool} metadata
|
|
14
|
+
examples: [`${tool} metadata show --chat 7`],
|
|
15
15
|
})
|
|
16
16
|
.description("a stored chat's cached description, as automatic tags read it")
|
|
17
17
|
.requiredOption("--chat <chat>", "a stored chat")
|
|
@@ -20,10 +20,10 @@ export const metadataCommand = (messenger) => {
|
|
|
20
20
|
const { chat } = this.opts();
|
|
21
21
|
renderAnswer(this, context, itemOf(snakeKeys(await context.withServices((services) => services.metadata.get(chat)))));
|
|
22
22
|
});
|
|
23
|
-
annotate(command.command("
|
|
23
|
+
annotate(command.command("sync"), {
|
|
24
24
|
output: { entity: "chat_metadata_refresh" },
|
|
25
25
|
effects: FETCH,
|
|
26
|
-
examples: [`${tool} metadata
|
|
26
|
+
examples: [`${tool} metadata sync --chat 7 --chat 8`, `${tool} metadata sync --only-missing --limit 20`],
|
|
27
27
|
})
|
|
28
28
|
.description("fetch group and channel descriptions again and cache them")
|
|
29
29
|
.option("--chat <chat>", "stored group/channel; repeat for several", (value, previous) => [...previous, value], [])
|
|
@@ -31,7 +31,7 @@ export const metadataCommand = (messenger) => {
|
|
|
31
31
|
.option("--limit <n>", "process at most 1–500 chats", positiveCount("--limit"), 50)
|
|
32
32
|
.action(async function () {
|
|
33
33
|
const context = messengerContext(this, messenger);
|
|
34
|
-
refuseLocalWrite(context, messenger.app.command, "metadata.
|
|
34
|
+
refuseLocalWrite(context, messenger.app.command, "metadata.sync");
|
|
35
35
|
const options = this.opts();
|
|
36
36
|
const limit = Number(options.limit);
|
|
37
37
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 500)
|