@hasna/testers 0.0.47 → 0.0.49

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/cli/index.js CHANGED
@@ -27025,12 +27025,60 @@ function listTestingWorkflows(filter) {
27025
27025
  const rows = db2.query(`SELECT * FROM testing_workflows${where} ORDER BY created_at DESC`).all(...params);
27026
27026
  return rows.map(workflowFromRow);
27027
27027
  }
27028
+ function updateTestingWorkflow(id, input) {
27029
+ const existing = getTestingWorkflow(id);
27030
+ if (!existing)
27031
+ throw new Error(`Testing workflow not found: ${id}`);
27032
+ const fields = [];
27033
+ const values = [];
27034
+ if (input.name !== undefined) {
27035
+ fields.push("name = ?");
27036
+ values.push(input.name);
27037
+ }
27038
+ if (input.description !== undefined) {
27039
+ fields.push("description = ?");
27040
+ values.push(input.description);
27041
+ }
27042
+ if (input.scenarioFilter !== undefined) {
27043
+ fields.push("scenario_filter = ?");
27044
+ values.push(JSON.stringify(normalizeFilter(input.scenarioFilter)));
27045
+ }
27046
+ if (input.personaIds !== undefined) {
27047
+ fields.push("persona_ids = ?");
27048
+ values.push(JSON.stringify(input.personaIds));
27049
+ }
27050
+ if (input.goal !== undefined) {
27051
+ fields.push("goal = ?");
27052
+ values.push(JSON.stringify(normalizeGoal(input.goal)));
27053
+ }
27054
+ if (input.execution !== undefined) {
27055
+ fields.push("execution = ?");
27056
+ values.push(JSON.stringify(normalizeExecution(input.execution)));
27057
+ }
27058
+ if (input.settings !== undefined) {
27059
+ fields.push("settings = ?");
27060
+ values.push(JSON.stringify(input.settings));
27061
+ }
27062
+ if (input.enabled !== undefined) {
27063
+ fields.push("enabled = ?");
27064
+ values.push(input.enabled ? 1 : 0);
27065
+ }
27066
+ if (fields.length === 0)
27067
+ return existing;
27068
+ fields.push("updated_at = ?");
27069
+ values.push(now(), existing.id);
27070
+ db2().query(`UPDATE testing_workflows SET ${fields.join(", ")} WHERE id = ?`).run(...values);
27071
+ return getTestingWorkflow(existing.id);
27072
+ }
27028
27073
  function deleteTestingWorkflow(id) {
27029
27074
  const existing = getTestingWorkflow(id);
27030
27075
  if (!existing)
27031
27076
  return false;
27032
27077
  return getDatabase().query("DELETE FROM testing_workflows WHERE id = ?").run(existing.id).changes > 0;
27033
27078
  }
27079
+ function db2() {
27080
+ return getDatabase();
27081
+ }
27034
27082
  var DEFAULT_EXECUTION;
27035
27083
  var init_workflows = __esm(() => {
27036
27084
  init_types();
@@ -27293,6 +27341,13 @@ function resolveSandboxEnv(env) {
27293
27341
  return;
27294
27342
  const resolved = {};
27295
27343
  for (const [key, value] of Object.entries(env)) {
27344
+ if (value.startsWith("$?")) {
27345
+ const optionalName = value.slice(2).trim();
27346
+ const optionalValue = optionalName ? process.env[optionalName] : undefined;
27347
+ if (optionalValue !== undefined)
27348
+ resolved[key] = optionalValue;
27349
+ continue;
27350
+ }
27296
27351
  const resolvedValue = resolveCredential(value);
27297
27352
  if (resolvedValue === null) {
27298
27353
  throw new Error(`Missing sandbox env value for ${key}`);
@@ -27326,6 +27381,8 @@ var init_workflow_runner = __esm(() => {
27326
27381
  ".next",
27327
27382
  ".turbo",
27328
27383
  ".cache",
27384
+ ".env",
27385
+ ".env.*",
27329
27386
  ".venv",
27330
27387
  "__pycache__"
27331
27388
  ];
@@ -27479,46 +27536,46 @@ __export(exports_sessions, {
27479
27536
  countSessions: () => countSessions
27480
27537
  });
27481
27538
  function createSession(input) {
27482
- const db2 = getDatabase();
27539
+ const db3 = getDatabase();
27483
27540
  const id = input.sessionId ?? uuid();
27484
27541
  const timestamp = now();
27485
- db2.query(`
27542
+ db3.query(`
27486
27543
  INSERT INTO sessions (id, tab_id, url, title, entries, entry_count, error_count, console_count, nav_count, status, start_time, end_time, created_at)
27487
27544
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
27488
27545
  `).run(id, input.tabId, input.url ?? null, input.title ?? null, input.entries, input.entryCount, input.errorCount ?? 0, input.consoleCount ?? 0, input.navCount ?? 0, input.status, input.startTime, input.endTime ?? null, timestamp);
27489
27546
  return getSession(id);
27490
27547
  }
27491
27548
  function getSession(id) {
27492
- const db2 = getDatabase();
27493
- let row = db2.query("SELECT * FROM sessions WHERE id = ?").get(id);
27549
+ const db3 = getDatabase();
27550
+ let row = db3.query("SELECT * FROM sessions WHERE id = ?").get(id);
27494
27551
  if (row)
27495
27552
  return sessionFromRow(row);
27496
27553
  const fullId = resolvePartialId("sessions", id);
27497
27554
  if (fullId) {
27498
- row = db2.query("SELECT * FROM sessions WHERE id = ?").get(fullId);
27555
+ row = db3.query("SELECT * FROM sessions WHERE id = ?").get(fullId);
27499
27556
  if (row)
27500
27557
  return sessionFromRow(row);
27501
27558
  }
27502
27559
  return null;
27503
27560
  }
27504
27561
  function listSessions(limit = 50, offset = 0) {
27505
- const db2 = getDatabase();
27506
- const rows = db2.query("SELECT * FROM sessions ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
27562
+ const db3 = getDatabase();
27563
+ const rows = db3.query("SELECT * FROM sessions ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
27507
27564
  return rows.map(sessionFromRow);
27508
27565
  }
27509
27566
  function deleteSession(id) {
27510
- const db2 = getDatabase();
27511
- const result = db2.query("DELETE FROM sessions WHERE id = ?").run(id);
27567
+ const db3 = getDatabase();
27568
+ const result = db3.query("DELETE FROM sessions WHERE id = ?").run(id);
27512
27569
  return result.changes > 0;
27513
27570
  }
27514
27571
  function searchSessions(query, limit = 20) {
27515
- const db2 = getDatabase();
27516
- const rows = db2.query("SELECT * FROM sessions WHERE url LIKE ? OR title LIKE ? ORDER BY created_at DESC LIMIT ?").all(`%${query}%`, `%${query}%`, limit);
27572
+ const db3 = getDatabase();
27573
+ const rows = db3.query("SELECT * FROM sessions WHERE url LIKE ? OR title LIKE ? ORDER BY created_at DESC LIMIT ?").all(`%${query}%`, `%${query}%`, limit);
27517
27574
  return rows.map(sessionFromRow);
27518
27575
  }
27519
27576
  function countSessions() {
27520
- const db2 = getDatabase();
27521
- const row = db2.query("SELECT COUNT(*) as count FROM sessions").get();
27577
+ const db3 = getDatabase();
27578
+ const row = db3.query("SELECT COUNT(*) as count FROM sessions").get();
27522
27579
  return row.count;
27523
27580
  }
27524
27581
  function sessionFromRow(row) {
@@ -28046,8 +28103,8 @@ function customRandom(alphabet, defaultSize, getRandom) {
28046
28103
  function customAlphabet(alphabet, size = 21) {
28047
28104
  return customRandom(alphabet, size, random);
28048
28105
  }
28049
- function runMigrations(db2) {
28050
- db2.run(`
28106
+ function runMigrations(db3) {
28107
+ db3.run(`
28051
28108
  CREATE TABLE IF NOT EXISTS _migrations (
28052
28109
  id INTEGER PRIMARY KEY,
28053
28110
  applied_at TEXT NOT NULL DEFAULT (datetime('now'))
@@ -28055,9 +28112,9 @@ function runMigrations(db2) {
28055
28112
  `);
28056
28113
  for (let i = 0;i < MIGRATIONS2.length; i++) {
28057
28114
  const migrationId = i + 1;
28058
- const exists = db2.query("SELECT id FROM _migrations WHERE id = ?").get(migrationId);
28115
+ const exists = db3.query("SELECT id FROM _migrations WHERE id = ?").get(migrationId);
28059
28116
  if (!exists) {
28060
- db2.run(MIGRATIONS2[i]);
28117
+ db3.run(MIGRATIONS2[i]);
28061
28118
  }
28062
28119
  }
28063
28120
  }
@@ -28082,11 +28139,11 @@ function ensureDir2(filePath) {
28082
28139
  function getDatabase2(path) {
28083
28140
  if (path) {
28084
28141
  ensureDir2(path);
28085
- const db2 = new Database4(path);
28086
- db2.run("PRAGMA journal_mode=WAL");
28087
- db2.run("PRAGMA foreign_keys=ON");
28088
- runMigrations(db2);
28089
- return db2;
28142
+ const db3 = new Database4(path);
28143
+ db3.run("PRAGMA journal_mode=WAL");
28144
+ db3.run("PRAGMA foreign_keys=ON");
28145
+ runMigrations(db3);
28146
+ return db3;
28090
28147
  }
28091
28148
  if (!_db) {
28092
28149
  const dbPath = getDbPath2();
@@ -28154,8 +28211,8 @@ function rowToWorkdir(row) {
28154
28211
  function getMachineId() {
28155
28212
  return process.env["HOSTNAME"] || hostname();
28156
28213
  }
28157
- function addWorkdir(input, db2) {
28158
- const d = db2 || getDatabase2();
28214
+ function addWorkdir(input, db3) {
28215
+ const d = db3 || getDatabase2();
28159
28216
  const project = getProject2(input.project_id, d);
28160
28217
  if (!project)
28161
28218
  throw new ProjectNotFoundError(input.project_id);
@@ -28172,18 +28229,18 @@ function addWorkdir(input, db2) {
28172
28229
  ON CONFLICT(project_id, path) DO UPDATE SET label = excluded.label, machine_id = excluded.machine_id`, [id, input.project_id, input.path, machineId, input.label ?? "main", isPrimary, ts]);
28173
28230
  return getWorkdir(input.project_id, input.path, d);
28174
28231
  }
28175
- function getWorkdir(projectId, path, db2) {
28176
- const d = db2 || getDatabase2();
28232
+ function getWorkdir(projectId, path, db3) {
28233
+ const d = db3 || getDatabase2();
28177
28234
  const row = d.query("SELECT * FROM project_workdirs WHERE project_id = ? AND path = ?").get(projectId, path);
28178
28235
  return row ? rowToWorkdir(row) : null;
28179
28236
  }
28180
- function listWorkdirs(projectId, db2) {
28181
- const d = db2 || getDatabase2();
28237
+ function listWorkdirs(projectId, db3) {
28238
+ const d = db3 || getDatabase2();
28182
28239
  const rows = d.query("SELECT * FROM project_workdirs WHERE project_id = ? ORDER BY is_primary DESC, created_at ASC").all(projectId);
28183
28240
  return rows.map(rowToWorkdir);
28184
28241
  }
28185
- function removeWorkdir(projectId, path, db2) {
28186
- const d = db2 || getDatabase2();
28242
+ function removeWorkdir(projectId, path, db3) {
28243
+ const d = db3 || getDatabase2();
28187
28244
  d.run("DELETE FROM project_workdirs WHERE project_id = ? AND path = ?", [projectId, path]);
28188
28245
  }
28189
28246
  function generateProjectId() {
@@ -28201,11 +28258,11 @@ function scaffoldProject(path) {
28201
28258
  mkdirSync32(join42(path, dir), { recursive: true });
28202
28259
  }
28203
28260
  }
28204
- function ensureUniqueSlug(base, db2, excludeId) {
28261
+ function ensureUniqueSlug(base, db3, excludeId) {
28205
28262
  let candidate = base;
28206
28263
  let suffix = 1;
28207
28264
  while (true) {
28208
- const row = db2.query("SELECT id FROM projects WHERE slug = ?").get(candidate);
28265
+ const row = db3.query("SELECT id FROM projects WHERE slug = ?").get(candidate);
28209
28266
  if (!row || row.id === excludeId)
28210
28267
  return candidate;
28211
28268
  suffix++;
@@ -28221,8 +28278,8 @@ function rowToProject(row) {
28221
28278
  last_opened_at: row.last_opened_at ?? null
28222
28279
  };
28223
28280
  }
28224
- function createProject2(input, db2) {
28225
- const d = db2 || getDatabase2();
28281
+ function createProject2(input, db3) {
28282
+ const d = db3 || getDatabase2();
28226
28283
  const id = generateProjectId();
28227
28284
  const ts = now2();
28228
28285
  const baseSlug = input.slug || slugify2(input.name);
@@ -28257,23 +28314,23 @@ function createProject2(input, db2) {
28257
28314
  }
28258
28315
  return getProject2(id, d);
28259
28316
  }
28260
- function getProject2(id, db2) {
28261
- const d = db2 || getDatabase2();
28317
+ function getProject2(id, db3) {
28318
+ const d = db3 || getDatabase2();
28262
28319
  const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
28263
28320
  return row ? rowToProject(row) : null;
28264
28321
  }
28265
- function getProjectBySlug(slug, db2) {
28266
- const d = db2 || getDatabase2();
28322
+ function getProjectBySlug(slug, db3) {
28323
+ const d = db3 || getDatabase2();
28267
28324
  const row = d.query("SELECT * FROM projects WHERE slug = ?").get(slug);
28268
28325
  return row ? rowToProject(row) : null;
28269
28326
  }
28270
- function getProjectByPath2(path, db2) {
28271
- const d = db2 || getDatabase2();
28327
+ function getProjectByPath2(path, db3) {
28328
+ const d = db3 || getDatabase2();
28272
28329
  const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
28273
28330
  return row ? rowToProject(row) : null;
28274
28331
  }
28275
- function listProjects2(filter = {}, db2) {
28276
- const d = db2 || getDatabase2();
28332
+ function listProjects2(filter = {}, db3) {
28333
+ const d = db3 || getDatabase2();
28277
28334
  const conditions = [];
28278
28335
  const params = [];
28279
28336
  if (filter.status) {
@@ -28292,8 +28349,8 @@ function listProjects2(filter = {}, db2) {
28292
28349
  }
28293
28350
  return rows.map(rowToProject);
28294
28351
  }
28295
- function updateProject(id, input, db2) {
28296
- const d = db2 || getDatabase2();
28352
+ function updateProject(id, input, db3) {
28353
+ const d = db3 || getDatabase2();
28297
28354
  const project = getProject2(id, d);
28298
28355
  if (!project)
28299
28356
  throw new ProjectNotFoundError(id);
@@ -28342,8 +28399,8 @@ function updateProject(id, input, db2) {
28342
28399
  d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
28343
28400
  return getProject2(id, d);
28344
28401
  }
28345
- function setIntegrations(id, integrations, db2) {
28346
- const d = db2 || getDatabase2();
28402
+ function setIntegrations(id, integrations, db3) {
28403
+ const d = db3 || getDatabase2();
28347
28404
  const project = getProject2(id, d);
28348
28405
  if (!project)
28349
28406
  throw new ProjectNotFoundError(id);
@@ -28363,16 +28420,16 @@ function setIntegrations(id, integrations, db2) {
28363
28420
  } catch {}
28364
28421
  return getProject2(id, d);
28365
28422
  }
28366
- function archiveProject(id, db2) {
28367
- const d = db2 || getDatabase2();
28423
+ function archiveProject(id, db3) {
28424
+ const d = db3 || getDatabase2();
28368
28425
  const project = getProject2(id, d);
28369
28426
  if (!project)
28370
28427
  throw new ProjectNotFoundError(id);
28371
28428
  d.run("UPDATE projects SET status = 'archived', updated_at = ? WHERE id = ?", [now2(), id]);
28372
28429
  return getProject2(id, d);
28373
28430
  }
28374
- function unarchiveProject(id, db2) {
28375
- const d = db2 || getDatabase2();
28431
+ function unarchiveProject(id, db3) {
28432
+ const d = db3 || getDatabase2();
28376
28433
  const project = getProject2(id, d);
28377
28434
  if (!project)
28378
28435
  throw new ProjectNotFoundError(id);
@@ -28389,8 +28446,8 @@ function levenshtein(a, b) {
28389
28446
  dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
28390
28447
  return dp[m][n];
28391
28448
  }
28392
- function resolveProject(idOrSlug, db2) {
28393
- const d = db2 || getDatabase2();
28449
+ function resolveProject(idOrSlug, db3) {
28450
+ const d = db3 || getDatabase2();
28394
28451
  let project = getProject2(idOrSlug, d);
28395
28452
  if (project)
28396
28453
  return project;
@@ -28413,22 +28470,22 @@ function resolveProject(idOrSlug, db2) {
28413
28470
  function syncRowToLog(row) {
28414
28471
  return { ...row, direction: row.direction, status: row.status };
28415
28472
  }
28416
- function startSyncLog(projectId, direction, db2) {
28417
- const d = db2 || getDatabase2();
28473
+ function startSyncLog(projectId, direction, db3) {
28474
+ const d = db3 || getDatabase2();
28418
28475
  const id = uuid2();
28419
28476
  const ts = now2();
28420
28477
  d.run("INSERT INTO sync_log (id, project_id, direction, status, started_at) VALUES (?, ?, ?, 'running', ?)", [id, projectId, direction, ts]);
28421
28478
  return d.query("SELECT * FROM sync_log WHERE id = ?").get(id);
28422
28479
  }
28423
- function completeSyncLog(id, result, db2) {
28424
- const d = db2 || getDatabase2();
28480
+ function completeSyncLog(id, result, db3) {
28481
+ const d = db3 || getDatabase2();
28425
28482
  const status = result.error ? "failed" : "completed";
28426
28483
  d.run("UPDATE sync_log SET status = ?, files_synced = ?, bytes = ?, error = ?, completed_at = ? WHERE id = ?", [status, result.files_synced ?? 0, result.bytes ?? 0, result.error ?? null, now2(), id]);
28427
28484
  const row = d.query("SELECT * FROM sync_log WHERE id = ?").get(id);
28428
28485
  return syncRowToLog(row);
28429
28486
  }
28430
- function listSyncLogs(projectId, limit = 20, db2) {
28431
- const d = db2 || getDatabase2();
28487
+ function listSyncLogs(projectId, limit = 20, db3) {
28488
+ const d = db3 || getDatabase2();
28432
28489
  const rows = d.query("SELECT * FROM sync_log WHERE project_id = ? ORDER BY started_at DESC LIMIT ?").all(projectId, limit);
28433
28490
  return rows.map(syncRowToLog);
28434
28491
  }
@@ -58796,7 +58853,7 @@ function buildPeriod(days) {
58796
58853
  return { periodStart, periodEnd };
58797
58854
  }
58798
58855
  async function collectComplianceData(options) {
58799
- const db2 = getDatabase();
58856
+ const db3 = getDatabase();
58800
58857
  const runsInPeriod = listRuns({
58801
58858
  projectId: options.projectId,
58802
58859
  limit: 1e4
@@ -58811,13 +58868,13 @@ async function collectComplianceData(options) {
58811
58868
  scanConditions.push("project_id = ?");
58812
58869
  scanParams.push(options.projectId);
58813
58870
  }
58814
- const scanIssues = db2.query(`SELECT type, severity FROM scan_issues WHERE ${scanConditions.join(" AND ")}`).all(...scanParams);
58871
+ const scanIssues = db3.query(`SELECT type, severity FROM scan_issues WHERE ${scanConditions.join(" AND ")}`).all(...scanParams);
58815
58872
  const injectionProbesRun = scanIssues.filter((i2) => i2.type === "injection" || i2.type === "sql_injection" || i2.type === "xss").length;
58816
58873
  const injectionVulnsFound = scanIssues.filter((i2) => (i2.type === "injection" || i2.type === "xss") && (i2.severity === "high" || i2.severity === "critical")).length;
58817
58874
  const piiLeaksDetected = scanIssues.filter((i2) => i2.type === "pii" || i2.type === "pii_leak").length;
58818
58875
  let goldenAnswerDriftEvents = 0;
58819
58876
  try {
58820
- const driftRows = db2.query(`SELECT COUNT(*) as count FROM golden_check_results WHERE drift_detected = 1 AND created_at >= ? AND created_at <= ?`).get(options.periodStart, options.periodEnd);
58877
+ const driftRows = db3.query(`SELECT COUNT(*) as count FROM golden_check_results WHERE drift_detected = 1 AND created_at >= ? AND created_at <= ?`).get(options.periodStart, options.periodEnd);
58821
58878
  goldenAnswerDriftEvents = driftRows?.count ?? 0;
58822
58879
  } catch {
58823
58880
  goldenAnswerDriftEvents = 0;
@@ -58829,7 +58886,7 @@ async function collectComplianceData(options) {
58829
58886
  let flakyScenarioCount = 0;
58830
58887
  if (runIds.length > 0) {
58831
58888
  const placeholders = runIds.map(() => "?").join(", ");
58832
- const evalResults = db2.query(`SELECT r.status, r.metadata FROM results r
58889
+ const evalResults = db3.query(`SELECT r.status, r.metadata FROM results r
58833
58890
  JOIN scenarios s ON r.scenario_id = s.id
58834
58891
  WHERE r.run_id IN (${placeholders}) AND s.scenario_type = 'eval'`).all(...runIds);
58835
58892
  evalScenariosRun = evalResults.length;
@@ -58842,9 +58899,9 @@ async function collectComplianceData(options) {
58842
58899
  totalEvalScore += er.status === "passed" ? 1 : 0;
58843
58900
  }
58844
58901
  }
58845
- const a11yRows = db2.query(`SELECT COUNT(*) as count FROM scan_issues WHERE type = 'a11y' AND severity = 'critical' AND first_seen_at >= ? AND first_seen_at <= ?`).get(options.periodStart, options.periodEnd);
58902
+ const a11yRows = db3.query(`SELECT COUNT(*) as count FROM scan_issues WHERE type = 'a11y' AND severity = 'critical' AND first_seen_at >= ? AND first_seen_at <= ?`).get(options.periodStart, options.periodEnd);
58846
58903
  a11yViolationsCritical = a11yRows?.count ?? 0;
58847
- const flakyRows = db2.query(`SELECT COUNT(DISTINCT scenario_id) as count FROM results WHERE run_id IN (${placeholders}) AND status = 'flaky'`).get(...runIds);
58904
+ const flakyRows = db3.query(`SELECT COUNT(DISTINCT scenario_id) as count FROM results WHERE run_id IN (${placeholders}) AND status = 'flaky'`).get(...runIds);
58848
58905
  flakyScenarioCount = flakyRows?.count ?? 0;
58849
58906
  }
58850
58907
  const averageEvalScore = evalScenariosRun > 0 ? totalEvalScore / evalScenariosRun : 1;
@@ -59093,6 +59150,297 @@ var init_openapi_import = __esm(() => {
59093
59150
  init_api_checks();
59094
59151
  });
59095
59152
 
59153
+ // src/lib/next-route-inventory.ts
59154
+ var exports_next_route_inventory = {};
59155
+ __export(exports_next_route_inventory, {
59156
+ scenarioInputForNextRoute: () => scenarioInputForNextRoute,
59157
+ importNextRouteInventory: () => importNextRouteInventory,
59158
+ discoverNextRouteInventory: () => discoverNextRouteInventory
59159
+ });
59160
+ import { existsSync as existsSync18, readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
59161
+ import { basename as basename3, join as join20, relative as relative4, resolve as resolve3 } from "path";
59162
+ function discoverNextRouteInventory(options) {
59163
+ const rootDir = resolve3(options.rootDir);
59164
+ const appDir = resolveAppDir(rootDir, options.appDir);
59165
+ const includePages = options.includePages !== false;
59166
+ const includeApi = options.includeApi !== false;
59167
+ const files = walkRouteFiles(appDir);
59168
+ const items = files.map((file) => routeItemFromFile(rootDir, appDir, file)).filter((item) => Boolean(item)).filter((item) => item.kind === "page" ? includePages : includeApi).sort((a2, b2) => `${a2.kind}:${a2.routePath}:${a2.file}`.localeCompare(`${b2.kind}:${b2.routePath}:${b2.file}`)).slice(0, options.limit);
59169
+ const categories = {};
59170
+ for (const item of items) {
59171
+ categories[item.category] = (categories[item.category] ?? 0) + 1;
59172
+ }
59173
+ return {
59174
+ rootDir,
59175
+ appDir,
59176
+ total: items.length,
59177
+ pages: items.filter((item) => item.kind === "page").length,
59178
+ apiRoutes: items.filter((item) => item.kind === "api").length,
59179
+ dynamic: items.filter((item) => item.dynamic).length,
59180
+ categories,
59181
+ items
59182
+ };
59183
+ }
59184
+ function scenarioInputForNextRoute(item, projectId) {
59185
+ const label = item.kind === "page" ? "page" : "API route";
59186
+ const methodList = item.methods.length > 0 ? item.methods.join(", ") : "discovered methods";
59187
+ const dynamicStep = item.dynamic ? "Substitute dynamic path parameters with valid fixture values from the target org before opening or calling the route." : undefined;
59188
+ const pageSteps = [
59189
+ dynamicStep,
59190
+ `Open the Next.js ${label} ${item.routePath}.`,
59191
+ "Wait for the route to finish loading and verify it does not show a blank shell, framework error page, or unexpected auth loop.",
59192
+ "Exercise visible primary navigation, tabs, filters, dialogs, forms, and safe buttons on this route.",
59193
+ "Verify the route stays within the expected org/workspace context and does not emit console errors."
59194
+ ].filter(Boolean);
59195
+ const apiSteps = [
59196
+ dynamicStep,
59197
+ `Call the ${methodList} handler(s) for ${item.routePath} using safe fixture data.`,
59198
+ "Verify expected authentication, authorization, validation, and tenant isolation behavior.",
59199
+ "For mutating methods, use harmless test payloads and confirm the response does not create cross-org side effects.",
59200
+ "Verify response status, JSON shape, and error messages are stable and regression-safe."
59201
+ ].filter(Boolean);
59202
+ return {
59203
+ name: `Next ${label}: ${item.routePath}`,
59204
+ description: `Source-discovered ${label} from ${item.file}. Verify route behavior and regressions for ${item.category}.`,
59205
+ steps: item.kind === "page" ? pageSteps : apiSteps,
59206
+ tags: item.tags,
59207
+ priority: item.priority,
59208
+ targetPath: item.routePath,
59209
+ requiresAuth: item.requiresAuth,
59210
+ assertions: item.kind === "page" ? SAFE_PAGE_ASSERTIONS : [],
59211
+ metadata: {
59212
+ source: "next-route-inventory",
59213
+ routeFile: item.file,
59214
+ routeKind: item.kind,
59215
+ category: item.category,
59216
+ methods: item.methods,
59217
+ dynamic: item.dynamic,
59218
+ groups: item.groups
59219
+ },
59220
+ projectId
59221
+ };
59222
+ }
59223
+ function importNextRouteInventory(options) {
59224
+ const inventory = discoverNextRouteInventory(options);
59225
+ let created = 0;
59226
+ let updated = 0;
59227
+ let deduped = 0;
59228
+ const scenarios = [];
59229
+ const workflows = [];
59230
+ if (options.createScenarios) {
59231
+ for (const item of inventory.items) {
59232
+ const result = upsertScenario(scenarioInputForNextRoute(item, options.projectId));
59233
+ scenarios.push(result.scenario);
59234
+ if (result.action === "created")
59235
+ created++;
59236
+ else if (result.action === "updated")
59237
+ updated++;
59238
+ else
59239
+ deduped++;
59240
+ }
59241
+ }
59242
+ if (options.createWorkflows) {
59243
+ workflows.push(...upsertRouteInventoryWorkflows(inventory, options));
59244
+ }
59245
+ return { inventory, created, updated, deduped, scenarios, workflows };
59246
+ }
59247
+ function upsertRouteInventoryWorkflows(inventory, options) {
59248
+ const workflows = [];
59249
+ const categories = Object.keys(inventory.categories).sort();
59250
+ for (const category of categories) {
59251
+ const kinds = new Set(inventory.items.filter((item) => item.category === category).map((item) => item.kind));
59252
+ for (const kind of kinds) {
59253
+ const name = `Next route inventory ${category} ${kind}`;
59254
+ const scenarioTags = ["next-route", `area:${category}`, `route:${kind}`];
59255
+ const execution = {
59256
+ target: options.workflowTarget ?? "sandbox",
59257
+ provider: options.workflowProvider,
59258
+ sandboxCleanup: "delete",
59259
+ sandboxSyncStrategy: "rsync",
59260
+ ...options.workflowExecution
59261
+ };
59262
+ const existing = listTestingWorkflows({ projectId: options.projectId, enabled: undefined }).find((workflow) => workflow.name === name);
59263
+ const input = {
59264
+ name,
59265
+ description: `Source-discovered Next.js ${kind} coverage for ${category} routes.`,
59266
+ projectId: options.projectId,
59267
+ scenarioFilter: { tags: scenarioTags },
59268
+ execution
59269
+ };
59270
+ workflows.push(existing ? updateTestingWorkflow(existing.id, input) : createTestingWorkflow(input));
59271
+ }
59272
+ }
59273
+ return workflows;
59274
+ }
59275
+ function resolveAppDir(rootDir, appDir) {
59276
+ const candidates = appDir ? [resolve3(rootDir, appDir)] : [
59277
+ join20(rootDir, "packages", "web", "app"),
59278
+ join20(rootDir, "app"),
59279
+ rootDir
59280
+ ];
59281
+ for (const candidate of candidates) {
59282
+ if (existsSync18(candidate) && statSync4(candidate).isDirectory())
59283
+ return candidate;
59284
+ }
59285
+ throw new Error(`Next.js app directory not found under ${rootDir}`);
59286
+ }
59287
+ function walkRouteFiles(appDir) {
59288
+ const files = [];
59289
+ function walk(dir) {
59290
+ for (const entry of readdirSync6(dir)) {
59291
+ if (WALK_EXCLUDES.has(entry))
59292
+ continue;
59293
+ const fullPath = join20(dir, entry);
59294
+ const stat = statSync4(fullPath);
59295
+ if (stat.isDirectory()) {
59296
+ walk(fullPath);
59297
+ } else if (ROUTE_FILE_NAMES.has(entry)) {
59298
+ files.push(fullPath);
59299
+ }
59300
+ }
59301
+ }
59302
+ walk(appDir);
59303
+ return files;
59304
+ }
59305
+ function routeItemFromFile(rootDir, appDir, file) {
59306
+ const fileName = basename3(file);
59307
+ const kind = fileName.startsWith("page.") ? "page" : "api";
59308
+ const relativeFile = relative4(rootDir, file);
59309
+ const appRelative = relative4(appDir, file).split(/[\\/]/);
59310
+ const routeSegments = appRelative.slice(0, -1);
59311
+ const groups = routeSegments.filter((segment) => segment.startsWith("(") && segment.endsWith(")")).map((segment) => segment.slice(1, -1));
59312
+ const pathSegments = routeSegments.filter((segment) => !segment.startsWith("(")).filter((segment) => !segment.startsWith("@")).map(normalizeRouteSegment).filter(Boolean);
59313
+ const routePath = `/${pathSegments.join("/")}`.replace(/\/+/g, "/");
59314
+ const normalizedRoutePath = routePath === "/" ? "/" : routePath.replace(/\/$/, "");
59315
+ const methods = kind === "api" ? extractRouteMethods(file) : [];
59316
+ const category = classifyRoute(normalizedRoutePath, groups, relativeFile);
59317
+ const dynamic = routeSegments.some((segment) => segment.includes("["));
59318
+ const requiresAuth = inferRequiresAuth(normalizedRoutePath, groups, kind);
59319
+ return {
59320
+ kind,
59321
+ routePath: normalizedRoutePath,
59322
+ file: relativeFile,
59323
+ category,
59324
+ groups,
59325
+ methods,
59326
+ dynamic,
59327
+ requiresAuth,
59328
+ tags: tagsForRoute({ kind, routePath: normalizedRoutePath, category, groups, dynamic, requiresAuth }),
59329
+ priority: priorityForRoute(normalizedRoutePath, category, kind)
59330
+ };
59331
+ }
59332
+ function normalizeRouteSegment(segment) {
59333
+ if (segment.startsWith("[[...") && segment.endsWith("]]")) {
59334
+ return `:${segment.slice(5, -2)}*?`;
59335
+ }
59336
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
59337
+ return `:${segment.slice(4, -1)}*`;
59338
+ }
59339
+ if (segment.startsWith("[") && segment.endsWith("]")) {
59340
+ return `:${segment.slice(1, -1)}`;
59341
+ }
59342
+ return segment;
59343
+ }
59344
+ function extractRouteMethods(file) {
59345
+ const source = readFileSync9(file, "utf8");
59346
+ const methods = new Set;
59347
+ const pattern = /\b(?:export\s+)?(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b|\bexport\s+const\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/g;
59348
+ for (const match of source.matchAll(pattern)) {
59349
+ const method = match[1] ?? match[2];
59350
+ if (method)
59351
+ methods.add(method);
59352
+ }
59353
+ return [...methods].sort();
59354
+ }
59355
+ function classifyRoute(routePath, groups, file) {
59356
+ const haystack = `${routePath} ${groups.join(" ")} ${file}`.toLowerCase();
59357
+ if (haystack.includes("admin"))
59358
+ return "admin";
59359
+ if (haystack.includes("auth") || routePath.startsWith("/cli/device"))
59360
+ return "auth";
59361
+ if (haystack.includes("ai-runtime") || /\/(chat|sessions|memories|knowledge|learning|copilot|guardrails)\b/.test(routePath))
59362
+ return "ai-runtime";
59363
+ if (haystack.includes("commerce") || /\/(billing|shop|agent-wallet|domains|whois-profiles)\b/.test(routePath))
59364
+ return "commerce";
59365
+ if (haystack.includes("communications") || /\/(telephony|emails)\b/.test(routePath))
59366
+ return "communications";
59367
+ if (haystack.includes("crm") || routePath.includes("/contacts"))
59368
+ return "crm";
59369
+ if (haystack.includes("integrations") || routePath.includes("/connectors"))
59370
+ return "integrations";
59371
+ if (haystack.includes("dashboard") || routePath.includes(":orgSlug"))
59372
+ return "dashboard";
59373
+ if (haystack.includes("public") || haystack.includes("pages"))
59374
+ return "public";
59375
+ if (routePath.startsWith("/api/"))
59376
+ return "api";
59377
+ return "app";
59378
+ }
59379
+ function inferRequiresAuth(routePath, groups, kind) {
59380
+ const haystack = `${routePath} ${groups.join(" ")}`.toLowerCase();
59381
+ if (haystack.includes("auth") || haystack.includes("public") || haystack.includes("webhook"))
59382
+ return false;
59383
+ if (routePath.startsWith("/api/v1/auth/"))
59384
+ return false;
59385
+ if (routePath.startsWith("/api/"))
59386
+ return true;
59387
+ return kind === "page" && (haystack.includes("admin") || haystack.includes("dashboard") || routePath.includes(":orgSlug") || routePath.startsWith("/settings"));
59388
+ }
59389
+ function tagsForRoute(input) {
59390
+ const tags = new Set([
59391
+ "next-route",
59392
+ `route:${input.kind}`,
59393
+ `area:${input.category}`,
59394
+ input.category
59395
+ ]);
59396
+ for (const group of input.groups)
59397
+ tags.add(`group:${group}`);
59398
+ if (input.dynamic)
59399
+ tags.add("dynamic-route");
59400
+ if (input.requiresAuth)
59401
+ tags.add("auth-required");
59402
+ if (input.routePath.startsWith("/api/"))
59403
+ tags.add("api");
59404
+ return [...tags];
59405
+ }
59406
+ function priorityForRoute(routePath, category, kind) {
59407
+ if (category === "auth")
59408
+ return "critical";
59409
+ if (category === "commerce" || category === "ai-runtime")
59410
+ return "critical";
59411
+ if (category === "admin" || category === "dashboard")
59412
+ return "high";
59413
+ if (kind === "api")
59414
+ return "high";
59415
+ if (routePath === "/" || category === "public")
59416
+ return "medium";
59417
+ return "medium";
59418
+ }
59419
+ var ROUTE_FILE_NAMES, WALK_EXCLUDES, SAFE_PAGE_ASSERTIONS;
59420
+ var init_next_route_inventory = __esm(() => {
59421
+ init_scenarios();
59422
+ init_workflows();
59423
+ ROUTE_FILE_NAMES = new Set([
59424
+ "page.tsx",
59425
+ "page.ts",
59426
+ "page.jsx",
59427
+ "page.js",
59428
+ "page.mdx",
59429
+ "route.ts",
59430
+ "route.js"
59431
+ ]);
59432
+ WALK_EXCLUDES = new Set([
59433
+ ".git",
59434
+ ".next",
59435
+ ".turbo",
59436
+ "node_modules",
59437
+ "dist",
59438
+ "build",
59439
+ "coverage"
59440
+ ]);
59441
+ SAFE_PAGE_ASSERTIONS = [{ type: "no_console_errors" }];
59442
+ });
59443
+
59096
59444
  // src/lib/generator.ts
59097
59445
  var exports_generator = {};
59098
59446
  __export(exports_generator, {
@@ -59391,7 +59739,7 @@ async function recordSession(url, options) {
59391
59739
  await Promise.race([
59392
59740
  page.waitForEvent("close").catch(() => {}),
59393
59741
  context.waitForEvent("close").catch(() => {}),
59394
- new Promise((resolve3) => setTimeout(resolve3, timeout))
59742
+ new Promise((resolve4) => setTimeout(resolve4, timeout))
59395
59743
  ]);
59396
59744
  clearInterval(pollInterval);
59397
59745
  try {
@@ -59663,49 +60011,49 @@ __export(exports_agents, {
59663
60011
  getAgent: () => getAgent
59664
60012
  });
59665
60013
  function registerAgent(input) {
59666
- const db2 = getDatabase();
59667
- const existing = db2.query("SELECT * FROM agents WHERE name = ?").get(input.name);
60014
+ const db3 = getDatabase();
60015
+ const existing = db3.query("SELECT * FROM agents WHERE name = ?").get(input.name);
59668
60016
  if (existing) {
59669
- db2.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), existing.id);
60017
+ db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), existing.id);
59670
60018
  return getAgent(existing.id);
59671
60019
  }
59672
60020
  const id = uuid();
59673
60021
  const timestamp = now();
59674
- db2.query(`
60022
+ db3.query(`
59675
60023
  INSERT INTO agents (id, name, description, role, metadata, created_at, last_seen_at)
59676
60024
  VALUES (?, ?, ?, ?, '{}', ?, ?)
59677
60025
  `).run(id, input.name, input.description ?? null, input.role ?? null, timestamp, timestamp);
59678
60026
  return getAgent(id);
59679
60027
  }
59680
60028
  function getAgent(id) {
59681
- const db2 = getDatabase();
59682
- const row = db2.query("SELECT * FROM agents WHERE id = ?").get(id);
60029
+ const db3 = getDatabase();
60030
+ const row = db3.query("SELECT * FROM agents WHERE id = ?").get(id);
59683
60031
  return row ? agentFromRow(row) : null;
59684
60032
  }
59685
60033
  function getAgentByName(name) {
59686
- const db2 = getDatabase();
59687
- const row = db2.query("SELECT * FROM agents WHERE name = ?").get(name);
60034
+ const db3 = getDatabase();
60035
+ const row = db3.query("SELECT * FROM agents WHERE name = ?").get(name);
59688
60036
  return row ? agentFromRow(row) : null;
59689
60037
  }
59690
60038
  function listAgents() {
59691
- const db2 = getDatabase();
59692
- const rows = db2.query("SELECT * FROM agents ORDER BY created_at DESC").all();
60039
+ const db3 = getDatabase();
60040
+ const rows = db3.query("SELECT * FROM agents ORDER BY created_at DESC").all();
59693
60041
  return rows.map(agentFromRow);
59694
60042
  }
59695
60043
  function heartbeatAgent(id) {
59696
- const db2 = getDatabase();
59697
- const affected = db2.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), id);
60044
+ const db3 = getDatabase();
60045
+ const affected = db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), id);
59698
60046
  if (affected.changes === 0)
59699
60047
  return null;
59700
60048
  return getAgent(id);
59701
60049
  }
59702
60050
  function setAgentFocus(id, scenarioId) {
59703
- const db2 = getDatabase();
60051
+ const db3 = getDatabase();
59704
60052
  const agent = getAgent(id);
59705
60053
  if (!agent)
59706
60054
  return null;
59707
60055
  const metadata = { ...agent.metadata ?? {}, focus: scenarioId };
59708
- db2.query("UPDATE agents SET metadata = ?, last_seen_at = ? WHERE id = ?").run(JSON.stringify(metadata), now(), id);
60056
+ db3.query("UPDATE agents SET metadata = ?, last_seen_at = ? WHERE id = ?").run(JSON.stringify(metadata), now(), id);
59709
60057
  return getAgent(id);
59710
60058
  }
59711
60059
  var init_agents = __esm(() => {
@@ -76959,7 +77307,7 @@ function createProviderToolFactoryWithOutputSchema({
76959
77307
  supportsDeferredResults
76960
77308
  });
76961
77309
  }
76962
- async function resolve3(value) {
77310
+ async function resolve4(value) {
76963
77311
  if (typeof value === "function") {
76964
77312
  value = value();
76965
77313
  }
@@ -78646,7 +78994,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78646
78994
  try {
78647
78995
  const { value } = await getFromApi({
78648
78996
  url: `${this.config.baseURL}/config`,
78649
- headers: await resolve3(this.config.headers()),
78997
+ headers: await resolve4(this.config.headers()),
78650
78998
  successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
78651
78999
  failedResponseHandler: createJsonErrorResponseHandler({
78652
79000
  errorSchema: exports_external2.any(),
@@ -78664,7 +79012,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78664
79012
  const baseUrl = new URL(this.config.baseURL);
78665
79013
  const { value } = await getFromApi({
78666
79014
  url: `${baseUrl.origin}/v1/credits`,
78667
- headers: await resolve3(this.config.headers()),
79015
+ headers: await resolve4(this.config.headers()),
78668
79016
  successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
78669
79017
  failedResponseHandler: createJsonErrorResponseHandler({
78670
79018
  errorSchema: exports_external2.any(),
@@ -78710,7 +79058,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78710
79058
  }
78711
79059
  const { value } = await getFromApi({
78712
79060
  url: `${baseUrl.origin}/v1/report?${searchParams.toString()}`,
78713
- headers: await resolve3(this.config.headers()),
79061
+ headers: await resolve4(this.config.headers()),
78714
79062
  successfulResponseHandler: createJsonResponseHandler(gatewaySpendReportResponseSchema),
78715
79063
  failedResponseHandler: createJsonErrorResponseHandler({
78716
79064
  errorSchema: exports_external2.any(),
@@ -78732,7 +79080,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78732
79080
  const baseUrl = new URL(this.config.baseURL);
78733
79081
  const { value } = await getFromApi({
78734
79082
  url: `${baseUrl.origin}/v1/generation?id=${encodeURIComponent(params.id)}`,
78735
- headers: await resolve3(this.config.headers()),
79083
+ headers: await resolve4(this.config.headers()),
78736
79084
  successfulResponseHandler: createJsonResponseHandler(gatewayGenerationInfoResponseSchema),
78737
79085
  failedResponseHandler: createJsonErrorResponseHandler({
78738
79086
  errorSchema: exports_external2.any(),
@@ -78765,7 +79113,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78765
79113
  async doGenerate(options) {
78766
79114
  const { args, warnings } = await this.getArgs(options);
78767
79115
  const { abortSignal } = options;
78768
- const resolvedHeaders = await resolve3(this.config.headers());
79116
+ const resolvedHeaders = await resolve4(this.config.headers());
78769
79117
  try {
78770
79118
  const {
78771
79119
  responseHeaders,
@@ -78773,7 +79121,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78773
79121
  rawValue: rawResponse
78774
79122
  } = await postJsonToApi({
78775
79123
  url: this.getUrl(),
78776
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve3(this.config.o11yHeaders)),
79124
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve4(this.config.o11yHeaders)),
78777
79125
  body: args,
78778
79126
  successfulResponseHandler: createJsonResponseHandler(exports_external2.any()),
78779
79127
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -78796,11 +79144,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78796
79144
  async doStream(options) {
78797
79145
  const { args, warnings } = await this.getArgs(options);
78798
79146
  const { abortSignal } = options;
78799
- const resolvedHeaders = await resolve3(this.config.headers());
79147
+ const resolvedHeaders = await resolve4(this.config.headers());
78800
79148
  try {
78801
79149
  const { value: response, responseHeaders } = await postJsonToApi({
78802
79150
  url: this.getUrl(),
78803
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve3(this.config.o11yHeaders)),
79151
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve4(this.config.o11yHeaders)),
78804
79152
  body: args,
78805
79153
  successfulResponseHandler: createEventSourceResponseHandler(exports_external2.any()),
78806
79154
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -78885,7 +79233,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78885
79233
  providerOptions
78886
79234
  }) {
78887
79235
  var _a92;
78888
- const resolvedHeaders = await resolve3(this.config.headers());
79236
+ const resolvedHeaders = await resolve4(this.config.headers());
78889
79237
  try {
78890
79238
  const {
78891
79239
  responseHeaders,
@@ -78893,7 +79241,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78893
79241
  rawValue
78894
79242
  } = await postJsonToApi({
78895
79243
  url: this.getUrl(),
78896
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
79244
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
78897
79245
  body: {
78898
79246
  values,
78899
79247
  ...providerOptions ? { providerOptions } : {}
@@ -78949,7 +79297,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78949
79297
  abortSignal
78950
79298
  }) {
78951
79299
  var _a92, _b92, _c2, _d2;
78952
- const resolvedHeaders = await resolve3(this.config.headers());
79300
+ const resolvedHeaders = await resolve4(this.config.headers());
78953
79301
  try {
78954
79302
  const {
78955
79303
  responseHeaders,
@@ -78957,7 +79305,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78957
79305
  rawValue
78958
79306
  } = await postJsonToApi({
78959
79307
  url: this.getUrl(),
78960
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
79308
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
78961
79309
  body: {
78962
79310
  prompt,
78963
79311
  n: n2,
@@ -79032,11 +79380,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
79032
79380
  abortSignal
79033
79381
  }) {
79034
79382
  var _a92;
79035
- const resolvedHeaders = await resolve3(this.config.headers());
79383
+ const resolvedHeaders = await resolve4(this.config.headers());
79036
79384
  try {
79037
79385
  const { responseHeaders, value: responseBody } = await postJsonToApi({
79038
79386
  url: this.getUrl(),
79039
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders), { accept: "text/event-stream" }),
79387
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders), { accept: "text/event-stream" }),
79040
79388
  body: {
79041
79389
  prompt,
79042
79390
  n: n2,
@@ -79159,7 +79507,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
79159
79507
  abortSignal,
79160
79508
  providerOptions
79161
79509
  }) {
79162
- const resolvedHeaders = await resolve3(this.config.headers());
79510
+ const resolvedHeaders = await resolve4(this.config.headers());
79163
79511
  try {
79164
79512
  const {
79165
79513
  responseHeaders,
@@ -79167,7 +79515,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
79167
79515
  rawValue
79168
79516
  } = await postJsonToApi({
79169
79517
  url: this.getUrl(),
79170
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
79518
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
79171
79519
  body: {
79172
79520
  documents,
79173
79521
  query,
@@ -88585,7 +88933,7 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
88585
88933
  const schema = asSchema(inputSchema);
88586
88934
  return {
88587
88935
  name: "object",
88588
- responseFormat: resolve3(schema.jsonSchema).then((jsonSchema2) => ({
88936
+ responseFormat: resolve4(schema.jsonSchema).then((jsonSchema2) => ({
88589
88937
  type: "json",
88590
88938
  schema: jsonSchema2,
88591
88939
  ...name21 != null && { name: name21 },
@@ -88646,7 +88994,7 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
88646
88994
  const elementSchema = asSchema(inputElementSchema);
88647
88995
  return {
88648
88996
  name: "array",
88649
- responseFormat: resolve3(elementSchema.jsonSchema).then((jsonSchema2) => {
88997
+ responseFormat: resolve4(elementSchema.jsonSchema).then((jsonSchema2) => {
88650
88998
  const { $schema, ...itemSchema } = jsonSchema2;
88651
88999
  return {
88652
89000
  type: "json",
@@ -91543,9 +91891,9 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
91543
91891
  ...options
91544
91892
  }) {
91545
91893
  var _a21, _b16, _c2, _d2, _e2;
91546
- const resolvedBody = await resolve3(this.body);
91547
- const resolvedHeaders = await resolve3(this.headers);
91548
- const resolvedCredentials = await resolve3(this.credentials);
91894
+ const resolvedBody = await resolve4(this.body);
91895
+ const resolvedHeaders = await resolve4(this.headers);
91896
+ const resolvedCredentials = await resolve4(this.credentials);
91549
91897
  const baseHeaders = {
91550
91898
  ...normalizeHeaders(resolvedHeaders),
91551
91899
  ...normalizeHeaders(options.headers)
@@ -91593,9 +91941,9 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
91593
91941
  }
91594
91942
  async reconnectToStream(options) {
91595
91943
  var _a21, _b16, _c2, _d2, _e2;
91596
- const resolvedBody = await resolve3(this.body);
91597
- const resolvedHeaders = await resolve3(this.headers);
91598
- const resolvedCredentials = await resolve3(this.credentials);
91944
+ const resolvedBody = await resolve4(this.body);
91945
+ const resolvedHeaders = await resolve4(this.headers);
91946
+ const resolvedCredentials = await resolve4(this.credentials);
91599
91947
  const baseHeaders = {
91600
91948
  ...normalizeHeaders(resolvedHeaders),
91601
91949
  ...normalizeHeaders(options.headers)
@@ -93816,7 +94164,7 @@ __export(exports_session_converter, {
93816
94164
  convertSessionToScenario: () => convertSessionToScenario,
93817
94165
  convertSessionFile: () => convertSessionFile
93818
94166
  });
93819
- import { readFileSync as readFileSync9 } from "fs";
94167
+ import { readFileSync as readFileSync10 } from "fs";
93820
94168
  import { extname } from "path";
93821
94169
  function parseRrwebSession(events) {
93822
94170
  const result = [];
@@ -94001,7 +94349,7 @@ ${condensed}`;
94001
94349
  };
94002
94350
  }
94003
94351
  async function convertSessionFile(filePath, format, options) {
94004
- const raw = readFileSync9(filePath, "utf-8");
94352
+ const raw = readFileSync10(filePath, "utf-8");
94005
94353
  let parsed;
94006
94354
  try {
94007
94355
  parsed = JSON.parse(raw);
@@ -94035,7 +94383,7 @@ function detectSessionFormat(filePath) {
94035
94383
  if (ext === ".har")
94036
94384
  return "har";
94037
94385
  try {
94038
- const content = readFileSync9(filePath, "utf-8").trim();
94386
+ const content = readFileSync10(filePath, "utf-8").trim();
94039
94387
  const parsed = JSON.parse(content);
94040
94388
  if (Array.isArray(parsed) && parsed[0]?.type !== undefined && typeof parsed[0]?.timestamp === "number") {
94041
94389
  return "rrweb";
@@ -94089,28 +94437,28 @@ function checkResultFromRow(row) {
94089
94437
  };
94090
94438
  }
94091
94439
  function createGoldenAnswer(input) {
94092
- const db2 = getDatabase();
94440
+ const db3 = getDatabase();
94093
94441
  const id = uuid();
94094
94442
  const short_id = shortUuid();
94095
94443
  const timestamp = now();
94096
- db2.query(`
94444
+ db3.query(`
94097
94445
  INSERT INTO golden_answers (id, short_id, project_id, question, golden_answer, constraints, endpoint, judge_model, enabled, created_at, updated_at)
94098
94446
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
94099
94447
  `).run(id, short_id, input.projectId ?? null, input.question, input.goldenAnswer, JSON.stringify(input.constraints ?? []), input.endpoint, input.judgeModel ?? null, input.enabled === false ? 0 : 1, timestamp, timestamp);
94100
94448
  return getGoldenAnswer(id);
94101
94449
  }
94102
94450
  function getGoldenAnswer(id) {
94103
- const db2 = getDatabase();
94104
- let row = db2.query("SELECT * FROM golden_answers WHERE id = ?").get(id);
94451
+ const db3 = getDatabase();
94452
+ let row = db3.query("SELECT * FROM golden_answers WHERE id = ?").get(id);
94105
94453
  if (row)
94106
94454
  return goldenFromRow(row);
94107
- row = db2.query("SELECT * FROM golden_answers WHERE short_id = ?").get(id);
94455
+ row = db3.query("SELECT * FROM golden_answers WHERE short_id = ?").get(id);
94108
94456
  if (row)
94109
94457
  return goldenFromRow(row);
94110
94458
  return null;
94111
94459
  }
94112
94460
  function listGoldenAnswers(filter2) {
94113
- const db2 = getDatabase();
94461
+ const db3 = getDatabase();
94114
94462
  const conditions = [];
94115
94463
  const params = [];
94116
94464
  if (filter2?.projectId) {
@@ -94126,11 +94474,11 @@ function listGoldenAnswers(filter2) {
94126
94474
  sql += " WHERE " + conditions.join(" AND ");
94127
94475
  }
94128
94476
  sql += " ORDER BY created_at DESC";
94129
- const rows = db2.query(sql).all(...params);
94477
+ const rows = db3.query(sql).all(...params);
94130
94478
  return rows.map(goldenFromRow);
94131
94479
  }
94132
94480
  function updateGoldenAnswer(id, input) {
94133
- const db2 = getDatabase();
94481
+ const db3 = getDatabase();
94134
94482
  const timestamp = now();
94135
94483
  const sets = ["updated_at = ?"];
94136
94484
  const params = [timestamp];
@@ -94159,26 +94507,26 @@ function updateGoldenAnswer(id, input) {
94159
94507
  params.push(input.enabled ? 1 : 0);
94160
94508
  }
94161
94509
  params.push(id);
94162
- db2.query(`UPDATE golden_answers SET ${sets.join(", ")} WHERE id = ? OR short_id = ?`).run(...params, id);
94510
+ db3.query(`UPDATE golden_answers SET ${sets.join(", ")} WHERE id = ? OR short_id = ?`).run(...params, id);
94163
94511
  return getGoldenAnswer(id);
94164
94512
  }
94165
94513
  function deleteGoldenAnswer(id) {
94166
- const db2 = getDatabase();
94167
- const result = db2.query("DELETE FROM golden_answers WHERE id = ? OR short_id = ?").run(id, id);
94514
+ const db3 = getDatabase();
94515
+ const result = db3.query("DELETE FROM golden_answers WHERE id = ? OR short_id = ?").run(id, id);
94168
94516
  return (result.changes ?? 0) > 0;
94169
94517
  }
94170
94518
  function createGoldenCheckResult(input) {
94171
- const db2 = getDatabase();
94519
+ const db3 = getDatabase();
94172
94520
  const id = uuid();
94173
94521
  const timestamp = now();
94174
- db2.query(`
94522
+ db3.query(`
94175
94523
  INSERT INTO golden_check_results (id, golden_id, response, similarity_score, passed, drift_detected, judge_model, provider, created_at)
94176
94524
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
94177
94525
  `).run(id, input.goldenId, input.response, input.similarityScore ?? null, input.passed ? 1 : 0, input.driftDetected ? 1 : 0, input.judgeModel ?? null, input.provider ?? null, timestamp);
94178
94526
  return listGoldenCheckResults(input.goldenId, { limit: 1 })[0];
94179
94527
  }
94180
94528
  function listGoldenCheckResults(goldenId, options) {
94181
- const db2 = getDatabase();
94529
+ const db3 = getDatabase();
94182
94530
  const params = [goldenId];
94183
94531
  let sql = "SELECT * FROM golden_check_results WHERE golden_id = ?";
94184
94532
  if (options?.since) {
@@ -94190,7 +94538,7 @@ function listGoldenCheckResults(goldenId, options) {
94190
94538
  sql += " LIMIT ?";
94191
94539
  params.push(options.limit);
94192
94540
  }
94193
- const rows = db2.query(sql).all(...params);
94541
+ const rows = db3.query(sql).all(...params);
94194
94542
  return rows.map(checkResultFromRow);
94195
94543
  }
94196
94544
  function getLatestGoldenCheckResult(goldenId) {
@@ -94479,7 +94827,7 @@ import chalk6 from "chalk";
94479
94827
  // package.json
94480
94828
  var package_default = {
94481
94829
  name: "@hasna/testers",
94482
- version: "0.0.47",
94830
+ version: "0.0.49",
94483
94831
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
94484
94832
  type: "module",
94485
94833
  main: "dist/index.js",
@@ -94578,9 +94926,9 @@ init_todos_connector();
94578
94926
  init_browser();
94579
94927
  import { render, Box, Text, useInput, useApp } from "ink";
94580
94928
  import React, { useState } from "react";
94581
- import { readFileSync as readFileSync10, readdirSync as readdirSync6, writeFileSync as writeFileSync7 } from "fs";
94929
+ import { readFileSync as readFileSync11, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "fs";
94582
94930
  import { createInterface } from "readline";
94583
- import { join as join20, resolve as resolve4 } from "path";
94931
+ import { join as join21, resolve as resolve5 } from "path";
94584
94932
 
94585
94933
  // src/lib/init.ts
94586
94934
  init_paths();
@@ -96613,51 +96961,51 @@ function fromRow3(row) {
96613
96961
  };
96614
96962
  }
96615
96963
  function createEnvironment(input) {
96616
- const db2 = getDatabase();
96964
+ const db3 = getDatabase();
96617
96965
  const id = uuid();
96618
96966
  const timestamp = now();
96619
96967
  const meta = JSON.stringify({ variables: input.variables ?? {} });
96620
- db2.query(`
96968
+ db3.query(`
96621
96969
  INSERT INTO environments (id, name, url, auth_preset_name, project_id, is_default, metadata, created_at)
96622
96970
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
96623
96971
  `).run(id, input.name, input.url, input.authPresetName ?? null, input.projectId ?? null, input.isDefault ? 1 : 0, meta, timestamp);
96624
96972
  return getEnvironmentById(id);
96625
96973
  }
96626
96974
  function getEnvironmentById(id) {
96627
- const db2 = getDatabase();
96628
- const row = db2.query("SELECT * FROM environments WHERE id = ?").get(id);
96975
+ const db3 = getDatabase();
96976
+ const row = db3.query("SELECT * FROM environments WHERE id = ?").get(id);
96629
96977
  return row ? fromRow3(row) : null;
96630
96978
  }
96631
96979
  function getEnvironment(name) {
96632
- const db2 = getDatabase();
96633
- const row = db2.query("SELECT * FROM environments WHERE name = ?").get(name);
96980
+ const db3 = getDatabase();
96981
+ const row = db3.query("SELECT * FROM environments WHERE name = ?").get(name);
96634
96982
  return row ? fromRow3(row) : null;
96635
96983
  }
96636
96984
  function listEnvironments(projectId) {
96637
- const db2 = getDatabase();
96985
+ const db3 = getDatabase();
96638
96986
  if (projectId) {
96639
- const rows2 = db2.query("SELECT * FROM environments WHERE project_id = ? ORDER BY is_default DESC, created_at DESC").all(projectId);
96987
+ const rows2 = db3.query("SELECT * FROM environments WHERE project_id = ? ORDER BY is_default DESC, created_at DESC").all(projectId);
96640
96988
  return rows2.map(fromRow3);
96641
96989
  }
96642
- const rows = db2.query("SELECT * FROM environments ORDER BY is_default DESC, created_at DESC").all();
96990
+ const rows = db3.query("SELECT * FROM environments ORDER BY is_default DESC, created_at DESC").all();
96643
96991
  return rows.map(fromRow3);
96644
96992
  }
96645
96993
  function deleteEnvironment(name) {
96646
- const db2 = getDatabase();
96647
- const result = db2.query("DELETE FROM environments WHERE name = ?").run(name);
96994
+ const db3 = getDatabase();
96995
+ const result = db3.query("DELETE FROM environments WHERE name = ?").run(name);
96648
96996
  return result.changes > 0;
96649
96997
  }
96650
96998
  function setDefaultEnvironment(name) {
96651
- const db2 = getDatabase();
96652
- db2.exec("UPDATE environments SET is_default = 0");
96653
- const result = db2.query("UPDATE environments SET is_default = 1 WHERE name = ?").run(name);
96999
+ const db3 = getDatabase();
97000
+ db3.exec("UPDATE environments SET is_default = 0");
97001
+ const result = db3.query("UPDATE environments SET is_default = 1 WHERE name = ?").run(name);
96654
97002
  if (result.changes === 0) {
96655
97003
  throw new Error(`Environment not found: ${name}`);
96656
97004
  }
96657
97005
  }
96658
97006
  function getDefaultEnvironment() {
96659
- const db2 = getDatabase();
96660
- const row = db2.query("SELECT * FROM environments WHERE is_default = 1 LIMIT 1").get();
97007
+ const db3 = getDatabase();
97008
+ const row = db3.query("SELECT * FROM environments WHERE is_default = 1 LIMIT 1").get();
96661
97009
  return row ? fromRow3(row) : null;
96662
97010
  }
96663
97011
 
@@ -96666,7 +97014,7 @@ init_ci();
96666
97014
  init_assertions();
96667
97015
  init_paths();
96668
97016
  init_sessions();
96669
- import { existsSync as existsSync18, mkdirSync as mkdirSync15 } from "fs";
97017
+ import { existsSync as existsSync19, mkdirSync as mkdirSync15 } from "fs";
96670
97018
 
96671
97019
  // src/lib/repo-discovery.ts
96672
97020
  init_paths();
@@ -97224,12 +97572,12 @@ async function runRepoTests(opts) {
97224
97572
  specResults.push(result);
97225
97573
  const resultId = uuid();
97226
97574
  const timestamp = now();
97227
- const db2 = getDatabase();
97228
- db2.exec("PRAGMA foreign_keys = OFF");
97575
+ const db3 = getDatabase();
97576
+ db3.exec("PRAGMA foreign_keys = OFF");
97229
97577
  try {
97230
97578
  const reasoning = result.status === "passed" ? "All tests passed" : (result.error ?? "").slice(0, 500) || null;
97231
97579
  const errorStr = result.status !== "passed" ? result.error ?? null : null;
97232
- db2.query(`
97580
+ db3.query(`
97233
97581
  INSERT INTO results (id, run_id, scenario_id, status, reasoning, error, steps_completed, steps_total, duration_ms, model, tokens_used, cost_cents, metadata, created_at, persona_id, persona_name)
97234
97582
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, NULL, NULL)
97235
97583
  `).run(resultId, run.id, "__repo__", result.status, reasoning, errorStr, result.testResults.filter((t) => t.status === "passed").length, result.testResults.length || 1, result.durationMs, "repo-native", JSON.stringify({
@@ -97238,7 +97586,7 @@ async function runRepoTests(opts) {
97238
97586
  testResults: result.testResults
97239
97587
  }), timestamp);
97240
97588
  } finally {
97241
- db2.exec("PRAGMA foreign_keys = ON");
97589
+ db3.exec("PRAGMA foreign_keys = ON");
97242
97590
  }
97243
97591
  const resultRecord = { id: resultId };
97244
97592
  if (result.stdout || result.stderr) {
@@ -97385,21 +97733,31 @@ function envCredentialRef(value) {
97385
97733
  return;
97386
97734
  return trimmed.startsWith("$") ? trimmed : `$${trimmed}`;
97387
97735
  }
97388
- function parseSandboxEnv(values) {
97389
- if (!values?.length)
97736
+ function assertEnvVarName(key, rawValue) {
97737
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
97738
+ throw new Error(`Invalid sandbox env var name: ${key || rawValue}`);
97739
+ }
97740
+ }
97741
+ function parseSandboxEnv(values, optionalValues = []) {
97742
+ if (!values?.length && !optionalValues?.length)
97390
97743
  return;
97391
97744
  const env = {};
97392
- for (const value of values) {
97745
+ for (const value of values ?? []) {
97393
97746
  const trimmed = value.trim();
97394
97747
  if (!trimmed)
97395
97748
  continue;
97396
97749
  const separator = trimmed.indexOf("=");
97397
97750
  const key = separator >= 0 ? trimmed.slice(0, separator).trim() : trimmed;
97398
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
97399
- throw new Error(`Invalid sandbox env var name: ${key || value}`);
97400
- }
97751
+ assertEnvVarName(key, value);
97401
97752
  env[key] = separator >= 0 ? trimmed.slice(separator + 1) : `$${key}`;
97402
97753
  }
97754
+ for (const value of optionalValues ?? []) {
97755
+ const key = value.trim();
97756
+ if (!key)
97757
+ continue;
97758
+ assertEnvVarName(key, value);
97759
+ env[key] = `$?${key}`;
97760
+ }
97403
97761
  return Object.keys(env).length > 0 ? env : undefined;
97404
97762
  }
97405
97763
  function AddForm({ onComplete }) {
@@ -97675,18 +98033,18 @@ program2.command("prod-debug <target>").description("Create a safe production de
97675
98033
  }, config2.prodDebug);
97676
98034
  const output = opts.json ? JSON.stringify(plan, null, 2) : formatProdDebugPlan(plan);
97677
98035
  if (opts.output) {
97678
- writeFileSync7(resolve4(opts.output), output + `
98036
+ writeFileSync7(resolve5(opts.output), output + `
97679
98037
  `);
97680
98038
  } else {
97681
98039
  log(output);
97682
98040
  }
97683
98041
  });
97684
98042
  var CONFIG_DIR5 = getTestersDir();
97685
- var CONFIG_PATH4 = join20(CONFIG_DIR5, "config.json");
98043
+ var CONFIG_PATH4 = join21(CONFIG_DIR5, "config.json");
97686
98044
  function getActiveProject() {
97687
98045
  try {
97688
- if (existsSync18(CONFIG_PATH4)) {
97689
- const raw = JSON.parse(readFileSync10(CONFIG_PATH4, "utf-8"));
98046
+ if (existsSync19(CONFIG_PATH4)) {
98047
+ const raw = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
97690
98048
  return raw.activeProject ?? undefined;
97691
98049
  }
97692
98050
  } catch {}
@@ -97893,7 +98251,7 @@ program2.command("delete <id>").description("Delete a scenario").option("-y, --y
97893
98251
  }
97894
98252
  if (!opts.yes) {
97895
98253
  process.stdout.write(chalk6.yellow(`Delete scenario ${scenario.shortId} "${scenario.name}"? [y/N] `));
97896
- const answer = await new Promise((resolve5) => {
98254
+ const answer = await new Promise((resolve6) => {
97897
98255
  let buf = "";
97898
98256
  process.stdin.setRawMode?.(true);
97899
98257
  process.stdin.resume();
@@ -97903,7 +98261,7 @@ program2.command("delete <id>").description("Delete a scenario").option("-y, --y
97903
98261
  process.stdin.pause();
97904
98262
  process.stdout.write(`
97905
98263
  `);
97906
- resolve5(buf);
98264
+ resolve6(buf);
97907
98265
  });
97908
98266
  });
97909
98267
  if (answer !== "y" && answer !== "yes") {
@@ -97932,7 +98290,7 @@ program2.command("remove <id>").alias("uninstall").description("Remove a scenari
97932
98290
  }
97933
98291
  if (!opts.yes) {
97934
98292
  process.stdout.write(chalk6.yellow(`Remove scenario ${scenario.shortId} "${scenario.name}"? [y/N] `));
97935
- const answer = await new Promise((resolve5) => {
98293
+ const answer = await new Promise((resolve6) => {
97936
98294
  let buf = "";
97937
98295
  process.stdin.setRawMode?.(true);
97938
98296
  process.stdin.resume();
@@ -97942,7 +98300,7 @@ program2.command("remove <id>").alias("uninstall").description("Remove a scenari
97942
98300
  process.stdin.pause();
97943
98301
  process.stdout.write(`
97944
98302
  `);
97945
- resolve5(buf);
98303
+ resolve6(buf);
97946
98304
  });
97947
98305
  });
97948
98306
  if (answer !== "y" && answer !== "yes") {
@@ -98145,7 +98503,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
98145
98503
  `);
98146
98504
  }
98147
98505
  };
98148
- await new Promise((resolve5) => {
98506
+ await new Promise((resolve6) => {
98149
98507
  const poll = setInterval(() => {
98150
98508
  const run3 = getRun(runId);
98151
98509
  if (!run3)
@@ -98153,7 +98511,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
98153
98511
  renderTable();
98154
98512
  if (DONE_STATUSES.has(run3.status)) {
98155
98513
  clearInterval(poll);
98156
- resolve5();
98514
+ resolve6();
98157
98515
  }
98158
98516
  }, POLL_INTERVAL);
98159
98517
  });
@@ -98548,15 +98906,15 @@ program2.command("screenshots <id>").description("List screenshots for a run or
98548
98906
  });
98549
98907
  program2.command("import <dir>").description("Import markdown test files as scenarios").action((dir) => {
98550
98908
  try {
98551
- const absDir = resolve4(dir);
98552
- const files = readdirSync6(absDir).filter((f2) => f2.endsWith(".md"));
98909
+ const absDir = resolve5(dir);
98910
+ const files = readdirSync7(absDir).filter((f2) => f2.endsWith(".md"));
98553
98911
  if (files.length === 0) {
98554
98912
  log(chalk6.dim("No .md files found in directory."));
98555
98913
  return;
98556
98914
  }
98557
98915
  let imported = 0;
98558
98916
  for (const file2 of files) {
98559
- const content = readFileSync10(join20(absDir, file2), "utf-8");
98917
+ const content = readFileSync11(join21(absDir, file2), "utf-8");
98560
98918
  const lines = content.split(`
98561
98919
  `);
98562
98920
  let name21 = file2.replace(/\.md$/, "");
@@ -98612,11 +98970,11 @@ program2.command("export [format]").description("Export scenarios as JSON (defau
98612
98970
  const outputPath = opts.output ?? "testers-export.json";
98613
98971
  const data = JSON.stringify(scenarios, null, 2);
98614
98972
  writeFileSync7(outputPath, data, "utf-8");
98615
- log(chalk6.green(`Exported ${scenarios.length} scenario(s) to ${resolve4(outputPath)}`));
98973
+ log(chalk6.green(`Exported ${scenarios.length} scenario(s) to ${resolve5(outputPath)}`));
98616
98974
  return;
98617
98975
  }
98618
98976
  const outputDir = opts.output ?? ".";
98619
- if (!existsSync18(outputDir)) {
98977
+ if (!existsSync19(outputDir)) {
98620
98978
  mkdirSync15(outputDir, { recursive: true });
98621
98979
  }
98622
98980
  for (const s2 of scenarios) {
@@ -98644,13 +99002,13 @@ program2.command("export [format]").description("Export scenarios as JSON (defau
98644
99002
  lines.push("");
98645
99003
  }
98646
99004
  const safeFilename = s2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
98647
- const filePath = join20(outputDir, `${s2.shortId}-${safeFilename}.md`);
99005
+ const filePath = join21(outputDir, `${s2.shortId}-${safeFilename}.md`);
98648
99006
  writeFileSync7(filePath, lines.join(`
98649
99007
  `), "utf-8");
98650
99008
  log(chalk6.dim(` ${s2.shortId}: ${s2.name} \u2192 ${filePath}`));
98651
99009
  }
98652
99010
  log(chalk6.green(`
98653
- Exported ${scenarios.length} scenario(s) as markdown to ${resolve4(outputDir)}`));
99011
+ Exported ${scenarios.length} scenario(s) as markdown to ${resolve5(outputDir)}`));
98654
99012
  } catch (error40) {
98655
99013
  logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
98656
99014
  process.exit(1);
@@ -98669,7 +99027,7 @@ program2.command("status").description("Show database and auth status").action((
98669
99027
  try {
98670
99028
  const config2 = loadConfig();
98671
99029
  const hasApiKey = !!config2.anthropicApiKey || !!process.env["ANTHROPIC_API_KEY"];
98672
- const dbPath = join20(getTestersDir(), "testers.db");
99030
+ const dbPath = join21(getTestersDir(), "testers.db");
98673
99031
  log("");
98674
99032
  log(chalk6.bold(" Open Testers Status"));
98675
99033
  log("");
@@ -98819,13 +99177,13 @@ projectCmd.command("export-open <id>").description("Register a testers project i
98819
99177
  projectCmd.command("use <name>").description("Set active project (find or create)").option("--json", "Output as JSON", false).action((name21, opts) => {
98820
99178
  try {
98821
99179
  const project = ensureProject(name21, process.cwd());
98822
- if (!existsSync18(CONFIG_DIR5)) {
99180
+ if (!existsSync19(CONFIG_DIR5)) {
98823
99181
  mkdirSync15(CONFIG_DIR5, { recursive: true });
98824
99182
  }
98825
99183
  let config2 = {};
98826
- if (existsSync18(CONFIG_PATH4)) {
99184
+ if (existsSync19(CONFIG_PATH4)) {
98827
99185
  try {
98828
- config2 = JSON.parse(readFileSync10(CONFIG_PATH4, "utf-8"));
99186
+ config2 = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
98829
99187
  } catch {}
98830
99188
  }
98831
99189
  config2.activeProject = project.id;
@@ -98843,7 +99201,7 @@ projectCmd.command("use <name>").description("Set active project (find or create
98843
99201
  var repoCmd = program2.command("repo").description("Discover and run repo-native Playwright tests");
98844
99202
  repoCmd.command("discover [path]").alias("scan").description("Discover Playwright tests in a repo").option("--refresh", "Force a fresh scan, ignoring cache", false).option("--json", "Output as JSON", false).option("--base-url <url>", "Override the suggested base URL").action((path, opts) => {
98845
99203
  try {
98846
- const repoPath = resolve4(path ?? process.cwd());
99204
+ const repoPath = resolve5(path ?? process.cwd());
98847
99205
  const snapshot = discoverRepo({
98848
99206
  repoPath,
98849
99207
  refresh: opts.refresh,
@@ -98909,7 +99267,7 @@ repoCmd.command("discover [path]").alias("scan").description("Discover Playwrigh
98909
99267
  });
98910
99268
  repoCmd.command("prepare [path]").alias("prep").description("Install dependencies and browsers for repo tests").option("--all", "Run all prep steps (install, browsers, build, seed)", false).option("--install", "Install dependencies", false).option("--browsers", "Install Playwright browsers", false).option("--build", "Build the app", false).option("--seed", "Seed the database", false).option("--refresh", "Force fresh discovery scan", false).option("--json", "Output as JSON", false).action((path, opts) => {
98911
99269
  try {
98912
- const repoPath = resolve4(path ?? process.cwd());
99270
+ const repoPath = resolve5(path ?? process.cwd());
98913
99271
  const snapshot = discoverRepo({ repoPath, refresh: opts.refresh });
98914
99272
  const steps = [];
98915
99273
  if (opts.all) {
@@ -98987,7 +99345,7 @@ repoCmd.command("run [path]").description("Run discovered Playwright tests nativ
98987
99345
  return acc;
98988
99346
  }, []).option("--timeout <ms>", "Timeout per spec file", "300000").option("--url <url>", "Dev server URL").option("--project <id>", "Project ID for result storage").option("--label <text>", "Run label").option("--json", "Output as JSON", false).action(async (path, opts) => {
98989
99347
  try {
98990
- const repoPath = resolve4(path ?? process.cwd());
99348
+ const repoPath = resolve5(path ?? process.cwd());
98991
99349
  const snapshot = discoverRepo({
98992
99350
  repoPath,
98993
99351
  refresh: opts.refresh,
@@ -99053,7 +99411,7 @@ repoCmd.command("run [path]").description("Run discovered Playwright tests nativ
99053
99411
  repoCmd.command("cache [path]").description("Manage discovery cache").option("--clear", "Clear discovery cache", false).option("--status", "Show cache status", false).action((path, opts) => {
99054
99412
  try {
99055
99413
  if (opts.clear) {
99056
- const repoPath2 = path ? resolve4(path) : undefined;
99414
+ const repoPath2 = path ? resolve5(path) : undefined;
99057
99415
  clearDiscoveryCache(repoPath2);
99058
99416
  if (repoPath2) {
99059
99417
  log(chalk6.green("Discovery cache cleared for this repo."));
@@ -99063,7 +99421,7 @@ repoCmd.command("cache [path]").description("Manage discovery cache").option("--
99063
99421
  return;
99064
99422
  }
99065
99423
  if (opts.status) {
99066
- const repoPath2 = resolve4(path ?? process.cwd());
99424
+ const repoPath2 = resolve5(path ?? process.cwd());
99067
99425
  const info2 = getDiscoveryCacheInfo(repoPath2);
99068
99426
  if (!info2) {
99069
99427
  log(chalk6.dim("No discovery cache for this repo."));
@@ -99077,7 +99435,7 @@ repoCmd.command("cache [path]").description("Manage discovery cache").option("--
99077
99435
  log("");
99078
99436
  return;
99079
99437
  }
99080
- const repoPath = resolve4(path ?? process.cwd());
99438
+ const repoPath = resolve5(path ?? process.cwd());
99081
99439
  const info = getDiscoveryCacheInfo(repoPath);
99082
99440
  if (!info) {
99083
99441
  log(chalk6.dim("No discovery cache. Run 'testers repo discover' to create one."));
@@ -99166,8 +99524,8 @@ sessionCmd.command("show <id>").description("Show details of a recorded session"
99166
99524
  });
99167
99525
  sessionCmd.command("import <file>").description("Import a session JSON file exported from the Chrome extension").action(async (file2) => {
99168
99526
  try {
99169
- const { readFileSync: readFileSync11 } = await import("fs");
99170
- const raw = readFileSync11(file2, "utf-8");
99527
+ const { readFileSync: readFileSync12 } = await import("fs");
99528
+ const raw = readFileSync12(file2, "utf-8");
99171
99529
  const data = JSON.parse(raw);
99172
99530
  const items = Array.isArray(data) ? data : [data];
99173
99531
  const { createSession: createSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
@@ -99407,7 +99765,7 @@ program2.command("daemon").description("Start the scheduler daemon").option("--i
99407
99765
  } catch (err) {
99408
99766
  logError(chalk6.red(`Daemon error: ${err instanceof Error ? err.message : String(err)}`));
99409
99767
  }
99410
- await new Promise((resolve5) => setTimeout(resolve5, intervalMs));
99768
+ await new Promise((resolve6) => setTimeout(resolve6, intervalMs));
99411
99769
  }
99412
99770
  };
99413
99771
  process.on("SIGINT", () => {
@@ -99436,9 +99794,9 @@ program2.command("ci [provider]").description("Print or write a CI workflow (def
99436
99794
  }
99437
99795
  const workflow = generateGitHubActionsWorkflow();
99438
99796
  if (opts.output) {
99439
- const outPath = resolve4(opts.output);
99797
+ const outPath = resolve5(opts.output);
99440
99798
  const outDir = outPath.replace(/\/[^/]*$/, "");
99441
- if (outDir && !existsSync18(outDir)) {
99799
+ if (outDir && !existsSync19(outDir)) {
99442
99800
  mkdirSync15(outDir, { recursive: true });
99443
99801
  }
99444
99802
  writeFileSync7(outPath, workflow, "utf-8");
@@ -99472,11 +99830,11 @@ program2.command("init").description("Initialize a new testing project").option(
99472
99830
  log(` ${chalk6.dim(s2.shortId)} ${s2.name} ${chalk6.dim(`[${s2.tags.join(", ")}]`)}`);
99473
99831
  }
99474
99832
  if (opts.ci === "github") {
99475
- const workflowDir = join20(process.cwd(), ".github", "workflows");
99476
- if (!existsSync18(workflowDir)) {
99833
+ const workflowDir = join21(process.cwd(), ".github", "workflows");
99834
+ if (!existsSync19(workflowDir)) {
99477
99835
  mkdirSync15(workflowDir, { recursive: true });
99478
99836
  }
99479
- const workflowPath = join20(workflowDir, "testers.yml");
99837
+ const workflowPath = join21(workflowDir, "testers.yml");
99480
99838
  writeFileSync7(workflowPath, generateGitHubActionsWorkflow(), "utf-8");
99481
99839
  log(` CI: ${chalk6.green("GitHub Actions workflow written to .github/workflows/testers.yml")}`);
99482
99840
  } else if (opts.ci) {
@@ -99486,7 +99844,7 @@ program2.command("init").description("Initialize a new testing project").option(
99486
99844
  if (opts.yes)
99487
99845
  return;
99488
99846
  const rl2 = createInterface({ input: process.stdin, output: process.stdout });
99489
- const ask = (q2) => new Promise((resolve5) => rl2.question(q2, resolve5));
99847
+ const ask = (q2) => new Promise((resolve6) => rl2.question(q2, resolve6));
99490
99848
  try {
99491
99849
  const envAnswer = await ask(" Would you like to configure environments? [y/N] ");
99492
99850
  if (envAnswer.trim().toLowerCase() === "y") {
@@ -99670,14 +100028,14 @@ program2.command("quick-qa <url>").alias("quick-check").description("Run a fast
99670
100028
  wcagLevel
99671
100029
  });
99672
100030
  if (opts.output) {
99673
- writeFileSync7(resolve4(opts.output), JSON.stringify(result, null, 2));
100031
+ writeFileSync7(resolve5(opts.output), JSON.stringify(result, null, 2));
99674
100032
  }
99675
100033
  if (opts.json) {
99676
100034
  log(JSON.stringify(result, null, 2));
99677
100035
  } else {
99678
100036
  log(formatQuickQaReport(result));
99679
100037
  if (opts.output)
99680
- log(chalk6.dim(`Wrote JSON results to ${resolve4(opts.output)}`));
100038
+ log(chalk6.dim(`Wrote JSON results to ${resolve5(opts.output)}`));
99681
100039
  }
99682
100040
  process.exit(getQuickQaExitCode(result));
99683
100041
  } catch (error40) {
@@ -99722,7 +100080,7 @@ program2.command("report [run-id]").description("Generate HTML test report or co
99722
100080
  });
99723
100081
  if (opts.output && opts.output !== "report.html") {
99724
100082
  writeFileSync7(opts.output, content, "utf-8");
99725
- const absPath2 = resolve4(opts.output);
100083
+ const absPath2 = resolve5(opts.output);
99726
100084
  log(chalk6.green(`Compliance report written to ${absPath2}`));
99727
100085
  } else {
99728
100086
  log(content);
@@ -99736,7 +100094,7 @@ program2.command("report [run-id]").description("Generate HTML test report or co
99736
100094
  html = generateHtmlReport(runId);
99737
100095
  }
99738
100096
  writeFileSync7(opts.output, html, "utf-8");
99739
- const absPath = resolve4(opts.output);
100097
+ const absPath = resolve5(opts.output);
99740
100098
  log(chalk6.green(`Report generated: ${absPath}`));
99741
100099
  if (opts.open) {
99742
100100
  const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
@@ -100093,6 +100451,62 @@ Imported ${imported} scenarios from API spec:`));
100093
100451
  process.exit(1);
100094
100452
  }
100095
100453
  });
100454
+ var inventoryCmd = program2.command("inventory").description("Discover source-derived app route/action inventories");
100455
+ inventoryCmd.command("next [root]").description("Discover Next.js app routes and optionally import route coverage scenarios").option("--app-dir <path>", "Next.js app directory relative to root (default: packages/web/app or app)").option("--project <id>", "Project ID").option("--no-pages", "Do not include page.tsx/page.ts routes").option("--no-api", "Do not include route.ts/route.js API routes").option("--limit <n>", "Limit discovered routes").option("--create-scenarios", "Upsert source-derived route coverage scenarios", false).option("--create-workflows", "Upsert grouped workflows by area and route kind", false).option("--workflow-target <target>", "Workflow execution target: local or sandbox", "sandbox").option("--sandbox-provider <provider>", "Sandbox provider for created workflows", "e2b").option("--sandbox-cleanup <mode>", "Sandbox cleanup mode: delete, stop, or keep", "delete").option("--sandbox-sync <strategy>", "Sandbox upload sync strategy: rsync or archive", "rsync").option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
100456
+ acc.push(val);
100457
+ return acc;
100458
+ }, []).option("--timeout <ms>", "Workflow timeout in milliseconds").option("--json", "Output as JSON", false).action(async (root, opts) => {
100459
+ try {
100460
+ const { importNextRouteInventory: importNextRouteInventory2 } = await Promise.resolve().then(() => (init_next_route_inventory(), exports_next_route_inventory));
100461
+ const projectId = resolveProject2(opts.project) ?? undefined;
100462
+ const env = parseSandboxEnv(undefined, opts.sandboxEnvOptional);
100463
+ const result = importNextRouteInventory2({
100464
+ rootDir: root ?? process.cwd(),
100465
+ appDir: opts.appDir,
100466
+ projectId,
100467
+ includePages: opts.pages !== false,
100468
+ includeApi: opts.api !== false,
100469
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
100470
+ createScenarios: opts.createScenarios,
100471
+ createWorkflows: opts.createWorkflows,
100472
+ workflowTarget: opts.workflowTarget,
100473
+ workflowProvider: opts.workflowTarget === "sandbox" ? opts.sandboxProvider : undefined,
100474
+ workflowExecution: {
100475
+ target: opts.workflowTarget,
100476
+ provider: opts.workflowTarget === "sandbox" ? opts.sandboxProvider : undefined,
100477
+ sandboxCleanup: opts.sandboxCleanup,
100478
+ sandboxSyncStrategy: opts.sandboxSync,
100479
+ timeoutMs: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
100480
+ env
100481
+ }
100482
+ });
100483
+ if (opts.json) {
100484
+ log(JSON.stringify(result, null, 2));
100485
+ return;
100486
+ }
100487
+ log("");
100488
+ log(chalk6.bold(" Next.js Route Inventory"));
100489
+ log(chalk6.dim(` Root: ${result.inventory.rootDir}`));
100490
+ log(chalk6.dim(` App: ${result.inventory.appDir}`));
100491
+ log("");
100492
+ log(` Routes: ${chalk6.cyan(String(result.inventory.total))} (${result.inventory.pages} pages, ${result.inventory.apiRoutes} API, ${result.inventory.dynamic} dynamic)`);
100493
+ log(` Scenarios: ${chalk6.green(String(result.created))} created, ${chalk6.yellow(String(result.updated))} updated, ${chalk6.dim(String(result.deduped))} deduped`);
100494
+ log(` Workflows: ${result.workflows.length}`);
100495
+ log("");
100496
+ for (const [category, count] of Object.entries(result.inventory.categories).sort()) {
100497
+ log(` ${category.padEnd(18)} ${count}`);
100498
+ }
100499
+ log("");
100500
+ if (!opts.createScenarios)
100501
+ log(chalk6.dim(" Add --create-scenarios to upsert route scenarios."));
100502
+ if (!opts.createWorkflows)
100503
+ log(chalk6.dim(" Add --create-workflows to upsert grouped workflows."));
100504
+ log("");
100505
+ } catch (error40) {
100506
+ logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
100507
+ process.exit(1);
100508
+ }
100509
+ });
100096
100510
  program2.command("generate <url>").description("Crawl app and synthesize test scenarios using AI (any provider)").option("--max <n>", "Max scenarios to generate", "10").option("--max-pages <n>", "Max pages to crawl", "10").option("--focus <topic>", "Focus on specific area e.g. 'auth flows', 'checkout'").option("--persona <desc>", "Persona perspective e.g. 'first-time user'").option("--model <model>", "AI model (claude-haiku, gpt-4o-mini, gemini-2.0-flash, etc.)").option("--save", "Persist generated scenarios to DB", false).option("--project <id>", "Project ID").option("--headed", "Run browser in headed mode", false).option("--json", "Output as JSON", false).action(async (url2, opts) => {
100097
100511
  try {
100098
100512
  const { generateScenarios: generateScenarios2 } = await Promise.resolve().then(() => (init_generator(), exports_generator));
@@ -100555,11 +100969,11 @@ program2.command("doctor").description("Check system setup and configuration").a
100555
100969
  log(chalk6.red("\u2717") + " ANTHROPIC_API_KEY is not set (required for AI-powered tests)");
100556
100970
  allPassed = false;
100557
100971
  }
100558
- const dbPath = join20(getTestersDir(), "testers.db");
100972
+ const dbPath = join21(getTestersDir(), "testers.db");
100559
100973
  try {
100560
100974
  const { Database: Database5 } = await import("bun:sqlite");
100561
- const db2 = new Database5(dbPath, { create: true });
100562
- db2.close();
100975
+ const db3 = new Database5(dbPath, { create: true });
100976
+ db3.close();
100563
100977
  log(chalk6.green("\u2713") + ` Database accessible: ${dbPath}`);
100564
100978
  } catch (err) {
100565
100979
  log(chalk6.red("\u2717") + ` Database not accessible at ${dbPath}: ${err instanceof Error ? err.message : String(err)}`);
@@ -100609,7 +101023,7 @@ program2.command("serve").description("Start the Open Testers web dashboard").op
100609
101023
  try {
100610
101024
  const port = parseInt(opts.port, 10);
100611
101025
  const url2 = `http://localhost:${port}`;
100612
- const serverBin = join20(resolve4(process.execPath, ".."), "..", "dist", "server", "index.js");
101026
+ const serverBin = join21(resolve5(process.execPath, ".."), "..", "dist", "server", "index.js");
100613
101027
  const { join: pathJoin, resolve: pathResolve, dirname: dirname7 } = await import("path");
100614
101028
  const { fileURLToPath: fileURLToPath2 } = await import("url");
100615
101029
  const serverPath = pathJoin(dirname7(fileURLToPath2(import.meta.url)), "..", "server", "index.js");
@@ -101028,6 +101442,9 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
101028
101442
  }, []).option("--max-iterations <n>", "Goal-loop iteration cap", "10").option("--target <target>", "Execution target: local or sandbox", "local").option("--sandbox-provider <provider>", "Sandbox provider: e2b, daytona, or modal").option("--sandbox-image <image>", "Sandbox image/template").option("--sandbox-remote-dir <path>", "Remote working directory for sandbox runs").option("--sandbox-cleanup <mode>", "Sandbox cleanup mode: delete, stop, or keep", "delete").option("--sandbox-sync <strategy>", "Sandbox upload sync strategy: rsync or archive", "rsync").option("--sandbox-setup-command <command>", "Shell command to run before testers in the sandbox").option("--sandbox-package <spec>", "Package spec to execute in the sandbox", "@hasna/testers").option("--sandbox-env <assignment>", "Sandbox env var; KEY forwards host KEY, KEY=value stores value (repeatable)", (val, acc) => {
101029
101443
  acc.push(val);
101030
101444
  return acc;
101445
+ }, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
101446
+ acc.push(val);
101447
+ return acc;
101031
101448
  }, []).option("--sandbox-app-source <path>", "Local app source directory to upload into the sandbox").option("--sandbox-app-remote-dir <path>", "Remote app directory inside the sandbox (default: <sandbox-remote-dir>/app)").option("--sandbox-app-start-command <command>", "Shell command to start the app before testers runs").option("--sandbox-app-url <url>", "URL testers should target inside the sandbox after the app starts").option("--sandbox-app-wait-url <url>", "URL to poll before starting testers (defaults to --sandbox-app-url)").option("--sandbox-app-wait-timeout <ms>", "App readiness wait timeout in milliseconds").option("--e2b-template <name>", "Legacy alias for --sandbox-image").option("--timeout <ms>", "Workflow timeout").option("--json", "Output as JSON", false).action((name21, opts) => {
101032
101449
  try {
101033
101450
  const workflow = createTestingWorkflow({
@@ -101054,7 +101471,7 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
101054
101471
  sandboxSyncStrategy: opts.sandboxSync,
101055
101472
  setupCommand: opts.sandboxSetupCommand,
101056
101473
  packageSpec: opts.sandboxPackage,
101057
- env: parseSandboxEnv(opts.sandboxEnv),
101474
+ env: parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional),
101058
101475
  appSourceDir: opts.sandboxAppSource,
101059
101476
  appRemoteDir: opts.sandboxAppRemoteDir,
101060
101477
  appStartCommand: opts.sandboxAppStartCommand,
@@ -101073,6 +101490,36 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
101073
101490
  process.exit(1);
101074
101491
  }
101075
101492
  });
101493
+ workflowCmd.command("update <id>").description("Update a saved testing workflow").option("--sandbox-env <assignment>", "Sandbox env var; KEY forwards host KEY, KEY=value stores value (repeatable)", (val, acc) => {
101494
+ acc.push(val);
101495
+ return acc;
101496
+ }, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
101497
+ acc.push(val);
101498
+ return acc;
101499
+ }, []).option("--clear-sandbox-env", "Replace existing sandbox env vars instead of merging", false).option("--json", "Output as JSON", false).action((id, opts) => {
101500
+ try {
101501
+ const workflow = getTestingWorkflow(id);
101502
+ if (!workflow) {
101503
+ logError(chalk6.red(`Workflow not found: ${id}`));
101504
+ process.exit(1);
101505
+ }
101506
+ const envPatch = parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional);
101507
+ const execution = {
101508
+ ...workflow.execution,
101509
+ env: opts.clearSandboxEnv ? envPatch : { ...workflow.execution.env ?? {}, ...envPatch ?? {} }
101510
+ };
101511
+ if (Object.keys(execution.env ?? {}).length === 0)
101512
+ delete execution.env;
101513
+ const updated = updateTestingWorkflow(workflow.id, { execution });
101514
+ if (opts.json)
101515
+ log(JSON.stringify(updated, null, 2));
101516
+ else
101517
+ log(chalk6.green(`Workflow updated: ${chalk6.bold(updated.id.slice(0, 8))} \u2014 ${updated.name}`));
101518
+ } catch (error40) {
101519
+ logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
101520
+ process.exit(1);
101521
+ }
101522
+ });
101076
101523
  workflowCmd.command("list").description("List saved testing workflows").option("--project <id>", "Project ID").option("--all", "Include disabled workflows", false).option("--json", "Output as JSON", false).action((opts) => {
101077
101524
  const workflows = listTestingWorkflows({
101078
101525
  projectId: opts.project ? resolveProject2(opts.project) : undefined,
@@ -101342,7 +101789,7 @@ personaCmd.command("delete <id>").description("Delete a persona").option("-y, --
101342
101789
  }
101343
101790
  if (!opts.yes) {
101344
101791
  process.stdout.write(chalk6.yellow(`Delete persona ${persona.shortId} "${persona.name}"? [y/N] `));
101345
- const answer = await new Promise((resolve5) => {
101792
+ const answer = await new Promise((resolve6) => {
101346
101793
  let buf = "";
101347
101794
  process.stdin.setRawMode?.(true);
101348
101795
  process.stdin.resume();
@@ -101352,7 +101799,7 @@ personaCmd.command("delete <id>").description("Delete a persona").option("-y, --
101352
101799
  process.stdin.pause();
101353
101800
  process.stdout.write(`
101354
101801
  `);
101355
- resolve5(buf);
101802
+ resolve6(buf);
101356
101803
  });
101357
101804
  });
101358
101805
  if (answer !== "y" && answer !== "yes") {
@@ -101513,7 +101960,7 @@ evalCmd.command("rag <url>").description("Run RAG quality evaluation \u2014 fait
101513
101960
  let ragTestCases = [];
101514
101961
  if (opts.docs) {
101515
101962
  try {
101516
- const raw = readFileSync10(opts.docs, "utf-8");
101963
+ const raw = readFileSync11(opts.docs, "utf-8");
101517
101964
  ragTestCases = JSON.parse(raw);
101518
101965
  } catch {
101519
101966
  logError(chalk6.red(`Failed to read docs file: ${opts.docs}`));
@@ -101603,9 +102050,9 @@ Created golden answer check ${chalk6.bold(golden2.shortId)}`));
101603
102050
  }
101604
102051
  const ask = (prompt) => {
101605
102052
  const rl2 = createInterface({ input: process.stdin, output: process.stdout });
101606
- return new Promise((resolve5) => rl2.question(prompt, (ans) => {
102053
+ return new Promise((resolve6) => rl2.question(prompt, (ans) => {
101607
102054
  rl2.close();
101608
- resolve5(ans.trim());
102055
+ resolve6(ans.trim());
101609
102056
  }));
101610
102057
  };
101611
102058
  const question = await ask("Question (what this endpoint should answer): ");
@@ -101766,9 +102213,9 @@ program2.command("run-many <url>").description("Run scenarios \xD7 personas matr
101766
102213
  });
101767
102214
  program2.command("run-script <file>").description("Run a hybrid test script (.ts) that exports an array of HybridScenario objects").option("--url <url>", "Base URL to run against").option("--json", "Output as JSON", false).action(async (file2, opts) => {
101768
102215
  try {
101769
- const { resolve: resolve5 } = await import("path");
102216
+ const { resolve: resolve6 } = await import("path");
101770
102217
  const { runHybridScenario: runHybridScenario2 } = await Promise.resolve().then(() => (init_hybrid_runner(), exports_hybrid_runner));
101771
- const scriptPath = resolve5(process.cwd(), file2);
102218
+ const scriptPath = resolve6(process.cwd(), file2);
101772
102219
  const mod = await import(scriptPath);
101773
102220
  const scenarios = mod.scenarios ?? mod.default ?? [];
101774
102221
  if (!Array.isArray(scenarios) || scenarios.length === 0) {