vent-hq 0.8.2 → 0.8.3

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
@@ -287,13 +287,17 @@ function printError(message) {
287
287
  }
288
288
  function printInfo(message, { force } = {}) {
289
289
  if (!force && !isTTY && !_verbose) return;
290
- process.stderr.write(blue("\u25B8") + ` ${message}
291
- `);
290
+ const line = blue("\u25B8") + ` ${message}
291
+ `;
292
+ process.stderr.write(line);
293
+ if (!isTTY && force) stdoutSync(line);
292
294
  }
293
295
  function printSuccess(message, { force } = {}) {
294
296
  if (!force && !isTTY && !_verbose) return;
295
- process.stderr.write(green("\u2714") + ` ${message}
296
- `);
297
+ const line = green("\u2714") + ` ${message}
298
+ `;
299
+ process.stderr.write(line);
300
+ if (!isTTY && force) stdoutSync(line);
297
301
  }
298
302
 
299
303
  // src/lib/sse.ts
@@ -304,68 +308,109 @@ function log(msg) {
304
308
  `;
305
309
  process.stderr.write(line);
306
310
  }
311
+ var MAX_RETRIES = 5;
312
+ var RETRY_DELAY_MS = 2e3;
307
313
  async function* streamRunEvents(runId, apiKey, signal) {
308
314
  const url = `${API_BASE}/runs/${runId}/stream`;
309
- log(`connecting to ${url}`);
310
- const res = await fetch(url, {
311
- headers: { Authorization: `Bearer ${apiKey}` },
312
- signal
313
- });
314
- log(`response: status=${res.status} content-type=${res.headers.get("content-type")}`);
315
- if (!res.ok) {
316
- const body = await res.text();
317
- log(`error body: ${body}`);
318
- throw new Error(`SSE stream failed (${res.status}): ${body}`);
319
- }
320
- if (!res.body) {
321
- throw new Error("SSE stream returned no body");
322
- }
323
- const reader = res.body.getReader();
324
- const decoder = new TextDecoder();
325
- let buffer = "";
326
- let chunkCount = 0;
327
- let eventCount = 0;
328
- try {
329
- while (true) {
330
- const { done, value } = await reader.read();
331
- if (done) {
332
- log(`stream done after ${chunkCount} chunks, ${eventCount} events`);
333
- break;
334
- }
335
- chunkCount++;
336
- const chunk = decoder.decode(value, { stream: true });
337
- buffer += chunk;
338
- if (chunkCount <= 3 || chunkCount % 10 === 0) {
339
- log(`chunk #${chunkCount} (${chunk.length} bytes) buffer=${buffer.length} bytes`);
340
- }
341
- const lines = buffer.split("\n");
342
- buffer = lines.pop();
343
- for (const line of lines) {
344
- if (line.startsWith("data: ")) {
345
- const raw = line.slice(6);
346
- try {
347
- const event = JSON.parse(raw);
348
- eventCount++;
349
- log(`parsed event #${eventCount}: type=${event.event_type}`);
350
- yield event;
351
- if (event.event_type === "run_complete") {
352
- log("run_complete received \u2014 closing stream");
353
- return;
315
+ const seenIds = /* @__PURE__ */ new Set();
316
+ let retries = 0;
317
+ while (retries <= MAX_RETRIES) {
318
+ if (retries > 0) {
319
+ log(`reconnecting (attempt ${retries}/${MAX_RETRIES}) after ${RETRY_DELAY_MS}ms\u2026`);
320
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
321
+ }
322
+ log(`connecting to ${url}`);
323
+ let res;
324
+ try {
325
+ res = await fetch(url, {
326
+ headers: { Authorization: `Bearer ${apiKey}` },
327
+ signal
328
+ });
329
+ } catch (err) {
330
+ if (err.name === "AbortError") throw err;
331
+ log(`fetch error: ${err.message}`);
332
+ retries++;
333
+ continue;
334
+ }
335
+ log(`response: status=${res.status} content-type=${res.headers.get("content-type")}`);
336
+ if (!res.ok) {
337
+ const body = await res.text();
338
+ log(`error body: ${body}`);
339
+ throw new Error(`SSE stream failed (${res.status}): ${body}`);
340
+ }
341
+ if (!res.body) {
342
+ throw new Error("SSE stream returned no body");
343
+ }
344
+ const reader = res.body.getReader();
345
+ const decoder = new TextDecoder();
346
+ let buffer = "";
347
+ let chunkCount = 0;
348
+ let eventCount = 0;
349
+ let gotRunComplete = false;
350
+ let streamError = null;
351
+ try {
352
+ while (true) {
353
+ let readResult;
354
+ try {
355
+ readResult = await reader.read();
356
+ } catch (err) {
357
+ if (err.name === "AbortError") throw err;
358
+ streamError = err;
359
+ log(`read error: ${streamError.message}`);
360
+ break;
361
+ }
362
+ const { done, value } = readResult;
363
+ if (done) {
364
+ log(`stream done after ${chunkCount} chunks, ${eventCount} events`);
365
+ break;
366
+ }
367
+ chunkCount++;
368
+ const chunk = decoder.decode(value, { stream: true });
369
+ buffer += chunk;
370
+ if (chunkCount <= 3 || chunkCount % 10 === 0) {
371
+ log(`chunk #${chunkCount} (${chunk.length} bytes) buffer=${buffer.length} bytes`);
372
+ }
373
+ const lines = buffer.split("\n");
374
+ buffer = lines.pop();
375
+ for (const line of lines) {
376
+ if (line.startsWith("data: ")) {
377
+ const raw = line.slice(6);
378
+ try {
379
+ const event = JSON.parse(raw);
380
+ eventCount++;
381
+ if (event.id && seenIds.has(event.id)) {
382
+ log(`skipping duplicate event ${event.id}`);
383
+ continue;
384
+ }
385
+ if (event.id) seenIds.add(event.id);
386
+ log(`parsed event #${eventCount}: type=${event.event_type}`);
387
+ yield event;
388
+ if (event.event_type === "run_complete") {
389
+ log("run_complete received \u2014 closing stream");
390
+ gotRunComplete = true;
391
+ return;
392
+ }
393
+ } catch {
394
+ log(`malformed JSON: ${raw.slice(0, 200)}`);
395
+ }
396
+ } else if (line.startsWith(": ")) {
397
+ if (chunkCount <= 3) {
398
+ log(`heartbeat: "${line}"`);
354
399
  }
355
- } catch {
356
- log(`malformed JSON: ${raw.slice(0, 200)}`);
357
- }
358
- } else if (line.startsWith(": ")) {
359
- if (chunkCount <= 3) {
360
- log(`heartbeat: "${line}"`);
361
400
  }
362
401
  }
363
402
  }
403
+ } finally {
404
+ reader.releaseLock();
405
+ log("reader released");
406
+ }
407
+ if (gotRunComplete) return;
408
+ retries++;
409
+ if (retries <= MAX_RETRIES) {
410
+ log(`stream ended without run_complete \u2014 will retry (${retries}/${MAX_RETRIES})`);
364
411
  }
365
- } finally {
366
- reader.releaseLock();
367
- log("reader released");
368
412
  }
413
+ log(`exhausted ${MAX_RETRIES} retries without run_complete`);
369
414
  }
370
415
 
371
416
  // src/lib/relay.ts
@@ -720,6 +765,17 @@ async function runCommand(args) {
720
765
  }
721
766
  debug(`filtered to test: ${args.test}`);
722
767
  }
768
+ const cfgPlatform = config;
769
+ if (cfgPlatform.connection?.platform?.api_key_env && !cfgPlatform.connection.platform.api_key) {
770
+ const envName = cfgPlatform.connection.platform.api_key_env;
771
+ const resolved = process.env[envName];
772
+ if (!resolved) {
773
+ printError(`Platform API key not found: environment variable ${envName} is not set.`);
774
+ return 2;
775
+ }
776
+ cfgPlatform.connection.platform.api_key = resolved;
777
+ debug(`resolved platform API key from ${envName}`);
778
+ }
723
779
  const cfg = config;
724
780
  if (cfg.connection?.start_command) {
725
781
  const freePort = await findFreePort();
@@ -5037,6 +5093,7 @@ var ToolCallMetricsSchema = external_exports.object({
5037
5093
  var PlatformConfigSchema = external_exports.object({
5038
5094
  provider: external_exports.enum(["vapi", "retell", "elevenlabs", "bland"]),
5039
5095
  api_key_env: external_exports.string(),
5096
+ api_key: external_exports.string().optional(),
5040
5097
  agent_id: external_exports.string().optional()
5041
5098
  });
5042
5099
  var AudioAnalysisGradeThresholdsSchema = external_exports.object({
@@ -6577,7 +6634,7 @@ async function main() {
6577
6634
  return 0;
6578
6635
  }
6579
6636
  if (command === "--version" || command === "-v") {
6580
- const pkg = await import("./package-EHKFHRSK.mjs");
6637
+ const pkg = await import("./package-ULWE2HB5.mjs");
6581
6638
  console.log(`vent-hq ${pkg.default.version}`);
6582
6639
  return 0;
6583
6640
  }
@@ -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.8.3",
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.8.2",
3
+ "version": "0.8.3",
4
4
  "type": "module",
5
5
  "description": "Vent CLI — CI/CD for voice AI agents",
6
6
  "bin": {
@@ -9,6 +9,10 @@
9
9
  "files": [
10
10
  "dist"
11
11
  ],
12
+ "scripts": {
13
+ "build": "node scripts/bundle.mjs",
14
+ "clean": "rm -rf dist"
15
+ },
12
16
  "keywords": [
13
17
  "vent",
14
18
  "cli",
@@ -33,12 +37,8 @@
33
37
  },
34
38
  "devDependencies": {
35
39
  "@types/ws": "^8.5.0",
36
- "esbuild": "^0.24.0",
37
- "@vent/relay-client": "0.1.0",
38
- "@vent/shared": "0.0.1"
39
- },
40
- "scripts": {
41
- "build": "node scripts/bundle.mjs",
42
- "clean": "rm -rf dist"
40
+ "@vent/relay-client": "workspace:*",
41
+ "@vent/shared": "workspace:*",
42
+ "esbuild": "^0.24.0"
43
43
  }
44
- }
44
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Stephan Gazarov
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.