coolhand-cli 0.6.0 → 0.8.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 +22 -2
- package/dist/api/feedback-client.d.ts +17 -0
- package/dist/api/feedback-client.d.ts.map +1 -0
- package/dist/api/feedback-client.js +40 -0
- package/dist/api/feedback-client.js.map +1 -0
- package/dist/api/last-sync.d.ts +8 -6
- package/dist/api/last-sync.d.ts.map +1 -1
- package/dist/api/last-sync.js +27 -16
- package/dist/api/last-sync.js.map +1 -1
- package/dist/bin.js +0 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +194 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze-claude-sessions.d.ts +6 -0
- package/dist/commands/analyze-claude-sessions.d.ts.map +1 -1
- package/dist/commands/analyze-claude-sessions.js +60 -9
- package/dist/commands/analyze-claude-sessions.js.map +1 -1
- package/dist/commands/claude.d.ts +3 -22
- package/dist/commands/claude.d.ts.map +1 -1
- package/dist/commands/claude.js +3 -142
- package/dist/commands/claude.js.map +1 -1
- package/dist/commands/clients.d.ts.map +1 -1
- package/dist/commands/clients.js +2 -1
- package/dist/commands/clients.js.map +1 -1
- package/dist/commands/close-optimization.js +1 -1
- package/dist/commands/close-optimization.js.map +1 -1
- package/dist/commands/get-feedback.d.ts +3 -0
- package/dist/commands/get-feedback.d.ts.map +1 -0
- package/dist/commands/get-feedback.js +70 -0
- package/dist/commands/get-feedback.js.map +1 -0
- package/dist/commands/get-optimization.d.ts.map +1 -1
- package/dist/commands/get-optimization.js +61 -10
- package/dist/commands/get-optimization.js.map +1 -1
- package/dist/commands/mcp-call.d.ts.map +1 -1
- package/dist/commands/mcp-call.js +14 -50
- package/dist/commands/mcp-call.js.map +1 -1
- package/dist/commands/monitor.d.ts +9 -0
- package/dist/commands/monitor.d.ts.map +1 -0
- package/dist/commands/monitor.js +10 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/commands/search-feedback.d.ts +3 -0
- package/dist/commands/search-feedback.d.ts.map +1 -0
- package/dist/commands/search-feedback.js +71 -0
- package/dist/commands/search-feedback.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -1
- 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/parse-when.d.ts +17 -0
- package/dist/parse-when.d.ts.map +1 -0
- package/dist/parse-when.js +49 -0
- package/dist/parse-when.js.map +1 -0
- package/dist/proxy/proxy.d.ts +2 -0
- package/dist/proxy/proxy.d.ts.map +1 -1
- package/dist/proxy/proxy.js +1 -0
- package/dist/proxy/proxy.js.map +1 -1
- package/dist/proxy/sender.d.ts +1 -0
- package/dist/proxy/sender.d.ts.map +1 -1
- package/dist/proxy/sender.js +1 -1
- package/dist/proxy/sender.js.map +1 -1
- package/dist/proxy/wrap-runner.d.ts +36 -0
- package/dist/proxy/wrap-runner.d.ts.map +1 -0
- package/dist/proxy/wrap-runner.js +153 -0
- package/dist/proxy/wrap-runner.js.map +1 -0
- package/dist/sessions/claude-scanner.d.ts +14 -1
- package/dist/sessions/claude-scanner.d.ts.map +1 -1
- package/dist/sessions/claude-scanner.js +205 -41
- package/dist/sessions/claude-scanner.js.map +1 -1
- package/dist/sessions/cowork-scanner.d.ts +2 -0
- package/dist/sessions/cowork-scanner.d.ts.map +1 -1
- package/dist/sessions/cowork-scanner.js +22 -9
- package/dist/sessions/cowork-scanner.js.map +1 -1
- package/dist/sessions/redact-secrets.d.ts +14 -0
- package/dist/sessions/redact-secrets.d.ts.map +1 -0
- package/dist/sessions/redact-secrets.js +47 -0
- package/dist/sessions/redact-secrets.js.map +1 -0
- package/dist/sessions/session-filter.d.ts +28 -0
- package/dist/sessions/session-filter.d.ts.map +1 -0
- package/dist/sessions/session-filter.js +38 -0
- package/dist/sessions/session-filter.js.map +1 -0
- package/dist/types.d.ts +40 -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 +2 -2
- package/dist/commands/accounts.d.ts +0 -3
- package/dist/commands/accounts.d.ts.map +0 -1
- package/dist/commands/accounts.js +0 -59
- package/dist/commands/accounts.js.map +0 -1
- package/dist/commands/add-optimization-comment.d.ts +0 -3
- package/dist/commands/add-optimization-comment.d.ts.map +0 -1
- package/dist/commands/add-optimization-comment.js +0 -28
- package/dist/commands/add-optimization-comment.js.map +0 -1
- package/dist/commands/capture-sessions.d.ts +0 -3
- package/dist/commands/capture-sessions.d.ts.map +0 -1
- package/dist/commands/capture-sessions.js +0 -79
- package/dist/commands/capture-sessions.js.map +0 -1
- package/dist/commands/create-optimization.d.ts +0 -3
- package/dist/commands/create-optimization.d.ts.map +0 -1
- package/dist/commands/create-optimization.js +0 -38
- package/dist/commands/create-optimization.js.map +0 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Requires Node 20 or newer.
|
|
|
22
22
|
## Analyze Claude sessions
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
coolhand analyze-claude-sessions [--dry-run] [--client-id ID] [--json]
|
|
25
|
+
coolhand analyze-claude-sessions [--dry-run] [--client-id ID] [--json] [filter options]
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Upload your historical Claude Code session transcripts to your Coolhand account for analysis. Coolhand analyzes the uploaded conversations to surface:
|
|
@@ -31,7 +31,7 @@ Upload your historical Claude Code session transcripts to your Coolhand account
|
|
|
31
31
|
- **Efficiency gaps** — workflows with unnecessary back-and-forth or redundant steps
|
|
32
32
|
- **Cost insights** — sessions with high token usage relative to their outcome
|
|
33
33
|
|
|
34
|
-
> **What gets uploaded**: The conversation transcripts stored in `~/.claude/projects/` — the messages exchanged between you and Claude, including any code or context you shared in those conversations. Use `--dry-run` to preview exactly what would be sent before submitting anything.
|
|
34
|
+
> **What gets uploaded**: The conversation transcripts stored in `~/.claude/projects/` — the messages exchanged between you and Claude, including any code or context you shared in those conversations. Use `--dry-run` to preview exactly what would be sent before submitting anything. You control the scope: `--since`/`--until` bound the time period, and `--project`/`--exclude-project`/`--projects-dir` choose which folders are uploaded from — excluded sessions are never even read from disk.
|
|
35
35
|
|
|
36
36
|
See [Session capture](./docs/session-capture.md) for capture logic, duplicate-avoidance details, and the full flag reference.
|
|
37
37
|
|
|
@@ -52,6 +52,23 @@ Set `COOLHAND_AGENT_NAME` to avoid passing `--agent-name` on every call. Optiona
|
|
|
52
52
|
|
|
53
53
|
See [Your AI agent has notes](https://michael.carroll.io/talks/2026/your-ai-agent-has-notes) for a presentation on the research & best practices for using this pattern.
|
|
54
54
|
|
|
55
|
+
## Optimizations
|
|
56
|
+
|
|
57
|
+
Coolhand surfaces optimizations found for your production agents — a way to search, inspect, and act on suggested improvements without leaving the terminal:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Find open, high-impact optimizations
|
|
61
|
+
coolhand search-optimizations --status proposed --sort-by impact_desc
|
|
62
|
+
|
|
63
|
+
# Inspect one in full (human-readable by default; add --json for scripting)
|
|
64
|
+
coolhand get-optimization opt-123
|
|
65
|
+
|
|
66
|
+
# Apply the fix, then close it out with a reason
|
|
67
|
+
coolhand close-optimization opt-123 "Added the suggested index; verified query latency dropped from 800ms to 120ms in staging."
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`get-optimization` prints a scannable summary by default — title, status, impact/complexity, analysis, and plan — not a raw JSON dump. Internal agent transcript data (`orchestrator_messages`) is only included with `--full`. See [docs/commands.md](./docs/commands.md) for the full flag reference, including `update-optimization` for editing an optimization's fields in place.
|
|
71
|
+
|
|
55
72
|
## Commands
|
|
56
73
|
|
|
57
74
|
| Command | Description |
|
|
@@ -62,6 +79,7 @@ See [Your AI agent has notes](https://michael.carroll.io/talks/2026/your-ai-agen
|
|
|
62
79
|
| `coolhand whoami` | Show the currently configured client |
|
|
63
80
|
| `coolhand clients` | List or switch the default client |
|
|
64
81
|
| `coolhand claude` | Run Claude CLI through the Coolhand proxy |
|
|
82
|
+
| `coolhand monitor` | Run an arbitrary CLI through the Coolhand proxy |
|
|
65
83
|
| `coolhand list-workloads` | List workloads with optional search and pagination |
|
|
66
84
|
| `coolhand get-workload` | Fetch a single workload by ID |
|
|
67
85
|
| `coolhand update-workload` | Update a workload's name and/or description |
|
|
@@ -69,6 +87,8 @@ See [Your AI agent has notes](https://michael.carroll.io/talks/2026/your-ai-agen
|
|
|
69
87
|
| `coolhand get-optimization` | Fetch a single optimization by ID |
|
|
70
88
|
| `coolhand update-optimization` | Update an optimization's fields |
|
|
71
89
|
| `coolhand close-optimization` | Close an optimization with a reason |
|
|
90
|
+
| `coolhand search-feedback` | Search and filter feedback records |
|
|
91
|
+
| `coolhand get-feedback` | Fetch a single feedback record by ID |
|
|
72
92
|
| `coolhand analyze-claude-sessions` | Submit Claude sessions for pattern and cost analysis |
|
|
73
93
|
| `coolhand wildcard` | Record an agent blocker and exit cleanly |
|
|
74
94
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Coolhand } from 'coolhand-node';
|
|
2
|
+
import { CliError } from '../errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the CLI's stored client and returns a `Coolhand` instance authenticated with its
|
|
5
|
+
* *private* key. `searchFeedback`/`getFeedback` (coolhand-node#89) require the private key — the
|
|
6
|
+
* public key used by `wildcard`/`createFeedback` will 401 here.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getFeedbackClient(opts?: {
|
|
9
|
+
clientId?: string;
|
|
10
|
+
}): Promise<Coolhand>;
|
|
11
|
+
/**
|
|
12
|
+
* Maps an error thrown by `Coolhand#searchFeedback`/`getFeedback` to a `CliError` with a clear,
|
|
13
|
+
* actionable message — a 401 means the stored private key was rejected (mirrors `mcp-call.ts`'s
|
|
14
|
+
* hint), a 404 uses the caller-supplied message (e.g. "Feedback \"x\" not found").
|
|
15
|
+
*/
|
|
16
|
+
export declare function mapFeedbackHttpError(err: unknown, notFoundMessage: string): CliError;
|
|
17
|
+
//# sourceMappingURL=feedback-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feedback-client.d.ts","sourceRoot":"","sources":["../../src/api/feedback-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAa,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoB3F;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,GAAG,QAAQ,CAcpF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Coolhand, HttpError } from 'coolhand-node';
|
|
2
|
+
import { CliError } from '../errors.js';
|
|
3
|
+
import { loadConfig, resolveClient } from '../config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the CLI's stored client and returns a `Coolhand` instance authenticated with its
|
|
6
|
+
* *private* key. `searchFeedback`/`getFeedback` (coolhand-node#89) require the private key — the
|
|
7
|
+
* public key used by `wildcard`/`createFeedback` will 401 here.
|
|
8
|
+
*/
|
|
9
|
+
export async function getFeedbackClient(opts = {}) {
|
|
10
|
+
const cfg = await loadConfig();
|
|
11
|
+
const client = await resolveClient(cfg, opts.clientId);
|
|
12
|
+
if (!client.private_key) {
|
|
13
|
+
throw new CliError('NO_PRIVATE_KEY', "No private key configured. Run 'coolhand login --scope private' first.");
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
// Coolhand validates baseUrl in its constructor (https required; http only for localhost).
|
|
17
|
+
return new Coolhand({ apiKey: client.private_key, baseUrl: client.base_url, silent: true });
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
throw new CliError('INVALID_BASE_URL', `Invalid base_url for client: ${client.base_url} (${err.message})`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Maps an error thrown by `Coolhand#searchFeedback`/`getFeedback` to a `CliError` with a clear,
|
|
25
|
+
* actionable message — a 401 means the stored private key was rejected (mirrors `mcp-call.ts`'s
|
|
26
|
+
* hint), a 404 uses the caller-supplied message (e.g. "Feedback \"x\" not found").
|
|
27
|
+
*/
|
|
28
|
+
export function mapFeedbackHttpError(err, notFoundMessage) {
|
|
29
|
+
if (err instanceof HttpError) {
|
|
30
|
+
if (err.status === 401) {
|
|
31
|
+
return new CliError('FEEDBACK_ERROR', "The stored private key was rejected. Run 'coolhand login --scope private' to re-authenticate.");
|
|
32
|
+
}
|
|
33
|
+
if (err.status === 404) {
|
|
34
|
+
return new CliError('FEEDBACK_ERROR', notFoundMessage);
|
|
35
|
+
}
|
|
36
|
+
return new CliError('FEEDBACK_ERROR', `Feedback request failed (${err.status}): ${err.message}`);
|
|
37
|
+
}
|
|
38
|
+
return new CliError('FEEDBACK_ERROR', `Feedback request failed: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=feedback-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feedback-client.js","sourceRoot":"","sources":["../../src/api/feedback-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAA8B,EAAE;IACtE,MAAM,GAAG,GAAG,MAAM,UAAU,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEvD,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACxB,MAAM,IAAI,QAAQ,CAChB,gBAAgB,EAChB,wEAAwE,CACzE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,2FAA2F;QAC3F,OAAO,IAAI,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9F,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,QAAQ,CAChB,kBAAkB,EAClB,gCAAgC,MAAM,CAAC,QAAQ,KAAM,GAAa,CAAC,OAAO,GAAG,CAC9E,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY,EAAE,eAAuB;IACxE,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;QAC7B,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,OAAO,IAAI,QAAQ,CACjB,gBAAgB,EAChB,+FAA+F,CAChG,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,OAAO,IAAI,QAAQ,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,IAAI,QAAQ,CAAC,gBAAgB,EAAE,4BAA4B,GAAG,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,IAAI,QAAQ,CAAC,gBAAgB,EAAE,4BAA6B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;AAC9F,CAAC"}
|
package/dist/api/last-sync.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ask the server for the timestamp of the most recent log it already holds
|
|
2
|
+
* Ask the server for the timestamp of the most recent Claude Code log it already holds, so
|
|
3
3
|
* `capture-sessions` only re-scans files newer than that. This is a server-authoritative cutoff that
|
|
4
4
|
* survives local state-file loss or a reinstall.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Queries the `/api/v2/llm_request_logs` index for the single newest `claude_code` row. The server
|
|
7
|
+
* has no default sort, so newest-first is requested explicitly via `q[s]`. Index reads require the
|
|
8
|
+
* client's PRIVATE key (`private_key`, or the `COOLHAND_PRIVATE_KEY` env var); the public ingest
|
|
9
|
+
* key is write-only and the server answers it with a 401.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* It NEVER throws: any problem (no private key, bad base url, 401, network failure, non-JSON body,
|
|
12
|
+
* an empty result set, or an invalid `created_at`) returns `null` so the caller can fall back to
|
|
13
|
+
* local state.
|
|
12
14
|
*
|
|
13
15
|
* `opts.clientId` is expected to be an **already-resolved** `client_id` string (e.g. from
|
|
14
16
|
* `resolveClient`), not a raw user input. `getClient` is used here deliberately: by the time this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"last-sync.d.ts","sourceRoot":"","sources":["../../src/api/last-sync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"last-sync.d.ts","sourceRoot":"","sources":["../../src/api/last-sync.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,aAAa,CAAC,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAmE1F"}
|
package/dist/api/last-sync.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { loadConfig, getClient } from '../config.js';
|
|
2
2
|
import { DEFAULT_BASE_URL } from '../types.js';
|
|
3
|
-
import { PACKAGE_IDENTIFIER } from '../version.js';
|
|
4
|
-
/** Identifies this tool's logs in the server's collector field (matches the ingest collector). */
|
|
5
|
-
const COLLECTOR = `${PACKAGE_IDENTIFIER}/claude-code`;
|
|
6
3
|
/**
|
|
7
|
-
* Ask the server for the timestamp of the most recent log it already holds
|
|
4
|
+
* Ask the server for the timestamp of the most recent Claude Code log it already holds, so
|
|
8
5
|
* `capture-sessions` only re-scans files newer than that. This is a server-authoritative cutoff that
|
|
9
6
|
* survives local state-file loss or a reinstall.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* Queries the `/api/v2/llm_request_logs` index for the single newest `claude_code` row. The server
|
|
9
|
+
* has no default sort, so newest-first is requested explicitly via `q[s]`. Index reads require the
|
|
10
|
+
* client's PRIVATE key (`private_key`, or the `COOLHAND_PRIVATE_KEY` env var); the public ingest
|
|
11
|
+
* key is write-only and the server answers it with a 401.
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* It NEVER throws: any problem (no private key, bad base url, 401, network failure, non-JSON body,
|
|
14
|
+
* an empty result set, or an invalid `created_at`) returns `null` so the caller can fall back to
|
|
15
|
+
* local state.
|
|
17
16
|
*
|
|
18
17
|
* `opts.clientId` is expected to be an **already-resolved** `client_id` string (e.g. from
|
|
19
18
|
* `resolveClient`), not a raw user input. `getClient` is used here deliberately: by the time this
|
|
@@ -26,7 +25,7 @@ export async function fetchLastSync(opts = {}) {
|
|
|
26
25
|
try {
|
|
27
26
|
const cfg = await loadConfig();
|
|
28
27
|
const client = getClient(cfg, opts.clientId);
|
|
29
|
-
apiKey = client?.
|
|
28
|
+
apiKey = client?.private_key ?? process.env.COOLHAND_PRIVATE_KEY;
|
|
30
29
|
baseUrl = client?.base_url ?? DEFAULT_BASE_URL;
|
|
31
30
|
}
|
|
32
31
|
catch {
|
|
@@ -41,8 +40,10 @@ export async function fetchLastSync(opts = {}) {
|
|
|
41
40
|
if (parsedBaseUrl.protocol !== 'http:' && parsedBaseUrl.protocol !== 'https:') {
|
|
42
41
|
return null;
|
|
43
42
|
}
|
|
44
|
-
const endpoint = new URL('/api/v2/llm_request_logs
|
|
45
|
-
endpoint.searchParams.set('
|
|
43
|
+
const endpoint = new URL('/api/v2/llm_request_logs', parsedBaseUrl);
|
|
44
|
+
endpoint.searchParams.set('q[source_api_in][]', 'claude_code');
|
|
45
|
+
endpoint.searchParams.set('q[s]', 'created_at desc');
|
|
46
|
+
endpoint.searchParams.set('per', '1');
|
|
46
47
|
url = endpoint.toString();
|
|
47
48
|
}
|
|
48
49
|
catch {
|
|
@@ -60,20 +61,30 @@ export async function fetchLastSync(opts = {}) {
|
|
|
60
61
|
return null;
|
|
61
62
|
}
|
|
62
63
|
if (!res.ok) {
|
|
63
|
-
//
|
|
64
|
+
// 401 (key lacks read access) or any other non-2xx — degrade to local state.
|
|
64
65
|
return null;
|
|
65
66
|
}
|
|
66
67
|
let body;
|
|
67
68
|
try {
|
|
68
|
-
body =
|
|
69
|
+
body = await res.json();
|
|
69
70
|
}
|
|
70
71
|
catch {
|
|
71
72
|
return null;
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
+
// The index returns a bare JSON array; an empty one means the server holds no
|
|
75
|
+
// claude_code logs for this client yet.
|
|
76
|
+
if (!Array.isArray(body) || body.length === 0) {
|
|
74
77
|
return null;
|
|
75
78
|
}
|
|
76
|
-
const
|
|
79
|
+
const newest = body[0];
|
|
80
|
+
if (newest === null || typeof newest !== 'object') {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const createdAt = newest.created_at;
|
|
84
|
+
if (typeof createdAt !== 'string') {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const date = new Date(createdAt);
|
|
77
88
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
78
89
|
}
|
|
79
90
|
//# sourceMappingURL=last-sync.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"last-sync.js","sourceRoot":"","sources":["../../src/api/last-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"last-sync.js","sourceRoot":"","sources":["../../src/api/last-sync.ts"],"names":[],"mappings":"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"}
|
package/dist/bin.js
CHANGED
|
File without changes
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AA2CA,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC;CACpD;AAkRD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAgE7F;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAiDpD;AAkdD,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsIzD"}
|
package/dist/cli.js
CHANGED
|
@@ -12,14 +12,19 @@ import { run as runCloseOptimization } from './commands/close-optimization.js';
|
|
|
12
12
|
import { run as runUpdateOptimization } from './commands/update-optimization.js';
|
|
13
13
|
import { run as runWildcard } from './commands/wildcard.js';
|
|
14
14
|
import { run as runClaude } from './commands/claude.js';
|
|
15
|
+
import { run as runMonitor } from './commands/monitor.js';
|
|
15
16
|
import { run as runAnalyzeClaudeSessions } from './commands/analyze-claude-sessions.js';
|
|
16
17
|
import { run as runListWorkloads } from './commands/list-workloads.js';
|
|
17
18
|
import { run as runGetWorkload } from './commands/get-workload.js';
|
|
18
19
|
import { run as runUpdateWorkload } from './commands/update-workload.js';
|
|
19
20
|
import { run as runFlushPending, spawnBackgroundFlush } from './commands/flush-pending.js';
|
|
21
|
+
import { run as runSearchFeedback } from './commands/search-feedback.js';
|
|
22
|
+
import { run as runGetFeedback } from './commands/get-feedback.js';
|
|
20
23
|
import { countPending, flushFailed } from './pending-store.js';
|
|
21
24
|
import { confirm } from './prompt.js';
|
|
22
|
-
const BOOLEAN_FLAGS = new Set(['all', 'help', 'h', 'json', 'version', 'v', 'dry-run', 'include-archived', 'include-system', 'include-templates']);
|
|
25
|
+
const BOOLEAN_FLAGS = new Set(['all', 'help', 'h', 'json', 'version', 'v', 'dry-run', 'include-archived', 'include-system', 'include-templates', 'full', 'matched', 'unmatched']);
|
|
26
|
+
/** Flags whose repeated occurrences accumulate into an array instead of overwriting. */
|
|
27
|
+
const REPEATABLE_FLAGS = new Set(['project', 'exclude-project']);
|
|
23
28
|
const COMMANDS = [
|
|
24
29
|
{
|
|
25
30
|
name: 'login',
|
|
@@ -91,8 +96,9 @@ const COMMANDS = [
|
|
|
91
96
|
{
|
|
92
97
|
name: 'get-optimization',
|
|
93
98
|
oneLiner: 'Get a single optimization by ID',
|
|
94
|
-
usage: 'coolhand get-optimization <id> [options]',
|
|
99
|
+
usage: 'coolhand get-optimization <id> [--full] [options]',
|
|
95
100
|
options: [
|
|
101
|
+
{ flag: '--full', description: 'Include the internal orchestrator_messages transcript (large; contains raw thoughtSignature blobs), on both text and --json output' },
|
|
96
102
|
{ flag: '--client-id ID', description: 'Use a specific stored client' },
|
|
97
103
|
{ flag: '--json', description: 'Emit JSON output instead of human-readable text' },
|
|
98
104
|
],
|
|
@@ -132,6 +138,35 @@ const COMMANDS = [
|
|
|
132
138
|
{ flag: '--json', description: 'Emit JSON output instead of human-readable text' },
|
|
133
139
|
],
|
|
134
140
|
},
|
|
141
|
+
{
|
|
142
|
+
name: 'search-feedback',
|
|
143
|
+
oneLiner: 'Search and filter feedback records (requires a private key)',
|
|
144
|
+
usage: 'coolhand search-feedback [options]',
|
|
145
|
+
options: [
|
|
146
|
+
{ flag: '--sentiment positive|negative|neutral', description: 'Filter by sentiment' },
|
|
147
|
+
{ flag: '--search TEXT', description: 'Filter by explanation substring' },
|
|
148
|
+
{ flag: '--creator-id ID', description: 'Filter by creator_unique_id' },
|
|
149
|
+
{ flag: '--workload-id ID', description: 'Filter by workload ID' },
|
|
150
|
+
{ flag: '--matched', description: 'Only feedback linked to an LLM request log' },
|
|
151
|
+
{ flag: '--unmatched', description: 'Only feedback not linked to an LLM request log' },
|
|
152
|
+
{ flag: '--since DATE', description: 'Only feedback created at or after DATE' },
|
|
153
|
+
{ flag: '--sort-by created_at|updated_at', description: 'Sort field (default: created_at)' },
|
|
154
|
+
{ flag: '--sort-dir asc|desc', description: 'Sort direction (default: desc)' },
|
|
155
|
+
{ flag: '--page N', description: 'Page number (default: 1)' },
|
|
156
|
+
{ flag: '--per-page N', description: 'Results per page (default: 25, max: 100)' },
|
|
157
|
+
{ flag: '--client-id ID', description: 'Use a specific stored client' },
|
|
158
|
+
{ flag: '--json', description: 'Emit JSON output instead of human-readable text' },
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'get-feedback',
|
|
163
|
+
oneLiner: 'Get a single feedback record by ID (requires a private key)',
|
|
164
|
+
usage: 'coolhand get-feedback <feedback-id> [options]',
|
|
165
|
+
options: [
|
|
166
|
+
{ flag: '--client-id ID', description: 'Use a specific stored client' },
|
|
167
|
+
{ flag: '--json', description: 'Emit JSON output instead of human-readable text' },
|
|
168
|
+
],
|
|
169
|
+
},
|
|
135
170
|
{
|
|
136
171
|
name: 'analyze-claude-sessions',
|
|
137
172
|
oneLiner: 'Submit historical Claude Code sessions to Coolhand for pattern and cost analysis',
|
|
@@ -140,6 +175,11 @@ const COMMANDS = [
|
|
|
140
175
|
{ flag: '--dry-run', description: 'Scan and report what would be submitted, without sending' },
|
|
141
176
|
{ flag: '--client-id ID', description: 'Use a specific stored client' },
|
|
142
177
|
{ flag: '--json', description: 'Emit JSON output instead of human-readable text' },
|
|
178
|
+
{ flag: '--since WHEN', description: 'Only sessions modified at or after WHEN (YYYY-MM-DD, ISO datetime, or 12h/7d/2w)' },
|
|
179
|
+
{ flag: '--until WHEN', description: 'Only sessions modified at or before WHEN (same formats; a date means its whole day)' },
|
|
180
|
+
{ flag: '--projects-dir PATH', description: 'Scan PATH instead of ~/.claude/projects' },
|
|
181
|
+
{ flag: '--project NAME', description: 'Only sessions from matching project folders (repeatable, comma-separable)' },
|
|
182
|
+
{ flag: '--exclude-project NAME', description: 'Skip sessions from matching project folders (repeatable, comma-separable)' },
|
|
143
183
|
],
|
|
144
184
|
},
|
|
145
185
|
{
|
|
@@ -151,6 +191,15 @@ const COMMANDS = [
|
|
|
151
191
|
{ flag: '[claude args...]', description: 'Everything after `claude` is passed straight to the Claude CLI' },
|
|
152
192
|
],
|
|
153
193
|
},
|
|
194
|
+
{
|
|
195
|
+
name: 'monitor',
|
|
196
|
+
oneLiner: 'Run an arbitrary CLI through the Coolhand proxy (captures LLM calls)',
|
|
197
|
+
usage: 'coolhand [--client-id ID] monitor [--] <command> [args...]',
|
|
198
|
+
options: [
|
|
199
|
+
{ flag: '--client-id ID', description: 'Use a specific stored client (must come before `monitor`, not after)' },
|
|
200
|
+
{ flag: '<command> [args...]', description: 'The CLI to run and its arguments, e.g. `kimi --resume`' },
|
|
201
|
+
],
|
|
202
|
+
},
|
|
154
203
|
{
|
|
155
204
|
name: 'list-workloads',
|
|
156
205
|
oneLiner: 'List workloads with optional name search and pagination',
|
|
@@ -303,6 +352,18 @@ export function peelClientId(argv) {
|
|
|
303
352
|
export function parseArgs(argv) {
|
|
304
353
|
const flags = {};
|
|
305
354
|
const positional = [];
|
|
355
|
+
const setValueFlag = (name, value) => {
|
|
356
|
+
const prev = flags[name];
|
|
357
|
+
if (REPEATABLE_FLAGS.has(name) && typeof prev === 'string') {
|
|
358
|
+
flags[name] = [prev, value];
|
|
359
|
+
}
|
|
360
|
+
else if (REPEATABLE_FLAGS.has(name) && Array.isArray(prev)) {
|
|
361
|
+
flags[name] = [...prev, value];
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
flags[name] = value;
|
|
365
|
+
}
|
|
366
|
+
};
|
|
306
367
|
let i = 0;
|
|
307
368
|
while (i < argv.length) {
|
|
308
369
|
const arg = argv[i];
|
|
@@ -313,14 +374,14 @@ export function parseArgs(argv) {
|
|
|
313
374
|
if (arg.startsWith('--')) {
|
|
314
375
|
const eq = arg.indexOf('=');
|
|
315
376
|
if (eq >= 0) {
|
|
316
|
-
|
|
377
|
+
setValueFlag(arg.slice(2, eq), arg.slice(eq + 1));
|
|
317
378
|
i += 1;
|
|
318
379
|
}
|
|
319
380
|
else {
|
|
320
381
|
const name = arg.slice(2);
|
|
321
382
|
const next = argv[i + 1];
|
|
322
383
|
if (!BOOLEAN_FLAGS.has(name) && next !== undefined && !next.startsWith('-')) {
|
|
323
|
-
|
|
384
|
+
setValueFlag(name, next);
|
|
324
385
|
i += 2;
|
|
325
386
|
}
|
|
326
387
|
else {
|
|
@@ -479,6 +540,9 @@ function getOptimizationOptions(parsed) {
|
|
|
479
540
|
if (parsed.flags.json === true) {
|
|
480
541
|
opts.json = true;
|
|
481
542
|
}
|
|
543
|
+
if (parsed.flags.full === true) {
|
|
544
|
+
opts.full = true;
|
|
545
|
+
}
|
|
482
546
|
return opts;
|
|
483
547
|
}
|
|
484
548
|
function closeOptimizationOptions(parsed) {
|
|
@@ -519,6 +583,15 @@ function updateOptimizationOptions(parsed) {
|
|
|
519
583
|
}
|
|
520
584
|
return opts;
|
|
521
585
|
}
|
|
586
|
+
/** Flatten a repeatable flag's value(s) into a trimmed list, splitting each on commas. */
|
|
587
|
+
function stringListFlag(value) {
|
|
588
|
+
const raw = Array.isArray(value) ? value : typeof value === 'string' ? [value] : [];
|
|
589
|
+
const items = raw
|
|
590
|
+
.flatMap((entry) => entry.split(','))
|
|
591
|
+
.map((entry) => entry.trim())
|
|
592
|
+
.filter((entry) => entry.length > 0);
|
|
593
|
+
return items.length > 0 ? items : undefined;
|
|
594
|
+
}
|
|
522
595
|
function analyzeClaudeSessionsOptions(parsed) {
|
|
523
596
|
const opts = {};
|
|
524
597
|
if (parsed.flags['dry-run'] === true) {
|
|
@@ -530,6 +603,23 @@ function analyzeClaudeSessionsOptions(parsed) {
|
|
|
530
603
|
if (parsed.flags.json === true) {
|
|
531
604
|
opts.json = true;
|
|
532
605
|
}
|
|
606
|
+
if (typeof parsed.flags.since === 'string') {
|
|
607
|
+
opts.since = parsed.flags.since;
|
|
608
|
+
}
|
|
609
|
+
if (typeof parsed.flags.until === 'string') {
|
|
610
|
+
opts.until = parsed.flags.until;
|
|
611
|
+
}
|
|
612
|
+
if (typeof parsed.flags['projects-dir'] === 'string') {
|
|
613
|
+
opts.projectsDir = parsed.flags['projects-dir'];
|
|
614
|
+
}
|
|
615
|
+
const projects = stringListFlag(parsed.flags.project);
|
|
616
|
+
if (projects) {
|
|
617
|
+
opts.projects = projects;
|
|
618
|
+
}
|
|
619
|
+
const excludeProjects = stringListFlag(parsed.flags['exclude-project']);
|
|
620
|
+
if (excludeProjects) {
|
|
621
|
+
opts.excludeProjects = excludeProjects;
|
|
622
|
+
}
|
|
533
623
|
return opts;
|
|
534
624
|
}
|
|
535
625
|
function getWorkloadOptions(parsed) {
|
|
@@ -618,6 +708,88 @@ function listWorkloadsOptions(parsed) {
|
|
|
618
708
|
}
|
|
619
709
|
return opts;
|
|
620
710
|
}
|
|
711
|
+
const SENTIMENT_VALUES = new Set(['positive', 'negative', 'neutral']);
|
|
712
|
+
const SORT_BY_VALUES = new Set(['created_at', 'updated_at']);
|
|
713
|
+
const SORT_DIR_VALUES = new Set(['asc', 'desc']);
|
|
714
|
+
function searchFeedbackOptions(parsed) {
|
|
715
|
+
const opts = {};
|
|
716
|
+
if (typeof parsed.flags.sentiment === 'string') {
|
|
717
|
+
if (!SENTIMENT_VALUES.has(parsed.flags.sentiment)) {
|
|
718
|
+
throw new CliError('INVALID_ARGS', '--sentiment must be one of: positive, negative, neutral');
|
|
719
|
+
}
|
|
720
|
+
opts.sentiment = parsed.flags.sentiment;
|
|
721
|
+
}
|
|
722
|
+
if (typeof parsed.flags.search === 'string') {
|
|
723
|
+
opts.search = parsed.flags.search;
|
|
724
|
+
}
|
|
725
|
+
if (typeof parsed.flags['creator-id'] === 'string') {
|
|
726
|
+
opts.creatorId = parsed.flags['creator-id'];
|
|
727
|
+
}
|
|
728
|
+
if (typeof parsed.flags['workload-id'] === 'string') {
|
|
729
|
+
opts.workloadId = parsed.flags['workload-id'];
|
|
730
|
+
}
|
|
731
|
+
if (parsed.flags.matched === true && parsed.flags.unmatched === true) {
|
|
732
|
+
throw new CliError('INVALID_ARGS', '--matched and --unmatched are mutually exclusive');
|
|
733
|
+
}
|
|
734
|
+
if (parsed.flags.matched === true) {
|
|
735
|
+
opts.matched = true;
|
|
736
|
+
}
|
|
737
|
+
if (parsed.flags.unmatched === true) {
|
|
738
|
+
opts.unmatched = true;
|
|
739
|
+
}
|
|
740
|
+
if (typeof parsed.flags.since === 'string') {
|
|
741
|
+
opts.since = parsed.flags.since;
|
|
742
|
+
}
|
|
743
|
+
if (typeof parsed.flags['sort-by'] === 'string') {
|
|
744
|
+
if (!SORT_BY_VALUES.has(parsed.flags['sort-by'])) {
|
|
745
|
+
throw new CliError('INVALID_ARGS', '--sort-by must be one of: created_at, updated_at');
|
|
746
|
+
}
|
|
747
|
+
opts.sortBy = parsed.flags['sort-by'];
|
|
748
|
+
}
|
|
749
|
+
if (typeof parsed.flags['sort-dir'] === 'string') {
|
|
750
|
+
if (!SORT_DIR_VALUES.has(parsed.flags['sort-dir'])) {
|
|
751
|
+
throw new CliError('INVALID_ARGS', '--sort-dir must be one of: asc, desc');
|
|
752
|
+
}
|
|
753
|
+
opts.sortDir = parsed.flags['sort-dir'];
|
|
754
|
+
}
|
|
755
|
+
if (typeof parsed.flags['page'] === 'string') {
|
|
756
|
+
const raw = parsed.flags['page'];
|
|
757
|
+
const n = parseInt(raw, 10);
|
|
758
|
+
if (!/^\d+$/.test(raw) || isNaN(n) || n < 1) {
|
|
759
|
+
throw new CliError('INVALID_ARGS', '--page must be a positive integer');
|
|
760
|
+
}
|
|
761
|
+
opts.page = n;
|
|
762
|
+
}
|
|
763
|
+
if (typeof parsed.flags['per-page'] === 'string') {
|
|
764
|
+
const raw = parsed.flags['per-page'];
|
|
765
|
+
const n = parseInt(raw, 10);
|
|
766
|
+
if (!/^\d+$/.test(raw) || isNaN(n) || n < 1 || n > 100) {
|
|
767
|
+
throw new CliError('INVALID_ARGS', '--per-page must be an integer between 1 and 100');
|
|
768
|
+
}
|
|
769
|
+
opts.perPage = n;
|
|
770
|
+
}
|
|
771
|
+
if (typeof parsed.flags['client-id'] === 'string') {
|
|
772
|
+
opts.clientId = parsed.flags['client-id'];
|
|
773
|
+
}
|
|
774
|
+
if (parsed.flags.json === true) {
|
|
775
|
+
opts.json = true;
|
|
776
|
+
}
|
|
777
|
+
return opts;
|
|
778
|
+
}
|
|
779
|
+
function getFeedbackOptions(parsed) {
|
|
780
|
+
const id = parsed.positional[0];
|
|
781
|
+
if (!id) {
|
|
782
|
+
throw new CliError('INVALID_ARGS', 'get-feedback requires a <feedback-id> argument');
|
|
783
|
+
}
|
|
784
|
+
const opts = { id };
|
|
785
|
+
if (typeof parsed.flags['client-id'] === 'string') {
|
|
786
|
+
opts.clientId = parsed.flags['client-id'];
|
|
787
|
+
}
|
|
788
|
+
if (parsed.flags.json === true) {
|
|
789
|
+
opts.json = true;
|
|
790
|
+
}
|
|
791
|
+
return opts;
|
|
792
|
+
}
|
|
621
793
|
function wildcardOptions(parsed) {
|
|
622
794
|
const complaint = typeof parsed.flags.complaint === 'string' ? parsed.flags.complaint.trim() : '';
|
|
623
795
|
if (!complaint) {
|
|
@@ -692,6 +864,20 @@ export async function run(argv) {
|
|
|
692
864
|
if (argv2[0] === 'claude') {
|
|
693
865
|
return await runClaude({ args: argv2.slice(1), clientId: globalClientId });
|
|
694
866
|
}
|
|
867
|
+
// `monitor` is a passthrough wrapper too: everything after the wrapped
|
|
868
|
+
// command's name goes to it verbatim (e.g. `coolhand monitor kimi --resume`).
|
|
869
|
+
// An optional leading `--` may be used to separate it from `monitor` itself.
|
|
870
|
+
if (argv2[0] === 'monitor') {
|
|
871
|
+
let rest = argv2.slice(1);
|
|
872
|
+
if (rest[0] === '--') {
|
|
873
|
+
rest = rest.slice(1);
|
|
874
|
+
}
|
|
875
|
+
const [command, ...args] = rest;
|
|
876
|
+
if (!command) {
|
|
877
|
+
throw new CliError('INVALID_ARGS', 'monitor requires a command to run, e.g. `coolhand monitor -- kimi --resume`');
|
|
878
|
+
}
|
|
879
|
+
return await runMonitor({ command, args, clientId: globalClientId });
|
|
880
|
+
}
|
|
695
881
|
parsed = parseArgs(argv2);
|
|
696
882
|
// Merge a peeled global --client-id if the per-command flag wasn't also supplied.
|
|
697
883
|
if (globalClientId !== undefined) {
|
|
@@ -759,6 +945,10 @@ export async function run(argv) {
|
|
|
759
945
|
return await runUpdateOptimization(updateOptimizationOptions(parsed));
|
|
760
946
|
case 'wildcard':
|
|
761
947
|
return await runWildcard(wildcardOptions(parsed));
|
|
948
|
+
case 'search-feedback':
|
|
949
|
+
return await runSearchFeedback(searchFeedbackOptions(parsed));
|
|
950
|
+
case 'get-feedback':
|
|
951
|
+
return await runGetFeedback(getFeedbackOptions(parsed));
|
|
762
952
|
case 'analyze-claude-sessions':
|
|
763
953
|
return await runAnalyzeClaudeSessions(analyzeClaudeSessionsOptions(parsed));
|
|
764
954
|
case 'list-workloads':
|