@bli-cockpit/cli 0.2.222 → 0.2.236

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 (75) hide show
  1. package/dist/agent-plugins-converge.js +182 -0
  2. package/dist/agent-plugins.js +116 -0
  3. package/dist/autostart-darwin-plist.js +2 -2
  4. package/dist/autostart-windows-scripts.js +2 -2
  5. package/dist/commands/agent-door.js +35 -0
  6. package/dist/commands/approvals.js +3 -1
  7. package/dist/commands/brain.js +23 -0
  8. package/dist/commands/ci.js +23 -0
  9. package/dist/commands/docs-export.js +62 -0
  10. package/dist/commands/docs.js +3 -0
  11. package/dist/commands/doctor-access.js +2 -2
  12. package/dist/commands/doctor-agent-plugins.js +82 -0
  13. package/dist/commands/doctor-pipeline.js +2 -2
  14. package/dist/commands/doctor-report.js +1 -0
  15. package/dist/commands/doctor-update.js +2 -2
  16. package/dist/commands/doctor.js +6 -0
  17. package/dist/commands/drills-render.js +63 -0
  18. package/dist/commands/drills.js +152 -0
  19. package/dist/commands/hits.js +83 -0
  20. package/dist/commands/intentions.js +49 -0
  21. package/dist/commands/local-args-docs-export.js +23 -0
  22. package/dist/commands/local-args-tower-admin.js +8 -0
  23. package/dist/commands/local-args-tower-brain-ladder.js +90 -0
  24. package/dist/commands/local-args-tower-brain.js +11 -1
  25. package/dist/commands/local-args-tower-docs-msg.js +9 -3
  26. package/dist/commands/local-args-tower-drills.js +87 -0
  27. package/dist/commands/local-args-tower-hits.js +93 -0
  28. package/dist/commands/local-args-tower-intentions.js +70 -0
  29. package/dist/commands/local-args-tower-logs-search.js +51 -0
  30. package/dist/commands/local-args-tower-logs.js +3 -0
  31. package/dist/commands/local-args-tower-memory.js +67 -1
  32. package/dist/commands/local-args-tower-pages.js +3 -1
  33. package/dist/commands/local-args-tower-people.js +25 -0
  34. package/dist/commands/local-args-tower-usage.js +4 -4
  35. package/dist/commands/local-args-tower-who.js +36 -0
  36. package/dist/commands/local-args-tower.js +8 -1
  37. package/dist/commands/local-args.js +15 -1
  38. package/dist/commands/local-auth.js +5 -2
  39. package/dist/commands/local-command-shapes-auth.js +1 -0
  40. package/dist/commands/local-command-shapes-drills.js +7 -0
  41. package/dist/commands/local-command-shapes-hits.js +5 -0
  42. package/dist/commands/local-help-commands-tower-ops.js +123 -1
  43. package/dist/commands/local-help-commands-tower.js +19 -7
  44. package/dist/commands/local-help-commands.js +3 -0
  45. package/dist/commands/local-help.js +24 -4
  46. package/dist/commands/local-run-ops.js +11 -0
  47. package/dist/commands/local.js +16 -8
  48. package/dist/commands/logs-search.js +45 -0
  49. package/dist/commands/logs.js +3 -0
  50. package/dist/commands/memory-exam.js +47 -0
  51. package/dist/commands/memory-repeats.js +44 -0
  52. package/dist/commands/memory-search-print.js +95 -0
  53. package/dist/commands/memory-source.js +39 -0
  54. package/dist/commands/memory-store.js +18 -30
  55. package/dist/commands/memory-verb-list.js +7 -2
  56. package/dist/commands/notes-reads.js +20 -0
  57. package/dist/commands/notes-writes.js +24 -0
  58. package/dist/commands/notes.js +4 -2
  59. package/dist/commands/people.js +29 -0
  60. package/dist/commands/public-root.js +1 -1
  61. package/dist/commands/spend.js +79 -0
  62. package/dist/commands/sync-heal-agent-plugins.js +46 -0
  63. package/dist/commands/sync-heal.js +6 -0
  64. package/dist/commands/team.js +35 -0
  65. package/dist/commands/usage.js +9 -0
  66. package/dist/commands/who.js +36 -0
  67. package/dist/evidence-upload-direct.js +44 -0
  68. package/dist/evidence-upload-object.js +18 -4
  69. package/dist/evidence-upload-plan.js +1 -0
  70. package/dist/evidence-upload-transport.js +2 -0
  71. package/dist/local-state-config.js +21 -1
  72. package/dist/local-state.js +1 -1
  73. package/dist/process-runner.js +7 -3
  74. package/dist/state-dir-audit.js +1 -0
  75. package/package.json +4 -4
@@ -16,10 +16,11 @@
16
16
  * verbatim, no logic change.
17
17
  */
18
18
  import { parseGlossaryArgs } from "./glossary.js";
19
+ import { parseSpendArgs } from "./spend.js";
19
20
  import { parseRootsArgs } from "./roots.js";
20
21
  import { SURFACE_ALIASES } from "./local-help.js";
21
22
  import { parseAgentRulesArgs, parseAnalyzeArgs, parseAutostartArgs, parseBackfillArgs, parseCleanArgs, parseDoctorArgs, parseInstallArgs, parseLoginArgs, parseMemoryArgs, parseLogoutArgs, parseOnboardArgs, parseReleaseArgs, parseServeArgs, parseSessionsArgs, parseSourcesArgs, parseStartArgs, parseStatusArgs, parseSyncArgs, parseUpdateArgs, } from "./local-args-collector.js";
22
- import { parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseApprovalsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
23
+ import { parseCiArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
23
24
  // `normalizeUrl` has always been part of this module's surface — `local.ts` and
24
25
  // `local-auth.ts` import it from here — so it stays exported from this address
25
26
  // even though it now lives next door. The same goes for the four names the
@@ -76,6 +77,7 @@ export function parseLocalArgs(argv) {
76
77
  return parseModelsArgs(argv.slice(1));
77
78
  case "scout":
78
79
  return parseScoutArgs(argv.slice(1));
80
+ case "ci": return parseCiArgs(argv.slice(1));
79
81
  case "ops":
80
82
  return parseOpsArgs(argv.slice(1));
81
83
  case "slack":
@@ -112,6 +114,8 @@ export function parseLocalArgs(argv) {
112
114
  return parseSourcesArgs(argv.slice(1));
113
115
  case "glossary":
114
116
  return parseGlossaryArgs(argv.slice(1));
117
+ case "spend":
118
+ return parseSpendArgs(argv.slice(1));
115
119
  case "docs":
116
120
  return parseDocsArgs(argv.slice(1));
117
121
  case "msg":
@@ -144,8 +148,18 @@ export function parseLocalArgs(argv) {
144
148
  return parseLogsArgs(argv.slice(1));
145
149
  case "brain":
146
150
  return parseBrainArgs(argv.slice(1));
151
+ case "hits":
152
+ return parseHitsArgs(argv.slice(1));
153
+ case "intentions":
154
+ return parseIntentionsArgs(argv.slice(1));
155
+ case "who":
156
+ return parseWhoArgs(argv.slice(1));
157
+ case "people":
158
+ return parsePeopleArgs(argv.slice(1));
147
159
  case "approvals":
148
160
  return parseApprovalsArgs(argv.slice(1));
161
+ case "drills":
162
+ return parseDrillsArgs(argv.slice(1));
149
163
  case "manager":
150
164
  return parseManagerArgs(argv.slice(1));
151
165
  case "image":
@@ -10,7 +10,7 @@
10
10
  import { execFile } from "node:child_process";
11
11
  import { errorMessage, isInteractiveStdin, readLine, writeLine, settleYesNo, } from "./cli-io.js";
12
12
  import { normalizeUrl } from "./local-args.js";
13
- import { getCollectorRuntimePaths, pairLocalCollector, readLocalCollectorSessionFile, readLocalSessionReference, } from "../local-state.js";
13
+ import { getCollectorRuntimePaths, pairLocalCollector, readLocalCollectorSessionFile, sameDashboardUrl, readLocalSessionReference, } from "../local-state.js";
14
14
  const OTP_CODE_PROMPT = [
15
15
  "Email code needed:",
16
16
  " Check your latest Tower email for a 6- to 10-digit code.",
@@ -254,8 +254,11 @@ export function canReuseOnboardSession(session, claimedOwnerEmail, dashboardUrl)
254
254
  return false;
255
255
  }
256
256
  const expectedDashboardUrl = normalizeUrlForComparison(dashboardUrl);
257
+ // The two production addresses are one deployment: a session paired on the
258
+ // old vercel.app address stays reusable against the new default.
259
+ const sessionDashboardUrl = normalizeUrlForComparison(session.dashboard_url);
257
260
  if (expectedDashboardUrl &&
258
- normalizeUrlForComparison(session.dashboard_url) !== expectedDashboardUrl) {
261
+ !(sessionDashboardUrl && sameDashboardUrl(sessionDashboardUrl, expectedDashboardUrl))) {
259
262
  return false;
260
263
  }
261
264
  return true;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `cockpit drills` command shape (BLI-19259), beside `local-command-shapes-approvals.ts` for the same reason:
3
+ * `local-command-shapes.ts` sits at the readability ceiling.
4
+ *
5
+ * `theme set` is typed as two words and parsed to ONE action word (`theme-set`), the way `rules create` becomes `rules-create`.
6
+ */
7
+ export {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `cockpit hits` command shape (BLI-19257), beside `local-command-shapes-brain.ts` for the same reason:
3
+ * `local-command-shapes.ts` sits at the readability ceiling.
4
+ */
5
+ export {};
@@ -42,7 +42,29 @@ export const TOWER_OPS_COMMAND_HELP = [
42
42
  "--json writes one object to stdout: { ok, kind, source, window, lines, templates[], routine, notes, text, measure };",
43
43
  "a refusal is { ok: false, error, detail } and exits 1. Run list and teaching: docs/runbooks/tower-logs.md.",
44
44
  "cockpit logs keys mint|rotate <source> [--json]: super admin only; prints the source ingest key once. Rotation invalidates the old key.",
45
- "The bli-tower MCP server has the twins: logs_digest and logs_key_mint.",
45
+ "cockpit logs search <project> [--since 30m|6h (default 1h) | --from <iso> [--to <iso>]] [--source gcp|supabase|vercel|railway|deploys|db-host|<label>] [--stream api|postgres|auth]",
46
+ " [--status 500|5xx|500-503] [--method GET] [--route <text>] [--severity error] [--q <text>] [--limit <n>] [--context] [--json]: owner or admin only.",
47
+ " Reads the project's RAW logs live (window at most 24h), scrubs every line, caps the answer (default 100, at most 200) and says \"capped at N of M\".",
48
+ " Nothing is stored. --context adds the application lines that share each request's trace (Google Cloud). A member gets admin_or_brain_only.",
49
+ "The bli-tower MCP server has the twins: logs_digest, logs_key_mint and logs_search.",
50
+ ],
51
+ ],
52
+ [
53
+ "intentions",
54
+ [
55
+ "Usage: cockpit intentions [list] [--status open|overdue|done|dropped|all] [--project <name>] [--limit <n>] [--dashboard-url <url>] [--json]",
56
+ " cockpit intentions show <id> [--json]",
57
+ " cockpit intentions done <id> --evidence \"<what settled it>\" [--by person|agent] [--json]",
58
+ " cockpit intentions drop <id> --evidence \"<why>\" [--by person|agent] [--json]",
59
+ " cockpit intentions reopen <id> [--json]",
60
+ "Prospective memory: what a person or an agent said they would do later, tied to a trigger (a date, a merged PR, a switch, a ticket,",
61
+ "a meeting). Tower reads sessions, meeting notes and messages for such promises, closes the ones a machine can verify (a PR merged,",
62
+ "a switch holding the value, a ticket Done) and tells the brain and the daily DM about the overdue ones. A promise to switch something",
63
+ "on is done only when the switch is on. <id> is the uuid or the 8-character prefix list prints. Closing needs evidence (3 to 300 characters).",
64
+ "A private one (from a private session or a direct message) is visible to its owner only.",
65
+ "--json writes one object to stdout: { ok, text, intentions[], counts, coverage } (list) or { ok, text, intention } (show, done, drop, reopen);",
66
+ "a refusal is { ok: false, error, detail } and exits 1 (invalid_status, intention_not_found_or_unreadable, already_closed, not_paired, ...).",
67
+ "Teaching: docs/architecture/intentions.md. The bli-tower MCP server has the twins: intentions_list, intentions_show, intentions_settle.",
46
68
  ],
47
69
  ],
48
70
  [
@@ -54,6 +76,15 @@ export const TOWER_OPS_COMMAND_HELP = [
54
76
  "Ids are incident UUIDs or BLI issue identifiers. Retry reopens needs_human or not_actionable rows, resets attempts and claims,",
55
77
  "and records who, why and when. --all-needs-human selects only needs_human; --project narrows every selection. Other states are skipped.",
56
78
  "Only members and super admins can retry. One comment is posted per incident issue. MCP twin: brain_retry.",
79
+ "",
80
+ "Ladder (BLI-19250, the Tower owner only): cockpit brain ladder show|promote <class> [--anyway] [--note <text>]|demote <class>|predict \"<question>\"|answer \"<reply>\" [--json]",
81
+ "Before the brain or an agent asks you for a call it writes down what it predicts you will answer; your answer scores it. `show` lists",
82
+ "every decision class with hits of scored calls, trend and whether it is eligible (95% over at least 20 scored). `promote <class>` switches one",
83
+ "class on so the brain may decide those itself; only you run it, nothing promotes automatically, and money, people, destructive and",
84
+ "rls_widening can never be promoted. `--anyway` switches on a class that is not yet eligible. `demote` switches it off again.",
85
+ "`predict` and `answer` are what an agent calls around an ask (--source-kind/--source-ref name the ask; --prediction names the row).",
86
+ "MCP twins: brain_ladder_show, brain_ladder_promote, brain_ladder_demote, brain_ladder_predict, brain_ladder_answer. Teaching: docs/architecture/edward-predictor.md.",
87
+ "",
57
88
  "Tower's incident queue. Every 15 minutes the log-digest cron turns each recurring problem in a project's logs (classes",
58
89
  "our_bug, config_drift, capacity, unknown) into ONE incident with ONE Tower issue; a repeat adds to its count, never a twin.",
59
90
  "A template quiet for 24 hours is marked cleared. This prints, per project, how many incidents are open, being worked,",
@@ -66,6 +97,75 @@ export const TOWER_OPS_COMMAND_HELP = [
66
97
  "Teaching: docs/architecture/tower-brain.md. The bli-tower MCP server has the twin: brain_status.",
67
98
  ],
68
99
  ],
100
+ [
101
+ "who",
102
+ [
103
+ "Usage: cockpit who [<topic>] [--layer frontend|backend|data|infrastructure|docs|tooling] [--limit <1-10>] [--dashboard-url <url>] [--json]",
104
+ "",
105
+ "Who knows what. A team remembers who to ask: this prints, for a project, one layer of it, or a category, the people and the",
106
+ "standing per-repo agent who hold the most recent and most frequent work there, ranked, with the counts of sessions, memories,",
107
+ "tickets, pull requests and commits behind each, and who to ask first. With no topic it prints the top three of every project.",
108
+ "<topic> a project (Tower, Pearl Portal, Kolm, PortPal, ...) or a category label, exact. Several words need no quotes.",
109
+ " Anything else answers unknown_topic with the project names and the nearest category names.",
110
+ "--layer <layer> one layer of the project. Layer and category rankings count only the shared memories Jev filed with that",
111
+ " label; project rankings also count sessions (by transcript start), tickets, pull requests and commits.",
112
+ "--limit <n> holders shown per area, 1 to 10 (default 5).",
113
+ "Identity is the repo's one resolver: an exact match, never a guess. A handle that matches nobody is counted, not ranked.",
114
+ "--json writes one object to stdout: { ok, topic, resolved_as, layer, as_of, run, scope, areas[{ project, category, layer, holders[], route }], text };",
115
+ "a refusal is { ok: false, error, detail } and exits 1 (unknown_topic, no_directory_yet, invalid_layer, not_paired, ...).",
116
+ "Teaching: docs/architecture/who-knows-what.md. The bli-tower MCP server has the twin: who_knows.",
117
+ ],
118
+ ],
119
+ [
120
+ "hits",
121
+ [
122
+ "Usage: cockpit hits [--mine | --person <email|github-login|name> | --agent | --project <id>] [--days <n>] [--patterns] [--learning] [--top <n>] [--dashboard-url <url>] [--json]",
123
+ " cockpit hits timeline [--weeks <n>] [--dashboard-url <url>] [--json]",
124
+ " cockpit hits coach [--dashboard-url <url>] [--json]",
125
+ " cockpit hits approve --scope <team|agents|project:<id>> [--version <n>] [--dashboard-url <url>] [--json]",
126
+ "",
127
+ "The hit ledger. A HIT is one machine-evidenced punch a person or an agent took: a red CI run, a revert, a closed-unmerged PR,",
128
+ "a rolled-back deploy, an incident, a correction. Hits of the same masked failure class (a signature) repeat, and a repeat weighs more.",
129
+ "This prints the heaviest classes, the 5 to 10 root patterns they roll up into (with the share of the weight the top 5 and top 10",
130
+ "cover), and the fighters with the most weight. Over the last --days (default 30). Never a prompt or a transcript line.",
131
+ "Who sees what: an owner or admin reads everyone and defaults to the team; a member reads only their own hits and defaults to --mine.",
132
+ "--person, --agent, --project and the team view are owner or admin only; a member who asks gets owner_or_admin_only.",
133
+ "--mine your own hits (works for everyone).",
134
+ "--person <label> one person, by email, exact GitHub login or exact name (no fuzzy match: unknown_person, ambiguous_person).",
135
+ "--agent the agents' hits. --project <id> one registry project's hits.",
136
+ "--patterns lead with the patterns, ranked by weight, each with its variations.",
137
+ "--learning per-class repeats, days between hits and recovery trend, for --mine or --person.",
138
+ "--top <n> how many classes and patterns to list (default 10, at most 50). --days <n> window (at most 365).",
139
+ "",
140
+ "Timeline: cockpit hits timeline is your weekly review: for each of the last --weeks (default 8, at most 26) YOUR hits, repeat rate,",
141
+ "recovery time, wins, and the levels your prompts were judged at, with the week's low (worst distress) and high (best win). It shows only",
142
+ "the signed-in person's OWN hits, wins and distress levels and nobody else's, for everyone, owners and admins included.",
143
+ "Coach: cockpit hits coach prints the open coach alerts (a spike, a weekly pile-up, a plateau where someone is stuck or is not sparring),",
144
+ "newest first, one plain sentence each. Owner or admin only. It is for the coaches and is never shown to the person it is about.",
145
+ "Approve: cockpit hits approve --scope team approves the proposed pattern list of that scope (the newest version, or --version <n>).",
146
+ "Owner or admin only; a retired or empty list is refused by name.",
147
+ "--json writes one object to stdout: the door's whole answer with raw labels and a text field; a refusal is { ok: false, error, detail } and exits 1.",
148
+ "The bli-tower MCP server has the twins: hits_read, hits_timeline, hits_coach and hits_approve_patterns.",
149
+ ],
150
+ ],
151
+ [
152
+ "people",
153
+ [
154
+ "Usage: cockpit people show [<person>] [--dashboard-url <url>] [--json]",
155
+ "",
156
+ "People models, OWNER ONLY. One profile per principal and teammate, rebuilt weekly from material the whole org can already",
157
+ "read (public Slack channels, Tower channels that are neither direct nor private, tickets, pull requests, shared memories,",
158
+ "team-visible meeting claims; never direct messages, mail, private channels, or anyone's private memories or sessions):",
159
+ "the projects they are active in, open commitments, what they care about, how fast and where they answer, which kinds of asks",
160
+ "they say yes to, and their pronouns only when they stated them (otherwise they/them). Every line carries its as-of date and the",
161
+ "number of items behind it; an empty field says why it is empty.",
162
+ "<person> an exact name, email or person id. Several words need no quotes. No person lists everyone with a profile.",
163
+ "Only the company owner (and the owner's own owner-scoped brain token) is answered; anyone else gets owner_only (403).",
164
+ "--json writes one object to stdout: { ok, person_id, name, built_at, profile, text } (or { ok, people[], text } for the list);",
165
+ "a refusal is { ok: false, error, detail } and exits 1 (owner_only, unknown_person, ambiguous_person, no_profile_yet, not_paired, ...).",
166
+ "Teaching: docs/architecture/people-models.md. The bli-tower MCP server has the twin: people_show.",
167
+ ],
168
+ ],
69
169
  [
70
170
  "approvals",
71
171
  [
@@ -89,6 +189,24 @@ export const TOWER_OPS_COMMAND_HELP = [
89
189
  "approvals_edit, approvals_reject, approvals_rules, approvals_rules_create, approvals_rules_revoke.",
90
190
  ],
91
191
  ],
192
+ [
193
+ "drills",
194
+ [
195
+ "Usage: cockpit drills [list] [--dashboard-url <url>] [--json]",
196
+ "",
197
+ " cockpit drills start open the next drill: the change or command, what it printed, and the question. The cause is NOT shown",
198
+ " cockpit drills answer --confidence <1-5> your answer on stdin (or --text), and how sure you are; then the real cause and the guard are shown",
199
+ " cockpit drills theme your theme and the choices",
200
+ " cockpit drills theme set <theme> [--play <t>] [--watch <t>] [--train <t>] video-game (the default), boxing, muay-thai, crime-drama, anime, sports, or your own words",
201
+ " cockpit drills review your own week: hits, wins, highs, lows, where you have stopped improving, what to practise",
202
+ "A drill replays a real mistake the team already paid for. You answer first, so a wrong answer you were sure about is the one you remember; it comes back 1, 3 and 7 days later.",
203
+ "Each of your top patterns is a boss: pass its drills, then a stretch of your own PRs without taking that hit again. There is no leaderboard; these are your own rows only.",
204
+ "Anything that needs your hit history says so when it is not connected yet, instead of showing a zero.",
205
+ "--home <dir> and --dashboard-url <url> are for tests and staging.",
206
+ "--json writes one object to stdout (the door's body) and nothing else; a refusal is { ok: false, error, detail } and exits 1.",
207
+ "Teaching: docs/architecture/drills.md. The bli-tower MCP server has the twins: drills_list, drills_start, drills_answer, drills_theme, drills_theme_set, drills_review.",
208
+ ],
209
+ ],
92
210
  [
93
211
  "manager",
94
212
  [
@@ -246,6 +364,10 @@ export const TOWER_OPS_COMMAND_HELP = [
246
364
  "Run `cockpit login` first if this machine is not paired.",
247
365
  ],
248
366
  ],
367
+ [
368
+ "ci",
369
+ ["Usage: cockpit ci report [--repo <owner/name>] [--json]", "Reads the daily stored CI report, as-of age and coverage. MCP twin: ci_report."],
370
+ ],
249
371
  [
250
372
  "ops",
251
373
  [
@@ -47,7 +47,7 @@ export const TOWER_COMMAND_HELP = [
47
47
  [
48
48
  "docs",
49
49
  [
50
- "Usage: cockpit docs [list|tree|read <id|slug> [<id|slug>...]|read --parent <id> --bodies|create|update <id>|delete <id|slug> [<id|slug>...]|link <id|slug> <notion-page>|sync-status [<id|slug>]] [flags]",
50
+ "Usage: cockpit docs [list|tree|read <id|slug> [<id|slug>...]|read --parent <id> --bodies|create|update <id>|delete <id|slug> [<id|slug>...]|link <id|slug> <notion-page>|sync-status [<id|slug>]|export <id|slug> [--format md|pdf|docx|txt] [--out <path>]] [flags]",
51
51
  "",
52
52
  "The Tower document library, typed. Bare `cockpit docs` lists every document you may read.",
53
53
  "list [--parent <id>|root] [--query <text> [--word]] [--limit <n>] — every document you may see: id, visibility, body size, slug, title. Metadata only — a list never carries a body, so it stays cheap enough for an agent to call (--json was 604 KB for 155 documents before BLI-3737, and is now a few tens of KB). --json shape: { documents: [{ id, slug, title, visibility, parent_id, source, updated_at, body_chars }] }.",
@@ -67,6 +67,7 @@ export const TOWER_COMMAND_HELP = [
67
67
  "delete <id|slug> [<id|slug>...] — soft-deletes one or more documents (BLI-10555): the browser's own delete, sets deleted_at, never a real SQL DELETE. Recoverable — only a super_admin clearing deleted_at brings it back, there is no self-service undo. Deleting a parent does NOT delete its children; they keep their own parent_id and stay exactly where they were. Several refs resolve against ONE shared list call, then one DELETE each — one bad ref never loses the rest. --json shape: one ref answers { ok, document_id }; several answer { documents: [{ ref, ok, document_id } | { ref, ok: false, error, detail }] }.",
68
68
  "link <id|slug> <notion-url|page-id> — ties the document to a Notion page so the two-way sync moves it both ways. The page is the truth from then on: the next sync pulls it over this copy and keeps the copy as a version. `link <id|slug> --clear` unties it. Refusals: invalid_notion_page, notion_page_already_linked, document_not_writable.",
69
69
  "sync-status [<id|slug>] — every linked document's Notion sync state, or one's: synced (the body is what both sides last agreed on), tower_changed (edited here since; the next write-back pushes it), never_synced (linked, no baseline yet). Tower's half only: a Notion edit shows up at the next pull, not here. Metadata only, never a body.",
70
+ "export <id|slug> [--format md|pdf|docx|txt] [--out <path>] — writes the document as a file (ticket 20694): md is the stored Markdown exactly, txt is readable plain text, docx is a Word file with real headings, lists, tables and links, pdf is a paginated page with the title and last-updated date in the header. Default format md; default path is the document's slug plus the extension in the current directory (--out names a file, or a directory to put it in), and an existing file is replaced. Prints the path; --json shape: { ok, path, bytes, format }. To copy a page's text, `cockpit docs read` already does it.",
70
71
  "A document body is never accepted on the command line — --file (safest on Windows) or a pipe only, same discipline as `cockpit notes paste`.",
71
72
  "--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
72
73
  "A refusal keeps Tower's own reason label — needs_rls_client, document_not_found_or_unreadable, document_not_writable, slug_taken, circular_parent, and so on.",
@@ -207,15 +208,15 @@ export const TOWER_COMMAND_HELP = [
207
208
  [
208
209
  "usage",
209
210
  [
210
- "Usage: cockpit usage people [--by-repo | --by-topic | --by-subject | --by-task] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--since <n>d|<n>h|<iso>] [--detail] [--until <iso>] [--include-automated] [--json]",
211
+ "Usage: cockpit usage people [--by-repo | --by-topic | --by-subject | --by-task | --by-client] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--since <n>d|<n>h|<iso>] [--detail] [--until <iso>] [--include-automated] [--json]",
211
212
  " cockpit usage sessions [--person <exact-name>] [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--tool claude|codex] [--since <window>] [--until <iso>] [--cursor <cursor>] [--limit <n>] [--json]",
212
213
  " cockpit usage session <id> [--from-window <n>] [--json]",
213
214
  " --by-topic groups work types; --by-subject groups open subjects from session summaries.",
214
215
  "",
215
- "--by-topic adds topic rows, including (unlabelled). Choose only one grouping. --by-repo adds project rows under each person (top 10; --all shows every repo). --person me uses your signed-in email.",
216
+ "--by-client (ticket 19258) shows which app each person worked in (Claude Code CLI, desktop or IDE; Codex CLI, desktop, IDE or exec): the share of sessions and of session time, and the median hours from session start to the pull request opened and merged and the active minutes per merged PR, each with its N, compared only within one person and only with 5 or more sessions on each side. --by-topic adds topic rows, including (unlabelled). Choose only one grouping. --by-repo adds project rows under each person (top 10; --all shows every repo). --person me uses your signed-in email.",
216
217
  "Claude Code and Codex usage per person: sessions observed and extracted, tokens (total, output,",
217
218
  "and the input / cache split when the row carries it), and an API list-price equivalent that is",
218
- "labelled as such and is never actual spend. A super_admin sees everyone; a member sees their own row.",
219
+ "labelled as such and is never actual spend. Every signed-in role sees everyone's numbers (a team decision, BLI-13026); `usage sessions` still lists a member only their own sessions.",
219
220
  "--since takes <n>d (1 to 365), <n>h (1 to 8760), or an ISO timestamp; --until an ISO timestamp (default now).",
220
221
  "--detail adds Output, Input, Cache read and Cache creation. Counts use K/M/B; dollars are rounded.",
221
222
  "PRs opened, PRs merged and Commits come from the pull requests Tower mirrors from GitHub, matched to people by GitHub login. n/a means Tower cannot match that person to a login: unknown, not zero. The footer says how many repositories the mirror holds, the range of its pull requests and what matched nobody; --json lists the repositories.",
@@ -364,7 +365,7 @@ export const TOWER_COMMAND_HELP = [
364
365
  [
365
366
  "notes",
366
367
  [
367
- "Usage: cockpit notes [list|show <id>|shelf|shelves|folders|mkdir <path>|rmdir <path>|rename <path>|upload <paths...> [--wait]|paste|share <id>|unshare <id>|move <id>|accounts|connect|detach <id>|sync <id>|sync-status [<id>]|recordings <note-id>|recording <id>] [flags]",
368
+ "Usage: cockpit notes [list|show <id>|shelf|shelves|folders|mkdir <path>|rmdir <path>|rename <path>|upload <paths...> [--wait]|paste|share <id>|unshare <id>|summarize <id>|move <id>|accounts|connect|detach <id>|sync <id>|sync-status [<id>]|recordings <note-id>|recording <id>] [flags]",
368
369
  "Audio uploads: MP3, M4A, WAV, WebM and OGG, up to 1 GB. Over 4 MB (a whole meeting) the file goes straight to storage and is transcribed in the background; --wait then polls for up to 4 hours and prints progress, otherwise for at most 330 seconds.",
369
370
  "",
370
371
  "folders [--tree] [--json]: list folders and visible note counts.",
@@ -381,6 +382,7 @@ export const TOWER_COMMAND_HELP = [
381
382
  "paste [--file <path>] [--name <n>] [--exclude \"<sentence>\"] — put text in. --file <path> is safest (any editor encoding is decoded). Stdin works too: `pbpaste | cockpit notes paste` on macOS; on Windows use PowerShell 7 (`Get-Clipboard | cockpit notes paste`) — PowerShell 5.1 turns non-ASCII into `?` on pipes.",
382
383
  "--exclude carries your own sentence about what to leave out, exactly as the browser's box does.",
383
384
  "share <id> [--yes] — let everyone signed in read the statements that are safe to share. Asks first in a terminal; --yes is required without one, and --json implies --yes.",
385
+ "summarize <id> — write a new summary (decisions, action items, key points) where every claim cites the speaker, timestamp and segment id (s12) it came from; claims the transcript does not support are dropped. Takes up to a minute; earlier summaries are kept. `show <id>` prints the newest one.",
384
386
  "unshare <id> — take it back. Never asks: it only ever narrows who can read.",
385
387
  "move <id> (--to \"<shelf>\"|--clear-shelf) — put it on a different shelf, or take the shelf off.",
386
388
  "place <id> [--apply] — say which shelf this note belongs on and why, from the meeting it is part of, who was in the room and its own name. Moves nothing without --apply, and never overrules a shelf somebody typed.",
@@ -409,13 +411,18 @@ export const TOWER_COMMAND_HELP = [
409
411
  [
410
412
  "memory",
411
413
  [
412
- "Usage: cockpit memory [map|knowledge|rules|search|save|update|forget|private|public|profile|consolidation|chain|requeue|install|status|log] [flags]",
414
+ "Usage: cockpit memory [map|knowledge|rules|search|save|update|forget|private|public|profile|consolidation|chain|source|exam|requeue|install|status|log] [flags]",
413
415
  "",
414
416
  " map [--json] — projects, topics, memory counts and importance from the shared map.",
415
417
  " knowledge [--person <name>] [--project <name>] [--by-project] [--limit <n>] [--json] — ranked project ideas.",
416
418
  " rules [--person <name>] [--project <name>] [--limit <n>] [--json] — standing rules grouped by person.",
417
419
  "The STORE, over the same /api/memory/* doors the bli-memory MCP server calls:",
418
- " search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent]",
420
+ " search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent] [--include-originals|--no-originals] [--include-source|--no-source]",
421
+ " a memory the nightly dream merged keeps its originals, shown under it. A question with a number, version,",
422
+ " date or id (or 'exactly') also searches them, and so does a question about how things were ('used to', 'previously'):",
423
+ " the facts a newer one replaced come back with when they were true. --include-originals does that for any question, --no-originals for none.",
424
+ " --include-source adds, under the top hits, the verbatim stretch of the raw session or meeting each came from;",
425
+ " a question that asks for an exact detail does that for the top two with no flag; --no-source never.",
419
426
  " repeat --container to search more than one space; omit it for this repository's own.",
420
427
  " --limit caps the memories (default 5); up to 3 Tower pages and tickets ride beside",
421
428
  " them whatever it says. For more Tower rows, use `cockpit search`.",
@@ -438,6 +445,11 @@ export const TOWER_COMMAND_HELP = [
438
445
  " Read-only: the pass is a nightly cron, it never deletes a memory, and",
439
446
  " report mode changes nothing on the shelf at all.",
440
447
  " chain <id> the originals behind a consolidated memory (\"from 4 memories by Edward, Vee\").",
448
+ " exam [--night <YYYY-MM-DD>] [--misses] the nightly memory exam (BLI-19249): purity (right answers over graded),",
449
+ " graded of planned, the trend against the last seven nights and the worst project. --misses lists the missed",
450
+ " questions with the reason label. Yours alone: the exam quotes your own sessions, so no one else reads it.",
451
+ " source <id> [--query \"<words>\"] [--chars 200..6000] the raw session or meeting behind a memory: the verbatim",
452
+ " stretch around the fact (3,000 characters by default), as stored. Only what your own session may read.",
441
453
  " Private originals show only to their owner or a super admin; the rest are counted.",
442
454
  " requeue --since <iso> [--until <iso>] [--source hook|harvest] [--reason <label>] [--apply]",
443
455
  " puts finished extractions back in the queue after the store refused their",
@@ -23,6 +23,7 @@ import { TOWER_OPS_COMMAND_HELP } from "./local-help-commands-tower-ops.js";
23
23
  export function localSubcommandHelp(command) {
24
24
  const helpByCommand = new Map([
25
25
  ["glossary", ["Usage: cockpit glossary list|add <wrong> --to <name>|remove <wrong> [--json]", "Quote phrases. Removing a form prevents future corrections of it."]],
26
+ ["spend", ["Usage: cockpit spend memory [--days 1|7|30] [--dashboard-url <url>] [--json]", "Spend by memory operation (write, recall, dream, update_forget, backfill, label, exam, predictor, people, who, intentions, salience, chat, other), ranked top first with list and real dollars, share, trend against the previous window, and how much of the real OpenAI bill the ledger explains. What it does not explain is shown as untagged. A super_admin reads it."]],
26
27
  ["roots", ["Usage: cockpit roots add <dir> [--allow-home-root] [--json]", "Approves a work directory, saves an approval receipt, and adds it to collection. Run cockpit doctor afterward."]],
27
28
  // BLI-3709: the Tower nouns live next door, split along the same seam the
28
29
  // argument parsers already use — see `local-help-commands-tower.ts`.
@@ -191,6 +192,8 @@ export function localSubcommandHelp(command) {
191
192
  " Open to every role; it says so when the view is read-only for you.",
192
193
  " cockpit team github-login add|remove <person> <login> [--json] — exact roster UUID, name or email; super admin or a member managing their own extra logins. History stays.",
193
194
  " cockpit team github-login list <person> [--json] — primary and extra GitHub logins.",
195
+ " cockpit team timezone set <person> <iana-zone> [--json] — super admin only. Sets the zone a person lives their day in, e.g. Asia/Ho_Chi_Minh or America/Vancouver; the person is an exact roster name, email or roster UUID. The word `unknown` clears it. EST, +07:00 and other non-IANA names are refused.",
196
+ " cockpit team timezone list [--json] — super admin only. Everyone on the roster with their zone; `unknown` means nobody set one (readers then use UTC and say so).",
194
197
  " cockpit team invite <email> --role <role> [--team-id <uuid>]",
195
198
  " Admin only. Defaults to your own team; Tower emails them a sign-in link.",
196
199
  " cockpit team role <userId> --role <role> [--yes]",
@@ -52,6 +52,7 @@ export const rootCommandNames = new Set([
52
52
  "model",
53
53
  "models",
54
54
  "scout",
55
+ "ci",
55
56
  "ops",
56
57
  "slack",
57
58
  "settings",
@@ -70,6 +71,7 @@ export const rootCommandNames = new Set([
70
71
  "clean",
71
72
  "sources",
72
73
  "glossary",
74
+ "spend",
73
75
  "docs",
74
76
  "msg",
75
77
  "issue",
@@ -86,7 +88,12 @@ export const rootCommandNames = new Set([
86
88
  "speed",
87
89
  "logs",
88
90
  "brain",
91
+ "hits",
92
+ "intentions",
93
+ "who",
94
+ "people",
89
95
  "approvals",
96
+ "drills",
90
97
  "manager",
91
98
  "image",
92
99
  ...SURFACE_ALIASES.keys(),
@@ -113,16 +120,18 @@ export function localCommandHelp(command) {
113
120
  " cockpit model [show|set <provider:model>] [--json]",
114
121
  " cockpit models [list|show <provider:model>|compare <provider:model> <provider:model> [...]] [--highlight] [--dashboard-url <url>] [--json]",
115
122
  " cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
123
+ " cockpit ci report [--repo <owner/name>] [--json]",
116
124
  " cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
117
125
  " cockpit slack [connect [--workspace bli|blue_pearl] | disconnect --workspace bli|blue_pearl | coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [--person <p>] [--channel <c>] [--query <text> [--word]] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--format compact|full]] [--json]",
118
126
  " cockpit settings [personal [--chat-model <key>] [--brief-model <key>] | switches [set <key> <value>] | models [set --chat <key>] [--memory <id>] | cli-floor [<version>] | env list|set --project <p> --file <f> --content-stdin|delete --id <uuid> [--yes]] [--json]",
119
127
  " cockpit team github-login add|remove <person> <login> | list <person> [--json]",
128
+ " cockpit team timezone set <person> <iana-zone|unknown> | list [--json]",
120
129
  " cockpit team [members | invite <email> --role <role> [--team-id <uuid>] | role <userId> --role <role> [--yes] | device [list] | device revoke <id|name> --reason <label> [--note \"<text>\"] [--yes]] [--json]",
121
130
  " cockpit workbook [<project> [<doc>]] [--section <id>] [--markdown] [--width <n>] [--dashboard-url <url>] [--json]",
122
131
  " cockpit brief [edit|rewrite|history] [--for <person>] [--date <YYYY-MM-DD>] [--delta [--against <YYYY-MM-DD>]] [--version <pageId>] [--tldr|--full] [--versions] [--claims] [--days <n>] [--reason \"<why>\"] [--wait|--no-wait] [--dashboard-url <url>] [--json]",
123
132
  " cockpit brief status [--who <person>] [--render] [--dashboard-url <url>] [--json]",
124
133
  " cockpit correct --claim <claimId> --text \"<what is wrong>\" [--for <person>] [--version <pageId>] [--supersedes <id>] [--dashboard-url <url>] [--json]",
125
- " cockpit notes [list|show <id>|shelf|shelves|folders|mkdir <path>|rmdir <path>|rename <path>|upload <paths...> [--wait]|paste|share <id>|unshare <id>|move <id>|place <id>] [--folder <path>] [--series <shelf>] [--kind <kind>] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--file <path>] [--name <n>] [--exclude \"<sentence>\"] [--to \"<shelf>\"|--clear-shelf] [--apply] [--yes] [--dashboard-url <url>] [--json]",
134
+ " cockpit notes [list|show <id>|shelf|shelves|folders|mkdir <path>|rmdir <path>|rename <path>|upload <paths...> [--wait]|paste|share <id>|unshare <id>|summarize <id>|move <id>|place <id>] [--folder <path>] [--series <shelf>] [--kind <kind>] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--file <path>] [--name <n>] [--exclude \"<sentence>\"] [--to \"<shelf>\"|--clear-shelf] [--apply] [--yes] [--dashboard-url <url>] [--json]",
126
135
  " cockpit notes [accounts [--show-webhook <account-id>]|connect --provider fellow --workspace <sub> --key-stdin [--name <n>]|connect --provider wisprflow|recordings <note-id>|recording <id>|webhook <account-id> --secret-stdin|detach <account-id>|sync <account-id>] [--json] (the key and the signing secret are read from stdin, never a flag)",
127
136
  " cockpit backfill (--since-days <n>|--all) [--source codex|claude] [--dry-run] [--max-files <n>] [--max-depth <n>] [--max-repos <n>] [--lock-wait <seconds>] [--yes] [--workspace <path>] [--json]",
128
137
  " cockpit status [--workspace <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
@@ -130,7 +139,7 @@ export function localCommandHelp(command) {
130
139
  " cockpit serve [--port <port>] [--workspace <path>]",
131
140
  " cockpit autostart [install|uninstall|status] [--workspace <path>] [--dashboard-url <url>] [--interval-seconds <n>] [--json]",
132
141
  " cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--workspace <path>] [--json]",
133
- " cockpit memory [search \"<question>\"|save|update <id>|forget <id>|profile|install|status|log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"] [--container <tag>] [--limit <n>] [--no-recent] [--custom-id <id>] [--extract] [--content-stdin] [--reason <label>] [--file <path>] [--dashboard-url <url>] [--dry-run] [--json]",
142
+ " cockpit memory [search \"<question>\"|save|update <id>|forget <id>|profile|install|status|log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"] [--container <tag>] [--limit <n>] [--no-recent] [--include-originals|--no-originals] [--include-source|--no-source] [--custom-id <id>] [--extract] [--content-stdin] [--reason <label>] [--file <path>] [--dashboard-url <url>] [--dry-run] [--json]",
134
143
  " cockpit clean [--dry-run] [--all-committed] [--reconcile] [--dashboard-url <url>] [--json]",
135
144
  " cockpit sources check [--older-than <days>] [--dashboard-url <url>] [--json]",
136
145
  " cockpit docs [list|tree|read <id|slug>|create --title <t>|update <id>|delete <id|slug>] [--parent <id>|root|--clear-parent] [--query <text> [--word]] [--limit <n>] [--visibility org|private] [--file <path>|--body-stdin] [--allow-empty] [--dashboard-url <url>] [--json]",
@@ -145,10 +154,20 @@ export function localCommandHelp(command) {
145
154
  " cockpit release [--dry-run] [--skip-checks] [--no-floor] [--tag <tag>] [--access <public|restricted>] [--otp <code>]",
146
155
  " cockpit careers [list|show <id>|rescreen <id>|send-takehome <id>|decide <id> --decision <passed_on|archived|reopen>|followup <id> [--record-sent-at <iso>]|interview <id> <--at <iso>|--clear>|grade <id> [--now]|resume <id> [--out <path>]|takehome show <role>|takehome set <role> --body-file <path>|settings show|settings set --enabled <true|false> --followup-enabled <true|false> --followup-after-days <n> --takehome-window-hours <n>] [--role <slug>] [--min-score <n>] [--since <date>] [--stage <s>] [--force] [--json]",
147
156
  " cockpit glossary list|add <wrong> --to <name>|remove <wrong> [--dashboard-url <url>] [--json]",
157
+ " cockpit spend memory [--days 1|7|30] [--dashboard-url <url>] [--json]",
148
158
  " cockpit rename [list|resolve <id> --scope <page|everywhere|undo>] [--dashboard-url <url>] [--json]",
149
159
  " cockpit logs <collector|dashboard|railway|railway:<service>|-> [--since 15m|1h|2d|<iso>] [--raw <template-id> [--limit <n>]] [--all-levels] [--table <file>] [--json]",
160
+ " cockpit intentions [list [--status open|overdue|done|dropped|all] [--project <name>] [--limit <n>]|show <id>|done <id> --evidence <text>|drop <id> --evidence <text>|reopen <id>] [--dashboard-url <url>] [--json]",
161
+ " cockpit who [<topic>] [--layer frontend|backend|data|infrastructure|docs|tooling] [--limit <1-10>] [--dashboard-url <url>] [--json]",
162
+ " cockpit people show [<person>] [--dashboard-url <url>] [--json]",
150
163
  " cockpit brain status [--project <id>] [--show open|running|needs_human|fixed|not_actionable|cleared] [--dashboard-url <url>] [--json]",
164
+ " cockpit brain ladder <show|promote <class> [--anyway]|demote <class>|predict \"<question>\"|answer \"<reply>\" --prediction <id>> [--json]",
165
+ " cockpit hits [--mine | --person <email|github-login|name> | --agent | --project <id>] [--days <n>] [--patterns] [--learning] [--top <n>] [--dashboard-url <url>] [--json]",
166
+ " cockpit hits timeline [--weeks <n>] [--dashboard-url <url>] [--json]",
167
+ " cockpit hits coach [--dashboard-url <url>] [--json]",
168
+ " cockpit hits approve --scope <team|agents|project:<id>> [--version <n>] [--dashboard-url <url>] [--json]",
151
169
  " cockpit approvals [list|show <id>|create|approve <id> [--edit]|edit <id> [--approve]|reject <id>|rules [--active true|false]|rules create <card-id> [--any-audience] [--label <text>]|rules revoke <rule-id> [--reason <text>]] [--status <s>] [--limit <n>] [--before <cursor>] [--dashboard-url <url>] [--json]",
170
+ " cockpit drills [list|start|answer --confidence <1-5> [--text <words>] [--attempt <id>]|theme|theme set <theme> [--play <t>] [--watch <t>] [--train <t>]|review] [--dashboard-url <url>] [--json]",
152
171
  " cockpit manager <clients|client <client>|run <run-id>|list <client>|queue <client>|show <id>|add <client> --title <t>|edit <id> --version <v>|done|reopen|link|approve|dismiss|proposal|archive|unarchive> [--timeline] [--dashboard-url <url>] [--json]",
153
172
  " cockpit image <describe <file> [--session <id>]|show <sha256-or-prefix>> [--dashboard-url <url>] [--json]",
154
173
  " cockpit setup [--since <7d|30d|date>] [--details] [--person <name|email>] [--compare <name,name>] [--json]",
@@ -162,7 +181,7 @@ export function localCommandHelp(command) {
162
181
  " cockpit messages [same verbs and flags as cockpit msg]",
163
182
  " cockpit meeting-notes [same verbs and flags as cockpit notes]",
164
183
  " cockpit speed [--page <name>]... [--runs <n>] [--dashboard-url <url>] [--json]",
165
- " cockpit usage people [--by-repo | --by-topic | --by-subject | --by-task] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--detail] [--since <n>d|<n>h|<iso>] [--until <iso>] [--include-automated] [--dashboard-url <url>] [--json]",
184
+ " cockpit usage people [--by-repo | --by-topic | --by-subject | --by-task | --by-client] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--detail] [--since <n>d|<n>h|<iso>] [--until <iso>] [--include-automated] [--dashboard-url <url>] [--json]",
166
185
  "",
167
186
  `Default dashboard: ${DEFAULT_DASHBOARD_URL}. Omit --dashboard-url for normal production use; pass it only for staging/custom dashboards or to force a different pairing.`,
168
187
  "--quiet on any command (or COCKPIT_QUIET=1) drops the `[tower client] responded {...}` trace line from stderr; --json already implies it. A failure still logs — only that success-path trace goes quiet (BLI-10450).",
@@ -180,7 +199,8 @@ export function suggestCommand(word) {
180
199
  const distance = editDistance(typed, name);
181
200
  if (distance > 2 || distance >= name.length)
182
201
  continue;
183
- if (!best || distance < best.distance)
202
+ // A tie goes to the name closest in length to what was typed, so `wrok` is `work` and not the shorter `who`.
203
+ if (!best || distance < best.distance || (distance === best.distance && Math.abs(name.length - typed.length) < Math.abs(best.name.length - typed.length)))
184
204
  best = { name, distance };
185
205
  }
186
206
  return best?.name ?? null;
@@ -0,0 +1,11 @@
1
+ import { runScout } from "./scout.js";
2
+ import { runCi } from "./ci.js";
3
+ import { runOps } from "./ops.js";
4
+ /** Dispatch the operational report commands. */
5
+ export function runOpsCommand(command, io) {
6
+ switch (command.kind) {
7
+ case "scout": return runScout(command, io);
8
+ case "ci": return runCi(command, io);
9
+ case "ops": return runOps(command, io);
10
+ }
11
+ }
@@ -1,4 +1,6 @@
1
+ import { runOpsCommand } from "./local-run-ops.js";
1
2
  import { runGlossary } from "./glossary.js";
3
+ import { runSpend } from "./spend.js";
2
4
  import { defaultIo, errorMessage, writeLine } from "./cli-io.js";
3
5
  import { isLocalHelpRequest, localCommandHelp, rootCommandNames } from "./local-help.js";
4
6
  import { beginStage, endStage } from "../crash-guard.js";
@@ -20,8 +22,6 @@ import { runStatus } from "./status.js";
20
22
  import { runSessions } from "./sessions.js";
21
23
  import { runJarvis } from "./jarvis.js";
22
24
  import { runModels } from "./models.js";
23
- import { runScout } from "./scout.js";
24
- import { runOps } from "./ops.js";
25
25
  import { runSlack } from "./slack.js";
26
26
  import { runModel, runSettings } from "./settings.js";
27
27
  import { runTeam } from "./team.js";
@@ -54,14 +54,16 @@ import { runSetup } from "./setup.js";
54
54
  import { runUsage } from "./usage.js";
55
55
  import { runSpeed } from "./speed.js";
56
56
  import { runLogs } from "./logs.js";
57
- import { runApprovals } from "./approvals.js";
57
+ import { runApprovals, runDrills } from "./approvals.js";
58
58
  import { runManager } from "./manager.js";
59
59
  import { runImage } from "./image.js";
60
60
  import { runBrain } from "./brain.js";
61
+ import { runHits } from "./hits.js";
62
+ import { runIntentions } from "./intentions.js";
63
+ import { runWho } from "./who.js";
64
+ import { runPeople } from "./people.js";
61
65
  import { parseLocalArgs } from "./local-args.js";
62
- // `./local.js` is the published entry point for this command surface: the
63
- // public CLI's generated root, commands/root.ts, doctor.ts and the test suite
64
- // all import from here. Splitting the file must not move a name off it.
66
+ // `./local.js` is the published entry point (the public CLI root, root.ts, doctor.ts, tests): a split must not move a name off it.
65
67
  export { rootCommandNames, localCommandHelp, SURFACE_ALIASES, unknownCommandJson, unknownCommandLine, } from "./local-help.js";
66
68
  export { buildCollectorHeartbeat, collectionRootLabel, collectionRootLabels, sendCollectorHeartbeatBestEffort, } from "./heartbeat.js";
67
69
  export { classifySyncFailureRecords, classifySyncHealthError, redactedSyncErrorDetail, reportInstallEventsBestEffort, SYNC_ERROR_DETAIL_MAX_CHARS, } from "./install-receipts.js";
@@ -171,9 +173,9 @@ export async function runLocalCockpitCli(argv, io = { ...defaultIo(), runDoctorA
171
173
  case "models":
172
174
  return await runModels(command, io);
173
175
  case "scout":
174
- return await runScout(command, io);
176
+ case "ci":
175
177
  case "ops":
176
- return await runOps(command, io);
178
+ return await runOpsCommand(command, io);
177
179
  case "slack":
178
180
  return await runSlack(command, io);
179
181
  case "settings":
@@ -229,6 +231,7 @@ export async function runLocalCockpitCli(argv, io = { ...defaultIo(), runDoctorA
229
231
  return await runSourcesCheck(command, io);
230
232
  case "glossary":
231
233
  return await runGlossary(command, io);
234
+ case "spend": return await runSpend(command, io);
232
235
  case "docs": return await runDocs(command, io);
233
236
  case "msg": return await runMsg(command, io);
234
237
  case "issue": return await runIssue(command, io);
@@ -245,7 +248,12 @@ export async function runLocalCockpitCli(argv, io = { ...defaultIo(), runDoctorA
245
248
  case "speed": return await runSpeed(command, io);
246
249
  case "logs": return await runLogs(command, io);
247
250
  case "brain": return await runBrain(command, io);
251
+ case "hits": return await runHits(command, io);
252
+ case "intentions": return await runIntentions(command, io);
253
+ case "who": return await runWho(command, io);
254
+ case "people": return await runPeople(command, io);
248
255
  case "approvals": return await runApprovals(command, io);
256
+ case "drills": return await runDrills(command, io);
249
257
  case "manager": return await runManager(command, io);
250
258
  case "image": return await runImage(command, io);
251
259
  }
@@ -0,0 +1,45 @@
1
+ import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor } from "./agent-door.js";
2
+ import { writeLine } from "./cli-io.js";
3
+ const TAG = "[logs search cli]";
4
+ const READ_DEADLINE_MS = 65_000;
5
+ export async function runLogsSearch(command, io) {
6
+ const search = command.search;
7
+ let door;
8
+ try {
9
+ door = await openAgentDoor("logs search", command, io);
10
+ }
11
+ catch (error) {
12
+ const detail = error instanceof Error ? error.message : "this machine is not paired with Tower";
13
+ writeLine(io.stderr, `${TAG} refused ${JSON.stringify({ reason: "not_paired" })}`);
14
+ if (command.json)
15
+ writeLine(io.stdout, JSON.stringify({ ok: false, error: "not_paired", detail }));
16
+ else
17
+ writeLine(io.stderr, detail);
18
+ return 1;
19
+ }
20
+ const query = new URLSearchParams({ project: command.source });
21
+ const set = (name, value) => value && query.set(name, value);
22
+ set("source", search.logSource);
23
+ set("stream", search.stream);
24
+ set("since", search.since);
25
+ set("from", search.from);
26
+ set("to", search.to);
27
+ set("status", search.status);
28
+ set("method", search.method);
29
+ set("route", search.route);
30
+ set("severity", search.severity);
31
+ set("q", search.q);
32
+ if (search.limit !== undefined)
33
+ query.set("limit", String(search.limit));
34
+ if (search.context)
35
+ query.set("context", "1");
36
+ const answer = await askAgentDoor(door, { path: `/api/logs/search?${query.toString()}`, method: "GET", label: "logs search", timeoutMs: READ_DEADLINE_MS });
37
+ if (!answer.ok)
38
+ return failAgentDoor(door, TAG, answer.reason, answer.detail);
39
+ const body = answer.body;
40
+ writeLine(io.stderr, `${TAG} answered ${JSON.stringify({ project: command.source, source: body.source, matched: body.matched, returned: body.returned, capped: body.capped })}`);
41
+ if (command.json)
42
+ return emitAgentDoor(door, body);
43
+ writeLine(io.stdout, body.text ?? "");
44
+ return 0;
45
+ }