cueloop 0.1.0-alpha.90 → 0.1.0-alpha.92
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 +6 -6
- package/src/args.ts +2 -2
- package/src/main.ts +49 -4
- package/src/thread-commands.ts +25 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cueloop",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.92",
|
|
4
4
|
"description": "Terminal review surface for coding agents: annotate plans and diffs, return a message, send structured feedback back",
|
|
5
5
|
"homepage": "https://github.com/mmurakaru/cueloop#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"build:binary": "bun build --compile --minify --external cpu-features ./src/main.ts --outfile dist/cueloop"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@cueloop/adapters": "0.1.0-alpha.
|
|
34
|
-
"@cueloop/client": "0.1.0-alpha.
|
|
35
|
-
"@cueloop/daemon": "0.1.0-alpha.
|
|
36
|
-
"@cueloop/extension-api": "0.1.0-alpha.
|
|
37
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
33
|
+
"@cueloop/adapters": "0.1.0-alpha.92",
|
|
34
|
+
"@cueloop/client": "0.1.0-alpha.92",
|
|
35
|
+
"@cueloop/daemon": "0.1.0-alpha.92",
|
|
36
|
+
"@cueloop/extension-api": "0.1.0-alpha.92",
|
|
37
|
+
"@cueloop/schema": "0.1.0-alpha.92",
|
|
38
38
|
"@modelcontextprotocol/server": "2.0.0",
|
|
39
39
|
"@valibot/to-json-schema": "1.4.0",
|
|
40
40
|
"valibot": "^1.5.0"
|
package/src/args.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface ParsedArgs {
|
|
|
7
7
|
flags: Record<string, string | boolean>;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function parseArgs(argv: string[]): ParsedArgs {
|
|
10
|
+
export function parseArgs(argv: string[], booleanFlags: readonly string[] = []): ParsedArgs {
|
|
11
11
|
const positional: string[] = [];
|
|
12
12
|
const flags: Record<string, string | boolean> = {};
|
|
13
13
|
|
|
@@ -26,7 +26,7 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
26
26
|
const key = argument.slice(2);
|
|
27
27
|
const next = argv[i + 1];
|
|
28
28
|
|
|
29
|
-
if (next !== undefined && !next.startsWith("--")) {
|
|
29
|
+
if (!booleanFlags.includes(key) && next !== undefined && !next.startsWith("--")) {
|
|
30
30
|
flags[key] = next;
|
|
31
31
|
i++;
|
|
32
32
|
} else {
|
package/src/main.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
import { sessionCommand } from "./thread-commands";
|
|
24
24
|
import { CLI_VERSION } from "./version";
|
|
25
25
|
import { DaemonClient } from "@cueloop/daemon/client";
|
|
26
|
-
import type { Thread } from "@cueloop/schema";
|
|
26
|
+
import type { Thread, ThreadSurfaceOpenStatus } from "@cueloop/schema";
|
|
27
27
|
import { openReview } from "@cueloop/daemon/thread-review";
|
|
28
28
|
|
|
29
29
|
const argv = process.argv.slice(2);
|
|
@@ -107,6 +107,7 @@ const commandHandlers: CommandHandlers = {
|
|
|
107
107
|
stop: async () => (await import("./daemon-control")).stopCommand(),
|
|
108
108
|
restart: async () => (await import("./daemon-control")).restartCommand(),
|
|
109
109
|
plan: (rest) => planCommand(rest),
|
|
110
|
+
pair: (rest) => pairCommand(rest),
|
|
110
111
|
reply: (rest) => replyCommand(rest),
|
|
111
112
|
diff: (rest) => diffCommand(rest),
|
|
112
113
|
prototype: (rest) => prototypeCommand(rest),
|
|
@@ -278,6 +279,43 @@ async function diffCommand(argv: string[]): Promise<number> {
|
|
|
278
279
|
return runTui(workbench.id, "review");
|
|
279
280
|
}
|
|
280
281
|
|
|
282
|
+
async function pairCommand(argv: string[]): Promise<number> {
|
|
283
|
+
const startedAt = new Date().toISOString();
|
|
284
|
+
const parsed = parseArgs(argv, ["open-tab", "no-tui"]);
|
|
285
|
+
const client = await DaemonClient.connect({ autostart: true });
|
|
286
|
+
let thread: Thread;
|
|
287
|
+
let openStatus: ThreadSurfaceOpenStatus | undefined;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const sessionId = parsed.positional[0];
|
|
291
|
+
|
|
292
|
+
if (sessionId !== undefined) {
|
|
293
|
+
thread = await client.sessionGet(sessionId);
|
|
294
|
+
if (thread.artifact.meta.workbench !== true || thread.status !== "pending") {
|
|
295
|
+
throw new Error(`Thread ${sessionId} is not an open workbench`);
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
thread = await client.sessionWorkbench(process.cwd());
|
|
299
|
+
}
|
|
300
|
+
if (parsed.flags["open-tab"] === true) {
|
|
301
|
+
const { openHerdrThreadSurface } = await import("@cueloop/daemon/herdr-thread-surface");
|
|
302
|
+
|
|
303
|
+
openStatus = await openHerdrThreadSurface(thread, client, process.env, "tab", "pair");
|
|
304
|
+
}
|
|
305
|
+
} finally {
|
|
306
|
+
client.close();
|
|
307
|
+
}
|
|
308
|
+
if (parsed.flags["no-tui"] === true || parsed.flags["open-tab"] === true) {
|
|
309
|
+
console.log(
|
|
310
|
+
JSON.stringify({ threadId: thread.id, startedAt, ...(openStatus && { openStatus }) }),
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return runTui(thread.id, "pair");
|
|
317
|
+
}
|
|
318
|
+
|
|
281
319
|
/**
|
|
282
320
|
* `cueloop review` disambiguates create from open by intent:
|
|
283
321
|
* - bare `cueloop review`, `--open`/`--latest`, or a `ses_*` selector opens a
|
|
@@ -310,10 +348,16 @@ async function reviewEntry(argv: string[]): Promise<number> {
|
|
|
310
348
|
* omitted) restores the remembered one. Resolve the factory here so the heavy client
|
|
311
349
|
* module stays lazily imported for non-TUI commands.
|
|
312
350
|
*/
|
|
313
|
-
async function runTui(sessionId?: string, layout?: "review" | "plan"): Promise<number> {
|
|
314
|
-
const { runClient, reviewLayout, planLayout } = await import("@cueloop/client");
|
|
351
|
+
async function runTui(sessionId?: string, layout?: "review" | "plan" | "pair"): Promise<number> {
|
|
352
|
+
const { runClient, pairLayout, reviewLayout, planLayout } = await import("@cueloop/client");
|
|
315
353
|
const resolved =
|
|
316
|
-
layout === "review"
|
|
354
|
+
layout === "review"
|
|
355
|
+
? reviewLayout()
|
|
356
|
+
: layout === "plan"
|
|
357
|
+
? planLayout()
|
|
358
|
+
: layout === "pair"
|
|
359
|
+
? pairLayout()
|
|
360
|
+
: undefined;
|
|
317
361
|
|
|
318
362
|
return runClient({ sessionId, layout: resolved });
|
|
319
363
|
}
|
|
@@ -331,6 +375,7 @@ function printHelp(): void {
|
|
|
331
375
|
" cueloop reply [id|title] open the latest pending reply review (the agent's previous message)",
|
|
332
376
|
" cueloop diff [id|title] review your working tree (untracked files included);",
|
|
333
377
|
" with a clean tree, open the latest pending diff review",
|
|
378
|
+
" cueloop pair [thread-id] open a live workbench; --open-tab opens Herdr; --no-tui prints its Thread ID",
|
|
334
379
|
" cueloop review <pr> review a pull request (--no-tui prints the session)",
|
|
335
380
|
" cueloop prototype <file.md> review a component design doc (or open the latest by id/title)",
|
|
336
381
|
"",
|
package/src/thread-commands.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
newAnnotationId,
|
|
13
13
|
type Anchor,
|
|
14
14
|
type Annotation,
|
|
15
|
+
type AnnotationTarget,
|
|
15
16
|
} from "@cueloop/schema";
|
|
16
17
|
import * as v from "valibot";
|
|
17
18
|
import { DaemonClient } from "@cueloop/daemon/client";
|
|
@@ -127,7 +128,7 @@ async function annotateAnchor(
|
|
|
127
128
|
flags: Record<string, string | boolean>,
|
|
128
129
|
client: DaemonClient,
|
|
129
130
|
id: string,
|
|
130
|
-
): Promise<{ anchor: Anchor; replyTo?: string }> {
|
|
131
|
+
): Promise<{ anchor: Anchor; replyTo?: string; target?: AnnotationTarget }> {
|
|
131
132
|
const replyTo = stringFlag(flags, "reply-to");
|
|
132
133
|
|
|
133
134
|
if (replyTo !== undefined) {
|
|
@@ -137,7 +138,9 @@ async function annotateAnchor(
|
|
|
137
138
|
if (!root) throw new Error(`no comment ${replyTo} to reply to`);
|
|
138
139
|
|
|
139
140
|
// a reply to a reply still hangs off the discussion's root comment
|
|
140
|
-
return
|
|
141
|
+
return root.target === undefined
|
|
142
|
+
? { anchor: root.anchor, replyTo: root.replyTo ?? root.id }
|
|
143
|
+
: { anchor: root.anchor, replyTo: root.replyTo ?? root.id, target: root.target };
|
|
141
144
|
}
|
|
142
145
|
const selector = stringFlag(flags, "selector");
|
|
143
146
|
const quote =
|
|
@@ -163,8 +166,8 @@ async function sessionAnnotateCommand({
|
|
|
163
166
|
const id = required(positional[1], "session id");
|
|
164
167
|
const author = stringFlag(flags, "author");
|
|
165
168
|
const body = await annotateBody(flags, client, id);
|
|
166
|
-
const { anchor, replyTo } = await annotateAnchor(flags, client, id);
|
|
167
|
-
const
|
|
169
|
+
const { anchor, replyTo, target } = await annotateAnchor(flags, client, id);
|
|
170
|
+
const base: Omit<Annotation, "createdAt"> = {
|
|
168
171
|
id: stringFlag(flags, "annotation-id") ?? newAnnotationId(),
|
|
169
172
|
kind: stringFlag(flags, "kind") ?? "comment",
|
|
170
173
|
anchor,
|
|
@@ -172,7 +175,9 @@ async function sessionAnnotateCommand({
|
|
|
172
175
|
author,
|
|
173
176
|
};
|
|
174
177
|
|
|
175
|
-
if (replyTo !== undefined)
|
|
178
|
+
if (replyTo !== undefined) base.replyTo = replyTo;
|
|
179
|
+
const annotation: Omit<Annotation, "createdAt"> =
|
|
180
|
+
target === undefined ? base : { ...base, target };
|
|
176
181
|
out(await client.sessionAnnotate(id, annotation, stringFlag(flags, "author-name")));
|
|
177
182
|
|
|
178
183
|
return 0;
|
|
@@ -340,6 +345,7 @@ async function sessionEventsCommand({
|
|
|
340
345
|
}: SessionContext): Promise<number> {
|
|
341
346
|
const id = required(positional[1], "session id");
|
|
342
347
|
const once = flags.once === true;
|
|
348
|
+
const ready = flags.ready === true;
|
|
343
349
|
const stream = new Promise<number>((resolve) => {
|
|
344
350
|
client.onEvent((event) => {
|
|
345
351
|
if (event.sessionId !== id) return;
|
|
@@ -347,11 +353,24 @@ async function sessionEventsCommand({
|
|
|
347
353
|
if (once) resolve(0);
|
|
348
354
|
});
|
|
349
355
|
});
|
|
356
|
+
let finished = false;
|
|
357
|
+
const disconnect = new Promise<number>((resolve) => {
|
|
358
|
+
client.onDisconnect(() => {
|
|
359
|
+
if (finished) return;
|
|
360
|
+
console.error("daemon connection closed - restart cueloop session events to resume");
|
|
361
|
+
resolve(1);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
350
364
|
|
|
351
365
|
await client.subscribe();
|
|
352
366
|
await client.sessionGet(id);
|
|
367
|
+
if (ready) console.log(JSON.stringify({ event: "events.ready", sessionId: id }));
|
|
368
|
+
|
|
369
|
+
const code = await Promise.race([stream, disconnect]);
|
|
370
|
+
|
|
371
|
+
finished = true;
|
|
353
372
|
|
|
354
|
-
return
|
|
373
|
+
return code;
|
|
355
374
|
}
|
|
356
375
|
|
|
357
376
|
async function sessionSendMessageCommand({
|