cueloop 0.1.0-alpha.58 → 0.1.0-alpha.59

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cueloop",
3
- "version": "0.1.0-alpha.58",
3
+ "version": "0.1.0-alpha.59",
4
4
  "description": "Terminal review surface for coding agents: annotate plans and diffs, return a verdict, send structured feedback back",
5
5
  "homepage": "https://github.com/mmurakaru/cueloop#readme",
6
6
  "bugs": {
@@ -30,10 +30,10 @@
30
30
  "build:binary": "bun build --compile --external cpu-features ./src/main.ts --outfile dist/cueloop"
31
31
  },
32
32
  "dependencies": {
33
- "@cueloop/adapters": "0.1.0-alpha.58",
34
- "@cueloop/client": "0.1.0-alpha.58",
35
- "@cueloop/daemon": "0.1.0-alpha.58",
36
- "@cueloop/schema": "0.1.0-alpha.58"
33
+ "@cueloop/adapters": "0.1.0-alpha.59",
34
+ "@cueloop/client": "0.1.0-alpha.59",
35
+ "@cueloop/daemon": "0.1.0-alpha.59",
36
+ "@cueloop/schema": "0.1.0-alpha.59"
37
37
  },
38
38
  "engines": {
39
39
  "bun": ">=1.3.0"
package/src/main.ts CHANGED
@@ -29,103 +29,93 @@ import { openReview, resolveWorkspace } from "@cueloop/daemon/review";
29
29
  const argv = process.argv.slice(2);
30
30
  const cmd = argv[0];
31
31
 
32
+ async function daemonCommand(): Promise<number> {
33
+ const { DaemonServer } = await import("@cueloop/daemon");
34
+ const server = new DaemonServer({ idleExitMs: 0 });
35
+ const path = server.start();
36
+ if (path === null) {
37
+ console.error("a cueloop daemon already owns this home - nothing to do");
38
+ return 1;
39
+ }
40
+ console.log(`cueloop daemon (foreground) on ${path}`);
41
+ await new Promise(() => {}); // run until signalled
42
+ return 0;
43
+ }
44
+
45
+ async function serveEntry(rest: string[]): Promise<number> {
46
+ const { positional, flags } = parseArgs(rest);
47
+ const port = stringFlag(flags, "port");
48
+ const { serveClient } = await import("@cueloop/client");
49
+ const handle = await serveClient({
50
+ port: port !== undefined ? Number(port) : undefined,
51
+ host: stringFlag(flags, "host"),
52
+ sessionId: positional[0],
53
+ });
54
+ console.log(
55
+ [
56
+ "",
57
+ `observers join with: ssh -p ${handle.port} ${handle.host === "0.0.0.0" || handle.host === "::" ? "<this-host>" : handle.host}`,
58
+ "",
59
+ "no passwords, no keys: anyone who can reach this address can watch.",
60
+ "share the address deliberately (SSH tunnel, tailnet). observers are",
61
+ "read-only; you stay the one writable controller via `cueloop` locally.",
62
+ "ctrl-c stops serving.",
63
+ ].join("\n"),
64
+ );
65
+ const stop = () => void handle.close().finally(() => process.exit(0));
66
+ process.on("SIGINT", stop);
67
+ process.on("SIGTERM", stop);
68
+ await new Promise(() => {}); // serve until signalled
69
+ return 0;
70
+ }
71
+
72
+ async function shareEntry(rest: string[]): Promise<number> {
73
+ const { positional, flags } = parseArgs(rest);
74
+ const { shareCommand, sharePullCommand } = await import("./share-command");
75
+ const port = stringFlag(flags, "port");
76
+ const target = {
77
+ host: stringFlag(flags, "host"),
78
+ port: port !== undefined ? Number(port) : undefined,
79
+ };
80
+ if (positional[0] === "pull") return sharePullCommand({ ...target, sessionId: positional[1] });
81
+ return shareCommand({ ...target, sessionId: positional[0] });
82
+ }
83
+
84
+ type CommandHandler = (rest: string[]) => number | Promise<number>;
85
+
86
+ const commandHandlers: Record<string, CommandHandler> = {
87
+ session: (rest) => sessionCommand(rest),
88
+ daemon: () => daemonCommand(),
89
+ plan: (rest) => planCommand(rest),
90
+ diff: (rest) => diffCommand(rest),
91
+ prototype: (rest) => prototypeCommand(rest),
92
+ serve: (rest) => serveEntry(rest),
93
+ share: (rest) => shareEntry(rest),
94
+ wake: async (rest) => (await import("./wake-command")).wakeCommand(rest),
95
+ actions: async (rest) => (await import("./actions-command")).actionsCommand(rest),
96
+ refine: async (rest) => (await import("./refine-command")).refineCommand(rest),
97
+ review: (rest) => reviewEntry(rest),
98
+ "review-post": async (rest) => (await import("./pr")).reviewPostCommand(rest),
99
+ };
100
+
101
+ const versionAliases = new Set(["-v", "--version", "version"]);
102
+ const helpAliases = new Set(["-h", "--help", "help"]);
103
+
32
104
  async function main(): Promise<number> {
33
- switch (cmd) {
34
- case "session":
35
- return sessionCommand(argv.slice(1));
36
- case "daemon": {
37
- const { DaemonServer } = await import("@cueloop/daemon");
38
- const server = new DaemonServer({ idleExitMs: 0 });
39
- const path = server.start();
40
- if (path === null) {
41
- console.error("a cueloop daemon already owns this home - nothing to do");
42
- return 1;
43
- }
44
- console.log(`cueloop daemon (foreground) on ${path}`);
45
- await new Promise(() => {}); // run until signalled
46
- return 0;
47
- }
48
- case "plan":
49
- return planCommand(argv.slice(1));
50
- case "diff":
51
- return diffCommand(argv.slice(1));
52
- case "prototype":
53
- return prototypeCommand(argv.slice(1));
54
- case "serve": {
55
- const { positional, flags } = parseArgs(argv.slice(1));
56
- const port = stringFlag(flags, "port");
57
- const { serveClient } = await import("@cueloop/client");
58
- const handle = await serveClient({
59
- port: port !== undefined ? Number(port) : undefined,
60
- host: stringFlag(flags, "host"),
61
- sessionId: positional[0],
62
- });
63
- console.log(
64
- [
65
- "",
66
- `observers join with: ssh -p ${handle.port} ${handle.host === "0.0.0.0" || handle.host === "::" ? "<this-host>" : handle.host}`,
67
- "",
68
- "no passwords, no keys: anyone who can reach this address can watch.",
69
- "share the address deliberately (SSH tunnel, tailnet). observers are",
70
- "read-only; you stay the one writable controller via `cueloop` locally.",
71
- "ctrl-c stops serving.",
72
- ].join("\n"),
73
- );
74
- const stop = () => void handle.close().finally(() => process.exit(0));
75
- process.on("SIGINT", stop);
76
- process.on("SIGTERM", stop);
77
- await new Promise(() => {}); // serve until signalled
78
- return 0;
79
- }
80
- case "share": {
81
- const { positional, flags } = parseArgs(argv.slice(1));
82
- const { shareCommand, sharePullCommand } = await import("./share-command");
83
- const port = stringFlag(flags, "port");
84
- const target = {
85
- host: stringFlag(flags, "host"),
86
- port: port !== undefined ? Number(port) : undefined,
87
- };
88
- if (positional[0] === "pull")
89
- return sharePullCommand({ ...target, sessionId: positional[1] });
90
- return shareCommand({ ...target, sessionId: positional[0] });
91
- }
92
- case "wake": {
93
- const { wakeCommand } = await import("./wake-command");
94
- return wakeCommand(argv.slice(1));
95
- }
96
- case "actions": {
97
- const { actionsCommand } = await import("./actions-command");
98
- return actionsCommand(argv.slice(1));
99
- }
100
- case "refine": {
101
- const { refineCommand } = await import("./refine-command");
102
- return refineCommand(argv.slice(1));
103
- }
104
- case "review":
105
- return reviewEntry(argv.slice(1));
106
- case "review-post": {
107
- const { reviewPostCommand } = await import("./pr");
108
- return reviewPostCommand(argv.slice(1));
109
- }
110
- case "-v":
111
- case "--version":
112
- case "version":
113
- console.log(CLI_VERSION);
114
- return 0;
115
- case "-h":
116
- case "--help":
117
- case "help":
118
- printHelp();
119
- return 0;
120
- case undefined:
121
- return runTui();
122
- default: {
123
- // `cueloop <session-id>` opens the TUI on that session
124
- if (cmd.startsWith("ses_")) return runTui(cmd);
125
- printHelp();
126
- return 2;
127
- }
105
+ if (cmd === undefined) return runTui();
106
+ const handler = commandHandlers[cmd];
107
+ if (handler !== undefined) return handler(argv.slice(1));
108
+ if (versionAliases.has(cmd)) {
109
+ console.log(CLI_VERSION);
110
+ return 0;
111
+ }
112
+ if (helpAliases.has(cmd)) {
113
+ printHelp();
114
+ return 0;
128
115
  }
116
+ if (cmd.startsWith("ses_")) return runTui(cmd);
117
+ printHelp();
118
+ return 2;
129
119
  }
130
120
 
131
121
  /**
@@ -19,6 +19,126 @@ function out(value: unknown): void {
19
19
  console.log(JSON.stringify(value, null, 2));
20
20
  }
21
21
 
22
+ type SessionFlags = Record<string, string | boolean>;
23
+ type SessionContext = { client: DaemonClient; positional: string[]; flags: SessionFlags };
24
+ type SessionVerbHandler = (context: SessionContext) => Promise<number>;
25
+
26
+ async function sessionCreate({ client, flags }: SessionContext): Promise<number> {
27
+ const contentFile = stringFlag(flags, "content-file");
28
+ const content = contentFile ? await Bun.file(contentFile).text() : await readStdin();
29
+ // per-file agent notes for diff sessions: a JSON array of { path, body }
30
+ const notesFile = stringFlag(flags, "notes-file");
31
+ const notes = notesFile
32
+ ? (JSON.parse(await Bun.file(notesFile).text()) as ReviewNote[])
33
+ : undefined;
34
+ const review = await openReview(client, {
35
+ type: (stringFlag(flags, "type") ?? "plan") as ArtifactType,
36
+ content,
37
+ cwd: stringFlag(flags, "cwd"),
38
+ agent: stringFlag(flags, "agent"),
39
+ agentSessionId: stringFlag(flags, "agent-session-id"),
40
+ planPath: stringFlag(flags, "plan-path"),
41
+ prototypePath: stringFlag(flags, "prototype-path"),
42
+ title: stringFlag(flags, "title"),
43
+ notes,
44
+ });
45
+ // herdr auto-open: render the review in a tab (no-op outside herdr).
46
+ await openHerdrPaneForReview(review.session, client);
47
+ out(review.session);
48
+ return 0;
49
+ }
50
+
51
+ async function sessionGetCommand({ client, positional }: SessionContext): Promise<number> {
52
+ out(await client.sessionGet(required(positional[1], "session id")));
53
+ return 0;
54
+ }
55
+
56
+ async function sessionListCommand({ client, flags }: SessionContext): Promise<number> {
57
+ const status = stringFlag(flags, "status") as "pending" | "resolved" | undefined;
58
+ out(await client.sessionList(status ? { status } : undefined));
59
+ return 0;
60
+ }
61
+
62
+ async function sessionWaitCommand({ client, positional, flags }: SessionContext): Promise<number> {
63
+ const id = required(positional[1], "session id");
64
+ const timeoutMs = Number(stringFlag(flags, "timeout-ms") ?? "60000");
65
+ const session = await client.sessionWait(id, timeoutMs);
66
+ if (session === null) {
67
+ out({ status: "pending" });
68
+ return 0;
69
+ }
70
+ const { allow, feedback } = verdictResponse(session);
71
+ out({ status: "resolved", allow, verdict: session.verdict!.kind, feedback });
72
+ return 0;
73
+ }
74
+
75
+ async function sessionAnnotateCommand({
76
+ client,
77
+ positional,
78
+ flags,
79
+ }: SessionContext): Promise<number> {
80
+ const id = required(positional[1], "session id");
81
+ const quote = required(stringFlag(flags, "quote"), "--quote");
82
+ const author = stringFlag(flags, "author");
83
+ const body = await annotateBody(flags, client, id);
84
+ out(
85
+ await client.sessionAnnotate(
86
+ id,
87
+ {
88
+ id: stringFlag(flags, "annotation-id") ?? newAnnotationId(),
89
+ kind: stringFlag(flags, "kind") ?? "comment",
90
+ anchor: {
91
+ quote,
92
+ prefix: stringFlag(flags, "prefix") ?? "",
93
+ suffix: stringFlag(flags, "suffix") ?? "",
94
+ },
95
+ body,
96
+ ...(author ? { author } : {}),
97
+ },
98
+ stringFlag(flags, "author-name"),
99
+ ),
100
+ );
101
+ return 0;
102
+ }
103
+
104
+ async function sessionResolveCommand({
105
+ client,
106
+ positional,
107
+ flags,
108
+ }: SessionContext): Promise<number> {
109
+ const id = required(positional[1], "session id");
110
+ const kind = required(stringFlag(flags, "verdict"), "--verdict") as VerdictKind;
111
+ out(await client.sessionResolve(id, kind, stringFlag(flags, "summary") ?? ""));
112
+ return 0;
113
+ }
114
+
115
+ async function sessionSubmitRevisionCommand({
116
+ client,
117
+ positional,
118
+ flags,
119
+ }: SessionContext): Promise<number> {
120
+ const id = required(positional[1], "session id");
121
+ const contentFile = stringFlag(flags, "content-file");
122
+ const content = contentFile ? await Bun.file(contentFile).text() : await readStdin();
123
+ // --addressed a_1,a_2 - the annotation ids this revision acted on
124
+ const addressed = (stringFlag(flags, "addressed") ?? "")
125
+ .split(",")
126
+ .map((annotationId) => annotationId.trim())
127
+ .filter(Boolean);
128
+ out(await client.sessionSubmitRevision(id, content, addressed));
129
+ return 0;
130
+ }
131
+
132
+ const sessionVerbHandlers: Record<string, SessionVerbHandler> = {
133
+ create: sessionCreate,
134
+ get: sessionGetCommand,
135
+ list: sessionListCommand,
136
+ wait: sessionWaitCommand,
137
+ annotate: sessionAnnotateCommand,
138
+ resolve: sessionResolveCommand,
139
+ "submit-revision": sessionSubmitRevisionCommand,
140
+ };
141
+
22
142
  export async function sessionCommand(argv: string[]): Promise<number> {
23
143
  const { positional, flags } = parseArgs(argv);
24
144
  const verb = positional[0];
@@ -27,100 +147,14 @@ export async function sessionCommand(argv: string[]): Promise<number> {
27
147
  const role = stringFlag(flags, "role") === "agent" ? "agent" : undefined;
28
148
  const client = await DaemonClient.connect({ autostart: true, role });
29
149
  try {
30
- switch (verb) {
31
- case "create": {
32
- const contentFile = stringFlag(flags, "content-file");
33
- const content = contentFile ? await Bun.file(contentFile).text() : await readStdin();
34
- // per-file agent notes for diff sessions: a JSON array of { path, body }
35
- const notesFile = stringFlag(flags, "notes-file");
36
- const notes = notesFile
37
- ? (JSON.parse(await Bun.file(notesFile).text()) as ReviewNote[])
38
- : undefined;
39
- const review = await openReview(client, {
40
- type: (stringFlag(flags, "type") ?? "plan") as ArtifactType,
41
- content,
42
- cwd: stringFlag(flags, "cwd"),
43
- agent: stringFlag(flags, "agent"),
44
- agentSessionId: stringFlag(flags, "agent-session-id"),
45
- planPath: stringFlag(flags, "plan-path"),
46
- prototypePath: stringFlag(flags, "prototype-path"),
47
- title: stringFlag(flags, "title"),
48
- notes,
49
- });
50
- // herdr auto-open: render the review in a tab (no-op outside herdr).
51
- await openHerdrPaneForReview(review.session, client);
52
- out(review.session);
53
- return 0;
54
- }
55
- case "get": {
56
- out(await client.sessionGet(required(positional[1], "session id")));
57
- return 0;
58
- }
59
- case "list": {
60
- const status = stringFlag(flags, "status") as "pending" | "resolved" | undefined;
61
- out(await client.sessionList(status ? { status } : undefined));
62
- return 0;
63
- }
64
- case "wait": {
65
- const id = required(positional[1], "session id");
66
- const timeoutMs = Number(stringFlag(flags, "timeout-ms") ?? "60000");
67
- const session = await client.sessionWait(id, timeoutMs);
68
- if (session === null) {
69
- out({ status: "pending" });
70
- return 0;
71
- }
72
- const { allow, feedback } = verdictResponse(session);
73
- out({ status: "resolved", allow, verdict: session.verdict!.kind, feedback });
74
- return 0;
75
- }
76
- case "annotate": {
77
- const id = required(positional[1], "session id");
78
- const quote = required(stringFlag(flags, "quote"), "--quote");
79
- const author = stringFlag(flags, "author");
80
- const body = await annotateBody(flags, client, id);
81
- out(
82
- await client.sessionAnnotate(
83
- id,
84
- {
85
- id: stringFlag(flags, "annotation-id") ?? newAnnotationId(),
86
- kind: stringFlag(flags, "kind") ?? "comment",
87
- anchor: {
88
- quote,
89
- prefix: stringFlag(flags, "prefix") ?? "",
90
- suffix: stringFlag(flags, "suffix") ?? "",
91
- },
92
- body,
93
- ...(author ? { author } : {}),
94
- },
95
- stringFlag(flags, "author-name"),
96
- ),
97
- );
98
- return 0;
99
- }
100
- case "resolve": {
101
- const id = required(positional[1], "session id");
102
- const kind = required(stringFlag(flags, "verdict"), "--verdict") as VerdictKind;
103
- out(await client.sessionResolve(id, kind, stringFlag(flags, "summary") ?? ""));
104
- return 0;
105
- }
106
- case "submit-revision": {
107
- const id = required(positional[1], "session id");
108
- const contentFile = stringFlag(flags, "content-file");
109
- const content = contentFile ? await Bun.file(contentFile).text() : await readStdin();
110
- // --addressed a_1,a_2 - the annotation ids this revision acted on
111
- const addressed = (stringFlag(flags, "addressed") ?? "")
112
- .split(",")
113
- .map((annotationId) => annotationId.trim())
114
- .filter(Boolean);
115
- out(await client.sessionSubmitRevision(id, content, addressed));
116
- return 0;
117
- }
118
- default:
119
- console.error(
120
- "usage: cueloop session <create|get|list|wait|annotate|resolve|submit-revision> [flags]",
121
- );
122
- return 2;
150
+ const handler = verb !== undefined ? sessionVerbHandlers[verb] : undefined;
151
+ if (handler === undefined) {
152
+ console.error(
153
+ "usage: cueloop session <create|get|list|wait|annotate|resolve|submit-revision> [flags]",
154
+ );
155
+ return 2;
123
156
  }
157
+ return await handler({ client, positional, flags });
124
158
  } finally {
125
159
  client.close();
126
160
  }