diff options
| author | Adam Malczewski <[email protected]> | 2026-06-21 19:20:10 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-21 19:20:10 +0900 |
| commit | c5e9fd6cd6565b55fab1bf2b9d8dacf8ba72a9f4 (patch) | |
| tree | 809bd93eaa25646f237fb4b1ddd3719e25aaca90 /packages/cli/src/args.ts | |
| parent | ea0e938eca3072649dc8707c999ec00cf87b986a (diff) | |
| download | dispatch-c5e9fd6cd6565b55fab1bf2b9d8dacf8ba72a9f4.tar.gz dispatch-c5e9fd6cd6565b55fab1bf2b9d8dacf8ba72a9f4.zip | |
feat(cli): list, read, send commands (Wave 3)
CLI gains three new sub-commands:
- dispatch list [--server] — list conversations (short ID + title + activity)
- dispatch read <id> [--server] — block until turn settles, print last AI message
- dispatch send <id> --text [--queue] [--open] [--cwd] [--effort] [--server]
- Default: blocking (consumes NDJSON stream, prints accumulated text + conv ID)
- --queue: non-blocking (POST /conversations/:id/queue, exit immediately)
- --open: signals frontend to open the conversation tab (POST /conversations/:id/open)
Short-ID resolution: 4+ char prefix → GET /conversations?q= → resolve to full ID.
32+ char input is treated as a full UUID (no resolution). Errors on 0 or >1 matches.
48 new tests (108 total in cli). Pure arg parser + HTTP client functions, zero vi.mock.
Diffstat (limited to 'packages/cli/src/args.ts')
| -rw-r--r-- | packages/cli/src/args.ts | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/packages/cli/src/args.ts b/packages/cli/src/args.ts index b3fa1e5..b4478b0 100644 --- a/packages/cli/src/args.ts +++ b/packages/cli/src/args.ts @@ -26,6 +26,18 @@ export type ParsedCommand = readonly reasoningEffort?: ReasoningEffort | undefined; readonly showReasoning: boolean; } + | { readonly kind: "list"; readonly server: string; readonly query?: string } + | { readonly kind: "read"; readonly server: string; readonly conversationId: string } + | { + readonly kind: "send"; + readonly server: string; + readonly conversationId: string; + readonly text: string; + readonly queue: boolean; + readonly open: boolean; + readonly cwd?: string; + readonly reasoningEffort?: ReasoningEffort; + } | { readonly kind: "help" } | { readonly kind: "error"; readonly message: string }; @@ -56,6 +68,119 @@ export function parseArgs(argv: readonly string[], opts: ParseOpts): ParsedComma return { kind: "models", server }; } + if (first === "list") { + let server = opts.defaultServer; + let query: string | undefined; + for (let i = 1; i < argv.length; i++) { + const arg = argv[i] as string; + if (arg === "--server") { + if (i + 1 >= argv.length) return { kind: "error", message: "--server requires a value" }; + server = argv[++i] as string; + } else if (arg.startsWith("--")) { + return { kind: "error", message: `Unknown flag: ${arg}` }; + } else if (query !== undefined) { + return { kind: "error", message: `Unexpected argument for 'list': ${arg}` }; + } else { + query = arg; + } + } + return { kind: "list", server, ...(query !== undefined && { query }) }; + } + + if (first === "read") { + let server = opts.defaultServer; + let conversationId: string | undefined; + for (let i = 1; i < argv.length; i++) { + const arg = argv[i] as string; + if (arg === "--server") { + if (i + 1 >= argv.length) return { kind: "error", message: "--server requires a value" }; + server = argv[++i] as string; + } else if (arg.startsWith("--")) { + return { kind: "error", message: `Unknown flag: ${arg}` }; + } else if (conversationId !== undefined) { + return { kind: "error", message: `Unexpected argument for 'read': ${arg}` }; + } else { + conversationId = arg; + } + } + if (conversationId === undefined) { + return { kind: "error", message: "'read' requires a conversation id" }; + } + return { kind: "read", server, conversationId }; + } + + if (first === "send") { + let server = opts.defaultServer; + let conversationId: string | undefined; + let text: string | undefined; + let queue = false; + let open = false; + let cwd: string | undefined; + let reasoningEffort: ReasoningEffort | undefined; + + for (let i = 1; i < argv.length; i++) { + const arg = argv[i] as string; + switch (arg) { + case "--server": + if (i + 1 >= argv.length) return { kind: "error", message: "--server requires a value" }; + server = argv[++i] as string; + break; + case "--text": + if (i + 1 >= argv.length) return { kind: "error", message: "--text requires a value" }; + text = argv[++i]; + break; + case "--queue": + queue = true; + break; + case "--open": + open = true; + break; + case "--cwd": + if (i + 1 >= argv.length) return { kind: "error", message: "--cwd requires a value" }; + cwd = argv[++i]; + break; + case "--effort": { + if (i + 1 >= argv.length) + return { + kind: "error", + message: `--effort requires a value (one of: ${VALID_EFFORTS.join(", ")})`, + }; + const val = argv[++i] as string; + if (!isValidEffort(val)) + return { + kind: "error", + message: `Invalid effort level "${val}". Must be one of: ${VALID_EFFORTS.join(", ")}`, + }; + reasoningEffort = val; + break; + } + default: + if (arg.startsWith("--")) return { kind: "error", message: `Unknown flag: ${arg}` }; + if (conversationId !== undefined) + return { kind: "error", message: `Unexpected argument for 'send': ${arg}` }; + conversationId = arg; + } + } + + if (conversationId === undefined) { + return { kind: "error", message: "'send' requires a conversation id" }; + } + if (text === undefined) { + return { kind: "error", message: "'send' requires --text" }; + } + + return { + kind: "send", + server, + conversationId, + text, + queue, + open, + ...(cwd !== undefined && { cwd }), + ...(reasoningEffort !== undefined && { reasoningEffort }), + }; + } + // Chat mode: first arg is the model name const modelName = first; let text: string | undefined; |
