@hasna/testers 0.0.47 → 0.0.48

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;
@@ -59663,49 +59720,49 @@ __export(exports_agents, {
59663
59720
  getAgent: () => getAgent
59664
59721
  });
59665
59722
  function registerAgent(input) {
59666
- const db2 = getDatabase();
59667
- const existing = db2.query("SELECT * FROM agents WHERE name = ?").get(input.name);
59723
+ const db3 = getDatabase();
59724
+ const existing = db3.query("SELECT * FROM agents WHERE name = ?").get(input.name);
59668
59725
  if (existing) {
59669
- db2.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), existing.id);
59726
+ db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), existing.id);
59670
59727
  return getAgent(existing.id);
59671
59728
  }
59672
59729
  const id = uuid();
59673
59730
  const timestamp = now();
59674
- db2.query(`
59731
+ db3.query(`
59675
59732
  INSERT INTO agents (id, name, description, role, metadata, created_at, last_seen_at)
59676
59733
  VALUES (?, ?, ?, ?, '{}', ?, ?)
59677
59734
  `).run(id, input.name, input.description ?? null, input.role ?? null, timestamp, timestamp);
59678
59735
  return getAgent(id);
59679
59736
  }
59680
59737
  function getAgent(id) {
59681
- const db2 = getDatabase();
59682
- const row = db2.query("SELECT * FROM agents WHERE id = ?").get(id);
59738
+ const db3 = getDatabase();
59739
+ const row = db3.query("SELECT * FROM agents WHERE id = ?").get(id);
59683
59740
  return row ? agentFromRow(row) : null;
59684
59741
  }
59685
59742
  function getAgentByName(name) {
59686
- const db2 = getDatabase();
59687
- const row = db2.query("SELECT * FROM agents WHERE name = ?").get(name);
59743
+ const db3 = getDatabase();
59744
+ const row = db3.query("SELECT * FROM agents WHERE name = ?").get(name);
59688
59745
  return row ? agentFromRow(row) : null;
59689
59746
  }
59690
59747
  function listAgents() {
59691
- const db2 = getDatabase();
59692
- const rows = db2.query("SELECT * FROM agents ORDER BY created_at DESC").all();
59748
+ const db3 = getDatabase();
59749
+ const rows = db3.query("SELECT * FROM agents ORDER BY created_at DESC").all();
59693
59750
  return rows.map(agentFromRow);
59694
59751
  }
59695
59752
  function heartbeatAgent(id) {
59696
- const db2 = getDatabase();
59697
- const affected = db2.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), id);
59753
+ const db3 = getDatabase();
59754
+ const affected = db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), id);
59698
59755
  if (affected.changes === 0)
59699
59756
  return null;
59700
59757
  return getAgent(id);
59701
59758
  }
59702
59759
  function setAgentFocus(id, scenarioId) {
59703
- const db2 = getDatabase();
59760
+ const db3 = getDatabase();
59704
59761
  const agent = getAgent(id);
59705
59762
  if (!agent)
59706
59763
  return null;
59707
59764
  const metadata = { ...agent.metadata ?? {}, focus: scenarioId };
59708
- db2.query("UPDATE agents SET metadata = ?, last_seen_at = ? WHERE id = ?").run(JSON.stringify(metadata), now(), id);
59765
+ db3.query("UPDATE agents SET metadata = ?, last_seen_at = ? WHERE id = ?").run(JSON.stringify(metadata), now(), id);
59709
59766
  return getAgent(id);
59710
59767
  }
59711
59768
  var init_agents = __esm(() => {
@@ -94089,28 +94146,28 @@ function checkResultFromRow(row) {
94089
94146
  };
94090
94147
  }
94091
94148
  function createGoldenAnswer(input) {
94092
- const db2 = getDatabase();
94149
+ const db3 = getDatabase();
94093
94150
  const id = uuid();
94094
94151
  const short_id = shortUuid();
94095
94152
  const timestamp = now();
94096
- db2.query(`
94153
+ db3.query(`
94097
94154
  INSERT INTO golden_answers (id, short_id, project_id, question, golden_answer, constraints, endpoint, judge_model, enabled, created_at, updated_at)
94098
94155
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
94099
94156
  `).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
94157
  return getGoldenAnswer(id);
94101
94158
  }
94102
94159
  function getGoldenAnswer(id) {
94103
- const db2 = getDatabase();
94104
- let row = db2.query("SELECT * FROM golden_answers WHERE id = ?").get(id);
94160
+ const db3 = getDatabase();
94161
+ let row = db3.query("SELECT * FROM golden_answers WHERE id = ?").get(id);
94105
94162
  if (row)
94106
94163
  return goldenFromRow(row);
94107
- row = db2.query("SELECT * FROM golden_answers WHERE short_id = ?").get(id);
94164
+ row = db3.query("SELECT * FROM golden_answers WHERE short_id = ?").get(id);
94108
94165
  if (row)
94109
94166
  return goldenFromRow(row);
94110
94167
  return null;
94111
94168
  }
94112
94169
  function listGoldenAnswers(filter2) {
94113
- const db2 = getDatabase();
94170
+ const db3 = getDatabase();
94114
94171
  const conditions = [];
94115
94172
  const params = [];
94116
94173
  if (filter2?.projectId) {
@@ -94126,11 +94183,11 @@ function listGoldenAnswers(filter2) {
94126
94183
  sql += " WHERE " + conditions.join(" AND ");
94127
94184
  }
94128
94185
  sql += " ORDER BY created_at DESC";
94129
- const rows = db2.query(sql).all(...params);
94186
+ const rows = db3.query(sql).all(...params);
94130
94187
  return rows.map(goldenFromRow);
94131
94188
  }
94132
94189
  function updateGoldenAnswer(id, input) {
94133
- const db2 = getDatabase();
94190
+ const db3 = getDatabase();
94134
94191
  const timestamp = now();
94135
94192
  const sets = ["updated_at = ?"];
94136
94193
  const params = [timestamp];
@@ -94159,26 +94216,26 @@ function updateGoldenAnswer(id, input) {
94159
94216
  params.push(input.enabled ? 1 : 0);
94160
94217
  }
94161
94218
  params.push(id);
94162
- db2.query(`UPDATE golden_answers SET ${sets.join(", ")} WHERE id = ? OR short_id = ?`).run(...params, id);
94219
+ db3.query(`UPDATE golden_answers SET ${sets.join(", ")} WHERE id = ? OR short_id = ?`).run(...params, id);
94163
94220
  return getGoldenAnswer(id);
94164
94221
  }
94165
94222
  function deleteGoldenAnswer(id) {
94166
- const db2 = getDatabase();
94167
- const result = db2.query("DELETE FROM golden_answers WHERE id = ? OR short_id = ?").run(id, id);
94223
+ const db3 = getDatabase();
94224
+ const result = db3.query("DELETE FROM golden_answers WHERE id = ? OR short_id = ?").run(id, id);
94168
94225
  return (result.changes ?? 0) > 0;
94169
94226
  }
94170
94227
  function createGoldenCheckResult(input) {
94171
- const db2 = getDatabase();
94228
+ const db3 = getDatabase();
94172
94229
  const id = uuid();
94173
94230
  const timestamp = now();
94174
- db2.query(`
94231
+ db3.query(`
94175
94232
  INSERT INTO golden_check_results (id, golden_id, response, similarity_score, passed, drift_detected, judge_model, provider, created_at)
94176
94233
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
94177
94234
  `).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
94235
  return listGoldenCheckResults(input.goldenId, { limit: 1 })[0];
94179
94236
  }
94180
94237
  function listGoldenCheckResults(goldenId, options) {
94181
- const db2 = getDatabase();
94238
+ const db3 = getDatabase();
94182
94239
  const params = [goldenId];
94183
94240
  let sql = "SELECT * FROM golden_check_results WHERE golden_id = ?";
94184
94241
  if (options?.since) {
@@ -94190,7 +94247,7 @@ function listGoldenCheckResults(goldenId, options) {
94190
94247
  sql += " LIMIT ?";
94191
94248
  params.push(options.limit);
94192
94249
  }
94193
- const rows = db2.query(sql).all(...params);
94250
+ const rows = db3.query(sql).all(...params);
94194
94251
  return rows.map(checkResultFromRow);
94195
94252
  }
94196
94253
  function getLatestGoldenCheckResult(goldenId) {
@@ -94479,7 +94536,7 @@ import chalk6 from "chalk";
94479
94536
  // package.json
94480
94537
  var package_default = {
94481
94538
  name: "@hasna/testers",
94482
- version: "0.0.47",
94539
+ version: "0.0.48",
94483
94540
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
94484
94541
  type: "module",
94485
94542
  main: "dist/index.js",
@@ -96613,51 +96670,51 @@ function fromRow3(row) {
96613
96670
  };
96614
96671
  }
96615
96672
  function createEnvironment(input) {
96616
- const db2 = getDatabase();
96673
+ const db3 = getDatabase();
96617
96674
  const id = uuid();
96618
96675
  const timestamp = now();
96619
96676
  const meta = JSON.stringify({ variables: input.variables ?? {} });
96620
- db2.query(`
96677
+ db3.query(`
96621
96678
  INSERT INTO environments (id, name, url, auth_preset_name, project_id, is_default, metadata, created_at)
96622
96679
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
96623
96680
  `).run(id, input.name, input.url, input.authPresetName ?? null, input.projectId ?? null, input.isDefault ? 1 : 0, meta, timestamp);
96624
96681
  return getEnvironmentById(id);
96625
96682
  }
96626
96683
  function getEnvironmentById(id) {
96627
- const db2 = getDatabase();
96628
- const row = db2.query("SELECT * FROM environments WHERE id = ?").get(id);
96684
+ const db3 = getDatabase();
96685
+ const row = db3.query("SELECT * FROM environments WHERE id = ?").get(id);
96629
96686
  return row ? fromRow3(row) : null;
96630
96687
  }
96631
96688
  function getEnvironment(name) {
96632
- const db2 = getDatabase();
96633
- const row = db2.query("SELECT * FROM environments WHERE name = ?").get(name);
96689
+ const db3 = getDatabase();
96690
+ const row = db3.query("SELECT * FROM environments WHERE name = ?").get(name);
96634
96691
  return row ? fromRow3(row) : null;
96635
96692
  }
96636
96693
  function listEnvironments(projectId) {
96637
- const db2 = getDatabase();
96694
+ const db3 = getDatabase();
96638
96695
  if (projectId) {
96639
- const rows2 = db2.query("SELECT * FROM environments WHERE project_id = ? ORDER BY is_default DESC, created_at DESC").all(projectId);
96696
+ const rows2 = db3.query("SELECT * FROM environments WHERE project_id = ? ORDER BY is_default DESC, created_at DESC").all(projectId);
96640
96697
  return rows2.map(fromRow3);
96641
96698
  }
96642
- const rows = db2.query("SELECT * FROM environments ORDER BY is_default DESC, created_at DESC").all();
96699
+ const rows = db3.query("SELECT * FROM environments ORDER BY is_default DESC, created_at DESC").all();
96643
96700
  return rows.map(fromRow3);
96644
96701
  }
96645
96702
  function deleteEnvironment(name) {
96646
- const db2 = getDatabase();
96647
- const result = db2.query("DELETE FROM environments WHERE name = ?").run(name);
96703
+ const db3 = getDatabase();
96704
+ const result = db3.query("DELETE FROM environments WHERE name = ?").run(name);
96648
96705
  return result.changes > 0;
96649
96706
  }
96650
96707
  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);
96708
+ const db3 = getDatabase();
96709
+ db3.exec("UPDATE environments SET is_default = 0");
96710
+ const result = db3.query("UPDATE environments SET is_default = 1 WHERE name = ?").run(name);
96654
96711
  if (result.changes === 0) {
96655
96712
  throw new Error(`Environment not found: ${name}`);
96656
96713
  }
96657
96714
  }
96658
96715
  function getDefaultEnvironment() {
96659
- const db2 = getDatabase();
96660
- const row = db2.query("SELECT * FROM environments WHERE is_default = 1 LIMIT 1").get();
96716
+ const db3 = getDatabase();
96717
+ const row = db3.query("SELECT * FROM environments WHERE is_default = 1 LIMIT 1").get();
96661
96718
  return row ? fromRow3(row) : null;
96662
96719
  }
96663
96720
 
@@ -97224,12 +97281,12 @@ async function runRepoTests(opts) {
97224
97281
  specResults.push(result);
97225
97282
  const resultId = uuid();
97226
97283
  const timestamp = now();
97227
- const db2 = getDatabase();
97228
- db2.exec("PRAGMA foreign_keys = OFF");
97284
+ const db3 = getDatabase();
97285
+ db3.exec("PRAGMA foreign_keys = OFF");
97229
97286
  try {
97230
97287
  const reasoning = result.status === "passed" ? "All tests passed" : (result.error ?? "").slice(0, 500) || null;
97231
97288
  const errorStr = result.status !== "passed" ? result.error ?? null : null;
97232
- db2.query(`
97289
+ db3.query(`
97233
97290
  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
97291
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, NULL, NULL)
97235
97292
  `).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 +97295,7 @@ async function runRepoTests(opts) {
97238
97295
  testResults: result.testResults
97239
97296
  }), timestamp);
97240
97297
  } finally {
97241
- db2.exec("PRAGMA foreign_keys = ON");
97298
+ db3.exec("PRAGMA foreign_keys = ON");
97242
97299
  }
97243
97300
  const resultRecord = { id: resultId };
97244
97301
  if (result.stdout || result.stderr) {
@@ -97385,21 +97442,31 @@ function envCredentialRef(value) {
97385
97442
  return;
97386
97443
  return trimmed.startsWith("$") ? trimmed : `$${trimmed}`;
97387
97444
  }
97388
- function parseSandboxEnv(values) {
97389
- if (!values?.length)
97445
+ function assertEnvVarName(key, rawValue) {
97446
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
97447
+ throw new Error(`Invalid sandbox env var name: ${key || rawValue}`);
97448
+ }
97449
+ }
97450
+ function parseSandboxEnv(values, optionalValues = []) {
97451
+ if (!values?.length && !optionalValues?.length)
97390
97452
  return;
97391
97453
  const env = {};
97392
- for (const value of values) {
97454
+ for (const value of values ?? []) {
97393
97455
  const trimmed = value.trim();
97394
97456
  if (!trimmed)
97395
97457
  continue;
97396
97458
  const separator = trimmed.indexOf("=");
97397
97459
  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
- }
97460
+ assertEnvVarName(key, value);
97401
97461
  env[key] = separator >= 0 ? trimmed.slice(separator + 1) : `$${key}`;
97402
97462
  }
97463
+ for (const value of optionalValues ?? []) {
97464
+ const key = value.trim();
97465
+ if (!key)
97466
+ continue;
97467
+ assertEnvVarName(key, value);
97468
+ env[key] = `$?${key}`;
97469
+ }
97403
97470
  return Object.keys(env).length > 0 ? env : undefined;
97404
97471
  }
97405
97472
  function AddForm({ onComplete }) {
@@ -100558,8 +100625,8 @@ program2.command("doctor").description("Check system setup and configuration").a
100558
100625
  const dbPath = join20(getTestersDir(), "testers.db");
100559
100626
  try {
100560
100627
  const { Database: Database5 } = await import("bun:sqlite");
100561
- const db2 = new Database5(dbPath, { create: true });
100562
- db2.close();
100628
+ const db3 = new Database5(dbPath, { create: true });
100629
+ db3.close();
100563
100630
  log(chalk6.green("\u2713") + ` Database accessible: ${dbPath}`);
100564
100631
  } catch (err) {
100565
100632
  log(chalk6.red("\u2717") + ` Database not accessible at ${dbPath}: ${err instanceof Error ? err.message : String(err)}`);
@@ -101028,6 +101095,9 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
101028
101095
  }, []).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
101096
  acc.push(val);
101030
101097
  return acc;
101098
+ }, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
101099
+ acc.push(val);
101100
+ return acc;
101031
101101
  }, []).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
101102
  try {
101033
101103
  const workflow = createTestingWorkflow({
@@ -101054,7 +101124,7 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
101054
101124
  sandboxSyncStrategy: opts.sandboxSync,
101055
101125
  setupCommand: opts.sandboxSetupCommand,
101056
101126
  packageSpec: opts.sandboxPackage,
101057
- env: parseSandboxEnv(opts.sandboxEnv),
101127
+ env: parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional),
101058
101128
  appSourceDir: opts.sandboxAppSource,
101059
101129
  appRemoteDir: opts.sandboxAppRemoteDir,
101060
101130
  appStartCommand: opts.sandboxAppStartCommand,
@@ -101073,6 +101143,36 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
101073
101143
  process.exit(1);
101074
101144
  }
101075
101145
  });
101146
+ 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) => {
101147
+ acc.push(val);
101148
+ return acc;
101149
+ }, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
101150
+ acc.push(val);
101151
+ return acc;
101152
+ }, []).option("--clear-sandbox-env", "Replace existing sandbox env vars instead of merging", false).option("--json", "Output as JSON", false).action((id, opts) => {
101153
+ try {
101154
+ const workflow = getTestingWorkflow(id);
101155
+ if (!workflow) {
101156
+ logError(chalk6.red(`Workflow not found: ${id}`));
101157
+ process.exit(1);
101158
+ }
101159
+ const envPatch = parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional);
101160
+ const execution = {
101161
+ ...workflow.execution,
101162
+ env: opts.clearSandboxEnv ? envPatch : { ...workflow.execution.env ?? {}, ...envPatch ?? {} }
101163
+ };
101164
+ if (Object.keys(execution.env ?? {}).length === 0)
101165
+ delete execution.env;
101166
+ const updated = updateTestingWorkflow(workflow.id, { execution });
101167
+ if (opts.json)
101168
+ log(JSON.stringify(updated, null, 2));
101169
+ else
101170
+ log(chalk6.green(`Workflow updated: ${chalk6.bold(updated.id.slice(0, 8))} \u2014 ${updated.name}`));
101171
+ } catch (error40) {
101172
+ logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
101173
+ process.exit(1);
101174
+ }
101175
+ });
101076
101176
  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
101177
  const workflows = listTestingWorkflows({
101078
101178
  projectId: opts.project ? resolveProject2(opts.project) : undefined,
package/dist/index.js CHANGED
@@ -17223,6 +17223,8 @@ var APP_SOURCE_EXCLUDES = [
17223
17223
  ".next",
17224
17224
  ".turbo",
17225
17225
  ".cache",
17226
+ ".env",
17227
+ ".env.*",
17226
17228
  ".venv",
17227
17229
  "__pycache__"
17228
17230
  ];
@@ -17476,6 +17478,13 @@ function resolveSandboxEnv(env2) {
17476
17478
  return;
17477
17479
  const resolved = {};
17478
17480
  for (const [key, value] of Object.entries(env2)) {
17481
+ if (value.startsWith("$?")) {
17482
+ const optionalName = value.slice(2).trim();
17483
+ const optionalValue = optionalName ? process.env[optionalName] : undefined;
17484
+ if (optionalValue !== undefined)
17485
+ resolved[key] = optionalValue;
17486
+ continue;
17487
+ }
17479
17488
  const resolvedValue = resolveCredential(value);
17480
17489
  if (resolvedValue === null) {
17481
17490
  throw new Error(`Missing sandbox env value for ${key}`);
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"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"}
package/dist/mcp/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "@hasna/testers",
55
- version: "0.0.47",
55
+ version: "0.0.48",
56
56
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
57
57
  type: "module",
58
58
  main: "dist/index.js",
@@ -23754,6 +23754,13 @@ function resolveSandboxEnv(env2) {
23754
23754
  return;
23755
23755
  const resolved = {};
23756
23756
  for (const [key, value] of Object.entries(env2)) {
23757
+ if (value.startsWith("$?")) {
23758
+ const optionalName = value.slice(2).trim();
23759
+ const optionalValue = optionalName ? process.env[optionalName] : undefined;
23760
+ if (optionalValue !== undefined)
23761
+ resolved[key] = optionalValue;
23762
+ continue;
23763
+ }
23757
23764
  const resolvedValue = resolveCredential(value);
23758
23765
  if (resolvedValue === null) {
23759
23766
  throw new Error(`Missing sandbox env value for ${key}`);
@@ -23787,6 +23794,8 @@ var init_workflow_runner = __esm(() => {
23787
23794
  ".next",
23788
23795
  ".turbo",
23789
23796
  ".cache",
23797
+ ".env",
23798
+ ".env.*",
23790
23799
  ".venv",
23791
23800
  "__pycache__"
23792
23801
  ];
@@ -46937,7 +46937,7 @@ import { join as join14 } from "path";
46937
46937
  // package.json
46938
46938
  var package_default = {
46939
46939
  name: "@hasna/testers",
46940
- version: "0.0.47",
46940
+ version: "0.0.48",
46941
46941
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
46942
46942
  type: "module",
46943
46943
  main: "dist/index.js",
@@ -51339,6 +51339,8 @@ var APP_SOURCE_EXCLUDES = [
51339
51339
  ".next",
51340
51340
  ".turbo",
51341
51341
  ".cache",
51342
+ ".env",
51343
+ ".env.*",
51342
51344
  ".venv",
51343
51345
  "__pycache__"
51344
51346
  ];
@@ -51592,6 +51594,13 @@ function resolveSandboxEnv(env) {
51592
51594
  return;
51593
51595
  const resolved = {};
51594
51596
  for (const [key, value] of Object.entries(env)) {
51597
+ if (value.startsWith("$?")) {
51598
+ const optionalName = value.slice(2).trim();
51599
+ const optionalValue = optionalName ? process.env[optionalName] : undefined;
51600
+ if (optionalValue !== undefined)
51601
+ resolved[key] = optionalValue;
51602
+ continue;
51603
+ }
51595
51604
  const resolvedValue = resolveCredential(value);
51596
51605
  if (resolvedValue === null) {
51597
51606
  throw new Error(`Missing sandbox env value for ${key}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.47",
3
+ "version": "0.0.48",
4
4
  "description": "AI-powered QA testing CLI — spawns cheap AI agents to test web apps with headless browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",