@wirecat/cli-core 0.400.0 → 0.401.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +1 -1
  3. package/dist/cli/bot/store.js +1 -1
  4. package/dist/cli/index.d.ts +2 -1
  5. package/dist/cli/index.js +2 -1
  6. package/dist/cli/meeting-evidence-answer.d.ts +46 -0
  7. package/dist/cli/meeting-evidence-answer.js +22 -0
  8. package/dist/cli/meeting-preparation-answer.js +1 -13
  9. package/dist/cli/messenger/account-sessions-command.js +0 -2
  10. package/dist/cli/messenger/archive-commands.js +1 -1
  11. package/dist/cli/messenger/doctor-command.js +0 -1
  12. package/dist/cli/messenger/message-writes.d.ts +192 -2
  13. package/dist/cli/messenger/message-writes.js +6 -2
  14. package/dist/cli/messenger/messages-send-command.js +9 -6
  15. package/dist/cli/messenger/models-command.js +0 -3
  16. package/dist/cli/messenger/records-command.js +0 -2
  17. package/dist/cli/messenger/review.js +1 -1
  18. package/dist/cli/messenger/schedule-command.js +2 -2
  19. package/dist/cli/messenger/search-answers.js +2 -0
  20. package/dist/cli/messenger/server-command.js +0 -3
  21. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  22. package/dist/cli/messenger/store-maintenance-command.js +7 -7
  23. package/dist/cli/messenger/stored.js +4 -1
  24. package/dist/cli/messenger/tasks-command.js +10 -6
  25. package/dist/documents/index.d.ts +1 -0
  26. package/dist/documents/index.js +1 -0
  27. package/dist/entities/hit.d.ts +4 -0
  28. package/dist/entities/hit.js +4 -0
  29. package/dist/entities/index.d.ts +285 -0
  30. package/dist/entities/index.js +5 -1
  31. package/dist/entities/meeting-evidence.d.ts +86 -0
  32. package/dist/entities/meeting-evidence.js +25 -0
  33. package/dist/entities/meeting-preparation-parts.d.ts +14 -0
  34. package/dist/entities/meeting-preparation-parts.js +15 -13
  35. package/dist/entities/meeting.d.ts +1 -1
  36. package/dist/entities/meeting.js +1 -1
  37. package/dist/entities/message-write.d.ts +187 -0
  38. package/dist/entities/message-write.js +2 -0
  39. package/dist/errors/reasons.js +2 -0
  40. package/dist/mcp/agent.js +8 -4
  41. package/dist/meetings/cli/index.js +3 -1
  42. package/dist/meetings/index.d.ts +1 -1
  43. package/dist/meetings/index.js +1 -1
  44. package/dist/meetings/mcp/index.d.ts +15 -15
  45. package/dist/meetings/read-answers.d.ts +18 -0
  46. package/dist/meetings/read-answers.js +30 -0
  47. package/dist/ports/messenger.d.ts +10 -1
  48. package/dist/sends/guard.d.ts +1 -1
  49. package/dist/sends/guard.js +9 -5
  50. package/dist/sends/journal.d.ts +1 -1
  51. package/dist/sends/permissions.d.ts +1 -1
  52. package/dist/sends/permissions.js +31 -1
  53. package/dist/sends/upload.d.ts +4 -4
  54. package/dist/sends/upload.js +24 -20
  55. package/dist/services/documents/mirrors.d.ts +98 -0
  56. package/dist/services/documents/mirrors.js +144 -0
  57. package/dist/services/messages.js +1 -0
  58. package/dist/services/search-all-meetings.js +21 -8
  59. package/dist/services/task-rules.d.ts +1 -1
  60. package/dist/services/tasks.d.ts +2 -2
  61. package/dist/services/tasks.js +1 -1
  62. package/dist/store/sqlite/drizzle/bun.js +3 -3
  63. package/dist/store/sqlite/drizzle/bun.js.map +1 -1
  64. package/dist/store/sqlite/drizzle/core.js +2 -2
  65. package/dist/store/sqlite/drizzle/core.js.map +1 -1
  66. package/dist/store/sqlite/drizzle/node.js +3 -3
  67. package/dist/store/sqlite/drizzle/node.js.map +1 -1
  68. package/dist/store/sqlite/drizzle/{shared-O7CTKP45.js → shared-HKXF6HKX.js} +57 -57
  69. package/dist/store/sqlite/drizzle/{shared-O7CTKP45.js.map → shared-HKXF6HKX.js.map} +1 -1
  70. package/package.json +2 -2
  71. package/parity.json +6 -3
package/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.401.0 — 24.10.2026
11
+
12
+ ### Changed — may break callers
13
+
14
+ - **`events list` writes times as ISO 8601 and ids as strings**, as every other answer does: `starts_at`, `ends_at`, `deleted_at`, `created_at` and `updated_at` were epoch milliseconds, `id` and `event_series_id` numbers. No field is gone.
15
+
16
+ - **List duplicate-task pairs explicitly.** Use `tasks duplicates list [query...]` in CLI and MCP calls. The bare `tasks duplicates` group shows help or refuses in machine mode; it no longer reads the store. Reject and defer remain under that group.
17
+
18
+ ### Fixed
19
+
20
+ - **`search all` pages meeting matches.** With a meeting account in the search, `--page 2` repeated the meetings of the first page, and `has_more` could be false while meeting matches remained. The merged list is now read as deep as the page and cut, so pages follow on and `has_more` holds.
21
+
22
+ ### Added
23
+
24
+ - **Meeting answers keep what they knew.** A `search all` meeting hit carries its `scope` (transcript, chat or summary) and its stored line `id`, so two passages of one meeting stay apart. New entities `meeting_evidence`, `meeting_person_context` and `event`; `cli` exports `allAnswer`, `meetingEvidenceItem` and `meetingPersonContextItem`.
25
+
10
26
  ## 0.400.0 — 24.10.2026
11
27
 
12
28
  ### Fixed
@@ -21,6 +37,17 @@ shape of this file.
21
37
 
22
38
  ### Added
23
39
 
40
+ - **Album sends.** `messages send` takes `--photo` and `--file` more than once, and its MCP tool takes a list for
41
+ `photo` and `file`; together they go as one album, photos first. Each photo or file counts as one send toward the
42
+ hourly limit. An adapter returns every message of the album as `Sent.messages`, and `--json` answers with a
43
+ `messages` list beside `message`, which stays the first. A messenger that cannot send an album refuses it before
44
+ anything goes. A single attachment sends and answers as before.
45
+
46
+ - **Mirror runner for folder sync.** `syncMirrors` syncs each of a CLI's mirrors both ways under one lock and reports
47
+ a partly failed mirror as a `mirror.partial` issue; `fileMirrorState`, `mirrorId`, `mirrorStatus` and
48
+ `conflictReport` are the state file, id, local plan and conflict report drive-cli's `mirrors` used, moved here so
49
+ other CLIs can mirror folders and a watcher can reuse the same run.
50
+
24
51
  - **Meeting answer schemas.** `review`, `mentions list`, `stats meetings show` and `top`, `meetings people`, `meetings passage` and `meetings task-proposal` declare their answer entities (`meeting_review`, `meeting_link`, `meeting_stats`, `meeting_speaker`, `meeting_participants`, `meeting_passage`, `meeting_task_proposal`) for field discovery and projection. `meetings/` exports the item builders the meeting CLIs share: `meetingReviewItem`, `meetingLinkItem`, `meetingStatsItem`, `meetingSpeakerItem`.
25
52
 
26
53
  - **Listener answers declare their fields.** Live messages and events, and the final serve summary, expose exact schemas for discovery and cross-CLI output contracts. Provider-only attachment references stay private.
@@ -79,6 +106,8 @@ shape of this file.
79
106
 
80
107
  ### Changed — may break callers
81
108
 
109
+ - **MCP offers the writes no permission level stopped before.** `store backup|restore|migrate|repair|reset|clear|decrypt`, `store copies delete`, `server start|stop|restart`, model downloads, `schedule run|exec`, `doctor report create`, `account sessions end`, `chats delete|clear` and `bot store fetch` are MCP tools, each limited by a key named after its path (`store.reset`, `server.start`, `schedule.run`…), `ask` by default; set one to `deny` to keep an agent from it. `server`, `models`, `schedule`, `doctor` and `images` are now valid roots of a permission key. Command-line behaviour is unchanged. What to change in a CLI: a pinned MCP catalogue and a document listing what MCP leaves out.
110
+
82
111
  - **Watch uses the shared JSONL contract.** Read each message or event from `{ type: "item", data }` and wait for the final `type: "end"` record. Message and event fields are snake_case, message values use the canonical read shape with references, and live counter observations remain available. Normal stops and timeouts finish complete; a source failure finishes incomplete and returns its failure code. Update readers of the old raw lines.
83
112
 
84
113
  - **MCP offers the commands that change what a later run may do, behind `mcp.authority`.** `config set|unset`, `schedule command|agent|remove`, auto-reply rules and consents, recipients, chat rules, `server install`, `skill install`, model keys, media archive timers and `flood clear` are MCP tools again, offered only where the profile sets `permissions.mcp.authority` to `allow`; it is `deny` by default, so nothing changes until the owner turns it on. An agent cannot change that key through `config set`. What to change in a CLI: `grantsAuthority` no longer adds `mcp: false`, so `commands --json` no longer marks these commands, and a catalogue built with `toolsFromCommands` without `levels` lists them; update a pinned catalogue and a document that lists what MCP leaves out. A command of the CLI's own marked `grantsAuthority` (max `recipients add`) is gated the same way. A caller of `createBotServer` passes `authority: changesAuthorityAt(root)` to hide them; without it they are listed and refuse when called.
package/README.md CHANGED
@@ -178,7 +178,7 @@ or a launchd agent.
178
178
  - **A permission level per command path:** deny, readonly, ask or allow. The most specific key wins, and a built-in
179
179
  default can only tighten what the owner set. No level lets an agent delete for everyone.
180
180
  - **A recipient list and an hourly limit** for sends, and a journal of every attempt that records the length of the
181
- text, never the text.
181
+ text, never the text. Each photo or file of an album counts as one send.
182
182
  - **A send id across retries.** A write whose outcome is unknown is never retried automatically; a retry under the
183
183
  same send id cannot send twice.
184
184
  - **Deletion asks first,** at most ten messages at a time.
@@ -20,7 +20,7 @@ const wholeNumber = (value) => {
20
20
  * personal `messages sync` does. Mounted for a messenger whose Bot API pages back through a chat.
21
21
  */
22
22
  export const botStoreCommand = (bot, fetching) => {
23
- const command = annotate(new Command("store"), { mcp: false }).description("the bot's local copy on this machine");
23
+ const command = new Command("store").description("the bot's local copy on this machine");
24
24
  const fetch = annotate(command.command("fetch"), { effects: FETCH })
25
25
  .description("fetch a chat's history into the bot's local copy, newest first; run it again to continue")
26
26
  .argument("<chat>", "a chat id, or the title of a chat this bot has seen")
@@ -65,6 +65,7 @@ export { bufferedInput, type InputPolicy, inputPolicy, MAX_BUFFERED_INPUT, MAX_S
65
65
  export { answerFields, checkJson, type EntityFields, entityFields, type JsonFieldsResolver, type JsonRequest, liftJson, } from "./json-fields.js";
66
66
  export { commandPath, LIST_QUERY_GUIDE, listQuery, nativeField, nativeTerms, pagedList, pageOf, type QueryWords, queryArgument, queryHelp, queryTerms, searchText, termField, timeBounds, typedQuery, unfilteredQuery, withQuery, } from "./list-query.js";
67
67
  export { addMeetingBatchCommand, formatMeetingBatch, prepareMeetingBatch } from "./meeting-batch.js";
68
+ export { meetingEvidenceItem, meetingPersonContextItem } from "./meeting-evidence-answer.js";
68
69
  export { parseEvidenceFindingsJson, parseMeetingFindingsJson } from "./meeting-findings-input.js";
69
70
  export { meetingPreparationAnswer } from "./meeting-preparation-answer.js";
70
71
  export { addMeetingPreparationCommand } from "./meeting-preparation-command.js";
@@ -106,7 +107,7 @@ export { callsCommand, stickersCommand } from "./messenger/records-command.js";
106
107
  export { repliesCommand } from "./messenger/replies-command.js";
107
108
  export { reviewCommand } from "./messenger/review.js";
108
109
  export { scheduleCommand } from "./messenger/schedule-command.js";
109
- export { allHitItem, type HitItem, type MessageHitItem, messageHitItem, type NoteHitItem, noteHitItem, } from "./messenger/search-answers.js";
110
+ export { allAnswer, allHitItem, type HitItem, type MessageHitItem, messageHitItem, type NoteHitItem, noteHitItem, } from "./messenger/search-answers.js";
110
111
  export { searchCommand } from "./messenger/search-command.js";
111
112
  export { SEARCHES_QUERY, searchesCommand } from "./messenger/searches-command.js";
112
113
  export { serveCommand, servingProfiles } from "./messenger/serve-command.js";
package/dist/cli/index.js CHANGED
@@ -60,6 +60,7 @@ export { bufferedInput, inputPolicy, MAX_BUFFERED_INPUT, MAX_SECRET_INPUT, provi
60
60
  export { answerFields, checkJson, entityFields, liftJson, } from "./json-fields.js";
61
61
  export { commandPath, LIST_QUERY_GUIDE, listQuery, nativeField, nativeTerms, pagedList, pageOf, queryArgument, queryHelp, queryTerms, searchText, termField, timeBounds, typedQuery, unfilteredQuery, withQuery, } from "./list-query.js";
62
62
  export { addMeetingBatchCommand, formatMeetingBatch, prepareMeetingBatch } from "./meeting-batch.js";
63
+ export { meetingEvidenceItem, meetingPersonContextItem } from "./meeting-evidence-answer.js";
63
64
  export { parseEvidenceFindingsJson, parseMeetingFindingsJson } from "./meeting-findings-input.js";
64
65
  export { meetingPreparationAnswer } from "./meeting-preparation-answer.js";
65
66
  export { addMeetingPreparationCommand } from "./meeting-preparation-command.js";
@@ -101,7 +102,7 @@ export { callsCommand, stickersCommand } from "./messenger/records-command.js";
101
102
  export { repliesCommand } from "./messenger/replies-command.js";
102
103
  export { reviewCommand } from "./messenger/review.js";
103
104
  export { scheduleCommand } from "./messenger/schedule-command.js";
104
- export { allHitItem, messageHitItem, noteHitItem, } from "./messenger/search-answers.js";
105
+ export { allAnswer, allHitItem, messageHitItem, noteHitItem, } from "./messenger/search-answers.js";
105
106
  export { searchCommand } from "./messenger/search-command.js";
106
107
  export { SEARCHES_QUERY, searchesCommand } from "./messenger/searches-command.js";
107
108
  export { serveCommand, servingProfiles } from "./messenger/serve-command.js";
@@ -0,0 +1,46 @@
1
+ import type { MeetingEvidencePacket, PersonMeetingContext } from "../services/index.js";
2
+ /** A meeting evidence packet with `ref`, `offset_ms` and string ids, as every answer that carries one names them. */
3
+ export declare const mapEvidence: (evidence: MeetingEvidencePacket) => {
4
+ schemaVersion: 1;
5
+ kind: "meeting";
6
+ source: string;
7
+ fingerprint: string;
8
+ limits: {
9
+ cues: number;
10
+ bytes: number;
11
+ maxReadBytes: number;
12
+ maxTranscriptPages: number;
13
+ };
14
+ contentBytes: number;
15
+ coverage: {
16
+ provided: number;
17
+ included: number;
18
+ omitted: number | null;
19
+ providedExact: boolean;
20
+ nextReference: string | null;
21
+ transcriptPages: number;
22
+ skippedUnavailable: number;
23
+ hasMore: boolean;
24
+ truncatedBy: "cues" | "bytes" | "transcripts" | null;
25
+ input: "bounded-meeting-pages";
26
+ archiveCompleteness: "unknown";
27
+ nextTranscriptId: string | null;
28
+ };
29
+ items: {
30
+ fingerprint: string;
31
+ contentHash: string | null;
32
+ position: number;
33
+ endMs: number;
34
+ speakerName: string | null;
35
+ text: string;
36
+ revision: "current" | "superseded";
37
+ ref: string;
38
+ offsetMs: number;
39
+ transcriptId: string | null;
40
+ speakerParticipantId: string | null;
41
+ }[];
42
+ };
43
+ /** `meetings evidence`: the `meeting_evidence` entity. */
44
+ export declare const meetingEvidenceItem: (evidence: MeetingEvidencePacket) => unknown;
45
+ /** `meetings context`: the `meeting_person_context` entity. */
46
+ export declare const meetingPersonContextItem: (context: PersonMeetingContext) => unknown;
@@ -0,0 +1,22 @@
1
+ import { snakeKeys } from "./envelope.js";
2
+ const id = (value) => (value === null ? null : String(value));
3
+ const mapCue = ({ reference, startMs, ...cue }) => ({
4
+ ...cue,
5
+ ref: reference,
6
+ offsetMs: startMs,
7
+ transcriptId: id(cue.transcriptId),
8
+ speakerParticipantId: id(cue.speakerParticipantId),
9
+ });
10
+ /** A meeting evidence packet with `ref`, `offset_ms` and string ids, as every answer that carries one names them. */
11
+ export const mapEvidence = (evidence) => ({
12
+ ...evidence,
13
+ coverage: { ...evidence.coverage, nextTranscriptId: id(evidence.coverage.nextTranscriptId) },
14
+ items: evidence.items.map(mapCue),
15
+ });
16
+ /** `meetings evidence`: the `meeting_evidence` entity. */
17
+ export const meetingEvidenceItem = (evidence) => snakeKeys(mapEvidence(evidence));
18
+ /** `meetings context`: the `meeting_person_context` entity. */
19
+ export const meetingPersonContextItem = (context) => snakeKeys({
20
+ ...context,
21
+ items: context.items.map(({ reference, cues, ...item }) => ({ ...item, ref: reference, cues: cues.map(mapCue) })),
22
+ });
@@ -3,6 +3,7 @@ import * as v from "valibot";
3
3
  import { meetingPreparation } from "../entities/index.js";
4
4
  import { itemOf, metaNow, sealed, snakeKeys } from "./envelope.js";
5
5
  import { mapPreparationCommunications } from "./meeting-communications.js";
6
+ import { mapEvidence } from "./meeting-evidence-answer.js";
6
7
  import { boundMeetingPreparationItem } from "./meeting-preparation-budget.js";
7
8
  import { mapPreparationProjectEvidence } from "./meeting-preparation-projects.js";
8
9
  const id = (value) => (value === null ? null : String(value));
@@ -17,19 +18,6 @@ const mapMeeting = (meeting) => {
17
18
  updatedAt: at(meeting.updatedAt),
18
19
  };
19
20
  };
20
- const mapEvidence = (evidence) => {
21
- return {
22
- ...evidence,
23
- coverage: { ...evidence.coverage, nextTranscriptId: id(evidence.coverage.nextTranscriptId) },
24
- items: evidence.items.map(({ reference, startMs, ...cue }) => ({
25
- ...cue,
26
- ref: reference,
27
- offsetMs: startMs,
28
- transcriptId: id(cue.transcriptId),
29
- speakerParticipantId: id(cue.speakerParticipantId),
30
- })),
31
- };
32
- };
33
21
  export const meetingPreparationAnswer = (packet) => {
34
22
  const candidate = packet.upcoming.candidate;
35
23
  const mapped = snakeKeys({
@@ -23,11 +23,9 @@ export const accountSessionsCommand = (messenger) => {
23
23
  const found = await context.withMessenger((adapter) => capability(adapter, "sessions", "list the account's sessions")());
24
24
  renderAnswer(this, context, wholeList("account sessions list", found.map(sessionAnswer)));
25
25
  });
26
- // No MCP tool, at any permission level: it logs the owner out of the phone.
27
26
  sessions.addCommand(annotate(new Command("end"), {
28
27
  effects: REMOTE_WRITE,
29
28
  output: { entity: "ended_account_sessions" },
30
- mcp: false,
31
29
  examples: [`${tool} account sessions end --others`],
32
30
  })
33
31
  .description("log out every other device, your phone included; this one stays")
@@ -93,7 +93,7 @@ const leftLines = (left) => {
93
93
  * store is not offered. The refusal names the chats to a person but keeps them out of its message,
94
94
  * which a scheduled run's `last.json` keeps.
95
95
  */
96
- const clearCommand = (messenger) => annotate(new Command("clear"), { output: { entity: "store_clear" }, effects: LOCAL_WRITE, mcp: false })
96
+ const clearCommand = (messenger) => annotate(new Command("clear"), { output: { entity: "store_clear" }, effects: LOCAL_WRITE })
97
97
  .description("delete from the store the chats this account has left, with their messages")
98
98
  .option("--left", "the chats this account has left — the only thing this clears")
99
99
  .option("--allow-dangerous", "yes, delete — it cannot be undone, and a chat you left cannot be fetched again")
@@ -76,7 +76,6 @@ const reportCommand = (messenger) => {
76
76
  });
77
77
  destroysNothing(annotate(report.command("create"), {
78
78
  effects: LOCAL_WRITE,
79
- mcp: false,
80
79
  examples: [`${app.command} personal doctor report create --output ./problem-report.json`],
81
80
  }))
82
81
  .description("write a problem report to a file, and say where to send it")
@@ -4,14 +4,16 @@ interface Written {
4
4
  operationId: string;
5
5
  sendId?: string;
6
6
  message: Message;
7
+ messages?: Message[];
7
8
  scheduledFor?: string;
8
9
  }
9
10
  /**
10
11
  * A send, forward or edit: the message as it now stands, with its `operation_id` — for a send, the id a repeat
11
- * passes as `--operation-id` so it cannot make a second copy. A scheduled message gets a new id when it goes out,
12
+ * passes as `--operation-id` so it cannot make a second copy. An album adds `messages`, every one it made, in order;
13
+ * `message` stays its first, so a reader of one message reads an album as before. A scheduled message gets a new id when it goes out,
12
14
  * so its next step is the list that shows it, never a retry.
13
15
  */
14
- export declare const writtenAnswer: ({ operationId, sendId, message, scheduledFor }: Written, reader: AccountKey) => import("../envelope.js").ItemBody<{
16
+ export declare const writtenAnswer: ({ operationId, sendId, message, messages, scheduledFor }: Written, reader: AccountKey) => import("../envelope.js").ItemBody<{
15
17
  operation_id: string;
16
18
  message: {
17
19
  ref: string;
@@ -201,6 +203,194 @@ export declare const writtenAnswer: ({ operationId, sendId, message, scheduledFo
201
203
  [x: string]: unknown;
202
204
  } | undefined;
203
205
  };
206
+ messages?: {
207
+ ref: string;
208
+ id: string;
209
+ provider?: string | undefined;
210
+ account?: string | undefined;
211
+ chat_id: string;
212
+ chat_title?: string | null | undefined;
213
+ sender_id: string | null;
214
+ sender_name: string | null;
215
+ sender_username?: string | undefined;
216
+ sender_is_chat?: boolean | undefined;
217
+ mentions?: string[] | undefined;
218
+ timestamp: string;
219
+ edited_at: string | null;
220
+ text: string;
221
+ transcript?: string | undefined;
222
+ anchor?: boolean | undefined;
223
+ excerpt?: string | undefined;
224
+ matched_in?: "context" | "file" | "filename" | "picture" | "text" | "transcript" | undefined;
225
+ score?: number | undefined;
226
+ match?: "beginnings" | "corrected" | "words" | undefined;
227
+ corrections?: {
228
+ from: string;
229
+ to: string[];
230
+ }[] | undefined;
231
+ found_in?: "archive" | "both" | "server" | undefined;
232
+ discovery?: {
233
+ coverage: number;
234
+ matched_terms: string[];
235
+ missing_terms: string[];
236
+ parent?: string | undefined;
237
+ } | undefined;
238
+ context?: {
239
+ ref: string;
240
+ timestamp: string;
241
+ sender_name: string | null;
242
+ text: string;
243
+ }[] | undefined;
244
+ outgoing: boolean | null;
245
+ attachments: {
246
+ message?: string | undefined;
247
+ kind: string;
248
+ position?: number | undefined;
249
+ url?: string | undefined;
250
+ name?: string | undefined;
251
+ title?: string | undefined;
252
+ mime?: string | undefined;
253
+ size?: number | undefined;
254
+ width?: number | undefined;
255
+ height?: number | undefined;
256
+ duration?: number | undefined;
257
+ text?: string | undefined;
258
+ extraction?: {
259
+ origin: string;
260
+ extractor: string;
261
+ chars: number;
262
+ error?: string | undefined;
263
+ } | undefined;
264
+ local_path?: string | undefined;
265
+ buttons?: {
266
+ kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
267
+ text: string;
268
+ url?: string | undefined;
269
+ }[][] | undefined;
270
+ }[];
271
+ reply_to: {
272
+ id: string;
273
+ sender_id: string | null;
274
+ sender_name: string | null;
275
+ timestamp: string | null;
276
+ text: string;
277
+ attachments: {
278
+ message?: string | undefined;
279
+ kind: string;
280
+ position?: number | undefined;
281
+ url?: string | undefined;
282
+ name?: string | undefined;
283
+ title?: string | undefined;
284
+ mime?: string | undefined;
285
+ size?: number | undefined;
286
+ width?: number | undefined;
287
+ height?: number | undefined;
288
+ duration?: number | undefined;
289
+ text?: string | undefined;
290
+ extraction?: {
291
+ origin: string;
292
+ extractor: string;
293
+ chars: number;
294
+ error?: string | undefined;
295
+ } | undefined;
296
+ local_path?: string | undefined;
297
+ buttons?: {
298
+ kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
299
+ text: string;
300
+ url?: string | undefined;
301
+ }[][] | undefined;
302
+ }[];
303
+ outgoing: boolean | null;
304
+ } | null;
305
+ reply_to_id?: string | undefined;
306
+ forwarded_from: {
307
+ id: string;
308
+ sender_id: string | null;
309
+ sender_name: string | null;
310
+ timestamp: string | null;
311
+ text: string;
312
+ attachments: {
313
+ message?: string | undefined;
314
+ kind: string;
315
+ position?: number | undefined;
316
+ url?: string | undefined;
317
+ name?: string | undefined;
318
+ title?: string | undefined;
319
+ mime?: string | undefined;
320
+ size?: number | undefined;
321
+ width?: number | undefined;
322
+ height?: number | undefined;
323
+ duration?: number | undefined;
324
+ text?: string | undefined;
325
+ extraction?: {
326
+ origin: string;
327
+ extractor: string;
328
+ chars: number;
329
+ error?: string | undefined;
330
+ } | undefined;
331
+ local_path?: string | undefined;
332
+ buttons?: {
333
+ kind: "app" | "callback" | "chat" | "clipboard" | "contact" | "link" | "location" | "message" | "other";
334
+ text: string;
335
+ url?: string | undefined;
336
+ }[][] | undefined;
337
+ }[];
338
+ outgoing: boolean | null;
339
+ } | null;
340
+ thread_id?: string | undefined;
341
+ thread_root_id?: string | undefined;
342
+ reactions?: {
343
+ counts: {
344
+ reaction: string;
345
+ count: number;
346
+ }[];
347
+ mine: string | null;
348
+ total: number;
349
+ } | null | undefined;
350
+ scheduled_for?: string | undefined;
351
+ starred?: true | undefined;
352
+ delivery?: "delivered" | "read" | undefined;
353
+ email?: {
354
+ subject: string | null;
355
+ from: {
356
+ name: string | null;
357
+ address: string;
358
+ ref: string | null;
359
+ } | null;
360
+ to: {
361
+ name: string | null;
362
+ address: string;
363
+ ref: string | null;
364
+ }[];
365
+ cc: {
366
+ name: string | null;
367
+ address: string;
368
+ ref: string | null;
369
+ }[];
370
+ bcc: {
371
+ name: string | null;
372
+ address: string;
373
+ ref: string | null;
374
+ }[];
375
+ reply_addresses: {
376
+ name: string | null;
377
+ address: string;
378
+ ref: string | null;
379
+ }[];
380
+ labels: string[];
381
+ thread: string;
382
+ read: boolean | null;
383
+ flagged: boolean | null;
384
+ sender_score?: {
385
+ score: number;
386
+ signals: string[];
387
+ demoted?: true | undefined;
388
+ } | undefined;
389
+ } | undefined;
390
+ provider_metadata?: {
391
+ [x: string]: unknown;
392
+ } | undefined;
393
+ }[] | undefined;
204
394
  scheduled_for?: string | undefined;
205
395
  }>;
206
396
  /**
@@ -4,12 +4,16 @@ import { nextStep } from "../steps.js";
4
4
  import { messageItem } from "./message-items.js";
5
5
  /**
6
6
  * A send, forward or edit: the message as it now stands, with its `operation_id` — for a send, the id a repeat
7
- * passes as `--operation-id` so it cannot make a second copy. A scheduled message gets a new id when it goes out,
7
+ * passes as `--operation-id` so it cannot make a second copy. An album adds `messages`, every one it made, in order;
8
+ * `message` stays its first, so a reader of one message reads an album as before. A scheduled message gets a new id when it goes out,
8
9
  * so its next step is the list that shows it, never a retry.
9
10
  */
10
- export const writtenAnswer = ({ operationId, sendId, message, scheduledFor }, reader) => itemOf({
11
+ export const writtenAnswer = ({ operationId, sendId, message, messages, scheduledFor }, reader) => itemOf({
11
12
  operation_id: sendId ?? operationId,
12
13
  message: messageItem(message, reader),
14
+ ...(messages === undefined || messages.length < 2
15
+ ? {}
16
+ : { messages: messages.map((one) => messageItem(one, reader)) }),
13
17
  ...(scheduledFor === undefined ? {} : { scheduled_for: scheduledFor }),
14
18
  }, scheduledFor === undefined ? {} : { next: [nextStep("messages.scheduled", { args: { chat: message.chatId } })] });
15
19
  /**
@@ -1,6 +1,6 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { annotate } from "@wirecat/cli-base/commands";
3
- import { Command } from "commander";
3
+ import { Command, Option } from "commander";
4
4
  import { sendTime } from "../../domain/send-time.js";
5
5
  import { threadIdOf } from "../../domain/thread.js";
6
6
  import { typedSendAs } from "../../sends/send-as.js";
@@ -15,6 +15,9 @@ import { readAll } from "./stdin.js";
15
15
  * of what this profile tried to send, and it never holds the text.
16
16
  */
17
17
  export const HTML_HELP = "the text is HTML: <b>, <i>, <a href>, <code>";
18
+ const collect = (value, previous) => [...previous, value];
19
+ /** The empty list as its default is what tells the MCP tool to take a list for it. */
20
+ const repeatable = (flags, description) => new Option(flags, description).argParser(collect).default([], "none");
18
21
  export const sendCommand = (messenger) => {
19
22
  const send = annotate(new Command("send"), {
20
23
  examples: [`${messenger.app.command} personal messages send "Book club" "The meeting starts at 10."`],
@@ -38,9 +41,9 @@ export const sendCommand = (messenger) => {
38
41
  send
39
42
  .option("--no-preview", "no preview card for a link in the text")
40
43
  .option("--md", "read this messenger's Markdown; see its formatting guide for supported syntax")
41
- .option("--file <file>", "attach a file; the text becomes its caption")
42
- .option("--photo <file>", "attach a .jpg, .png or .webp as a photo; the text becomes its caption")
43
- .option("--as-file", "send the --file as a file to download, a video included")
44
+ .addOption(repeatable("--file <file>", "attach a file; the text becomes its caption; repeat it for an album"))
45
+ .addOption(repeatable("--photo <file>", "attach a .jpg, .png or .webp as a photo; the text becomes its caption; repeat it for an album"))
46
+ .option("--as-file", "send each --file as a file to download, a video included")
44
47
  .option("--voice <file>", "send an Ogg Opus file as a voice message, alone, with no text")
45
48
  .addOption(ownerOnlyOption("--allow-any-file", "send a file even from credential folders or this CLI's own folders"));
46
49
  if (messenger.scheduledSends !== false)
@@ -76,8 +79,8 @@ const sendText = async (command, messenger, chat, text) => {
76
79
  throw new CliError("validation_error", "--comment-to needs the post's message id");
77
80
  const read = { app: messenger.app, env: context.env, anyFile: allowAnyFile === true };
78
81
  const attachments = await readAttachments({
79
- ...(photo === undefined ? {} : { photo }),
80
- ...(file === undefined ? {} : { file }),
82
+ photo,
83
+ file,
81
84
  ...(voice === undefined ? {} : { voice }),
82
85
  ...(text === undefined ? {} : { text }),
83
86
  asFile: asFile === true,
@@ -76,7 +76,6 @@ export const modelsCommand = (messenger) => {
76
76
  });
77
77
  });
78
78
  reachesOtherService(annotate(audio.command("download"), {
79
- mcp: false,
80
79
  effects: FETCH,
81
80
  output: { entity: "model" },
82
81
  examples: [`${tool} models audio download parakeet-v3`],
@@ -124,7 +123,6 @@ export const modelsCommand = (messenger) => {
124
123
  });
125
124
  });
126
125
  reachesOtherService(annotate(text.command("download"), {
127
- mcp: false,
128
126
  effects: FETCH,
129
127
  output: { entity: "model" },
130
128
  examples: [`${tool} models text download ${DEFAULT_TEXT_MODEL}`],
@@ -197,7 +195,6 @@ export const imageModelsCommand = (messenger) => {
197
195
  });
198
196
  });
199
197
  reachesOtherService(annotate(image.command("download"), {
200
- mcp: false,
201
198
  effects: FETCH,
202
199
  output: { entity: "model" },
203
200
  examples: [`${tool} images models download ${DEFAULT_IMAGE_MODEL}`],
@@ -85,7 +85,6 @@ export const chatDeletionCommands = (messenger) => [
85
85
  annotate(new Command("delete"), {
86
86
  output: { entity: "chat_removal" },
87
87
  effects: REMOTE_WRITE,
88
- mcp: false,
89
88
  examples: [`${messenger.app.command} personal chats delete "Book club"`],
90
89
  })
91
90
  .description("delete a chat from this account; the others in it keep it and its messages")
@@ -98,7 +97,6 @@ export const chatDeletionCommands = (messenger) => [
98
97
  annotate(new Command("clear"), {
99
98
  output: { entity: "chat_removal" },
100
99
  effects: REMOTE_WRITE,
101
- mcp: false,
102
100
  examples: [`${messenger.app.command} personal chats clear "Book club"`],
103
101
  })
104
102
  .description("delete every message in a chat for this account; the others in it keep theirs")
@@ -113,7 +113,7 @@ const notes = (found, pretty, renderer) => {
113
113
  renderer.note(`${chat.title ?? chat.id}: its admins are not known, so only your answers count`);
114
114
  if (found.tasks && found.tasks.added + found.tasks.closed > 0)
115
115
  renderer.note(`tasks: ${found.tasks.added} opened, ${found.tasks.closed} closed by the rules` +
116
- (found.tasks.review > 0 ? `, ${found.tasks.review} pair(s) to review — tasks duplicates` : ""));
116
+ (found.tasks.review > 0 ? `, ${found.tasks.review} pair(s) to review — tasks duplicates list` : ""));
117
117
  if (found.partial && pretty)
118
118
  renderer.warn(PARTIAL_NOTE);
119
119
  if (found.offline && pretty)
@@ -192,7 +192,7 @@ export const scheduleCommand = (messenger) => {
192
192
  .join("\n");
193
193
  });
194
194
  });
195
- annotate(command.command("run"), { effects: REMOTE_WRITE, mcp: false })
195
+ annotate(command.command("run"), { effects: REMOTE_WRITE })
196
196
  .description("run a schedule now, as its timer would, and wait for it")
197
197
  .argument("<name>")
198
198
  .action(async function (name) {
@@ -216,7 +216,7 @@ export const scheduleCommand = (messenger) => {
216
216
  rmSync(file, { force: true });
217
217
  say({ name, removed: true }, [`Removed ${name}.`]);
218
218
  });
219
- annotate(command.command("exec"), { effects: REMOTE_WRITE, mcp: false })
219
+ annotate(command.command("exec"), { effects: REMOTE_WRITE })
220
220
  .description("what a timer runs, given the schedule's file; to run one by hand, use run")
221
221
  .argument("<file>")
222
222
  .action(async function (file) {
@@ -147,6 +147,8 @@ export const allHitItem = (item) => item.kind === "meeting"
147
147
  timestamp: item.timestamp,
148
148
  excerpt: item.text,
149
149
  offset_ms: item.startMs,
150
+ scope: item.scope,
151
+ id: String(item.id),
150
152
  }
151
153
  : defined({
152
154
  kind: item.kind,
@@ -185,7 +185,6 @@ export const serverCommand = (messenger, options = {}) => {
185
185
  const withIdle = (sub) => options.idle ? sub.option("--idle <duration>", "stop after this long with nobody using it — 15m, 1h") : sub;
186
186
  withIdle(destroysNothing(annotate(command.command("start"), {
187
187
  effects: LOCAL_WRITE,
188
- mcp: false,
189
188
  examples: [`${app.command} personal server start`],
190
189
  }))
191
190
  .summary("start this profile's background server")
@@ -194,7 +193,6 @@ export const serverCommand = (messenger, options = {}) => {
194
193
  });
195
194
  destroysNothing(annotate(command.command("stop"), {
196
195
  effects: LOCAL_WRITE,
197
- mcp: false,
198
196
  examples: [`${app.command} personal server stop`],
199
197
  }))
200
198
  .description("stop this profile's serve — through the unit if it runs under one")
@@ -202,7 +200,6 @@ export const serverCommand = (messenger, options = {}) => {
202
200
  await stop(prepare(this));
203
201
  });
204
202
  withIdle(destroysNothing(annotate(command.command("restart"), {
205
- mcp: false,
206
203
  effects: LOCAL_WRITE,
207
204
  examples: [`${app.command} personal server restart`],
208
205
  })).description("stop it and start it again")).action(async function () {
@@ -2,9 +2,9 @@ import { Command } from "commander";
2
2
  import type { Messenger } from "./context.js";
3
3
  /** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
4
4
  export declare const storeSummary: (env: NodeJS.ProcessEnv) => Promise<{
5
- error?: undefined;
6
5
  path: string;
7
6
  exists: boolean;
7
+ error?: undefined;
8
8
  } | {
9
9
  path: string;
10
10
  exists: boolean;