cueloop 0.1.0-alpha.8 → 0.1.0-alpha.80
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 +28 -23
- package/src/actions-command.ts +49 -0
- package/src/args.ts +21 -10
- package/src/daemon-control.test.ts +68 -0
- package/src/daemon-control.ts +104 -0
- package/src/dev-command.ts +112 -0
- package/src/main.ts +329 -95
- package/src/open-target.test.ts +241 -0
- package/src/open-target.ts +128 -0
- package/src/pr.ts +56 -48
- package/src/refine-command.test.ts +187 -0
- package/src/refine-command.ts +42 -0
- package/src/share-command.test.ts +361 -0
- package/src/share-command.ts +147 -0
- package/src/thread-commands.ts +501 -0
- package/src/update-command.test.ts +203 -0
- package/src/update-command.ts +198 -0
- package/src/version.ts +5 -0
- package/src/wake-command.test.ts +14 -0
- package/src/wake-command.ts +44 -0
- package/src/session-cmds.ts +0 -107
- package/src/working-tree.ts +0 -27
package/src/main.ts
CHANGED
|
@@ -1,106 +1,313 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* cueloop entry points
|
|
4
|
-
* `cueloop diff`
|
|
5
|
-
*
|
|
6
|
-
* `cueloop
|
|
7
|
-
* `cueloop
|
|
3
|
+
* cueloop entry points: `cueloop` opens the TUI on the inbox, the primitive-first
|
|
4
|
+
* openers `cueloop plan|reply|diff|review` open the latest pending review of
|
|
5
|
+
* that type (or address one by id/title), `cueloop diff`/`cueloop review <pr>` also
|
|
6
|
+
* keep their create paths, `cueloop session *` mirrors the daemon API for
|
|
7
|
+
* agents and scripts, `cueloop serve` shares a session over ssh (read-only
|
|
8
|
+
* observers), `cueloop daemon` runs the daemon in the foreground.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { basename, resolve } from "node:path";
|
|
12
|
+
import { parseArgs, stringFlag, type ParsedArgs } from "./args";
|
|
13
|
+
import {
|
|
14
|
+
isDiffReview,
|
|
15
|
+
isPlanReview,
|
|
16
|
+
isPrototypeReview,
|
|
17
|
+
isPrReview,
|
|
18
|
+
isReplyReview,
|
|
19
|
+
isSessionId,
|
|
20
|
+
openTargetMessage,
|
|
21
|
+
resolveOpenTarget,
|
|
22
|
+
} from "./open-target";
|
|
23
|
+
import { sessionCommand } from "./thread-commands";
|
|
24
|
+
import { CLI_VERSION } from "./version";
|
|
13
25
|
import { DaemonClient } from "@cueloop/daemon/client";
|
|
14
|
-
import {
|
|
26
|
+
import type { Thread } from "@cueloop/schema";
|
|
27
|
+
import { openReview } from "@cueloop/daemon/thread-review";
|
|
15
28
|
|
|
16
29
|
const argv = process.argv.slice(2);
|
|
17
30
|
const cmd = argv[0];
|
|
18
31
|
|
|
32
|
+
async function daemonCommand(argv: string[]): Promise<number> {
|
|
33
|
+
const { DaemonServer } = await import("@cueloop/daemon");
|
|
34
|
+
// Explicit foreground never idle-exits; --autostart (a re-exec from the client)
|
|
35
|
+
// takes the normal idle-exit, matching the source main.ts entry.
|
|
36
|
+
const envIdle = process.env.CUELOOP_IDLE_EXIT_MS;
|
|
37
|
+
let idleExitMs: number | undefined = 0;
|
|
38
|
+
|
|
39
|
+
if (argv.includes("--autostart")) idleExitMs = envIdle ? Number(envIdle) : undefined;
|
|
40
|
+
const server = new DaemonServer({ idleExitMs });
|
|
41
|
+
const path = server.start();
|
|
42
|
+
|
|
43
|
+
if (path === null) {
|
|
44
|
+
console.error("a cueloop daemon already owns this home - nothing to do");
|
|
45
|
+
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
console.log(`cueloop daemon (foreground) on ${path}`);
|
|
49
|
+
await new Promise(() => {}); // run until signalled
|
|
50
|
+
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function serveEntry(rest: string[]): Promise<number> {
|
|
55
|
+
const { positional, flags } = parseArgs(rest);
|
|
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
|
+
|
|
64
|
+
console.log(
|
|
65
|
+
[
|
|
66
|
+
"",
|
|
67
|
+
`observers join with: ssh -p ${handle.port} ${handle.host === "0.0.0.0" || handle.host === "::" ? "<this-host>" : handle.host}`,
|
|
68
|
+
"",
|
|
69
|
+
"no passwords, no keys: anyone who can reach this address can watch.",
|
|
70
|
+
"share the address deliberately (SSH tunnel, tailnet). observers are",
|
|
71
|
+
"read-only; you stay the one writable controller via `cueloop` locally.",
|
|
72
|
+
"ctrl-c stops serving.",
|
|
73
|
+
].join("\n"),
|
|
74
|
+
);
|
|
75
|
+
const stop = () => void handle.close().finally(() => process.exit(0));
|
|
76
|
+
|
|
77
|
+
process.on("SIGINT", stop);
|
|
78
|
+
process.on("SIGTERM", stop);
|
|
79
|
+
await new Promise(() => {}); // serve until signalled
|
|
80
|
+
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function shareEntry(rest: string[]): Promise<number> {
|
|
85
|
+
const { positional, flags } = parseArgs(rest);
|
|
86
|
+
const { shareCommand, sharePullCommand } = await import("./share-command");
|
|
87
|
+
const port = stringFlag(flags, "port");
|
|
88
|
+
const target = {
|
|
89
|
+
host: stringFlag(flags, "host"),
|
|
90
|
+
port: port !== undefined ? Number(port) : undefined,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (positional[0] === "pull") return sharePullCommand({ ...target, sessionId: positional[1] });
|
|
94
|
+
|
|
95
|
+
return shareCommand({ ...target, sessionId: positional[0], fork: flags.fork === true });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
type CommandHandler = (rest: string[]) => number | Promise<number>;
|
|
99
|
+
|
|
100
|
+
interface CommandHandlers {
|
|
101
|
+
[command: string]: CommandHandler;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const commandHandlers: CommandHandlers = {
|
|
105
|
+
session: (rest) => sessionCommand(rest),
|
|
106
|
+
daemon: (rest) => daemonCommand(rest),
|
|
107
|
+
stop: async () => (await import("./daemon-control")).stopCommand(),
|
|
108
|
+
restart: async () => (await import("./daemon-control")).restartCommand(),
|
|
109
|
+
plan: (rest) => planCommand(rest),
|
|
110
|
+
reply: (rest) => replyCommand(rest),
|
|
111
|
+
diff: (rest) => diffCommand(rest),
|
|
112
|
+
prototype: (rest) => prototypeCommand(rest),
|
|
113
|
+
serve: (rest) => serveEntry(rest),
|
|
114
|
+
share: (rest) => shareEntry(rest),
|
|
115
|
+
wake: async (rest) => (await import("./wake-command")).wakeCommand(rest),
|
|
116
|
+
actions: async (rest) => (await import("./actions-command")).actionsCommand(rest),
|
|
117
|
+
refine: async (rest) => (await import("./refine-command")).refineCommand(rest),
|
|
118
|
+
update: async (rest) => (await import("./update-command")).updateCommand(rest),
|
|
119
|
+
review: (rest) => reviewEntry(rest),
|
|
120
|
+
"review-post": async (rest) => (await import("./pr")).reviewPostCommand(rest),
|
|
121
|
+
dev: async () => (await import("./dev-command")).devCommand(),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const versionAliases = new Set(["-v", "--version", "version"]);
|
|
125
|
+
const helpAliases = new Set(["-h", "--help", "help"]);
|
|
126
|
+
|
|
19
127
|
async function main(): Promise<number> {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
console.error("a cueloop daemon already owns this home - nothing to do");
|
|
29
|
-
return 1;
|
|
30
|
-
}
|
|
31
|
-
console.log(`cueloop daemon (foreground) on ${path}`);
|
|
32
|
-
await new Promise(() => {}); // run until signalled
|
|
33
|
-
return 0;
|
|
34
|
-
}
|
|
35
|
-
case "diff": {
|
|
36
|
-
const workspace = await resolveWorkspace();
|
|
37
|
-
const diff = await workingTreeDiff();
|
|
38
|
-
if (!diff.trim()) {
|
|
39
|
-
console.error("working tree is clean - nothing to review");
|
|
40
|
-
return 1;
|
|
41
|
-
}
|
|
42
|
-
const client = await DaemonClient.connect({ autostart: true });
|
|
43
|
-
const review = await openReview(client, {
|
|
44
|
-
type: "diff",
|
|
45
|
-
content: diff,
|
|
46
|
-
workspace,
|
|
47
|
-
title: `working tree @ ${workspace.branch}`,
|
|
48
|
-
});
|
|
49
|
-
client.close();
|
|
50
|
-
return runTui(review.id);
|
|
51
|
-
}
|
|
52
|
-
case "serve": {
|
|
53
|
-
const { positional, flags } = parseArgs(argv.slice(1));
|
|
54
|
-
const port = flagStr(flags, "port");
|
|
55
|
-
const { serveClient } = await import("@cueloop/client");
|
|
56
|
-
const handle = await serveClient({
|
|
57
|
-
port: port !== undefined ? Number(port) : undefined,
|
|
58
|
-
host: flagStr(flags, "host"),
|
|
59
|
-
sessionId: positional[0],
|
|
60
|
-
});
|
|
61
|
-
console.log(
|
|
62
|
-
[
|
|
63
|
-
"",
|
|
64
|
-
`observers join with: ssh -p ${handle.port} ${handle.host === "0.0.0.0" || handle.host === "::" ? "<this-host>" : handle.host}`,
|
|
65
|
-
"",
|
|
66
|
-
"no passwords, no keys: anyone who can reach this address can watch.",
|
|
67
|
-
"share the address deliberately (SSH tunnel, tailnet). observers are",
|
|
68
|
-
"read-only; you stay the one writable controller via `cueloop` locally.",
|
|
69
|
-
"ctrl-c stops serving.",
|
|
70
|
-
].join("\n"),
|
|
71
|
-
);
|
|
72
|
-
const stop = () => void handle.close().finally(() => process.exit(0));
|
|
73
|
-
process.on("SIGINT", stop);
|
|
74
|
-
process.on("SIGTERM", stop);
|
|
75
|
-
await new Promise(() => {}); // serve until signalled
|
|
76
|
-
return 0;
|
|
77
|
-
}
|
|
78
|
-
case "review": {
|
|
79
|
-
const { reviewCommand } = await import("./pr");
|
|
80
|
-
return reviewCommand(argv.slice(1));
|
|
81
|
-
}
|
|
82
|
-
case "review-post": {
|
|
83
|
-
const { reviewPostCommand } = await import("./pr");
|
|
84
|
-
return reviewPostCommand(argv.slice(1));
|
|
85
|
-
}
|
|
86
|
-
case "--help":
|
|
87
|
-
case "help":
|
|
88
|
-
printHelp();
|
|
89
|
-
return 0;
|
|
90
|
-
case undefined:
|
|
91
|
-
return runTui();
|
|
92
|
-
default: {
|
|
93
|
-
// `cueloop <session-id>` opens the TUI on that session
|
|
94
|
-
if (cmd.startsWith("ses_")) return runTui(cmd);
|
|
95
|
-
printHelp();
|
|
96
|
-
return 2;
|
|
97
|
-
}
|
|
128
|
+
if (cmd === undefined) return runTui();
|
|
129
|
+
const handler = commandHandlers[cmd];
|
|
130
|
+
|
|
131
|
+
if (handler !== undefined) return handler(argv.slice(1));
|
|
132
|
+
if (versionAliases.has(cmd)) {
|
|
133
|
+
console.log(CLI_VERSION);
|
|
134
|
+
|
|
135
|
+
return 0;
|
|
98
136
|
}
|
|
137
|
+
if (helpAliases.has(cmd)) {
|
|
138
|
+
printHelp();
|
|
139
|
+
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
if (cmd.startsWith("ses_")) return runTui(cmd);
|
|
143
|
+
printHelp();
|
|
144
|
+
|
|
145
|
+
return 2;
|
|
99
146
|
}
|
|
100
147
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The id-or-title selector for a primitive-first opener: the bare positional, or a
|
|
150
|
+
* value handed to `--open`/`--latest`. A bare `--latest`/`--open` flag carries
|
|
151
|
+
* no value, so the selector stays undefined and the opener defaults to the
|
|
152
|
+
* latest pending review.
|
|
153
|
+
*/
|
|
154
|
+
function openSelector(parsed: ParsedArgs): string | undefined {
|
|
155
|
+
return (
|
|
156
|
+
parsed.positional[0] ?? stringFlag(parsed.flags, "open") ?? stringFlag(parsed.flags, "latest")
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Resolve one review of the primitive's scope and open it in the TUI, or print the
|
|
162
|
+
* miss and fail. `emptyMessage` overrides the default no-pending line for the
|
|
163
|
+
* one caller that needs a scope-specific hint (a clean working tree).
|
|
164
|
+
*/
|
|
165
|
+
async function openReviewOfKind(
|
|
166
|
+
match: (session: Thread) => boolean,
|
|
167
|
+
label: string,
|
|
168
|
+
selector: string | undefined,
|
|
169
|
+
layout: "review" | "plan" | undefined,
|
|
170
|
+
emptyMessage?: string,
|
|
171
|
+
): Promise<number> {
|
|
172
|
+
const client = await DaemonClient.connect({ autostart: true });
|
|
173
|
+
let sessions: Thread[];
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
sessions = await client.sessionList();
|
|
177
|
+
} finally {
|
|
178
|
+
client.close();
|
|
179
|
+
}
|
|
180
|
+
const target = resolveOpenTarget(sessions, { match, selector });
|
|
181
|
+
|
|
182
|
+
if (target.kind === "session") return runTui(target.sessionId, layout);
|
|
183
|
+
if (target.kind === "no-pending" && emptyMessage !== undefined) {
|
|
184
|
+
console.error(emptyMessage);
|
|
185
|
+
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
console.error(openTargetMessage(label, target));
|
|
189
|
+
|
|
190
|
+
return 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** `cueloop plan [id|title]` - open the latest pending plan, or address one. */
|
|
194
|
+
async function planCommand(argv: string[]): Promise<number> {
|
|
195
|
+
return openReviewOfKind(isPlanReview, "plan", openSelector(parseArgs(argv)), "plan");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* `cueloop reply [id|title]` - open the latest pending reply review, or address
|
|
200
|
+
* one. A reply artifact is the agent's previous message, submitted for review
|
|
201
|
+
* by the /cueloop:reply skill; the opener is scope-only, like plan.
|
|
202
|
+
*/
|
|
203
|
+
async function replyCommand(argv: string[]): Promise<number> {
|
|
204
|
+
return openReviewOfKind(isReplyReview, "reply", openSelector(parseArgs(argv)), "plan");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* `cueloop prototype <file.md>` creates a review of a component design doc
|
|
209
|
+
* (API / Composition / Callstack); an `.html` file instead creates the opt-in
|
|
210
|
+
* experimental pixel mockup. A selector or `--open`/`--latest` (or a bare call
|
|
211
|
+
* with no file) opens the latest pending prototype review instead.
|
|
212
|
+
*/
|
|
213
|
+
async function prototypeCommand(argv: string[]): Promise<number> {
|
|
214
|
+
const parsed = parseArgs(argv);
|
|
215
|
+
const selector = openSelector(parsed);
|
|
216
|
+
const wantsOpen = "open" in parsed.flags || "latest" in parsed.flags;
|
|
217
|
+
// a markdown design doc is the default; an .html entry is the opt-in pixel mockup
|
|
218
|
+
const isHtmlPrototype = selector?.endsWith(".html") ?? false;
|
|
219
|
+
const isMarkdownPrototype = selector?.endsWith(".md") || selector?.endsWith(".markdown") || false;
|
|
220
|
+
const looksLikeFile =
|
|
221
|
+
selector !== undefined && !isSessionId(selector) && (isHtmlPrototype || isMarkdownPrototype);
|
|
222
|
+
|
|
223
|
+
if (wantsOpen || !looksLikeFile)
|
|
224
|
+
return openReviewOfKind(isPrototypeReview, "prototype", selector, "plan");
|
|
225
|
+
|
|
226
|
+
const path = resolve(selector);
|
|
227
|
+
const content = await Bun.file(path)
|
|
228
|
+
.text()
|
|
229
|
+
.catch(() => undefined);
|
|
230
|
+
|
|
231
|
+
if (content === undefined) {
|
|
232
|
+
console.error(`prototype: cannot read ${path}`);
|
|
233
|
+
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
const client = await DaemonClient.connect({ autostart: true });
|
|
237
|
+
const review = await openReview(client, {
|
|
238
|
+
type: "prototype",
|
|
239
|
+
content,
|
|
240
|
+
// only an HTML entry carries a prototypePath, the signal for the experimental pixel mode
|
|
241
|
+
prototypePath: isHtmlPrototype ? path : undefined,
|
|
242
|
+
title: basename(path),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
client.close();
|
|
246
|
+
|
|
247
|
+
return runTui(review.id, "plan");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* `cueloop diff` opens the repo's workbench in the review layout:
|
|
252
|
+
* - a selector (`cueloop diff <id|title>`) or an explicit `--open`/`--latest`
|
|
253
|
+
* opens a specific pending diff thread;
|
|
254
|
+
* - a bare `cueloop diff` find-or-creates the per-repo workbench thread - a live-diff
|
|
255
|
+
* annotation container - so it is immediately annotatable and always shows the
|
|
256
|
+
* current working tree, reusing the same thread across re-runs.
|
|
257
|
+
*/
|
|
258
|
+
async function diffCommand(argv: string[]): Promise<number> {
|
|
259
|
+
const parsed = parseArgs(argv);
|
|
260
|
+
const selector = openSelector(parsed);
|
|
261
|
+
const wantsOpen = selector !== undefined || "open" in parsed.flags || "latest" in parsed.flags;
|
|
262
|
+
|
|
263
|
+
if (wantsOpen) return openReviewOfKind(isDiffReview, "diff", selector, "review");
|
|
264
|
+
|
|
265
|
+
const client = await DaemonClient.connect({ autostart: true });
|
|
266
|
+
const workbench = await client.sessionWorkbench(process.cwd());
|
|
267
|
+
|
|
268
|
+
client.close();
|
|
269
|
+
|
|
270
|
+
return runTui(workbench.id, "review");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* `cueloop review` disambiguates create from open by intent:
|
|
275
|
+
* - bare `cueloop review`, `--open`/`--latest`, or a `ses_*` selector opens a
|
|
276
|
+
* pending PR review (a diff carrying a `pr` reference);
|
|
277
|
+
* - any other positional is a PR reference and takes the create path in pr.ts.
|
|
278
|
+
*/
|
|
279
|
+
async function reviewEntry(argv: string[]): Promise<number> {
|
|
280
|
+
const parsed = parseArgs(argv);
|
|
281
|
+
const explicitOpen = "open" in parsed.flags || "latest" in parsed.flags;
|
|
282
|
+
const selector = openSelector(parsed);
|
|
283
|
+
const looksLikeSessionId = selector !== undefined && isSessionId(selector);
|
|
284
|
+
const wantsCreate = !explicitOpen && !looksLikeSessionId && selector !== undefined;
|
|
285
|
+
|
|
286
|
+
if (wantsCreate) {
|
|
287
|
+
const { reviewCommand } = await import("./pr");
|
|
288
|
+
|
|
289
|
+
return reviewCommand(argv);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return openReviewOfKind(
|
|
293
|
+
isPrReview,
|
|
294
|
+
"PR",
|
|
295
|
+
explicitOpen || looksLikeSessionId ? selector : undefined,
|
|
296
|
+
"review",
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* A create-command dictates the pane composition it opens in; a bare launch (`layout`
|
|
302
|
+
* omitted) restores the remembered one. Resolve the factory here so the heavy client
|
|
303
|
+
* module stays lazily imported for non-TUI commands.
|
|
304
|
+
*/
|
|
305
|
+
async function runTui(sessionId?: string, layout?: "review" | "plan"): Promise<number> {
|
|
306
|
+
const { runClient, reviewLayout, planLayout } = await import("@cueloop/client");
|
|
307
|
+
const resolved =
|
|
308
|
+
layout === "review" ? reviewLayout() : layout === "plan" ? planLayout() : undefined;
|
|
309
|
+
|
|
310
|
+
return runClient({ sessionId, layout: resolved });
|
|
104
311
|
}
|
|
105
312
|
|
|
106
313
|
function printHelp(): void {
|
|
@@ -108,17 +315,44 @@ function printHelp(): void {
|
|
|
108
315
|
[
|
|
109
316
|
"cueloop - review surface for coding agents",
|
|
110
317
|
"",
|
|
111
|
-
"usage:",
|
|
318
|
+
"usage: cueloop [command] [options]",
|
|
319
|
+
"",
|
|
320
|
+
"common commands:",
|
|
112
321
|
" cueloop open the inbox",
|
|
113
|
-
" cueloop
|
|
114
|
-
" cueloop
|
|
322
|
+
" cueloop plan [id|title] open the latest pending plan review (or one by id/title)",
|
|
323
|
+
" cueloop reply [id|title] open the latest pending reply review (the agent's previous message)",
|
|
324
|
+
" cueloop diff [id|title] review your working tree (untracked files included);",
|
|
325
|
+
" with a clean tree, open the latest pending diff review",
|
|
115
326
|
" cueloop review <pr> review a pull request (--no-tui prints the session)",
|
|
116
|
-
" cueloop
|
|
327
|
+
" cueloop prototype <file.md> review a component design doc (or open the latest by id/title)",
|
|
328
|
+
"",
|
|
329
|
+
"share:",
|
|
117
330
|
" cueloop serve [session-id] share over ssh: observers are read-only,",
|
|
118
331
|
" you stay the controller (--port 2222, --host 127.0.0.1;",
|
|
119
332
|
" password-less - share the address deliberately)",
|
|
120
|
-
" cueloop
|
|
333
|
+
" cueloop share [session-id] hand a plan to a teammate: copies one",
|
|
334
|
+
" ssh line (--host cueloop.dev, --port 22)",
|
|
335
|
+
" cueloop share pull [session-id] pull a teammate's annotations back into the plan",
|
|
336
|
+
"",
|
|
337
|
+
"open a specific review:",
|
|
338
|
+
" cueloop <session-id> open one session",
|
|
339
|
+
" cueloop review [id|title] open the latest pending PR review (or one by id/title)",
|
|
340
|
+
" cueloop <plan|diff|review> --latest open the latest pending review of that type",
|
|
341
|
+
"",
|
|
342
|
+
"scripting:",
|
|
343
|
+
" cueloop session <primitive> [flags] script the daemon (create|get|list|wait|annotate|remove|cut|restore|curate|set-viewed|navigate|branch|switch|label|fork|name-self|events|send-message|bind-harness|pending-deliveries|acknowledge-delivery|submit-revision)",
|
|
344
|
+
" cueloop actions list list the quick-action vocabulary (for annotate --action)",
|
|
345
|
+
" cueloop refine mine past reviews into a markdown report + writeback proposals",
|
|
346
|
+
" cueloop update [--dry-run] update the installed cueloop binary (--dry-run reports the target only)",
|
|
347
|
+
" cueloop wake <id> [--harness codex --thread <id>] resume the agent with the message (spawn detached)",
|
|
348
|
+
" cueloop review-post <id> <pr> post a resolved session's message back to the PR",
|
|
121
349
|
" cueloop daemon run the daemon in the foreground",
|
|
350
|
+
" cueloop stop stop the local daemon",
|
|
351
|
+
" cueloop restart stop the local daemon and start a fresh one",
|
|
352
|
+
" cueloop dev open the TUI on an isolated home seeded with example threads",
|
|
353
|
+
"",
|
|
354
|
+
" cueloop -v, --version print the installed version",
|
|
355
|
+
" cueloop -h, --help print this help",
|
|
122
356
|
].join("\n"),
|
|
123
357
|
);
|
|
124
358
|
}
|