cueloop 0.1.0-alpha.63 → 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.63",
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.63",
34
- "@cueloop/client": "0.1.0-alpha.63",
35
- "@cueloop/daemon": "0.1.0-alpha.63",
36
- "@cueloop/schema": "0.1.0-alpha.63"
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 { ARTIFACT_TYPES, isArtifactType, newAnnotationId, 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,10 +16,14 @@ 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>;
@@ -35,7 +40,7 @@ async function sessionCreate({ client, flags }: SessionContext): Promise<number>
35
40
  // per-file agent notes for diff sessions: a JSON array of { path, body }
36
41
  const notesFile = stringFlag(flags, "notes-file");
37
42
  const notes = notesFile
38
- ? (JSON.parse(await Bun.file(notesFile).text()) as ReviewNote[])
43
+ ? v.parse(ReviewNotesSchema, JSON.parse(await Bun.file(notesFile).text()))
39
44
  : undefined;
40
45
  const review = await openReview(client, {
41
46
  type,
@@ -63,7 +68,8 @@ async function sessionGetCommand({ client, positional }: SessionContext): Promis
63
68
  }
64
69
 
65
70
  async function sessionListCommand({ client, flags }: SessionContext): Promise<number> {
66
- const status = stringFlag(flags, "status") as "pending" | "resolved" | undefined;
71
+ const rawStatus = stringFlag(flags, "status");
72
+ const status = rawStatus ? v.parse(SessionStatusSchema, rawStatus) : undefined;
67
73
 
68
74
  out(await client.sessionList(status ? { status } : undefined));
69
75
 
@@ -109,7 +115,7 @@ async function sessionAnnotateCommand({
109
115
  suffix: stringFlag(flags, "suffix") ?? "",
110
116
  },
111
117
  body,
112
- ...(author ? { author } : {}),
118
+ author,
113
119
  },
114
120
  stringFlag(flags, "author-name"),
115
121
  ),
@@ -124,7 +130,7 @@ async function sessionResolveCommand({
124
130
  flags,
125
131
  }: SessionContext): Promise<number> {
126
132
  const id = required(positional[1], "session id");
127
- const kind = required(stringFlag(flags, "verdict"), "--verdict") as VerdictKind;
133
+ const kind = v.parse(VerdictKindSchema, required(stringFlag(flags, "verdict"), "--verdict"));
128
134
 
129
135
  out(await client.sessionResolve(id, kind, stringFlag(flags, "summary") ?? ""));
130
136
 
@@ -150,7 +156,11 @@ async function sessionSubmitRevisionCommand({
150
156
  return 0;
151
157
  }
152
158
 
153
- const sessionVerbHandlers: Record<string, SessionVerbHandler> = {
159
+ interface SessionVerbHandlers {
160
+ [verb: string]: SessionVerbHandler;
161
+ }
162
+
163
+ const sessionVerbHandlers: SessionVerbHandlers = {
154
164
  create: sessionCreate,
155
165
  get: sessionGetCommand,
156
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[] } {