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 +6 -5
- package/src/args.ts +4 -2
- package/src/main.ts +5 -1
- package/src/session-commands.ts +25 -9
- package/src/share-command.test.ts +14 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cueloop",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
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.
|
|
34
|
-
"@cueloop/client": "0.1.0-alpha.
|
|
35
|
-
"@cueloop/daemon": "0.1.0-alpha.
|
|
36
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
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
|
|
45
|
+
const result = v.safeParse(v.string(), flags[key]);
|
|
44
46
|
|
|
45
|
-
return
|
|
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
|
-
|
|
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),
|
package/src/session-commands.ts
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* JSON on stdout; exit code 0 unless the daemon returned an error.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
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
|
|
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:
|
|
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())
|
|
43
|
+
? v.parse(ReviewNotesSchema, JSON.parse(await Bun.file(notesFile).text()))
|
|
33
44
|
: undefined;
|
|
34
45
|
const review = await openReview(client, {
|
|
35
|
-
type
|
|
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
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
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
|
-
|
|
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[] } {
|