summaryrefslogtreecommitdiffhomepage
path: root/packages/cli/src/args.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-21 19:20:10 +0900
committerAdam Malczewski <[email protected]>2026-06-21 19:20:10 +0900
commitc5e9fd6cd6565b55fab1bf2b9d8dacf8ba72a9f4 (patch)
tree809bd93eaa25646f237fb4b1ddd3719e25aaca90 /packages/cli/src/args.ts
parentea0e938eca3072649dc8707c999ec00cf87b986a (diff)
downloaddispatch-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.ts125
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;