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 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
- throw new Error(`SSE stream failed (${res.status}): ${await res.text()}`);
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) break;
153
- buffer += decoder.decode(value, { stream: true });
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(line.slice(6));
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-OFMTOIAB.mjs");
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vent-hq",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "description": "Vent CLI — CI/CD for voice AI agents",
6
6
  "bin": {