cueloop 0.1.0-alpha.62 → 0.1.0-alpha.64

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.62",
3
+ "version": "0.1.0-alpha.64",
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,11 @@
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.62",
34
- "@cueloop/client": "0.1.0-alpha.62",
35
- "@cueloop/daemon": "0.1.0-alpha.62",
36
- "@cueloop/schema": "0.1.0-alpha.62"
33
+ "@cueloop/adapters": "0.1.0-alpha.64",
34
+ "@cueloop/client": "0.1.0-alpha.64",
35
+ "@cueloop/daemon": "0.1.0-alpha.64",
36
+ "@cueloop/schema": "0.1.0-alpha.64",
37
+ "valibot": "^1.4.2"
37
38
  },
38
39
  "engines": {
39
40
  "bun": ">=1.3.0"
package/src/args.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /** Tiny flag parser: --key value, --key=value, boolean --flag, positionals. */
2
2
 
3
+ import * as v from "valibot";
4
+
3
5
  export interface ParsedArgs {
4
6
  positional: string[];
5
7
  flags: Record<string, string | boolean>;
@@ -40,7 +42,7 @@ export function stringFlag(
40
42
  flags: Record<string, string | boolean>,
41
43
  key: string,
42
44
  ): string | undefined {
43
- const value = flags[key];
45
+ const result = v.safeParse(v.string(), flags[key]);
44
46
 
45
- return typeof value === "string" ? value : undefined;
47
+ return result.success ? result.output : undefined;
46
48
  }
package/src/main.ts CHANGED
@@ -98,7 +98,11 @@ async function shareEntry(rest: string[]): Promise<number> {
98
98
 
99
99
  type CommandHandler = (rest: string[]) => number | Promise<number>;
100
100
 
101
- const commandHandlers: Record<string, CommandHandler> = {
101
+ interface CommandHandlers {
102
+ [command: string]: CommandHandler;
103
+ }
104
+
105
+ const commandHandlers: CommandHandlers = {
102
106
  session: (rest) => sessionCommand(rest),
103
107
  daemon: (rest) => daemonCommand(rest),
104
108
  plan: (rest) => planCommand(rest),
@@ -4,10 +4,11 @@
4
4
  * JSON on stdout; exit code 0 unless the daemon returned an error.
5
5
  */
6
6
 
7
- import { newAnnotationId, type ArtifactType, type VerdictKind } from "@cueloop/schema";
7
+ import { ARTIFACT_TYPES, isArtifactType, newAnnotationId } from "@cueloop/schema";
8
+ import * as v from "valibot";
8
9
  import { DaemonClient } from "@cueloop/daemon/client";
9
10
  import { openHerdrPaneForReview } from "@cueloop/daemon/herdr-pane";
10
- import { openReview, verdictResponse, type ReviewNote } from "@cueloop/daemon/review";
11
+ import { openReview, verdictResponse } from "@cueloop/daemon/review";
11
12
  import { loadConfig, quickActionBody, resolveQuickAction } from "@cueloop/client/config";
12
13
  import { parseArgs, stringFlag } from "./args";
13
14
 
@@ -15,24 +16,34 @@ async function readStdin(): Promise<string> {
15
16
  return await new Response(Bun.stdin.stream()).text();
16
17
  }
17
18
 
18
- function out(value: unknown): void {
19
+ function out(value: Parameters<typeof JSON.stringify>[0]): void {
19
20
  console.log(JSON.stringify(value, null, 2));
20
21
  }
21
22
 
23
+ const ReviewNotesSchema = v.array(v.object({ path: v.string(), body: v.string() }));
24
+ const SessionStatusSchema = v.picklist(["pending", "resolved"]);
25
+ const VerdictKindSchema = v.picklist(["comment", "approve", "request_changes"]);
26
+
22
27
  type SessionFlags = Record<string, string | boolean>;
23
28
  type SessionContext = { client: DaemonClient; positional: string[]; flags: SessionFlags };
24
29
  type SessionVerbHandler = (context: SessionContext) => Promise<number>;
25
30
 
26
31
  async function sessionCreate({ client, flags }: SessionContext): Promise<number> {
32
+ const type = stringFlag(flags, "type") ?? "plan";
33
+
34
+ if (!isArtifactType(type)) {
35
+ console.error(`unknown artifact type "${type}" - one of: ${ARTIFACT_TYPES.join(", ")}`);
36
+ return 2;
37
+ }
27
38
  const contentFile = stringFlag(flags, "content-file");
28
39
  const content = contentFile ? await Bun.file(contentFile).text() : await readStdin();
29
40
  // per-file agent notes for diff sessions: a JSON array of { path, body }
30
41
  const notesFile = stringFlag(flags, "notes-file");
31
42
  const notes = notesFile
32
- ? (JSON.parse(await Bun.file(notesFile).text()) as ReviewNote[])
43
+ ? v.parse(ReviewNotesSchema, JSON.parse(await Bun.file(notesFile).text()))
33
44
  : undefined;
34
45
  const review = await openReview(client, {
35
- type: (stringFlag(flags, "type") ?? "plan") as ArtifactType,
46
+ type,
36
47
  content,
37
48
  cwd: stringFlag(flags, "cwd"),
38
49
  agent: stringFlag(flags, "agent"),
@@ -57,7 +68,8 @@ async function sessionGetCommand({ client, positional }: SessionContext): Promis
57
68
  }
58
69
 
59
70
  async function sessionListCommand({ client, flags }: SessionContext): Promise<number> {
60
- const status = stringFlag(flags, "status") as "pending" | "resolved" | undefined;
71
+ const rawStatus = stringFlag(flags, "status");
72
+ const status = rawStatus ? v.parse(SessionStatusSchema, rawStatus) : undefined;
61
73
 
62
74
  out(await client.sessionList(status ? { status } : undefined));
63
75
 
@@ -103,7 +115,7 @@ async function sessionAnnotateCommand({
103
115
  suffix: stringFlag(flags, "suffix") ?? "",
104
116
  },
105
117
  body,
106
- ...(author ? { author } : {}),
118
+ author,
107
119
  },
108
120
  stringFlag(flags, "author-name"),
109
121
  ),
@@ -118,7 +130,7 @@ async function sessionResolveCommand({
118
130
  flags,
119
131
  }: SessionContext): Promise<number> {
120
132
  const id = required(positional[1], "session id");
121
- const kind = required(stringFlag(flags, "verdict"), "--verdict") as VerdictKind;
133
+ const kind = v.parse(VerdictKindSchema, required(stringFlag(flags, "verdict"), "--verdict"));
122
134
 
123
135
  out(await client.sessionResolve(id, kind, stringFlag(flags, "summary") ?? ""));
124
136
 
@@ -144,7 +156,11 @@ async function sessionSubmitRevisionCommand({
144
156
  return 0;
145
157
  }
146
158
 
147
- const sessionVerbHandlers: Record<string, SessionVerbHandler> = {
159
+ interface SessionVerbHandlers {
160
+ [verb: string]: SessionVerbHandler;
161
+ }
162
+
163
+ const sessionVerbHandlers: SessionVerbHandlers = {
148
164
  create: sessionCreate,
149
165
  get: sessionGetCommand,
150
166
  list: sessionListCommand,
@@ -30,11 +30,20 @@ function annotationFixture(id: string, author?: string): Annotation {
30
30
  return author ? { ...base, author } : base;
31
31
  }
32
32
 
33
+ const unimplemented = (member: string) => () =>
34
+ Promise.reject(new Error(`fakeClient does not implement ${member}`));
35
+
33
36
  /** A SessionClient that answers get/list from a fixed list and records the share/merge verbs. */
34
37
  function fakeClient(sessions: ReviewSession[]): SessionClient {
35
38
  return {
39
+ onEvent: () => () => {},
40
+ subscribe: async () => {},
36
41
  sessionGet: async (id: string) => sessions.find((session) => session.id === id)!,
37
42
  sessionList: async () => sessions,
43
+ sessionAnnotate: unimplemented("sessionAnnotate"),
44
+ sessionRemoveAnnotation: unimplemented("sessionRemoveAnnotation"),
45
+ sessionSetWorkingCopy: unimplemented("sessionSetWorkingCopy"),
46
+ sessionSetViewed: unimplemented("sessionSetViewed"),
38
47
  sessionSetShareId: mock(async (id: string, shareId: string) => {
39
48
  const session = sessions.find((candidate) => candidate.id === id)!;
40
49
 
@@ -51,7 +60,11 @@ function fakeClient(sessions: ReviewSession[]): SessionClient {
51
60
 
52
61
  return session;
53
62
  }),
54
- } as unknown as SessionClient;
63
+ sessionDelete: unimplemented("sessionDelete"),
64
+ sessionSetSelfName: unimplemented("sessionSetSelfName"),
65
+ sessionResolve: unimplemented("sessionResolve"),
66
+ close: () => {},
67
+ };
55
68
  }
56
69
 
57
70
  function depsSpy(overrides: Partial<ShareDeps> = {}): ShareDeps & { lines: string[] } {