vent-hq 0.5.1 → 0.5.2
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/dist/index.mjs +73 -6
- package/dist/package-QE6OAK7W.mjs +51 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -131,14 +131,24 @@ async function apiFetch(path3, apiKey, options = {}) {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// src/lib/sse.ts
|
|
134
|
+
function log(msg) {
|
|
135
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
136
|
+
const line = `[vent:sse ${ts}] ${msg}
|
|
137
|
+
`;
|
|
138
|
+
process.stderr.write(line);
|
|
139
|
+
}
|
|
134
140
|
async function* streamRunEvents(runId, apiKey, signal) {
|
|
135
141
|
const url = `${API_BASE}/runs/${runId}/stream`;
|
|
142
|
+
log(`connecting to ${url}`);
|
|
136
143
|
const res = await fetch(url, {
|
|
137
144
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
138
145
|
signal
|
|
139
146
|
});
|
|
147
|
+
log(`response: status=${res.status} content-type=${res.headers.get("content-type")}`);
|
|
140
148
|
if (!res.ok) {
|
|
141
|
-
|
|
149
|
+
const body = await res.text();
|
|
150
|
+
log(`error body: ${body}`);
|
|
151
|
+
throw new Error(`SSE stream failed (${res.status}): ${body}`);
|
|
142
152
|
}
|
|
143
153
|
if (!res.body) {
|
|
144
154
|
throw new Error("SSE stream returned no body");
|
|
@@ -146,28 +156,48 @@ async function* streamRunEvents(runId, apiKey, signal) {
|
|
|
146
156
|
const reader = res.body.getReader();
|
|
147
157
|
const decoder = new TextDecoder();
|
|
148
158
|
let buffer = "";
|
|
159
|
+
let chunkCount = 0;
|
|
160
|
+
let eventCount = 0;
|
|
149
161
|
try {
|
|
150
162
|
while (true) {
|
|
151
163
|
const { done, value } = await reader.read();
|
|
152
|
-
if (done)
|
|
153
|
-
|
|
164
|
+
if (done) {
|
|
165
|
+
log(`stream done after ${chunkCount} chunks, ${eventCount} events`);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
chunkCount++;
|
|
169
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
170
|
+
buffer += chunk;
|
|
171
|
+
if (chunkCount <= 3 || chunkCount % 10 === 0) {
|
|
172
|
+
log(`chunk #${chunkCount} (${chunk.length} bytes) buffer=${buffer.length} bytes`);
|
|
173
|
+
}
|
|
154
174
|
const lines = buffer.split("\n");
|
|
155
175
|
buffer = lines.pop();
|
|
156
176
|
for (const line of lines) {
|
|
157
177
|
if (line.startsWith("data: ")) {
|
|
178
|
+
const raw = line.slice(6);
|
|
158
179
|
try {
|
|
159
|
-
const event = JSON.parse(
|
|
180
|
+
const event = JSON.parse(raw);
|
|
181
|
+
eventCount++;
|
|
182
|
+
log(`parsed event #${eventCount}: type=${event.event_type}`);
|
|
160
183
|
yield event;
|
|
161
184
|
if (event.event_type === "run_complete") {
|
|
185
|
+
log("run_complete received \u2014 closing stream");
|
|
162
186
|
return;
|
|
163
187
|
}
|
|
164
188
|
} catch {
|
|
189
|
+
log(`malformed JSON: ${raw.slice(0, 200)}`);
|
|
190
|
+
}
|
|
191
|
+
} else if (line.startsWith(": ")) {
|
|
192
|
+
if (chunkCount <= 3) {
|
|
193
|
+
log(`heartbeat: "${line}"`);
|
|
165
194
|
}
|
|
166
195
|
}
|
|
167
196
|
}
|
|
168
197
|
}
|
|
169
198
|
} finally {
|
|
170
199
|
reader.releaseLock();
|
|
200
|
+
log("reader released");
|
|
171
201
|
}
|
|
172
202
|
}
|
|
173
203
|
|
|
@@ -511,19 +541,31 @@ function printSuccess(message) {
|
|
|
511
541
|
}
|
|
512
542
|
|
|
513
543
|
// src/commands/run.ts
|
|
544
|
+
function log2(msg) {
|
|
545
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
546
|
+
const line = `[vent ${ts}] ${msg}
|
|
547
|
+
`;
|
|
548
|
+
process.stdout.write(line);
|
|
549
|
+
process.stderr.write(line);
|
|
550
|
+
}
|
|
514
551
|
async function runCommand(args) {
|
|
552
|
+
log2(`start args=${JSON.stringify({ file: args.file, test: args.test, json: args.json, submit: args.submit })}`);
|
|
515
553
|
const apiKey = args.apiKey ?? await loadApiKey();
|
|
516
554
|
if (!apiKey) {
|
|
517
555
|
printError("No API key found. Set VENT_API_KEY, run `npx vent-hq login`, or pass --api-key.");
|
|
518
556
|
return 2;
|
|
519
557
|
}
|
|
558
|
+
log2(`api-key resolved (${apiKey.slice(0, 8)}\u2026)`);
|
|
520
559
|
let config;
|
|
521
560
|
try {
|
|
522
561
|
if (args.file) {
|
|
562
|
+
log2(`reading config file: ${args.file}`);
|
|
523
563
|
const raw = await fs2.readFile(args.file, "utf-8");
|
|
524
564
|
config = JSON.parse(raw);
|
|
565
|
+
log2(`config parsed \u2014 keys: ${Object.keys(config).join(", ")}`);
|
|
525
566
|
} else if (args.config) {
|
|
526
567
|
config = JSON.parse(args.config);
|
|
568
|
+
log2("config parsed from --config flag");
|
|
527
569
|
} else {
|
|
528
570
|
printError("Provide --config '{...}' or -f <file>.");
|
|
529
571
|
return 2;
|
|
@@ -552,12 +594,15 @@ async function runCommand(args) {
|
|
|
552
594
|
return 2;
|
|
553
595
|
}
|
|
554
596
|
cfg2.conversation_tests = match;
|
|
597
|
+
log2(`filtered to test: ${args.test}`);
|
|
555
598
|
}
|
|
556
599
|
const cfg = config;
|
|
557
600
|
if (cfg.connection?.start_command) {
|
|
558
601
|
const freePort = await findFreePort();
|
|
559
602
|
cfg.connection.agent_port = freePort;
|
|
603
|
+
log2(`auto-port assigned: ${freePort}`);
|
|
560
604
|
}
|
|
605
|
+
log2("submitting run to API\u2026");
|
|
561
606
|
printInfo("Submitting run\u2026");
|
|
562
607
|
let submitResult;
|
|
563
608
|
try {
|
|
@@ -565,16 +610,20 @@ async function runCommand(args) {
|
|
|
565
610
|
method: "POST",
|
|
566
611
|
body: JSON.stringify({ config })
|
|
567
612
|
});
|
|
613
|
+
log2(`API response status: ${res.status}`);
|
|
568
614
|
submitResult = await res.json();
|
|
569
615
|
} catch (err) {
|
|
616
|
+
log2(`submit error: ${err.message}`);
|
|
570
617
|
printError(`Submit failed: ${err.message}`);
|
|
571
618
|
return 2;
|
|
572
619
|
}
|
|
573
620
|
const { run_id } = submitResult;
|
|
574
621
|
if (!run_id) {
|
|
622
|
+
log2(`no run_id in response: ${JSON.stringify(submitResult)}`);
|
|
575
623
|
printError("Server returned no run_id. Response: " + JSON.stringify(submitResult));
|
|
576
624
|
return 2;
|
|
577
625
|
}
|
|
626
|
+
log2(`run created: ${run_id} status=${submitResult.status} has_relay=${!!submitResult.relay_config}`);
|
|
578
627
|
printInfo(`Run ${run_id} created.`);
|
|
579
628
|
if (args.submit) {
|
|
580
629
|
if (submitResult.relay_config) {
|
|
@@ -594,56 +643,74 @@ async function runCommand(args) {
|
|
|
594
643
|
}
|
|
595
644
|
let relay = null;
|
|
596
645
|
if (submitResult.relay_config) {
|
|
646
|
+
log2(`starting relay \u2014 agent_port=${submitResult.relay_config.agent_port} start_command="${submitResult.relay_config.start_command}" health=${submitResult.relay_config.health_endpoint}`);
|
|
597
647
|
printInfo("Starting relay for local agent\u2026");
|
|
598
648
|
try {
|
|
599
649
|
relay = await startRelay(submitResult.relay_config);
|
|
650
|
+
log2("relay connected, agent healthy, run activated");
|
|
600
651
|
printInfo("Relay connected, agent started.");
|
|
601
652
|
} catch (err) {
|
|
653
|
+
log2(`relay error: ${err.message}`);
|
|
602
654
|
printError(`Relay failed: ${err.message}`);
|
|
603
655
|
return 2;
|
|
604
656
|
}
|
|
605
657
|
}
|
|
658
|
+
log2(`connecting to SSE stream for run ${run_id}\u2026`);
|
|
606
659
|
printInfo(`Streaming results for run ${run_id}\u2026`);
|
|
607
660
|
const abortController = new AbortController();
|
|
608
661
|
let exitCode = 0;
|
|
609
662
|
const testResults = [];
|
|
610
663
|
let runCompleteData = null;
|
|
611
664
|
const onSignal = () => {
|
|
665
|
+
log2("received SIGINT/SIGTERM \u2014 aborting stream");
|
|
612
666
|
abortController.abort();
|
|
613
667
|
};
|
|
614
668
|
process.on("SIGINT", onSignal);
|
|
615
669
|
process.on("SIGTERM", onSignal);
|
|
616
670
|
try {
|
|
671
|
+
let eventCount = 0;
|
|
617
672
|
for await (const event of streamRunEvents(run_id, apiKey, abortController.signal)) {
|
|
673
|
+
eventCount++;
|
|
674
|
+
const meta = event.metadata_json ?? {};
|
|
675
|
+
log2(`event #${eventCount}: type=${event.event_type} meta_keys=[${Object.keys(meta).join(",")}] message="${event.message ?? ""}"`);
|
|
618
676
|
printEvent(event, args.json);
|
|
619
677
|
if (event.event_type === "test_completed") {
|
|
620
678
|
testResults.push(event);
|
|
679
|
+
log2(`test_completed: name=${meta.test_name} status=${meta.status} duration=${meta.duration_ms}ms completed=${meta.completed}/${meta.total}`);
|
|
621
680
|
}
|
|
622
681
|
if (event.event_type === "run_complete") {
|
|
623
|
-
const meta = event.metadata_json ?? {};
|
|
624
682
|
runCompleteData = meta;
|
|
625
683
|
const status = meta.status;
|
|
626
684
|
exitCode = status === "pass" ? 0 : 1;
|
|
685
|
+
log2(`run_complete: status=${status} exitCode=${exitCode}`);
|
|
627
686
|
}
|
|
628
687
|
}
|
|
688
|
+
log2(`SSE stream ended \u2014 received ${eventCount} events total`);
|
|
629
689
|
} catch (err) {
|
|
630
690
|
if (err.name !== "AbortError") {
|
|
691
|
+
log2(`stream error: ${err.message}`);
|
|
631
692
|
printError(`Stream error: ${err.message}`);
|
|
632
693
|
exitCode = 2;
|
|
694
|
+
} else {
|
|
695
|
+
log2("stream aborted (user signal)");
|
|
633
696
|
}
|
|
634
697
|
} finally {
|
|
635
698
|
process.off("SIGINT", onSignal);
|
|
636
699
|
process.off("SIGTERM", onSignal);
|
|
637
700
|
if (relay) {
|
|
701
|
+
log2("cleaning up relay\u2026");
|
|
638
702
|
await relay.cleanup();
|
|
703
|
+
log2("relay cleaned up");
|
|
639
704
|
}
|
|
640
705
|
}
|
|
706
|
+
log2(`summary: testResults=${testResults.length} runComplete=${!!runCompleteData} exitCode=${exitCode}`);
|
|
641
707
|
if (runCompleteData && testResults.length > 0) {
|
|
642
708
|
printSummary(testResults, runCompleteData, run_id, args.json);
|
|
643
709
|
} else if (testResults.length === 0 && exitCode !== 2) {
|
|
644
710
|
process.stdout.write(`run_id: ${run_id} \u2014 no test results received
|
|
645
711
|
`);
|
|
646
712
|
}
|
|
713
|
+
log2(`exiting with code ${exitCode}`);
|
|
647
714
|
return exitCode;
|
|
648
715
|
}
|
|
649
716
|
function findFreePort() {
|
|
@@ -6273,7 +6340,7 @@ async function main() {
|
|
|
6273
6340
|
process.exit(0);
|
|
6274
6341
|
}
|
|
6275
6342
|
if (command === "--version" || command === "-v") {
|
|
6276
|
-
const pkg = await import("./package-
|
|
6343
|
+
const pkg = await import("./package-QE6OAK7W.mjs");
|
|
6277
6344
|
process.stdout.write(`vent-hq ${pkg.default.version}
|
|
6278
6345
|
`);
|
|
6279
6346
|
process.exit(0);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-U4M3XDTH.mjs";
|
|
3
|
+
|
|
4
|
+
// package.json
|
|
5
|
+
var package_default = {
|
|
6
|
+
name: "vent-hq",
|
|
7
|
+
version: "0.5.2",
|
|
8
|
+
type: "module",
|
|
9
|
+
description: "Vent CLI \u2014 CI/CD for voice AI agents",
|
|
10
|
+
bin: {
|
|
11
|
+
"vent-hq": "dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
files: [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
scripts: {
|
|
17
|
+
build: "node scripts/bundle.mjs",
|
|
18
|
+
clean: "rm -rf dist"
|
|
19
|
+
},
|
|
20
|
+
keywords: [
|
|
21
|
+
"vent",
|
|
22
|
+
"cli",
|
|
23
|
+
"voice",
|
|
24
|
+
"agent",
|
|
25
|
+
"testing",
|
|
26
|
+
"ci-cd"
|
|
27
|
+
],
|
|
28
|
+
license: "MIT",
|
|
29
|
+
publishConfig: {
|
|
30
|
+
access: "public"
|
|
31
|
+
},
|
|
32
|
+
repository: {
|
|
33
|
+
type: "git",
|
|
34
|
+
url: "https://github.com/vent-hq/vent",
|
|
35
|
+
directory: "packages/cli"
|
|
36
|
+
},
|
|
37
|
+
homepage: "https://ventmcp.dev",
|
|
38
|
+
dependencies: {
|
|
39
|
+
"@clack/prompts": "^1.1.0",
|
|
40
|
+
ws: "^8.18.0"
|
|
41
|
+
},
|
|
42
|
+
devDependencies: {
|
|
43
|
+
"@types/ws": "^8.5.0",
|
|
44
|
+
"@vent/relay-client": "workspace:*",
|
|
45
|
+
"@vent/shared": "workspace:*",
|
|
46
|
+
esbuild: "^0.24.0"
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export {
|
|
50
|
+
package_default as default
|
|
51
|
+
};
|