cueloop 0.1.0-alpha.5 → 0.1.0-alpha.50
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 +24 -23
- package/src/actions-command.ts +44 -0
- package/src/args.ts +13 -10
- package/src/main.ts +224 -33
- package/src/open-target.test.ts +233 -0
- package/src/open-target.ts +120 -0
- package/src/pr.ts +16 -6
- package/src/refine-command.test.ts +117 -0
- package/src/refine-command.ts +241 -0
- package/src/session-commands.ts +154 -0
- package/src/share-command.test.ts +220 -0
- package/src/share-command.ts +133 -0
- package/src/version.ts +5 -0
- package/src/wake-command.test.ts +14 -0
- package/src/wake-command.ts +37 -0
- package/src/session-cmds.ts +0 -114
- package/src/workspace.ts +0 -35
package/package.json
CHANGED
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cueloop",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.50",
|
|
4
|
+
"description": "Terminal review surface for coding agents: annotate plans and diffs, return a verdict, send structured feedback back",
|
|
5
|
+
"homepage": "https://github.com/mmurakaru/cueloop#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/mmurakaru/cueloop/issues"
|
|
8
|
+
},
|
|
4
9
|
"license": "Apache-2.0",
|
|
5
|
-
"
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/mmurakaru/cueloop.git",
|
|
13
|
+
"directory": "packages/cli"
|
|
14
|
+
},
|
|
6
15
|
"bin": {
|
|
7
16
|
"cueloop": "./src/main.ts"
|
|
8
17
|
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
9
23
|
"exports": {
|
|
10
24
|
".": "./src/main.ts"
|
|
11
25
|
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"@cueloop/schema": "0.1.0-alpha.5",
|
|
14
|
-
"@cueloop/daemon": "0.1.0-alpha.5",
|
|
15
|
-
"@cueloop/client": "0.1.0-alpha.5"
|
|
16
|
-
},
|
|
17
26
|
"publishConfig": {
|
|
18
27
|
"access": "public"
|
|
19
28
|
},
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@cueloop/adapters": "0.1.0-alpha.50",
|
|
31
|
+
"@cueloop/client": "0.1.0-alpha.50",
|
|
32
|
+
"@cueloop/daemon": "0.1.0-alpha.50",
|
|
33
|
+
"@cueloop/schema": "0.1.0-alpha.50"
|
|
34
|
+
},
|
|
24
35
|
"engines": {
|
|
25
36
|
"bun": ">=1.3.0"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
"type": "git",
|
|
29
|
-
"url": "git+https://github.com/mmurakaru/cueloop.git",
|
|
30
|
-
"directory": "packages/cli"
|
|
31
|
-
},
|
|
32
|
-
"homepage": "https://github.com/mmurakaru/cueloop#readme",
|
|
33
|
-
"bugs": {
|
|
34
|
-
"url": "https://github.com/mmurakaru/cueloop/issues"
|
|
35
|
-
},
|
|
36
|
-
"description": "Terminal review surface for coding agents: annotate plans and diffs, return a verdict, send structured feedback back"
|
|
37
|
-
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cueloop actions list [--session <id>]` - print the quick-action vocabulary
|
|
3
|
+
* (the same presets a human picks in the marker popover) so a review-side agent
|
|
4
|
+
* can reference one by name or index via `session annotate --action`. With
|
|
5
|
+
* `--session`, the vocabulary comes from that session's repo (matching what
|
|
6
|
+
* `--action` will resolve); otherwise from the caller's cwd. JSON on stdout.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { loadConfig } from "@cueloop/client/config";
|
|
10
|
+
import { DaemonClient } from "@cueloop/daemon/client";
|
|
11
|
+
import { parseArgs, stringFlag } from "./args";
|
|
12
|
+
|
|
13
|
+
export async function actionsCommand(argv: string[]): Promise<number> {
|
|
14
|
+
const { positional, flags } = parseArgs(argv);
|
|
15
|
+
const verb = positional[0] ?? "list";
|
|
16
|
+
if (verb !== "list") {
|
|
17
|
+
console.error("usage: cueloop actions list [--session <id>]");
|
|
18
|
+
return 2;
|
|
19
|
+
}
|
|
20
|
+
const role = stringFlag(flags, "role") === "agent" ? "agent" : undefined;
|
|
21
|
+
const repoRoot = await repoRootForSession(stringFlag(flags, "session"), role);
|
|
22
|
+
const actions = loadConfig({ repoRoot }).actions;
|
|
23
|
+
const numbered = actions.map((action, index) => ({
|
|
24
|
+
index: index + 1,
|
|
25
|
+
prompt: action.prompt,
|
|
26
|
+
metadata: action.metadata,
|
|
27
|
+
}));
|
|
28
|
+
console.log(JSON.stringify(numbered, null, 2));
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The repo the given session lives in, or the caller's cwd when no session is named. */
|
|
33
|
+
async function repoRootForSession(
|
|
34
|
+
sessionId: string | undefined,
|
|
35
|
+
role: "agent" | undefined,
|
|
36
|
+
): Promise<string> {
|
|
37
|
+
if (sessionId === undefined) return process.cwd();
|
|
38
|
+
const client = await DaemonClient.connect({ autostart: true, role });
|
|
39
|
+
try {
|
|
40
|
+
return (await client.sessionGet(sessionId)).workspace.repoRoot;
|
|
41
|
+
} finally {
|
|
42
|
+
client.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/args.ts
CHANGED
|
@@ -9,16 +9,16 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
9
9
|
const positional: string[] = [];
|
|
10
10
|
const flags: Record<string, string | boolean> = {};
|
|
11
11
|
for (let i = 0; i < argv.length; i++) {
|
|
12
|
-
const
|
|
13
|
-
if (!
|
|
14
|
-
positional.push(
|
|
12
|
+
const argument = argv[i]!;
|
|
13
|
+
if (!argument.startsWith("--")) {
|
|
14
|
+
positional.push(argument);
|
|
15
15
|
continue;
|
|
16
16
|
}
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
flags[
|
|
17
|
+
const equalsIndex = argument.indexOf("=");
|
|
18
|
+
if (equalsIndex !== -1) {
|
|
19
|
+
flags[argument.slice(2, equalsIndex)] = argument.slice(equalsIndex + 1);
|
|
20
20
|
} else {
|
|
21
|
-
const key =
|
|
21
|
+
const key = argument.slice(2);
|
|
22
22
|
const next = argv[i + 1];
|
|
23
23
|
if (next !== undefined && !next.startsWith("--")) {
|
|
24
24
|
flags[key] = next;
|
|
@@ -31,7 +31,10 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
31
31
|
return { positional, flags };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
export function stringFlag(
|
|
35
|
+
flags: Record<string, string | boolean>,
|
|
36
|
+
key: string,
|
|
37
|
+
): string | undefined {
|
|
38
|
+
const value = flags[key];
|
|
39
|
+
return typeof value === "string" ? value : undefined;
|
|
37
40
|
}
|
package/src/main.ts
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
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 verb-first
|
|
4
|
+
* openers `cueloop plan|diff|review` open the latest pending review of that
|
|
5
|
+
* 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
|
+
isSessionId,
|
|
19
|
+
openTargetMessage,
|
|
20
|
+
resolveOpenTarget,
|
|
21
|
+
} from "./open-target";
|
|
22
|
+
import { sessionCommand } from "./session-commands";
|
|
23
|
+
import { CLI_VERSION } from "./version";
|
|
13
24
|
import { DaemonClient } from "@cueloop/daemon/client";
|
|
25
|
+
import { workingTreeDiff } from "@cueloop/daemon/working-tree";
|
|
26
|
+
import type { ReviewSession } from "@cueloop/schema";
|
|
27
|
+
import { openReview, resolveWorkspace } from "@cueloop/daemon/review";
|
|
14
28
|
|
|
15
29
|
const argv = process.argv.slice(2);
|
|
16
30
|
const cmd = argv[0];
|
|
@@ -31,29 +45,19 @@ async function main(): Promise<number> {
|
|
|
31
45
|
await new Promise(() => {}); // run until signalled
|
|
32
46
|
return 0;
|
|
33
47
|
}
|
|
34
|
-
case "
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
const client = await DaemonClient.connect({ autostart: true });
|
|
42
|
-
const session = await client.sessionCreate(workspace, {
|
|
43
|
-
type: "diff",
|
|
44
|
-
content: diff,
|
|
45
|
-
meta: { title: `working tree @ ${workspace.branch}`, cwd: process.cwd() },
|
|
46
|
-
});
|
|
47
|
-
client.close();
|
|
48
|
-
return runTui(session.id);
|
|
49
|
-
}
|
|
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));
|
|
50
54
|
case "serve": {
|
|
51
55
|
const { positional, flags } = parseArgs(argv.slice(1));
|
|
52
|
-
const port =
|
|
56
|
+
const port = stringFlag(flags, "port");
|
|
53
57
|
const { serveClient } = await import("@cueloop/client");
|
|
54
58
|
const handle = await serveClient({
|
|
55
59
|
port: port !== undefined ? Number(port) : undefined,
|
|
56
|
-
host:
|
|
60
|
+
host: stringFlag(flags, "host"),
|
|
57
61
|
sessionId: positional[0],
|
|
58
62
|
});
|
|
59
63
|
console.log(
|
|
@@ -73,14 +77,42 @@ async function main(): Promise<number> {
|
|
|
73
77
|
await new Promise(() => {}); // serve until signalled
|
|
74
78
|
return 0;
|
|
75
79
|
}
|
|
76
|
-
case "
|
|
77
|
-
const {
|
|
78
|
-
|
|
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));
|
|
79
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));
|
|
80
106
|
case "review-post": {
|
|
81
107
|
const { reviewPostCommand } = await import("./pr");
|
|
82
108
|
return reviewPostCommand(argv.slice(1));
|
|
83
109
|
}
|
|
110
|
+
case "-v":
|
|
111
|
+
case "--version":
|
|
112
|
+
case "version":
|
|
113
|
+
console.log(CLI_VERSION);
|
|
114
|
+
return 0;
|
|
115
|
+
case "-h":
|
|
84
116
|
case "--help":
|
|
85
117
|
case "help":
|
|
86
118
|
printHelp();
|
|
@@ -96,6 +128,143 @@ async function main(): Promise<number> {
|
|
|
96
128
|
}
|
|
97
129
|
}
|
|
98
130
|
|
|
131
|
+
/**
|
|
132
|
+
* The id-or-title selector for a verb-first opener: the bare positional, or a
|
|
133
|
+
* value handed to `--open`/`--latest`. A bare `--latest`/`--open` flag carries
|
|
134
|
+
* no value, so the selector stays undefined and the opener defaults to the
|
|
135
|
+
* latest pending review.
|
|
136
|
+
*/
|
|
137
|
+
function openSelector(parsed: ParsedArgs): string | undefined {
|
|
138
|
+
return (
|
|
139
|
+
parsed.positional[0] ?? stringFlag(parsed.flags, "open") ?? stringFlag(parsed.flags, "latest")
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolve one review of the verb's scope and open it in the TUI, or print the
|
|
145
|
+
* miss and fail. `emptyMessage` overrides the default no-pending line for the
|
|
146
|
+
* one caller that needs a scope-specific hint (a clean working tree).
|
|
147
|
+
*/
|
|
148
|
+
async function openReviewOfKind(
|
|
149
|
+
match: (session: ReviewSession) => boolean,
|
|
150
|
+
label: string,
|
|
151
|
+
selector: string | undefined,
|
|
152
|
+
emptyMessage?: string,
|
|
153
|
+
): Promise<number> {
|
|
154
|
+
const client = await DaemonClient.connect({ autostart: true });
|
|
155
|
+
let sessions: ReviewSession[];
|
|
156
|
+
try {
|
|
157
|
+
sessions = await client.sessionList();
|
|
158
|
+
} finally {
|
|
159
|
+
client.close();
|
|
160
|
+
}
|
|
161
|
+
const target = resolveOpenTarget(sessions, { match, selector });
|
|
162
|
+
if (target.kind === "session") return runTui(target.sessionId);
|
|
163
|
+
if (target.kind === "no-pending" && emptyMessage !== undefined) {
|
|
164
|
+
console.error(emptyMessage);
|
|
165
|
+
return 1;
|
|
166
|
+
}
|
|
167
|
+
console.error(openTargetMessage(label, target));
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** `cueloop plan [id|title]` - open the latest pending plan, or address one. */
|
|
172
|
+
async function planCommand(argv: string[]): Promise<number> {
|
|
173
|
+
return openReviewOfKind(isPlanReview, "plan", openSelector(parseArgs(argv)));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* `cueloop prototype <file.html>` creates a review of a rendered HTML file;
|
|
178
|
+
* a selector or `--open`/`--latest` (or a bare call with no file) opens the
|
|
179
|
+
* latest pending prototype review instead.
|
|
180
|
+
*/
|
|
181
|
+
async function prototypeCommand(argv: string[]): Promise<number> {
|
|
182
|
+
const parsed = parseArgs(argv);
|
|
183
|
+
const selector = openSelector(parsed);
|
|
184
|
+
const wantsOpen = "open" in parsed.flags || "latest" in parsed.flags;
|
|
185
|
+
const looksLikeFile =
|
|
186
|
+
selector !== undefined && !isSessionId(selector) && selector.endsWith(".html");
|
|
187
|
+
if (wantsOpen || !looksLikeFile)
|
|
188
|
+
return openReviewOfKind(isPrototypeReview, "prototype", selector);
|
|
189
|
+
|
|
190
|
+
const path = resolve(selector);
|
|
191
|
+
const html = await Bun.file(path)
|
|
192
|
+
.text()
|
|
193
|
+
.catch(() => undefined);
|
|
194
|
+
if (html === undefined) {
|
|
195
|
+
console.error(`prototype: cannot read ${path}`);
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
const client = await DaemonClient.connect({ autostart: true });
|
|
199
|
+
const review = await openReview(client, {
|
|
200
|
+
type: "prototype",
|
|
201
|
+
content: html,
|
|
202
|
+
prototypePath: path,
|
|
203
|
+
title: basename(path),
|
|
204
|
+
});
|
|
205
|
+
client.close();
|
|
206
|
+
return runTui(review.id);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* `cueloop diff` disambiguates create from open by intent:
|
|
211
|
+
* - a selector (`cueloop diff <id|title>`) or an explicit `--open`/`--latest`
|
|
212
|
+
* opens a pending diff review;
|
|
213
|
+
* - otherwise a dirty working tree still creates a review as before;
|
|
214
|
+
* - a clean working tree carries no create input, so it opens the latest
|
|
215
|
+
* pending diff review instead of erroring.
|
|
216
|
+
*/
|
|
217
|
+
async function diffCommand(argv: string[]): Promise<number> {
|
|
218
|
+
const parsed = parseArgs(argv);
|
|
219
|
+
const selector = openSelector(parsed);
|
|
220
|
+
const wantsOpen = selector !== undefined || "open" in parsed.flags || "latest" in parsed.flags;
|
|
221
|
+
if (wantsOpen) return openReviewOfKind(isDiffReview, "diff", selector);
|
|
222
|
+
|
|
223
|
+
const workspace = await resolveWorkspace();
|
|
224
|
+
const diff = await workingTreeDiff();
|
|
225
|
+
if (!diff.patch.trim()) {
|
|
226
|
+
return openReviewOfKind(
|
|
227
|
+
isDiffReview,
|
|
228
|
+
"diff",
|
|
229
|
+
undefined,
|
|
230
|
+
"working tree is clean and no pending diff review - nothing to open",
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
const client = await DaemonClient.connect({ autostart: true });
|
|
234
|
+
const review = await openReview(client, {
|
|
235
|
+
type: "diff",
|
|
236
|
+
content: diff.patch,
|
|
237
|
+
files: diff.files,
|
|
238
|
+
workspace,
|
|
239
|
+
title: `working tree @ ${workspace.branch}`,
|
|
240
|
+
});
|
|
241
|
+
client.close();
|
|
242
|
+
return runTui(review.id);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* `cueloop review` disambiguates create from open by intent:
|
|
247
|
+
* - bare `cueloop review`, `--open`/`--latest`, or a `ses_*` selector opens a
|
|
248
|
+
* pending PR review (a diff carrying a `pr` reference);
|
|
249
|
+
* - any other positional is a PR reference and takes the create path in pr.ts.
|
|
250
|
+
*/
|
|
251
|
+
async function reviewEntry(argv: string[]): Promise<number> {
|
|
252
|
+
const parsed = parseArgs(argv);
|
|
253
|
+
const explicitOpen = "open" in parsed.flags || "latest" in parsed.flags;
|
|
254
|
+
const selector = openSelector(parsed);
|
|
255
|
+
const looksLikeSessionId = selector !== undefined && isSessionId(selector);
|
|
256
|
+
const wantsCreate = !explicitOpen && !looksLikeSessionId && selector !== undefined;
|
|
257
|
+
if (wantsCreate) {
|
|
258
|
+
const { reviewCommand } = await import("./pr");
|
|
259
|
+
return reviewCommand(argv);
|
|
260
|
+
}
|
|
261
|
+
return openReviewOfKind(
|
|
262
|
+
isPrReview,
|
|
263
|
+
"PR",
|
|
264
|
+
explicitOpen || looksLikeSessionId ? selector : undefined,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
99
268
|
async function runTui(sessionId?: string): Promise<number> {
|
|
100
269
|
const { runClient } = await import("@cueloop/client");
|
|
101
270
|
return runClient({ sessionId });
|
|
@@ -106,17 +275,39 @@ function printHelp(): void {
|
|
|
106
275
|
[
|
|
107
276
|
"cueloop - review surface for coding agents",
|
|
108
277
|
"",
|
|
109
|
-
"usage:",
|
|
278
|
+
"usage: cueloop [command] [options]",
|
|
279
|
+
"",
|
|
280
|
+
"common commands:",
|
|
110
281
|
" cueloop open the inbox",
|
|
111
|
-
" cueloop
|
|
112
|
-
" cueloop diff
|
|
282
|
+
" cueloop plan [id|title] open the latest pending plan review (or one by id/title)",
|
|
283
|
+
" cueloop diff [id|title] review your working tree (untracked files included);",
|
|
284
|
+
" with a clean tree, open the latest pending diff review",
|
|
113
285
|
" cueloop review <pr> review a pull request (--no-tui prints the session)",
|
|
114
|
-
" cueloop
|
|
286
|
+
" cueloop prototype <file.html> review a rendered HTML prototype (or open the latest by id/title)",
|
|
287
|
+
"",
|
|
288
|
+
"share:",
|
|
115
289
|
" cueloop serve [session-id] share over ssh: observers are read-only,",
|
|
116
290
|
" you stay the controller (--port 2222, --host 127.0.0.1;",
|
|
117
291
|
" password-less - share the address deliberately)",
|
|
292
|
+
" cueloop share [session-id] hand a plan to a teammate: copies one",
|
|
293
|
+
" ssh line (--host cueloop.dev, --port 22)",
|
|
294
|
+
" cueloop share pull [session-id] pull a teammate's annotations back into the plan",
|
|
295
|
+
"",
|
|
296
|
+
"open a specific review:",
|
|
297
|
+
" cueloop <session-id> open one session",
|
|
298
|
+
" cueloop review [id|title] open the latest pending PR review (or one by id/title)",
|
|
299
|
+
" cueloop <plan|diff|review> --latest open the latest pending review of that type",
|
|
300
|
+
"",
|
|
301
|
+
"scripting:",
|
|
118
302
|
" cueloop session <verb> [flags] script the daemon (create|get|list|wait|annotate|resolve|submit-revision)",
|
|
303
|
+
" cueloop actions list list the quick-action vocabulary (for annotate --action)",
|
|
304
|
+
" cueloop refine mine past reviews into a markdown report + writeback proposals",
|
|
305
|
+
" cueloop wake <id> [--harness codex --thread <id>] resume the agent with the verdict (spawn detached)",
|
|
306
|
+
" cueloop review-post <id> <pr> post a resolved session's verdict back to the PR",
|
|
119
307
|
" cueloop daemon run the daemon in the foreground",
|
|
308
|
+
"",
|
|
309
|
+
" cueloop -v, --version print the installed version",
|
|
310
|
+
" cueloop -h, --help print this help",
|
|
120
311
|
].join("\n"),
|
|
121
312
|
);
|
|
122
313
|
}
|