coolhand-cli 0.11.1 → 0.13.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 (195) hide show
  1. package/README.md +13 -1
  2. package/dist/api/client-file-client.d.ts +19 -0
  3. package/dist/api/client-file-client.d.ts.map +1 -0
  4. package/dist/api/client-file-client.js +47 -0
  5. package/dist/api/client-file-client.js.map +1 -0
  6. package/dist/api/help-center-client.d.ts +26 -0
  7. package/dist/api/help-center-client.d.ts.map +1 -0
  8. package/dist/api/help-center-client.js +90 -0
  9. package/dist/api/help-center-client.js.map +1 -0
  10. package/dist/api/last-sync.d.ts.map +1 -1
  11. package/dist/api/last-sync.js +2 -1
  12. package/dist/api/last-sync.js.map +1 -1
  13. package/dist/api/workload-client.d.ts +16 -0
  14. package/dist/api/workload-client.d.ts.map +1 -0
  15. package/dist/api/workload-client.js +28 -0
  16. package/dist/api/workload-client.js.map +1 -0
  17. package/dist/auth/callback-server.d.ts.map +1 -1
  18. package/dist/auth/callback-server.js +13 -1
  19. package/dist/auth/callback-server.js.map +1 -1
  20. package/dist/auto-update/index.d.ts +61 -0
  21. package/dist/auto-update/index.d.ts.map +1 -0
  22. package/dist/auto-update/index.js +355 -0
  23. package/dist/auto-update/index.js.map +1 -0
  24. package/dist/auto-update/select.d.ts +28 -0
  25. package/dist/auto-update/select.d.ts.map +1 -0
  26. package/dist/auto-update/select.js +45 -0
  27. package/dist/auto-update/select.js.map +1 -0
  28. package/dist/auto-update/state.d.ts +25 -0
  29. package/dist/auto-update/state.d.ts.map +1 -0
  30. package/dist/auto-update/state.js +81 -0
  31. package/dist/auto-update/state.js.map +1 -0
  32. package/dist/cli.d.ts.map +1 -1
  33. package/dist/cli.js +525 -5
  34. package/dist/cli.js.map +1 -1
  35. package/dist/commands/capture-turn.d.ts +18 -0
  36. package/dist/commands/capture-turn.d.ts.map +1 -0
  37. package/dist/commands/capture-turn.js +152 -0
  38. package/dist/commands/capture-turn.js.map +1 -0
  39. package/dist/commands/download-client-file.d.ts +9 -0
  40. package/dist/commands/download-client-file.d.ts.map +1 -0
  41. package/dist/commands/download-client-file.js +71 -0
  42. package/dist/commands/download-client-file.js.map +1 -0
  43. package/dist/commands/fetch-log.d.ts.map +1 -1
  44. package/dist/commands/fetch-log.js +9 -0
  45. package/dist/commands/fetch-log.js.map +1 -1
  46. package/dist/commands/flush-pending.d.ts.map +1 -1
  47. package/dist/commands/flush-pending.js +1 -30
  48. package/dist/commands/flush-pending.js.map +1 -1
  49. package/dist/commands/get-help-article.d.ts +3 -0
  50. package/dist/commands/get-help-article.d.ts.map +1 -0
  51. package/dist/commands/get-help-article.js +49 -0
  52. package/dist/commands/get-help-article.js.map +1 -0
  53. package/dist/commands/get-template.d.ts.map +1 -1
  54. package/dist/commands/get-template.js +18 -1
  55. package/dist/commands/get-template.js.map +1 -1
  56. package/dist/commands/link-feedback.d.ts +5 -0
  57. package/dist/commands/link-feedback.d.ts.map +1 -0
  58. package/dist/commands/link-feedback.js +89 -0
  59. package/dist/commands/link-feedback.js.map +1 -0
  60. package/dist/commands/list-client-files.d.ts +3 -0
  61. package/dist/commands/list-client-files.d.ts.map +1 -0
  62. package/dist/commands/list-client-files.js +55 -0
  63. package/dist/commands/list-client-files.js.map +1 -0
  64. package/dist/commands/list-help-categories.d.ts +3 -0
  65. package/dist/commands/list-help-categories.d.ts.map +1 -0
  66. package/dist/commands/list-help-categories.js +47 -0
  67. package/dist/commands/list-help-categories.js.map +1 -0
  68. package/dist/commands/search-help-articles.d.ts +3 -0
  69. package/dist/commands/search-help-articles.d.ts.map +1 -0
  70. package/dist/commands/search-help-articles.js +73 -0
  71. package/dist/commands/search-help-articles.js.map +1 -0
  72. package/dist/commands/search-logs.d.ts.map +1 -1
  73. package/dist/commands/search-logs.js +26 -0
  74. package/dist/commands/search-logs.js.map +1 -1
  75. package/dist/commands/search-templates.d.ts.map +1 -1
  76. package/dist/commands/search-templates.js +12 -0
  77. package/dist/commands/search-templates.js.map +1 -1
  78. package/dist/commands/search-workloads.d.ts +3 -0
  79. package/dist/commands/search-workloads.d.ts.map +1 -0
  80. package/dist/commands/search-workloads.js +79 -0
  81. package/dist/commands/search-workloads.js.map +1 -0
  82. package/dist/commands/update-client-file.d.ts +3 -0
  83. package/dist/commands/update-client-file.d.ts.map +1 -0
  84. package/dist/commands/update-client-file.js +39 -0
  85. package/dist/commands/update-client-file.js.map +1 -0
  86. package/dist/commands/update.d.ts +9 -0
  87. package/dist/commands/update.d.ts.map +1 -0
  88. package/dist/commands/update.js +63 -0
  89. package/dist/commands/update.js.map +1 -0
  90. package/dist/config.d.ts +2 -0
  91. package/dist/config.d.ts.map +1 -1
  92. package/dist/config.js +25 -7
  93. package/dist/config.js.map +1 -1
  94. package/dist/errors.d.ts +1 -1
  95. package/dist/errors.d.ts.map +1 -1
  96. package/dist/errors.js.map +1 -1
  97. package/dist/format-cost.d.ts +9 -0
  98. package/dist/format-cost.d.ts.map +1 -0
  99. package/dist/format-cost.js +22 -0
  100. package/dist/format-cost.js.map +1 -0
  101. package/dist/hooks/adapters/claude.d.ts +12 -0
  102. package/dist/hooks/adapters/claude.d.ts.map +1 -0
  103. package/dist/hooks/adapters/claude.js +98 -0
  104. package/dist/hooks/adapters/claude.js.map +1 -0
  105. package/dist/hooks/adapters/copilot.d.ts +15 -0
  106. package/dist/hooks/adapters/copilot.d.ts.map +1 -0
  107. package/dist/hooks/adapters/copilot.js +96 -0
  108. package/dist/hooks/adapters/copilot.js.map +1 -0
  109. package/dist/hooks/adapters/index.d.ts +5 -0
  110. package/dist/hooks/adapters/index.d.ts.map +1 -0
  111. package/dist/hooks/adapters/index.js +8 -0
  112. package/dist/hooks/adapters/index.js.map +1 -0
  113. package/dist/hooks/adapters/shared.d.ts +25 -0
  114. package/dist/hooks/adapters/shared.d.ts.map +1 -0
  115. package/dist/hooks/adapters/shared.js +37 -0
  116. package/dist/hooks/adapters/shared.js.map +1 -0
  117. package/dist/hooks/disabled.d.ts +3 -0
  118. package/dist/hooks/disabled.d.ts.map +1 -0
  119. package/dist/hooks/disabled.js +5 -0
  120. package/dist/hooks/disabled.js.map +1 -0
  121. package/dist/hooks/hook-log.d.ts +7 -0
  122. package/dist/hooks/hook-log.d.ts.map +1 -0
  123. package/dist/hooks/hook-log.js +34 -0
  124. package/dist/hooks/hook-log.js.map +1 -0
  125. package/dist/hooks/identity.d.ts +21 -0
  126. package/dist/hooks/identity.d.ts.map +1 -0
  127. package/dist/hooks/identity.js +121 -0
  128. package/dist/hooks/identity.js.map +1 -0
  129. package/dist/hooks/log-turn.d.ts +23 -0
  130. package/dist/hooks/log-turn.d.ts.map +1 -0
  131. package/dist/hooks/log-turn.js +48 -0
  132. package/dist/hooks/log-turn.js.map +1 -0
  133. package/dist/hooks/send-turn.d.ts +32 -0
  134. package/dist/hooks/send-turn.d.ts.map +1 -0
  135. package/dist/hooks/send-turn.js +305 -0
  136. package/dist/hooks/send-turn.js.map +1 -0
  137. package/dist/hooks/silence.d.ts +7 -0
  138. package/dist/hooks/silence.d.ts.map +1 -0
  139. package/dist/hooks/silence.js +22 -0
  140. package/dist/hooks/silence.js.map +1 -0
  141. package/dist/hooks/stdin.d.ts +11 -0
  142. package/dist/hooks/stdin.d.ts.map +1 -0
  143. package/dist/hooks/stdin.js +51 -0
  144. package/dist/hooks/stdin.js.map +1 -0
  145. package/dist/hooks/turn-state.d.ts +40 -0
  146. package/dist/hooks/turn-state.d.ts.map +1 -0
  147. package/dist/hooks/turn-state.js +97 -0
  148. package/dist/hooks/turn-state.js.map +1 -0
  149. package/dist/hooks/types.d.ts +74 -0
  150. package/dist/hooks/types.d.ts.map +1 -0
  151. package/dist/hooks/types.js +3 -0
  152. package/dist/hooks/types.js.map +1 -0
  153. package/dist/logger.d.ts.map +1 -1
  154. package/dist/logger.js +12 -1
  155. package/dist/logger.js.map +1 -1
  156. package/dist/pending-store.d.ts +22 -2
  157. package/dist/pending-store.d.ts.map +1 -1
  158. package/dist/pending-store.js +25 -3
  159. package/dist/pending-store.js.map +1 -1
  160. package/dist/pending-upload.d.ts +12 -0
  161. package/dist/pending-upload.d.ts.map +1 -0
  162. package/dist/pending-upload.js +51 -0
  163. package/dist/pending-upload.js.map +1 -0
  164. package/dist/sessions/blocks.d.ts +51 -0
  165. package/dist/sessions/blocks.d.ts.map +1 -0
  166. package/dist/sessions/blocks.js +193 -0
  167. package/dist/sessions/blocks.js.map +1 -0
  168. package/dist/sessions/claude-scanner.d.ts +60 -1
  169. package/dist/sessions/claude-scanner.d.ts.map +1 -1
  170. package/dist/sessions/claude-scanner.js +277 -1
  171. package/dist/sessions/claude-scanner.js.map +1 -1
  172. package/dist/sessions/copilot-scanner.d.ts +27 -0
  173. package/dist/sessions/copilot-scanner.d.ts.map +1 -0
  174. package/dist/sessions/copilot-scanner.js +171 -0
  175. package/dist/sessions/copilot-scanner.js.map +1 -0
  176. package/dist/sessions/redact-secrets.d.ts.map +1 -1
  177. package/dist/sessions/redact-secrets.js +49 -10
  178. package/dist/sessions/redact-secrets.js.map +1 -1
  179. package/dist/sessions/turn-envelope.d.ts +62 -0
  180. package/dist/sessions/turn-envelope.d.ts.map +1 -0
  181. package/dist/sessions/turn-envelope.js +182 -0
  182. package/dist/sessions/turn-envelope.js.map +1 -0
  183. package/dist/sessions/usage.d.ts +23 -0
  184. package/dist/sessions/usage.d.ts.map +1 -0
  185. package/dist/sessions/usage.js +56 -0
  186. package/dist/sessions/usage.js.map +1 -0
  187. package/dist/skills/skill-scanner.d.ts.map +1 -1
  188. package/dist/skills/skill-scanner.js +3 -1
  189. package/dist/skills/skill-scanner.js.map +1 -1
  190. package/dist/types.d.ts +107 -0
  191. package/dist/types.d.ts.map +1 -1
  192. package/dist/types.js.map +1 -1
  193. package/dist/version.d.ts +2 -2
  194. package/dist/version.js +1 -1
  195. package/package.json +7 -4
package/dist/cli.js CHANGED
@@ -9,13 +9,22 @@ import { run as runClients } from './commands/clients.js';
9
9
  import { run as runSearchOptimizations } from './commands/search-optimizations.js';
10
10
  import { run as runGetOptimization } from './commands/get-optimization.js';
11
11
  import { run as runCloseOptimization } from './commands/close-optimization.js';
12
+ import { run as runUpdate } from './commands/update.js';
13
+ import { run as runAutoUpdate, maybeAutoUpdate } from './auto-update/index.js';
12
14
  import { run as runUpdateOptimization } from './commands/update-optimization.js';
13
15
  import { run as runWildcard } from './commands/wildcard.js';
16
+ import { run as runCaptureTurn } from './commands/capture-turn.js';
17
+ import { isCaptureDisabled } from './hooks/disabled.js';
18
+ import { logHookEvent } from './hooks/hook-log.js';
19
+ import { HOOK_AGENTS } from './hooks/types.js';
14
20
  import { run as runClaude } from './commands/claude.js';
15
21
  import { run as runMonitor } from './commands/monitor.js';
16
22
  import { run as runAnalyzeClaudeSessions } from './commands/analyze-claude-sessions.js';
17
23
  import { run as runMapClaudeProjects } from './commands/map-claude-projects.js';
18
24
  import { run as runUploadClientFile } from './commands/upload-client-file.js';
25
+ import { run as runListClientFiles } from './commands/list-client-files.js';
26
+ import { run as runDownloadClientFile } from './commands/download-client-file.js';
27
+ import { run as runUpdateClientFile } from './commands/update-client-file.js';
19
28
  import { run as runSyncSkills } from './commands/sync-skills.js';
20
29
  import { run as runListWorkloads } from './commands/list-workloads.js';
21
30
  import { run as runGetWorkload } from './commands/get-workload.js';
@@ -23,15 +32,20 @@ import { run as runUpdateWorkload } from './commands/update-workload.js';
23
32
  import { run as runFetchLog } from './commands/fetch-log.js';
24
33
  import { run as runSearchLogs } from './commands/search-logs.js';
25
34
  import { run as runSearchTemplates } from './commands/search-templates.js';
35
+ import { run as runSearchWorkloads } from './commands/search-workloads.js';
26
36
  import { run as runGetTemplate } from './commands/get-template.js';
27
37
  import { run as runFlushPending, spawnBackgroundFlush } from './commands/flush-pending.js';
28
38
  import { run as runSearchFeedback } from './commands/search-feedback.js';
29
39
  import { run as runGetFeedback } from './commands/get-feedback.js';
40
+ import { run as runSearchHelpArticles } from './commands/search-help-articles.js';
41
+ import { run as runGetHelpArticle } from './commands/get-help-article.js';
42
+ import { run as runListHelpCategories } from './commands/list-help-categories.js';
43
+ import { run as runLinkFeedback } from './commands/link-feedback.js';
30
44
  import { run as runSearchReferencedFiles } from './commands/search-referenced-files.js';
31
45
  import { run as runListReferencedFileSessions } from './commands/list-referenced-file-sessions.js';
32
46
  import { countPending, flushFailed } from './pending-store.js';
33
47
  import { confirm } from './prompt.js';
34
- const BOOLEAN_FLAGS = new Set(['all', 'help', 'h', 'json', 'version', 'v', 'dry-run', 'include-archived', 'include-system', 'include-templates', 'full', 'matched', 'unmatched', 'include-thinking', 'unmatched-only', 'include-prompts', 'include-deprecated', 'force']);
48
+ const BOOLEAN_FLAGS = new Set(['all', 'help', 'h', 'json', 'version', 'v', 'dry-run', 'include-archived', 'include-system', 'include-templates', 'full', 'matched', 'unmatched', 'include-thinking', 'unmatched-only', 'include-prompts', 'include-total', 'include-metrics', 'no-metrics', 'include-deprecated', 'force', 'check', 'anonymous', 'no-declare']);
35
49
  /** Flags whose repeated occurrences accumulate into an array instead of overwriting. */
36
50
  const REPEATABLE_FLAGS = new Set(['project', 'exclude-project', 'skill', 'exclude-skill', 'source']);
37
51
  const COMMANDS = [
@@ -67,6 +81,15 @@ const COMMANDS = [
67
81
  { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
68
82
  ],
69
83
  },
84
+ {
85
+ name: 'update',
86
+ oneLiner: 'Upgrade to the newest stable release that is at least 48 hours old',
87
+ usage: 'coolhand update [options]',
88
+ options: [
89
+ { flag: '--check', description: 'Report the available version without installing it' },
90
+ { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
91
+ ],
92
+ },
70
93
  {
71
94
  name: 'whoami',
72
95
  oneLiner: 'Show the currently configured client',
@@ -152,6 +175,20 @@ const COMMANDS = [
152
175
  { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
153
176
  ],
154
177
  },
178
+ {
179
+ name: 'capture-turn',
180
+ oneLiner: 'Agent hook: send the turn that just finished, with the conversation so far, to Coolhand',
181
+ usage: 'coolhand capture-turn --agent claude|copilot [--skill NAME] [options] (reads the hook JSON on stdin)',
182
+ options: [
183
+ { flag: '--agent claude|copilot', description: 'Which agent is calling the hook (required)' },
184
+ { flag: '--skill NAME', description: 'Skill that enabled the hook; sent as metadata (required for --agent copilot, which sends only chats that invoked it)' },
185
+ { flag: '--api-key KEY', description: 'Public API key only, never a private key (or set COOLHAND_API_KEY). No login needed' },
186
+ { flag: '--base-url URL', description: 'Coolhand base URL (default: https://coolhandlabs.com)' },
187
+ { flag: '--skill-version V', description: "The skill's version; sent as metadata.skill_version" },
188
+ { flag: '--no-declare', description: "Don't declare a workload for the skill (by default `--skill NAME` declares one workload per skill)" },
189
+ { flag: '--anonymous', description: 'Anonymous machine id instead of the git email or COOLHAND_CAPTURE_USER; also omits the working directory' },
190
+ ],
191
+ },
155
192
  {
156
193
  name: 'search-feedback',
157
194
  oneLiner: 'Search and filter feedback records (requires a private key)',
@@ -172,6 +209,17 @@ const COMMANDS = [
172
209
  { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
173
210
  ],
174
211
  },
212
+ {
213
+ name: 'link-feedback',
214
+ oneLiner: 'Link feedback records to an optimization in bulk (requires a private key)',
215
+ usage: 'coolhand link-feedback <optimization-id> [feedback-id...] [--file PATH|--file=-] [--note TEXT] [options]',
216
+ options: [
217
+ { flag: '--file PATH', description: 'Read feedback ids from a file (whitespace/comma separated); use --file=- for stdin' },
218
+ { flag: '--note TEXT', description: 'Note attached to every link created' },
219
+ { flag: '--client-id ID', description: 'Use a specific stored client' },
220
+ { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
221
+ ],
222
+ },
175
223
  {
176
224
  name: 'get-feedback',
177
225
  oneLiner: 'Get a single feedback record by ID (requires a private key)',
@@ -222,6 +270,40 @@ const COMMANDS = [
222
270
  { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
223
271
  ],
224
272
  },
273
+ {
274
+ name: 'list-client-files',
275
+ oneLiner: "List the client's uploaded files, newest first (requires a client admin's private key)",
276
+ usage: 'coolhand list-client-files [options]',
277
+ options: [
278
+ { flag: '--status VALUE', description: 'Filter by status: draft, published, or archived' },
279
+ { flag: '--file-type TYPE', description: 'Filter by type: slide_deck, report, or document' },
280
+ { flag: '--page N', description: 'Page number (default: 1)' },
281
+ { flag: '--per-page N', description: 'Results per page (default: 25, max: 100)' },
282
+ { flag: '--client-id ID', description: 'Use a specific stored client' },
283
+ { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
284
+ ],
285
+ },
286
+ {
287
+ name: 'download-client-file',
288
+ oneLiner: "Download a client file's contents to disk (requires a client admin's private key)",
289
+ usage: 'coolhand download-client-file <file-id> [options]',
290
+ options: [
291
+ { flag: '--output PATH', description: 'Where to write the file; a directory keeps the server-provided filename (default: that filename in the current directory)' },
292
+ { flag: '--force', description: 'Overwrite the destination if it already exists' },
293
+ { flag: '--client-id ID', description: 'Use a specific stored client' },
294
+ { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
295
+ ],
296
+ },
297
+ {
298
+ name: 'update-client-file',
299
+ oneLiner: "Change a client file's status (requires a client admin's private key)",
300
+ usage: 'coolhand update-client-file <file-id> --status <draft|published|archived> [options]',
301
+ options: [
302
+ { flag: '--status VALUE', description: 'New status: draft, published, or archived (required)' },
303
+ { flag: '--client-id ID', description: 'Use a specific stored client' },
304
+ { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
305
+ ],
306
+ },
225
307
  {
226
308
  name: 'sync-skills',
227
309
  oneLiner: 'Upload locally-built Claude skills (SKILL.md files) to Coolhand as client files',
@@ -318,8 +400,15 @@ const COMMANDS = [
318
400
  { flag: '--source-api VALUE', description: "Filter by source API (e.g. 'openai', 'anthropic', 'vertex')" },
319
401
  { flag: '--source-api-result VALUE', description: 'Filter by result status: success, failed, operational, unmatched' },
320
402
  { flag: '--unmatched-only', description: 'Only return logs with no assigned template' },
321
- { flag: '--days-back N', description: 'Limit to logs created in the last N days (unrestricted if omitted)' },
403
+ { flag: '--source-application VALUE', description: 'Filter by exact source_application' },
404
+ { flag: '--project-path PATH', description: 'Filter by exact metadata.project_path' },
405
+ { flag: '--days-back N', description: 'Limit to logs created in the last N days (unrestricted if omitted; ignored with --since/--until)' },
406
+ { flag: '--since TS', description: 'Only logs created at or after this ISO8601 timestamp (replaces --days-back)' },
407
+ { flag: '--until TS', description: 'Only logs created before this ISO8601 timestamp (exclusive; must be after --since)' },
408
+ { flag: '--min-cost USD', description: 'Only logs whose cost is at least this many USD (unpriced logs are excluded; 0 is valid)' },
409
+ { flag: '--order VALUE', description: "Order by cost instead of --sort: 'cost_desc' (highest cost first)" },
322
410
  { flag: '--include-prompts', description: 'Include system_prompt and user_prompt in results (may be large)' },
411
+ { flag: '--include-total', description: 'Ask the backend to compute the exact total_count/total_pages instead of the cheaper lower-bound estimate (costs a COUNT(*) server-side; off by default)' },
323
412
  { flag: '--sort VALUE', description: "Sort expression, e.g. 'created_at desc' (default: newest first)" },
324
413
  { flag: '--page N', description: 'Page number (default: 1)' },
325
414
  { flag: '--per-page N', description: 'Results per page (default: 25, max: 100)' },
@@ -337,16 +426,44 @@ const COMMANDS = [
337
426
  { flag: '--status VALUE', description: 'Filter by status: draft, published, or failure' },
338
427
  { flag: '--include-deprecated', description: 'Include deprecated templates (hidden by default)' },
339
428
  { flag: '--include-system', description: "Include the 'Unmatched' and 'Ignored API Calls' system buckets (hidden by default) — inspect 'Unmatched' when logs are misrouting" },
429
+ { flag: '--include-metrics', description: 'Add a cost and performance metrics object to each template' },
430
+ { flag: '--days-back N', description: 'Metrics window: rolling N days ending now (server default 28, max 365; ignored with --since)' },
431
+ { flag: '--since TS', description: 'Metrics window start, ISO8601 (inclusive; overrides --days-back)' },
432
+ { flag: '--until TS', description: 'Metrics window end, ISO8601 (exclusive; defaults to now)' },
340
433
  { flag: '--page N', description: 'Page number (default: 1)' },
341
434
  { flag: '--per-page N', description: 'Results per page (default: 25, max: 100)' },
342
435
  { flag: '--client-id ID', description: 'Use a specific stored client' },
343
436
  { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
344
437
  ],
345
- }, {
438
+ },
439
+ {
440
+ name: 'search-workloads',
441
+ oneLiner: 'List and search workloads, optionally with cost and performance metrics (requires a private key)',
442
+ usage: 'coolhand search-workloads [options]',
443
+ options: [
444
+ { flag: '--search TEXT', description: 'Case-insensitive substring to match in the workload name' },
445
+ { flag: '--include-archived', description: 'Include archived workloads (hidden by default)' },
446
+ { flag: '--include-system', description: "Include system workloads such as 'Unmatched' (hidden by default)" },
447
+ { flag: '--include-templates', description: "Add each workload's active templates and their routing patterns" },
448
+ { flag: '--include-metrics', description: 'Add a cost and performance metrics object to each workload' },
449
+ { flag: '--days-back N', description: 'Metrics window: rolling N days ending now (server default 28, max 365; ignored with --since)' },
450
+ { flag: '--since TS', description: 'Metrics window start, ISO8601 (inclusive; overrides --days-back)' },
451
+ { flag: '--until TS', description: 'Metrics window end, ISO8601 (exclusive; defaults to now)' },
452
+ { flag: '--page N', description: 'Page number (default: 1)' },
453
+ { flag: '--per-page N', description: 'Results per page (default: 25, max: 100)' },
454
+ { flag: '--client-id ID', description: 'Use a specific stored client' },
455
+ { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
456
+ ],
457
+ },
458
+ {
346
459
  name: 'get-template',
347
- oneLiner: 'Get a single LLM request template by ID, including its prompt patterns (requires a private key)',
460
+ oneLiner: 'Get a single LLM request template by ID, including its prompt patterns and metrics (requires a private key)',
348
461
  usage: 'coolhand get-template <template-id> [options]',
349
462
  options: [
463
+ { flag: '--no-metrics', description: 'Omit the metrics object (included by default)' },
464
+ { flag: '--days-back N', description: 'Metrics window: rolling N days ending now (server default 28, max 365; ignored with --since)' },
465
+ { flag: '--since TS', description: 'Metrics window start, ISO8601 (inclusive; overrides --days-back)' },
466
+ { flag: '--until TS', description: 'Metrics window end, ISO8601 (exclusive; defaults to now)' },
350
467
  { flag: '--client-id ID', description: 'Use a specific stored client' },
351
468
  { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
352
469
  ],
@@ -375,6 +492,40 @@ const COMMANDS = [
375
492
  { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
376
493
  ],
377
494
  },
495
+ {
496
+ name: 'search-help-articles',
497
+ oneLiner: 'Search or list Coolhand help center articles (no login needed)',
498
+ usage: 'coolhand search-help-articles [query] [options]',
499
+ options: [
500
+ { flag: '--query TEXT', description: 'Relevance search: quoted phrases, -exclusions and "or" work (or pass it as positional text). Omit to list every article' },
501
+ { flag: '--category SLUG', description: 'Only articles in one section (see list-help-categories)' },
502
+ { flag: '--page N', description: 'Page number (default: 1)' },
503
+ { flag: '--per-page N', description: 'Results per page (default: 25, max: 100)' },
504
+ { flag: '--base-url URL', description: 'Read from another Coolhand server; the stored key is only sent to the server you logged in to' },
505
+ { flag: '--client-id ID', description: 'Use a specific stored client (optional — reads work anonymously)' },
506
+ { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
507
+ ],
508
+ },
509
+ {
510
+ name: 'get-help-article',
511
+ oneLiner: 'Read one help center article by slug, with its full markdown body (no login needed)',
512
+ usage: 'coolhand get-help-article <slug> [options]',
513
+ options: [
514
+ { flag: '--base-url URL', description: 'Read from another Coolhand server; the stored key is only sent to the server you logged in to' },
515
+ { flag: '--client-id ID', description: 'Use a specific stored client (optional — reads work anonymously)' },
516
+ { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
517
+ ],
518
+ },
519
+ {
520
+ name: 'list-help-categories',
521
+ oneLiner: 'List help center sections and their allowed tags (no login needed)',
522
+ usage: 'coolhand list-help-categories [options]',
523
+ options: [
524
+ { flag: '--base-url URL', description: 'Read from another Coolhand server; the stored key is only sent to the server you logged in to' },
525
+ { flag: '--client-id ID', description: 'Use a specific stored client (optional — reads work anonymously)' },
526
+ { flag: '--json', description: 'Emit JSON output instead of human-readable text' },
527
+ ],
528
+ },
378
529
  {
379
530
  name: 'help',
380
531
  oneLiner: 'Show this message or per-command help',
@@ -587,6 +738,16 @@ function logoutOptions(parsed) {
587
738
  }
588
739
  return opts;
589
740
  }
741
+ function updateOptions(parsed) {
742
+ const opts = {};
743
+ if (parsed.flags.check === true) {
744
+ opts.check = true;
745
+ }
746
+ if (parsed.flags.json === true) {
747
+ opts.json = true;
748
+ }
749
+ return opts;
750
+ }
590
751
  function statusOptions(parsed) {
591
752
  const opts = {};
592
753
  if (typeof parsed.flags['client-id'] === 'string') {
@@ -934,6 +1095,145 @@ function fetchLogOptions(parsed) {
934
1095
  }
935
1096
  return opts;
936
1097
  }
1098
+ const CLIENT_FILE_STATUSES = new Set(['draft', 'published', 'archived']);
1099
+ function parseClientFileStatus(raw) {
1100
+ if (!CLIENT_FILE_STATUSES.has(raw)) {
1101
+ throw new CliError('INVALID_ARGS', '--status must be one of: draft, published, archived');
1102
+ }
1103
+ return raw;
1104
+ }
1105
+ function listClientFilesOptions(parsed) {
1106
+ const opts = {};
1107
+ if (typeof parsed.flags['status'] === 'string') {
1108
+ opts.status = parseClientFileStatus(parsed.flags['status']);
1109
+ }
1110
+ if (typeof parsed.flags['file-type'] === 'string') {
1111
+ if (!CLIENT_FILE_TYPES.has(parsed.flags['file-type'])) {
1112
+ throw new CliError('INVALID_ARGS', '--file-type must be one of: slide_deck, report, document');
1113
+ }
1114
+ opts.fileType = parsed.flags['file-type'];
1115
+ }
1116
+ if (typeof parsed.flags['page'] === 'string') {
1117
+ const raw = parsed.flags['page'];
1118
+ const n = parseInt(raw, 10);
1119
+ if (!/^\d+$/.test(raw) || isNaN(n) || n < 1) {
1120
+ throw new CliError('INVALID_ARGS', '--page must be a positive integer');
1121
+ }
1122
+ opts.page = n;
1123
+ }
1124
+ if (typeof parsed.flags['per-page'] === 'string') {
1125
+ const raw = parsed.flags['per-page'];
1126
+ const n = parseInt(raw, 10);
1127
+ if (!/^\d+$/.test(raw) || isNaN(n) || n < 1 || n > 100) {
1128
+ throw new CliError('INVALID_ARGS', '--per-page must be an integer between 1 and 100');
1129
+ }
1130
+ opts.perPage = n;
1131
+ }
1132
+ if (typeof parsed.flags['client-id'] === 'string') {
1133
+ opts.clientId = parsed.flags['client-id'];
1134
+ }
1135
+ if (parsed.flags.json === true) {
1136
+ opts.json = true;
1137
+ }
1138
+ return opts;
1139
+ }
1140
+ function downloadClientFileOptions(parsed) {
1141
+ const id = parsed.positional[0];
1142
+ if (!id) {
1143
+ throw new CliError('INVALID_ARGS', 'download-client-file requires a <file-id> argument');
1144
+ }
1145
+ const opts = { id };
1146
+ if ('output' in parsed.flags) {
1147
+ const v = parsed.flags['output'];
1148
+ if (typeof v !== 'string' || v.length === 0) {
1149
+ throw new CliError('INVALID_ARGS', '--output requires a path argument');
1150
+ }
1151
+ opts.output = v;
1152
+ }
1153
+ if (parsed.flags['force'] === true) {
1154
+ opts.force = true;
1155
+ }
1156
+ if (typeof parsed.flags['client-id'] === 'string') {
1157
+ opts.clientId = parsed.flags['client-id'];
1158
+ }
1159
+ if (parsed.flags.json === true) {
1160
+ opts.json = true;
1161
+ }
1162
+ return opts;
1163
+ }
1164
+ function updateClientFileOptions(parsed) {
1165
+ const id = parsed.positional[0];
1166
+ if (!id) {
1167
+ throw new CliError('INVALID_ARGS', 'update-client-file requires a <file-id> argument');
1168
+ }
1169
+ const rawStatus = parsed.flags['status'];
1170
+ if (typeof rawStatus !== 'string') {
1171
+ throw new CliError('INVALID_ARGS', 'update-client-file requires --status (draft, published, or archived)');
1172
+ }
1173
+ const opts = { id, status: parseClientFileStatus(rawStatus) };
1174
+ if (typeof parsed.flags['client-id'] === 'string') {
1175
+ opts.clientId = parsed.flags['client-id'];
1176
+ }
1177
+ if (parsed.flags.json === true) {
1178
+ opts.json = true;
1179
+ }
1180
+ return opts;
1181
+ }
1182
+ /** Strict positive integer (`/^\d+$/`, `>= 1`), as `--days-back` has always been parsed. */
1183
+ function positiveIntFlag(parsed, name) {
1184
+ const raw = parsed.flags[name];
1185
+ if (raw === undefined) {
1186
+ return undefined;
1187
+ }
1188
+ const n = typeof raw === 'string' ? parseInt(raw, 10) : NaN;
1189
+ if (typeof raw !== 'string' || !/^\d+$/.test(raw) || isNaN(n) || n < 1) {
1190
+ throw new CliError('INVALID_ARGS', `--${name} must be a positive integer`);
1191
+ }
1192
+ return n;
1193
+ }
1194
+ /** `--per-page`: strict integer between 1 and 100. */
1195
+ function perPageFlag(parsed) {
1196
+ const raw = parsed.flags['per-page'];
1197
+ if (raw === undefined) {
1198
+ return undefined;
1199
+ }
1200
+ const n = typeof raw === 'string' ? parseInt(raw, 10) : NaN;
1201
+ if (typeof raw !== 'string' || !/^\d+$/.test(raw) || isNaN(n) || n < 1 || n > 100) {
1202
+ throw new CliError('INVALID_ARGS', '--per-page must be an integer between 1 and 100');
1203
+ }
1204
+ return n;
1205
+ }
1206
+ /**
1207
+ * ISO8601 `--since`/`--until`: passed straight through, never parsed into a `Date` locally — the
1208
+ * server validates it and a 422 surfaces as a `CliError` with its message.
1209
+ */
1210
+ function timestampFlag(parsed, name) {
1211
+ const raw = parsed.flags[name];
1212
+ if (raw === undefined) {
1213
+ return undefined;
1214
+ }
1215
+ if (typeof raw !== 'string' || raw.length === 0) {
1216
+ throw new CliError('INVALID_ARGS', `--${name} requires an ISO8601 timestamp`);
1217
+ }
1218
+ return raw;
1219
+ }
1220
+ /** The metrics window flags shared by search-templates, get-template and search-workloads. */
1221
+ function metricsWindow(parsed) {
1222
+ const window = {};
1223
+ const daysBack = positiveIntFlag(parsed, 'days-back');
1224
+ if (daysBack !== undefined) {
1225
+ window.daysBack = daysBack;
1226
+ }
1227
+ const since = timestampFlag(parsed, 'since');
1228
+ if (since !== undefined) {
1229
+ window.since = since;
1230
+ }
1231
+ const until = timestampFlag(parsed, 'until');
1232
+ if (until !== undefined) {
1233
+ window.until = until;
1234
+ }
1235
+ return window;
1236
+ }
937
1237
  const TEMPLATE_STATUSES = new Set(['draft', 'published', 'failure']);
938
1238
  function searchTemplatesOptions(parsed) {
939
1239
  const opts = {};
@@ -956,6 +1256,10 @@ function searchTemplatesOptions(parsed) {
956
1256
  if (parsed.flags['include-system'] === true) {
957
1257
  opts.includeSystem = true;
958
1258
  }
1259
+ if (parsed.flags['include-metrics'] === true) {
1260
+ opts.includeMetrics = true;
1261
+ }
1262
+ Object.assign(opts, metricsWindow(parsed));
959
1263
  if (typeof parsed.flags['page'] === 'string') {
960
1264
  const raw = parsed.flags['page'];
961
1265
  const n = parseInt(raw, 10);
@@ -980,12 +1284,63 @@ function searchTemplatesOptions(parsed) {
980
1284
  }
981
1285
  return opts;
982
1286
  }
1287
+ function searchWorkloadsOptions(parsed) {
1288
+ const opts = {};
1289
+ if (typeof parsed.flags['search'] === 'string') {
1290
+ opts.search = parsed.flags['search'];
1291
+ }
1292
+ if (parsed.flags['include-archived'] === true) {
1293
+ opts.includeArchived = true;
1294
+ }
1295
+ if (parsed.flags['include-system'] === true) {
1296
+ opts.includeSystem = true;
1297
+ }
1298
+ if (parsed.flags['include-templates'] === true) {
1299
+ opts.includeTemplates = true;
1300
+ }
1301
+ if (parsed.flags['include-metrics'] === true) {
1302
+ opts.includeMetrics = true;
1303
+ }
1304
+ Object.assign(opts, metricsWindow(parsed));
1305
+ const page = positiveIntFlag(parsed, 'page');
1306
+ if (page !== undefined) {
1307
+ opts.page = page;
1308
+ }
1309
+ const perPage = perPageFlag(parsed);
1310
+ if (perPage !== undefined) {
1311
+ opts.perPage = perPage;
1312
+ }
1313
+ if (typeof parsed.flags['client-id'] === 'string') {
1314
+ opts.clientId = parsed.flags['client-id'];
1315
+ }
1316
+ if (parsed.flags.json === true) {
1317
+ opts.json = true;
1318
+ }
1319
+ return opts;
1320
+ }
983
1321
  function getTemplateOptions(parsed) {
984
1322
  const id = parsed.positional[0];
985
1323
  if (!id) {
986
1324
  throw new CliError('INVALID_ARGS', 'get-template requires a <template-id> argument');
987
1325
  }
988
1326
  const opts = { id };
1327
+ if (parsed.flags['no-metrics'] === true) {
1328
+ opts.noMetrics = true;
1329
+ }
1330
+ Object.assign(opts, metricsWindow(parsed));
1331
+ if (typeof parsed.flags['client-id'] === 'string') {
1332
+ opts.clientId = parsed.flags['client-id'];
1333
+ }
1334
+ if (parsed.flags.json === true) {
1335
+ opts.json = true;
1336
+ }
1337
+ return opts;
1338
+ }
1339
+ function helpCenterReadOptions(parsed) {
1340
+ const opts = {};
1341
+ if (typeof parsed.flags['base-url'] === 'string') {
1342
+ opts.baseUrl = parsed.flags['base-url'];
1343
+ }
989
1344
  if (typeof parsed.flags['client-id'] === 'string') {
990
1345
  opts.clientId = parsed.flags['client-id'];
991
1346
  }
@@ -994,6 +1349,47 @@ function getTemplateOptions(parsed) {
994
1349
  }
995
1350
  return opts;
996
1351
  }
1352
+ function searchHelpArticlesOptions(parsed) {
1353
+ const opts = helpCenterReadOptions(parsed);
1354
+ if (parsed.flags['query'] === true) {
1355
+ throw new CliError('INVALID_ARGS', '--query requires a value (attach a leading-dash query as --query=-text)');
1356
+ }
1357
+ const positionalQuery = parsed.positional.join(' ');
1358
+ if (typeof parsed.flags['query'] === 'string' && positionalQuery.trim()) {
1359
+ throw new CliError('INVALID_ARGS', 'Pass the search text either as --query or as positional text, not both');
1360
+ }
1361
+ const query = typeof parsed.flags['query'] === 'string' ? parsed.flags['query'] : positionalQuery;
1362
+ if (query.trim()) {
1363
+ opts.query = query;
1364
+ }
1365
+ if (typeof parsed.flags['category'] === 'string') {
1366
+ opts.category = parsed.flags['category'];
1367
+ }
1368
+ if (typeof parsed.flags['page'] === 'string') {
1369
+ const raw = parsed.flags['page'];
1370
+ const n = parseInt(raw, 10);
1371
+ if (!/^\d+$/.test(raw) || isNaN(n) || n < 1) {
1372
+ throw new CliError('INVALID_ARGS', '--page must be a positive integer');
1373
+ }
1374
+ opts.page = n;
1375
+ }
1376
+ if (typeof parsed.flags['per-page'] === 'string') {
1377
+ const raw = parsed.flags['per-page'];
1378
+ const n = parseInt(raw, 10);
1379
+ if (!/^\d+$/.test(raw) || isNaN(n) || n < 1 || n > 100) {
1380
+ throw new CliError('INVALID_ARGS', '--per-page must be an integer between 1 and 100');
1381
+ }
1382
+ opts.perPage = n;
1383
+ }
1384
+ return opts;
1385
+ }
1386
+ function getHelpArticleOptions(parsed) {
1387
+ const slug = parsed.positional[0];
1388
+ if (!slug) {
1389
+ throw new CliError('INVALID_ARGS', 'get-help-article requires a <slug> argument');
1390
+ }
1391
+ return { ...helpCenterReadOptions(parsed), slug };
1392
+ }
997
1393
  function searchLogsOptions(parsed) {
998
1394
  const opts = {};
999
1395
  if (typeof parsed.flags['template-id'] === 'string') {
@@ -1028,9 +1424,38 @@ function searchLogsOptions(parsed) {
1028
1424
  }
1029
1425
  opts.daysBack = n;
1030
1426
  }
1427
+ if (typeof parsed.flags['source-application'] === 'string') {
1428
+ opts.sourceApplication = parsed.flags['source-application'];
1429
+ }
1430
+ if (typeof parsed.flags['project-path'] === 'string') {
1431
+ opts.projectPath = parsed.flags['project-path'];
1432
+ }
1433
+ const since = timestampFlag(parsed, 'since');
1434
+ if (since !== undefined) {
1435
+ opts.since = since;
1436
+ }
1437
+ const until = timestampFlag(parsed, 'until');
1438
+ if (until !== undefined) {
1439
+ opts.until = until;
1440
+ }
1441
+ if (parsed.flags['min-cost'] !== undefined) {
1442
+ const raw = parsed.flags['min-cost'];
1443
+ // `0` is a valid floor and must reach the server, so compare against undefined, not falsiness.
1444
+ if (typeof raw !== 'string' || !/^\d+(\.\d+)?$/.test(raw)) {
1445
+ throw new CliError('INVALID_ARGS', '--min-cost must be a non-negative decimal number (USD)');
1446
+ }
1447
+ opts.minCost = parseFloat(raw);
1448
+ }
1449
+ if (typeof parsed.flags['order'] === 'string') {
1450
+ // Not allowlisted here: the server validates it and a 422 surfaces as a CliError.
1451
+ opts.order = parsed.flags['order'];
1452
+ }
1031
1453
  if (parsed.flags['include-prompts'] === true) {
1032
1454
  opts.includePrompts = true;
1033
1455
  }
1456
+ if (parsed.flags['include-total'] === true) {
1457
+ opts.includeTotal = true;
1458
+ }
1034
1459
  if (typeof parsed.flags['sort'] === 'string') {
1035
1460
  opts.sort = parsed.flags['sort'];
1036
1461
  }
@@ -1229,6 +1654,29 @@ function searchFeedbackOptions(parsed) {
1229
1654
  }
1230
1655
  return opts;
1231
1656
  }
1657
+ function linkFeedbackOptions(parsed) {
1658
+ const optimizationId = parsed.positional[0];
1659
+ if (!optimizationId) {
1660
+ throw new CliError('INVALID_ARGS', 'link-feedback requires an <optimization-id> argument');
1661
+ }
1662
+ const opts = { optimizationId, feedbackIds: parsed.positional.slice(1) };
1663
+ if (typeof parsed.flags.file === 'string') {
1664
+ opts.file = parsed.flags.file;
1665
+ }
1666
+ else if (parsed.flags.file === true) {
1667
+ throw new CliError('INVALID_ARGS', 'link-feedback --file requires a path; use --file=- to read ids from stdin');
1668
+ }
1669
+ if (typeof parsed.flags.note === 'string') {
1670
+ opts.note = parsed.flags.note;
1671
+ }
1672
+ if (typeof parsed.flags['client-id'] === 'string') {
1673
+ opts.clientId = parsed.flags['client-id'];
1674
+ }
1675
+ if (parsed.flags.json === true) {
1676
+ opts.json = true;
1677
+ }
1678
+ return opts;
1679
+ }
1232
1680
  function getFeedbackOptions(parsed) {
1233
1681
  const id = parsed.positional[0];
1234
1682
  if (!id) {
@@ -1270,9 +1718,40 @@ function wildcardOptions(parsed) {
1270
1718
  }
1271
1719
  return opts;
1272
1720
  }
1721
+ function captureTurnOptions(parsed) {
1722
+ const agent = parsed.flags.agent;
1723
+ if (typeof agent !== 'string' || !HOOK_AGENTS.includes(agent)) {
1724
+ throw new CliError('INVALID_ARGS', `capture-turn requires --agent ${HOOK_AGENTS.join('|')}`);
1725
+ }
1726
+ const opts = { agent: agent };
1727
+ // `skill` is a repeatable flag, so a doubled `--skill` arrives as an array; the last one wins.
1728
+ const skill = Array.isArray(parsed.flags.skill) ? parsed.flags.skill[parsed.flags.skill.length - 1] : parsed.flags.skill;
1729
+ if (typeof skill === 'string') {
1730
+ opts.skill = skill;
1731
+ }
1732
+ if (typeof parsed.flags['skill-version'] === 'string' && parsed.flags['skill-version'].trim()) {
1733
+ opts.skillVersion = parsed.flags['skill-version'].trim().slice(0, 100);
1734
+ }
1735
+ if (typeof parsed.flags['api-key'] === 'string') {
1736
+ opts.apiKey = parsed.flags['api-key'];
1737
+ }
1738
+ if (typeof parsed.flags['base-url'] === 'string' && parsed.flags['base-url']) {
1739
+ opts.baseUrl = parsed.flags['base-url'];
1740
+ }
1741
+ // `--anonymous=true|1|yes` arrive as strings. Anonymity must fail closed: only an explicit
1742
+ // false/0/no turns it off, and any other value still means anonymous.
1743
+ const anonymous = parsed.flags.anonymous;
1744
+ if (anonymous === true || (typeof anonymous === 'string' && !/^(false|0|no)$/i.test(anonymous))) {
1745
+ opts.anonymous = true;
1746
+ }
1747
+ if (parsed.flags['no-declare'] === true) {
1748
+ opts.declare = false;
1749
+ }
1750
+ return opts;
1751
+ }
1273
1752
  // Commands that must never trigger the failed-flush reminder: the flush itself, and
1274
1753
  // login (which runs its own pending-flush prompt).
1275
- const NO_FLUSH_REMINDER = new Set(['__flush-pending', 'login', 'help', 'version']);
1754
+ const NO_FLUSH_REMINDER = new Set(['__flush-pending', '__auto-update', 'login', 'help', 'version', 'capture-turn']);
1276
1755
  /**
1277
1756
  * Option B retry: if a previous background flush failed, remind the user on their next
1278
1757
  * command and offer to retry. Interactive-only (`confirm` returns false with no TTY), so
@@ -1312,6 +1791,8 @@ export async function run(argv) {
1312
1791
  // Peel a leading --client-id before command dispatch. The `claude` passthrough
1313
1792
  // skips parseArgs entirely, so we extract any global --client-id here first.
1314
1793
  const { clientId: globalClientId, rest: argv2 } = peelClientId(argv);
1794
+ const wantsHelp = argv2.includes('--help') || argv2.includes('-h');
1795
+ await maybeAutoUpdate({ command: wantsHelp ? 'help' : argv2[0], json: argv2.includes('--json') });
1315
1796
  // `claude` is a passthrough wrapper: everything after it goes to the Claude CLI
1316
1797
  // verbatim (e.g. `coolhand claude --resume`), so it must skip the flag parser.
1317
1798
  if (argv2[0] === 'claude') {
@@ -1378,6 +1859,10 @@ export async function run(argv) {
1378
1859
  switch (resolvedCommand) {
1379
1860
  case '__flush-pending':
1380
1861
  return await runFlushPending();
1862
+ case '__auto-update':
1863
+ return await runAutoUpdate();
1864
+ case 'update':
1865
+ return await runUpdate(updateOptions(parsed));
1381
1866
  case 'login':
1382
1867
  return await runLogin(loginOptions(parsed));
1383
1868
  case 'logout':
@@ -1398,8 +1883,29 @@ export async function run(argv) {
1398
1883
  return await runUpdateOptimization(updateOptimizationOptions(parsed));
1399
1884
  case 'wildcard':
1400
1885
  return await runWildcard(wildcardOptions(parsed));
1886
+ case 'capture-turn': {
1887
+ // Runs unattended inside an agent hook: a bad flag must not print into the agent's hook
1888
+ // channel or fail the hook. Only a human at a terminal gets the error.
1889
+ let captureOpts;
1890
+ try {
1891
+ captureOpts = captureTurnOptions(parsed);
1892
+ }
1893
+ catch (err) {
1894
+ if (process.stdin.isTTY) {
1895
+ throw err;
1896
+ }
1897
+ if (isCaptureDisabled()) {
1898
+ return ExitCode.OK;
1899
+ }
1900
+ await logHookEvent(`capture-turn: ${err.message}`);
1901
+ return ExitCode.OK;
1902
+ }
1903
+ return await runCaptureTurn(captureOpts);
1904
+ }
1401
1905
  case 'search-feedback':
1402
1906
  return await runSearchFeedback(searchFeedbackOptions(parsed));
1907
+ case 'link-feedback':
1908
+ return await runLinkFeedback(linkFeedbackOptions(parsed));
1403
1909
  case 'get-feedback':
1404
1910
  return await runGetFeedback(getFeedbackOptions(parsed));
1405
1911
  case 'analyze-claude-sessions':
@@ -1408,6 +1914,12 @@ export async function run(argv) {
1408
1914
  return await runMapClaudeProjects(mapClaudeProjectsOptions(parsed));
1409
1915
  case 'upload-client-file':
1410
1916
  return await runUploadClientFile(uploadClientFileOptions(parsed));
1917
+ case 'list-client-files':
1918
+ return await runListClientFiles(listClientFilesOptions(parsed));
1919
+ case 'download-client-file':
1920
+ return await runDownloadClientFile(downloadClientFileOptions(parsed));
1921
+ case 'update-client-file':
1922
+ return await runUpdateClientFile(updateClientFileOptions(parsed));
1411
1923
  case 'sync-skills':
1412
1924
  return await runSyncSkills(syncSkillsOptions(parsed));
1413
1925
  case 'list-workloads':
@@ -1422,8 +1934,16 @@ export async function run(argv) {
1422
1934
  return await runSearchLogs(searchLogsOptions(parsed));
1423
1935
  case 'search-templates':
1424
1936
  return await runSearchTemplates(searchTemplatesOptions(parsed));
1937
+ case 'search-workloads':
1938
+ return await runSearchWorkloads(searchWorkloadsOptions(parsed));
1425
1939
  case 'get-template':
1426
1940
  return await runGetTemplate(getTemplateOptions(parsed));
1941
+ case 'search-help-articles':
1942
+ return await runSearchHelpArticles(searchHelpArticlesOptions(parsed));
1943
+ case 'get-help-article':
1944
+ return await runGetHelpArticle(getHelpArticleOptions(parsed));
1945
+ case 'list-help-categories':
1946
+ return await runListHelpCategories(helpCenterReadOptions(parsed));
1427
1947
  case 'search-referenced-files':
1428
1948
  return await runSearchReferencedFiles(searchReferencedFilesOptions(parsed));
1429
1949
  case 'list-referenced-file-sessions':