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.
- package/README.md +13 -1
- package/dist/api/client-file-client.d.ts +19 -0
- package/dist/api/client-file-client.d.ts.map +1 -0
- package/dist/api/client-file-client.js +47 -0
- package/dist/api/client-file-client.js.map +1 -0
- package/dist/api/help-center-client.d.ts +26 -0
- package/dist/api/help-center-client.d.ts.map +1 -0
- package/dist/api/help-center-client.js +90 -0
- package/dist/api/help-center-client.js.map +1 -0
- package/dist/api/last-sync.d.ts.map +1 -1
- package/dist/api/last-sync.js +2 -1
- package/dist/api/last-sync.js.map +1 -1
- package/dist/api/workload-client.d.ts +16 -0
- package/dist/api/workload-client.d.ts.map +1 -0
- package/dist/api/workload-client.js +28 -0
- package/dist/api/workload-client.js.map +1 -0
- package/dist/auth/callback-server.d.ts.map +1 -1
- package/dist/auth/callback-server.js +13 -1
- package/dist/auth/callback-server.js.map +1 -1
- package/dist/auto-update/index.d.ts +61 -0
- package/dist/auto-update/index.d.ts.map +1 -0
- package/dist/auto-update/index.js +355 -0
- package/dist/auto-update/index.js.map +1 -0
- package/dist/auto-update/select.d.ts +28 -0
- package/dist/auto-update/select.d.ts.map +1 -0
- package/dist/auto-update/select.js +45 -0
- package/dist/auto-update/select.js.map +1 -0
- package/dist/auto-update/state.d.ts +25 -0
- package/dist/auto-update/state.d.ts.map +1 -0
- package/dist/auto-update/state.js +81 -0
- package/dist/auto-update/state.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +525 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/capture-turn.d.ts +18 -0
- package/dist/commands/capture-turn.d.ts.map +1 -0
- package/dist/commands/capture-turn.js +152 -0
- package/dist/commands/capture-turn.js.map +1 -0
- package/dist/commands/download-client-file.d.ts +9 -0
- package/dist/commands/download-client-file.d.ts.map +1 -0
- package/dist/commands/download-client-file.js +71 -0
- package/dist/commands/download-client-file.js.map +1 -0
- package/dist/commands/fetch-log.d.ts.map +1 -1
- package/dist/commands/fetch-log.js +9 -0
- package/dist/commands/fetch-log.js.map +1 -1
- package/dist/commands/flush-pending.d.ts.map +1 -1
- package/dist/commands/flush-pending.js +1 -30
- package/dist/commands/flush-pending.js.map +1 -1
- package/dist/commands/get-help-article.d.ts +3 -0
- package/dist/commands/get-help-article.d.ts.map +1 -0
- package/dist/commands/get-help-article.js +49 -0
- package/dist/commands/get-help-article.js.map +1 -0
- package/dist/commands/get-template.d.ts.map +1 -1
- package/dist/commands/get-template.js +18 -1
- package/dist/commands/get-template.js.map +1 -1
- package/dist/commands/link-feedback.d.ts +5 -0
- package/dist/commands/link-feedback.d.ts.map +1 -0
- package/dist/commands/link-feedback.js +89 -0
- package/dist/commands/link-feedback.js.map +1 -0
- package/dist/commands/list-client-files.d.ts +3 -0
- package/dist/commands/list-client-files.d.ts.map +1 -0
- package/dist/commands/list-client-files.js +55 -0
- package/dist/commands/list-client-files.js.map +1 -0
- package/dist/commands/list-help-categories.d.ts +3 -0
- package/dist/commands/list-help-categories.d.ts.map +1 -0
- package/dist/commands/list-help-categories.js +47 -0
- package/dist/commands/list-help-categories.js.map +1 -0
- package/dist/commands/search-help-articles.d.ts +3 -0
- package/dist/commands/search-help-articles.d.ts.map +1 -0
- package/dist/commands/search-help-articles.js +73 -0
- package/dist/commands/search-help-articles.js.map +1 -0
- package/dist/commands/search-logs.d.ts.map +1 -1
- package/dist/commands/search-logs.js +26 -0
- package/dist/commands/search-logs.js.map +1 -1
- package/dist/commands/search-templates.d.ts.map +1 -1
- package/dist/commands/search-templates.js +12 -0
- package/dist/commands/search-templates.js.map +1 -1
- package/dist/commands/search-workloads.d.ts +3 -0
- package/dist/commands/search-workloads.d.ts.map +1 -0
- package/dist/commands/search-workloads.js +79 -0
- package/dist/commands/search-workloads.js.map +1 -0
- package/dist/commands/update-client-file.d.ts +3 -0
- package/dist/commands/update-client-file.d.ts.map +1 -0
- package/dist/commands/update-client-file.js +39 -0
- package/dist/commands/update-client-file.js.map +1 -0
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +63 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -7
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/format-cost.d.ts +9 -0
- package/dist/format-cost.d.ts.map +1 -0
- package/dist/format-cost.js +22 -0
- package/dist/format-cost.js.map +1 -0
- package/dist/hooks/adapters/claude.d.ts +12 -0
- package/dist/hooks/adapters/claude.d.ts.map +1 -0
- package/dist/hooks/adapters/claude.js +98 -0
- package/dist/hooks/adapters/claude.js.map +1 -0
- package/dist/hooks/adapters/copilot.d.ts +15 -0
- package/dist/hooks/adapters/copilot.d.ts.map +1 -0
- package/dist/hooks/adapters/copilot.js +96 -0
- package/dist/hooks/adapters/copilot.js.map +1 -0
- package/dist/hooks/adapters/index.d.ts +5 -0
- package/dist/hooks/adapters/index.d.ts.map +1 -0
- package/dist/hooks/adapters/index.js +8 -0
- package/dist/hooks/adapters/index.js.map +1 -0
- package/dist/hooks/adapters/shared.d.ts +25 -0
- package/dist/hooks/adapters/shared.d.ts.map +1 -0
- package/dist/hooks/adapters/shared.js +37 -0
- package/dist/hooks/adapters/shared.js.map +1 -0
- package/dist/hooks/disabled.d.ts +3 -0
- package/dist/hooks/disabled.d.ts.map +1 -0
- package/dist/hooks/disabled.js +5 -0
- package/dist/hooks/disabled.js.map +1 -0
- package/dist/hooks/hook-log.d.ts +7 -0
- package/dist/hooks/hook-log.d.ts.map +1 -0
- package/dist/hooks/hook-log.js +34 -0
- package/dist/hooks/hook-log.js.map +1 -0
- package/dist/hooks/identity.d.ts +21 -0
- package/dist/hooks/identity.d.ts.map +1 -0
- package/dist/hooks/identity.js +121 -0
- package/dist/hooks/identity.js.map +1 -0
- package/dist/hooks/log-turn.d.ts +23 -0
- package/dist/hooks/log-turn.d.ts.map +1 -0
- package/dist/hooks/log-turn.js +48 -0
- package/dist/hooks/log-turn.js.map +1 -0
- package/dist/hooks/send-turn.d.ts +32 -0
- package/dist/hooks/send-turn.d.ts.map +1 -0
- package/dist/hooks/send-turn.js +305 -0
- package/dist/hooks/send-turn.js.map +1 -0
- package/dist/hooks/silence.d.ts +7 -0
- package/dist/hooks/silence.d.ts.map +1 -0
- package/dist/hooks/silence.js +22 -0
- package/dist/hooks/silence.js.map +1 -0
- package/dist/hooks/stdin.d.ts +11 -0
- package/dist/hooks/stdin.d.ts.map +1 -0
- package/dist/hooks/stdin.js +51 -0
- package/dist/hooks/stdin.js.map +1 -0
- package/dist/hooks/turn-state.d.ts +40 -0
- package/dist/hooks/turn-state.d.ts.map +1 -0
- package/dist/hooks/turn-state.js +97 -0
- package/dist/hooks/turn-state.js.map +1 -0
- package/dist/hooks/types.d.ts +74 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +3 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +12 -1
- package/dist/logger.js.map +1 -1
- package/dist/pending-store.d.ts +22 -2
- package/dist/pending-store.d.ts.map +1 -1
- package/dist/pending-store.js +25 -3
- package/dist/pending-store.js.map +1 -1
- package/dist/pending-upload.d.ts +12 -0
- package/dist/pending-upload.d.ts.map +1 -0
- package/dist/pending-upload.js +51 -0
- package/dist/pending-upload.js.map +1 -0
- package/dist/sessions/blocks.d.ts +51 -0
- package/dist/sessions/blocks.d.ts.map +1 -0
- package/dist/sessions/blocks.js +193 -0
- package/dist/sessions/blocks.js.map +1 -0
- package/dist/sessions/claude-scanner.d.ts +60 -1
- package/dist/sessions/claude-scanner.d.ts.map +1 -1
- package/dist/sessions/claude-scanner.js +277 -1
- package/dist/sessions/claude-scanner.js.map +1 -1
- package/dist/sessions/copilot-scanner.d.ts +27 -0
- package/dist/sessions/copilot-scanner.d.ts.map +1 -0
- package/dist/sessions/copilot-scanner.js +171 -0
- package/dist/sessions/copilot-scanner.js.map +1 -0
- package/dist/sessions/redact-secrets.d.ts.map +1 -1
- package/dist/sessions/redact-secrets.js +49 -10
- package/dist/sessions/redact-secrets.js.map +1 -1
- package/dist/sessions/turn-envelope.d.ts +62 -0
- package/dist/sessions/turn-envelope.d.ts.map +1 -0
- package/dist/sessions/turn-envelope.js +182 -0
- package/dist/sessions/turn-envelope.js.map +1 -0
- package/dist/sessions/usage.d.ts +23 -0
- package/dist/sessions/usage.d.ts.map +1 -0
- package/dist/sessions/usage.js +56 -0
- package/dist/sessions/usage.js.map +1 -0
- package/dist/skills/skill-scanner.d.ts.map +1 -1
- package/dist/skills/skill-scanner.js +3 -1
- package/dist/skills/skill-scanner.js.map +1 -1
- package/dist/types.d.ts +107 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- 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: '--
|
|
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':
|