vibe-coding-master 0.0.14 → 0.0.16
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/README.md +32 -34
- package/dist/backend/api/claude-hook-routes.js +3 -0
- package/dist/backend/api/message-routes.js +8 -40
- package/dist/backend/server.js +1 -23
- package/dist/backend/services/artifact-service.js +27 -2
- package/dist/backend/services/claude-hook-service.js +108 -49
- package/dist/backend/services/harness-service.js +18 -1
- package/dist/backend/services/message-service.js +307 -210
- package/dist/backend/services/round-service.js +2 -2
- package/dist/backend/services/session-service.js +25 -9
- package/dist/backend/templates/handoff.js +3 -0
- package/dist/backend/templates/harness/architect-agent.js +4 -3
- package/dist/backend/templates/harness/claude-root.js +5 -4
- package/dist/backend/templates/harness/coder-agent.js +4 -3
- package/dist/backend/templates/harness/project-manager-agent.js +5 -5
- package/dist/backend/templates/harness/reviewer-agent.js +4 -3
- package/dist/backend/templates/message-envelope.js +7 -3
- package/dist-frontend/assets/{index-DVhkEVnA.js → index-CvtyKEfS.js} +44 -44
- package/dist-frontend/index.html +1 -1
- package/docs/cc-best-practices.md +10 -9
- package/docs/product-design.md +64 -35
- package/docs/v1-architecture-design.md +31 -29
- package/docs/v1-implementation-plan.md +71 -50
- package/package.json +2 -3
- package/scripts/verify-package.mjs +0 -3
- package/dist/cli/vcmctl.js +0 -171
|
@@ -28,10 +28,9 @@ File:
|
|
|
28
28
|
Current package facts:
|
|
29
29
|
|
|
30
30
|
- package name: `vibe-coding-master`
|
|
31
|
-
- current version: `0.0.
|
|
31
|
+
- current version: `0.0.14`
|
|
32
32
|
- type: ESM
|
|
33
33
|
- `bin.vcm`: `dist/main.js`
|
|
34
|
-
- `bin.vcmctl`: `dist/cli/vcmctl.js`
|
|
35
34
|
- published files: `dist`, `dist-frontend`, `docs`, `scripts`, `README.md`
|
|
36
35
|
|
|
37
36
|
Scripts:
|
|
@@ -59,8 +58,6 @@ It verifies required files, shebangs, packaged static path behavior, and fronten
|
|
|
59
58
|
```text
|
|
60
59
|
src/
|
|
61
60
|
main.ts
|
|
62
|
-
cli/
|
|
63
|
-
vcmctl.ts
|
|
64
61
|
shared/
|
|
65
62
|
constants.ts
|
|
66
63
|
types/
|
|
@@ -108,23 +105,24 @@ Responsibilities:
|
|
|
108
105
|
- serve `dist-frontend` through backend in production mode
|
|
109
106
|
- close backend/Vite on `SIGINT`
|
|
110
107
|
|
|
111
|
-
###
|
|
108
|
+
### Removed CLI Surface
|
|
112
109
|
|
|
113
|
-
CLI
|
|
110
|
+
The target design removes `vcmctl` completely. Role sessions must not call a VCM CLI to send messages or hook events.
|
|
114
111
|
|
|
115
|
-
|
|
116
|
-
- `vcmctl reply`
|
|
117
|
-
- `vcmctl result`
|
|
118
|
-
- `vcmctl inbox`
|
|
119
|
-
- `vcmctl ready`
|
|
112
|
+
Remove:
|
|
120
113
|
|
|
121
|
-
|
|
114
|
+
- `src/cli/vcmctl.ts`
|
|
115
|
+
- `bin.vcmctl` from `package.json`
|
|
116
|
+
- packaged `dist/cli/vcmctl.js`
|
|
117
|
+
- `VCM_CTL_COMMAND` session environment
|
|
118
|
+
- harness rules that mention `vcmctl`
|
|
119
|
+
|
|
120
|
+
Role sessions still receive enough environment for direct hook POSTs and backend-aware terminal delivery:
|
|
122
121
|
|
|
123
122
|
- `VCM_API_URL`
|
|
124
123
|
- `VCM_TASK_SLUG`
|
|
125
124
|
- `VCM_ROLE`
|
|
126
|
-
|
|
127
|
-
Role sessions receive these env vars from `SessionService`.
|
|
125
|
+
- `VCM_SESSION_ID`
|
|
128
126
|
|
|
129
127
|
## 5. Shared Layer
|
|
130
128
|
|
|
@@ -239,10 +237,10 @@ Defines:
|
|
|
239
237
|
- `VcmOrchestrationMode`
|
|
240
238
|
- `VcmRoleMessage`
|
|
241
239
|
- `VcmOrchestrationState`
|
|
242
|
-
- `
|
|
243
|
-
- `
|
|
240
|
+
- `VcmRouteFile`
|
|
241
|
+
- `VcmRouteFileDispatchResult`
|
|
244
242
|
|
|
245
|
-
The state type
|
|
243
|
+
The state type should only expose the current manual/auto mode. Remove compatibility-only paused state when deleting `vcmctl`.
|
|
246
244
|
|
|
247
245
|
### `src/shared/types/round.ts`
|
|
248
246
|
|
|
@@ -679,41 +677,62 @@ In task-worktree mode:
|
|
|
679
677
|
Exports:
|
|
680
678
|
|
|
681
679
|
- `MessageService`
|
|
682
|
-
-
|
|
680
|
+
- `ScanPendingRouteFilesInput`
|
|
681
|
+
- `DispatchPendingRouteFileInput`
|
|
682
|
+
- `RouteFileRecord`
|
|
683
683
|
- `createMessageService(deps)`
|
|
684
684
|
|
|
685
685
|
Responsibilities:
|
|
686
686
|
|
|
687
687
|
- list messages
|
|
688
|
-
-
|
|
689
|
-
-
|
|
688
|
+
- scan route-file outboxes after Claude Code `Stop`
|
|
689
|
+
- snapshot pending route-file messages
|
|
690
690
|
- mark all open messages done for manual recovery
|
|
691
|
-
- stage/approve/reject messages
|
|
692
691
|
- get/update orchestration state
|
|
693
692
|
- enforce message policy
|
|
694
693
|
- enforce per-target-role in-flight delivery
|
|
695
|
-
-
|
|
696
|
-
-
|
|
697
|
-
- deliver the next queued message for that role after acknowledgement
|
|
694
|
+
- leave non-empty route files pending when a target role is busy, unavailable, or terminal submission fails
|
|
695
|
+
- deliver the next pending route file when a target role becomes idle
|
|
698
696
|
- persist message snapshots
|
|
699
|
-
-
|
|
700
|
-
-
|
|
697
|
+
- archive dispatched route-file bodies before clearing source files
|
|
698
|
+
- submit route-file messages to the target terminal
|
|
701
699
|
|
|
702
700
|
In task-worktree mode:
|
|
703
701
|
|
|
704
702
|
- message snapshots live under `task.worktreePath/.ai/vcm/messages`
|
|
705
703
|
- orchestration state lives under `task.worktreePath/.ai/vcm/orchestration`
|
|
706
|
-
-
|
|
704
|
+
- pending route files live under `task.worktreePath/.ai/vcm/handoffs/messages`
|
|
707
705
|
- terminal delivery uses the runtime session for the role, whose cwd is the task worktree
|
|
708
706
|
- message mutations are serialized per task inside the VCM process to avoid concurrent API calls bypassing the in-flight check
|
|
709
707
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
-
|
|
713
|
-
-
|
|
714
|
-
-
|
|
715
|
-
-
|
|
716
|
-
-
|
|
708
|
+
Route-file protocol:
|
|
709
|
+
|
|
710
|
+
- route file name is `<from-role>-<to-role>.md`
|
|
711
|
+
- the filename is the authoritative route; frontmatter cannot override `from` or `to`
|
|
712
|
+
- blank or whitespace-only files are ignored
|
|
713
|
+
- non-empty files are pending
|
|
714
|
+
- each directed route has exactly one pending file, so repeated sends by the same role to the same target become edits to the same file
|
|
715
|
+
- VCM scans after every `Stop` hook
|
|
716
|
+
- VCM scans the stopped role's outgoing files and pending files targeting newly idle roles
|
|
717
|
+
- VCM delivers at most one route file per target role per scan
|
|
718
|
+
- if several files target the same idle role, choose oldest modified time, then route name
|
|
719
|
+
- successful terminal write snapshots the body as `delivering`
|
|
720
|
+
- `UserPromptSubmit` confirms Claude Code accepted the prompt, stores `submitted`, then clears the source route file if it still contains that same message
|
|
721
|
+
- failed, blocked, manual, or unavailable delivery leaves the source route file unchanged
|
|
722
|
+
- `markAllDone` appends `acknowledged` snapshots and may clear pending route files only after user confirmation
|
|
723
|
+
|
|
724
|
+
Required service functions:
|
|
725
|
+
|
|
726
|
+
- `listMessages(taskSlug): Promise<VcmRoleMessage[]>`
|
|
727
|
+
- `listPendingRouteFiles(repoRoot, taskSlug): Promise<RouteFileRecord[]>`
|
|
728
|
+
- `scanAndDispatchPendingRouteFiles(input: { repoRoot: string; taskSlug: string; stoppedRole: RoleName }): Promise<void>`
|
|
729
|
+
- `readRouteFile(path): Promise<RouteFileRecord | null>`
|
|
730
|
+
- `dispatchPendingRouteFile(record): Promise<VcmRouteFileDispatchResult>`
|
|
731
|
+
- `appendMessageSnapshot(message): Promise<void>`
|
|
732
|
+
- `clearRouteFile(path): Promise<void>`
|
|
733
|
+
- `markAllDone(taskSlug, options): Promise<MarkAllMessagesDoneResult>`
|
|
734
|
+
|
|
735
|
+
`dispatchPendingRouteFile` must snapshot before clearing. Clearing means truncating the route file to an empty string, not deleting it.
|
|
717
736
|
|
|
718
737
|
### `src/backend/services/claude-hook-service.ts`
|
|
719
738
|
|
|
@@ -724,11 +743,12 @@ Exports:
|
|
|
724
743
|
|
|
725
744
|
Responsibilities:
|
|
726
745
|
|
|
727
|
-
- accept Claude Code hook events
|
|
728
|
-
- support only `UserPromptSubmit` and `Stop` in V1
|
|
746
|
+
- accept Claude Code `UserPromptSubmit` and `Stop` hook events directly over HTTP
|
|
729
747
|
- map hook events to the current VCM project, task, role, and persisted Claude session id
|
|
730
748
|
- update `RoleSessionRecord.activityStatus`: `UserPromptSubmit -> running`, `Stop -> idle`
|
|
731
|
-
- call `MessageService.confirmPromptSubmitted` when
|
|
749
|
+
- call `MessageService.confirmPromptSubmitted` when `UserPromptSubmit` includes a VCM message envelope
|
|
750
|
+
- call `MessageService.scanAndDispatchPendingRouteFiles` after recording the stop event
|
|
751
|
+
- never require `vcmctl hook-event`
|
|
732
752
|
|
|
733
753
|
### `src/backend/services/command-dispatcher.ts`
|
|
734
754
|
|
|
@@ -739,7 +759,7 @@ Exports:
|
|
|
739
759
|
- `CommandDispatcherDeps`
|
|
740
760
|
- `createCommandDispatcher(deps)`
|
|
741
761
|
|
|
742
|
-
|
|
762
|
+
Obsolete role-command dispatch path. Remove this as part of the route-file messaging migration; preferred orchestration is `MessageService` scanning `.ai/vcm/handoffs/messages/<from-role>-<to-role>.md` after `Stop`.
|
|
743
763
|
|
|
744
764
|
### `src/backend/services/harness-service.ts`
|
|
745
765
|
|
|
@@ -871,9 +891,8 @@ Responsibilities:
|
|
|
871
891
|
- map `activityStatus: "running"` to role state `answering`
|
|
872
892
|
- map `activityStatus: "idle"` plus `lastStopAt` to role state `idle`
|
|
873
893
|
- evaluate task-level round completion from messages plus role states
|
|
874
|
-
- wait for the latest active delivering/submitted
|
|
894
|
+
- wait for the latest active delivering/submitted target role in PM -> role -> PM chains
|
|
875
895
|
- ignore completion while queued or pending messages exist
|
|
876
|
-
- keep `stopSession` and `stopTask` as no-op lifecycle compatibility hooks
|
|
877
896
|
|
|
878
897
|
## 9. Backend API
|
|
879
898
|
|
|
@@ -897,9 +916,9 @@ Registers HTTP routes and the terminal WebSocket.
|
|
|
897
916
|
- `src/backend/api/project-routes.ts`: health, recent paths, connect/current project
|
|
898
917
|
- `src/backend/api/harness-routes.ts`: harness status/apply
|
|
899
918
|
- `src/backend/api/task-routes.ts`: tasks, task status, and Close Task cleanup endpoint; Close Task stops running role sessions before translation/task cleanup
|
|
900
|
-
- `src/backend/api/session-routes.ts`: session lifecycle
|
|
901
|
-
- `src/backend/api/artifact-routes.ts`: artifact
|
|
902
|
-
- `src/backend/api/message-routes.ts`:
|
|
919
|
+
- `src/backend/api/session-routes.ts`: session lifecycle
|
|
920
|
+
- `src/backend/api/artifact-routes.ts`: artifact and log reads/writes
|
|
921
|
+
- `src/backend/api/message-routes.ts`: message history, pending route files, Mark All Done recovery, and orchestration
|
|
903
922
|
- `src/backend/api/claude-hook-routes.ts`: Claude Code `UserPromptSubmit` and `Stop` hook receiver
|
|
904
923
|
- `src/backend/api/round-routes.ts`: task round completion state
|
|
905
924
|
- `src/backend/api/translation-routes.ts`: settings, prompt previews, provider test, start/poll, input/send, clear/retry
|
|
@@ -977,6 +996,7 @@ It calls:
|
|
|
977
996
|
- artifact endpoints
|
|
978
997
|
- message endpoints
|
|
979
998
|
- orchestration endpoints
|
|
999
|
+
- route-file inspection endpoints for the Messages modal
|
|
980
1000
|
- round completion endpoint
|
|
981
1001
|
- translation endpoints
|
|
982
1002
|
|
|
@@ -1100,7 +1120,7 @@ Exports:
|
|
|
1100
1120
|
- `getMessageCounts(messages)`
|
|
1101
1121
|
- `MessageTimeline(props)`
|
|
1102
1122
|
|
|
1103
|
-
Used inside the Messages modal. Current UI rows show sequence, timestamp, route, type, status, body preview,
|
|
1123
|
+
Used inside the Messages modal. Current UI rows show sequence, timestamp, route, type, status, body preview, source route file path, and a `Copy` button. The modal header includes `Mark All Done` for manual recovery from stuck queued/in-flight messages. Stage/approve/reject backend APIs should be removed with the route-file migration.
|
|
1104
1124
|
|
|
1105
1125
|
### `src/frontend/components/event-log.tsx`
|
|
1106
1126
|
|
|
@@ -1147,7 +1167,7 @@ Exports:
|
|
|
1147
1167
|
|
|
1148
1168
|
Renders permission select and session lifecycle buttons.
|
|
1149
1169
|
|
|
1150
|
-
There is no visible primary `Send Command` button in the current toolbar. Role-command dispatch
|
|
1170
|
+
There is no visible primary `Send Command` button in the current toolbar. Role-command dispatch should be removed with the route-file migration.
|
|
1151
1171
|
|
|
1152
1172
|
### `src/frontend/components/translation-panel.tsx`
|
|
1153
1173
|
|
|
@@ -1212,7 +1232,7 @@ Sidebar:
|
|
|
1212
1232
|
- `Repository Path` default open only when no task is selected
|
|
1213
1233
|
- `Settings` includes `Theme`, `Round alert`, `Try alert`, `Messages`, and `Events`
|
|
1214
1234
|
- `Theme` cycles through `System`, `Light`, and `Dark`; `System` follows the browser/OS color-scheme preference
|
|
1215
|
-
- `Round alert` is on by default and controls the in-app completion prompt plus
|
|
1235
|
+
- `Round alert` is on by default and controls the in-app completion prompt plus a soft two-note completion chime
|
|
1216
1236
|
- `Try alert` calls the same prompt/sound path without waiting for a real completed round
|
|
1217
1237
|
|
|
1218
1238
|
Task workspace:
|
|
@@ -1224,8 +1244,8 @@ Task workspace:
|
|
|
1224
1244
|
|
|
1225
1245
|
Round completion:
|
|
1226
1246
|
|
|
1227
|
-
- task state follows the latest active delivering/submitted
|
|
1228
|
-
- role activity uses `UserPromptSubmit -> running` and `Stop -> idle`
|
|
1247
|
+
- task state follows the latest active delivering/submitted message target role
|
|
1248
|
+
- role activity uses Claude Code `UserPromptSubmit` -> `running` and Claude Code `Stop` -> `idle`; VCM terminal submit also optimistically marks the target role `running`
|
|
1229
1249
|
- PM -> role -> PM chains complete after PM's final hook `Stop`, not after the intermediate role's `Stop`
|
|
1230
1250
|
- queued or pending messages keep the round active
|
|
1231
1251
|
- frontend dedupes `completionId` before showing a prompt or playing sound
|
|
@@ -1271,7 +1291,7 @@ Messages:
|
|
|
1271
1291
|
|
|
1272
1292
|
```text
|
|
1273
1293
|
<taskRepoRoot>/.ai/vcm/messages/<task>.jsonl
|
|
1274
|
-
<taskRepoRoot>/.ai/vcm/handoffs/messages/<
|
|
1294
|
+
<taskRepoRoot>/.ai/vcm/handoffs/messages/<from-role>-<to-role>.md
|
|
1275
1295
|
```
|
|
1276
1296
|
|
|
1277
1297
|
Orchestration:
|
|
@@ -1330,7 +1350,7 @@ For frontend layout changes, also verify manually:
|
|
|
1330
1350
|
- Messages modal opens from sidebar Settings
|
|
1331
1351
|
- Events modal opens from sidebar Settings
|
|
1332
1352
|
- Auto orchestration toggles on/off from the role toolbar
|
|
1333
|
-
- Auto orchestration switches to the target role tab when a
|
|
1353
|
+
- Auto orchestration switches to the target role tab when VCM submits a route-file message
|
|
1334
1354
|
- Round alert can be toggled from sidebar Settings and fires once after a chained round truly completes
|
|
1335
1355
|
- `Enter` in translation composer translates/sends
|
|
1336
1356
|
- `Shift+Enter` inserts newline
|
|
@@ -1350,6 +1370,7 @@ Do not reintroduce these into V1 docs or UI unless the product direction changes
|
|
|
1350
1370
|
- optional title input in New Task
|
|
1351
1371
|
- `Dirty: yes/no` sidebar label
|
|
1352
1372
|
- role command dispatch as the primary orchestration path
|
|
1373
|
+
- `vcmctl` as an active CLI or agent-facing message path
|
|
1353
1374
|
- per-role worktrees
|
|
1354
1375
|
- switching a task to another branch/worktree after creation
|
|
1355
1376
|
- a separate `Create task worktree` button outside task creation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibe-coding-master",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "Local GUI session cockpit for Claude Code role sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
"README.md"
|
|
12
12
|
],
|
|
13
13
|
"bin": {
|
|
14
|
-
"vcm": "dist/main.js"
|
|
15
|
-
"vcmctl": "dist/cli/vcmctl.js"
|
|
14
|
+
"vcm": "dist/main.js"
|
|
16
15
|
},
|
|
17
16
|
"engines": {
|
|
18
17
|
"node": "^20.0.0 || >=22.0.0"
|
|
@@ -7,7 +7,6 @@ const requiredFiles = [
|
|
|
7
7
|
"package.json",
|
|
8
8
|
"scripts/fix-node-pty-spawn-helper.mjs",
|
|
9
9
|
"dist/main.js",
|
|
10
|
-
"dist/cli/vcmctl.js",
|
|
11
10
|
"dist/backend/server.js",
|
|
12
11
|
"dist/backend/api/harness-routes.js",
|
|
13
12
|
"dist/backend/runtime/node-pty-runtime.js",
|
|
@@ -40,14 +39,12 @@ async function main() {
|
|
|
40
39
|
const pkg = JSON.parse(await readText("package.json"));
|
|
41
40
|
assertArrayIncludes(pkg.files, requiredFileEntries, "package.json files");
|
|
42
41
|
assertEqual(pkg.bin?.vcm, "dist/main.js", "package.json bin.vcm");
|
|
43
|
-
assertEqual(pkg.bin?.vcmctl, "dist/cli/vcmctl.js", "package.json bin.vcmctl");
|
|
44
42
|
|
|
45
43
|
for (const file of requiredFiles) {
|
|
46
44
|
await assertFile(file);
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
await assertStartsWith("dist/main.js", "#!/usr/bin/env node", "vcm bin shebang");
|
|
50
|
-
await assertStartsWith("dist/cli/vcmctl.js", "#!/usr/bin/env node", "vcmctl bin shebang");
|
|
51
48
|
|
|
52
49
|
const server = await readText("dist/backend/server.js");
|
|
53
50
|
assertIncludes(server, 'path.join(getAppRoot(), "dist-frontend")', "packaged static dir must resolve from app root");
|
package/dist/cli/vcmctl.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
async function main(argv = process.argv.slice(2)) {
|
|
4
|
-
const [command, ...rest] = argv;
|
|
5
|
-
if (!command || command === "--help" || command === "-h") {
|
|
6
|
-
printHelp();
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
if (command === "send") {
|
|
10
|
-
const options = parseOptions(rest);
|
|
11
|
-
await sendMessage({
|
|
12
|
-
fromRole: getEnvRole(),
|
|
13
|
-
toRole: requireOption(options.to, "--to"),
|
|
14
|
-
type: options.type ?? "task",
|
|
15
|
-
body: await resolveBody(options),
|
|
16
|
-
artifactRefs: options.artifactRefs
|
|
17
|
-
});
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
if (command === "reply" || command === "result") {
|
|
21
|
-
const options = parseOptions(rest);
|
|
22
|
-
await sendMessage({
|
|
23
|
-
fromRole: getEnvRole(),
|
|
24
|
-
toRole: "project-manager",
|
|
25
|
-
type: command === "result" ? "result" : options.type ?? "question",
|
|
26
|
-
body: await resolveBody(options),
|
|
27
|
-
artifactRefs: options.artifactRefs
|
|
28
|
-
});
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
if (command === "inbox") {
|
|
32
|
-
const response = await fetchJson(`${getApiUrl()}/api/tasks/${encodeURIComponent(getTaskSlug())}/messages`);
|
|
33
|
-
console.log(JSON.stringify(response, null, 2));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
if (command === "hook-event") {
|
|
37
|
-
await sendHookEvent(await readStdinJson());
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
if (command === "ready") {
|
|
41
|
-
console.log("ready: VCM readiness signaling is planned for a later phase.");
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
throw new Error(`Unknown vcmctl command: ${command}`);
|
|
45
|
-
}
|
|
46
|
-
async function sendMessage(input) {
|
|
47
|
-
const result = await fetchJson(`${getApiUrl()}/api/tasks/${encodeURIComponent(getTaskSlug())}/messages`, {
|
|
48
|
-
method: "POST",
|
|
49
|
-
body: JSON.stringify(input),
|
|
50
|
-
headers: {
|
|
51
|
-
"content-type": "application/json"
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
console.log(JSON.stringify(result, null, 2));
|
|
55
|
-
}
|
|
56
|
-
async function sendHookEvent(event) {
|
|
57
|
-
const input = {
|
|
58
|
-
taskSlug: getTaskSlug(),
|
|
59
|
-
role: getEnvRole(),
|
|
60
|
-
event
|
|
61
|
-
};
|
|
62
|
-
await fetchJson(`${getApiUrl()}/api/hooks/claude-code`, {
|
|
63
|
-
method: "POST",
|
|
64
|
-
body: JSON.stringify(input),
|
|
65
|
-
headers: {
|
|
66
|
-
"content-type": "application/json"
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
async function readStdinJson() {
|
|
71
|
-
const chunks = [];
|
|
72
|
-
for await (const chunk of process.stdin) {
|
|
73
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
74
|
-
}
|
|
75
|
-
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
76
|
-
if (!raw) {
|
|
77
|
-
return {};
|
|
78
|
-
}
|
|
79
|
-
return JSON.parse(raw);
|
|
80
|
-
}
|
|
81
|
-
function parseOptions(args) {
|
|
82
|
-
const options = {
|
|
83
|
-
artifactRefs: []
|
|
84
|
-
};
|
|
85
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
86
|
-
const arg = args[index];
|
|
87
|
-
if (arg === "--to") {
|
|
88
|
-
options.to = requireValue(args, index += 1);
|
|
89
|
-
}
|
|
90
|
-
else if (arg === "--type") {
|
|
91
|
-
options.type = requireValue(args, index += 1);
|
|
92
|
-
}
|
|
93
|
-
else if (arg === "--body") {
|
|
94
|
-
options.body = requireValue(args, index += 1);
|
|
95
|
-
}
|
|
96
|
-
else if (arg === "--body-file") {
|
|
97
|
-
options.bodyFile = requireValue(args, index += 1);
|
|
98
|
-
}
|
|
99
|
-
else if (arg === "--artifact") {
|
|
100
|
-
options.artifactRefs.push(requireValue(args, index += 1));
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return options;
|
|
107
|
-
}
|
|
108
|
-
async function resolveBody(options) {
|
|
109
|
-
if (options.bodyFile) {
|
|
110
|
-
return fs.readFile(options.bodyFile, "utf8");
|
|
111
|
-
}
|
|
112
|
-
if (options.body) {
|
|
113
|
-
return options.body;
|
|
114
|
-
}
|
|
115
|
-
throw new Error("Message body is required. Use --body or --body-file.");
|
|
116
|
-
}
|
|
117
|
-
function requireValue(args, index) {
|
|
118
|
-
const value = args[index];
|
|
119
|
-
if (!value) {
|
|
120
|
-
throw new Error("Missing option value.");
|
|
121
|
-
}
|
|
122
|
-
return value;
|
|
123
|
-
}
|
|
124
|
-
function requireOption(value, name) {
|
|
125
|
-
if (!value) {
|
|
126
|
-
throw new Error(`Missing required option: ${name}`);
|
|
127
|
-
}
|
|
128
|
-
return value;
|
|
129
|
-
}
|
|
130
|
-
async function fetchJson(url, init = {}) {
|
|
131
|
-
const response = await fetch(url, init);
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
const payload = await response.json().catch(() => null);
|
|
134
|
-
const message = payload?.error?.hint
|
|
135
|
-
? `${payload.error.message} ${payload.error.hint}`
|
|
136
|
-
: payload?.error?.message ?? `Request failed: ${response.status}`;
|
|
137
|
-
throw new Error(message);
|
|
138
|
-
}
|
|
139
|
-
return response.json();
|
|
140
|
-
}
|
|
141
|
-
function getApiUrl() {
|
|
142
|
-
return process.env.VCM_API_URL ?? "http://127.0.0.1:4173";
|
|
143
|
-
}
|
|
144
|
-
function getTaskSlug() {
|
|
145
|
-
return requireEnv("VCM_TASK_SLUG");
|
|
146
|
-
}
|
|
147
|
-
function getEnvRole() {
|
|
148
|
-
return requireEnv("VCM_ROLE");
|
|
149
|
-
}
|
|
150
|
-
function requireEnv(name) {
|
|
151
|
-
const value = process.env[name];
|
|
152
|
-
if (!value) {
|
|
153
|
-
throw new Error(`${name} is not set.`);
|
|
154
|
-
}
|
|
155
|
-
return value;
|
|
156
|
-
}
|
|
157
|
-
function printHelp() {
|
|
158
|
-
console.log(`vcmctl
|
|
159
|
-
|
|
160
|
-
Usage:
|
|
161
|
-
vcmctl send --to coder --type task --body-file /tmp/message.md
|
|
162
|
-
vcmctl reply --type blocked --body "Need clarification."
|
|
163
|
-
vcmctl result --body-file /tmp/result.md --artifact .ai/vcm/handoffs/implementation-log.md
|
|
164
|
-
vcmctl inbox
|
|
165
|
-
vcmctl hook-event < hook-payload.json
|
|
166
|
-
`);
|
|
167
|
-
}
|
|
168
|
-
main().catch((error) => {
|
|
169
|
-
console.error(error.message);
|
|
170
|
-
process.exit(1);
|
|
171
|
-
});
|