@wirecat/cli-core 0.395.0 → 0.397.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 (109) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/README.md +1 -0
  3. package/dist/background/daily-archive-timer.d.ts +40 -0
  4. package/dist/background/daily-archive-timer.js +181 -0
  5. package/dist/background/index.d.ts +2 -0
  6. package/dist/background/index.js +1 -0
  7. package/dist/cli/commands-command.js +6 -0
  8. package/dist/cli/index.d.ts +1 -1
  9. package/dist/cli/index.js +1 -1
  10. package/dist/cli/input-fields.d.ts +3 -0
  11. package/dist/cli/input-fields.js +27 -0
  12. package/dist/cli/messenger/account-command.js +1 -0
  13. package/dist/cli/messenger/account-sessions-command.js +3 -1
  14. package/dist/cli/messenger/admin-commands.js +1 -0
  15. package/dist/cli/messenger/admin-contacts-command.js +9 -3
  16. package/dist/cli/messenger/archive-commands.js +3 -1
  17. package/dist/cli/messenger/attachments-command.js +32 -10
  18. package/dist/cli/messenger/backfill-command.js +4 -0
  19. package/dist/cli/messenger/chats-command.js +1 -0
  20. package/dist/cli/messenger/chats-members-command.js +4 -0
  21. package/dist/cli/messenger/chats-stats-command.js +1 -0
  22. package/dist/cli/messenger/chats-tracking-command.js +4 -1
  23. package/dist/cli/messenger/complete-command.js +1 -1
  24. package/dist/cli/messenger/contacts-command.js +9 -3
  25. package/dist/cli/messenger/conversation-answers.d.ts +5 -0
  26. package/dist/cli/messenger/conversations-command.js +18 -1
  27. package/dist/cli/messenger/counters-command.js +1 -0
  28. package/dist/cli/messenger/download-command.js +1 -0
  29. package/dist/cli/messenger/flood-command.js +1 -0
  30. package/dist/cli/messenger/guard-commands.js +5 -4
  31. package/dist/cli/messenger/inbox.js +1 -0
  32. package/dist/cli/messenger/mcp-command.js +6 -2
  33. package/dist/cli/messenger/media-archive-command.d.ts +3 -0
  34. package/dist/cli/messenger/media-archive-command.js +72 -0
  35. package/dist/cli/messenger/media-archive-move-command.d.ts +3 -0
  36. package/dist/cli/messenger/media-archive-move-command.js +40 -0
  37. package/dist/cli/messenger/media-archive-timer-command.d.ts +3 -0
  38. package/dist/cli/messenger/media-archive-timer-command.js +70 -0
  39. package/dist/cli/messenger/message-answers.d.ts +5 -0
  40. package/dist/cli/messenger/message-writes.d.ts +5 -0
  41. package/dist/cli/messenger/messages-comments-command.js +1 -0
  42. package/dist/cli/messenger/messages-delete-command.js +1 -1
  43. package/dist/cli/messenger/messages-search-command.js +1 -0
  44. package/dist/cli/messenger/messages-stats-command.js +1 -0
  45. package/dist/cli/messenger/models-command.js +3 -0
  46. package/dist/cli/messenger/private-people-command.js +12 -10
  47. package/dist/cli/messenger/rankings-command.js +2 -0
  48. package/dist/cli/messenger/reactions-command.js +8 -2
  49. package/dist/cli/messenger/retention-command.js +1 -0
  50. package/dist/cli/messenger/review.js +1 -0
  51. package/dist/cli/messenger/search-answers.js +2 -0
  52. package/dist/cli/messenger/search-command.js +5 -1
  53. package/dist/cli/messenger/searches-command.d.ts +2 -0
  54. package/dist/cli/messenger/searches-command.js +26 -8
  55. package/dist/cli/messenger/server-command.js +7 -2
  56. package/dist/cli/messenger/store-maintenance-command.js +7 -0
  57. package/dist/cli/messenger/tags-command.js +6 -2
  58. package/dist/cli/messenger/tasks-command.js +10 -7
  59. package/dist/cli/messenger/transcribe-command.js +1 -0
  60. package/dist/cli/messenger/watch-command.js +1 -0
  61. package/dist/cli/program.js +5 -2
  62. package/dist/entities/contact-read.d.ts +119 -0
  63. package/dist/entities/contact-read.js +50 -0
  64. package/dist/entities/evidence.d.ts +40 -0
  65. package/dist/entities/index.d.ts +191 -2
  66. package/dist/entities/index.js +7 -1
  67. package/dist/entities/message.d.ts +9 -0
  68. package/dist/entities/message.js +6 -0
  69. package/dist/errors/reasons.d.ts +1 -0
  70. package/dist/errors/reasons.js +15 -0
  71. package/dist/meetings/mcp/index.d.ts +15 -15
  72. package/dist/parity/catalogue.js +1 -1
  73. package/dist/parity/manifest.d.ts +3 -1
  74. package/dist/parity/manifest.js +2 -0
  75. package/dist/parity/wording.js +2 -0
  76. package/dist/search/lucene/registry.js +2 -2
  77. package/dist/sends/permissions.js +7 -0
  78. package/dist/services/index.d.ts +5 -0
  79. package/dist/services/index.js +2 -0
  80. package/dist/services/media-archive-files.d.ts +10 -0
  81. package/dist/services/media-archive-files.js +110 -0
  82. package/dist/services/media-archive-locks.d.ts +6 -0
  83. package/dist/services/media-archive-locks.js +71 -0
  84. package/dist/services/media-archive-manifest.d.ts +29 -0
  85. package/dist/services/media-archive-manifest.js +116 -0
  86. package/dist/services/media-archive-move.d.ts +21 -0
  87. package/dist/services/media-archive-move.js +163 -0
  88. package/dist/services/media-archive.d.ts +34 -0
  89. package/dist/services/media-archive.js +165 -0
  90. package/dist/services/messages-search.js +13 -2
  91. package/dist/services/messages.d.ts +5 -0
  92. package/dist/store/search-index.d.ts +4 -1
  93. package/dist/store/search-index.js +10 -1
  94. package/dist/store/sqlite/hit-places.d.ts +12 -0
  95. package/dist/store/sqlite/hit-places.js +76 -0
  96. package/dist/store/sqlite/lucene-fields.d.ts +8 -0
  97. package/dist/store/sqlite/lucene-fields.js +6 -4
  98. package/dist/store/sqlite/lucene.js +25 -70
  99. package/dist/store/sqlite/manifest.js +2 -0
  100. package/dist/store/sqlite/migrations.generated.js +19 -0
  101. package/dist/store/sqlite/note-index.d.ts +11 -1
  102. package/dist/store/sqlite/note-index.js +18 -6
  103. package/dist/store/sqlite/schema.d.ts +39 -0
  104. package/dist/store/sqlite/schema.js +4 -0
  105. package/dist/store/sqlite/transcript-query.d.ts +40 -0
  106. package/dist/store/sqlite/transcript-query.js +108 -0
  107. package/dist/store/sqlite/words.d.ts +6 -0
  108. package/package.json +3 -3
  109. package/parity.json +50 -1
@@ -16,8 +16,8 @@ export const privatePeopleCommands = (messenger) => {
16
16
  const alias = new Command("alias").description("a private local display name in the selected account");
17
17
  annotate(alias.command("set"), { effects: LOCAL_WRITE })
18
18
  .description("your own name for a person in this account; kept on this machine, never sent")
19
- .argument("<person>")
20
- .argument("<alias>")
19
+ .argument("<person>", "the person's id, reference or known name")
20
+ .argument("<alias>", "the private name to use for this person")
21
21
  .action(async function (person, value) {
22
22
  const context = messengerContext(this, messenger);
23
23
  refuseLocalWrite(context, messenger.app.command, "contacts.alias.set");
@@ -25,7 +25,7 @@ export const privatePeopleCommands = (messenger) => {
25
25
  });
26
26
  annotate(alias.command("rm"), { effects: LOCAL_WRITE })
27
27
  .description("drop your local name for a person; their own name shows again")
28
- .argument("<person>")
28
+ .argument("<person>", "the person's id, reference or known name")
29
29
  .action(async function (person) {
30
30
  const context = messengerContext(this, messenger);
31
31
  refuseLocalWrite(context, messenger.app.command, "contacts.alias.rm");
@@ -34,7 +34,7 @@ export const privatePeopleCommands = (messenger) => {
34
34
  const notes = new Command("notes").description("your private notes on a stored contact, the same in every account that sees them");
35
35
  withQuery(annotate(notes.command("list"), { effects: LOCAL_READ })
36
36
  .description("every private note you keep on a person, oldest first")
37
- .argument("<person>"), NOTES_QUERY).action(async function (person, words) {
37
+ .argument("<person>", "the person's id, reference or known name"), NOTES_QUERY).action(async function (person, words) {
38
38
  const context = messengerContext(this, messenger);
39
39
  listQuery(typedQuery(words), NOTES_QUERY, "contacts notes list");
40
40
  const items = await context.withServices((services) => services.privatePeople.notes(person));
@@ -42,8 +42,8 @@ export const privatePeopleCommands = (messenger) => {
42
42
  });
43
43
  annotate(notes.command("show"), { effects: LOCAL_READ, output: { entity: "contact_note" } })
44
44
  .description("one private note on a person, with its revision for edit")
45
- .argument("<person>")
46
- .argument("<id>")
45
+ .argument("<person>", "the person's id, reference or known name")
46
+ .argument("<id>", "the note id from contacts notes list")
47
47
  .action(async function (person, id) {
48
48
  const context = messengerContext(this, messenger);
49
49
  renderAnswer(this, context, itemOf(await context.withServices((services) => services.privatePeople.note(person, id))));
@@ -56,10 +56,12 @@ export const privatePeopleCommands = (messenger) => {
56
56
  .description(name === "add"
57
57
  ? "add a private note on a person from stdin or --file; never sent"
58
58
  : "replace a note's text; --revision must match the one you read")
59
- .argument("<person>")
59
+ .argument("<person>", "the person's id, reference or known name")
60
60
  .addOption(ownerOnlyOption("--file <path>", "read note text from a file; omitted or - reads stdin"));
61
61
  if (name === "edit")
62
- command.argument("<id>").requiredOption("--revision <n>", "the revision you read before editing");
62
+ command
63
+ .argument("<id>", "the note id from contacts notes list")
64
+ .requiredOption("--revision <n>", "the revision you read before editing");
63
65
  command.action(async function (person, id) {
64
66
  const context = messengerContext(this, messenger);
65
67
  refuseLocalWrite(context, messenger.app.command, `contacts.notes.${name}`);
@@ -76,8 +78,8 @@ export const privatePeopleCommands = (messenger) => {
76
78
  }
77
79
  annotate(notes.command("remove"), { effects: LOCAL_WRITE })
78
80
  .description("delete one private note on a person")
79
- .argument("<person>")
80
- .argument("<id>")
81
+ .argument("<person>", "the person's id, reference or known name")
82
+ .argument("<id>", "the note id from contacts notes list")
81
83
  .action(async function (person, id) {
82
84
  const context = messengerContext(this, messenger);
83
85
  refuseLocalWrite(context, messenger.app.command, "contacts.notes.remove");
@@ -25,6 +25,7 @@ const selectionOf = (value) => {
25
25
  };
26
26
  export const rankingsTopCommand = (messenger, target) => {
27
27
  const command = syncOptions(annotate(new Command("top"), { effects: REMOTE_READ, output: { entity: "ranking" } }), messenger)
28
+ .summary("rank messages or people by a measure or score")
28
29
  .description(`rank ${target === "messages" ? "stored messages" : "the human authors of stored messages"} by a measure or explainable score; counters are snapshots with per-field observation freshness`)
29
30
  .argument("[query...]", "a strict Lucene query; none selects every stored message")
30
31
  .addOption(new Option("--measure <name>", "the ranking metric; not with score or weights").choices(target === "messages" ? [...MESSAGE_MEASURES] : [...AUTHOR_MEASURES]))
@@ -86,6 +87,7 @@ const emojiSplit = (reactions) => [
86
87
  ...(reactions.length > SHOWN_REACTIONS ? [`+${reactions.length - SHOWN_REACTIONS}`] : []),
87
88
  ].join(" · ");
88
89
  export const rankingEvidenceCommand = (messenger, target) => annotate(new Command("evidence"), { effects: LOCAL_READ, output: { entity: "evidence" } })
90
+ .summary("evidence for an exact statistics selection")
89
91
  .description("bounded messages, answer pairs or retention members from an exact drilldown selection")
90
92
  .argument(target === "messages" ? "<message>" : "<person>", target === "messages"
91
93
  ? "the canonical message locator, or retention cohort reference, from drilldown"
@@ -6,7 +6,10 @@ import { messengerContext } from "./context.js";
6
6
  /** `reactions add|remove`: the owner's own reaction on a message. */
7
7
  export const reactionsCommand = (messenger) => {
8
8
  const reactions = new Command("reactions").description("react to messages");
9
- annotate(reactions.command("add"), { effects: REMOTE_WRITE })
9
+ annotate(reactions.command("add"), {
10
+ effects: REMOTE_WRITE,
11
+ examples: [`${messenger.app.command} personal reactions add "Book club" 101 👍`],
12
+ })
10
13
  .description("put your reaction on a message; it replaces the one you had")
11
14
  .argument("<chat>", messenger.chatArgument)
12
15
  .argument("<message>", "the message id")
@@ -16,7 +19,10 @@ export const reactionsCommand = (messenger) => {
16
19
  const reacted = await context.withServices((services) => services.messages.react({ chat, message: message.trim(), emoji: emoji.trim() }));
17
20
  renderAnswer(this, context, itemOf(snakeKeys(reacted)));
18
21
  });
19
- annotate(reactions.command("remove"), { effects: REMOTE_WRITE })
22
+ annotate(reactions.command("remove"), {
23
+ effects: REMOTE_WRITE,
24
+ examples: [`${messenger.app.command} personal reactions remove "Book club" 101`],
25
+ })
20
26
  .description("take your reaction off a message")
21
27
  .argument("<chat>", messenger.chatArgument)
22
28
  .argument("<message>", "the message id")
@@ -6,6 +6,7 @@ import { positiveCount } from "../paging.js";
6
6
  import { messengerContext } from "./context.js";
7
7
  import { refuseJsonl, retentionAnswer } from "./stats-answers.js";
8
8
  export const retentionCommand = (messenger) => annotate(new Command("retention"), { effects: REMOTE_READ, output: { entity: "retention" } })
9
+ .summary("joining cohorts and observed membership checkpoints")
9
10
  .description("joining cohorts and observed checkpoint membership from saved roster observations")
10
11
  .argument("<chat>", messenger.chatArgument)
11
12
  .option("--since-time <time>", "joining period starts at ISO 8601 or a relative time; last 90 days by default")
@@ -29,6 +29,7 @@ export const reviewCommand = (messenger) => annotate(new Command("review"), {
29
29
  output: { entity: "message", view: "summary" },
30
30
  query: REVIEW_QUERY,
31
31
  })
32
+ .summary("chats with new activity or unanswered messages")
32
33
  .description("every message, yours too, in chats that changed since a point — for reviewing who owes what")
33
34
  .addHelpText("after", queryHelp(REVIEW_QUERY))
34
35
  .argument("[query...]", `where the last review ended and which chat: date>2h, chat:"Book club"; ${REVIEW_DAYS} days ago if no date`)
@@ -21,6 +21,8 @@ export const messageHitItem = (hit, query = "") => {
21
21
  excerpt: hit.excerpt ?? excerpt(hit.text, query),
22
22
  matched_in: hit.matchedIn,
23
23
  score: hit.score ?? undefined,
24
+ match: hit.match,
25
+ corrections: hit.corrections,
24
26
  found_in: hit.source,
25
27
  discovery: hit.discovery
26
28
  ? defined({
@@ -34,6 +34,7 @@ const maxMeetingsOf = (value) => {
34
34
  };
35
35
  const allCommand = (messenger) => {
36
36
  const command = annotate(new Command("all"), { effects: REMOTE_READ, partial: true })
37
+ .summary("search stored messages, notes and selected meeting transcripts")
37
38
  .description((messenger.serverSearch
38
39
  ? "search messenger messages and notes in the local store, and the messenger's server for messages " +
39
40
  "(--backend; notes are local only)"
@@ -95,6 +96,7 @@ const allCommand = (messenger) => {
95
96
  });
96
97
  };
97
98
  const notesCommand = (messenger) => annotate(new Command("notes"), { effects: LOCAL_READ, partial: true, output: { entity: "note", view: "hit" } })
99
+ .summary("find stored notes by words and filters")
98
100
  .description("search the notes — written in memo, or imported from a notes folder — by words and, with the local " +
99
101
  "text model, by meaning; each hit says which found it and what it links to")
100
102
  .argument("<query...>", 'strict Lucene query: words, "phrases", AND/OR/NOT, tag:, source:internal (written in memo) or source:file ' +
@@ -150,7 +152,9 @@ const notesCommand = (messenger) => annotate(new Command("notes"), { effects: LO
150
152
  * hierarchy"). `topics` only where the CLI has forum topics.
151
153
  */
152
154
  export const searchCommand = (messenger, { topics = false } = {}) => {
153
- const search = new Command("search").description("find things by text: one resource when you know it (messages, notes), or search all across messages and notes; for mail, the separate memo CLI: memo search mail");
155
+ const search = new Command("search")
156
+ .summary("search the local archive")
157
+ .description("find things by text: one resource when you know it (messages, notes), or search all across messages and notes; for mail, the separate memo CLI: memo search mail");
154
158
  search
155
159
  .addCommand(allCommand(messenger))
156
160
  .addCommand(messagesSearchCommand(messenger))
@@ -1,4 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { type Messenger } from "./context.js";
3
+ /** `searches list` takes a query over the saved search fields, none of them a filter: the list is short and whole. */
4
+ export declare const SEARCHES_QUERY: import("@wirecat/cli-base/query").QueryRegistry;
3
5
  /** Saved searches and the history of `search messages` and `stats messages show`, in the local store only. */
4
6
  export declare const searchesCommand: (messenger: Messenger) => Command;
@@ -20,11 +20,20 @@ const line = ({ id, name, command, params, runs, lastRunAt }) => [
20
20
  `${runs} runs`,
21
21
  lastRunAt?.slice(0, 16).replace("T", " ") ?? "never run",
22
22
  ].join(" ");
23
- const SEARCHES_QUERY = unfilteredQuery("saved_search", savedSearch);
23
+ /** `searches list` takes a query over the saved search fields, none of them a filter: the list is short and whole. */
24
+ export const SEARCHES_QUERY = unfilteredQuery("saved_search", savedSearch);
24
25
  /** Saved searches and the history of `search messages` and `stats messages show`, in the local store only. */
25
26
  export const searchesCommand = (messenger) => {
26
- const searches = new Command("searches").description("saved searches and the history of search messages and stats messages show, kept in the local store; --saved runs one");
27
- annotate(searches.command("create"), { effects: LOCAL_WRITE, output: { entity: "saved_search" } })
27
+ const tool = `${messenger.app.command} personal`;
28
+ const searches = new Command("searches")
29
+ .summary("saved searches and their run history")
30
+ .description("saved searches and the history of search messages and stats messages show, kept in the local store; --saved runs one");
31
+ annotate(searches.command("create"), {
32
+ effects: LOCAL_WRITE,
33
+ output: { entity: "saved_search" },
34
+ examples: [`${tool} searches create project-drafts draft --chat "Project room"`],
35
+ })
36
+ .summary("save a search and its query")
28
37
  .description("save a search under a name without running it; search messages --saved <name> runs it")
29
38
  .argument("<name>", "up to 64 letters a–z, digits and hyphens, not only digits")
30
39
  .argument("[query...]", "the query, as for search messages; none matches every stored message")
@@ -63,7 +72,11 @@ export const searchesCommand = (messenger) => {
63
72
  const saved = await context.withServices((services) => services.searches.create(name, params, { replace: replace === true }));
64
73
  renderAnswer(this, context, itemOf(savedSearchItem(saved)), () => `${line(saved)}\n`);
65
74
  });
66
- annotate(searches.command("show"), { effects: REMOTE_READ, output: { entity: "saved_search" } })
75
+ annotate(searches.command("show"), {
76
+ effects: REMOTE_READ,
77
+ output: { entity: "saved_search" },
78
+ examples: [`${tool} searches show project-drafts`],
79
+ })
67
80
  .description("one saved search or earlier run: its query, options and how often it ran")
68
81
  .argument("<name|id>", "a saved search's name, or the id of any row of searches history")
69
82
  .action(async function (reference) {
@@ -71,7 +84,7 @@ export const searchesCommand = (messenger) => {
71
84
  const found = await context.withServices((services) => services.searches.show(reference));
72
85
  renderAnswer(this, context, itemOf(savedSearchItem(found)), () => `${line(found)}\n`);
73
86
  });
74
- withQuery(annotate(searches.command("list"), { effects: LOCAL_READ }), SEARCHES_QUERY)
87
+ withQuery(annotate(searches.command("list"), { effects: LOCAL_READ, examples: [`${tool} searches list`] }), SEARCHES_QUERY)
75
88
  .description("the saved searches, by name")
76
89
  .action(async function (words) {
77
90
  const context = messengerContext(this, messenger);
@@ -82,9 +95,10 @@ export const searchesCommand = (messenger) => {
82
95
  if (found.length === 0 && context.format === "pretty")
83
96
  context.renderer.note("no saved searches — `searches create <name> <query>` saves one");
84
97
  });
85
- annotate(withPaging(annotate(searches.command("history"), { effects: LOCAL_READ })), {
98
+ annotate(withPaging(annotate(searches.command("history"), { effects: LOCAL_READ, examples: [`${tool} searches history --limit 10`] })), {
86
99
  output: { entity: "saved_search" },
87
100
  })
101
+ .summary("past searches and counts, without their results")
88
102
  .description("the searches and counts that ran, newest first — saved ones included; never their results")
89
103
  .action(async function () {
90
104
  const context = messengerContext(this, messenger);
@@ -97,7 +111,11 @@ export const searchesCommand = (messenger) => {
97
111
  if (found.items.length === 0 && context.format === "pretty")
98
112
  context.renderer.note("no searches ran yet");
99
113
  });
100
- annotate(searches.command("delete"), { effects: LOCAL_WRITE, output: { entity: "saved_search" } })
114
+ annotate(searches.command("delete"), {
115
+ effects: LOCAL_WRITE,
116
+ output: { entity: "saved_search" },
117
+ examples: [`${tool} searches delete project-drafts`],
118
+ })
101
119
  .description("delete a saved search, or one run from the history")
102
120
  .argument("<name|id>", "a saved search's name, or the id of any row of searches history")
103
121
  .action(async function (reference) {
@@ -106,7 +124,7 @@ export const searchesCommand = (messenger) => {
106
124
  const deleted = await context.withServices((services) => services.searches.delete(reference));
107
125
  renderAnswer(this, context, itemOf(savedSearchItem(deleted)), () => `deleted ${deleted.name ?? `run ${deleted.id}`}\n`);
108
126
  });
109
- annotate(searches.command("clear"), { effects: LOCAL_WRITE })
127
+ annotate(searches.command("clear"), { effects: LOCAL_WRITE, examples: [`${tool} searches clear`] })
110
128
  .description("empty the history; saved searches stay")
111
129
  .action(async function () {
112
130
  const context = messengerContext(this, messenger);
@@ -60,7 +60,9 @@ const lockProcess = (app, marker, serveArgv) => (context, system, logPath) => ({
60
60
  */
61
61
  export const serverCommand = (messenger, options = {}) => {
62
62
  const { app } = messenger;
63
- const command = annotate(new Command("server"), { mcp: false }).description(`\`${app.command} serve\` in the background: start, stop, restart, status, logs; install adds a systemd or launchd unit`);
63
+ const command = annotate(new Command("server"), { mcp: false })
64
+ .summary("manage this profile's background server")
65
+ .description(`\`${app.command} serve\` in the background: start, stop, restart, status, logs; install adds a systemd or launchd unit`);
64
66
  const marker = (profile) => `${app.envPrefix}_SERVER=${profile}`;
65
67
  const serveArgv = options.serveArgv ?? ["serve"];
66
68
  const prepare = (self) => {
@@ -181,7 +183,9 @@ export const serverCommand = (messenger, options = {}) => {
181
183
  return ["--idle", idle];
182
184
  };
183
185
  const withIdle = (sub) => options.idle ? sub.option("--idle <duration>", "stop after this long with nobody using it — 15m, 1h") : sub;
184
- withIdle(destroysNothing(annotate(command.command("start"), { effects: LOCAL_WRITE, examples: [`${app.command} personal server start`] })).description("start serve in the background — through the unit if one is installed — and answer once it connects")).action(async function () {
186
+ withIdle(destroysNothing(annotate(command.command("start"), { effects: LOCAL_WRITE, examples: [`${app.command} personal server start`] }))
187
+ .summary("start this profile's background server")
188
+ .description("start serve in the background — through the unit if one is installed — and answer once it connects")).action(async function () {
185
189
  await start(prepare(this), idleArgs(this));
186
190
  });
187
191
  destroysNothing(annotate(command.command("stop"), { effects: LOCAL_WRITE, examples: [`${app.command} personal server stop`] }))
@@ -199,6 +203,7 @@ export const serverCommand = (messenger, options = {}) => {
199
203
  await start(prepared, args);
200
204
  });
201
205
  annotate(command.command("status"), { effects: LOCAL_READ, examples: [`${app.command} personal server status`] })
206
+ .summary("this profile's background-server status")
202
207
  .description("whether serve runs for this profile, since when, who started it, and the unit if there is one")
203
208
  .action(async function () {
204
209
  const prepared = prepare(this);
@@ -93,6 +93,7 @@ const refuseWhileServing = (command, messenger) => {
93
93
  }
94
94
  };
95
95
  const repairCommand = (messenger) => annotate(new Command("repair"), { effects: LOCAL_WRITE, mcp: false })
96
+ .summary("repair table shapes while keeping the old tables")
96
97
  .description("bring every table to this build's shape, deleting nothing: a table of the wrong shape is kept as a copy beside a new one")
97
98
  .option("--dry-run", "say what it would do, and change nothing")
98
99
  .action(async function () {
@@ -152,6 +153,7 @@ const copiesCommand = () => new Command("copies").description("the tables `store
152
153
  result(this, { path, deleted });
153
154
  }));
154
155
  const infoCommand = () => annotate(new Command("info"), { effects: LOCAL_READ, mcp: false })
156
+ .summary("the local archive's format, size and contents")
155
157
  .description("the store file: where it is, its size, its schema and how many rows it holds; changes nothing")
156
158
  .action(async function () {
157
159
  const path = storePath(environmentOf(this).env ?? process.env);
@@ -181,6 +183,7 @@ const infoCommand = () => annotate(new Command("info"), { effects: LOCAL_READ, m
181
183
  * newest message held, not `sync_ranges`: those are in message ids, which a chat list does not give.
182
184
  */
183
185
  const checkCommand = (messenger) => annotate(new Command("check"), { effects: LOCAL_READ, mcp: false })
186
+ .summary("check the local archive for problems")
184
187
  .description("whether the store is healthy — integrity, search indexes, disk, and which chats are behind")
185
188
  .action(async function () {
186
189
  const { renderer } = outputFor(this);
@@ -360,6 +363,7 @@ const chatsBehind = (database) => database
360
363
  refreshed: isoOf(row.refreshed),
361
364
  }));
362
365
  const migrateCommand = (messenger) => annotate(new Command("migrate"), { effects: LOCAL_WRITE, mcp: false })
366
+ .summary("migrate an older local archive")
363
367
  .description("bring the store up to this build's schema, then normalize, index and stem the messages and notes stored before it")
364
368
  .action(async function () {
365
369
  const { renderer } = outputFor(this);
@@ -428,6 +432,7 @@ const buildNoteIndex = (database, note, { reset = false } = {}) => {
428
432
  return { notesIndexed: drainNoteIndex(database, stemmerCache()) };
429
433
  };
430
434
  const reindexCommand = (messenger) => destroysNothing(annotate(new Command("reindex"), { effects: LOCAL_WRITE, mcp: false }))
435
+ .summary("rebuild the local archive's search indexes")
431
436
  .description("rebuild the word index, its typo vocabulary, the stems, the files' word index, the notes' indexes and who took part in what, from what is stored; loses nothing")
432
437
  .action(async function () {
433
438
  const { renderer } = outputFor(this);
@@ -540,6 +545,7 @@ const decryptCommand = () => annotate(new Command("decrypt"), { effects: LOCAL_W
540
545
  * `serve` runs: that opens the store on its first write, so until then it holds nothing to see.
541
546
  */
542
547
  const restoreCommand = (messenger) => annotate(new Command("restore"), { effects: LOCAL_WRITE, mcp: false })
548
+ .summary("restore a backup while keeping the current store")
543
549
  .description("put a backup in place of the store; the store it replaces is kept beside it, never deleted")
544
550
  .argument("<file>", "a file `store backup` wrote; one written with --encrypt asks for its password")
545
551
  .action(async function (file) {
@@ -656,6 +662,7 @@ const quiesce = async (path, messenger, then, warn, waitMs = STORE_LOCK_WAIT_MS)
656
662
  * schema. The backup is made before anything is deleted, and its path is said before the delete.
657
663
  */
658
664
  const resetCommand = (messenger) => annotate(new Command("reset"), { effects: LOCAL_WRITE, mcp: false })
665
+ .summary("back up the local archive and replace it with an empty one")
659
666
  .description("back the store up beside itself, then delete it and start an empty one at this build's schema; asks first, or --yes")
660
667
  .option("--no-backup", "delete the store without backing it up first")
661
668
  .action(async function ({ backup: keep }) {
@@ -29,7 +29,9 @@ const sourceOf = (value) => {
29
29
  /** The owner's labels, in the local store only: `tag:` in a search finds what they label. */
30
30
  export const tagsCommand = (messenger) => {
31
31
  const tool = `${messenger.app.command} personal`;
32
- const tags = new Command("tags").description("your own labels on chats, people, messages and tasks, kept in the local store and never sent; tag: in a search finds them");
32
+ const tags = new Command("tags")
33
+ .summary("tags on stored messages and other records")
34
+ .description("your own labels on chats, people, messages and tasks, kept in the local store and never sent; tag: in a search finds them");
33
35
  changesStore(annotate(tags.command("auto"), { effects: FETCH, examples: [`${tool} tags auto --chat "Book club" --dry-run`] }))
34
36
  .description("derive local group/channel tags from cached metadata using keyword rules")
35
37
  .option("--chat <chat>", "a stored group/channel; repeat to select several", (value, previous) => [...previous, value], [])
@@ -73,7 +75,9 @@ export const tagsCommand = (messenger) => {
73
75
  withQuery(annotate(tags.command("list"), {
74
76
  effects: LOCAL_READ,
75
77
  examples: [`${tool} tags list`, `${tool} tags list tag:reading`],
76
- }).description("what is tagged: this account's chats, messages and tasks, and the people of its messenger"), TAGGING_QUERY).action(async function (words) {
78
+ })
79
+ .summary("tagged chats, people, messages and tasks")
80
+ .description("what is tagged: this account's chats, messages and tasks, and the people of its messenger"), TAGGING_QUERY).action(async function (words) {
77
81
  const context = messengerContext(this, messenger);
78
82
  const query = listQuery(typedQuery(words), TAGGING_QUERY, "tags list");
79
83
  const found = await context.withServices((services) => services.tags.list({ ...(query ? { query } : {}), ...pageOf(context.settings) }));
@@ -129,7 +129,9 @@ export const tasksCommand = (messenger) => {
129
129
  const writerOf = (command) => readOnlyLockOf(messenger.app, environmentOf(command).env ?? process.env) || environmentOf(command).agent
130
130
  ? "agent"
131
131
  : "owner";
132
- const tasks = new Command("tasks").description("what waits on you — unanswered questions, mentions, requests, promises — kept in the local store; review and serve add them");
132
+ const tasks = new Command("tasks")
133
+ .summary("tasks found or kept in the local archive")
134
+ .description("what waits on you — unanswered questions, mentions, requests, promises — kept in the local store; review and serve add them");
133
135
  withQuery(annotate(tasks.command("list"), {
134
136
  effects: LOCAL_READ,
135
137
  examples: [`${tool} personal tasks list --limit 10`],
@@ -283,8 +285,8 @@ export const tasksCommand = (messenger) => {
283
285
  }))
284
286
  .summary("two tasks are not the same job: the pair is never offered again")
285
287
  .description("two tasks are not the same job: the pair leaves the list and is never offered again")
286
- .argument("<dup>", "the task listed as the duplicate, by its id or key")
287
- .argument("<task>", "the task it was paired with")
288
+ .argument("<task>", "the task listed as the duplicate, by its id or key")
289
+ .argument("<other-task>", "the task it was paired with")
288
290
  .action(decided("rejected", "tasks.duplicates.reject"));
289
291
  destroysNothing(annotate(duplicates.command("defer"), {
290
292
  effects: LOCAL_WRITE,
@@ -292,14 +294,14 @@ export const tasksCommand = (messenger) => {
292
294
  }))
293
295
  .summary("the fields and the text cannot tell: leave the pair to the owner")
294
296
  .description("the fields and the text cannot tell: leave the pair to the owner, who lists it with decision:unsure")
295
- .argument("<dup>", "the task listed as the duplicate, by its id or key")
296
- .argument("<task>", "the task it was paired with")
297
+ .argument("<task>", "the task listed as the duplicate, by its id or key")
298
+ .argument("<other-task>", "the task it was paired with")
297
299
  .action(decided("unsure", "tasks.duplicates.defer"));
298
300
  destroysNothing(annotate(tasks.command("merge"), { effects: LOCAL_WRITE, examples: [`${tool} tasks merge PR-14 --into PR-12`] }))
299
301
  .summary("one job seen twice: fold the duplicate into the task that stays")
300
302
  .description("one job seen twice: link every message of the duplicate to the task and close the duplicate dismissed, " +
301
303
  "duplicate-of the task; nothing is deleted, and tasks unmerge undoes it")
302
- .argument("<dup>", "the duplicate, by its id or key; it must be open")
304
+ .argument("<task>", "the duplicate, by its id or key; it must be open")
303
305
  .requiredOption("--into <id>", "the task that stays, by its id or key")
304
306
  .action(async function (dup) {
305
307
  const context = messengerContext(this, messenger);
@@ -312,7 +314,7 @@ export const tasksCommand = (messenger) => {
312
314
  destroysNothing(annotate(tasks.command("unmerge"), { effects: LOCAL_WRITE, examples: [`${tool} tasks unmerge PR-14`] }))
313
315
  .summary("undo a merge: reopen the task and remove its duplicate-of relation")
314
316
  .description("undo a merge: reopen the task and remove its duplicate-of relation; messages the merge linked stay linked")
315
- .argument("<dup>", "the merged task, by its id or key")
317
+ .argument("<task>", "the merged task, by its id or key")
316
318
  .action(async function (dup) {
317
319
  const context = messengerContext(this, messenger);
318
320
  refuseLocalWrite(context, messenger.app.command, "tasks.unmerge");
@@ -417,6 +419,7 @@ export const tasksCommand = (messenger) => {
417
419
  ],
418
420
  }))
419
421
  .summary("close a task: done, or dismissed when it needs no answer; an agent's unsure close waits on the owner")
422
+ .summary("close a task as done or dismissed")
420
423
  .description("close a task: done, or dismissed when it needs no answer. An agent (AI_AGENT or CLAUDECODE set) states its " +
421
424
  "--certainty; below config task_agent_close_min, or with task_agent_close off, the task stays open and the " +
422
425
  "close waits on the owner, who confirms it with tasks close or refuses it with tasks reject. A closed task " +
@@ -12,6 +12,7 @@ export const transcribeSubcommand = (messages, messenger) => annotate(messages.c
12
12
  examples: [`${messenger.app.command} personal messages transcribe "Book club" 101`],
13
13
  effects: REMOTE_READ,
14
14
  })
15
+ .summary("transcribe one voice message")
15
16
  .description(`a voice message as text — by ${messenger.name ?? messenger.app.command} where it can, else by a model on this machine`)
16
17
  .argument("<chat>", messenger.chatArgument)
17
18
  .argument("<message>", "the id of a voice message")
@@ -12,6 +12,7 @@ import { messengerContext } from "./context.js";
12
12
  * because a stream is not one value. Nothing is marked read.
13
13
  */
14
14
  export const watchCommand = (messenger) => annotate(new Command("watch"), { effects: REMOTE_READ, lifetime: "persistent" })
15
+ .summary("stream new messages until interrupted or timed out")
15
16
  .description("print new messages as they arrive, until Ctrl-C or --timeout (either ends it normally)")
16
17
  .option("--events", "also edits, deletions, reactions, poll votes and chat events; every line then names its event")
17
18
  .action(async function () {
@@ -20,6 +20,7 @@ import { changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTarge
20
20
  import { itemOf, renderAnswer, snakeKeys } from "./envelope.js";
21
21
  import { byteCount, DEFAULT_COMMAND_MS, DEFAULT_OUTPUT_BYTES, execution } from "./execution.js";
22
22
  import { installHelp } from "./help/first-screen.js";
23
+ import { showInputFields } from "./input-fields.js";
23
24
  import { MAX_BUFFERED_INPUT, provideInputPolicy } from "./input-policy.js";
24
25
  import { checkJson, liftJson } from "./json-fields.js";
25
26
  import { movedPath, movedPointer } from "./moved.js";
@@ -354,7 +355,7 @@ const runOnce = async (argv, definition, options) => {
354
355
  // Only before `--`: what follows is a positional, an agent's text among them, never a command word.
355
356
  const end = lifted.indexOf("--");
356
357
  for (const words of [flags, end < 0 ? lifted : lifted.slice(0, end)])
357
- refuseJoinedPath(program, words.filter((word) => !word.startsWith("-")));
358
+ refuseJoinedPath(program, words.filter((word) => !word.startsWith("-")), profile !== undefined && !/\s/.test(profile) ? [profile] : []);
358
359
  }
359
360
  catch (failure) {
360
361
  const settled = await settleFailure(failure, { definition, program, rest: lifted, profile, options });
@@ -420,7 +421,8 @@ const runOnce = async (argv, definition, options) => {
420
421
  error.code === "commander.unknownCommand" &&
421
422
  rest[0] !== undefined &&
422
423
  !commandWords(program).has(rest[0])) {
423
- message += ` "${profile}" is not a command, so it was read as a profile name — which left "${rest[0]}" to be one.`;
424
+ message = message.trimEnd();
425
+ message += ` — "${profile}" is not a command, so it was read as a profile name — which left "${rest[0]}" to be one.`;
424
426
  }
425
427
  const failure = new CliError("validation_error", message);
426
428
  const settled = await settleFailure(failure, { definition, program, rest, profile, options });
@@ -577,6 +579,7 @@ const failed = ({ app }, program, rest, error, settled, debug) => {
577
579
  targets: judgedTargets(program),
578
580
  ...(effects ? { effects } : {}),
579
581
  });
582
+ showInputFields(object, (path && commandAt(program, path)) || program);
580
583
  const command = path || app.command;
581
584
  return {
582
585
  object,
@@ -0,0 +1,119 @@
1
+ import * as v from "valibot";
2
+ export declare const contactLookup: v.StrictObjectSchema<{
3
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
4
+ readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5
+ readonly username: v.NullableSchema<v.StringSchema<undefined>, undefined>;
6
+ }, undefined>;
7
+ export declare const contactSync: v.StrictObjectSchema<{
8
+ readonly added: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
9
+ readonly changed: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
10
+ readonly known: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
11
+ }, undefined>;
12
+ export declare const contactProfile: v.StrictObjectSchema<{
13
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
14
+ readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
15
+ readonly usernames: v.ArraySchema<v.StringSchema<undefined>, undefined>;
16
+ readonly bio: v.NullableSchema<v.StringSchema<undefined>, undefined>;
17
+ readonly birthday: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
18
+ readonly phone: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
19
+ readonly flags: Omit<v.StrictObjectSchema<{
20
+ readonly bot: v.BooleanSchema<undefined>;
21
+ readonly verified: v.BooleanSchema<undefined>;
22
+ readonly premium: v.BooleanSchema<undefined>;
23
+ readonly scam: v.BooleanSchema<undefined>;
24
+ readonly fake: v.BooleanSchema<undefined>;
25
+ readonly restricted: v.BooleanSchema<undefined>;
26
+ readonly deleted: v.BooleanSchema<undefined>;
27
+ readonly support: v.BooleanSchema<undefined>;
28
+ }, undefined>, "entries" | "~run" | "~standard" | "~types"> & {
29
+ readonly entries: {
30
+ readonly bot: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
31
+ readonly verified: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
32
+ readonly premium: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
33
+ readonly scam: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
34
+ readonly fake: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
35
+ readonly restricted: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
36
+ readonly deleted: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
37
+ readonly support: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
38
+ };
39
+ readonly "~standard": v.StandardProps<{
40
+ bot?: boolean | undefined;
41
+ verified?: boolean | undefined;
42
+ premium?: boolean | undefined;
43
+ scam?: boolean | undefined;
44
+ fake?: boolean | undefined;
45
+ restricted?: boolean | undefined;
46
+ deleted?: boolean | undefined;
47
+ support?: boolean | undefined;
48
+ }, {
49
+ bot?: boolean | undefined;
50
+ verified?: boolean | undefined;
51
+ premium?: boolean | undefined;
52
+ scam?: boolean | undefined;
53
+ fake?: boolean | undefined;
54
+ restricted?: boolean | undefined;
55
+ deleted?: boolean | undefined;
56
+ support?: boolean | undefined;
57
+ }>;
58
+ readonly "~run": (dataset: v.UnknownDataset, config: v.Config<v.BaseIssue<unknown>>) => v.OutputDataset<{
59
+ bot?: boolean | undefined;
60
+ verified?: boolean | undefined;
61
+ premium?: boolean | undefined;
62
+ scam?: boolean | undefined;
63
+ fake?: boolean | undefined;
64
+ restricted?: boolean | undefined;
65
+ deleted?: boolean | undefined;
66
+ support?: boolean | undefined;
67
+ }, v.BooleanIssue | v.StrictObjectIssue>;
68
+ readonly "~types"?: {
69
+ readonly input: {
70
+ bot?: boolean | undefined;
71
+ verified?: boolean | undefined;
72
+ premium?: boolean | undefined;
73
+ scam?: boolean | undefined;
74
+ fake?: boolean | undefined;
75
+ restricted?: boolean | undefined;
76
+ deleted?: boolean | undefined;
77
+ support?: boolean | undefined;
78
+ };
79
+ readonly output: {
80
+ bot?: boolean | undefined;
81
+ verified?: boolean | undefined;
82
+ premium?: boolean | undefined;
83
+ scam?: boolean | undefined;
84
+ fake?: boolean | undefined;
85
+ restricted?: boolean | undefined;
86
+ deleted?: boolean | undefined;
87
+ support?: boolean | undefined;
88
+ };
89
+ readonly issue: v.BooleanIssue | v.StrictObjectIssue;
90
+ } | undefined;
91
+ };
92
+ readonly seen: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
93
+ readonly contact: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
94
+ readonly mutual_contact: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
95
+ readonly common_chats_count: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>, undefined>;
96
+ readonly registered: v.OptionalSchema<v.NullableSchema<v.StrictObjectSchema<{
97
+ readonly at: v.StringSchema<undefined>;
98
+ readonly source: v.PicklistSchema<["telegram", "max", "estimate"], undefined>;
99
+ readonly precision: v.PicklistSchema<["day", "month"], undefined>;
100
+ }, undefined>, undefined>, undefined>;
101
+ readonly has_photo: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
102
+ readonly chats: v.ArraySchema<v.StrictObjectSchema<{
103
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
104
+ readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
105
+ readonly kind: v.PicklistSchema<["dialog", "group", "channel", "saved", "unknown"], undefined>;
106
+ readonly their_messages: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
107
+ readonly first_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
108
+ readonly last_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
109
+ readonly complete: v.BooleanSchema<undefined>;
110
+ }, undefined>, undefined>;
111
+ readonly aliases: v.ArraySchema<v.StrictObjectSchema<{
112
+ readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
113
+ readonly username: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
114
+ readonly link: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
115
+ readonly first_seen_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
116
+ readonly last_seen_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
117
+ readonly source: v.PicklistSchema<["profile", "messages"], undefined>;
118
+ }, undefined>, undefined>;
119
+ }, undefined>;