cueloop 0.1.0-alpha.45 → 0.1.0-alpha.46

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.45",
3
+ "version": "0.1.0-alpha.46",
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": {
@@ -27,10 +27,10 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@cueloop/adapters": "0.1.0-alpha.45",
31
- "@cueloop/client": "0.1.0-alpha.45",
32
- "@cueloop/daemon": "0.1.0-alpha.45",
33
- "@cueloop/schema": "0.1.0-alpha.45"
30
+ "@cueloop/adapters": "0.1.0-alpha.46",
31
+ "@cueloop/client": "0.1.0-alpha.46",
32
+ "@cueloop/daemon": "0.1.0-alpha.46",
33
+ "@cueloop/schema": "0.1.0-alpha.46"
34
34
  },
35
35
  "engines": {
36
36
  "bun": ">=1.3.0"
package/src/main.ts CHANGED
@@ -8,10 +8,12 @@
8
8
  * observers), `cueloop daemon` runs the daemon in the foreground.
9
9
  */
10
10
 
11
+ import { basename, resolve } from "node:path";
11
12
  import { parseArgs, stringFlag, type ParsedArgs } from "./args";
12
13
  import {
13
14
  isDiffReview,
14
15
  isPlanReview,
16
+ isPrototypeReview,
15
17
  isPrReview,
16
18
  isSessionId,
17
19
  openTargetMessage,
@@ -47,6 +49,8 @@ async function main(): Promise<number> {
47
49
  return planCommand(argv.slice(1));
48
50
  case "diff":
49
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
56
  const port = stringFlag(flags, "port");
@@ -165,6 +169,39 @@ async function planCommand(argv: string[]): Promise<number> {
165
169
  return openReviewOfKind(isPlanReview, "plan", openSelector(parseArgs(argv)));
166
170
  }
167
171
 
172
+ /**
173
+ * `cueloop prototype <file.html>` creates a review of a rendered HTML file;
174
+ * a selector or `--open`/`--latest` (or a bare call with no file) opens the
175
+ * latest pending prototype review instead.
176
+ */
177
+ async function prototypeCommand(argv: string[]): Promise<number> {
178
+ const parsed = parseArgs(argv);
179
+ const selector = openSelector(parsed);
180
+ const wantsOpen = "open" in parsed.flags || "latest" in parsed.flags;
181
+ const looksLikeFile =
182
+ selector !== undefined && !isSessionId(selector) && selector.endsWith(".html");
183
+ if (wantsOpen || !looksLikeFile)
184
+ return openReviewOfKind(isPrototypeReview, "prototype", selector);
185
+
186
+ const path = resolve(selector);
187
+ const html = await Bun.file(path)
188
+ .text()
189
+ .catch(() => undefined);
190
+ if (html === undefined) {
191
+ console.error(`prototype: cannot read ${path}`);
192
+ return 1;
193
+ }
194
+ const client = await DaemonClient.connect({ autostart: true });
195
+ const review = await openReview(client, {
196
+ type: "prototype",
197
+ content: html,
198
+ prototypePath: path,
199
+ title: basename(path),
200
+ });
201
+ client.close();
202
+ return runTui(review.id);
203
+ }
204
+
168
205
  /**
169
206
  * `cueloop diff` disambiguates create from open by intent:
170
207
  * - a selector (`cueloop diff <id|title>`) or an explicit `--open`/`--latest`
@@ -242,6 +279,7 @@ function printHelp(): void {
242
279
  " cueloop diff [id|title] review your working tree (untracked files included);",
243
280
  " with a clean tree, open the latest pending diff review",
244
281
  " cueloop review <pr> review a pull request (--no-tui prints the session)",
282
+ " cueloop prototype <file.html> review a rendered HTML prototype (or open the latest by id/title)",
245
283
  "",
246
284
  "share:",
247
285
  " cueloop serve [session-id] share over ssh: observers are read-only,",
@@ -5,6 +5,7 @@ import type { ArtifactType, ReviewSession } from "@cueloop/schema";
5
5
  import {
6
6
  isDiffReview,
7
7
  isPlanReview,
8
+ isPrototypeReview,
8
9
  isPrReview,
9
10
  isSessionId,
10
11
  openTargetMessage,
@@ -59,6 +60,12 @@ describe("verb scope predicates are disjoint", () => {
59
60
  expect(isPrReview(session({ id: "ses_p", type: "plan" }))).toBe(false);
60
61
  });
61
62
 
63
+ test("isPrototypeReview matches only prototype artifacts", () => {
64
+ expect(isPrototypeReview(session({ id: "ses_x", type: "prototype" }))).toBe(true);
65
+ expect(isPrototypeReview(session({ id: "ses_p", type: "plan" }))).toBe(false);
66
+ expect(isPrototypeReview(session({ id: "ses_d", type: "diff" }))).toBe(false);
67
+ });
68
+
62
69
  test("a PR review resolves under exactly one scope - never both diff and review", () => {
63
70
  // Arrange
64
71
  const prReview = session({ id: "ses_pr", type: "diff", pr: "42" });
@@ -28,6 +28,9 @@ import type { ReviewSession } from "@cueloop/schema";
28
28
  export function isPlanReview(session: ReviewSession): boolean {
29
29
  return session.artifact.type === "plan";
30
30
  }
31
+ export function isPrototypeReview(session: ReviewSession): boolean {
32
+ return session.artifact.type === "prototype";
33
+ }
31
34
  export function isDiffReview(session: ReviewSession): boolean {
32
35
  return session.artifact.type === "diff" && session.artifact.meta.pr === undefined;
33
36
  }
@@ -43,6 +43,7 @@ export async function sessionCommand(argv: string[]): Promise<number> {
43
43
  agent: stringFlag(flags, "agent"),
44
44
  agentSessionId: stringFlag(flags, "agent-session-id"),
45
45
  planPath: stringFlag(flags, "plan-path"),
46
+ prototypePath: stringFlag(flags, "prototype-path"),
46
47
  title: stringFlag(flags, "title"),
47
48
  notes,
48
49
  });