agent-qa 0.1.9 → 0.1.10

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.cjs CHANGED
@@ -277,7 +277,7 @@ async function loadAuthPluginsForRawConfig(raw, configPath = "agent-qa.config.ya
277
277
  if (!Array.isArray(declarations) || declarations.length === 0) return;
278
278
  const { loadLLMAuthPlugins: loadLLMAuthPlugins2 } = await import("@vostride/agent-qa-core");
279
279
  await loadLLMAuthPlugins2(declarations, {
280
- baseDir: (0, import_node_path5.dirname)((0, import_node_path5.resolve)(configPath))
280
+ baseDir: (0, import_node_path6.dirname)((0, import_node_path6.resolve)(configPath))
281
281
  });
282
282
  }
283
283
  async function resolveNamedConfig(configName, configPath = "agent-qa.config.yaml") {
@@ -340,11 +340,11 @@ async function resolveCredentials(configName, llmConfig) {
340
340
  return {};
341
341
  }
342
342
  }
343
- var import_node_path5;
343
+ var import_node_path6;
344
344
  var init_llm_utils = __esm({
345
345
  "src/llm-utils.ts"() {
346
346
  "use strict";
347
- import_node_path5 = require("path");
347
+ import_node_path6 = require("path");
348
348
  init_model_defaults();
349
349
  }
350
350
  });
@@ -355,7 +355,7 @@ __export(cli_exports, {
355
355
  program: () => program
356
356
  });
357
357
  module.exports = __toCommonJS(cli_exports);
358
- var import_commander18 = require("commander");
358
+ var import_commander19 = require("commander");
359
359
 
360
360
  // src/index.ts
361
361
  var index_exports = {};
@@ -508,130 +508,17 @@ function createCacheCommand() {
508
508
  var VERSION = (0, import_agent_qa_core3.getAgentQaVersion)();
509
509
 
510
510
  // src/commands/init.ts
511
- var import_commander3 = require("commander");
511
+ var import_commander2 = require("commander");
512
512
  var import_node_fs = require("fs");
513
- var import_node_path4 = require("path");
514
- var import_node_child_process2 = require("child_process");
513
+ var import_node_path3 = require("path");
515
514
  var import_yaml3 = require("yaml");
516
- var import_picocolors4 = __toESM(require("picocolors"), 1);
515
+ var import_picocolors3 = __toESM(require("picocolors"), 1);
517
516
  var import_select = __toESM(require("@inquirer/select"), 1);
518
517
  var import_input = __toESM(require("@inquirer/input"), 1);
519
518
  var import_checkbox = __toESM(require("@inquirer/checkbox"), 1);
520
-
521
- // src/commands/install-browsers.ts
522
- var import_node_child_process = require("child_process");
523
- var import_node_module = require("module");
524
- var import_node_path3 = require("path");
525
- var import_commander2 = require("commander");
526
- var import_picocolors3 = __toESM(require("picocolors"), 1);
527
- var import_meta = {};
528
- function selectedBrowserCount(selection) {
529
- return [selection.chromium, selection.firefox, selection.webkit].filter(Boolean).length;
530
- }
531
- function validateBrowserInstallSelection(selection) {
532
- if (!selection.all && selectedBrowserCount(selection) === 0) {
533
- return "Select at least one browser flag: --all, --chromium, --firefox, or --webkit.";
534
- }
535
- if (selection.all && selectedBrowserCount(selection) > 0) {
536
- return "Cannot combine --all with --chromium, --firefox, or --webkit.";
537
- }
538
- return null;
539
- }
540
- function buildBrowserInstallArgs(selection) {
541
- const args = [];
542
- if (selection.withDeps) args.push("--with-deps");
543
- if (selection.force) args.push("--force");
544
- if (!selection.all) {
545
- if (selection.chromium) args.push("chromium");
546
- if (selection.firefox) args.push("firefox");
547
- if (selection.webkit) args.push("webkit");
548
- }
549
- return args;
550
- }
551
- function formatInstallBrowsersRetryCommand(selection) {
552
- const args = ["agent-qa", "install-browsers"];
553
- if (selection.all) {
554
- args.push("--all");
555
- } else {
556
- if (selection.chromium) args.push("--chromium");
557
- if (selection.firefox) args.push("--firefox");
558
- if (selection.webkit) args.push("--webkit");
559
- }
560
- if (selection.withDeps) args.push("--with-deps");
561
- if (selection.force) args.push("--force");
562
- return args.join(" ");
563
- }
564
- function resolvePlaywrightCoreCli() {
565
- const require2 = (0, import_node_module.createRequire)(import_meta.url);
566
- const webEntry = require2.resolve("@vostride/agent-qa-web");
567
- const webRequire = (0, import_node_module.createRequire)(webEntry);
568
- const packagePath = webRequire.resolve("playwright-core/package.json");
569
- return (0, import_node_path3.join)((0, import_node_path3.dirname)(packagePath), "cli.js");
570
- }
571
- function normalizeSpawnStatus(result) {
572
- if (typeof result.status === "number") return result.status;
573
- return 1;
574
- }
575
- function runBrowserInstall(selection, deps = {}) {
576
- let cliPath;
577
- try {
578
- cliPath = (deps.resolveCli ?? resolvePlaywrightCoreCli)();
579
- } catch (error) {
580
- return { ok: false, status: 1, stage: "resolve", error };
581
- }
582
- const spawn = deps.spawn ?? import_node_child_process.spawnSync;
583
- const result = spawn(process.execPath, [cliPath, "install", ...buildBrowserInstallArgs(selection)], {
584
- stdio: "inherit"
585
- });
586
- if (result.error) {
587
- return { ok: false, status: normalizeSpawnStatus(result), stage: "spawn", error: result.error };
588
- }
589
- const status = normalizeSpawnStatus(result);
590
- return { ok: status === 0, status, stage: "installer" };
591
- }
592
- function printBrowserInstallFailure(selection, result) {
593
- if (result.stage === "resolve") {
594
- console.error(import_picocolors3.default.red("agent-qa could not find its bundled browser installer."));
595
- console.error(import_picocolors3.default.dim("Reinstall agent-qa dependencies, then retry:"));
596
- } else {
597
- console.error(import_picocolors3.default.red("Browser installation failed."));
598
- console.error(import_picocolors3.default.dim("Retry:"));
599
- }
600
- console.error(` ${import_picocolors3.default.cyan(formatInstallBrowsersRetryCommand(selection))}`);
601
- }
602
- function createInstallBrowsersCommand(deps = {}) {
603
- const cmd = new import_commander2.Command("install-browsers").description("Install browser support for agent-qa").option("--all", "install all Playwright-managed browsers for agent-qa").option("--chromium", "install Chromium browser support").option("--firefox", "install Firefox browser support").option("--webkit", "install WebKit browser support").option("--with-deps", "also install required system dependencies").option("--force", "force reinstall even if browser support already exists").action((opts) => {
604
- const selection = {
605
- all: !!opts.all,
606
- chromium: !!opts.chromium,
607
- firefox: !!opts.firefox,
608
- webkit: !!opts.webkit,
609
- withDeps: !!opts.withDeps,
610
- force: !!opts.force
611
- };
612
- const validationError = validateBrowserInstallSelection(selection);
613
- if (validationError) {
614
- console.error(import_picocolors3.default.red(`Error: ${validationError}`));
615
- console.error(cmd.helpInformation());
616
- process.exitCode = 1;
617
- return;
618
- }
619
- console.log(import_picocolors3.default.blue("Installing agent-qa browser support..."));
620
- const result = runBrowserInstall(selection, deps);
621
- if (result.ok) {
622
- console.log(import_picocolors3.default.green("\u2713 agent-qa browser support installed"));
623
- return;
624
- }
625
- printBrowserInstallFailure(selection, result);
626
- process.exitCode = result.status || 1;
627
- });
628
- return cmd;
629
- }
630
-
631
- // src/commands/init.ts
632
519
  var import_agent_qa_core4 = require("@vostride/agent-qa-core");
633
520
  init_model_defaults();
634
- var import_meta2 = {};
521
+ var import_meta = {};
635
522
  var INIT_CACHE_DIR = import_agent_qa_core4.DEFAULT_AGENT_QA_CACHE_DIR || ".agent-qa/cache";
636
523
  var INIT_RUNTIME_DIR = import_agent_qa_core4.DEFAULT_AGENT_QA_RUNTIME_DIR || ".agent-qa";
637
524
  var SUBSCRIPTION_AUTH_PACKAGE = "@vostride/agent-qa-subscription-auth";
@@ -695,9 +582,6 @@ function normalizePlatformSelection(platform) {
695
582
  return { capabilities: ["web", "mobile"], mobileRuntimes: ["ios"] };
696
583
  }
697
584
  }
698
- function hasMobile(platform) {
699
- return normalizePlatformSelection(platform).mobileRuntimes.length > 0;
700
- }
701
585
  function hasWeb(platform) {
702
586
  return normalizePlatformSelection(platform).capabilities.includes("web");
703
587
  }
@@ -851,7 +735,7 @@ function isSubscriptionProvider(provider) {
851
735
  }
852
736
  function readAgentQaPackageVersion() {
853
737
  try {
854
- const manifest = JSON.parse((0, import_node_fs.readFileSync)(new URL("../../package.json", import_meta2.url), "utf-8"));
738
+ const manifest = JSON.parse((0, import_node_fs.readFileSync)(new URL("../../package.json", import_meta.url), "utf-8"));
855
739
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : null;
856
740
  } catch {
857
741
  return null;
@@ -870,7 +754,7 @@ function configUsesSubscriptionAuth(config) {
870
754
  return authPlugins.some((plugin) => objectRecord(plugin)?.package === SUBSCRIPTION_AUTH_PACKAGE);
871
755
  }
872
756
  function upsertSubscriptionAuthPackageDependency(dir) {
873
- const packageJsonPath = (0, import_node_path4.join)(dir, PACKAGE_JSON_FILE);
757
+ const packageJsonPath = (0, import_node_path3.join)(dir, PACKAGE_JSON_FILE);
874
758
  const created = !(0, import_node_fs.existsSync)(packageJsonPath);
875
759
  const pkg = created ? { private: true } : JSON.parse((0, import_node_fs.readFileSync)(packageJsonPath, "utf-8"));
876
760
  if (!objectRecord(pkg)) {
@@ -1065,7 +949,7 @@ providers:
1065
949
  `;
1066
950
  }
1067
951
  function appendGitignore(dir) {
1068
- const gitignorePath = (0, import_node_path4.join)(dir, ".gitignore");
952
+ const gitignorePath = (0, import_node_path3.join)(dir, ".gitignore");
1069
953
  let existing = "";
1070
954
  if ((0, import_node_fs.existsSync)(gitignorePath)) {
1071
955
  existing = (0, import_node_fs.readFileSync)(gitignorePath, "utf-8");
@@ -1075,135 +959,38 @@ function appendGitignore(dir) {
1075
959
  const append = (existing.length > 0 && !existing.endsWith("\n") ? "\n" : "") + missing.join("\n") + "\n";
1076
960
  (0, import_node_fs.writeFileSync)(gitignorePath, existing + append);
1077
961
  }
1078
- function installPlaywrightBrowsers(platform) {
1079
- if (!hasWeb(platform)) return;
1080
- const selection = { chromium: true };
1081
- console.log(import_picocolors4.default.blue("Installing agent-qa browser support..."));
1082
- const result = runBrowserInstall(selection);
1083
- if (result.ok) {
1084
- console.log(import_picocolors4.default.green("\u2713 agent-qa browser support installed"));
1085
- } else {
1086
- console.log(import_picocolors4.default.yellow(`\u26A0 Browser installation failed. Run \`${formatInstallBrowsersRetryCommand(selection)}\` to retry.`));
1087
- }
1088
- }
1089
- function collectDriverNames(value, names) {
1090
- if (!value) return;
1091
- if (typeof value === "string") {
1092
- names.add(value);
1093
- return;
1094
- }
1095
- if (Array.isArray(value)) {
1096
- for (const item of value) collectDriverNames(item, names);
1097
- return;
1098
- }
1099
- if (typeof value !== "object") return;
1100
- const record = value;
1101
- if (typeof record.name === "string") names.add(record.name);
1102
- if (typeof record.driverName === "string") names.add(record.driverName);
1103
- if (typeof record.automationName === "string") names.add(record.automationName);
1104
- for (const [key, child] of Object.entries(record)) {
1105
- if (key !== "drivers" && key !== "installed" && key !== "name" && key !== "driverName" && typeof child === "object" && child !== null) {
1106
- names.add(key);
1107
- }
1108
- collectDriverNames(child, names);
1109
- }
1110
- }
1111
- function parseInstalledAppiumDrivers(output) {
1112
- const names = /* @__PURE__ */ new Set();
1113
- const trimmed = output.trim();
1114
- if (!trimmed) return names;
1115
- try {
1116
- collectDriverNames(JSON.parse(trimmed), names);
1117
- const normalizedNames = /* @__PURE__ */ new Set();
1118
- for (const name of names) {
1119
- const normalized = name.toLowerCase();
1120
- normalizedNames.add(normalized);
1121
- if (normalized.includes("uiautomator2")) normalizedNames.add("uiautomator2");
1122
- if (normalized.includes("xcuitest")) normalizedNames.add("xcuitest");
1123
- }
1124
- return normalizedNames;
1125
- } catch {
1126
- for (const line of trimmed.split("\n")) {
1127
- const normalized = line.toLowerCase();
1128
- if (normalized.includes("uiautomator2")) names.add("uiautomator2");
1129
- if (normalized.includes("xcuitest")) names.add("xcuitest");
1130
- }
1131
- return names;
1132
- }
1133
- }
1134
- function getInstalledAppiumDrivers() {
1135
- try {
1136
- const output = (0, import_node_child_process2.execSync)("appium driver list --installed --json", { stdio: "pipe", encoding: "utf-8" });
1137
- return parseInstalledAppiumDrivers(String(output));
1138
- } catch {
1139
- try {
1140
- const output = (0, import_node_child_process2.execSync)("appium driver list --installed", { stdio: "pipe", encoding: "utf-8" });
1141
- return parseInstalledAppiumDrivers(String(output));
1142
- } catch {
1143
- return /* @__PURE__ */ new Set();
1144
- }
1145
- }
1146
- }
1147
- function appiumDriverLabel(driver) {
1148
- return driver === "uiautomator2" ? "UiAutomator2" : "XCUITest";
1149
- }
1150
- function isAlreadyInstalledDriverError(err, driver) {
1151
- const details = [
1152
- err instanceof Error ? err.message : String(err),
1153
- err?.stdout,
1154
- err?.stderr
1155
- ].map(String).join("\n").toLowerCase();
1156
- return details.includes(`driver named "${driver}" is already installed`) || details.includes(`${driver}" is already installed`) || details.includes(`${driver} is already installed`);
1157
- }
1158
- function ensureAppiumDriver(driver, installedDrivers) {
1159
- const label = appiumDriverLabel(driver);
1160
- if (installedDrivers.has(driver)) {
1161
- console.log(import_picocolors4.default.green(`\u2713 ${label} driver already installed`));
1162
- return;
1163
- }
1164
- try {
1165
- (0, import_node_child_process2.execSync)(`appium driver install ${driver}`, { stdio: "inherit" });
1166
- console.log(import_picocolors4.default.green(`\u2713 ${label} driver installed`));
1167
- } catch (err) {
1168
- if (isAlreadyInstalledDriverError(err, driver)) {
1169
- console.log(import_picocolors4.default.green(`\u2713 ${label} driver already installed`));
1170
- return;
1171
- }
1172
- console.log(import_picocolors4.default.yellow(`\u26A0 ${label} driver installation failed. Run \`appium driver install ${driver}\` manually.`));
1173
- }
1174
- }
1175
- function installAppium(platform) {
1176
- if (!hasMobile(platform)) return;
1177
- const selection = normalizePlatformSelection(platform);
1178
- console.log(import_picocolors4.default.blue("Installing Appium..."));
1179
- let appiumInstalled = false;
1180
- try {
1181
- (0, import_node_child_process2.execSync)("appium --version", { stdio: "pipe" });
1182
- appiumInstalled = true;
1183
- console.log(import_picocolors4.default.green("\u2713 Appium already installed"));
1184
- } catch {
1185
- try {
1186
- (0, import_node_child_process2.execSync)("npm install -g appium", { stdio: "inherit" });
1187
- console.log(import_picocolors4.default.green("\u2713 Appium installed"));
1188
- appiumInstalled = true;
1189
- } catch {
1190
- console.log(import_picocolors4.default.yellow("\u26A0 Appium installation failed. Run `npm install -g appium` manually."));
1191
- }
1192
- }
1193
- if (!appiumInstalled) return;
1194
- const installedDrivers = getInstalledAppiumDrivers();
1195
- if (selection.mobileRuntimes.includes("android")) {
1196
- ensureAppiumDriver("uiautomator2", installedDrivers);
1197
- }
1198
- if (selection.mobileRuntimes.includes("ios")) {
1199
- ensureAppiumDriver("xcuitest", installedDrivers);
1200
- }
962
+ function browserSetupCommand(platform) {
963
+ return hasWeb(platform) ? "agent-qa install-browsers --chromium" : null;
964
+ }
965
+ function mobileDriverSetupCommand(platform) {
966
+ const runtimes = normalizePlatformSelection(platform).mobileRuntimes;
967
+ if (runtimes.length === 0) return null;
968
+ if (runtimes.includes("android") && runtimes.includes("ios")) return "agent-qa install-mobile-drivers --all";
969
+ if (runtimes.includes("android")) return "agent-qa install-mobile-drivers --android";
970
+ return "agent-qa install-mobile-drivers --ios";
971
+ }
972
+ function initNextSteps(platform) {
973
+ const steps = [];
974
+ const browserCommand = browserSetupCommand(platform);
975
+ const mobileCommand = mobileDriverSetupCommand(platform);
976
+ if (browserCommand) {
977
+ steps.push({ command: browserCommand, description: "Install browser support" });
978
+ }
979
+ if (mobileCommand) {
980
+ steps.push({ command: mobileCommand, description: "Install mobile driver support" });
981
+ }
982
+ steps.push(
983
+ { command: "agent-qa doctor", description: "Verify your environment" },
984
+ { command: "Edit tests/example-pass.yaml", description: "Write your first test" },
985
+ { command: "agent-qa run", description: "Run all tests" },
986
+ { command: "agent-qa dashboard", description: "View results in browser" }
987
+ );
988
+ return steps;
1201
989
  }
1202
990
  function createInitCommand() {
1203
- const cmd = new import_commander3.Command("init").description("Initialize a new agent-qa project").option("--dir <path>", "target directory", process.cwd()).option("--platform <type>", "platform to configure (web, android, ios, web+android, web+ios)").option("--skip-install", "skip auto-installation of browsers/Appium").option("--force", "overwrite existing files").action(async (opts) => {
1204
- const dir = (0, import_node_path4.resolve)(opts.dir);
991
+ const cmd = new import_commander2.Command("init").description("Initialize a new agent-qa project").option("--dir <path>", "target directory", process.cwd()).option("--platform <type>", "platform to configure (web, android, ios, web+android, web+ios)").option("--skip-install", "deprecated no-op; setup commands are shown after init").option("--force", "overwrite existing files").action(async (opts) => {
992
+ const dir = (0, import_node_path3.resolve)(opts.dir);
1205
993
  const force = opts.force ?? false;
1206
- const skipInstall = opts.skipInstall ?? false;
1207
994
  const isInteractive = !opts.platform;
1208
995
  let platform;
1209
996
  let provider = "anthropic-subscription";
@@ -1231,7 +1018,7 @@ function createInitCommand() {
1231
1018
  }) : [];
1232
1019
  platform = { capabilities, mobileRuntimes };
1233
1020
  const llmSetup = await (0, import_checkbox.default)({
1234
- message: `Subscription auth ${import_picocolors4.default.dim("(optional)")}`,
1021
+ message: `Subscription auth ${import_picocolors3.default.dim("(optional)")}`,
1235
1022
  choices: LLM_SETUP_CHOICES,
1236
1023
  required: true,
1237
1024
  validate: (value) => {
@@ -1266,12 +1053,12 @@ function createInitCommand() {
1266
1053
  provider = "anthropic-subscription";
1267
1054
  model = DEFAULT_ANTHROPIC_MODEL;
1268
1055
  }
1269
- const configPath = (0, import_node_path4.join)(dir, "agent-qa.config.yaml");
1056
+ const configPath = (0, import_node_path3.join)(dir, "agent-qa.config.yaml");
1270
1057
  if ((0, import_node_fs.existsSync)(configPath) && !force) {
1271
- console.log(import_picocolors4.default.yellow(`Config file already exists at ${configPath}. Use --force to overwrite.`));
1058
+ console.log(import_picocolors3.default.yellow(`Config file already exists at ${configPath}. Use --force to overwrite.`));
1272
1059
  return;
1273
1060
  }
1274
- const testsDir = (0, import_node_path4.join)(dir, "tests");
1061
+ const testsDir = (0, import_node_path3.join)(dir, "tests");
1275
1062
  if (!(0, import_node_fs.existsSync)(testsDir)) {
1276
1063
  (0, import_node_fs.mkdirSync)(testsDir, { recursive: true });
1277
1064
  }
@@ -1280,119 +1067,111 @@ function createInitCommand() {
1280
1067
  const yamlStr = (0, import_yaml3.stringify)(config);
1281
1068
  const configContent = addYamlComments(yamlStr);
1282
1069
  (0, import_node_fs.writeFileSync)(configPath, configContent);
1283
- console.log(import_picocolors4.default.green(`\u2713 Created ${configPath}`));
1070
+ console.log(import_picocolors3.default.green(`\u2713 Created ${configPath}`));
1284
1071
  const subscriptionAuthDependency = usesSubscriptionAuth ? upsertSubscriptionAuthPackageDependency(dir) : null;
1285
1072
  if (subscriptionAuthDependency) {
1286
1073
  const action = subscriptionAuthDependency.created ? "Created" : "Updated";
1287
- console.log(import_picocolors4.default.green(`\u2713 ${action} ${subscriptionAuthDependency.path}`));
1074
+ console.log(import_picocolors3.default.green(`\u2713 ${action} ${subscriptionAuthDependency.path}`));
1288
1075
  }
1289
- const secretsPath = (0, import_node_path4.join)(dir, DEFAULT_SECRETS_FILE);
1076
+ const secretsPath = (0, import_node_path3.join)(dir, DEFAULT_SECRETS_FILE);
1290
1077
  if (force || !(0, import_node_fs.existsSync)(secretsPath)) {
1291
1078
  (0, import_node_fs.writeFileSync)(secretsPath, "");
1292
- console.log(import_picocolors4.default.green(`\u2713 Created ${secretsPath}`));
1079
+ console.log(import_picocolors3.default.green(`\u2713 Created ${secretsPath}`));
1293
1080
  }
1294
- const envPath = (0, import_node_path4.join)(dir, DEFAULT_ENV_FILE);
1081
+ const envPath = (0, import_node_path3.join)(dir, DEFAULT_ENV_FILE);
1295
1082
  if (force || !(0, import_node_fs.existsSync)(envPath)) {
1296
1083
  (0, import_node_fs.writeFileSync)(envPath, "");
1297
- console.log(import_picocolors4.default.green(`\u2713 Created ${envPath}`));
1084
+ console.log(import_picocolors3.default.green(`\u2713 Created ${envPath}`));
1298
1085
  }
1299
- const hooksPath = (0, import_node_path4.join)(dir, DEFAULT_HOOKS_FILE);
1086
+ const hooksPath = (0, import_node_path3.join)(dir, DEFAULT_HOOKS_FILE);
1300
1087
  if (force || !(0, import_node_fs.existsSync)(hooksPath)) {
1301
1088
  (0, import_node_fs.writeFileSync)(hooksPath, buildHooksFile(platform));
1302
- console.log(import_picocolors4.default.green(`\u2713 Created ${hooksPath}`));
1089
+ console.log(import_picocolors3.default.green(`\u2713 Created ${hooksPath}`));
1303
1090
  }
1304
- const agentRulesPath = (0, import_node_path4.join)(dir, DEFAULT_AGENT_RULES_FILE);
1091
+ const agentRulesPath = (0, import_node_path3.join)(dir, DEFAULT_AGENT_RULES_FILE);
1305
1092
  if (force || !(0, import_node_fs.existsSync)(agentRulesPath)) {
1306
1093
  (0, import_node_fs.writeFileSync)(agentRulesPath, "# agent-qa Rules\n");
1307
- console.log(import_picocolors4.default.green(`\u2713 Created ${agentRulesPath}`));
1094
+ console.log(import_picocolors3.default.green(`\u2713 Created ${agentRulesPath}`));
1308
1095
  }
1309
- const localConfigPath = (0, import_node_path4.join)(dir, DEFAULT_LOCAL_CONFIG_FILE);
1096
+ const localConfigPath = (0, import_node_path3.join)(dir, DEFAULT_LOCAL_CONFIG_FILE);
1310
1097
  if (force || !(0, import_node_fs.existsSync)(localConfigPath)) {
1311
1098
  (0, import_node_fs.writeFileSync)(localConfigPath, buildLocalConfigTemplate());
1312
- console.log(import_picocolors4.default.green(`\u2713 Created ${localConfigPath}`));
1099
+ console.log(import_picocolors3.default.green(`\u2713 Created ${localConfigPath}`));
1313
1100
  }
1314
- const examplePassPath = (0, import_node_path4.join)(testsDir, "example-pass.yaml");
1101
+ const examplePassPath = (0, import_node_path3.join)(testsDir, "example-pass.yaml");
1315
1102
  (0, import_node_fs.writeFileSync)(examplePassPath, buildExampleTest(platform, "pass"));
1316
- console.log(import_picocolors4.default.green(`\u2713 Created ${examplePassPath}`));
1317
- const exampleFailPath = (0, import_node_path4.join)(testsDir, "example-fail.yaml");
1103
+ console.log(import_picocolors3.default.green(`\u2713 Created ${examplePassPath}`));
1104
+ const exampleFailPath = (0, import_node_path3.join)(testsDir, "example-fail.yaml");
1318
1105
  (0, import_node_fs.writeFileSync)(exampleFailPath, buildExampleTest(platform, "fail"));
1319
- console.log(import_picocolors4.default.green(`\u2713 Created ${exampleFailPath}`));
1106
+ console.log(import_picocolors3.default.green(`\u2713 Created ${exampleFailPath}`));
1320
1107
  if (hasWeb(platform)) {
1321
- const suitesDir = (0, import_node_path4.join)(dir, "suites");
1108
+ const suitesDir = (0, import_node_path3.join)(dir, "suites");
1322
1109
  if (!(0, import_node_fs.existsSync)(suitesDir)) {
1323
1110
  (0, import_node_fs.mkdirSync)(suitesDir, { recursive: true });
1324
1111
  }
1325
- const scriptsDir = (0, import_node_path4.join)(dir, "scripts");
1112
+ const scriptsDir = (0, import_node_path3.join)(dir, "scripts");
1326
1113
  if (!(0, import_node_fs.existsSync)(scriptsDir)) {
1327
1114
  (0, import_node_fs.mkdirSync)(scriptsDir, { recursive: true });
1328
1115
  }
1329
- const automationTestsDir = (0, import_node_path4.join)(testsDir, "automation-exercise");
1116
+ const automationTestsDir = (0, import_node_path3.join)(testsDir, "automation-exercise");
1330
1117
  if (!(0, import_node_fs.existsSync)(automationTestsDir)) {
1331
1118
  (0, import_node_fs.mkdirSync)(automationTestsDir, { recursive: true });
1332
1119
  }
1333
1120
  for (const test of AUTOMATION_EXERCISE_TESTS) {
1334
- const testPath = (0, import_node_path4.join)(automationTestsDir, test.fileName);
1121
+ const testPath = (0, import_node_path3.join)(automationTestsDir, test.fileName);
1335
1122
  (0, import_node_fs.writeFileSync)(testPath, buildAutomationExerciseTest(test));
1336
- console.log(import_picocolors4.default.green(`\u2713 Created ${testPath}`));
1123
+ console.log(import_picocolors3.default.green(`\u2713 Created ${testPath}`));
1337
1124
  }
1338
- const automationSuitePath = (0, import_node_path4.join)(suitesDir, "automation-exercise.suite.yaml");
1125
+ const automationSuitePath = (0, import_node_path3.join)(suitesDir, "automation-exercise.suite.yaml");
1339
1126
  (0, import_node_fs.writeFileSync)(automationSuitePath, buildAutomationExerciseSuite());
1340
- console.log(import_picocolors4.default.green(`\u2713 Created ${automationSuitePath}`));
1341
- const hnScriptPath = (0, import_node_path4.join)(scriptsDir, "fetch-hn-top-story.mjs");
1127
+ console.log(import_picocolors3.default.green(`\u2713 Created ${automationSuitePath}`));
1128
+ const hnScriptPath = (0, import_node_path3.join)(scriptsDir, "fetch-hn-top-story.mjs");
1342
1129
  (0, import_node_fs.writeFileSync)(hnScriptPath, buildHackerNewsHookScript());
1343
- console.log(import_picocolors4.default.green(`\u2713 Created ${hnScriptPath}`));
1344
- const hnTestPath = (0, import_node_path4.join)(testsDir, "hacker-news-top-story.yaml");
1130
+ console.log(import_picocolors3.default.green(`\u2713 Created ${hnScriptPath}`));
1131
+ const hnTestPath = (0, import_node_path3.join)(testsDir, "hacker-news-top-story.yaml");
1345
1132
  (0, import_node_fs.writeFileSync)(hnTestPath, buildHackerNewsTopStoryTest());
1346
- console.log(import_picocolors4.default.green(`\u2713 Created ${hnTestPath}`));
1133
+ console.log(import_picocolors3.default.green(`\u2713 Created ${hnTestPath}`));
1347
1134
  }
1348
1135
  appendGitignore(dir);
1349
- console.log(import_picocolors4.default.green("\u2713 Updated .gitignore"));
1350
- if (!skipInstall) {
1351
- installPlaywrightBrowsers(platform);
1352
- installAppium(platform);
1353
- }
1136
+ console.log(import_picocolors3.default.green("\u2713 Updated .gitignore"));
1354
1137
  console.log("");
1355
- console.log(import_picocolors4.default.green(import_picocolors4.default.bold("\u2713 agent-qa project initialized!")));
1138
+ console.log(import_picocolors3.default.green(import_picocolors3.default.bold("\u2713 agent-qa project initialized!")));
1356
1139
  console.log("");
1357
1140
  console.log(" Created:");
1358
- console.log(` ${import_picocolors4.default.dim("\u2022")} agent-qa.config.yaml`);
1141
+ console.log(` ${import_picocolors3.default.dim("\u2022")} agent-qa.config.yaml`);
1359
1142
  if (subscriptionAuthDependency) {
1360
- console.log(` ${import_picocolors4.default.dim("\u2022")} ${PACKAGE_JSON_FILE} dependency for ${SUBSCRIPTION_AUTH_PACKAGE}@${subscriptionAuthDependency.range}`);
1361
- }
1362
- console.log(` ${import_picocolors4.default.dim("\u2022")} ${DEFAULT_HOOKS_FILE}`);
1363
- console.log(` ${import_picocolors4.default.dim("\u2022")} ${DEFAULT_AGENT_RULES_FILE}`);
1364
- console.log(` ${import_picocolors4.default.dim("\u2022")} ${DEFAULT_ENV_FILE}`);
1365
- console.log(` ${import_picocolors4.default.dim("\u2022")} ${DEFAULT_SECRETS_FILE}`);
1366
- console.log(` ${import_picocolors4.default.dim("\u2022")} ${DEFAULT_LOCAL_CONFIG_FILE}`);
1367
- console.log(` ${import_picocolors4.default.dim("\u2022")} tests/example-pass.yaml`);
1368
- console.log(` ${import_picocolors4.default.dim("\u2022")} tests/example-fail.yaml`);
1143
+ console.log(` ${import_picocolors3.default.dim("\u2022")} ${PACKAGE_JSON_FILE} dependency for ${SUBSCRIPTION_AUTH_PACKAGE}@${subscriptionAuthDependency.range}`);
1144
+ }
1145
+ console.log(` ${import_picocolors3.default.dim("\u2022")} ${DEFAULT_HOOKS_FILE}`);
1146
+ console.log(` ${import_picocolors3.default.dim("\u2022")} ${DEFAULT_AGENT_RULES_FILE}`);
1147
+ console.log(` ${import_picocolors3.default.dim("\u2022")} ${DEFAULT_ENV_FILE}`);
1148
+ console.log(` ${import_picocolors3.default.dim("\u2022")} ${DEFAULT_SECRETS_FILE}`);
1149
+ console.log(` ${import_picocolors3.default.dim("\u2022")} ${DEFAULT_LOCAL_CONFIG_FILE}`);
1150
+ console.log(` ${import_picocolors3.default.dim("\u2022")} tests/example-pass.yaml`);
1151
+ console.log(` ${import_picocolors3.default.dim("\u2022")} tests/example-fail.yaml`);
1369
1152
  if (hasWeb(platform)) {
1370
- console.log(` ${import_picocolors4.default.dim("\u2022")} tests/automation-exercise/*.yaml`);
1371
- console.log(` ${import_picocolors4.default.dim("\u2022")} tests/hacker-news-top-story.yaml`);
1372
- console.log(` ${import_picocolors4.default.dim("\u2022")} suites/automation-exercise.suite.yaml`);
1373
- console.log(` ${import_picocolors4.default.dim("\u2022")} scripts/fetch-hn-top-story.mjs`);
1153
+ console.log(` ${import_picocolors3.default.dim("\u2022")} tests/automation-exercise/*.yaml`);
1154
+ console.log(` ${import_picocolors3.default.dim("\u2022")} tests/hacker-news-top-story.yaml`);
1155
+ console.log(` ${import_picocolors3.default.dim("\u2022")} suites/automation-exercise.suite.yaml`);
1156
+ console.log(` ${import_picocolors3.default.dim("\u2022")} scripts/fetch-hn-top-story.mjs`);
1374
1157
  }
1375
- console.log(` ${import_picocolors4.default.dim("\u2022")} .gitignore entries`);
1158
+ console.log(` ${import_picocolors3.default.dim("\u2022")} .gitignore entries`);
1376
1159
  console.log("");
1377
1160
  console.log(" Next steps:");
1378
- console.log(` 1. ${import_picocolors4.default.cyan("agent-qa doctor")} Verify your environment`);
1379
- console.log(` 2. Edit ${import_picocolors4.default.cyan("tests/example-pass.yaml")} with your first test`);
1380
- console.log(` 3. ${import_picocolors4.default.cyan("agent-qa run")} Run all tests`);
1381
- console.log(` 4. ${import_picocolors4.default.cyan("agent-qa dashboard")} View results in browser`);
1382
- if (skipInstall && hasWeb(platform)) {
1383
- console.log(` ${import_picocolors4.default.dim("\u2022")} Install browser support with ${import_picocolors4.default.cyan("agent-qa install-browsers --chromium")}`);
1161
+ for (const [index, step] of initNextSteps(platform).entries()) {
1162
+ console.log(` ${index + 1}. ${import_picocolors3.default.cyan(step.command)} ${step.description}`);
1384
1163
  }
1385
1164
  const generatedLLMs = config.registry?.llms ?? [];
1386
1165
  const subscriptionLLMs = generatedLLMs.filter((llm) => isSubscriptionProvider(String(llm.provider)));
1387
1166
  if (subscriptionLLMs.length === 0) {
1388
- console.log(` ${import_picocolors4.default.dim("\u2022")} Save credentials with ${import_picocolors4.default.cyan("agent-qa auth set --config default --type api-key")}`);
1167
+ console.log(` ${import_picocolors3.default.dim("\u2022")} Save credentials with ${import_picocolors3.default.cyan("agent-qa auth set --config default --type api-key")}`);
1389
1168
  if (provider === "anthropic-compatible") {
1390
- console.log(` ${import_picocolors4.default.dim("\u2022")} Bearer tokens use ${import_picocolors4.default.cyan("agent-qa auth set --config default --type bearer-token")}`);
1169
+ console.log(` ${import_picocolors3.default.dim("\u2022")} Bearer tokens use ${import_picocolors3.default.cyan("agent-qa auth set --config default --type bearer-token")}`);
1391
1170
  }
1392
1171
  } else {
1393
- console.log(` ${import_picocolors4.default.dim("\u2022")} Fetch ${SUBSCRIPTION_AUTH_PACKAGE} with your package manager install command`);
1172
+ console.log(` ${import_picocolors3.default.dim("\u2022")} Fetch ${SUBSCRIPTION_AUTH_PACKAGE} with your package manager install command`);
1394
1173
  for (const llm of subscriptionLLMs) {
1395
- console.log(` ${import_picocolors4.default.dim("\u2022")} Authenticate ${import_picocolors4.default.cyan(String(llm.name))} from ${import_picocolors4.default.cyan("agent-qa dashboard")}`);
1174
+ console.log(` ${import_picocolors3.default.dim("\u2022")} Authenticate ${import_picocolors3.default.cyan(String(llm.name))} from ${import_picocolors3.default.cyan("agent-qa dashboard")}`);
1396
1175
  }
1397
1176
  }
1398
1177
  console.log("");
@@ -1400,20 +1179,369 @@ function createInitCommand() {
1400
1179
  return cmd;
1401
1180
  }
1402
1181
 
1403
- // src/commands/doctor.ts
1182
+ // src/commands/install-browsers.ts
1183
+ var import_node_child_process = require("child_process");
1184
+ var import_node_module = require("module");
1185
+ var import_node_path4 = require("path");
1186
+ var import_commander3 = require("commander");
1187
+ var import_picocolors4 = __toESM(require("picocolors"), 1);
1188
+ var import_meta2 = {};
1189
+ function selectedBrowserCount(selection) {
1190
+ return [selection.chromium, selection.firefox, selection.webkit].filter(Boolean).length;
1191
+ }
1192
+ function validateBrowserInstallSelection(selection) {
1193
+ if (!selection.all && selectedBrowserCount(selection) === 0) {
1194
+ return "Select at least one browser flag: --all, --chromium, --firefox, or --webkit.";
1195
+ }
1196
+ if (selection.all && selectedBrowserCount(selection) > 0) {
1197
+ return "Cannot combine --all with --chromium, --firefox, or --webkit.";
1198
+ }
1199
+ return null;
1200
+ }
1201
+ function buildBrowserInstallArgs(selection) {
1202
+ const args = [];
1203
+ if (selection.withDeps) args.push("--with-deps");
1204
+ if (selection.force) args.push("--force");
1205
+ if (!selection.all) {
1206
+ if (selection.chromium) args.push("chromium");
1207
+ if (selection.firefox) args.push("firefox");
1208
+ if (selection.webkit) args.push("webkit");
1209
+ }
1210
+ return args;
1211
+ }
1212
+ function formatInstallBrowsersRetryCommand(selection) {
1213
+ const args = ["agent-qa", "install-browsers"];
1214
+ if (selection.all) {
1215
+ args.push("--all");
1216
+ } else {
1217
+ if (selection.chromium) args.push("--chromium");
1218
+ if (selection.firefox) args.push("--firefox");
1219
+ if (selection.webkit) args.push("--webkit");
1220
+ }
1221
+ if (selection.withDeps) args.push("--with-deps");
1222
+ if (selection.force) args.push("--force");
1223
+ return args.join(" ");
1224
+ }
1225
+ function resolvePlaywrightCoreCli() {
1226
+ const require2 = (0, import_node_module.createRequire)(import_meta2.url);
1227
+ const webEntry = require2.resolve("@vostride/agent-qa-web");
1228
+ const webRequire = (0, import_node_module.createRequire)(webEntry);
1229
+ const packagePath = webRequire.resolve("playwright-core/package.json");
1230
+ return (0, import_node_path4.join)((0, import_node_path4.dirname)(packagePath), "cli.js");
1231
+ }
1232
+ function normalizeSpawnStatus(result) {
1233
+ if (typeof result.status === "number") return result.status;
1234
+ return 1;
1235
+ }
1236
+ function runBrowserInstall(selection, deps = {}) {
1237
+ let cliPath;
1238
+ try {
1239
+ cliPath = (deps.resolveCli ?? resolvePlaywrightCoreCli)();
1240
+ } catch (error) {
1241
+ return { ok: false, status: 1, stage: "resolve", error };
1242
+ }
1243
+ const spawn = deps.spawn ?? import_node_child_process.spawnSync;
1244
+ const result = spawn(process.execPath, [cliPath, "install", ...buildBrowserInstallArgs(selection)], {
1245
+ stdio: "inherit"
1246
+ });
1247
+ if (result.error) {
1248
+ return { ok: false, status: normalizeSpawnStatus(result), stage: "spawn", error: result.error };
1249
+ }
1250
+ const status = normalizeSpawnStatus(result);
1251
+ return { ok: status === 0, status, stage: "installer" };
1252
+ }
1253
+ function printBrowserInstallFailure(selection, result) {
1254
+ if (result.stage === "resolve") {
1255
+ console.error(import_picocolors4.default.red("agent-qa could not find its bundled browser installer."));
1256
+ console.error(import_picocolors4.default.dim("Reinstall agent-qa dependencies, then retry:"));
1257
+ } else {
1258
+ console.error(import_picocolors4.default.red("Browser installation failed."));
1259
+ console.error(import_picocolors4.default.dim("Retry:"));
1260
+ }
1261
+ console.error(` ${import_picocolors4.default.cyan(formatInstallBrowsersRetryCommand(selection))}`);
1262
+ }
1263
+ function createInstallBrowsersCommand(deps = {}) {
1264
+ const cmd = new import_commander3.Command("install-browsers").description("Install browser support for agent-qa").option("--all", "install all Playwright-managed browsers for agent-qa").option("--chromium", "install Chromium browser support").option("--firefox", "install Firefox browser support").option("--webkit", "install WebKit browser support").option("--with-deps", "also install required system dependencies").option("--force", "force reinstall even if browser support already exists").action((opts) => {
1265
+ const selection = {
1266
+ all: !!opts.all,
1267
+ chromium: !!opts.chromium,
1268
+ firefox: !!opts.firefox,
1269
+ webkit: !!opts.webkit,
1270
+ withDeps: !!opts.withDeps,
1271
+ force: !!opts.force
1272
+ };
1273
+ const validationError = validateBrowserInstallSelection(selection);
1274
+ if (validationError) {
1275
+ console.error(import_picocolors4.default.red(`Error: ${validationError}`));
1276
+ console.error(cmd.helpInformation());
1277
+ process.exitCode = 1;
1278
+ return;
1279
+ }
1280
+ console.log(import_picocolors4.default.blue("Installing agent-qa browser support..."));
1281
+ const result = runBrowserInstall(selection, deps);
1282
+ if (result.ok) {
1283
+ console.log(import_picocolors4.default.green("\u2713 agent-qa browser support installed"));
1284
+ return;
1285
+ }
1286
+ printBrowserInstallFailure(selection, result);
1287
+ process.exitCode = result.status || 1;
1288
+ });
1289
+ return cmd;
1290
+ }
1291
+
1292
+ // src/commands/install-mobile-drivers.ts
1293
+ var import_node_child_process2 = require("child_process");
1294
+ var import_node_path5 = require("path");
1404
1295
  var import_commander4 = require("commander");
1296
+ var import_picocolors5 = __toESM(require("picocolors"), 1);
1297
+ var import_agent_qa_core5 = require("@vostride/agent-qa-core");
1298
+ var MOBILE_DRIVERS = [
1299
+ { target: "android", driver: "uiautomator2", label: "UiAutomator2" },
1300
+ { target: "ios", driver: "xcuitest", label: "XCUITest" }
1301
+ ];
1302
+ function selectedTargetCount(selection) {
1303
+ return [selection.android, selection.ios].filter(Boolean).length;
1304
+ }
1305
+ function selectedDrivers(selection) {
1306
+ if (selection.all) return MOBILE_DRIVERS;
1307
+ return MOBILE_DRIVERS.filter((driver) => selection[driver.target]);
1308
+ }
1309
+ function validateMobileDriverInstallSelection(selection) {
1310
+ if (!selection.all && selectedTargetCount(selection) === 0) {
1311
+ return "Select at least one mobile platform flag: --all, --android, or --ios.";
1312
+ }
1313
+ if (selection.all && selectedTargetCount(selection) > 0) {
1314
+ return "Cannot combine --all with --android or --ios.";
1315
+ }
1316
+ if (selection.unsafe && !selection.update) {
1317
+ return "Cannot use --unsafe without --update.";
1318
+ }
1319
+ return null;
1320
+ }
1321
+ function formatInstallMobileDriversRetryCommand(selection) {
1322
+ const args = ["agent-qa", "install-mobile-drivers"];
1323
+ if (selection.all) {
1324
+ args.push("--all");
1325
+ } else {
1326
+ if (selection.android) args.push("--android");
1327
+ if (selection.ios) args.push("--ios");
1328
+ }
1329
+ if (selection.update) args.push("--update");
1330
+ if (selection.unsafe) args.push("--unsafe");
1331
+ return args.join(" ");
1332
+ }
1333
+ function collectDriverNames(value, names) {
1334
+ if (!value) return;
1335
+ if (typeof value === "string") {
1336
+ names.add(value);
1337
+ return;
1338
+ }
1339
+ if (Array.isArray(value)) {
1340
+ for (const item of value) collectDriverNames(item, names);
1341
+ return;
1342
+ }
1343
+ if (typeof value !== "object") return;
1344
+ const record = value;
1345
+ if (typeof record.name === "string") names.add(record.name);
1346
+ if (typeof record.driverName === "string") names.add(record.driverName);
1347
+ if (typeof record.automationName === "string") names.add(record.automationName);
1348
+ for (const [key, child] of Object.entries(record)) {
1349
+ if (key !== "drivers" && key !== "installed" && key !== "name" && key !== "driverName" && typeof child === "object" && child !== null) {
1350
+ names.add(key);
1351
+ }
1352
+ collectDriverNames(child, names);
1353
+ }
1354
+ }
1355
+ function parseInstalledAppiumDrivers(output) {
1356
+ const names = /* @__PURE__ */ new Set();
1357
+ const trimmed = output.trim();
1358
+ if (!trimmed) return names;
1359
+ try {
1360
+ collectDriverNames(JSON.parse(trimmed), names);
1361
+ const normalizedNames = /* @__PURE__ */ new Set();
1362
+ for (const name of names) {
1363
+ const normalized = name.toLowerCase();
1364
+ normalizedNames.add(normalized);
1365
+ if (normalized.includes("uiautomator2")) normalizedNames.add("uiautomator2");
1366
+ if (normalized.includes("xcuitest")) normalizedNames.add("xcuitest");
1367
+ }
1368
+ return normalizedNames;
1369
+ } catch {
1370
+ for (const line of trimmed.split("\n")) {
1371
+ const normalized = line.toLowerCase();
1372
+ if (normalized.includes("uiautomator2")) names.add("uiautomator2");
1373
+ if (normalized.includes("xcuitest")) names.add("xcuitest");
1374
+ }
1375
+ return names;
1376
+ }
1377
+ }
1378
+ function errorStatus(error) {
1379
+ const status = error?.status;
1380
+ return typeof status === "number" ? status : 1;
1381
+ }
1382
+ function isMissingExecutableError(error) {
1383
+ return error?.code === "ENOENT";
1384
+ }
1385
+ function isAlreadyInstalledDriverError(error, driver) {
1386
+ const details = [
1387
+ error instanceof Error ? error.message : String(error),
1388
+ error?.stdout,
1389
+ error?.stderr
1390
+ ].map(String).join("\n").toLowerCase();
1391
+ return details.includes(`driver named "${driver}" is already installed`) || details.includes(`${driver}" is already installed`) || details.includes(`${driver} is already installed`);
1392
+ }
1393
+ function getInstalledAppiumDrivers(appiumCommand, execFile) {
1394
+ try {
1395
+ const output = execFile(appiumCommand, ["driver", "list", "--installed", "--json"], { stdio: "pipe", encoding: "utf-8" });
1396
+ return { ok: true, drivers: parseInstalledAppiumDrivers(String(output)) };
1397
+ } catch {
1398
+ try {
1399
+ const output = execFile(appiumCommand, ["driver", "list", "--installed"], { stdio: "pipe", encoding: "utf-8" });
1400
+ return { ok: true, drivers: parseInstalledAppiumDrivers(String(output)) };
1401
+ } catch (error) {
1402
+ return { ok: false, error, status: errorStatus(error) };
1403
+ }
1404
+ }
1405
+ }
1406
+ function driverOperationArgs(driver, selection, installed) {
1407
+ if (installed && !selection.update) {
1408
+ return { action: "reuse", args: null };
1409
+ }
1410
+ if (installed && selection.update) {
1411
+ const args = ["driver", "update", driver];
1412
+ if (selection.unsafe) args.push("--unsafe");
1413
+ return { action: "update", args };
1414
+ }
1415
+ return { action: "install", args: ["driver", "install", driver] };
1416
+ }
1417
+ function runMobileDriverInstall(selection, deps = {}) {
1418
+ const resolveAppium = deps.resolveAppium ?? import_agent_qa_core5.resolveAppiumExecutable;
1419
+ const execFile = deps.execFile ?? import_node_child_process2.execFileSync;
1420
+ const events = [];
1421
+ let appium;
1422
+ try {
1423
+ appium = resolveAppium({ cwd: selection.cwd ?? process.cwd() });
1424
+ } catch (error) {
1425
+ return { ok: false, status: 1, stage: "resolve", events, error };
1426
+ }
1427
+ try {
1428
+ execFile(appium.command, ["--version"], { stdio: "pipe" });
1429
+ } catch (error) {
1430
+ return { ok: false, status: errorStatus(error), stage: "check", appium, events, error };
1431
+ }
1432
+ const installedResult = getInstalledAppiumDrivers(appium.command, execFile);
1433
+ if (!installedResult.ok) {
1434
+ return { ok: false, status: installedResult.status, stage: "list", appium, events, error: installedResult.error };
1435
+ }
1436
+ let status = 0;
1437
+ for (const definition of selectedDrivers(selection)) {
1438
+ const installed = installedResult.drivers.has(definition.driver);
1439
+ const operation = driverOperationArgs(definition.driver, selection, installed);
1440
+ if (!operation.args) {
1441
+ events.push({ driver: definition.driver, label: definition.label, action: operation.action, ok: true });
1442
+ continue;
1443
+ }
1444
+ try {
1445
+ execFile(appium.command, operation.args, { stdio: "inherit" });
1446
+ events.push({ driver: definition.driver, label: definition.label, action: operation.action, ok: true });
1447
+ } catch (error) {
1448
+ if (operation.action === "install" && isAlreadyInstalledDriverError(error, definition.driver)) {
1449
+ events.push({ driver: definition.driver, label: definition.label, action: "reuse", ok: true });
1450
+ continue;
1451
+ }
1452
+ status = status || errorStatus(error);
1453
+ events.push({ driver: definition.driver, label: definition.label, action: operation.action, ok: false, error });
1454
+ }
1455
+ }
1456
+ return { ok: status === 0, status, stage: "driver", appium, events };
1457
+ }
1458
+ function resolveCommandCwd(command) {
1459
+ const configPath = command.parent?.opts().config ?? "agent-qa.config.yaml";
1460
+ return (0, import_node_path5.dirname)((0, import_node_path5.resolve)(configPath));
1461
+ }
1462
+ function printMobileDriverInstallResult(selection, result) {
1463
+ if (result.stage === "check" && isMissingExecutableError(result.error)) {
1464
+ console.error(import_picocolors5.default.red("Appium not found."));
1465
+ console.error(import_picocolors5.default.dim((0, import_agent_qa_core5.formatAppiumInstallGuidance)()));
1466
+ return;
1467
+ }
1468
+ if (result.stage === "resolve") {
1469
+ console.error(import_picocolors5.default.red("agent-qa could not resolve an Appium executable."));
1470
+ console.error(import_picocolors5.default.dim((0, import_agent_qa_core5.formatAppiumInstallGuidance)()));
1471
+ return;
1472
+ }
1473
+ if (result.stage === "check") {
1474
+ console.error(import_picocolors5.default.red("Could not run Appium."));
1475
+ console.error(import_picocolors5.default.dim((0, import_agent_qa_core5.formatAppiumInstallGuidance)()));
1476
+ return;
1477
+ }
1478
+ if (result.stage === "list") {
1479
+ console.error(import_picocolors5.default.red("Could not inspect installed Appium drivers."));
1480
+ console.error(import_picocolors5.default.dim("Retry:"));
1481
+ console.error(` ${import_picocolors5.default.cyan(formatInstallMobileDriversRetryCommand(selection))}`);
1482
+ return;
1483
+ }
1484
+ for (const event of result.events) {
1485
+ if (event.ok && event.action === "reuse") {
1486
+ console.log(import_picocolors5.default.green(`\u2713 ${event.label} driver already installed`));
1487
+ } else if (event.ok && event.action === "install") {
1488
+ console.log(import_picocolors5.default.green(`\u2713 ${event.label} driver installed`));
1489
+ } else if (event.ok && event.action === "update") {
1490
+ console.log(import_picocolors5.default.green(`\u2713 ${event.label} driver updated`));
1491
+ } else {
1492
+ const action = event.action === "update" ? "update" : "installation";
1493
+ console.error(import_picocolors5.default.red(`\u2717 ${event.label} driver ${action} failed`));
1494
+ }
1495
+ }
1496
+ if (!result.ok) {
1497
+ console.error(import_picocolors5.default.dim("Retry:"));
1498
+ console.error(` ${import_picocolors5.default.cyan(formatInstallMobileDriversRetryCommand(selection))}`);
1499
+ return;
1500
+ }
1501
+ console.log(import_picocolors5.default.green("\u2713 agent-qa mobile driver support ready"));
1502
+ }
1503
+ function createInstallMobileDriversCommand(deps = {}) {
1504
+ const cmd = new import_commander4.Command("install-mobile-drivers").description("Install Appium mobile drivers for agent-qa").option("--all", "install Android and iOS Appium drivers").option("--android", "install the UiAutomator2 Appium driver").option("--ios", "install the XCUITest Appium driver").option("--update", "update selected drivers when they are already installed").option("--unsafe", "allow Appium driver updates across major versions; requires --update").action((opts) => {
1505
+ const selection = {
1506
+ all: !!opts.all,
1507
+ android: !!opts.android,
1508
+ ios: !!opts.ios,
1509
+ update: !!opts.update,
1510
+ unsafe: !!opts.unsafe,
1511
+ cwd: resolveCommandCwd(cmd)
1512
+ };
1513
+ const validationError = validateMobileDriverInstallSelection(selection);
1514
+ if (validationError) {
1515
+ console.error(import_picocolors5.default.red(`Error: ${validationError}`));
1516
+ console.error(cmd.helpInformation());
1517
+ process.exitCode = 1;
1518
+ return;
1519
+ }
1520
+ console.log(import_picocolors5.default.blue("Installing agent-qa mobile driver support..."));
1521
+ const result = runMobileDriverInstall(selection, deps);
1522
+ printMobileDriverInstallResult(selection, result);
1523
+ if (!result.ok) {
1524
+ process.exitCode = result.status || 1;
1525
+ }
1526
+ });
1527
+ return cmd;
1528
+ }
1529
+
1530
+ // src/commands/doctor.ts
1531
+ var import_commander5 = require("commander");
1405
1532
  var import_node_child_process3 = require("child_process");
1406
1533
  var import_node_fs2 = require("fs");
1407
- var import_picocolors5 = __toESM(require("picocolors"), 1);
1534
+ var import_node_path7 = require("path");
1535
+ var import_picocolors6 = __toESM(require("picocolors"), 1);
1408
1536
  init_config();
1409
1537
  init_llm_utils();
1410
1538
  init_model_defaults();
1411
- var import_agent_qa_core5 = require("@vostride/agent-qa-core");
1539
+ var import_agent_qa_core6 = require("@vostride/agent-qa-core");
1412
1540
  function resolveDoctorWorkspace(config, configPath) {
1413
1541
  try {
1414
1542
  return {
1415
1543
  ok: true,
1416
- workspace: (0, import_agent_qa_core5.resolveWorkspacePaths)({
1544
+ workspace: (0, import_agent_qa_core6.resolveWorkspacePaths)({
1417
1545
  config,
1418
1546
  configPath
1419
1547
  })
@@ -1467,20 +1595,20 @@ function applyResolvedAuth(llm, auth) {
1467
1595
  function formatStatus(status) {
1468
1596
  switch (status) {
1469
1597
  case "pass":
1470
- return import_picocolors5.default.green("PASS");
1598
+ return import_picocolors6.default.green("PASS");
1471
1599
  case "warn":
1472
- return import_picocolors5.default.yellow("WARN");
1600
+ return import_picocolors6.default.yellow("WARN");
1473
1601
  case "fail":
1474
- return import_picocolors5.default.red("FAIL");
1602
+ return import_picocolors6.default.red("FAIL");
1475
1603
  case "skip":
1476
- return import_picocolors5.default.dim("SKIP");
1604
+ return import_picocolors6.default.dim("SKIP");
1477
1605
  }
1478
1606
  }
1479
1607
  function padDots(name, message) {
1480
1608
  const totalWidth = 60;
1481
1609
  const used = name.length + message.length;
1482
1610
  const dots = Math.max(2, totalWidth - used);
1483
- return import_picocolors5.default.dim(".".repeat(dots));
1611
+ return import_picocolors6.default.dim(".".repeat(dots));
1484
1612
  }
1485
1613
  function checkNodeVersion() {
1486
1614
  return {
@@ -1710,7 +1838,7 @@ function checkLLMConnection(config) {
1710
1838
  function checkPlaywright(config) {
1711
1839
  return {
1712
1840
  name: "Playwright",
1713
- fixInstructions: "Run `agent-qa install-browsers --chromium` or `agent-qa init`",
1841
+ fixInstructions: "Run `agent-qa install-browsers --chromium`",
1714
1842
  check: async () => {
1715
1843
  const browsers = config?.browsers;
1716
1844
  const hasBrowsers = browsers && browsers.length > 0;
@@ -1726,16 +1854,28 @@ function checkPlaywright(config) {
1726
1854
  }
1727
1855
  };
1728
1856
  }
1729
- function checkAppium(config) {
1857
+ function appiumResolverCwd(configPath) {
1858
+ return (0, import_node_path7.dirname)((0, import_node_path7.resolve)(configPath));
1859
+ }
1860
+ function mobileDriverInstallCommand(config) {
1861
+ const hasAndroid = hasAndroidPlatform(config);
1862
+ const hasIOS = hasIOSPlatform(config);
1863
+ if (hasAndroid && hasIOS) return "agent-qa install-mobile-drivers --all";
1864
+ if (hasAndroid) return "agent-qa install-mobile-drivers --android";
1865
+ if (hasIOS) return "agent-qa install-mobile-drivers --ios";
1866
+ return "agent-qa install-mobile-drivers --all";
1867
+ }
1868
+ function checkAppium(config, configPath) {
1730
1869
  return {
1731
1870
  name: "Appium",
1732
- fixInstructions: "Run `npm install -g appium`",
1871
+ fixInstructions: `${(0, import_agent_qa_core6.formatAppiumInstallGuidance)()} Then run \`${mobileDriverInstallCommand(config)}\`.`,
1733
1872
  check: async () => {
1734
1873
  if (!isMobilePlatform(config)) {
1735
1874
  return { status: "skip", message: "not configured" };
1736
1875
  }
1737
1876
  try {
1738
- const version = (0, import_node_child_process3.execSync)("appium --version", { stdio: "pipe" }).toString().trim();
1877
+ const appium = (0, import_agent_qa_core6.resolveAppiumExecutable)({ cwd: appiumResolverCwd(configPath) });
1878
+ const version = (0, import_node_child_process3.execFileSync)(appium.command, ["--version"], { stdio: "pipe" }).toString().trim();
1739
1879
  return { status: "pass", message: `v${version}` };
1740
1880
  } catch {
1741
1881
  return { status: "fail", message: "not installed" };
@@ -1743,16 +1883,17 @@ function checkAppium(config) {
1743
1883
  }
1744
1884
  };
1745
1885
  }
1746
- function checkAppiumDrivers(config) {
1886
+ function checkAppiumDrivers(config, configPath) {
1747
1887
  return {
1748
1888
  name: "Appium drivers",
1749
- fixInstructions: "Run `appium driver install uiautomator2` or `appium driver install xcuitest`",
1889
+ fixInstructions: `Run \`${mobileDriverInstallCommand(config)}\``,
1750
1890
  check: async () => {
1751
1891
  if (!isMobilePlatform(config)) {
1752
1892
  return { status: "skip", message: "not configured" };
1753
1893
  }
1754
1894
  try {
1755
- const output = (0, import_node_child_process3.execSync)("appium driver list --installed", { stdio: "pipe" }).toString();
1895
+ const appium = (0, import_agent_qa_core6.resolveAppiumExecutable)({ cwd: appiumResolverCwd(configPath) });
1896
+ const output = (0, import_node_child_process3.execFileSync)(appium.command, ["driver", "list", "--installed"], { stdio: "pipe" }).toString();
1756
1897
  const hasDrivers = output.includes("uiautomator2") || output.includes("xcuitest");
1757
1898
  if (hasDrivers) {
1758
1899
  return { status: "pass", message: "drivers installed" };
@@ -1847,8 +1988,8 @@ function checkTestDiscovery(config, configPath) {
1847
1988
  }
1848
1989
  const parts = [];
1849
1990
  const [testFiles, suiteFiles] = await Promise.all([
1850
- (0, import_agent_qa_core5.discoverWorkspaceFiles)({ workspace: resolved.workspace, kind: "test" }),
1851
- (0, import_agent_qa_core5.discoverWorkspaceFiles)({ workspace: resolved.workspace, kind: "suite" })
1991
+ (0, import_agent_qa_core6.discoverWorkspaceFiles)({ workspace: resolved.workspace, kind: "test" }),
1992
+ (0, import_agent_qa_core6.discoverWorkspaceFiles)({ workspace: resolved.workspace, kind: "suite" })
1852
1993
  ]);
1853
1994
  parts.push(`${testFiles.length} test file(s)`);
1854
1995
  parts.push(`${suiteFiles.length} suite file(s)`);
@@ -1888,7 +2029,7 @@ function getGlobalConfigPath(command) {
1888
2029
  return command.parent?.opts().config ?? "agent-qa.config.yaml";
1889
2030
  }
1890
2031
  function createDoctorCommand() {
1891
- const cmd = new import_commander4.Command("doctor").description("Validate environment and dependencies").action(async function() {
2032
+ const cmd = new import_commander5.Command("doctor").description("Validate environment and dependencies").action(async function() {
1892
2033
  const configPath = getGlobalConfigPath(this);
1893
2034
  let config = null;
1894
2035
  try {
@@ -1910,15 +2051,15 @@ function createDoctorCommand() {
1910
2051
  checkSubscriptionAuth(config),
1911
2052
  checkLLMConnection(config),
1912
2053
  checkPlaywright(config),
1913
- checkAppium(config),
1914
- checkAppiumDrivers(config),
2054
+ checkAppium(config, configPath),
2055
+ checkAppiumDrivers(config, configPath),
1915
2056
  checkAndroidSDK(config),
1916
2057
  checkXcode(config),
1917
2058
  checkTestDiscovery(config, configPath),
1918
2059
  checkValidation(config, configPath)
1919
2060
  ];
1920
2061
  console.log("");
1921
- console.log(import_picocolors5.default.bold(" agent-qa doctor"));
2062
+ console.log(import_picocolors6.default.bold(" agent-qa doctor"));
1922
2063
  console.log("");
1923
2064
  let hasFailure = false;
1924
2065
  const results = [];
@@ -1929,16 +2070,16 @@ function createDoctorCommand() {
1929
2070
  const status = formatStatus(result.status);
1930
2071
  console.log(` ${check.name} ${dots} ${result.message} ${status}`);
1931
2072
  if (result.status === "fail") {
1932
- console.log(import_picocolors5.default.dim(` Fix: ${check.fixInstructions}`));
2073
+ console.log(import_picocolors6.default.dim(` Fix: ${check.fixInstructions}`));
1933
2074
  hasFailure = true;
1934
2075
  }
1935
2076
  }
1936
2077
  console.log("");
1937
2078
  if (hasFailure) {
1938
- console.log(import_picocolors5.default.red(" Some checks failed. See fix instructions above."));
2079
+ console.log(import_picocolors6.default.red(" Some checks failed. See fix instructions above."));
1939
2080
  process.exitCode = 1;
1940
2081
  } else {
1941
- console.log(import_picocolors5.default.green(" All checks passed! Your environment is ready."));
2082
+ console.log(import_picocolors6.default.green(" All checks passed! Your environment is ready."));
1942
2083
  }
1943
2084
  console.log("");
1944
2085
  });
@@ -1947,16 +2088,16 @@ function createDoctorCommand() {
1947
2088
 
1948
2089
  // src/commands/run.ts
1949
2090
  var import_node_crypto = require("crypto");
1950
- var import_node_path7 = require("path");
1951
- var import_commander5 = require("commander");
1952
- var import_picocolors6 = __toESM(require("picocolors"), 1);
2091
+ var import_node_path9 = require("path");
2092
+ var import_commander6 = require("commander");
2093
+ var import_picocolors7 = __toESM(require("picocolors"), 1);
1953
2094
  init_config();
1954
- var import_agent_qa_core6 = require("@vostride/agent-qa-core");
1955
2095
  var import_agent_qa_core7 = require("@vostride/agent-qa-core");
2096
+ var import_agent_qa_core8 = require("@vostride/agent-qa-core");
1956
2097
 
1957
2098
  // src/devices.ts
1958
2099
  var import_node_fs3 = require("fs");
1959
- var import_node_path6 = require("path");
2100
+ var import_node_path8 = require("path");
1960
2101
  var import_yaml4 = require("yaml");
1961
2102
  var import_zod = require("zod");
1962
2103
  var LocalAppBindingSchema = import_zod.z.object({
@@ -1989,7 +2130,7 @@ var DISPLAY_NAMES = {
1989
2130
  browserstack: "BrowserStack"
1990
2131
  };
1991
2132
  function loadLocalBindings(dir) {
1992
- const filePath = (0, import_node_path6.join)(dir ?? process.cwd(), "agent-qa.local.yaml");
2133
+ const filePath = (0, import_node_path8.join)(dir ?? process.cwd(), "agent-qa.local.yaml");
1993
2134
  let content;
1994
2135
  try {
1995
2136
  content = (0, import_node_fs3.readFileSync)(filePath, "utf-8");
@@ -2045,10 +2186,10 @@ function resolveProviderCredentials(transport, localBindings) {
2045
2186
 
2046
2187
  // src/commands/run.ts
2047
2188
  init_llm_utils();
2048
- var import_agent_qa_core8 = require("@vostride/agent-qa-core");
2049
- var RUNTIME_ARTIFACTS_DIR = import_agent_qa_core6.DEFAULT_AGENT_QA_ARTIFACTS_DIR || ".agent-qa/artifacts";
2050
- var RUNTIME_CACHE_DIR = import_agent_qa_core6.DEFAULT_AGENT_QA_CACHE_DIR || ".agent-qa/cache";
2051
- var RUNTIME_VIDEOS_DIR = import_agent_qa_core6.DEFAULT_AGENT_QA_VIDEOS_DIR || ".agent-qa/artifacts/videos";
2189
+ var import_agent_qa_core9 = require("@vostride/agent-qa-core");
2190
+ var RUNTIME_ARTIFACTS_DIR = import_agent_qa_core7.DEFAULT_AGENT_QA_ARTIFACTS_DIR || ".agent-qa/artifacts";
2191
+ var RUNTIME_CACHE_DIR = import_agent_qa_core7.DEFAULT_AGENT_QA_CACHE_DIR || ".agent-qa/cache";
2192
+ var RUNTIME_VIDEOS_DIR = import_agent_qa_core7.DEFAULT_AGENT_QA_VIDEOS_DIR || ".agent-qa/artifacts/videos";
2052
2193
  var _appiumManager = null;
2053
2194
  async function acquireAppium(logLevel) {
2054
2195
  if (process.env.AGENT_QA_APPIUM_URL) return process.env.AGENT_QA_APPIUM_URL;
@@ -2173,13 +2314,13 @@ function resolveDashboardVideoDir(config, configDir, dashboardEnabled) {
2173
2314
  const recordingCfg = config.services?.recording;
2174
2315
  const configuredVideoDir = recordingCfg?.videoDir;
2175
2316
  if (configuredVideoDir) {
2176
- return (0, import_node_path7.resolve)(configDir, configuredVideoDir);
2317
+ return (0, import_node_path9.resolve)(configDir, configuredVideoDir);
2177
2318
  }
2178
2319
  if (dashboardEnabled) {
2179
2320
  const dashArtifactsDir = config.services?.dashboard?.artifactsDir;
2180
- return (0, import_node_path7.resolve)(configDir, dashArtifactsDir ?? RUNTIME_ARTIFACTS_DIR, "videos");
2321
+ return (0, import_node_path9.resolve)(configDir, dashArtifactsDir ?? RUNTIME_ARTIFACTS_DIR, "videos");
2181
2322
  }
2182
- return (0, import_node_path7.resolve)(configDir, RUNTIME_VIDEOS_DIR);
2323
+ return (0, import_node_path9.resolve)(configDir, RUNTIME_VIDEOS_DIR);
2183
2324
  }
2184
2325
  var SUPPORTED_REPORTERS = ["console", "junit", "stdout-live", "dashboard"];
2185
2326
  function parseVarFlags(flags) {
@@ -2199,8 +2340,8 @@ function resolveRunnerAttribute(transport) {
2199
2340
  return transport === "browserstack" ? "browserstack" : "local";
2200
2341
  }
2201
2342
  function buildCliRunAttributes(userAttributes, runner) {
2202
- return (0, import_agent_qa_core6.mergeRunAttributes)(
2203
- (0, import_agent_qa_core6.buildInternalRunAttributes)({ trigger: "cli", runner }),
2343
+ return (0, import_agent_qa_core7.mergeRunAttributes)(
2344
+ (0, import_agent_qa_core7.buildInternalRunAttributes)({ trigger: "cli", runner }),
2204
2345
  userAttributes
2205
2346
  );
2206
2347
  }
@@ -2213,17 +2354,17 @@ function readRunAttributesFromEnv() {
2213
2354
  } catch {
2214
2355
  throw new Error("inherited run attributes: AGENT_QA_RUN_ATTRIBUTES_JSON must contain valid run attributes JSON");
2215
2356
  }
2216
- const attributes = (0, import_agent_qa_core6.validateTrustedRunAttributes)(parsed, "inherited run attributes");
2357
+ const attributes = (0, import_agent_qa_core7.validateTrustedRunAttributes)(parsed, "inherited run attributes");
2217
2358
  return Object.keys(attributes).length > 0 ? attributes : void 0;
2218
2359
  }
2219
2360
  function resolveCliRunAttributes(userAttributes, runner, inheritedAttributes) {
2220
2361
  if (!inheritedAttributes) return buildCliRunAttributes(userAttributes, runner);
2221
- const trigger = inheritedAttributes[import_agent_qa_core6.ATTR_TRIGGER];
2362
+ const trigger = inheritedAttributes[import_agent_qa_core7.ATTR_TRIGGER];
2222
2363
  const inheritedUserAttributes = Object.fromEntries(
2223
- Object.entries(inheritedAttributes).filter(([key]) => key !== import_agent_qa_core6.ATTR_TRIGGER && key !== import_agent_qa_core6.ATTR_RUNNER)
2364
+ Object.entries(inheritedAttributes).filter(([key]) => key !== import_agent_qa_core7.ATTR_TRIGGER && key !== import_agent_qa_core7.ATTR_RUNNER)
2224
2365
  );
2225
- return (0, import_agent_qa_core6.mergeRunAttributes)(
2226
- (0, import_agent_qa_core6.buildInternalRunAttributes)({ trigger: trigger ?? "cli", runner }),
2366
+ return (0, import_agent_qa_core7.mergeRunAttributes)(
2367
+ (0, import_agent_qa_core7.buildInternalRunAttributes)({ trigger: trigger ?? "cli", runner }),
2227
2368
  inheritedUserAttributes
2228
2369
  );
2229
2370
  }
@@ -2232,7 +2373,7 @@ function writeRunAttributesToEnv(attributes) {
2232
2373
  }
2233
2374
  function printRunAttributes(attributes) {
2234
2375
  if (!attributes || Object.keys(attributes).length === 0) return;
2235
- const block = (0, import_agent_qa_core6.formatRunAttributesBlock)(attributes);
2376
+ const block = (0, import_agent_qa_core7.formatRunAttributesBlock)(attributes);
2236
2377
  if (block) console.log(block);
2237
2378
  }
2238
2379
  function resolveReporterSelection(opts, dashboardConfigured) {
@@ -2293,37 +2434,37 @@ async function captureSingleAnalyticsResult(reporter, result) {
2293
2434
  function formatParseErrors(errors) {
2294
2435
  for (const err of errors) {
2295
2436
  const location = `${err.file}:${err.line}:${err.column}`;
2296
- const severity = err.severity === "error" ? import_picocolors6.default.red("error") : import_picocolors6.default.yellow("warning");
2437
+ const severity = err.severity === "error" ? import_picocolors7.default.red("error") : import_picocolors7.default.yellow("warning");
2297
2438
  console.error(`${severity}: ${err.message}`);
2298
- console.error(` ${import_picocolors6.default.blue("-->")} ${location}`);
2439
+ console.error(` ${import_picocolors7.default.blue("-->")} ${location}`);
2299
2440
  if (err.source) {
2300
- console.error(` ${import_picocolors6.default.blue("|")} ${err.source}`);
2441
+ console.error(` ${import_picocolors7.default.blue("|")} ${err.source}`);
2301
2442
  }
2302
2443
  if (err.suggestion) {
2303
- console.error(` ${import_picocolors6.default.blue("=")} ${import_picocolors6.default.bold("help")}: ${err.suggestion}`);
2444
+ console.error(` ${import_picocolors7.default.blue("=")} ${import_picocolors7.default.bold("help")}: ${err.suggestion}`);
2304
2445
  }
2305
2446
  console.error();
2306
2447
  }
2307
2448
  }
2308
2449
  function createTestRunId() {
2309
- return process.env.AGENT_QA_RUN_ID ?? (0, import_agent_qa_core6.generateRunId)();
2450
+ return process.env.AGENT_QA_RUN_ID ?? (0, import_agent_qa_core7.generateRunId)();
2310
2451
  }
2311
2452
  function createSuiteRunId() {
2312
- return process.env.AGENT_QA_SUITE_QUEUE_ID ?? process.env.AGENT_QA_RUN_ID ?? (0, import_agent_qa_core6.generateRunId)();
2453
+ return process.env.AGENT_QA_SUITE_QUEUE_ID ?? process.env.AGENT_QA_RUN_ID ?? (0, import_agent_qa_core7.generateRunId)();
2313
2454
  }
2314
2455
  function printRunId(runId) {
2315
2456
  if (runId) console.log(`Run ID: ${runId}`);
2316
2457
  }
2317
2458
  function printMemoryStatus(log) {
2318
2459
  const parts = [];
2319
- if (log.added > 0) parts.push(import_picocolors6.default.green(`${log.added} added`));
2460
+ if (log.added > 0) parts.push(import_picocolors7.default.green(`${log.added} added`));
2320
2461
  if (log.confirmed > 0) parts.push(`${log.confirmed} confirmed`);
2321
- if (log.deprecated > 0) parts.push(import_picocolors6.default.yellow(`${log.deprecated} deprecated`));
2462
+ if (log.deprecated > 0) parts.push(import_picocolors7.default.yellow(`${log.deprecated} deprecated`));
2322
2463
  if (parts.length === 0) parts.push("no changes");
2323
2464
  const secs = log.curatorDuration >= 1e3 ? `${Math.round(log.curatorDuration / 1e3)}s` : `${log.curatorDuration}ms`;
2324
- console.log(` ${import_picocolors6.default.dim("Memory:")} ${parts.join(", ")} ${import_picocolors6.default.dim(`(${secs})`)}`);
2465
+ console.log(` ${import_picocolors7.default.dim("Memory:")} ${parts.join(", ")} ${import_picocolors7.default.dim(`(${secs})`)}`);
2325
2466
  for (const err of log.errors) {
2326
- console.log(` ${import_picocolors6.default.dim("Memory:")} ${import_picocolors6.default.yellow(`warning -- ${err}`)}`);
2467
+ console.log(` ${import_picocolors7.default.dim("Memory:")} ${import_picocolors7.default.yellow(`warning -- ${err}`)}`);
2327
2468
  }
2328
2469
  }
2329
2470
  function isGlobLikePattern(value) {
@@ -2338,15 +2479,15 @@ function matchesCliWorkspacePattern(workspace, kind, pattern, record) {
2338
2479
  testMatch: kind === "test" ? [pattern] : workspace.testMatch,
2339
2480
  suiteMatch: kind === "suite" ? [pattern] : workspace.suiteMatch
2340
2481
  };
2341
- return (0, import_agent_qa_core7.isWorkspacePathMatch)({
2482
+ return (0, import_agent_qa_core8.isWorkspacePathMatch)({
2342
2483
  workspace: patternWorkspace,
2343
2484
  kind,
2344
2485
  workspaceRelativePath: record.workspaceRelativePath
2345
2486
  });
2346
2487
  }
2347
2488
  async function discoverFilesForCliPatterns(workspace, patterns) {
2348
- const testRecords = await (0, import_agent_qa_core7.discoverWorkspaceFiles)({ workspace, kind: "test" });
2349
- const suiteRecords = await (0, import_agent_qa_core7.discoverWorkspaceFiles)({ workspace, kind: "suite" });
2489
+ const testRecords = await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace, kind: "test" });
2490
+ const suiteRecords = await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace, kind: "suite" });
2350
2491
  const discoveredByKind = {
2351
2492
  test: testRecords,
2352
2493
  suite: suiteRecords
@@ -2367,7 +2508,7 @@ async function discoverFilesForCliPatterns(workspace, patterns) {
2367
2508
  }
2368
2509
  continue;
2369
2510
  }
2370
- const record = await (0, import_agent_qa_core7.resolveWorkspaceFileTarget)({
2511
+ const record = await (0, import_agent_qa_core8.resolveWorkspaceFileTarget)({
2371
2512
  workspace,
2372
2513
  kind,
2373
2514
  filePath: pattern,
@@ -2395,21 +2536,21 @@ function buildHooksArtifact(hooks) {
2395
2536
  async function loadRuntimeSecrets(input3) {
2396
2537
  const configuredSecretsFile = input3.config.workspace?.secretsFile;
2397
2538
  if (typeof configuredSecretsFile !== "string" || configuredSecretsFile.trim().length === 0) {
2398
- console.error(import_picocolors6.default.red("Error: workspace.secretsFile is required. Run `agent-qa init` or set it in agent-qa.config.yaml."));
2539
+ console.error(import_picocolors7.default.red("Error: workspace.secretsFile is required. Run `agent-qa init` or set it in agent-qa.config.yaml."));
2399
2540
  process.exit(1);
2400
2541
  }
2401
- const secretsPath = input3.secretsFilePath ?? (0, import_node_path7.resolve)(input3.configDir, configuredSecretsFile);
2542
+ const secretsPath = input3.secretsFilePath ?? (0, import_node_path9.resolve)(input3.configDir, configuredSecretsFile);
2402
2543
  if (!input3.existsSync(secretsPath)) {
2403
- console.error(import_picocolors6.default.red(`Error: Secrets file not found: ${secretsPath}`));
2404
- console.error(import_picocolors6.default.dim("Create the file before running tests. It may be empty, but it must exist."));
2544
+ console.error(import_picocolors7.default.red(`Error: Secrets file not found: ${secretsPath}`));
2545
+ console.error(import_picocolors7.default.dim("Create the file before running tests. It may be empty, but it must exist."));
2405
2546
  process.exit(1);
2406
2547
  }
2407
2548
  let secretsContent;
2408
2549
  try {
2409
2550
  secretsContent = input3.readFileSync(secretsPath, "utf-8");
2410
2551
  } catch {
2411
- console.error(import_picocolors6.default.red(`Error: Secrets file could not be read: ${secretsPath}`));
2412
- console.error(import_picocolors6.default.dim("Check the path and file permissions before running tests."));
2552
+ console.error(import_picocolors7.default.red(`Error: Secrets file could not be read: ${secretsPath}`));
2553
+ console.error(import_picocolors7.default.dim("Check the path and file permissions before running tests."));
2413
2554
  process.exit(1);
2414
2555
  }
2415
2556
  try {
@@ -2426,7 +2567,7 @@ async function loadRuntimeSecrets(input3) {
2426
2567
  };
2427
2568
  } catch (err) {
2428
2569
  const message = err instanceof Error ? err.message : String(err);
2429
- console.error(import_picocolors6.default.red(`Error: Secrets file is invalid: ${message}`));
2570
+ console.error(import_picocolors7.default.red(`Error: Secrets file is invalid: ${message}`));
2430
2571
  process.exit(1);
2431
2572
  }
2432
2573
  }
@@ -2483,7 +2624,7 @@ async function resolveDeviceAndFarmSession(config, deviceName, testPlatform, tes
2483
2624
  }
2484
2625
  const browserstackApp = mobileResolved?.app.install?.browserstack;
2485
2626
  if (!browserstackApp && hasNativeAppIdentity(mobileResolved) && !isBrowserMode(resolved.match)) {
2486
- throw new import_agent_qa_core6.MobileSetupError({
2627
+ throw new import_agent_qa_core7.MobileSetupError({
2487
2628
  category: "app-install",
2488
2629
  message: `BrowserStack native app target "${mobileResolved?.targetName ?? "unknown"}" requires app.browserstack; app.path is local-only and is not used as a BrowserStack fallback.`,
2489
2630
  platform: resolved.platform,
@@ -2507,9 +2648,9 @@ async function resolveDeviceAndFarmSession(config, deviceName, testPlatform, tes
2507
2648
  appSourceTrace: mobileResolved?.sourceTrace
2508
2649
  });
2509
2650
  } catch (err) {
2510
- if (err instanceof import_agent_qa_core6.MobileSetupError) throw err;
2651
+ if (err instanceof import_agent_qa_core7.MobileSetupError) throw err;
2511
2652
  if (browserstackApp) {
2512
- throw new import_agent_qa_core6.MobileSetupError({
2653
+ throw new import_agent_qa_core7.MobileSetupError({
2513
2654
  category: "app-install",
2514
2655
  message: `Failed to resolve BrowserStack app reference: ${err instanceof Error ? err.message : String(err)}`,
2515
2656
  platform: resolved.platform,
@@ -2531,14 +2672,14 @@ function hasNativeAppIdentity(mobileResolved) {
2531
2672
  return Boolean(app?.bundleId || app?.appPackage || app?.appActivity || app?.deepLinkAppId);
2532
2673
  }
2533
2674
  function wrapMobileSetupError(err) {
2534
- if (err instanceof import_agent_qa_core6.MobileSetupError) {
2675
+ if (err instanceof import_agent_qa_core7.MobileSetupError) {
2535
2676
  throw new Error(`${err.category}: ${err.message}`);
2536
2677
  }
2537
2678
  throw err;
2538
2679
  }
2539
2680
  function resolveMobileRunOrThrow(input3) {
2540
2681
  try {
2541
- return (0, import_agent_qa_core6.resolveMobileRunConfig)(input3);
2682
+ return (0, import_agent_qa_core7.resolveMobileRunConfig)(input3);
2542
2683
  } catch (err) {
2543
2684
  wrapMobileSetupError(err);
2544
2685
  }
@@ -2589,7 +2730,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2589
2730
  const dashboardCfg = config.services?.dashboard;
2590
2731
  const reporterSelectionResult = resolveReporterSelection(opts, Boolean(dashboardCfg));
2591
2732
  if (reporterSelectionResult.error) {
2592
- console.error(import_picocolors6.default.red(`Error: ${reporterSelectionResult.error}`));
2733
+ console.error(import_picocolors7.default.red(`Error: ${reporterSelectionResult.error}`));
2593
2734
  process.exit(2);
2594
2735
  }
2595
2736
  const reporterSelection = reporterSelectionResult.selection;
@@ -2605,7 +2746,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2605
2746
  activeSuiteDbs.add(dashboardDb);
2606
2747
  return { dashboardDb, logStorage: dashboardDb };
2607
2748
  } catch (err) {
2608
- console.warn(import_picocolors6.default.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
2749
+ console.warn(import_picocolors7.default.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
2609
2750
  return {};
2610
2751
  }
2611
2752
  };
@@ -2646,8 +2787,8 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2646
2787
  const testEntries = [];
2647
2788
  const suiteMembers = [];
2648
2789
  for (const { test: relativePath, id: suiteEntryId } of suite.tests) {
2649
- if (!(0, import_agent_qa_core8.isPathInsideDir)(relativePath, configDir)) {
2650
- console.error(import_picocolors6.default.red(`Path traversal rejected: ${relativePath}`));
2790
+ if (!(0, import_agent_qa_core9.isPathInsideDir)(relativePath, configDir)) {
2791
+ console.error(import_picocolors7.default.red(`Path traversal rejected: ${relativePath}`));
2651
2792
  const runId = createSuiteRunId();
2652
2793
  if (dashboardDb) {
2653
2794
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -2685,7 +2826,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2685
2826
  const fileContent = await readFile5(absolutePath, "utf-8");
2686
2827
  const parseResult = parseTestFile2(fileContent, absolutePath);
2687
2828
  if (parseResult.errors.length > 0) {
2688
- console.error(import_picocolors6.default.red(`Parse errors in ${relativePath}:`));
2829
+ console.error(import_picocolors7.default.red(`Parse errors in ${relativePath}:`));
2689
2830
  formatParseErrors(parseResult.errors);
2690
2831
  const runId = createSuiteRunId();
2691
2832
  if (dashboardDb) {
@@ -2727,7 +2868,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2727
2868
  process.exit(2);
2728
2869
  }
2729
2870
  if (parseResult.tests.length === 0) {
2730
- console.error(import_picocolors6.default.red(`No test definition found in ${relativePath}`));
2871
+ console.error(import_picocolors7.default.red(`No test definition found in ${relativePath}`));
2731
2872
  const runId = createSuiteRunId();
2732
2873
  if (dashboardDb) {
2733
2874
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -2764,7 +2905,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2764
2905
  const testDef = parseResult.tests[0];
2765
2906
  if (testDef["test-id"] && testDef["test-id"] !== suiteEntryId) {
2766
2907
  const mismatchMsg = `ID mismatch in suite "${suite.name}": test file ${relativePath} has test-id "${testDef["test-id"]}" but suite entry specifies id "${suiteEntryId}"`;
2767
- console.error(import_picocolors6.default.red(mismatchMsg));
2908
+ console.error(import_picocolors7.default.red(mismatchMsg));
2768
2909
  const runId = createSuiteRunId();
2769
2910
  if (dashboardDb) {
2770
2911
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -2887,7 +3028,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2887
3028
  mobileResolved = { ...mobileResolved, appium: { url: appiumUrl, managed: !process.env.AGENT_QA_APPIUM_URL } };
2888
3029
  }
2889
3030
  } catch (err) {
2890
- throw new import_agent_qa_core6.MobileSetupError({
3031
+ throw new import_agent_qa_core7.MobileSetupError({
2891
3032
  category: "appium-startup",
2892
3033
  message: `Could not auto-start Appium: ${err instanceof Error ? err.message : String(err)}`,
2893
3034
  platform: suitePlatform,
@@ -2965,11 +3106,11 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2965
3106
  if (reporterSelection.dashboard && dashboardDb) {
2966
3107
  try {
2967
3108
  const { DashboardReporter } = await import("@vostride/agent-qa-dashboard");
2968
- const { resolve: resolve7 } = await import("path");
3109
+ const { resolve: resolve9 } = await import("path");
2969
3110
  const dashArtifactsDir = config.services?.dashboard?.artifactsDir ?? RUNTIME_ARTIFACTS_DIR;
2970
3111
  reporters.push(new DashboardReporter({
2971
3112
  db: dashboardDb,
2972
- artifactsDir: resolve7(configDir, dashArtifactsDir),
3113
+ artifactsDir: resolve9(configDir, dashArtifactsDir),
2973
3114
  redactor: variableContext?.secretRedactor,
2974
3115
  onRunCreated: (runId) => {
2975
3116
  lastSuiteRunId = runId;
@@ -2984,9 +3125,9 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2984
3125
  let actionCache;
2985
3126
  if (opts.cache !== false) {
2986
3127
  const { FileActionCache } = await import("@vostride/agent-qa-core");
2987
- const { resolve: resolve7 } = await import("path");
3128
+ const { resolve: resolve9 } = await import("path");
2988
3129
  actionCache = new FileActionCache({
2989
- dir: resolve7(configDir, config.services?.cache?.dir ?? RUNTIME_CACHE_DIR),
3130
+ dir: resolve9(configDir, config.services?.cache?.dir ?? RUNTIME_CACHE_DIR),
2990
3131
  ttl: config.services?.cache?.ttl ?? "7d",
2991
3132
  logger: logger.createScopedLogger("cache")
2992
3133
  });
@@ -2998,7 +3139,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2998
3139
  let suiteMemoryProvider;
2999
3140
  let suiteCircuitBreaker;
3000
3141
  const suiteMemoryConfig = config.services?.memory;
3001
- const suiteMemoryRoot = (0, import_agent_qa_core6.resolveMemoryRoot)(config, configDir);
3142
+ const suiteMemoryRoot = (0, import_agent_qa_core7.resolveMemoryRoot)(config, configDir);
3002
3143
  if (opts.memory !== false && suiteMemoryConfig?.enabled !== false) {
3003
3144
  try {
3004
3145
  const { createMemoryProvider } = await import("@vostride/agent-qa-core");
@@ -3008,7 +3149,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
3008
3149
  curatorLockTimeout: suiteMemoryConfig?.curatorLockTimeout
3009
3150
  });
3010
3151
  } catch (err) {
3011
- console.warn(` ${import_picocolors6.default.dim("Memory:")} init error -- ${err.message}`);
3152
+ console.warn(` ${import_picocolors7.default.dim("Memory:")} init error -- ${err.message}`);
3012
3153
  }
3013
3154
  if (suiteMemoryConfig?.circuitBreakerEnabled !== false) {
3014
3155
  const { CircuitBreaker } = await import("@vostride/agent-qa-core");
@@ -3147,19 +3288,19 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
3147
3288
  const batchOutcome = await flushSuiteParents();
3148
3289
  if (batchOutcome.cancelled || shutdownRequested) break;
3149
3290
  if (opts.bail && batchOutcome.failed) {
3150
- console.log(import_picocolors6.default.yellow("\nBailing out \u2014 stopping after first failed suite"));
3291
+ console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failed suite"));
3151
3292
  break;
3152
3293
  }
3153
3294
  const outcome = await runOneSuiteFile(suiteFile, suite);
3154
3295
  if (outcome.cancelled || shutdownRequested) break;
3155
3296
  if (opts.bail && outcome.failed) {
3156
- console.log(import_picocolors6.default.yellow("\nBailing out \u2014 stopping after first failed suite"));
3297
+ console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failed suite"));
3157
3298
  break;
3158
3299
  }
3159
3300
  }
3160
3301
  const finalBatchOutcome = await flushSuiteParents();
3161
3302
  if (opts.bail && finalBatchOutcome.failed) {
3162
- console.log(import_picocolors6.default.yellow("\nBailing out \u2014 stopping after first failed suite"));
3303
+ console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failed suite"));
3163
3304
  }
3164
3305
  process.removeListener("SIGINT", handleShutdown);
3165
3306
  process.removeListener("SIGTERM", handleShutdown);
@@ -3167,12 +3308,12 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
3167
3308
  return suiteResults.some((r) => r.status === "failed" || r.status === "cancelled") || shutdownRequested;
3168
3309
  }
3169
3310
  function createRunCommand() {
3170
- const cmd = new import_commander5.Command("run").description("Run test files").argument("[patterns...]", "glob patterns for test files").option("--browser <name>", "override browser (chromium, firefox, webkit)").option("--platform <name>", "filter by platform (web, android, ios)").option("--headless", "run in headless mode").option("--no-headless", "run in headed mode").option("--no-cache", "bypass action cache for this run").option("--no-memory", "disable runtime memory injection for this run").option("--bail", "stop on first test failure").option("--dry-run", "parse and list tests without executing").option("--list-tests", "list discovered test files without executing").option("--junit-output <path>", "path for JUnit XML output").option("--screenshot-dir <dir>", "directory for failure screenshots").option("--screenshot-mode <mode>", "when to capture: failure (default) or every-step").option("--reporter <names...>", "select reporters (default: console)").option("--record", "enable session video recording").option("--config-debug", "print resolved config with source attribution for each test").option("--test", "discover and run tests via testMatch patterns (default when no flags)").option("--suite", "discover and run suites via suiteMatch patterns").option("--all", "discover and run both tests (testMatch) and suites (suiteMatch)").option("--device <name>", "Override the mobile test/suite device for this run").option("--var <kv...>", "set variable KEY=VALUE (repeatable)").option("--run-attr <kv...>", "attach run attribute KEY=VALUE; repeatable").action(async (patterns, opts, command) => {
3311
+ const cmd = new import_commander6.Command("run").description("Run test files").argument("[patterns...]", "glob patterns for test files").option("--browser <name>", "override browser (chromium, firefox, webkit)").option("--platform <name>", "filter by platform (web, android, ios)").option("--headless", "run in headless mode").option("--no-headless", "run in headed mode").option("--no-cache", "bypass action cache for this run").option("--no-memory", "disable runtime memory injection for this run").option("--bail", "stop on first test failure").option("--dry-run", "parse and list tests without executing").option("--list-tests", "list discovered test files without executing").option("--junit-output <path>", "path for JUnit XML output").option("--screenshot-dir <dir>", "directory for failure screenshots").option("--screenshot-mode <mode>", "when to capture: failure (default) or every-step").option("--reporter <names...>", "select reporters (default: console)").option("--record", "enable session video recording").option("--config-debug", "print resolved config with source attribution for each test").option("--test", "discover and run tests via testMatch patterns (default when no flags)").option("--suite", "discover and run suites via suiteMatch patterns").option("--all", "discover and run both tests (testMatch) and suites (suiteMatch)").option("--device <name>", "Override the mobile test/suite device for this run").option("--var <kv...>", "set variable KEY=VALUE (repeatable)").option("--run-attr <kv...>", "attach run attribute KEY=VALUE; repeatable").action(async (patterns, opts, command) => {
3171
3312
  let _crashDb;
3172
3313
  try {
3173
3314
  const modeFlags = [opts.test, opts.suite, opts.all].filter(Boolean).length;
3174
3315
  if (modeFlags > 1) {
3175
- console.error(import_picocolors6.default.red("Error: --test, --suite, and --all are mutually exclusive."));
3316
+ console.error(import_picocolors7.default.red("Error: --test, --suite, and --all are mutually exclusive."));
3176
3317
  process.exit(1);
3177
3318
  }
3178
3319
  const program2 = command.parent;
@@ -3188,8 +3329,8 @@ function createRunCommand() {
3188
3329
  flags: flagOverrides,
3189
3330
  loadAuthPlugins: !(opts.dryRun || opts.listTests)
3190
3331
  });
3191
- const configFilePath = (0, import_node_path7.resolve)(globalOpts.config ?? "agent-qa.config.yaml");
3192
- const workspacePaths = (0, import_agent_qa_core7.resolveWorkspacePaths)({
3332
+ const configFilePath = (0, import_node_path9.resolve)(globalOpts.config ?? "agent-qa.config.yaml");
3333
+ const workspacePaths = (0, import_agent_qa_core8.resolveWorkspacePaths)({
3193
3334
  config,
3194
3335
  configPath: configFilePath
3195
3336
  });
@@ -3207,13 +3348,13 @@ function createRunCommand() {
3207
3348
  const inheritedRunAttributes = inheritedContextRunId ? readRunAttributesFromEnv() : void 0;
3208
3349
  let userRunAttributes;
3209
3350
  try {
3210
- const parsedRunAttrs = (0, import_agent_qa_core6.parseRunAttrFlags)(opts.runAttr);
3351
+ const parsedRunAttrs = (0, import_agent_qa_core7.parseRunAttrFlags)(opts.runAttr);
3211
3352
  userRunAttributes = parsedRunAttrs.attributes;
3212
3353
  for (const duplicateKey of parsedRunAttrs.duplicateKeys) {
3213
3354
  console.warn(`Warning: duplicate --run-attr key "${duplicateKey}"; using last value`);
3214
3355
  }
3215
3356
  } catch (err) {
3216
- console.error(import_picocolors6.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
3357
+ console.error(import_picocolors7.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
3217
3358
  process.exit(2);
3218
3359
  }
3219
3360
  const inlineVars = {};
@@ -3229,29 +3370,29 @@ function createRunCommand() {
3229
3370
  let resolvedEnvFilePath = null;
3230
3371
  resolvedEnvFilePath = workspacePaths.envFile.absolutePath;
3231
3372
  if (!existsSync8(workspacePaths.envFile.absolutePath)) {
3232
- console.error(import_picocolors6.default.red(`Error: Env file not found: ${workspacePaths.envFile.absolutePath}`));
3373
+ console.error(import_picocolors7.default.red(`Error: Env file not found: ${workspacePaths.envFile.absolutePath}`));
3233
3374
  process.exit(1);
3234
3375
  }
3235
3376
  try {
3236
3377
  rawEnvFileContent = readFileSync5(workspacePaths.envFile.absolutePath, "utf-8");
3237
3378
  envFileVars = parseEnvFile(rawEnvFileContent);
3238
3379
  } catch {
3239
- console.error(import_picocolors6.default.red(`Error: Env file could not be read: ${workspacePaths.envFile.absolutePath}`));
3380
+ console.error(import_picocolors7.default.red(`Error: Env file could not be read: ${workspacePaths.envFile.absolutePath}`));
3240
3381
  process.exit(1);
3241
3382
  }
3242
3383
  let resolvedHooks;
3243
3384
  let sandboxOptions;
3244
3385
  const hooksFilePath = workspacePaths.hooksFile.absolutePath;
3245
3386
  if (!existsSync8(hooksFilePath)) {
3246
- console.error(import_picocolors6.default.red(`Error: Hooks file not found: ${hooksFilePath}`));
3387
+ console.error(import_picocolors7.default.red(`Error: Hooks file not found: ${hooksFilePath}`));
3247
3388
  process.exit(1);
3248
3389
  }
3249
3390
  try {
3250
3391
  const { parseHooksFile } = await import("@vostride/agent-qa-core");
3251
3392
  const { hooks, errors } = await parseHooksFile(hooksFilePath);
3252
3393
  if (errors.length > 0) {
3253
- console.error(import_picocolors6.default.red("Hooks configuration errors:"));
3254
- for (const err of errors) console.error(import_picocolors6.default.red(` - ${err}`));
3394
+ console.error(import_picocolors7.default.red("Hooks configuration errors:"));
3395
+ for (const err of errors) console.error(import_picocolors7.default.red(` - ${err}`));
3255
3396
  process.exit(1);
3256
3397
  }
3257
3398
  resolvedHooks = new Map(hooks.map((h) => [h.id, h]));
@@ -3260,8 +3401,8 @@ function createRunCommand() {
3260
3401
  secretRedactor: runtimeSecrets.secretRedactor
3261
3402
  };
3262
3403
  } catch (err) {
3263
- console.error(import_picocolors6.default.red(`Error: Hooks file could not be read: ${hooksFilePath}`));
3264
- if (err instanceof Error && err.message) console.error(import_picocolors6.default.dim(err.message));
3404
+ console.error(import_picocolors7.default.red(`Error: Hooks file could not be read: ${hooksFilePath}`));
3405
+ if (err instanceof Error && err.message) console.error(import_picocolors7.default.dim(err.message));
3265
3406
  process.exit(1);
3266
3407
  }
3267
3408
  let agentRulesContent;
@@ -3269,7 +3410,7 @@ function createRunCommand() {
3269
3410
  try {
3270
3411
  agentRulesContent = await readFileAsync(workspacePaths.agentRules.absolutePath, "utf-8");
3271
3412
  } catch {
3272
- console.error(import_picocolors6.default.red(`Error: Agent rules file not found: ${workspacePaths.agentRules.absolutePath}`));
3413
+ console.error(import_picocolors7.default.red(`Error: Agent rules file not found: ${workspacePaths.agentRules.absolutePath}`));
3273
3414
  process.exit(1);
3274
3415
  }
3275
3416
  const effectiveLogLevel = globalOpts.logLevel ?? (globalOpts.verbose ? "debug" : void 0) ?? (globalOpts.quiet ? "silent" : void 0) ?? config.services?.logging?.level ?? "warn";
@@ -3281,41 +3422,41 @@ function createRunCommand() {
3281
3422
  discoveredTestFiles = selected.tests;
3282
3423
  discoveredSuiteFiles = selected.suites;
3283
3424
  } catch (err) {
3284
- console.error(import_picocolors6.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
3425
+ console.error(import_picocolors7.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
3285
3426
  process.exit(1);
3286
3427
  }
3287
3428
  if (discoveredTestFiles.length === 0 && discoveredSuiteFiles.length === 0) {
3288
3429
  const patternKind = patterns.every((pattern) => classifyRunPattern(pattern) === "suite") ? "suite" : "test";
3289
- console.error(import_picocolors6.default.red(`Error: No ${patternKind} files found matching patterns:`));
3290
- for (const p of patterns) console.error(import_picocolors6.default.red(` ${p}`));
3430
+ console.error(import_picocolors7.default.red(`Error: No ${patternKind} files found matching patterns:`));
3431
+ for (const p of patterns) console.error(import_picocolors7.default.red(` ${p}`));
3291
3432
  process.exit(2);
3292
3433
  }
3293
3434
  } else if (opts.suite) {
3294
- discoveredSuiteFiles = (await (0, import_agent_qa_core7.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "suite" })).map((record) => record.absolutePath);
3435
+ discoveredSuiteFiles = (await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "suite" })).map((record) => record.absolutePath);
3295
3436
  if (discoveredSuiteFiles.length === 0) {
3296
- console.error(import_picocolors6.default.red("Error: No suite files found matching workspace.suiteMatch"));
3437
+ console.error(import_picocolors7.default.red("Error: No suite files found matching workspace.suiteMatch"));
3297
3438
  for (const p of workspacePaths.suiteMatch) {
3298
- console.error(import_picocolors6.default.red(` ${p}`));
3439
+ console.error(import_picocolors7.default.red(` ${p}`));
3299
3440
  }
3300
3441
  process.exit(2);
3301
3442
  }
3302
3443
  } else if (opts.all) {
3303
- discoveredTestFiles = (await (0, import_agent_qa_core7.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "test" })).map((record) => record.absolutePath);
3304
- discoveredSuiteFiles = (await (0, import_agent_qa_core7.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "suite" })).map((record) => record.absolutePath);
3444
+ discoveredTestFiles = (await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "test" })).map((record) => record.absolutePath);
3445
+ discoveredSuiteFiles = (await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "suite" })).map((record) => record.absolutePath);
3305
3446
  if (discoveredTestFiles.length === 0 && discoveredSuiteFiles.length === 0) {
3306
- console.error(import_picocolors6.default.red("Error: No test or suite files found. Check workspace.testMatch and workspace.suiteMatch in agent-qa.config.yaml"));
3447
+ console.error(import_picocolors7.default.red("Error: No test or suite files found. Check workspace.testMatch and workspace.suiteMatch in agent-qa.config.yaml"));
3307
3448
  process.exit(2);
3308
3449
  }
3309
3450
  } else {
3310
3451
  if (!workspacePaths.testMatch || workspacePaths.testMatch.length === 0) {
3311
- console.error(import_picocolors6.default.red("Error: No testMatch patterns in config. Add workspace.testMatch to agent-qa.config.yaml."));
3452
+ console.error(import_picocolors7.default.red("Error: No testMatch patterns in config. Add workspace.testMatch to agent-qa.config.yaml."));
3312
3453
  process.exit(2);
3313
3454
  }
3314
- discoveredTestFiles = (await (0, import_agent_qa_core7.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "test" })).map((record) => record.absolutePath);
3455
+ discoveredTestFiles = (await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "test" })).map((record) => record.absolutePath);
3315
3456
  if (discoveredTestFiles.length === 0) {
3316
- console.error(import_picocolors6.default.red("Error: No test files found matching workspace.testMatch"));
3457
+ console.error(import_picocolors7.default.red("Error: No test files found matching workspace.testMatch"));
3317
3458
  for (const p of workspacePaths.testMatch) {
3318
- console.error(import_picocolors6.default.red(` ${p}`));
3459
+ console.error(import_picocolors7.default.red(` ${p}`));
3319
3460
  }
3320
3461
  process.exit(2);
3321
3462
  }
@@ -3330,7 +3471,7 @@ function createRunCommand() {
3330
3471
  }
3331
3472
  const files = discoveredTestFiles;
3332
3473
  if (opts.listTests) {
3333
- console.log(import_picocolors6.default.bold(`
3474
+ console.log(import_picocolors7.default.bold(`
3334
3475
  Discovered ${files.length} test file(s):
3335
3476
  `));
3336
3477
  for (const file of files) {
@@ -3342,7 +3483,7 @@ Discovered ${files.length} test file(s):
3342
3483
  const dashboardCfg2 = config.services?.dashboard;
3343
3484
  const reporterSelectionResult = resolveReporterSelection(opts, Boolean(dashboardCfg2));
3344
3485
  if (reporterSelectionResult.error) {
3345
- console.error(import_picocolors6.default.red(`Error: ${reporterSelectionResult.error}`));
3486
+ console.error(import_picocolors7.default.red(`Error: ${reporterSelectionResult.error}`));
3346
3487
  process.exit(2);
3347
3488
  }
3348
3489
  const reporterSelection = reporterSelectionResult.selection;
@@ -3359,13 +3500,13 @@ Discovered ${files.length} test file(s):
3359
3500
  logStorage = dashboardDb;
3360
3501
  _crashDb = dashboardDb;
3361
3502
  } catch (err) {
3362
- console.warn(import_picocolors6.default.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
3503
+ console.warn(import_picocolors7.default.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
3363
3504
  }
3364
3505
  }
3365
3506
  const { parseAllTests, formatParseError } = await import("@vostride/agent-qa-core");
3366
3507
  const parseResult = await parseAllTests(files);
3367
3508
  if (parseResult.errors.length > 0) {
3368
- console.error(import_picocolors6.default.red(`Found ${parseResult.errors.length} parse error(s):
3509
+ console.error(import_picocolors7.default.red(`Found ${parseResult.errors.length} parse error(s):
3369
3510
  `));
3370
3511
  for (const err of parseResult.errors) {
3371
3512
  console.error(formatParseError(err));
@@ -3461,17 +3602,17 @@ Discovered ${files.length} test file(s):
3461
3602
  }
3462
3603
  const filtered = parseResult.tests;
3463
3604
  if (filtered.length === 0) {
3464
- console.log(import_picocolors6.default.yellow("No tests match filters"));
3605
+ console.log(import_picocolors7.default.yellow("No tests match filters"));
3465
3606
  process.exit(0);
3466
3607
  }
3467
3608
  if (opts.dryRun) {
3468
- console.log(import_picocolors6.default.bold(`
3609
+ console.log(import_picocolors7.default.bold(`
3469
3610
  Found ${filtered.length} test(s):
3470
3611
  `));
3471
3612
  for (const test of filtered) {
3472
- console.log(` ${import_picocolors6.default.green("\u25CF")} ${test.name}`);
3613
+ console.log(` ${import_picocolors7.default.green("\u25CF")} ${test.name}`);
3473
3614
  if (opts.configDebug) {
3474
- console.log(import_picocolors6.default.bold(`
3615
+ console.log(import_picocolors7.default.bold(`
3475
3616
  Config for: ${test.name}`));
3476
3617
  console.log(formatConfigDebug(config, test.use, flagOverrides));
3477
3618
  console.log();
@@ -3509,11 +3650,11 @@ Found ${filtered.length} test(s):
3509
3650
  if (reporterSelection.dashboard && dashboardDb) {
3510
3651
  try {
3511
3652
  const { DashboardReporter } = await import("@vostride/agent-qa-dashboard");
3512
- const { resolve: resolve7 } = await import("path");
3653
+ const { resolve: resolve9 } = await import("path");
3513
3654
  const dashArtifactsDir = config.services?.dashboard?.artifactsDir ?? RUNTIME_ARTIFACTS_DIR;
3514
3655
  reporters.push(new DashboardReporter({
3515
3656
  db: dashboardDb,
3516
- artifactsDir: resolve7(configDir, dashArtifactsDir),
3657
+ artifactsDir: resolve9(configDir, dashArtifactsDir),
3517
3658
  redactor: runtimeSecrets.secretRedactor,
3518
3659
  onRunCreated: (runId) => {
3519
3660
  currentRunId = runId;
@@ -3521,7 +3662,7 @@ Found ${filtered.length} test(s):
3521
3662
  }
3522
3663
  }));
3523
3664
  } catch (err) {
3524
- console.warn(import_picocolors6.default.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
3665
+ console.warn(import_picocolors7.default.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
3525
3666
  }
3526
3667
  }
3527
3668
  const analyticsReporter = createAnalyticsRunReporter({ config, surface: "cli" });
@@ -3535,9 +3676,9 @@ Found ${filtered.length} test(s):
3535
3676
  });
3536
3677
  if (opts.cache !== false) {
3537
3678
  const { FileActionCache } = await import("@vostride/agent-qa-core");
3538
- const { resolve: resolve7 } = await import("path");
3679
+ const { resolve: resolve9 } = await import("path");
3539
3680
  actionCache = new FileActionCache({
3540
- dir: resolve7(configDir, config.services?.cache?.dir ?? RUNTIME_CACHE_DIR),
3681
+ dir: resolve9(configDir, config.services?.cache?.dir ?? RUNTIME_CACHE_DIR),
3541
3682
  ttl: config.services?.cache?.ttl ?? "7d",
3542
3683
  logger: logger.createScopedLogger("cache")
3543
3684
  });
@@ -3550,7 +3691,7 @@ Found ${filtered.length} test(s):
3550
3691
  let circuitBreaker;
3551
3692
  let circuitBreakerTripped = false;
3552
3693
  const memoryGlobalConfig = config.services?.memory;
3553
- const runtimeMemoryRoot = (0, import_agent_qa_core6.resolveMemoryRoot)(config, configDir);
3694
+ const runtimeMemoryRoot = (0, import_agent_qa_core7.resolveMemoryRoot)(config, configDir);
3554
3695
  if (opts.memory !== false && memoryGlobalConfig?.circuitBreakerEnabled !== false && memoryGlobalConfig?.enabled !== false) {
3555
3696
  const { CircuitBreaker } = await import("@vostride/agent-qa-core");
3556
3697
  circuitBreaker = new CircuitBreaker({
@@ -3590,7 +3731,7 @@ Found ${filtered.length} test(s):
3590
3731
  (f) => parseResult.tests.some((t) => t.name === test.name)
3591
3732
  ) ?? "unknown";
3592
3733
  analyticsFilePath = filePath;
3593
- const runId = process.env.AGENT_QA_RUN_ID ?? (0, import_agent_qa_core6.generateRunId)();
3734
+ const runId = process.env.AGENT_QA_RUN_ID ?? (0, import_agent_qa_core7.generateRunId)();
3594
3735
  analyticsRunId = runId;
3595
3736
  analyticsStartedAt = Date.now();
3596
3737
  currentRunId = runId;
@@ -3615,7 +3756,7 @@ Found ${filtered.length} test(s):
3615
3756
  const testMergedConfig = { ...config, use: mergedUse };
3616
3757
  const effectiveCacheEnabled = opts.cache !== false && mergedUse.cache !== false;
3617
3758
  if (opts.configDebug) {
3618
- console.log(import_picocolors6.default.bold(`
3759
+ console.log(import_picocolors7.default.bold(`
3619
3760
  Config for: ${test.name}`));
3620
3761
  console.log(formatConfigDebug(config, test.use, testFlagOverrides));
3621
3762
  console.log();
@@ -3642,7 +3783,7 @@ Config for: ${test.name}`));
3642
3783
  useDeviceName: testMergedUse.device,
3643
3784
  appState: testMergedUse.mobile?.appState,
3644
3785
  localBindings: testLocalBindings,
3645
- configFilePath: (0, import_node_path7.resolve)(configFilePath),
3786
+ configFilePath: (0, import_node_path9.resolve)(configFilePath),
3646
3787
  localConfigFilePath: testLocalBindings?.filePath,
3647
3788
  appiumUrl: process.env.AGENT_QA_APPIUM_URL
3648
3789
  });
@@ -3688,7 +3829,7 @@ Config for: ${test.name}`));
3688
3829
  testMobileResolved = { ...testMobileResolved, appium: { url: appiumUrl, managed: !process.env.AGENT_QA_APPIUM_URL } };
3689
3830
  }
3690
3831
  } catch (err) {
3691
- throw new import_agent_qa_core6.MobileSetupError({
3832
+ throw new import_agent_qa_core7.MobileSetupError({
3692
3833
  category: "appium-startup",
3693
3834
  message: `Could not auto-start Appium: ${err instanceof Error ? err.message : String(err)}`,
3694
3835
  platform: testPlatform,
@@ -3725,7 +3866,7 @@ Config for: ${test.name}`));
3725
3866
  });
3726
3867
  }
3727
3868
  if (effectiveLogLevel === "debug") {
3728
- console.log(import_picocolors6.default.dim(` Platform: ${testPlatform}${useFarm ? ` (farm: ${testResolvedDevice?.transport})` : ""}${testPlatform !== "web" ? " (adapter re-created)" : ""}`));
3869
+ console.log(import_picocolors7.default.dim(` Platform: ${testPlatform}${useFarm ? ` (farm: ${testResolvedDevice?.transport})` : ""}${testPlatform !== "web" ? " (adapter re-created)" : ""}`));
3729
3870
  }
3730
3871
  }
3731
3872
  const mergedForLLM = { registry: config.registry, use: testMergedConfig.use ?? config.use };
@@ -3736,7 +3877,7 @@ Config for: ${test.name}`));
3736
3877
  process.env.AGENT_QA_LLM_MODEL = plannerCfg.model;
3737
3878
  process.env.AGENT_QA_LLM_PROVIDER = plannerCfg.provider;
3738
3879
  if (effectiveLogLevel === "debug") {
3739
- console.log(import_picocolors6.default.dim(` Model: ${plannerCfg.model} (${plannerCfg.provider})`));
3880
+ console.log(import_picocolors7.default.dim(` Model: ${plannerCfg.model} (${plannerCfg.provider})`));
3740
3881
  }
3741
3882
  const plannerModel = await createModel(plannerModelConfig);
3742
3883
  const verifierModel = await createModel(verifierModelConfig);
@@ -3901,7 +4042,7 @@ Config for: ${test.name}`));
3901
4042
  const testId = testDef["test-id"] ?? testDef.name;
3902
4043
  memoryInitParams = { product, testId, memoryRoot };
3903
4044
  } catch (err) {
3904
- console.warn(` ${import_picocolors6.default.dim("Memory:")} init error -- ${err.message}`);
4045
+ console.warn(` ${import_picocolors7.default.dim("Memory:")} init error -- ${err.message}`);
3905
4046
  }
3906
4047
  }
3907
4048
  const result = await runTestWithRetry(testDef, {
@@ -3995,7 +4136,7 @@ Config for: ${test.name}`));
3995
4136
  try {
3996
4137
  const { shouldAblate, collectAllInjectedIds, deprecateOnFailure } = await import("@vostride/agent-qa-core");
3997
4138
  if (shouldAblate(result, memoryProvider)) {
3998
- console.log(` ${import_picocolors6.default.dim("Memory:")} ablation retry -- re-running without memory...`);
4139
+ console.log(` ${import_picocolors7.default.dim("Memory:")} ablation retry -- re-running without memory...`);
3999
4140
  const ablationAdapter = await createPlatformAdapter(testPlatform);
4000
4141
  const mergedBrowser = mergedUse.browser;
4001
4142
  const ablationPlatformConfig = buildPlatformConfig(testPlatform, testResolvedDevice, testTimeoutsForSetup, mergedBrowser ?? config.use?.browser, testMergedConfig.use?.logCapture ?? config.use?.logCapture, testFarmSession, testMobileResolved);
@@ -4040,7 +4181,7 @@ Config for: ${test.name}`));
4040
4181
  circuitBreaker.record({ withMemory: false, passed: ablationResult.status === "passed" });
4041
4182
  }
4042
4183
  if (ablationResult.status === "passed") {
4043
- console.log(` ${import_picocolors6.default.dim("Memory:")} ablation confirmed memory caused failure -- penalizing observations`);
4184
+ console.log(` ${import_picocolors7.default.dim("Memory:")} ablation confirmed memory caused failure -- penalizing observations`);
4044
4185
  const injectedMap = collectAllInjectedIds(result, memoryProvider);
4045
4186
  await deprecateOnFailure({
4046
4187
  testResult: result,
@@ -4051,14 +4192,14 @@ Config for: ${test.name}`));
4051
4192
  });
4052
4193
  ablationHandledDeprecation = true;
4053
4194
  } else {
4054
- console.log(` ${import_picocolors6.default.dim("Memory:")} ablation retry also failed -- memory not the cause`);
4195
+ console.log(` ${import_picocolors7.default.dim("Memory:")} ablation retry also failed -- memory not the cause`);
4055
4196
  }
4056
4197
  } finally {
4057
4198
  await ablationAdapter.cleanup();
4058
4199
  }
4059
4200
  }
4060
4201
  } catch (err) {
4061
- console.warn(` ${import_picocolors6.default.dim("Memory:")} ablation error -- ${err.message}`);
4202
+ console.warn(` ${import_picocolors7.default.dim("Memory:")} ablation error -- ${err.message}`);
4062
4203
  }
4063
4204
  }
4064
4205
  if (memoryProvider && memoryConfig?.curatorEnabled !== false && !ablationHandledDeprecation) {
@@ -4092,7 +4233,7 @@ Config for: ${test.name}`));
4092
4233
  }
4093
4234
  }
4094
4235
  } catch (err) {
4095
- console.warn(` ${import_picocolors6.default.dim("Memory:")} curator error -- ${err.message}`);
4236
+ console.warn(` ${import_picocolors7.default.dim("Memory:")} curator error -- ${err.message}`);
4096
4237
  }
4097
4238
  }
4098
4239
  results.push(result);
@@ -4103,13 +4244,13 @@ Config for: ${test.name}`));
4103
4244
  }
4104
4245
  if (circuitBreaker?.isTripped() && !circuitBreakerTripped) {
4105
4246
  circuitBreakerTripped = true;
4106
- console.log(` ${import_picocolors6.default.yellow("Memory: circuit breaker tripped -- disabling memory injection for remaining tests")}`);
4247
+ console.log(` ${import_picocolors7.default.yellow("Memory: circuit breaker tripped -- disabling memory injection for remaining tests")}`);
4107
4248
  }
4108
4249
  if (result.status === "failed" && actionCache && effectiveCacheEnabled && result.steps.length > 0) {
4109
4250
  const { hashStepInstruction: hashStepInstruction2 } = await import("@vostride/agent-qa-core");
4110
4251
  const { rm: rm3 } = await import("fs/promises");
4111
- const { resolve: resolve7, join: join8 } = await import("path");
4112
- const cacheDir = resolve7(configDir, config.services?.cache?.dir ?? RUNTIME_CACHE_DIR);
4252
+ const { resolve: resolve9, join: join8 } = await import("path");
4253
+ const cacheDir = resolve9(configDir, config.services?.cache?.dir ?? RUNTIME_CACHE_DIR);
4113
4254
  const cacheLog = logger.createScopedLogger("cache");
4114
4255
  let purged = 0;
4115
4256
  for (let i = 0; i < result.steps.length; i++) {
@@ -4188,13 +4329,13 @@ Config for: ${test.name}`));
4188
4329
  }
4189
4330
  const parallelBatchFailed = await flushParallelRuns();
4190
4331
  if (opts.bail && parallelBatchFailed) {
4191
- console.log(import_picocolors6.default.yellow("\nBailing out \u2014 stopping after first failure"));
4332
+ console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failure"));
4192
4333
  bailTriggered = true;
4193
4334
  break;
4194
4335
  }
4195
4336
  const outcome = await runOneDirectTest(test);
4196
4337
  if (opts.bail && outcome.failed) {
4197
- console.log(import_picocolors6.default.yellow("\nBailing out \u2014 stopping after first failure"));
4338
+ console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failure"));
4198
4339
  bailTriggered = true;
4199
4340
  break;
4200
4341
  }
@@ -4202,7 +4343,7 @@ Config for: ${test.name}`));
4202
4343
  if (!bailTriggered) {
4203
4344
  const parallelBatchFailed = await flushParallelRuns();
4204
4345
  if (opts.bail && parallelBatchFailed) {
4205
- console.log(import_picocolors6.default.yellow("\nBailing out \u2014 stopping after first failure"));
4346
+ console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failure"));
4206
4347
  }
4207
4348
  }
4208
4349
  releaseAppium();
@@ -4221,7 +4362,7 @@ Config for: ${test.name}`));
4221
4362
  const errMsg = err instanceof Error ? err.message : String(err);
4222
4363
  const errStack = err instanceof Error ? err.stack ?? "" : "";
4223
4364
  const formattedError = formatFrameworkError(errMsg);
4224
- console.error(import_picocolors6.default.red(formattedError.displayMessage));
4365
+ console.error(import_picocolors7.default.red(formattedError.displayMessage));
4225
4366
  if (_crashDb) {
4226
4367
  const fullError = `${errMsg}
4227
4368
  ${errStack}`;
@@ -4272,11 +4413,11 @@ ${errStack}`;
4272
4413
  }
4273
4414
 
4274
4415
  // src/commands/dashboard.ts
4275
- var import_commander6 = require("commander");
4276
- var import_node_path8 = require("path");
4416
+ var import_commander7 = require("commander");
4417
+ var import_node_path10 = require("path");
4277
4418
  var import_node_module2 = require("module");
4278
- var import_picocolors7 = __toESM(require("picocolors"), 1);
4279
- var import_agent_qa_core9 = require("@vostride/agent-qa-core");
4419
+ var import_picocolors8 = __toESM(require("picocolors"), 1);
4420
+ var import_agent_qa_core10 = require("@vostride/agent-qa-core");
4280
4421
  init_config();
4281
4422
  init_llm_utils();
4282
4423
  init_model_defaults();
@@ -4288,9 +4429,9 @@ async function startDashboardBackedServices(opts, command, labels) {
4288
4429
  const config = await resolveConfig({
4289
4430
  configPath: globalOpts.config
4290
4431
  });
4291
- const configFilePath = (0, import_node_path8.resolve)(globalOpts.config ?? "agent-qa.config.yaml");
4292
- const configDir = (0, import_node_path8.dirname)(configFilePath);
4293
- const workspacePaths = (0, import_agent_qa_core9.resolveWorkspacePaths)({
4432
+ const configFilePath = (0, import_node_path10.resolve)(globalOpts.config ?? "agent-qa.config.yaml");
4433
+ const configDir = (0, import_node_path10.dirname)(configFilePath);
4434
+ const workspacePaths = (0, import_agent_qa_core10.resolveWorkspacePaths)({
4294
4435
  config,
4295
4436
  configPath: configFilePath,
4296
4437
  requireExistingFiles: true
@@ -4329,17 +4470,17 @@ async function startDashboardBackedServices(opts, command, labels) {
4329
4470
  try {
4330
4471
  const require2 = (0, import_node_module2.createRequire)(import_meta3.url);
4331
4472
  const pkgPath = require2.resolve("@vostride/agent-qa-dashboard-ui/package.json");
4332
- uiDir = (0, import_node_path8.resolve)((0, import_node_path8.dirname)(pkgPath), "dist");
4473
+ uiDir = (0, import_node_path10.resolve)((0, import_node_path10.dirname)(pkgPath), "dist");
4333
4474
  } catch {
4334
4475
  if (globalOpts.verbose) {
4335
- console.log(import_picocolors7.default.yellow("Warning: @vostride/agent-qa-dashboard-ui not found \u2014 API-only mode"));
4476
+ console.log(import_picocolors8.default.yellow("Warning: @vostride/agent-qa-dashboard-ui not found \u2014 API-only mode"));
4336
4477
  }
4337
4478
  }
4338
4479
  const { close } = await startServer({
4339
4480
  db,
4340
4481
  port,
4341
4482
  uiDir,
4342
- artifactsDir: (0, import_node_path8.resolve)(configDir, artifactsDir),
4483
+ artifactsDir: (0, import_node_path10.resolve)(configDir, artifactsDir),
4343
4484
  workspacePaths,
4344
4485
  configPath: configFilePath,
4345
4486
  mcp: mcpConfig,
@@ -4355,22 +4496,22 @@ async function startDashboardBackedServices(opts, command, labels) {
4355
4496
  const shutdown = async () => {
4356
4497
  if (shuttingDown) return;
4357
4498
  shuttingDown = true;
4358
- console.log(import_picocolors7.default.dim(`
4499
+ console.log(import_picocolors8.default.dim(`
4359
4500
  Shutting down ${labels.shutdown}...`));
4360
4501
  close();
4361
- await (0, import_agent_qa_core9.flushAnalytics)({ config }).catch(() => {
4502
+ await (0, import_agent_qa_core10.flushAnalytics)({ config }).catch(() => {
4362
4503
  });
4363
4504
  process.exit(0);
4364
4505
  };
4365
4506
  process.on("SIGINT", shutdown);
4366
4507
  process.on("SIGTERM", shutdown);
4367
4508
  } catch (err) {
4368
- console.error(import_picocolors7.default.red(`${labels.error} error: ${err instanceof Error ? err.message : String(err)}`));
4509
+ console.error(import_picocolors8.default.red(`${labels.error} error: ${err instanceof Error ? err.message : String(err)}`));
4369
4510
  process.exit(2);
4370
4511
  }
4371
4512
  }
4372
4513
  function createDashboardCommand() {
4373
- const cmd = new import_commander6.Command("dashboard").description("Start the dashboard web server").option("--port <number>", "server port (default: 3470)").option("--db <path>", "database file path").option("--open", "open browser after start").action(async (opts, command) => {
4514
+ const cmd = new import_commander7.Command("dashboard").description("Start the dashboard web server").option("--port <number>", "server port (default: 3470)").option("--db <path>", "database file path").option("--open", "open browser after start").action(async (opts, command) => {
4374
4515
  await startDashboardBackedServices(opts, command, {
4375
4516
  error: "Dashboard",
4376
4517
  shutdown: "dashboard"
@@ -4379,7 +4520,7 @@ function createDashboardCommand() {
4379
4520
  return cmd;
4380
4521
  }
4381
4522
  function createServeCommand() {
4382
- const cmd = new import_commander6.Command("serve").description("Start configured local agent-qa services").action(async (_opts, command) => {
4523
+ const cmd = new import_commander7.Command("serve").description("Start configured local agent-qa services").action(async (_opts, command) => {
4383
4524
  await startDashboardBackedServices({}, command, {
4384
4525
  error: "Serve",
4385
4526
  shutdown: "local services"
@@ -4389,12 +4530,12 @@ function createServeCommand() {
4389
4530
  }
4390
4531
 
4391
4532
  // src/commands/mcp.ts
4392
- var import_commander7 = require("commander");
4393
- var import_picocolors8 = __toESM(require("picocolors"), 1);
4394
- var import_agent_qa_core10 = require("@vostride/agent-qa-core");
4533
+ var import_commander8 = require("commander");
4534
+ var import_picocolors9 = __toESM(require("picocolors"), 1);
4535
+ var import_agent_qa_core11 = require("@vostride/agent-qa-core");
4395
4536
  init_config();
4396
4537
  function createMcpCommand() {
4397
- const cmd = new import_commander7.Command("mcp").description("Start MCP (Model Context Protocol) server over stdio").action(async (command) => {
4538
+ const cmd = new import_commander8.Command("mcp").description("Start MCP (Model Context Protocol) server over stdio").action(async (command) => {
4398
4539
  try {
4399
4540
  const program2 = command.parent;
4400
4541
  const globalOpts = program2?.opts() ?? {};
@@ -4408,11 +4549,11 @@ function createMcpCommand() {
4408
4549
  try {
4409
4550
  await startMcpServer({ analyticsConfig: config });
4410
4551
  } finally {
4411
- await (0, import_agent_qa_core10.flushAnalytics)(config ? { config } : void 0).catch(() => {
4552
+ await (0, import_agent_qa_core11.flushAnalytics)(config ? { config } : void 0).catch(() => {
4412
4553
  });
4413
4554
  }
4414
4555
  } catch (err) {
4415
- console.error(import_picocolors8.default.red("Failed to start MCP server:"), err instanceof Error ? err.message : String(err));
4556
+ console.error(import_picocolors9.default.red("Failed to start MCP server:"), err instanceof Error ? err.message : String(err));
4416
4557
  process.exitCode = 1;
4417
4558
  }
4418
4559
  });
@@ -4420,8 +4561,8 @@ function createMcpCommand() {
4420
4561
  }
4421
4562
 
4422
4563
  // src/commands/config.ts
4423
- var import_commander8 = require("commander");
4424
- var import_picocolors9 = __toESM(require("picocolors"), 1);
4564
+ var import_commander9 = require("commander");
4565
+ var import_picocolors10 = __toESM(require("picocolors"), 1);
4425
4566
  var import_yaml5 = require("yaml");
4426
4567
  var import_promises3 = require("fs/promises");
4427
4568
  var import_node_readline2 = require("readline");
@@ -4451,10 +4592,10 @@ async function readStdin() {
4451
4592
  }
4452
4593
  function plainPrompt(question) {
4453
4594
  const rl = (0, import_node_readline2.createInterface)({ input: process.stdin, output: process.stdout });
4454
- return new Promise((resolve7) => {
4595
+ return new Promise((resolve9) => {
4455
4596
  rl.question(question, (answer) => {
4456
4597
  rl.close();
4457
- resolve7(answer.trim());
4598
+ resolve9(answer.trim());
4458
4599
  });
4459
4600
  });
4460
4601
  }
@@ -4467,14 +4608,14 @@ async function getValue(key, providedValue) {
4467
4608
  }
4468
4609
  async function setAction(key, value) {
4469
4610
  if (isSensitiveKey(key)) {
4470
- console.error(import_picocolors9.default.red("Credential values are not written by config set."));
4611
+ console.error(import_picocolors10.default.red("Credential values are not written by config set."));
4471
4612
  console.error("Use agent-qa auth set --config <name> --type api-key|bearer-token for compatible or Gemini configs.");
4472
4613
  console.error("Use plugins.auth plus the dashboard auth flow for subscription configs.");
4473
4614
  return;
4474
4615
  }
4475
4616
  const resolved = await getValue(key, value);
4476
4617
  if (!resolved) {
4477
- console.error(import_picocolors9.default.red("No value provided."));
4618
+ console.error(import_picocolors10.default.red("No value provided."));
4478
4619
  return;
4479
4620
  }
4480
4621
  const configPath = "agent-qa.config.yaml";
@@ -4491,7 +4632,7 @@ async function setAction(key, value) {
4491
4632
  }
4492
4633
  doc.setIn(key.split("."), resolved);
4493
4634
  await (0, import_promises3.writeFile)(configPath, doc.toString());
4494
- console.log(import_picocolors9.default.green(`\u2713 ${key} = ${resolved} saved to agent-qa.config.yaml`));
4635
+ console.log(import_picocolors10.default.green(`\u2713 ${key} = ${resolved} saved to agent-qa.config.yaml`));
4495
4636
  }
4496
4637
  async function showAction() {
4497
4638
  const fileConfig = await loadConfigFile("agent-qa.config.yaml");
@@ -4499,20 +4640,20 @@ async function showAction() {
4499
4640
  const { mergeConfigs: mergeConfigs2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4500
4641
  const globalConfig = mergeConfigs2(fileConfig, envOverrides, {});
4501
4642
  const table = formatConfigDebug(globalConfig, {}, {});
4502
- console.log(import_picocolors9.default.bold("\nResolved Configuration:\n"));
4643
+ console.log(import_picocolors10.default.bold("\nResolved Configuration:\n"));
4503
4644
  console.log(table);
4504
4645
  console.log();
4505
4646
  }
4506
4647
  function createConfigCommand() {
4507
- const cmd = new import_commander8.Command("config").description("Manage configuration");
4648
+ const cmd = new import_commander9.Command("config").description("Manage configuration");
4508
4649
  cmd.command("set <key> [value]").description("Set a config value").action(setAction);
4509
4650
  cmd.command("show").description("Show resolved config with source attribution").action(showAction);
4510
4651
  return cmd;
4511
4652
  }
4512
4653
 
4513
4654
  // src/commands/queue.ts
4514
- var import_commander9 = require("commander");
4515
- var import_picocolors10 = __toESM(require("picocolors"), 1);
4655
+ var import_commander10 = require("commander");
4656
+ var import_picocolors11 = __toESM(require("picocolors"), 1);
4516
4657
  init_config();
4517
4658
  function timeAgo(dateStr) {
4518
4659
  const diff = Date.now() - new Date(dateStr).getTime();
@@ -4536,7 +4677,7 @@ async function resolveServerUrl(opts, globalOpts) {
4536
4677
  }
4537
4678
  }
4538
4679
  function createQueueCommand() {
4539
- const cmd = new import_commander9.Command("queue").description("Manage the test execution queue");
4680
+ const cmd = new import_commander10.Command("queue").description("Manage the test execution queue");
4540
4681
  cmd.command("list").description("Show pending and running jobs in the queue").option("--json", "output raw JSON").option("--all", "include recently completed jobs").option("--server <url>", "dashboard server URL").action(async (opts, command) => {
4541
4682
  try {
4542
4683
  const program2 = command.parent.parent;
@@ -4547,13 +4688,13 @@ function createQueueCommand() {
4547
4688
  try {
4548
4689
  const response = await fetch(endpoint);
4549
4690
  if (!response.ok) {
4550
- console.error(import_picocolors10.default.red(`Server returned ${response.status}: ${response.statusText}`));
4691
+ console.error(import_picocolors11.default.red(`Server returned ${response.status}: ${response.statusText}`));
4551
4692
  process.exit(1);
4552
4693
  }
4553
4694
  data = await response.json();
4554
4695
  } catch {
4555
- console.error(import_picocolors10.default.red(`Could not connect to dashboard server at ${serverUrl}`));
4556
- console.error(import_picocolors10.default.dim("Make sure the dashboard is running: agent-qa dashboard"));
4696
+ console.error(import_picocolors11.default.red(`Could not connect to dashboard server at ${serverUrl}`));
4697
+ console.error(import_picocolors11.default.dim("Make sure the dashboard is running: agent-qa dashboard"));
4557
4698
  process.exit(1);
4558
4699
  }
4559
4700
  if (opts.json) {
@@ -4561,19 +4702,19 @@ function createQueueCommand() {
4561
4702
  return;
4562
4703
  }
4563
4704
  console.log();
4564
- console.log(import_picocolors10.default.bold("Queue Status"));
4565
- console.log(` Concurrency: ${import_picocolors10.default.blue(String(data.concurrency))} slots (${import_picocolors10.default.yellow(String(data.activeSlots))} active)`);
4705
+ console.log(import_picocolors11.default.bold("Queue Status"));
4706
+ console.log(` Concurrency: ${import_picocolors11.default.blue(String(data.concurrency))} slots (${import_picocolors11.default.yellow(String(data.activeSlots))} active)`);
4566
4707
  console.log();
4567
4708
  const pending = data.pending?.jobs ?? [];
4568
4709
  const running = data.running?.jobs ?? [];
4569
4710
  if (pending.length === 0 && running.length === 0) {
4570
- console.log(import_picocolors10.default.dim(" Queue is empty \u2014 no pending or running jobs"));
4711
+ console.log(import_picocolors11.default.dim(" Queue is empty \u2014 no pending or running jobs"));
4571
4712
  console.log();
4572
4713
  return;
4573
4714
  }
4574
4715
  if (pending.length > 0) {
4575
- console.log(import_picocolors10.default.bold(`Pending (${pending.length} jobs):`));
4576
- console.log(import_picocolors10.default.dim(" # ID Name Priority Source Queued"));
4716
+ console.log(import_picocolors11.default.bold(`Pending (${pending.length} jobs):`));
4717
+ console.log(import_picocolors11.default.dim(" # ID Name Priority Source Queued"));
4577
4718
  for (let i = 0; i < pending.length; i++) {
4578
4719
  const job = pending[i];
4579
4720
  const num = String(i + 1).padEnd(3);
@@ -4582,37 +4723,37 @@ function createQueueCommand() {
4582
4723
  const priority = String(job.priority ?? 0).padEnd(8);
4583
4724
  const source = (job.source ?? "").padEnd(10);
4584
4725
  const queued = job.createdAt ? timeAgo(job.createdAt) : "";
4585
- console.log(` ${num} ${import_picocolors10.default.dim(id)} ${name} ${priority} ${source} ${import_picocolors10.default.dim(queued)}`);
4726
+ console.log(` ${num} ${import_picocolors11.default.dim(id)} ${name} ${priority} ${source} ${import_picocolors11.default.dim(queued)}`);
4586
4727
  }
4587
4728
  console.log();
4588
4729
  }
4589
4730
  if (running.length > 0) {
4590
- console.log(import_picocolors10.default.bold(`Running (${running.length} jobs):`));
4591
- console.log(import_picocolors10.default.dim(" ID Name Source Started"));
4731
+ console.log(import_picocolors11.default.bold(`Running (${running.length} jobs):`));
4732
+ console.log(import_picocolors11.default.dim(" ID Name Source Started"));
4592
4733
  for (const job of running) {
4593
4734
  const id = (job.id ?? "").slice(0, 12).padEnd(12);
4594
4735
  const name = (job.name ?? "").slice(0, 18).padEnd(18);
4595
4736
  const source = (job.source ?? "").padEnd(10);
4596
4737
  const started = job.startedAt ? timeAgo(job.startedAt) : "";
4597
- console.log(` ${import_picocolors10.default.green(id)} ${name} ${source} ${import_picocolors10.default.dim(started)}`);
4738
+ console.log(` ${import_picocolors11.default.green(id)} ${name} ${source} ${import_picocolors11.default.dim(started)}`);
4598
4739
  }
4599
4740
  console.log();
4600
4741
  }
4601
4742
  if (opts.all && data.recent?.length > 0) {
4602
- console.log(import_picocolors10.default.bold(`Recent (${data.recent.length} jobs):`));
4603
- console.log(import_picocolors10.default.dim(" ID Name Status Duration"));
4743
+ console.log(import_picocolors11.default.bold(`Recent (${data.recent.length} jobs):`));
4744
+ console.log(import_picocolors11.default.dim(" ID Name Status Duration"));
4604
4745
  for (const job of data.recent) {
4605
4746
  const id = (job.id ?? "").slice(0, 12).padEnd(12);
4606
4747
  const name = (job.name ?? "").slice(0, 18).padEnd(18);
4607
4748
  const status = (job.status ?? "").padEnd(8);
4608
4749
  const duration = job.duration ? `${Math.round(job.duration / 1e3)}s` : "";
4609
- const statusColor = job.status === "passed" ? import_picocolors10.default.green : job.status === "failed" ? import_picocolors10.default.red : import_picocolors10.default.dim;
4610
- console.log(` ${import_picocolors10.default.dim(id)} ${name} ${statusColor(status)} ${import_picocolors10.default.dim(duration)}`);
4750
+ const statusColor = job.status === "passed" ? import_picocolors11.default.green : job.status === "failed" ? import_picocolors11.default.red : import_picocolors11.default.dim;
4751
+ console.log(` ${import_picocolors11.default.dim(id)} ${name} ${statusColor(status)} ${import_picocolors11.default.dim(duration)}`);
4611
4752
  }
4612
4753
  console.log();
4613
4754
  }
4614
4755
  } catch (err) {
4615
- console.error(import_picocolors10.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
4756
+ console.error(import_picocolors11.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
4616
4757
  process.exit(1);
4617
4758
  }
4618
4759
  });
@@ -4625,25 +4766,25 @@ function createQueueCommand() {
4625
4766
  try {
4626
4767
  response = await fetch(`${serverUrl}/api/runs/${runId}/cancel`, { method: "POST" });
4627
4768
  } catch {
4628
- console.error(import_picocolors10.default.red(`Could not connect to dashboard server at ${serverUrl}`));
4629
- console.error(import_picocolors10.default.dim("Make sure the dashboard is running: agent-qa dashboard"));
4769
+ console.error(import_picocolors11.default.red(`Could not connect to dashboard server at ${serverUrl}`));
4770
+ console.error(import_picocolors11.default.dim("Make sure the dashboard is running: agent-qa dashboard"));
4630
4771
  process.exit(1);
4631
4772
  }
4632
4773
  if (response.ok) {
4633
4774
  const data = await response.json();
4634
4775
  if (data.cancelled) {
4635
- console.log(import_picocolors10.default.green(`\u2713 Cancelled job ${runId}`));
4776
+ console.log(import_picocolors11.default.green(`\u2713 Cancelled job ${runId}`));
4636
4777
  } else {
4637
- console.log(import_picocolors10.default.red(`Failed to cancel: ${data.error ?? "Unknown error"}`));
4778
+ console.log(import_picocolors11.default.red(`Failed to cancel: ${data.error ?? "Unknown error"}`));
4638
4779
  }
4639
4780
  } else if (response.status === 404) {
4640
- console.log(import_picocolors10.default.red(`Job not found: ${runId}`));
4781
+ console.log(import_picocolors11.default.red(`Job not found: ${runId}`));
4641
4782
  } else {
4642
4783
  const data = await response.json().catch(() => ({}));
4643
- console.log(import_picocolors10.default.red(`Failed to cancel: ${data.error ?? response.statusText}`));
4784
+ console.log(import_picocolors11.default.red(`Failed to cancel: ${data.error ?? response.statusText}`));
4644
4785
  }
4645
4786
  } catch (err) {
4646
- console.error(import_picocolors10.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
4787
+ console.error(import_picocolors11.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
4647
4788
  process.exit(1);
4648
4789
  }
4649
4790
  });
@@ -4651,24 +4792,24 @@ function createQueueCommand() {
4651
4792
  }
4652
4793
 
4653
4794
  // src/commands/validate.ts
4654
- var import_commander10 = require("commander");
4655
- var import_agent_qa_core11 = require("@vostride/agent-qa-core");
4795
+ var import_commander11 = require("commander");
4796
+ var import_agent_qa_core12 = require("@vostride/agent-qa-core");
4656
4797
  init_config();
4657
4798
  function createValidateCommand() {
4658
- const cmd = new import_commander10.Command("validate").description("Validate config, test files, and suite references").argument("[files...]", "specific files to validate (default: auto-discover all)").action(async (files, command) => {
4799
+ const cmd = new import_commander11.Command("validate").description("Validate config, test files, and suite references").argument("[files...]", "specific files to validate (default: auto-discover all)").action(async (files, command) => {
4659
4800
  const configPath = command.parent?.opts().config ?? "agent-qa.config.yaml";
4660
4801
  let workspace;
4661
4802
  try {
4662
4803
  const config = await resolveConfig({ configPath });
4663
- workspace = (0, import_agent_qa_core11.resolveWorkspacePaths)({ config, configPath });
4804
+ workspace = (0, import_agent_qa_core12.resolveWorkspacePaths)({ config, configPath });
4664
4805
  } catch {
4665
4806
  }
4666
- const result = await (0, import_agent_qa_core11.validateProject)({
4807
+ const result = await (0, import_agent_qa_core12.validateProject)({
4667
4808
  configPath,
4668
4809
  files: files.length > 0 ? files : void 0,
4669
4810
  workspace
4670
4811
  });
4671
- const output = (0, import_agent_qa_core11.formatDiagnostics)(result);
4812
+ const output = (0, import_agent_qa_core12.formatDiagnostics)(result);
4672
4813
  process.stdout.write(output);
4673
4814
  if (result.errorCount > 0) {
4674
4815
  process.exitCode = 1;
@@ -4678,9 +4819,9 @@ function createValidateCommand() {
4678
4819
  }
4679
4820
 
4680
4821
  // src/commands/auth.ts
4681
- var import_commander11 = require("commander");
4822
+ var import_commander12 = require("commander");
4682
4823
  var import_node_child_process4 = require("child_process");
4683
- var import_picocolors11 = __toESM(require("picocolors"), 1);
4824
+ var import_picocolors12 = __toESM(require("picocolors"), 1);
4684
4825
  var import_input2 = __toESM(require("@inquirer/input"), 1);
4685
4826
  var import_password = __toESM(require("@inquirer/password"), 1);
4686
4827
  var PROVIDER_LABELS = {
@@ -4715,10 +4856,10 @@ function getGlobalConfigPath2(command) {
4715
4856
  return command.parent?.parent?.opts().config ?? command.parent?.opts().config;
4716
4857
  }
4717
4858
  function printCredentialGuidance(configName, provider) {
4718
- console.log(import_picocolors11.default.red(`Config ${configName} uses ${providerLabel(provider)} credentials.`));
4719
- console.log(import_picocolors11.default.dim("Use agent-qa auth set --config <name> --type api-key"));
4859
+ console.log(import_picocolors12.default.red(`Config ${configName} uses ${providerLabel(provider)} credentials.`));
4860
+ console.log(import_picocolors12.default.dim("Use agent-qa auth set --config <name> --type api-key"));
4720
4861
  if (provider === "anthropic-compatible") {
4721
- console.log(import_picocolors11.default.dim("or --type bearer-token"));
4862
+ console.log(import_picocolors12.default.dim("or --type bearer-token"));
4722
4863
  }
4723
4864
  }
4724
4865
  function applyAuthToModelConfig(llmConfig, auth) {
@@ -4738,15 +4879,15 @@ function applyAuthToModelConfig(llmConfig, auth) {
4738
4879
  function statusForResolvedAuth(auth) {
4739
4880
  switch (auth.kind) {
4740
4881
  case "api-key":
4741
- return import_picocolors11.default.green("Saved API key");
4882
+ return import_picocolors12.default.green("Saved API key");
4742
4883
  case "bearer-token":
4743
- return import_picocolors11.default.green("Saved bearer token");
4884
+ return import_picocolors12.default.green("Saved bearer token");
4744
4885
  case "auth-fetch":
4745
- return Number(auth.expires ?? Date.now() + 1) <= Date.now() ? import_picocolors11.default.red("Expired") : import_picocolors11.default.green("OAuth connected");
4886
+ return Number(auth.expires ?? Date.now() + 1) <= Date.now() ? import_picocolors12.default.red("Expired") : import_picocolors12.default.green("OAuth connected");
4746
4887
  case "unauthenticated":
4747
- return import_picocolors11.default.dim("No credential");
4888
+ return import_picocolors12.default.dim("No credential");
4748
4889
  case "missing":
4749
- return import_picocolors11.default.red("Missing credential");
4890
+ return import_picocolors12.default.red("Missing credential");
4750
4891
  }
4751
4892
  }
4752
4893
  function extractProviderError(err) {
@@ -4777,16 +4918,16 @@ function openAuthorizationUrl(url) {
4777
4918
  });
4778
4919
  }
4779
4920
  function printMissingSubscriptionPluginGuidance(configName, provider) {
4780
- console.log(import_picocolors11.default.red(`Provider "${provider}" is configured for "${configName}", but no auth plugin is registered.`));
4781
- console.log(import_picocolors11.default.dim(`Config: ${configName}`));
4782
- console.log(import_picocolors11.default.dim(`Provider: ${provider}`));
4783
- console.log(import_picocolors11.default.dim("Add the subscription auth plugin to package.json and install with your package manager:"));
4784
- console.log(import_picocolors11.default.dim(' "devDependencies": { "@vostride/agent-qa-subscription-auth": "<agent-qa version>" }'));
4785
- console.log(import_picocolors11.default.dim("Declare it in agent-qa.config.yaml under plugins.auth:"));
4786
- console.log(import_picocolors11.default.dim(" plugins:"));
4787
- console.log(import_picocolors11.default.dim(" auth:"));
4788
- console.log(import_picocolors11.default.dim(" - package: @vostride/agent-qa-subscription-auth"));
4789
- console.log(import_picocolors11.default.dim("Then authenticate from agent-qa dashboard or rerun this command."));
4921
+ console.log(import_picocolors12.default.red(`Provider "${provider}" is configured for "${configName}", but no auth plugin is registered.`));
4922
+ console.log(import_picocolors12.default.dim(`Config: ${configName}`));
4923
+ console.log(import_picocolors12.default.dim(`Provider: ${provider}`));
4924
+ console.log(import_picocolors12.default.dim("Add the subscription auth plugin to package.json and install with your package manager:"));
4925
+ console.log(import_picocolors12.default.dim(' "devDependencies": { "@vostride/agent-qa-subscription-auth": "<agent-qa version>" }'));
4926
+ console.log(import_picocolors12.default.dim("Declare it in agent-qa.config.yaml under plugins.auth:"));
4927
+ console.log(import_picocolors12.default.dim(" plugins:"));
4928
+ console.log(import_picocolors12.default.dim(" auth:"));
4929
+ console.log(import_picocolors12.default.dim(" - package: @vostride/agent-qa-subscription-auth"));
4930
+ console.log(import_picocolors12.default.dim("Then authenticate from agent-qa dashboard or rerun this command."));
4790
4931
  }
4791
4932
  async function readTokensFromPlugin(provider, plugin, started) {
4792
4933
  if (started.waitForTokens) {
@@ -4794,19 +4935,19 @@ async function readTokensFromPlugin(provider, plugin, started) {
4794
4935
  }
4795
4936
  if (plugin.dashboardAuth.mode === "manual-code") {
4796
4937
  if (!plugin.exchangeCode) {
4797
- console.log(import_picocolors11.default.red(`Provider "${provider}" does not support CLI code exchange. Use agent-qa dashboard.`));
4938
+ console.log(import_picocolors12.default.red(`Provider "${provider}" does not support CLI code exchange. Use agent-qa dashboard.`));
4798
4939
  process.exitCode = 1;
4799
4940
  return null;
4800
4941
  }
4801
4942
  const code = (await (0, import_input2.default)({ message: "Authorization code:" })).trim();
4802
4943
  if (!code) {
4803
- console.log(import_picocolors11.default.red("Authorization code is required"));
4944
+ console.log(import_picocolors12.default.red("Authorization code is required"));
4804
4945
  process.exitCode = 1;
4805
4946
  return null;
4806
4947
  }
4807
4948
  return await plugin.exchangeCode({ code, sessionState: started.sessionState });
4808
4949
  }
4809
- console.log(import_picocolors11.default.red(`Provider "${provider}" does not support CLI auth login. Use agent-qa dashboard.`));
4950
+ console.log(import_picocolors12.default.red(`Provider "${provider}" does not support CLI auth login. Use agent-qa dashboard.`));
4810
4951
  process.exitCode = 1;
4811
4952
  return null;
4812
4953
  }
@@ -4819,14 +4960,14 @@ async function runSubscriptionPluginLogin(configName, provider) {
4819
4960
  return;
4820
4961
  }
4821
4962
  if (!plugin.startAuth) {
4822
- console.log(import_picocolors11.default.red(`Provider "${provider}" does not support CLI auth login. Use agent-qa dashboard.`));
4963
+ console.log(import_picocolors12.default.red(`Provider "${provider}" does not support CLI auth login. Use agent-qa dashboard.`));
4823
4964
  process.exitCode = 1;
4824
4965
  return;
4825
4966
  }
4826
4967
  let started;
4827
4968
  try {
4828
4969
  started = await plugin.startAuth({ configName });
4829
- console.log(`Open this URL to authenticate: ${import_picocolors11.default.cyan(started.authorizeUrl)}`);
4970
+ console.log(`Open this URL to authenticate: ${import_picocolors12.default.cyan(started.authorizeUrl)}`);
4830
4971
  openAuthorizationUrl(started.authorizeUrl);
4831
4972
  const tokens = await readTokensFromPlugin(provider, plugin, started);
4832
4973
  if (!tokens) return;
@@ -4835,18 +4976,18 @@ async function runSubscriptionPluginLogin(configName, provider) {
4835
4976
  provider: plugin.credentialProviderId,
4836
4977
  tokens
4837
4978
  });
4838
- console.log(import_picocolors11.default.green(`Authenticated ${configName} with ${plugin.label}`));
4979
+ console.log(import_picocolors12.default.green(`Authenticated ${configName} with ${plugin.label}`));
4839
4980
  } catch (err) {
4840
- console.log(import_picocolors11.default.red(err instanceof Error ? err.message : String(err)));
4981
+ console.log(import_picocolors12.default.red(err instanceof Error ? err.message : String(err)));
4841
4982
  process.exitCode = 1;
4842
4983
  } finally {
4843
4984
  started?.cleanup?.();
4844
4985
  }
4845
4986
  }
4846
4987
  function createAuthCommand() {
4847
- const cmd = new import_commander11.Command("auth").description("Manage LLM config credentials");
4988
+ const cmd = new import_commander12.Command("auth").description("Manage LLM config credentials");
4848
4989
  cmd.addCommand(
4849
- new import_commander11.Command("login").description("Authenticate with a subscription provider").requiredOption("--config <name>", "named subscription LLM config to authenticate").action(async (opts, command) => {
4990
+ new import_commander12.Command("login").description("Authenticate with a subscription provider").requiredOption("--config <name>", "named subscription LLM config to authenticate").action(async (opts, command) => {
4850
4991
  const { resolveNamedConfig: resolveNamedConfig2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
4851
4992
  const configPath = getGlobalConfigPath2(command);
4852
4993
  let provider;
@@ -4854,13 +4995,13 @@ function createAuthCommand() {
4854
4995
  try {
4855
4996
  const requestedName = normalizeConfigName(opts.config);
4856
4997
  if (!requestedName) {
4857
- console.log(import_picocolors11.default.red("configName is required"));
4998
+ console.log(import_picocolors12.default.red("configName is required"));
4858
4999
  return;
4859
5000
  }
4860
5001
  const resolved = await resolveNamedConfig2(requestedName, configPath);
4861
5002
  const resolvedName = normalizeConfigName(resolved.config.name);
4862
5003
  if (!resolvedName) {
4863
- console.log(import_picocolors11.default.red("Config name is invalid"));
5004
+ console.log(import_picocolors12.default.red("Config name is invalid"));
4864
5005
  return;
4865
5006
  }
4866
5007
  configName = resolvedName;
@@ -4871,17 +5012,17 @@ function createAuthCommand() {
4871
5012
  }
4872
5013
  provider = configProvider;
4873
5014
  } catch (err) {
4874
- console.log(import_picocolors11.default.red(err instanceof Error ? err.message : String(err)));
5015
+ console.log(import_picocolors12.default.red(err instanceof Error ? err.message : String(err)));
4875
5016
  return;
4876
5017
  }
4877
5018
  await runSubscriptionPluginLogin(configName, provider);
4878
5019
  })
4879
5020
  );
4880
5021
  cmd.addCommand(
4881
- new import_commander11.Command("set").description("Save a credential for a named LLM config").requiredOption("--config <name>", "named LLM config to save a credential for").requiredOption("--type <type>", "credential type: api-key or bearer-token").argument("[secret]", "credential secret; prompted when omitted").action(async (secretArg, opts, command) => {
5022
+ new import_commander12.Command("set").description("Save a credential for a named LLM config").requiredOption("--config <name>", "named LLM config to save a credential for").requiredOption("--type <type>", "credential type: api-key or bearer-token").argument("[secret]", "credential secret; prompted when omitted").action(async (secretArg, opts, command) => {
4882
5023
  const credentialType = opts.type.trim();
4883
5024
  if (credentialType !== "api-key" && credentialType !== "bearer-token") {
4884
- console.log(import_picocolors11.default.red("type must be api-key or bearer-token"));
5025
+ console.log(import_picocolors12.default.red("type must be api-key or bearer-token"));
4885
5026
  return;
4886
5027
  }
4887
5028
  const { resolveNamedConfig: resolveNamedConfig2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
@@ -4890,44 +5031,44 @@ function createAuthCommand() {
4890
5031
  try {
4891
5032
  resolved = await resolveNamedConfig2(opts.config, configPath);
4892
5033
  } catch (err) {
4893
- console.log(import_picocolors11.default.red(err instanceof Error ? err.message : String(err)));
5034
+ console.log(import_picocolors12.default.red(err instanceof Error ? err.message : String(err)));
4894
5035
  return;
4895
5036
  }
4896
5037
  const configName = resolved.config.name ?? opts.config;
4897
5038
  const provider = resolved.config.provider;
4898
5039
  if (SUBSCRIPTION_PROVIDERS2.has(provider)) {
4899
- console.log(import_picocolors11.default.red("Subscription providers use OAuth login"));
5040
+ console.log(import_picocolors12.default.red("Subscription providers use OAuth login"));
4900
5041
  return;
4901
5042
  }
4902
5043
  if (credentialType === "bearer-token" && provider !== "anthropic-compatible") {
4903
- console.log(import_picocolors11.default.red("bearer-token credentials are only supported for anthropic-compatible configs"));
5044
+ console.log(import_picocolors12.default.red("bearer-token credentials are only supported for anthropic-compatible configs"));
4904
5045
  return;
4905
5046
  }
4906
5047
  if (credentialType === "api-key" && !API_KEY_CREDENTIAL_PROVIDERS.has(provider)) {
4907
- console.log(import_picocolors11.default.red("api-key credentials are not supported for this provider"));
5048
+ console.log(import_picocolors12.default.red("api-key credentials are not supported for this provider"));
4908
5049
  return;
4909
5050
  }
4910
5051
  const secret = (secretArg ?? await (0, import_password.default)({ message: "Secret:" })).trim();
4911
5052
  if (!secret) {
4912
- console.log(import_picocolors11.default.red("Secret is required"));
5053
+ console.log(import_picocolors12.default.red("Secret is required"));
4913
5054
  return;
4914
5055
  }
4915
5056
  const { writeAuth } = await import("@vostride/agent-qa-core");
4916
5057
  if (credentialType === "bearer-token") {
4917
5058
  await writeAuth(configName, { type: "bearer", provider: "anthropic-compatible", token: secret });
4918
- console.log(import_picocolors11.default.green(`Saved bearer token for ${configName}`));
5059
+ console.log(import_picocolors12.default.green(`Saved bearer token for ${configName}`));
4919
5060
  } else {
4920
5061
  await writeAuth(configName, { type: "api", provider, key: secret });
4921
- console.log(import_picocolors11.default.green(`Saved API key for ${configName}`));
5062
+ console.log(import_picocolors12.default.green(`Saved API key for ${configName}`));
4922
5063
  }
4923
5064
  })
4924
5065
  );
4925
5066
  cmd.addCommand(
4926
- new import_commander11.Command("status").description("Show credential status for configured LLMs").action(async function() {
5067
+ new import_commander12.Command("status").description("Show credential status for configured LLMs").action(async function() {
4927
5068
  const { loadAuthPluginsForRawConfig: loadAuthPluginsForRawConfig2, resolveModelAuth: resolveModelAuth2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
4928
5069
  const { loadConfigFile: loadConfigFile2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4929
5070
  const configPath = getGlobalConfigPath2(this) ?? "agent-qa.config.yaml";
4930
- console.log(import_picocolors11.default.bold("Auth status:\n"));
5071
+ console.log(import_picocolors12.default.bold("Auth status:\n"));
4931
5072
  let llms;
4932
5073
  let defaultLLM;
4933
5074
  try {
@@ -4938,25 +5079,25 @@ function createAuthCommand() {
4938
5079
  } catch {
4939
5080
  }
4940
5081
  if (!llms?.length) {
4941
- console.log(import_picocolors11.default.dim(" No LLM configs found"));
5082
+ console.log(import_picocolors12.default.dim(" No LLM configs found"));
4942
5083
  return;
4943
5084
  }
4944
5085
  const maxNameLen = Math.max(...llms.map((c) => String(c.name ?? "").length), 4) + 12;
4945
5086
  for (const llm of llms) {
4946
5087
  const name = String(llm.name ?? "");
4947
5088
  const isDefault = name === defaultLLM;
4948
- const label = isDefault ? `${name} ${import_picocolors11.default.dim("(default)")}` : name;
5089
+ const label = isDefault ? `${name} ${import_picocolors12.default.dim("(default)")}` : name;
4949
5090
  const rawLabel = isDefault ? `${name} (default)` : name;
4950
5091
  const padLen = Math.max(0, maxNameLen - rawLabel.length);
4951
5092
  const dots = ".".repeat(padLen + 2);
4952
5093
  const provider = llm.provider;
4953
5094
  const status = statusForResolvedAuth(await resolveModelAuth2(name, llm));
4954
- console.log(` ${label} ${import_picocolors11.default.dim(`[${providerLabel(provider)}]`)} ${import_picocolors11.default.dim(dots)} ${status}`);
5095
+ console.log(` ${label} ${import_picocolors12.default.dim(`[${providerLabel(provider)}]`)} ${import_picocolors12.default.dim(dots)} ${status}`);
4955
5096
  }
4956
5097
  })
4957
5098
  );
4958
5099
  cmd.addCommand(
4959
- new import_commander11.Command("logout").description("Remove stored credentials for a named config").option("--config <name>", "named LLM config to log out from").action(async (opts, command) => {
5100
+ new import_commander12.Command("logout").description("Remove stored credentials for a named config").option("--config <name>", "named LLM config to log out from").action(async (opts, command) => {
4960
5101
  const { removeAuth } = await import("@vostride/agent-qa-core");
4961
5102
  const { resolveNamedConfig: resolveNamedConfig2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
4962
5103
  const configPath = getGlobalConfigPath2(command);
@@ -4968,16 +5109,16 @@ function createAuthCommand() {
4968
5109
  const resolved = await resolveNamedConfig2(void 0, configPath);
4969
5110
  storeKey = resolved.config.name;
4970
5111
  } catch {
4971
- console.log(import_picocolors11.default.red("Specify --config <name> or run with a config file"));
5112
+ console.log(import_picocolors12.default.red("Specify --config <name> or run with a config file"));
4972
5113
  return;
4973
5114
  }
4974
5115
  }
4975
5116
  await removeAuth(storeKey);
4976
- console.log(import_picocolors11.default.green(`Logged out from ${storeKey}`));
5117
+ console.log(import_picocolors12.default.green(`Logged out from ${storeKey}`));
4977
5118
  })
4978
5119
  );
4979
5120
  cmd.addCommand(
4980
- new import_commander11.Command("test").description("Test LLM connection using named config credentials").option("--config <name>", "named LLM config to test").option("--provider <name>", "override config provider").option("--model <name>", "override config model").action(async (opts, command) => {
5121
+ new import_commander12.Command("test").description("Test LLM connection using named config credentials").option("--config <name>", "named LLM config to test").option("--provider <name>", "override config provider").option("--model <name>", "override config model").action(async (opts, command) => {
4981
5122
  const { resolveModelAuth: resolveModelAuth2, resolveNamedConfig: resolveNamedConfig2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
4982
5123
  const configPath = getGlobalConfigPath2(command);
4983
5124
  let planner;
@@ -4988,19 +5129,19 @@ function createAuthCommand() {
4988
5129
  planner = modelFields;
4989
5130
  configName = name;
4990
5131
  } catch (err) {
4991
- console.log(import_picocolors11.default.red(err instanceof Error ? err.message : String(err)));
5132
+ console.log(import_picocolors12.default.red(err instanceof Error ? err.message : String(err)));
4992
5133
  return;
4993
5134
  }
4994
5135
  if (opts.provider) planner = { ...planner, provider: opts.provider };
4995
5136
  if (opts.model) planner = { ...planner, model: opts.model };
4996
- console.log(import_picocolors11.default.dim(`Testing config: ${configName}...`));
5137
+ console.log(import_picocolors12.default.dim(`Testing config: ${configName}...`));
4997
5138
  const auth = await resolveModelAuth2(configName, planner);
4998
5139
  if (auth.kind === "missing") {
4999
- console.log(import_picocolors11.default.red(auth.message));
5140
+ console.log(import_picocolors12.default.red(auth.message));
5000
5141
  return;
5001
5142
  }
5002
5143
  if (auth.kind === "unauthenticated") {
5003
- console.log(import_picocolors11.default.dim(auth.message));
5144
+ console.log(import_picocolors12.default.dim(auth.message));
5004
5145
  }
5005
5146
  const { createModel, getProviderOptions } = await import("@vostride/agent-qa-core");
5006
5147
  const { generateText } = await import("ai");
@@ -5018,24 +5159,24 @@ function createAuthCommand() {
5018
5159
  ...providerOpts ? { providerOptions: providerOpts } : {}
5019
5160
  });
5020
5161
  const elapsed = Date.now() - start;
5021
- console.log(import_picocolors11.default.green(`Connected to ${planner.provider}/${planner.model} (${elapsed}ms)`));
5162
+ console.log(import_picocolors12.default.green(`Connected to ${planner.provider}/${planner.model} (${elapsed}ms)`));
5022
5163
  } catch (err) {
5023
5164
  const { message, statusCode } = extractProviderError(err);
5024
5165
  const fullContext = `${message} ${statusCode ?? ""}`;
5025
5166
  if (/auth|unauthorized|401|invalid.*key|permission/i.test(fullContext)) {
5026
- console.log(import_picocolors11.default.red("Authentication failed. Check the saved credential for this config."));
5167
+ console.log(import_picocolors12.default.red("Authentication failed. Check the saved credential for this config."));
5027
5168
  } else if (/model.*not.*support|not found|404|does not exist/i.test(fullContext)) {
5028
- console.log(import_picocolors11.default.red("Model not found. Check the model name."));
5169
+ console.log(import_picocolors12.default.red("Model not found. Check the model name."));
5029
5170
  } else if (/rate|429|quota|limit/i.test(fullContext)) {
5030
- console.log(import_picocolors11.default.red("Rate limited. Try again later."));
5171
+ console.log(import_picocolors12.default.red("Rate limited. Try again later."));
5031
5172
  } else if (/ECONNREFUSED|ENOTFOUND|timeout|abort|fetch failed|network/i.test(fullContext)) {
5032
- console.log(import_picocolors11.default.red("Network error. Check the exact base URL and try again."));
5173
+ console.log(import_picocolors12.default.red("Network error. Check the exact base URL and try again."));
5033
5174
  } else if (/invalid_request|400|bad request/i.test(fullContext)) {
5034
- console.log(import_picocolors11.default.red("Invalid request. Check model and provider settings."));
5175
+ console.log(import_picocolors12.default.red("Invalid request. Check model and provider settings."));
5035
5176
  } else {
5036
- console.log(import_picocolors11.default.red(`Connection failed. ${message}`));
5177
+ console.log(import_picocolors12.default.red(`Connection failed. ${message}`));
5037
5178
  }
5038
- console.log(import_picocolors11.default.dim(message));
5179
+ console.log(import_picocolors12.default.dim(message));
5039
5180
  } finally {
5040
5181
  clearTimeout(timeout);
5041
5182
  }
@@ -5045,36 +5186,36 @@ function createAuthCommand() {
5045
5186
  }
5046
5187
 
5047
5188
  // src/commands/devices.ts
5048
- var import_commander12 = require("commander");
5189
+ var import_commander13 = require("commander");
5049
5190
  var import_node_child_process5 = require("child_process");
5050
5191
  var import_node_fs4 = require("fs");
5051
- var import_node_path9 = require("path");
5192
+ var import_node_path11 = require("path");
5052
5193
  var import_yaml6 = require("yaml");
5053
- var import_picocolors12 = __toESM(require("picocolors"), 1);
5194
+ var import_picocolors13 = __toESM(require("picocolors"), 1);
5054
5195
  init_config();
5055
5196
  function formatStatus2(transport, localBinding) {
5056
5197
  if (transport !== "local") {
5057
- return import_picocolors12.default.green("Ready") + import_picocolors12.default.dim(" (no local binding required)");
5198
+ return import_picocolors13.default.green("Ready") + import_picocolors13.default.dim(" (no local binding required)");
5058
5199
  }
5059
5200
  if (!localBinding) {
5060
- return import_picocolors12.default.red("UNBOUND") + import_picocolors12.default.yellow(" - run `agent-qa devices init`");
5201
+ return import_picocolors13.default.red("UNBOUND") + import_picocolors13.default.yellow(" - run `agent-qa devices init`");
5061
5202
  }
5062
5203
  const keys = Object.entries(localBinding).slice(0, 2);
5063
5204
  const details = keys.map(([k, v]) => `${k}: ${v}`).join(", ");
5064
- return import_picocolors12.default.green("Bound") + import_picocolors12.default.dim(` (${details})`);
5205
+ return import_picocolors13.default.green("Bound") + import_picocolors13.default.dim(` (${details})`);
5065
5206
  }
5066
5207
  async function listDevices() {
5067
5208
  let config;
5068
5209
  try {
5069
5210
  config = await resolveConfig({});
5070
5211
  } catch (err) {
5071
- console.error(import_picocolors12.default.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
5212
+ console.error(import_picocolors13.default.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
5072
5213
  process.exitCode = 1;
5073
5214
  return;
5074
5215
  }
5075
5216
  const devices = config.registry?.devices;
5076
5217
  if (!devices || Object.keys(devices).length === 0) {
5077
- console.log(import_picocolors12.default.yellow("No devices configured in registry.devices"));
5218
+ console.log(import_picocolors13.default.yellow("No devices configured in registry.devices"));
5078
5219
  return;
5079
5220
  }
5080
5221
  const localBindings = loadLocalBindings();
@@ -5082,13 +5223,13 @@ async function listDevices() {
5082
5223
  const platWidth = 10;
5083
5224
  const transWidth = 14;
5084
5225
  console.log("");
5085
- console.log(import_picocolors12.default.bold(" Configured Devices"));
5226
+ console.log(import_picocolors13.default.bold(" Configured Devices"));
5086
5227
  console.log("");
5087
5228
  console.log(
5088
- ` ${import_picocolors12.default.dim("Name".padEnd(nameWidth))} ${import_picocolors12.default.dim("Platform".padEnd(platWidth))} ${import_picocolors12.default.dim("Transport".padEnd(transWidth))} ${import_picocolors12.default.dim("Status")}`
5229
+ ` ${import_picocolors13.default.dim("Name".padEnd(nameWidth))} ${import_picocolors13.default.dim("Platform".padEnd(platWidth))} ${import_picocolors13.default.dim("Transport".padEnd(transWidth))} ${import_picocolors13.default.dim("Status")}`
5089
5230
  );
5090
5231
  console.log(
5091
- ` ${import_picocolors12.default.dim("\u2500".repeat(nameWidth))} ${import_picocolors12.default.dim("\u2500".repeat(platWidth))} ${import_picocolors12.default.dim("\u2500".repeat(transWidth))} ${import_picocolors12.default.dim("\u2500".repeat(30))}`
5232
+ ` ${import_picocolors13.default.dim("\u2500".repeat(nameWidth))} ${import_picocolors13.default.dim("\u2500".repeat(platWidth))} ${import_picocolors13.default.dim("\u2500".repeat(transWidth))} ${import_picocolors13.default.dim("\u2500".repeat(30))}`
5092
5233
  );
5093
5234
  for (const [name, profile] of Object.entries(devices)) {
5094
5235
  const localBinding = localBindings?.devices?.[name] ?? null;
@@ -5139,7 +5280,7 @@ function scanSimctlDevices() {
5139
5280
  }
5140
5281
  }
5141
5282
  function appendGitignoreEntry(dir, entry) {
5142
- const gitignorePath = (0, import_node_path9.join)(dir, ".gitignore");
5283
+ const gitignorePath = (0, import_node_path11.join)(dir, ".gitignore");
5143
5284
  let existing = "";
5144
5285
  if ((0, import_node_fs4.existsSync)(gitignorePath)) {
5145
5286
  existing = (0, import_node_fs4.readFileSync)(gitignorePath, "utf-8");
@@ -5150,30 +5291,30 @@ function appendGitignoreEntry(dir, entry) {
5150
5291
  }
5151
5292
  async function initDevices() {
5152
5293
  const dir = process.cwd();
5153
- const localFilePath = (0, import_node_path9.join)(dir, "agent-qa.local.yaml");
5294
+ const localFilePath = (0, import_node_path11.join)(dir, "agent-qa.local.yaml");
5154
5295
  if ((0, import_node_fs4.existsSync)(localFilePath)) {
5155
- console.log(import_picocolors12.default.yellow("agent-qa.local.yaml already exists. Skipping generation."));
5296
+ console.log(import_picocolors13.default.yellow("agent-qa.local.yaml already exists. Skipping generation."));
5156
5297
  return;
5157
5298
  }
5158
- console.log(import_picocolors12.default.bold("\n Scanning for connected devices...\n"));
5299
+ console.log(import_picocolors13.default.bold("\n Scanning for connected devices...\n"));
5159
5300
  const adbDevices = scanAdbDevices();
5160
5301
  const simctlDevices = scanSimctlDevices();
5161
5302
  if (adbDevices.length > 0) {
5162
- console.log(` ${import_picocolors12.default.green("Android:")} found ${adbDevices.length} device(s)`);
5303
+ console.log(` ${import_picocolors13.default.green("Android:")} found ${adbDevices.length} device(s)`);
5163
5304
  for (const d of adbDevices) {
5164
- console.log(` ${import_picocolors12.default.dim("\u2022")} ${d.model} (${d.serial})`);
5305
+ console.log(` ${import_picocolors13.default.dim("\u2022")} ${d.model} (${d.serial})`);
5165
5306
  }
5166
5307
  } else {
5167
- console.log(` ${import_picocolors12.default.dim("Android:")} no devices found (adb not available or no devices connected)`);
5308
+ console.log(` ${import_picocolors13.default.dim("Android:")} no devices found (adb not available or no devices connected)`);
5168
5309
  }
5169
5310
  if (simctlDevices.length > 0) {
5170
5311
  const booted = simctlDevices.filter((d) => d.state === "Booted");
5171
- console.log(` ${import_picocolors12.default.green("iOS:")} found ${simctlDevices.length} simulator(s) (${booted.length} booted)`);
5312
+ console.log(` ${import_picocolors13.default.green("iOS:")} found ${simctlDevices.length} simulator(s) (${booted.length} booted)`);
5172
5313
  for (const d of booted) {
5173
- console.log(` ${import_picocolors12.default.dim("\u2022")} ${d.name} (${d.udid})`);
5314
+ console.log(` ${import_picocolors13.default.dim("\u2022")} ${d.name} (${d.udid})`);
5174
5315
  }
5175
5316
  } else {
5176
- console.log(` ${import_picocolors12.default.dim("iOS:")} no simulators found (simctl not available or none installed)`);
5317
+ console.log(` ${import_picocolors13.default.dim("iOS:")} no simulators found (simctl not available or none installed)`);
5177
5318
  }
5178
5319
  let config;
5179
5320
  try {
@@ -5207,20 +5348,20 @@ async function initDevices() {
5207
5348
  (0, import_node_fs4.writeFileSync)(localFilePath, yamlContent);
5208
5349
  appendGitignoreEntry(dir, "agent-qa.local.yaml");
5209
5350
  console.log("");
5210
- console.log(import_picocolors12.default.green(` Generated agent-qa.local.yaml with ${bindCount} device binding(s)`));
5211
- console.log(import_picocolors12.default.dim(" Added agent-qa.local.yaml to .gitignore"));
5351
+ console.log(import_picocolors13.default.green(` Generated agent-qa.local.yaml with ${bindCount} device binding(s)`));
5352
+ console.log(import_picocolors13.default.dim(" Added agent-qa.local.yaml to .gitignore"));
5212
5353
  console.log("");
5213
5354
  }
5214
5355
  function createDevicesCommand() {
5215
- const cmd = new import_commander12.Command("devices").description("Manage device profiles and local bindings");
5356
+ const cmd = new import_commander13.Command("devices").description("Manage device profiles and local bindings");
5216
5357
  cmd.command("list").description("Show configured devices with merged shared + local view").action(listDevices);
5217
5358
  cmd.command("init").description("Scan connected devices and generate agent-qa.local.yaml").action(initDevices);
5218
5359
  return cmd;
5219
5360
  }
5220
5361
 
5221
5362
  // src/commands/ids.ts
5222
- var import_commander13 = require("commander");
5223
- var import_picocolors13 = __toESM(require("picocolors"), 1);
5363
+ var import_commander14 = require("commander");
5364
+ var import_picocolors14 = __toESM(require("picocolors"), 1);
5224
5365
  var import_agent_qa_ids = require("@vostride/agent-qa-ids");
5225
5366
  function parseEntityType(value) {
5226
5367
  if (import_agent_qa_ids.ENTITY_ID_TYPES.includes(value)) {
@@ -5229,7 +5370,7 @@ function parseEntityType(value) {
5229
5370
  throw new Error(`Unknown ID type "${value}". Expected one of: ${import_agent_qa_ids.ENTITY_ID_TYPES.join(", ")}`);
5230
5371
  }
5231
5372
  function createIdsCommand() {
5232
- const cmd = new import_commander13.Command("ids").description("Generate and validate canonical agent-qa entity IDs");
5373
+ const cmd = new import_commander14.Command("ids").description("Generate and validate canonical agent-qa entity IDs");
5233
5374
  cmd.command("generate").description("Generate a canonical agent-qa ID using id-agent").argument("<type>", `entity type: ${import_agent_qa_ids.ENTITY_ID_TYPES.join("|")}`).option("--json", "print structured JSON").action((typeValue, opts) => {
5234
5375
  try {
5235
5376
  const type = parseEntityType(typeValue);
@@ -5241,7 +5382,7 @@ function createIdsCommand() {
5241
5382
  console.log(id);
5242
5383
  }
5243
5384
  } catch (err) {
5244
- console.error(import_picocolors13.default.red(err instanceof Error ? err.message : String(err)));
5385
+ console.error(import_picocolors14.default.red(err instanceof Error ? err.message : String(err)));
5245
5386
  process.exitCode = 1;
5246
5387
  }
5247
5388
  });
@@ -5260,15 +5401,15 @@ function createIdsCommand() {
5260
5401
  if (opts.json) {
5261
5402
  console.log(JSON.stringify(result, null, 2));
5262
5403
  } else if (valid) {
5263
- console.log(import_picocolors13.default.green(result.message));
5404
+ console.log(import_picocolors14.default.green(result.message));
5264
5405
  } else {
5265
- console.error(import_picocolors13.default.red(result.message));
5406
+ console.error(import_picocolors14.default.red(result.message));
5266
5407
  }
5267
5408
  if (!valid) {
5268
5409
  process.exitCode = 1;
5269
5410
  }
5270
5411
  } catch (err) {
5271
- console.error(import_picocolors13.default.red(err instanceof Error ? err.message : String(err)));
5412
+ console.error(import_picocolors14.default.red(err instanceof Error ? err.message : String(err)));
5272
5413
  process.exitCode = 1;
5273
5414
  }
5274
5415
  });
@@ -5276,18 +5417,18 @@ function createIdsCommand() {
5276
5417
  }
5277
5418
 
5278
5419
  // src/commands/create-test.ts
5279
- var import_commander14 = require("commander");
5420
+ var import_commander15 = require("commander");
5280
5421
  var import_agent_qa_ids2 = require("@vostride/agent-qa-ids");
5281
5422
  var import_yaml7 = require("yaml");
5282
5423
  var import_promises4 = require("fs/promises");
5283
5424
  var import_node_fs5 = require("fs");
5284
- var import_node_path10 = require("path");
5285
- var import_picocolors14 = __toESM(require("picocolors"), 1);
5425
+ var import_node_path12 = require("path");
5426
+ var import_picocolors15 = __toESM(require("picocolors"), 1);
5286
5427
  function createCreateTestCommand() {
5287
- const cmd = new import_commander14.Command("create-test").description("Scaffold a new test YAML file with auto-generated ID").argument("<path>", "output file path for the test YAML").action(async (path) => {
5288
- const resolved = (0, import_node_path10.resolve)(path);
5428
+ const cmd = new import_commander15.Command("create-test").description("Scaffold a new test YAML file with auto-generated ID").argument("<path>", "output file path for the test YAML").action(async (path) => {
5429
+ const resolved = (0, import_node_path12.resolve)(path);
5289
5430
  if ((0, import_node_fs5.existsSync)(resolved)) {
5290
- console.error(import_picocolors14.default.red("Error: File already exists: " + resolved));
5431
+ console.error(import_picocolors15.default.red("Error: File already exists: " + resolved));
5291
5432
  process.exitCode = 1;
5292
5433
  return;
5293
5434
  }
@@ -5299,27 +5440,27 @@ function createCreateTestCommand() {
5299
5440
  steps: ["Describe your first test step here"]
5300
5441
  };
5301
5442
  const yamlContent = (0, import_yaml7.stringify)(template);
5302
- await (0, import_promises4.mkdir)((0, import_node_path10.dirname)(resolved), { recursive: true });
5443
+ await (0, import_promises4.mkdir)((0, import_node_path12.dirname)(resolved), { recursive: true });
5303
5444
  await (0, import_promises4.writeFile)(resolved, yamlContent, "utf-8");
5304
- console.log(import_picocolors14.default.green("Created " + resolved));
5305
- console.log(import_picocolors14.default.dim(" test-id: " + testId));
5445
+ console.log(import_picocolors15.default.green("Created " + resolved));
5446
+ console.log(import_picocolors15.default.dim(" test-id: " + testId));
5306
5447
  });
5307
5448
  return cmd;
5308
5449
  }
5309
5450
 
5310
5451
  // src/commands/create-suite.ts
5311
- var import_commander15 = require("commander");
5452
+ var import_commander16 = require("commander");
5312
5453
  var import_agent_qa_ids3 = require("@vostride/agent-qa-ids");
5313
5454
  var import_yaml8 = require("yaml");
5314
5455
  var import_promises5 = require("fs/promises");
5315
5456
  var import_node_fs6 = require("fs");
5316
- var import_node_path11 = require("path");
5317
- var import_picocolors15 = __toESM(require("picocolors"), 1);
5457
+ var import_node_path13 = require("path");
5458
+ var import_picocolors16 = __toESM(require("picocolors"), 1);
5318
5459
  function createCreateSuiteCommand() {
5319
- const cmd = new import_commander15.Command("create-suite").description("Scaffold a new suite YAML file with auto-generated ID").argument("<path>", "output file path for the suite YAML").action(async (path) => {
5320
- const resolved = (0, import_node_path11.resolve)(path);
5460
+ const cmd = new import_commander16.Command("create-suite").description("Scaffold a new suite YAML file with auto-generated ID").argument("<path>", "output file path for the suite YAML").action(async (path) => {
5461
+ const resolved = (0, import_node_path13.resolve)(path);
5321
5462
  if ((0, import_node_fs6.existsSync)(resolved)) {
5322
- console.error(import_picocolors15.default.red("Error: File already exists: " + resolved));
5463
+ console.error(import_picocolors16.default.red("Error: File already exists: " + resolved));
5323
5464
  process.exitCode = 1;
5324
5465
  return;
5325
5466
  }
@@ -5333,22 +5474,22 @@ function createCreateSuiteCommand() {
5333
5474
  "tests: []",
5334
5475
  "tests: []\n # - test: ./path/to/test.yaml\n # id: my-test-id"
5335
5476
  );
5336
- await (0, import_promises5.mkdir)((0, import_node_path11.dirname)(resolved), { recursive: true });
5477
+ await (0, import_promises5.mkdir)((0, import_node_path13.dirname)(resolved), { recursive: true });
5337
5478
  await (0, import_promises5.writeFile)(resolved, yamlContent, "utf-8");
5338
- console.log(import_picocolors15.default.green("Created " + resolved));
5339
- console.log(import_picocolors15.default.dim(" suite-id: " + suiteId));
5479
+ console.log(import_picocolors16.default.green("Created " + resolved));
5480
+ console.log(import_picocolors16.default.dim(" suite-id: " + suiteId));
5340
5481
  });
5341
5482
  return cmd;
5342
5483
  }
5343
5484
 
5344
5485
  // src/commands/clean-memory.ts
5345
- var import_commander16 = require("commander");
5346
- var import_picocolors16 = __toESM(require("picocolors"), 1);
5486
+ var import_commander17 = require("commander");
5487
+ var import_picocolors17 = __toESM(require("picocolors"), 1);
5347
5488
  var import_node_readline3 = require("readline");
5348
5489
  var import_promises6 = require("fs/promises");
5349
5490
  var import_node_fs7 = require("fs");
5350
- var import_node_path12 = require("path");
5351
- var import_agent_qa_core12 = require("@vostride/agent-qa-core");
5491
+ var import_node_path14 = require("path");
5492
+ var import_agent_qa_core13 = require("@vostride/agent-qa-core");
5352
5493
  init_config();
5353
5494
  function formatSize(bytes) {
5354
5495
  if (bytes === 0) return "0 B";
@@ -5356,13 +5497,13 @@ function formatSize(bytes) {
5356
5497
  return `${(bytes / 1048576).toFixed(1)} MB`;
5357
5498
  }
5358
5499
  async function getOrphanInfo(memoryRoot, tier, scope) {
5359
- const scopePath = (0, import_node_path12.join)(memoryRoot, tier, scope);
5360
- const obsFiles = await (0, import_agent_qa_core12.listObservations)(scopePath);
5500
+ const scopePath = (0, import_node_path14.join)(memoryRoot, tier, scope);
5501
+ const obsFiles = await (0, import_agent_qa_core13.listObservations)(scopePath);
5361
5502
  let newest = null;
5362
5503
  for (const filename of obsFiles) {
5363
5504
  try {
5364
- const content = await (0, import_promises6.readFile)((0, import_node_path12.join)(scopePath, filename), "utf-8");
5365
- const { data } = (0, import_agent_qa_core12.parseObservation)(content, filename);
5505
+ const content = await (0, import_promises6.readFile)((0, import_node_path14.join)(scopePath, filename), "utf-8");
5506
+ const { data } = (0, import_agent_qa_core13.parseObservation)(content, filename);
5366
5507
  if (data?.last_confirmed) {
5367
5508
  if (!newest || data.last_confirmed > newest) {
5368
5509
  newest = data.last_confirmed;
@@ -5376,7 +5517,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
5376
5517
  const entries = await (0, import_promises6.readdir)(scopePath);
5377
5518
  for (const entry of entries) {
5378
5519
  try {
5379
- const s = await (0, import_promises6.stat)((0, import_node_path12.join)(scopePath, entry));
5520
+ const s = await (0, import_promises6.stat)((0, import_node_path14.join)(scopePath, entry));
5380
5521
  if (s.isFile()) totalSize += s.size;
5381
5522
  } catch {
5382
5523
  }
@@ -5384,7 +5525,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
5384
5525
  } catch {
5385
5526
  }
5386
5527
  return {
5387
- path: (0, import_node_path12.join)(tier, scope),
5528
+ path: (0, import_node_path14.join)(tier, scope),
5388
5529
  tier,
5389
5530
  scope,
5390
5531
  fileCount: obsFiles.length,
@@ -5393,21 +5534,21 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
5393
5534
  };
5394
5535
  }
5395
5536
  function createCleanMemoryCommand() {
5396
- const cmd = new import_commander16.Command("clean-memory").description("Remove orphaned memory observation directories").option("-y, --yes", "skip confirmation prompt").action(async (_opts, command) => {
5537
+ const cmd = new import_commander17.Command("clean-memory").description("Remove orphaned memory observation directories").option("-y, --yes", "skip confirmation prompt").action(async (_opts, command) => {
5397
5538
  const opts = command.opts();
5398
5539
  const configPath = command.parent?.opts()?.config ?? "agent-qa.config.yaml";
5399
5540
  const config = await resolveConfig({ configPath });
5400
- const workspace = (0, import_agent_qa_core12.resolveWorkspacePaths)({ config, configPath });
5541
+ const workspace = (0, import_agent_qa_core13.resolveWorkspacePaths)({ config, configPath });
5401
5542
  const expectedProducts = /* @__PURE__ */ new Set();
5402
5543
  const targets = config.registry?.targets ?? {};
5403
5544
  for (const [name, entry] of Object.entries(targets)) {
5404
5545
  expectedProducts.add(entry?.product ?? name);
5405
5546
  }
5406
5547
  const expectedTests = /* @__PURE__ */ new Set();
5407
- for (const file of await (0, import_agent_qa_core12.discoverWorkspaceFiles)({ workspace, kind: "test" })) {
5548
+ for (const file of await (0, import_agent_qa_core13.discoverWorkspaceFiles)({ workspace, kind: "test" })) {
5408
5549
  try {
5409
5550
  const content = await (0, import_promises6.readFile)(file.absolutePath, "utf-8");
5410
- const result = (0, import_agent_qa_core12.parseTestFile)(content, file.workspaceRelativePath);
5551
+ const result = (0, import_agent_qa_core13.parseTestFile)(content, file.workspaceRelativePath);
5411
5552
  for (const t of result.tests) {
5412
5553
  if (t["test-id"]) expectedTests.add(t["test-id"]);
5413
5554
  expectedTests.add(t.name);
@@ -5416,17 +5557,17 @@ function createCleanMemoryCommand() {
5416
5557
  }
5417
5558
  }
5418
5559
  const expectedSuites = /* @__PURE__ */ new Set();
5419
- for (const file of await (0, import_agent_qa_core12.discoverWorkspaceFiles)({ workspace, kind: "suite" })) {
5560
+ for (const file of await (0, import_agent_qa_core13.discoverWorkspaceFiles)({ workspace, kind: "suite" })) {
5420
5561
  try {
5421
- const suite = await (0, import_agent_qa_core12.parseSuiteFile)(file.absolutePath);
5562
+ const suite = await (0, import_agent_qa_core13.parseSuiteFile)(file.absolutePath);
5422
5563
  if (suite["suite-id"]) expectedSuites.add(suite["suite-id"]);
5423
5564
  expectedSuites.add(suite.name);
5424
5565
  } catch {
5425
5566
  }
5426
5567
  }
5427
- const memoryRoot = (0, import_agent_qa_core12.resolveMemoryRoot)(config, workspace.configDir);
5568
+ const memoryRoot = (0, import_agent_qa_core13.resolveMemoryRoot)(config, workspace.configDir);
5428
5569
  if (!(0, import_node_fs7.existsSync)(memoryRoot)) {
5429
- console.log(import_picocolors16.default.dim("No memory directory found."));
5570
+ console.log(import_picocolors17.default.dim("No memory directory found."));
5430
5571
  return;
5431
5572
  }
5432
5573
  const TIERS = [
@@ -5436,7 +5577,7 @@ function createCleanMemoryCommand() {
5436
5577
  ];
5437
5578
  const orphans = [];
5438
5579
  for (const tier of TIERS) {
5439
- const tierPath = (0, import_node_path12.join)(memoryRoot, tier.dir);
5580
+ const tierPath = (0, import_node_path14.join)(memoryRoot, tier.dir);
5440
5581
  let entries;
5441
5582
  try {
5442
5583
  entries = await (0, import_promises6.readdir)(tierPath);
@@ -5445,7 +5586,7 @@ function createCleanMemoryCommand() {
5445
5586
  }
5446
5587
  for (const entry of entries) {
5447
5588
  try {
5448
- const s = await (0, import_promises6.stat)((0, import_node_path12.join)(tierPath, entry));
5589
+ const s = await (0, import_promises6.stat)((0, import_node_path14.join)(tierPath, entry));
5449
5590
  if (!s.isDirectory()) continue;
5450
5591
  } catch {
5451
5592
  continue;
@@ -5456,17 +5597,17 @@ function createCleanMemoryCommand() {
5456
5597
  }
5457
5598
  }
5458
5599
  if (orphans.length === 0) {
5459
- console.log(import_picocolors16.default.dim("No orphaned memory directories found."));
5600
+ console.log(import_picocolors17.default.dim("No orphaned memory directories found."));
5460
5601
  return;
5461
5602
  }
5462
5603
  const maxPathLen = Math.max("Path".length, ...orphans.map((o) => o.path.length));
5463
5604
  console.log("");
5464
- console.log(import_picocolors16.default.bold("Orphaned memory directories:"));
5605
+ console.log(import_picocolors17.default.bold("Orphaned memory directories:"));
5465
5606
  console.log("");
5466
- console.log(" " + import_picocolors16.default.dim("Path".padEnd(maxPathLen + 2) + "Files".padStart(7) + " " + "Last Confirmed".padEnd(20) + "Size".padStart(10)));
5467
- console.log(" " + import_picocolors16.default.dim("-".repeat(maxPathLen + 2 + 7 + 2 + 20 + 10)));
5607
+ console.log(" " + import_picocolors17.default.dim("Path".padEnd(maxPathLen + 2) + "Files".padStart(7) + " " + "Last Confirmed".padEnd(20) + "Size".padStart(10)));
5608
+ console.log(" " + import_picocolors17.default.dim("-".repeat(maxPathLen + 2 + 7 + 2 + 20 + 10)));
5468
5609
  for (const o of orphans) {
5469
- const confirmed = o.lastConfirmed ?? import_picocolors16.default.dim("never");
5610
+ const confirmed = o.lastConfirmed ?? import_picocolors17.default.dim("never");
5470
5611
  console.log(" " + o.path.padEnd(maxPathLen + 2) + String(o.fileCount).padStart(7) + " " + String(confirmed).padEnd(20) + formatSize(o.size).padStart(10));
5471
5612
  }
5472
5613
  console.log("");
@@ -5482,23 +5623,23 @@ function createCleanMemoryCommand() {
5482
5623
  }
5483
5624
  }
5484
5625
  for (const orphan of orphans) {
5485
- await (0, import_promises6.rm)((0, import_node_path12.join)(memoryRoot, orphan.tier, orphan.scope), { recursive: true, force: true });
5626
+ await (0, import_promises6.rm)((0, import_node_path14.join)(memoryRoot, orphan.tier, orphan.scope), { recursive: true, force: true });
5486
5627
  }
5487
- console.log(import_picocolors16.default.green(`Deleted ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}.`));
5628
+ console.log(import_picocolors17.default.green(`Deleted ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}.`));
5488
5629
  });
5489
5630
  return cmd;
5490
5631
  }
5491
5632
 
5492
5633
  // src/commands/skills.ts
5493
- var import_commander17 = require("commander");
5634
+ var import_commander18 = require("commander");
5494
5635
  var import_node_fs8 = require("fs");
5495
- var import_node_path13 = require("path");
5636
+ var import_node_path15 = require("path");
5496
5637
  var import_node_url = require("url");
5497
5638
  var import_meta4 = {};
5498
5639
  function findPackageRoot(startDir) {
5499
- let current = (0, import_node_path13.resolve)(startDir);
5640
+ let current = (0, import_node_path15.resolve)(startDir);
5500
5641
  while (true) {
5501
- const packagePath = (0, import_node_path13.join)(current, "package.json");
5642
+ const packagePath = (0, import_node_path15.join)(current, "package.json");
5502
5643
  if ((0, import_node_fs8.existsSync)(packagePath)) {
5503
5644
  try {
5504
5645
  const pkg = JSON.parse((0, import_node_fs8.readFileSync)(packagePath, "utf-8"));
@@ -5508,9 +5649,9 @@ function findPackageRoot(startDir) {
5508
5649
  } catch {
5509
5650
  }
5510
5651
  }
5511
- const parent = (0, import_node_path13.dirname)(current);
5652
+ const parent = (0, import_node_path15.dirname)(current);
5512
5653
  if (parent === current) {
5513
- return (0, import_node_path13.resolve)(startDir);
5654
+ return (0, import_node_path15.resolve)(startDir);
5514
5655
  }
5515
5656
  current = parent;
5516
5657
  }
@@ -5519,20 +5660,20 @@ function hasSkillDirectories(dir) {
5519
5660
  if (!(0, import_node_fs8.existsSync)(dir)) return false;
5520
5661
  try {
5521
5662
  return (0, import_node_fs8.readdirSync)(dir).some((entry) => {
5522
- const skillDir = (0, import_node_path13.join)(dir, entry);
5523
- return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0, import_node_path13.join)(skillDir, "SKILL.md"));
5663
+ const skillDir = (0, import_node_path15.join)(dir, entry);
5664
+ return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0, import_node_path15.join)(skillDir, "SKILL.md"));
5524
5665
  });
5525
5666
  } catch {
5526
5667
  return false;
5527
5668
  }
5528
5669
  }
5529
- function resolveAgentQaSkillsDirectory(startDir = (0, import_node_path13.dirname)((0, import_node_url.fileURLToPath)(import_meta4.url))) {
5670
+ function resolveAgentQaSkillsDirectory(startDir = (0, import_node_path15.dirname)((0, import_node_url.fileURLToPath)(import_meta4.url))) {
5530
5671
  const packageRoot = findPackageRoot(startDir);
5531
- const packagedSkills = (0, import_node_path13.join)(packageRoot, "skills");
5672
+ const packagedSkills = (0, import_node_path15.join)(packageRoot, "skills");
5532
5673
  if (hasSkillDirectories(packagedSkills)) {
5533
5674
  return { path: packagedSkills, source: "package" };
5534
5675
  }
5535
- const sourceSkills = (0, import_node_path13.resolve)(packageRoot, "../..", "skills");
5676
+ const sourceSkills = (0, import_node_path15.resolve)(packageRoot, "../..", "skills");
5536
5677
  if (hasSkillDirectories(sourceSkills)) {
5537
5678
  return { path: sourceSkills, source: "source" };
5538
5679
  }
@@ -5546,21 +5687,21 @@ function parseSkillDescription(skillPath) {
5546
5687
  function listAgentQaSkills(skillsDir) {
5547
5688
  if (!hasSkillDirectories(skillsDir)) return [];
5548
5689
  return (0, import_node_fs8.readdirSync)(skillsDir).filter((entry) => {
5549
- const skillDir = (0, import_node_path13.join)(skillsDir, entry);
5550
- return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0, import_node_path13.join)(skillDir, "SKILL.md"));
5690
+ const skillDir = (0, import_node_path15.join)(skillsDir, entry);
5691
+ return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0, import_node_path15.join)(skillDir, "SKILL.md"));
5551
5692
  }).sort().map((entry) => {
5552
- const skillDir = (0, import_node_path13.join)(skillsDir, entry);
5693
+ const skillDir = (0, import_node_path15.join)(skillsDir, entry);
5553
5694
  return {
5554
5695
  name: entry,
5555
5696
  path: skillDir,
5556
- description: parseSkillDescription((0, import_node_path13.join)(skillDir, "SKILL.md"))
5697
+ description: parseSkillDescription((0, import_node_path15.join)(skillDir, "SKILL.md"))
5557
5698
  };
5558
5699
  });
5559
5700
  }
5560
5701
  function createSkillsCommand(options = {}) {
5561
5702
  const stdout = options.stdout ?? process.stdout;
5562
5703
  const stderr = options.stderr ?? process.stderr;
5563
- return new import_commander17.Command("skills").description("List packaged agent-qa skills").option("--json", "print machine-readable output").action((opts) => {
5704
+ return new import_commander18.Command("skills").description("List packaged agent-qa skills").option("--json", "print machine-readable output").action((opts) => {
5564
5705
  const resolved = resolveAgentQaSkillsDirectory(options.startDir);
5565
5706
  const skills = listAgentQaSkills(resolved.path);
5566
5707
  if (resolved.source === "missing" || skills.length === 0) {
@@ -5641,13 +5782,14 @@ function rejectRemovedCliSurface(input3 = {}) {
5641
5782
 
5642
5783
  // src/cli.ts
5643
5784
  rejectRemovedCliSurface();
5644
- var program = new import_commander18.Command();
5785
+ var program = new import_commander19.Command();
5645
5786
  program.enablePositionalOptions();
5646
5787
  program.name("agent-qa").description("AI-powered testing framework").version(VERSION);
5647
5788
  program.option("--config <path>", "config file path", "agent-qa.config.yaml").option("--log-level <level>", "log verbosity: silent|error|warn|info|debug").option("--verbose", "shorthand for --log-level debug").option("--quiet", "shorthand for --log-level silent");
5648
5789
  program.addCommand(createRunCommand());
5649
5790
  program.addCommand(createInitCommand());
5650
5791
  program.addCommand(createInstallBrowsersCommand());
5792
+ program.addCommand(createInstallMobileDriversCommand());
5651
5793
  program.addCommand(createDoctorCommand());
5652
5794
  program.addCommand(createDashboardCommand());
5653
5795
  program.addCommand(createServeCommand());