summaryrefslogtreecommitdiffhomepage
path: root/packages/cli
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-21 22:55:31 +0900
committerAdam Malczewski <[email protected]>2026-06-21 22:55:31 +0900
commit9797e095b7c268cf2d4e7b8d35a5f04a7d363fb1 (patch)
treea85cf5240037d019624ac194e4650c7b2f8d8ab2 /packages/cli
parentf518ae6f98681225d921c66985f1115d20119f00 (diff)
downloaddispatch-9797e095b7c268cf2d4e7b8d35a5f04a7d363fb1.tar.gz
dispatch-9797e095b7c268cf2d4e7b8d35a5f04a7d363fb1.zip
feat(cli): add 'open' command to signal frontend without sending a message
dispatch open <conversationId> broadcasts a conversation.open WS message to all connected frontend clients without sending any message. Useful after 'read' or 'send --queue' when you just want the frontend to open/focus a conversation's tab.
Diffstat (limited to 'packages/cli')
-rw-r--r--packages/cli/src/args.test.ts31
-rw-r--r--packages/cli/src/args.ts23
-rw-r--r--packages/cli/src/main.ts17
3 files changed, 71 insertions, 0 deletions
diff --git a/packages/cli/src/args.test.ts b/packages/cli/src/args.test.ts
index 70a4868..62bcafd 100644
--- a/packages/cli/src/args.test.ts
+++ b/packages/cli/src/args.test.ts
@@ -325,4 +325,35 @@ describe("parseArgs", () => {
expect(result.kind).toBe("error");
});
});
+
+ describe("open", () => {
+ it("parses 'open' with conversation id", () => {
+ expect(parseArgs(["open", "deadbeef"], { defaultServer })).toEqual({
+ kind: "open",
+ server: "http://localhost:24203",
+ conversationId: "deadbeef",
+ });
+ });
+
+ it("parses 'open' with --server", () => {
+ expect(
+ parseArgs(["open", "deadbeef", "--server", "http://example.com"], { defaultServer }),
+ ).toEqual({
+ kind: "open",
+ server: "http://example.com",
+ conversationId: "deadbeef",
+ });
+ });
+
+ it("requires a conversation id", () => {
+ const result = parseArgs(["open"], { defaultServer });
+ expect(result.kind).toBe("error");
+ if (result.kind === "error") expect(result.message).toContain("conversation id");
+ });
+
+ it("rejects unknown flags", () => {
+ const result = parseArgs(["open", "deadbeef", "--bogus"], { defaultServer });
+ expect(result.kind).toBe("error");
+ });
+ });
});
diff --git a/packages/cli/src/args.ts b/packages/cli/src/args.ts
index 4d6652e..d4ed0e9 100644
--- a/packages/cli/src/args.ts
+++ b/packages/cli/src/args.ts
@@ -28,6 +28,7 @@ export type ParsedCommand =
readonly open: boolean;
}
| { readonly kind: "list"; readonly server: string; readonly query?: string }
+ | { readonly kind: "open"; readonly server: string; readonly conversationId: string }
| { readonly kind: "read"; readonly server: string; readonly conversationId: string }
| {
readonly kind: "send";
@@ -110,6 +111,28 @@ export function parseArgs(argv: readonly string[], opts: ParseOpts): ParsedComma
return { kind: "read", server, conversationId };
}
+ if (first === "open") {
+ 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 'open': ${arg}` };
+ } else {
+ conversationId = arg;
+ }
+ }
+ if (conversationId === undefined) {
+ return { kind: "error", message: "'open' requires a conversation id" };
+ }
+ return { kind: "open", server, conversationId };
+ }
+
if (first === "send") {
let server = opts.defaultServer;
let conversationId: string | undefined;
diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts
index 04e7231..365096a 100644
--- a/packages/cli/src/main.ts
+++ b/packages/cli/src/main.ts
@@ -24,6 +24,7 @@ const USAGE = `Usage:
dispatch models [--server <url>]
dispatch list [<prefix>] [--server <url>]
dispatch read <conversationId> [--server <url>]
+ dispatch open <conversationId> [--server <url>]
dispatch send <conversationId> --text "..." [--queue] [--open] [--cwd <dir>] [--effort <level>] [--server <url>]
dispatch <modelName> --text "..." [--file <path>] [--cwd <dir>] [--conversation <id>] [--effort <level>] [--server <url>] [--show-reasoning] [--open]
dispatch --help
@@ -73,6 +74,22 @@ async function main(): Promise<void> {
if (last.content.length > 0) process.stdout.write(`${last.content}\n`);
break;
}
+ case "open": {
+ const resolved = await resolveConversationId(
+ { fetchImpl: globalThis.fetch },
+ { server: parsed.server, shortId: parsed.conversationId },
+ );
+ if (typeof resolved !== "string") {
+ process.stderr.write(`${resolved.error}\n`);
+ process.exit(1);
+ }
+ await openConversation(
+ { fetchImpl: globalThis.fetch },
+ { server: parsed.server, conversationId: resolved },
+ );
+ process.stdout.write(`Signaled frontend to open ${resolved}\n`);
+ break;
+ }
case "send": {
const resolved = await resolveConversationId(
{ fetchImpl: globalThis.fetch },