@wirecat/cli-core 0.400.0 → 0.402.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 (78) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +1 -1
  3. package/dist/cli/bot/api.js +5 -2
  4. package/dist/cli/bot/store.js +1 -1
  5. package/dist/cli/config-command.js +13 -7
  6. package/dist/cli/context.d.ts +2 -0
  7. package/dist/cli/index.d.ts +2 -1
  8. package/dist/cli/index.js +2 -1
  9. package/dist/cli/meeting-evidence-answer.d.ts +46 -0
  10. package/dist/cli/meeting-evidence-answer.js +22 -0
  11. package/dist/cli/meeting-preparation-answer.js +1 -13
  12. package/dist/cli/messenger/account-sessions-command.js +0 -2
  13. package/dist/cli/messenger/archive-commands.js +1 -1
  14. package/dist/cli/messenger/doctor-command.js +0 -1
  15. package/dist/cli/messenger/message-writes.d.ts +192 -2
  16. package/dist/cli/messenger/message-writes.js +6 -2
  17. package/dist/cli/messenger/messages-send-command.js +9 -6
  18. package/dist/cli/messenger/models-command.js +0 -3
  19. package/dist/cli/messenger/records-command.js +0 -2
  20. package/dist/cli/messenger/review.js +1 -1
  21. package/dist/cli/messenger/schedule-command.js +2 -2
  22. package/dist/cli/messenger/search-answers.js +2 -0
  23. package/dist/cli/messenger/server-command.js +0 -3
  24. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  25. package/dist/cli/messenger/store-maintenance-command.js +7 -7
  26. package/dist/cli/messenger/stored.js +4 -1
  27. package/dist/cli/messenger/tasks-command.js +10 -6
  28. package/dist/cli/program.js +5 -1
  29. package/dist/documents/index.d.ts +1 -0
  30. package/dist/documents/index.js +1 -0
  31. package/dist/entities/hit.d.ts +4 -0
  32. package/dist/entities/hit.js +4 -0
  33. package/dist/entities/index.d.ts +285 -0
  34. package/dist/entities/index.js +5 -1
  35. package/dist/entities/meeting-evidence.d.ts +86 -0
  36. package/dist/entities/meeting-evidence.js +25 -0
  37. package/dist/entities/meeting-preparation-parts.d.ts +14 -0
  38. package/dist/entities/meeting-preparation-parts.js +15 -13
  39. package/dist/entities/meeting.d.ts +1 -1
  40. package/dist/entities/meeting.js +1 -1
  41. package/dist/entities/message-write.d.ts +187 -0
  42. package/dist/entities/message-write.js +2 -0
  43. package/dist/errors/reasons.js +2 -0
  44. package/dist/mcp/agent.js +12 -6
  45. package/dist/meetings/cli/index.js +3 -1
  46. package/dist/meetings/index.d.ts +1 -1
  47. package/dist/meetings/index.js +1 -1
  48. package/dist/meetings/mcp/index.d.ts +15 -15
  49. package/dist/meetings/read-answers.d.ts +18 -0
  50. package/dist/meetings/read-answers.js +30 -0
  51. package/dist/ports/messenger.d.ts +10 -1
  52. package/dist/search/dictionary.d.ts +20 -0
  53. package/dist/search/dictionary.js +106 -0
  54. package/dist/sends/guard.d.ts +1 -1
  55. package/dist/sends/guard.js +9 -5
  56. package/dist/sends/journal.d.ts +1 -1
  57. package/dist/sends/permissions.d.ts +10 -1
  58. package/dist/sends/permissions.js +43 -1
  59. package/dist/sends/upload.d.ts +4 -4
  60. package/dist/sends/upload.js +24 -20
  61. package/dist/services/documents/mirrors.d.ts +98 -0
  62. package/dist/services/documents/mirrors.js +144 -0
  63. package/dist/services/messages.js +1 -0
  64. package/dist/services/search-all-meetings.js +21 -8
  65. package/dist/services/task-rules.d.ts +1 -1
  66. package/dist/services/tasks.d.ts +2 -2
  67. package/dist/services/tasks.js +1 -1
  68. package/dist/store/sqlite/drizzle/bun.js +3 -3
  69. package/dist/store/sqlite/drizzle/bun.js.map +1 -1
  70. package/dist/store/sqlite/drizzle/core.js +2 -2
  71. package/dist/store/sqlite/drizzle/core.js.map +1 -1
  72. package/dist/store/sqlite/drizzle/node.js +3 -3
  73. package/dist/store/sqlite/drizzle/node.js.map +1 -1
  74. package/dist/store/sqlite/drizzle/{shared-O7CTKP45.js → shared-HKXF6HKX.js} +57 -57
  75. package/dist/store/sqlite/drizzle/{shared-O7CTKP45.js.map → shared-HKXF6HKX.js.map} +1 -1
  76. package/dist/store/sqlite/transcript-query.js +15 -5
  77. package/package.json +4 -2
  78. package/parity.json +6 -3
package/CHANGELOG.md CHANGED
@@ -7,6 +7,32 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.402.0 — 24.10.2026
11
+
12
+ ### Changed — may break callers
13
+
14
+ - **MCP offers `bot api`, behind `bot.api`.** The generated `bot api <operation>` commands (raw calls to Bot API methods, which skip the send guard and the bot's recipient list) are bot MCP tools again, offered only where the profile sets `permissions.bot.api` to `allow`; it is `deny` by default for an MCP server, so nothing changes until the owner turns it on, and a broader `bot: allow` does not lift it. A call made after the owner turned it off is refused naming the key and the terminal command; an agent cannot set the key through `config set` (the guard on `permissions.mcp…` covers `permissions.bot.api`). Breaking for a CLI: `generatedApiCommand` no longer marks the group `mcp: false`, and each operation now declares effects (`remote: read` or `write`), so `commands --json` shows them and a catalogue built with `toolsFromCommands` without `levels` lists the operations; update a pinned bot catalogue and a document that lists what MCP leaves out. Under `<PREFIX>_READ_ONLY` a write operation now refuses on the command line too. The command line's `bot api` is otherwise unchanged.
15
+
16
+ ### Fixed
17
+
18
+ - **Voice-note search no longer turns an English word into another real English word.** A word in a transcript widens to the spellings speech recognition garbles only past a dictionary of its language: Russian as before, and now English and Spanish, following the store's word-form languages (`search_stemmers`). Before, «cost» also found «cast». A Latin word with no dictionary for its language, and English and Spanish number words, no longer widen.
19
+
20
+ ## 0.401.0 — 24.10.2026
21
+
22
+ ### Changed — may break callers
23
+
24
+ - **`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.
25
+
26
+ - **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.
27
+
28
+ ### Fixed
29
+
30
+ - **`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.
31
+
32
+ ### Added
33
+
34
+ - **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`.
35
+
10
36
  ## 0.400.0 — 24.10.2026
11
37
 
12
38
  ### Fixed
@@ -21,6 +47,17 @@ shape of this file.
21
47
 
22
48
  ### Added
23
49
 
50
+ - **Album sends.** `messages send` takes `--photo` and `--file` more than once, and its MCP tool takes a list for
51
+ `photo` and `file`; together they go as one album, photos first. Each photo or file counts as one send toward the
52
+ hourly limit. An adapter returns every message of the album as `Sent.messages`, and `--json` answers with a
53
+ `messages` list beside `message`, which stays the first. A messenger that cannot send an album refuses it before
54
+ anything goes. A single attachment sends and answers as before.
55
+
56
+ - **Mirror runner for folder sync.** `syncMirrors` syncs each of a CLI's mirrors both ways under one lock and reports
57
+ a partly failed mirror as a `mirror.partial` issue; `fileMirrorState`, `mirrorId`, `mirrorStatus` and
58
+ `conflictReport` are the state file, id, local plan and conflict report drive-cli's `mirrors` used, moved here so
59
+ other CLIs can mirror folders and a watcher can reuse the same run.
60
+
24
61
  - **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
62
 
26
63
  - **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 +116,8 @@ shape of this file.
79
116
 
80
117
  ### Changed — may break callers
81
118
 
119
+ - **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.
120
+
82
121
  - **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
122
 
84
123
  - **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.
@@ -1,15 +1,18 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { annotate } from "@wirecat/cli-base/commands";
3
3
  import { Command, Option } from "commander";
4
+ import { REMOTE_READ, REMOTE_WRITE } from "../effects.js";
4
5
  import { apiFlagOf, apiOptionKey, readApiBody } from "./api-input.js";
5
6
  export const generatedApiCommand = (api) => {
6
- const group = annotate(new Command("api"), { mcp: false }).description(api.description ?? "every operation of the official Bot API, generated from its schema");
7
+ const group = new Command("api").description(api.description ?? "every operation of the official Bot API, generated from its schema");
7
8
  group.option("--store-token <profile>", "keep a returned authentication token only in this bot profile's OS keyring; never print it");
8
9
  for (const operation of api.operations) {
9
10
  const binding = operation.binding.kind === "http"
10
11
  ? `${operation.binding.method} ${operation.binding.path}`
11
12
  : operation.binding.name;
12
- const command = new Command(operation.command).description(`${operation.summary ?? operation.id} — ${operation.effect} (${binding})`);
13
+ const command = annotate(new Command(operation.command), {
14
+ effects: operation.effect === "read" ? REMOTE_READ : REMOTE_WRITE,
15
+ }).description(`${operation.summary ?? operation.id} — ${operation.effect} (${binding})`);
13
16
  for (const parameter of operation.parameters) {
14
17
  if (parameter.sensitive)
15
18
  continue;
@@ -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")
@@ -2,7 +2,7 @@ import { CliError, pathsAreOverridden } from "@wirecat/cli-base";
2
2
  import { annotate } from "@wirecat/cli-base/commands";
3
3
  import { Command } from "commander";
4
4
  import { envName } from "../ports/app.js";
5
- import { AUTHORITY_KEY } from "../sends/permissions.js";
5
+ import { AUTHORITY_KEY, BOT_API_KEY } from "../sends/permissions.js";
6
6
  import { loadSnakeCaseConfig } from "./config-keys.js";
7
7
  import { baseContext, environmentOf } from "./context.js";
8
8
  import { grantsAuthority, LOCAL_READ, LOCAL_WRITE } from "./effects.js";
@@ -96,7 +96,7 @@ export const configCommand = (app, config, { permissionKey } = {}) => {
96
96
  throw new CliError("permission_error", `this process is locked to profile ${settings.profile} (${lock}) — ${setting === "default_profile" ? setting : "--defaults"} changes other profiles`);
97
97
  }
98
98
  if (environmentOf(this).agent)
99
- refuseAuthorityKey(setting, action === "set" ? String(given) : undefined);
99
+ refuseGuardedKey(setting, action === "set" ? String(given) : undefined);
100
100
  if (action === "set")
101
101
  refuseUnknownKey(this, setting, String(given), permissionKey);
102
102
  const saved = config.changeSetting(settings.configPath, {
@@ -137,10 +137,11 @@ export const refuseUnknownKey = (command, setting, value, permissionKey) => {
137
137
  }
138
138
  };
139
139
  /**
140
- * An agent may hold `mcp.authority` and change other settings, but never the key that let it in: on any profile, in
141
- * the defaults, or by replacing `permissions` whole. Only the owner, in a terminal, turns it on or off (NEED-1662).
140
+ * An agent may hold `mcp.authority` or `bot.api` and change other settings, but never the key that let it in: on any
141
+ * profile, in the defaults, or by replacing `permissions` whole. Only the owner, in a terminal, turns it on or off
142
+ * (NEED-1662, NEED-1679).
142
143
  */
143
- const refuseAuthorityKey = (setting, value) => {
144
+ const refuseGuardedKey = (setting, value) => {
144
145
  const keys = setting.startsWith("permissions.")
145
146
  ? [setting.slice("permissions.".length)]
146
147
  : setting === "permissions"
@@ -148,9 +149,14 @@ const refuseAuthorityKey = (setting, value) => {
148
149
  ? ["mcp"]
149
150
  : objectKeys(value)
150
151
  : [];
151
- if (!keys.some((key) => key === "mcp" || key.startsWith("mcp.")))
152
+ const guarded = keys.some((key) => key === BOT_API_KEY || key.startsWith(`${BOT_API_KEY}.`))
153
+ ? BOT_API_KEY
154
+ : keys.some((key) => key === "mcp" || key.startsWith("mcp."))
155
+ ? AUTHORITY_KEY
156
+ : undefined;
157
+ if (!guarded)
152
158
  return;
153
- throw new CliError("permission_error", `an agent cannot change permissions.${AUTHORITY_KEY} — the owner sets it in a terminal`, { permission: AUTHORITY_KEY });
159
+ throw new CliError("permission_error", `an agent cannot change permissions.${guarded} — the owner sets it in a terminal`, { permission: guarded });
154
160
  };
155
161
  /** The keys of a `permissions` object given whole; anything else is left to the schema to refuse. */
156
162
  const objectKeys = (value) => {
@@ -87,6 +87,8 @@ export interface AgentRun {
87
87
  overrides?: readonly string[];
88
88
  /** The server's levels allow `mcp.authority`: a command that changes what a later run may do may run. */
89
89
  authority?: boolean;
90
+ /** The server's levels allow `bot.api`: a raw Bot API call, past the send guard, may run. */
91
+ botApi?: boolean;
90
92
  }
91
93
  export interface RelayedRun {
92
94
  /** The profile serve holds; a command for another is refused before it touches the connection. */
@@ -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}`],