@wirecat/cli-core 0.393.0 → 0.395.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 (57) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/dist/cli/config-command.js +5 -2
  3. package/dist/cli/messenger/account-command.js +8 -3
  4. package/dist/cli/messenger/account-sessions-command.js +7 -2
  5. package/dist/cli/messenger/archive-commands.js +28 -0
  6. package/dist/cli/messenger/contacts-command.js +29 -0
  7. package/dist/cli/messenger/conversation-examples.d.ts +2 -0
  8. package/dist/cli/messenger/conversation-examples.js +25 -0
  9. package/dist/cli/messenger/conversations-command.js +2 -0
  10. package/dist/cli/messenger/mcp-command.js +12 -3
  11. package/dist/cli/messenger/models-command.js +15 -6
  12. package/dist/cli/messenger/polls-command.js +20 -5
  13. package/dist/cli/messenger/records-command.js +10 -2
  14. package/dist/cli/messenger/search-command.js +12 -0
  15. package/dist/cli/messenger/server-command.js +19 -7
  16. package/dist/cli/messenger/stats-command.js +26 -0
  17. package/dist/cli/messenger/tasks-command.js +18 -6
  18. package/dist/cli/runs/command.js +11 -4
  19. package/dist/cli/skill-command.js +6 -1
  20. package/dist/entities/index.d.ts +1 -1
  21. package/dist/entities/saved-search.d.ts +1 -1
  22. package/dist/entities/saved-search.js +1 -1
  23. package/dist/errors/reasons.js +1 -0
  24. package/dist/meetings/cli/search.js +1 -1
  25. package/dist/meetings/hit-query.d.ts +10 -0
  26. package/dist/meetings/hit-query.js +85 -1
  27. package/dist/meetings/index.d.ts +1 -1
  28. package/dist/meetings/index.js +1 -1
  29. package/dist/sends/permissions.js +2 -1
  30. package/dist/services/index.d.ts +5 -2
  31. package/dist/services/index.js +3 -1
  32. package/dist/services/meeting-combined.d.ts +1 -1
  33. package/dist/services/meeting-combined.js +4 -3
  34. package/dist/services/meeting-hit-search.d.ts +1 -1
  35. package/dist/services/meeting-hit-search.js +4 -2
  36. package/dist/services/meeting-searches.d.ts +25 -0
  37. package/dist/services/meeting-searches.js +35 -0
  38. package/dist/services/people.d.ts +5 -0
  39. package/dist/services/people.js +2 -0
  40. package/dist/services/person-context.d.ts +12 -2
  41. package/dist/services/person-context.js +2 -2
  42. package/dist/services/person-preparation.d.ts +66 -0
  43. package/dist/services/person-preparation.js +126 -0
  44. package/dist/services/search-record.d.ts +1 -1
  45. package/dist/services/searches.d.ts +1 -0
  46. package/dist/services/searches.js +17 -4
  47. package/dist/store/exports.d.ts +1 -0
  48. package/dist/store/people.d.ts +6 -0
  49. package/dist/store/people.js +2 -0
  50. package/dist/store/searches.d.ts +5 -4
  51. package/dist/store/searches.js +4 -4
  52. package/dist/store/sqlite/person-preparation.d.ts +48 -0
  53. package/dist/store/sqlite/person-preparation.js +72 -0
  54. package/dist/store/sqlite/searches.d.ts +4 -4
  55. package/dist/store/sqlite/searches.js +21 -6
  56. package/package.json +1 -1
  57. package/parity.json +7 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,69 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
5
5
  minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
6
6
  shape of this file.
7
7
 
8
+ ## Unreleased
9
+
10
+ ## 0.395.0 — 23.10.2026
11
+
12
+ ### Changed — may break callers
13
+
14
+ - `searches list`, `searches history` and `searches clear` cover only message searches. The table is shared by
15
+ every CLI on the store; a saved meeting search shown, run or deleted from a messenger is refused with
16
+ `details.reason: "search.kind"`. `SearchesStore.savedSearches`, `searchHistory` and `clearSearchHistory` take an
17
+ optional list of kinds.
18
+
19
+ ### Added
20
+
21
+ - `search meetings` takes `date:`, `project:`, `tag:` and `topic:` in the query: `budget project:RES date>=30d`. They
22
+ choose meetings as `--since-time`/`--until-time`, `--project`, `--tag` and `--topic` do, join the words with AND,
23
+ and are refused inside OR, NOT and groups (`query.unsupported-field`) or when the same filter is also an option.
24
+ Search by meaning embeds only the words. `liftMeetingFields` (`@wirecat/cli-core/meetings`) does the split.
25
+
26
+ - `meetingSearchesService` (`@wirecat/cli-core/services`): saved meeting searches, kind `meetings-search`, in the
27
+ same table: create, list, show, delete and `run`, which counts the run.
28
+
29
+ - MCP setup and diagnostic commands include examples in help and command discovery.
30
+
31
+ - Account commands include concrete examples in help and command discovery.
32
+
33
+ - Search commands include concrete examples in help and command discovery.
34
+
35
+ - Configuration commands include concrete examples in help and command discovery.
36
+
37
+ - Skill commands include concrete examples in help and command discovery.
38
+
39
+ - Task commands include concrete examples in help and command discovery.
40
+
41
+ - Chat deletion and history clearing include examples in help and command discovery.
42
+
43
+ - Poll commands include concrete examples in help and command discovery.
44
+
45
+ - Listing poll voters includes an example in help and command discovery.
46
+
47
+ - Model commands include concrete examples in help and command discovery.
48
+
49
+ - Recorded-run commands include concrete examples in help and command discovery.
50
+
51
+ - Conversation commands include examples in help and command discovery, with guidance for submitting a batch answer.
52
+
53
+ - Server commands include concrete examples in help and command discovery.
54
+
55
+ ## 0.394.0 — 23.10.2026
56
+
57
+ ### Added
58
+
59
+ - Statistics commands include examples in help and command discovery, with guidance for reusing an exact evidence selection.
60
+
61
+ - Store commands include concrete examples in help and command discovery.
62
+
63
+ - **`contacts prepare <person>`: what to know before talking to someone.** What `contacts context` gives, plus the
64
+ one-to-one chats where their message is the newest the store holds, open tasks assigned to them, accepted facts
65
+ about them and their projects, your notes about them (withheld when the profile denies `contacts.notes.list`), and
66
+ their recent meetings — each with its reference, under one byte budget, with `prepare.coverage` saying what each
67
+ section left out and why. Tasks of another account of the same messenger, and facts on a project bound to one, stay
68
+ out. It reads messages, so `messages: deny` refuses it, as it does `contacts context`. `personPreparation` in
69
+ services and `people.preparation` on the store.
70
+
8
71
  ## 0.393.0 — 23.10.2026
9
72
 
10
73
  ### Added
@@ -16,7 +16,7 @@ import { changeStoreSetting, isStoreSetting, STORE_SETTINGS, storeSettings } fro
16
16
  */
17
17
  export const configCommand = (app, config, { permissionKey } = {}) => {
18
18
  const command = annotate(new Command("config"), { mcp: false }).description("the settings in force, and where each one came from");
19
- annotate(command.command("show"), { effects: LOCAL_READ })
19
+ annotate(command.command("show"), { effects: LOCAL_READ, examples: [`${app.command} personal config show`] })
20
20
  .description("the profile, the profiles that exist, and each setting with where it came from")
21
21
  .option("--bot", "the settings a bot command on this profile gets, rather than the personal account's")
22
22
  .action(async function () {
@@ -42,7 +42,10 @@ export const configCommand = (app, config, { permissionKey } = {}) => {
42
42
  }
43
43
  });
44
44
  for (const action of ["set", "unset"]) {
45
- const sub = grantsAuthority(annotate(command.command(action), { effects: LOCAL_WRITE }))
45
+ const sub = grantsAuthority(annotate(command.command(action), {
46
+ effects: LOCAL_WRITE,
47
+ examples: [`${app.command} personal config ${action} limit${action === "set" ? " 20" : ""}`],
48
+ }))
46
49
  .argument("<setting>",
47
50
  // default_profile is only removed now: `config unset` still takes it, `config set` refuses it.
48
51
  `one of: ${[...config.allSettings.filter((one) => action === "unset" || one !== "default_profile"), ...STORE_SETTINGS, "account_groups.<name>"].join(", ")}`)
@@ -14,9 +14,10 @@ import { accountAnswer, renderItem, wholeList } from "./chat-answers.js";
14
14
  import { messengerContext } from "./context.js";
15
15
  import { privacyCommand } from "./records-command.js";
16
16
  export const accountCommand = (messenger) => {
17
+ const tool = `${messenger.app.command} personal`;
17
18
  const account = new Command("account")
18
19
  .description("the logged-in account")
19
- .addCommand(annotate(new Command("list"), { effects: REMOTE_READ })
20
+ .addCommand(annotate(new Command("list"), { effects: REMOTE_READ, examples: [`${tool} account list`] })
20
21
  .description("every profile on this computer, and the account each is logged in as; asks the messenger nothing")
21
22
  .action(async function () {
22
23
  const context = messengerContext(this, messenger, { needsProfile: false });
@@ -45,7 +46,11 @@ export const accountCommand = (messenger) => {
45
46
  await store?.close();
46
47
  }
47
48
  }))
48
- .addCommand(annotate(new Command("show"), { effects: REMOTE_READ, output: { entity: "account" } })
49
+ .addCommand(annotate(new Command("show"), {
50
+ effects: REMOTE_READ,
51
+ output: { entity: "account" },
52
+ examples: [`${tool} account show`],
53
+ })
49
54
  .description("who this profile is logged in as; the phone number shows its last four digits")
50
55
  .option("--show-phone", "print the whole phone number")
51
56
  .action(async function () {
@@ -54,7 +59,7 @@ export const accountCommand = (messenger) => {
54
59
  const account = await context.withMessenger((connection) => connection.me());
55
60
  renderAnswer(this, context, itemOf(accountAnswer(messenger.provider, whole ? account : maskedAccount(account))));
56
61
  }))
57
- .addCommand(annotate(new Command("update"), { effects: REMOTE_WRITE })
62
+ .addCommand(annotate(new Command("update"), { effects: REMOTE_WRITE, examples: [`${tool} account update --first-name Alex`] })
58
63
  .description("change the name, the description or the photo everyone sees on your profile")
59
64
  .option("--first-name <name>", "your first name")
60
65
  .option("--last-name <name>", "your last name")
@@ -11,8 +11,9 @@ import { messengerContext } from "./context.js";
11
11
  const SESSIONS_QUERY = unfilteredQuery("account_session", accountSession);
12
12
  /** `account sessions …` — where else the account is logged in, not this tool's own `session`. */
13
13
  export const accountSessionsCommand = (messenger) => {
14
+ const tool = `${messenger.app.command} personal`;
14
15
  const sessions = new Command("sessions").description(`where else this account is logged in — not \`${messenger.app.command} session\`, which is this tool's own login`);
15
- withQuery(annotate(sessions.command("list"), { effects: REMOTE_READ }), SESSIONS_QUERY)
16
+ withQuery(annotate(sessions.command("list"), { effects: REMOTE_READ, examples: [`${tool} account sessions list`] }), SESSIONS_QUERY)
16
17
  .description("every device and app logged in to this account; nothing is ended")
17
18
  .action(async function (words) {
18
19
  const context = messengerContext(this, messenger);
@@ -21,7 +22,11 @@ export const accountSessionsCommand = (messenger) => {
21
22
  renderAnswer(this, context, wholeList("account sessions list", found.map(sessionAnswer)));
22
23
  });
23
24
  // No MCP tool, at any permission level: it logs the owner out of the phone.
24
- sessions.addCommand(annotate(new Command("end"), { effects: REMOTE_WRITE, mcp: false })
25
+ sessions.addCommand(annotate(new Command("end"), {
26
+ effects: REMOTE_WRITE,
27
+ mcp: false,
28
+ examples: [`${tool} account sessions end --others`],
29
+ })
25
30
  .description("log out every other device, your phone included; this one stays")
26
31
  .option("--others", "every session but this one")
27
32
  .action(async function () {
@@ -12,6 +12,7 @@ import { kindsOf } from "../../services/inbox.js";
12
12
  import { dateWindow } from "../../services/moment.js";
13
13
  import { destroysNothing, LOCAL_READ, LOCAL_WRITE } from "../effects.js";
14
14
  import { itemOf, listOf, renderAnswer, snakeKeys } from "../envelope.js";
15
+ import { commandAt } from "../steps.js";
15
16
  import { jobsCommand } from "./backfill-command.js";
16
17
  import { messengerContext } from "./context.js";
17
18
  import { admitPassword, linesOf, openFolder, runFileFor, saveManifest, sealRun, writeChanges } from "./export-folder.js";
@@ -30,6 +31,33 @@ export const storeCommand = (messenger) => {
30
31
  .addCommand(clearCommand(messenger));
31
32
  for (const command of storeMaintenanceCommands(messenger))
32
33
  store.addCommand(command);
34
+ const examples = {
35
+ backup: "./backup.db",
36
+ check: "",
37
+ clear: "--left --dry-run",
38
+ "copies delete": "messages_copy_1",
39
+ decrypt: "./backup.enc --output ./restored.db",
40
+ export: '"Book club" --output ./messages.jsonl',
41
+ "gaps plan": '"Book club"',
42
+ "gaps repair": '"Book club" --limit 100',
43
+ info: "",
44
+ "jobs cancel": "job-101",
45
+ "jobs clear": "",
46
+ "jobs list": "",
47
+ "jobs retry": "--failed",
48
+ "jobs show": "",
49
+ migrate: "",
50
+ reindex: "",
51
+ repair: "--dry-run",
52
+ reset: "",
53
+ restore: "./backup.db",
54
+ status: '"Book club"',
55
+ };
56
+ for (const [path, args] of Object.entries(examples)) {
57
+ const command = commandAt(store, path);
58
+ if (command)
59
+ annotate(command, { examples: [`${messenger.app.command} personal store ${path} ${args}`.trimEnd()] });
60
+ }
33
61
  return store;
34
62
  };
35
63
  /**
@@ -137,6 +137,35 @@ export const contactsCommand = (messenger) => {
137
137
  if (hasMore)
138
138
  context.renderer.note("cut at --limit; a larger one shows more");
139
139
  });
140
+ annotate(contacts.command("prepare"), {
141
+ effects: REMOTE_READ,
142
+ examples: [`${messenger.app.command} contacts prepare Ana`],
143
+ })
144
+ .summary("what to know before you talk to someone, each item with its reference")
145
+ .description("before you talk to someone: what contacts context gives, plus the one-to-one chats where they wrote last, " +
146
+ "open tasks, accepted facts, your notes and their recent meetings, each with its reference; never connects")
147
+ .argument("<person>", "their id, @username, or part of their name")
148
+ .option("--limit <n>", `at most this many messages, and items in each section; ${CONTEXT_MESSAGES} if not given`, positiveCount("--limit"))
149
+ .option("--since-time <time>", "no message or meeting older than this ISO 8601 time, or 2h / 1d ago")
150
+ .action(async function (person) {
151
+ const { limit, sinceTime } = this.opts();
152
+ const context = messengerContext(this, messenger);
153
+ const window = dateWindow(sinceTime === undefined ? {} : { sinceTime });
154
+ const found = await context.withServices((services) => services.people.prepare(person, {
155
+ messages: limit ?? CONTEXT_MESSAGES,
156
+ notes: levelFor(context.settings.permissions, "contacts.notes.list").level !== "deny",
157
+ ...(window.since === undefined ? {} : { since: window.since }),
158
+ }));
159
+ const { hasMore, ...rest } = found;
160
+ renderAnswer(this, context, itemOf(snakeKeys(rest), {
161
+ next: syncsFirst(messenger)
162
+ ? found.notRead
163
+ .filter((chat) => chat.provider === messenger.provider)
164
+ .map((chat) => nextStep("messages.sync", { args: { chat: chat.chatId } }))
165
+ : [],
166
+ meta: { ...window.meta, ...(hasMore ? { truncated: true } : {}) },
167
+ }));
168
+ });
140
169
  annotate(contacts.command("timeline"), { effects: REMOTE_READ })
141
170
  .description("everything one person took part in, in every messenger linked to them — messages they wrote or were " +
142
171
  "mentioned in, chats, mail, meetings, tasks — newest first, from the store; never connects")
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare const conversationExamples: (root: Command, tool: string) => void;
@@ -0,0 +1,25 @@
1
+ import { annotate } from "@wirecat/cli-base/commands";
2
+ import { commandAt } from "../steps.js";
3
+ export const conversationExamples = (root, tool) => {
4
+ const examples = {
5
+ "batches next": '--chat "Book club"',
6
+ "batches status": '--chat "Book club"',
7
+ build: '--chat "Book club"',
8
+ "consents list": "",
9
+ "consents revoke": '--chat "Book club"',
10
+ "embed clear": '--chat "Book club"',
11
+ "embed status": '--chat "Book club"',
12
+ "links add": '--batch "$batch"',
13
+ "links clear": '--chat "Book club"',
14
+ list: '"Book club"',
15
+ related: '"Book club" 101',
16
+ show: '"Book club" 101',
17
+ status: "",
18
+ };
19
+ for (const [path, args] of Object.entries(examples)) {
20
+ const command = commandAt(root, path);
21
+ if (command)
22
+ annotate(command, { examples: [`${tool} personal conversations ${path} ${args}`.trimEnd()] });
23
+ }
24
+ commandAt(root, "links add")?.addHelpText("after", "\nSet batch to the id from conversations batches next; supply the JSON answer to that batch on stdin.\n");
25
+ };
@@ -21,6 +21,7 @@ import { nextStep, rootOf, runnable, stepLine } from "../steps.js";
21
21
  import { answerOf as askOwner } from "./ask.js";
22
22
  import { messengerContext, refuseLocalWrite } from "./context.js";
23
23
  import { conversationShown, foundConversations, readinessList, refreshedAnswer } from "./conversation-answers.js";
24
+ import { conversationExamples } from "./conversation-examples.js";
24
25
  import { messageItems } from "./message-items.js";
25
26
  import { syncOptions, syncRequest } from "./search-sync-options.js";
26
27
  import { readAll } from "./stdin.js";
@@ -363,6 +364,7 @@ export const conversationsCommand = (messenger) => {
363
364
  renderAnswer(this, context, itemOf({ revoked: true }));
364
365
  });
365
366
  conversations.addCommand(embed);
367
+ conversationExamples(conversations, messenger.app.command);
366
368
  return conversations;
367
369
  };
368
370
  /**
@@ -52,7 +52,10 @@ export const mcpCommand = (messenger) => {
52
52
  const { serveOverStdio } = await import("../../mcp/server.js");
53
53
  await serveOverStdio(this, context, messenger, {});
54
54
  });
55
- command.addCommand(withFlags(annotate(new Command("config"), { effects: LOCAL_READ }).description("print the mcpServers entry for Claude Desktop, Cursor and others, with full paths; writes nothing")).action(async function () {
55
+ command.addCommand(withFlags(annotate(new Command("config"), {
56
+ effects: LOCAL_READ,
57
+ examples: [`${app.command} personal mcp config`],
58
+ }).description("print the mcpServers entry for Claude Desktop, Cursor and others, with full paths; writes nothing")).action(async function () {
56
59
  const flags = this.optsWithGlobals();
57
60
  const context = messengerContext(this, messenger);
58
61
  const { settings, env } = context;
@@ -67,7 +70,10 @@ export const mcpCommand = (messenger) => {
67
70
  // Pasted into a file, so a person gets the client's own JSON, indented; a script finds it under `config`.
68
71
  renderAnswer(this, context, itemOf({ config: entry.config }, { next: entry.nodeBound ? [nextStep("mcp.config", { args: {} })] : [] }), () => JSON.stringify(entry.config, null, 2));
69
72
  }));
70
- command.addCommand(withFlags(grantsAuthority(annotate(new Command("setup"), { effects: LOCAL_WRITE }))
73
+ command.addCommand(withFlags(grantsAuthority(annotate(new Command("setup"), {
74
+ effects: LOCAL_WRITE,
75
+ examples: [`${app.command} personal mcp setup codex --allow-writes`],
76
+ }))
71
77
  .description("add this profile's local MCP server to Codex or Claude Code")
72
78
  .argument("<client>", "codex or claude-code")
73
79
  .option("--allow-writes", "acknowledge that this profile offers writing tools")).action(async function (client) {
@@ -98,7 +104,10 @@ export const mcpCommand = (messenger) => {
98
104
  throw new CliError("validation_error", error instanceof Error ? error.message : "MCP setup failed");
99
105
  }
100
106
  }));
101
- command.addCommand(withFlags(annotate(new Command("doctor"), { effects: LOCAL_READ }).description("check this profile's local MCP handshake and tool list")).action(async function () {
107
+ command.addCommand(withFlags(annotate(new Command("doctor"), {
108
+ effects: LOCAL_READ,
109
+ examples: [`${app.command} personal mcp doctor`],
110
+ }).description("check this profile's local MCP handshake and tool list")).action(async function () {
102
111
  const context = messengerContext(this, messenger);
103
112
  const { settings, env } = context;
104
113
  const given = environmentOf(this).mcp ?? {};
@@ -57,9 +57,10 @@ const downloaded = (body) => "item" in body ? `${body.item.id}: downloaded and w
57
57
  const modelLine = (item, width) => `${item.default ? "*" : " "} ${item.id.padEnd(width)} ${megabytes(item.size_bytes).padStart(7)} ` +
58
58
  (item.downloaded ? "downloaded" : "—".padEnd(10));
59
59
  export const modelsCommand = (messenger) => {
60
+ const tool = `${messenger.app.command} personal`;
60
61
  const models = annotate(new Command("models"), { mcp: false }).description("models that run on this machine");
61
62
  const audio = models.command("audio").description("speech models for transcribing voice messages");
62
- withQuery(annotate(audio.command("list"), { effects: LOCAL_READ }), MODELS_QUERY)
63
+ withQuery(annotate(audio.command("list"), { effects: LOCAL_READ, examples: [`${tool} models audio list`] }), MODELS_QUERY)
63
64
  .description("the speech models, most suitable first, which are downloaded, and which one is the default")
64
65
  .action(async function (words) {
65
66
  const context = baseContext(this, messenger.resolveSettings);
@@ -73,7 +74,11 @@ export const modelsCommand = (messenger) => {
73
74
  context.renderer.note(`kept in ${directory}`);
74
75
  });
75
76
  });
76
- reachesOtherService(annotate(audio.command("download"), { effects: FETCH, output: { entity: "model" } }))
77
+ reachesOtherService(annotate(audio.command("download"), {
78
+ effects: FETCH,
79
+ output: { entity: "model" },
80
+ examples: [`${tool} models audio download parakeet-v3`],
81
+ }))
77
82
  .argument("<model>", "a model id from `models audio list`")
78
83
  .description("download a speech model once, checked against the sha256 this version expects")
79
84
  .action(async function (id) {
@@ -101,7 +106,7 @@ export const modelsCommand = (messenger) => {
101
106
  }, { unbounded: true });
102
107
  });
103
108
  const text = models.command("text").description("embedding models for searching conversations by meaning");
104
- withQuery(annotate(text.command("list"), { effects: LOCAL_READ }), MODELS_QUERY)
109
+ withQuery(annotate(text.command("list"), { effects: LOCAL_READ, examples: [`${tool} models text list`] }), MODELS_QUERY)
105
110
  .description("the embedding models, most suitable first, which are downloaded, and which one is the default")
106
111
  .action(async function (words) {
107
112
  const context = baseContext(this, messenger.resolveSettings);
@@ -115,7 +120,11 @@ export const modelsCommand = (messenger) => {
115
120
  context.renderer.note(`kept in ${directory}`);
116
121
  });
117
122
  });
118
- reachesOtherService(annotate(text.command("download"), { effects: FETCH, output: { entity: "model" } }))
123
+ reachesOtherService(annotate(text.command("download"), {
124
+ effects: FETCH,
125
+ output: { entity: "model" },
126
+ examples: [`${tool} models text download ${DEFAULT_TEXT_MODEL}`],
127
+ }))
119
128
  .argument("<model>", "a model id from `models text list`")
120
129
  .addOption(new Option("--accept-terms", "accept the model's licence terms, for a model that has its own"))
121
130
  .description("download an embedding model once, checked against the sha256 this version expects")
@@ -131,7 +140,7 @@ export const modelsCommand = (messenger) => {
131
140
  }, { unbounded: true });
132
141
  });
133
142
  const key = text.command("key").description("API keys for embedding and analysis providers");
134
- grantsAuthority(annotate(key.command("set"), { effects: LOCAL_WRITE }))
143
+ grantsAuthority(annotate(key.command("set"), { effects: LOCAL_WRITE, examples: [`${tool} models text key set openai`] }))
135
144
  .argument("<provider>", "openai, anthropic, or the host of a --base-url server that wants a key")
136
145
  .description("store a key, typed at a hidden prompt or piped on stdin — never as an argument")
137
146
  .action(async function (provider) {
@@ -145,7 +154,7 @@ export const modelsCommand = (messenger) => {
145
154
  renderAnswer(this, context, itemOf({ provider, stored: source }));
146
155
  });
147
156
  });
148
- grantsAuthority(annotate(key.command("remove"), { effects: LOCAL_WRITE }))
157
+ grantsAuthority(annotate(key.command("remove"), { effects: LOCAL_WRITE, examples: [`${tool} models text key remove openai`] }))
149
158
  .argument("<provider>", "openai, anthropic, or a server's host")
150
159
  .description("forget a stored key")
151
160
  .action(async function (provider) {
@@ -22,7 +22,10 @@ export const pollsCommand = (messenger) => {
22
22
  const polls = new Command("polls").description(messenger.pollClose === false
23
23
  ? "read a poll, vote in it, create one"
24
24
  : "read a poll, vote in it, close your own, create one");
25
- annotate(polls.command("show"), { effects: REMOTE_READ })
25
+ annotate(polls.command("show"), {
26
+ effects: REMOTE_READ,
27
+ examples: [`${messenger.app.command} personal polls show "Book club" 101`],
28
+ })
26
29
  .description("a poll and its answer ids, as the message carries it now")
27
30
  .argument("<chat>", messenger.chatArgument)
28
31
  .argument("<message>", "the id of the message that carries the poll")
@@ -35,7 +38,10 @@ export const pollsCommand = (messenger) => {
35
38
  renderAnswer(this, context, itemOf(snakeKeys(poll)));
36
39
  });
37
40
  if (messenger.pollVoters)
38
- polls.addCommand(withQuery(annotate(annotate(new Command("voters"), { effects: REMOTE_READ })
41
+ polls.addCommand(withQuery(annotate(annotate(new Command("voters"), {
42
+ effects: REMOTE_READ,
43
+ examples: [`${messenger.app.command} personal polls voters "Book club" 101 answers:0 --limit 10`],
44
+ })
39
45
  .description(messenger.pollAnonymous === false
40
46
  ? "who voted for what, newest first"
41
47
  : "who voted for what, newest first; not in an anonymous poll")
@@ -60,7 +66,10 @@ export const pollsCommand = (messenger) => {
60
66
  args: { chat, message, ...(words.length > 0 ? { query: words.join(" ") } : {}) },
61
67
  }), lines(({ voted_at, person_id, name, answers }) => [voted_at, name ?? person_id, answers.join(",")].join(" ")));
62
68
  }));
63
- annotate(polls.command("vote"), { effects: REMOTE_WRITE })
69
+ annotate(polls.command("vote"), {
70
+ effects: REMOTE_WRITE,
71
+ examples: [`${messenger.app.command} personal polls vote "Book club" 101 0`],
72
+ })
64
73
  .description(messenger.pollAnonymous === false
65
74
  ? "vote in a poll, or take your vote back; the others see it"
66
75
  : "vote in a poll, or take your vote back; the others see it unless the poll is anonymous")
@@ -85,7 +94,10 @@ export const pollsCommand = (messenger) => {
85
94
  renderAnswer(this, context, itemOf(snakeKeys(voted)));
86
95
  });
87
96
  if (messenger.pollClose !== false)
88
- annotate(polls.command("close"), { effects: REMOTE_WRITE })
97
+ annotate(polls.command("close"), {
98
+ effects: REMOTE_WRITE,
99
+ examples: [`${messenger.app.command} personal polls close "Book club" 101`],
100
+ })
89
101
  .description("close your own poll; nobody can vote after that, and it cannot be reopened")
90
102
  .argument("<chat>", messenger.chatArgument)
91
103
  .argument("<message>", "the id of your own message that carries the poll")
@@ -94,7 +106,10 @@ export const pollsCommand = (messenger) => {
94
106
  const closed = await context.withMessenger((connection) => guardedClose(context.guard, connection, { chat, message: message.trim() }));
95
107
  renderAnswer(this, context, itemOf(snakeKeys(closed)));
96
108
  });
97
- const create = annotate(polls.command("create"), { effects: REMOTE_WRITE })
109
+ const create = annotate(polls.command("create"), {
110
+ effects: REMOTE_WRITE,
111
+ examples: [`${messenger.app.command} personal polls create "Book club" "Which day?" Monday Tuesday`],
112
+ })
98
113
  .description(messenger.pollAnonymous === false
99
114
  ? "send a poll to a chat, as a message of its own; everyone sees who voted for what"
100
115
  : "send a poll to a chat, as a message of its own; public unless --anonymous")
@@ -82,7 +82,11 @@ export const callsCommand = (messenger) => {
82
82
  };
83
83
  /** `chats delete` and `chats clear` — for this account only; at the default level they ask first. */
84
84
  export const chatDeletionCommands = (messenger) => [
85
- annotate(new Command("delete"), { effects: REMOTE_WRITE, mcp: false })
85
+ annotate(new Command("delete"), {
86
+ effects: REMOTE_WRITE,
87
+ mcp: false,
88
+ examples: [`${messenger.app.command} personal chats delete "Book club"`],
89
+ })
86
90
  .description("delete a chat from this account; the others in it keep it and its messages")
87
91
  .argument("<chat>", messenger.chatArgument)
88
92
  .addOption(ownerOnlyOption("--allow-dangerous", "go ahead without the question an ask level puts before a deletion"))
@@ -90,7 +94,11 @@ export const chatDeletionCommands = (messenger) => [
90
94
  const context = messengerContext(this, messenger);
91
95
  renderAnswer(this, context, itemOf(snakeKeys(await context.withServices((services) => services.admin.deleteChat(chat)))));
92
96
  }),
93
- annotate(new Command("clear"), { effects: REMOTE_WRITE, mcp: false })
97
+ annotate(new Command("clear"), {
98
+ effects: REMOTE_WRITE,
99
+ mcp: false,
100
+ examples: [`${messenger.app.command} personal chats clear "Book club"`],
101
+ })
94
102
  .description("delete every message in a chat for this account; the others in it keep theirs")
95
103
  .argument("<chat>", messenger.chatArgument)
96
104
  .addOption(ownerOnlyOption("--allow-dangerous", "go ahead without the question an ask level puts before a deletion"))
@@ -6,6 +6,7 @@ import { searchNotesOf } from "../../services/notes-search.js";
6
6
  import { LOCAL_READ, REMOTE_READ } from "../effects.js";
7
7
  import { renderAnswer } from "../envelope.js";
8
8
  import { positiveCount } from "../paging.js";
9
+ import { commandAt } from "../steps.js";
9
10
  import { messengerContext } from "./context.js";
10
11
  import { conversationsSearchCommand } from "./conversations-command.js";
11
12
  import { messagesSearchCommand } from "./messages-search-command.js";
@@ -157,5 +158,16 @@ export const searchCommand = (messenger, { topics = false } = {}) => {
157
158
  .addCommand(conversationsSearchCommand(messenger));
158
159
  if (topics)
159
160
  search.addCommand(topicsSearchCommand(messenger));
161
+ for (const [path, args] of Object.entries({
162
+ all: "draft",
163
+ conversations: '"Reviewing the project draft"',
164
+ messages: "draft --limit 10",
165
+ notes: "draft --limit 10",
166
+ topics: '"Book club" draft',
167
+ })) {
168
+ const command = commandAt(search, path);
169
+ if (command)
170
+ annotate(command, { examples: [`${messenger.app.command} personal search ${path} ${args}`] });
171
+ }
160
172
  return search;
161
173
  };
@@ -181,21 +181,24 @@ export const serverCommand = (messenger, options = {}) => {
181
181
  return ["--idle", idle];
182
182
  };
183
183
  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 })).description("start serve in the background — through the unit if one is installed — and answer once it connects")).action(async function () {
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 () {
185
185
  await start(prepare(this), idleArgs(this));
186
186
  });
187
- destroysNothing(annotate(command.command("stop"), { effects: LOCAL_WRITE }))
187
+ destroysNothing(annotate(command.command("stop"), { effects: LOCAL_WRITE, examples: [`${app.command} personal server stop`] }))
188
188
  .description("stop this profile's serve — through the unit if it runs under one")
189
189
  .action(async function () {
190
190
  await stop(prepare(this));
191
191
  });
192
- withIdle(destroysNothing(annotate(command.command("restart"), { effects: LOCAL_WRITE })).description("stop it and start it again")).action(async function () {
192
+ withIdle(destroysNothing(annotate(command.command("restart"), {
193
+ effects: LOCAL_WRITE,
194
+ examples: [`${app.command} personal server restart`],
195
+ })).description("stop it and start it again")).action(async function () {
193
196
  const args = idleArgs(this);
194
197
  const prepared = prepare(this);
195
198
  await stop({ ...prepared, say: () => { } });
196
199
  await start(prepared, args);
197
200
  });
198
- annotate(command.command("status"), { effects: LOCAL_READ })
201
+ annotate(command.command("status"), { effects: LOCAL_READ, examples: [`${app.command} personal server status`] })
199
202
  .description("whether serve runs for this profile, since when, who started it, and the unit if there is one")
200
203
  .action(async function () {
201
204
  const prepared = prepare(this);
@@ -261,7 +264,10 @@ export const serverCommand = (messenger, options = {}) => {
261
264
  ...flood.deadlines.map((one) => `Asked to wait before ${one.operation}${one.chatId ? ` in chat ${one.chatId}` : ""} until ${clock(one.until)}.`),
262
265
  ], outdated ? [nextStep("server.restart", { args: {} })] : []);
263
266
  });
264
- annotate(command.command("logs"), { effects: LOCAL_READ })
267
+ annotate(command.command("logs"), {
268
+ effects: LOCAL_READ,
269
+ examples: [`${app.command} personal server logs --limit 20`],
270
+ })
265
271
  .description("serve's latest log lines — from the journal under systemd, else its log file")
266
272
  .option("-n, --limit <n>", "how many lines", (value) => wholeNumber(value), 50)
267
273
  .action(async function () {
@@ -272,7 +278,10 @@ export const serverCommand = (messenger, options = {}) => {
272
278
  context.renderer.note(`no log yet for profile ${unit.profile}`);
273
279
  renderAnswer(this, context, wholeList("server logs", text.split("\n").filter(Boolean)), () => text ? readableLog(text.replace(/\n$/, "")) : "");
274
280
  });
275
- grantsAuthority(annotate(command.command("install"), { effects: LOCAL_WRITE }))
281
+ grantsAuthority(annotate(command.command("install"), {
282
+ effects: LOCAL_WRITE,
283
+ examples: [`${app.command} personal server install`],
284
+ }))
276
285
  .description("write a systemd user unit or a launchd agent for this profile; starts nothing")
277
286
  .action(function () {
278
287
  const { context, platform, unit, say } = prepare(this);
@@ -287,7 +296,10 @@ export const serverCommand = (messenger, options = {}) => {
287
296
  `A daily follow-up review is off. To turn it on: ${app.command} ${unit.profile} schedule agent follow-ups claude "${followUpsPrompt(app.command)}" --at 08:30`,
288
297
  ]);
289
298
  });
290
- grantsAuthority(annotate(command.command("uninstall"), { effects: LOCAL_WRITE }))
299
+ grantsAuthority(annotate(command.command("uninstall"), {
300
+ effects: LOCAL_WRITE,
301
+ examples: [`${app.command} personal server uninstall`],
302
+ }))
291
303
  .description("remove this profile's unit; stop it first")
292
304
  .action(async function () {
293
305
  const { context, platform, unit, say } = prepare(this);
@@ -12,6 +12,7 @@ import { EVENTS_DAYS } from "../../services/chats.js";
12
12
  import { dateWindow } from "../../services/moment.js";
13
13
  import { destroysNothing, LOCAL_WRITE } from "../effects.js";
14
14
  import { renderAnswer } from "../envelope.js";
15
+ import { commandAt } from "../steps.js";
15
16
  import { adminStatisticsCommand } from "./admin-statistics-command.js";
16
17
  import { statsCommand as chatStatsCommand } from "./chats-stats-command.js";
17
18
  import { messengerContext } from "./context.js";
@@ -84,5 +85,30 @@ export const statsCommand = (messenger, loadRenderer = chartRenderer) => {
84
85
  context.renderer.note("partial data — the chart shows lower bounds");
85
86
  renderAnswer(this, context, chartAnswer(chart, chartFile, window.meta));
86
87
  });
88
+ const examples = {
89
+ charts: '"Book club" --output ./activity.svg',
90
+ "chats newcomers": '"Book club"',
91
+ "chats retention": '"Book club"',
92
+ "chats show": '"Book club"',
93
+ "contacts evidence": '101 --selection "$selection" --component messages',
94
+ "contacts responses": '--chat "Project room"',
95
+ "contacts top": '--chat "Book club" --limit 10',
96
+ "messages counters refresh": '--chat "Book club" --dry-run',
97
+ "messages counters show": '--chat "Book club" --limit 10',
98
+ "messages discussion": '--chat "Book club"',
99
+ "messages evidence": '"$message" --selection "$selection" --component reactions',
100
+ "messages show": '--chat "Book club" --by day',
101
+ "messages top": '--chat "Book club" --limit 10',
102
+ "messages unanswered": '--chat "Project room"',
103
+ "tasks show": '--chat "Project room"',
104
+ };
105
+ for (const [path, args] of Object.entries(examples)) {
106
+ const command = commandAt(stats, path);
107
+ if (command)
108
+ annotate(command, { examples: [`${messenger.app.command} personal stats ${path} ${args}`] });
109
+ }
110
+ for (const target of ["messages", "contacts"]) {
111
+ commandAt(stats, `${target} evidence`)?.addHelpText("after", `\nSet selection to drilldown.selection from stats ${target} top; copy the matching row's ${target === "messages" ? "message locator into message" : "person id"}.\n`);
112
+ }
87
113
  return stats;
88
114
  };