agent-qa 0.1.9 → 0.1.11
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 +829 -684
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +754 -601
- package/dist/cli.js.map +1 -1
- package/package.json +9 -9
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,
|
|
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
|
|
343
|
+
var import_node_path6;
|
|
344
344
|
var init_llm_utils = __esm({
|
|
345
345
|
"src/llm-utils.ts"() {
|
|
346
346
|
"use strict";
|
|
347
|
-
|
|
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
|
|
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
|
|
511
|
+
var import_commander2 = require("commander");
|
|
512
512
|
var import_node_fs = require("fs");
|
|
513
|
-
var
|
|
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
|
|
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
|
|
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",
|
|
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,
|
|
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,
|
|
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
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1083
|
-
if (
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
}
|
|
1095
|
-
if (
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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
|
|
1204
|
-
const dir = (0,
|
|
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";
|
|
@@ -1214,8 +1001,8 @@ function createInitCommand() {
|
|
|
1214
1001
|
const capabilities = await (0, import_checkbox.default)({
|
|
1215
1002
|
message: "What platform will you test?",
|
|
1216
1003
|
choices: [
|
|
1217
|
-
{ value: "web", name: "Web", description: "Test web apps with Playwright" },
|
|
1218
|
-
{ value: "mobile", name: "Mobile", description: "Test Android or iOS apps with Appium" }
|
|
1004
|
+
{ value: "web", name: "Web", description: "Test web apps with Playwright", checked: true },
|
|
1005
|
+
{ value: "mobile", name: "Mobile", description: "Test Android or iOS apps with Appium", checked: false }
|
|
1219
1006
|
],
|
|
1220
1007
|
required: true,
|
|
1221
1008
|
validate: (value) => value.length > 0 || "Select at least one platform."
|
|
@@ -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 ${
|
|
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,
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
1074
|
+
console.log(import_picocolors3.default.green(`\u2713 ${action} ${subscriptionAuthDependency.path}`));
|
|
1288
1075
|
}
|
|
1289
|
-
const secretsPath = (0,
|
|
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(
|
|
1079
|
+
console.log(import_picocolors3.default.green(`\u2713 Created ${secretsPath}`));
|
|
1293
1080
|
}
|
|
1294
|
-
const envPath = (0,
|
|
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(
|
|
1084
|
+
console.log(import_picocolors3.default.green(`\u2713 Created ${envPath}`));
|
|
1298
1085
|
}
|
|
1299
|
-
const hooksPath = (0,
|
|
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(
|
|
1089
|
+
console.log(import_picocolors3.default.green(`\u2713 Created ${hooksPath}`));
|
|
1303
1090
|
}
|
|
1304
|
-
const agentRulesPath = (0,
|
|
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(
|
|
1094
|
+
console.log(import_picocolors3.default.green(`\u2713 Created ${agentRulesPath}`));
|
|
1308
1095
|
}
|
|
1309
|
-
const localConfigPath = (0,
|
|
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(
|
|
1099
|
+
console.log(import_picocolors3.default.green(`\u2713 Created ${localConfigPath}`));
|
|
1313
1100
|
}
|
|
1314
|
-
const examplePassPath = (0,
|
|
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(
|
|
1317
|
-
const exampleFailPath = (0,
|
|
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(
|
|
1106
|
+
console.log(import_picocolors3.default.green(`\u2713 Created ${exampleFailPath}`));
|
|
1320
1107
|
if (hasWeb(platform)) {
|
|
1321
|
-
const suitesDir = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
1121
|
+
const testPath = (0, import_node_path3.join)(automationTestsDir, test.fileName);
|
|
1335
1122
|
(0, import_node_fs.writeFileSync)(testPath, buildAutomationExerciseTest(test));
|
|
1336
|
-
console.log(
|
|
1123
|
+
console.log(import_picocolors3.default.green(`\u2713 Created ${testPath}`));
|
|
1337
1124
|
}
|
|
1338
|
-
const automationSuitePath = (0,
|
|
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(
|
|
1341
|
-
const hnScriptPath = (0,
|
|
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(
|
|
1344
|
-
const hnTestPath = (0,
|
|
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(
|
|
1133
|
+
console.log(import_picocolors3.default.green(`\u2713 Created ${hnTestPath}`));
|
|
1347
1134
|
}
|
|
1348
1135
|
appendGitignore(dir);
|
|
1349
|
-
console.log(
|
|
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(
|
|
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(` ${
|
|
1141
|
+
console.log(` ${import_picocolors3.default.dim("\u2022")} agent-qa.config.yaml`);
|
|
1359
1142
|
if (subscriptionAuthDependency) {
|
|
1360
|
-
console.log(` ${
|
|
1361
|
-
}
|
|
1362
|
-
console.log(` ${
|
|
1363
|
-
console.log(` ${
|
|
1364
|
-
console.log(` ${
|
|
1365
|
-
console.log(` ${
|
|
1366
|
-
console.log(` ${
|
|
1367
|
-
console.log(` ${
|
|
1368
|
-
console.log(` ${
|
|
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(` ${
|
|
1371
|
-
console.log(` ${
|
|
1372
|
-
console.log(` ${
|
|
1373
|
-
console.log(` ${
|
|
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(` ${
|
|
1158
|
+
console.log(` ${import_picocolors3.default.dim("\u2022")} .gitignore entries`);
|
|
1376
1159
|
console.log("");
|
|
1377
1160
|
console.log(" Next steps:");
|
|
1378
|
-
|
|
1379
|
-
|
|
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(` ${
|
|
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(` ${
|
|
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(` ${
|
|
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(` ${
|
|
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/
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
1598
|
+
return import_picocolors6.default.green("PASS");
|
|
1471
1599
|
case "warn":
|
|
1472
|
-
return
|
|
1600
|
+
return import_picocolors6.default.yellow("WARN");
|
|
1473
1601
|
case "fail":
|
|
1474
|
-
return
|
|
1602
|
+
return import_picocolors6.default.red("FAIL");
|
|
1475
1603
|
case "skip":
|
|
1476
|
-
return
|
|
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
|
|
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`
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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,
|
|
1851
|
-
(0,
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1951
|
-
var
|
|
1952
|
-
var
|
|
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
|
|
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,
|
|
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
|
|
2049
|
-
var RUNTIME_ARTIFACTS_DIR =
|
|
2050
|
-
var RUNTIME_CACHE_DIR =
|
|
2051
|
-
var RUNTIME_VIDEOS_DIR =
|
|
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,
|
|
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,
|
|
2321
|
+
return (0, import_node_path9.resolve)(configDir, dashArtifactsDir ?? RUNTIME_ARTIFACTS_DIR, "videos");
|
|
2181
2322
|
}
|
|
2182
|
-
return (0,
|
|
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,
|
|
2203
|
-
(0,
|
|
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,
|
|
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[
|
|
2362
|
+
const trigger = inheritedAttributes[import_agent_qa_core7.ATTR_TRIGGER];
|
|
2222
2363
|
const inheritedUserAttributes = Object.fromEntries(
|
|
2223
|
-
Object.entries(inheritedAttributes).filter(([key]) => key !==
|
|
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,
|
|
2226
|
-
(0,
|
|
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,
|
|
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" ?
|
|
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(` ${
|
|
2439
|
+
console.error(` ${import_picocolors7.default.blue("-->")} ${location}`);
|
|
2299
2440
|
if (err.source) {
|
|
2300
|
-
console.error(` ${
|
|
2441
|
+
console.error(` ${import_picocolors7.default.blue("|")} ${err.source}`);
|
|
2301
2442
|
}
|
|
2302
2443
|
if (err.suggestion) {
|
|
2303
|
-
console.error(` ${
|
|
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,
|
|
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,
|
|
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(
|
|
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(
|
|
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(` ${
|
|
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(` ${
|
|
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,
|
|
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,
|
|
2349
|
-
const suiteRecords = await (0,
|
|
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,
|
|
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(
|
|
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,
|
|
2542
|
+
const secretsPath = input3.secretsFilePath ?? (0, import_node_path9.resolve)(input3.configDir, configuredSecretsFile);
|
|
2402
2543
|
if (!input3.existsSync(secretsPath)) {
|
|
2403
|
-
console.error(
|
|
2404
|
-
console.error(
|
|
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(
|
|
2412
|
-
console.error(
|
|
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(
|
|
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
|
|
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
|
|
2651
|
+
if (err instanceof import_agent_qa_core7.MobileSetupError) throw err;
|
|
2511
2652
|
if (browserstackApp) {
|
|
2512
|
-
throw new
|
|
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
|
|
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,
|
|
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(
|
|
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(
|
|
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,
|
|
2650
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
3128
|
+
const { resolve: resolve9 } = await import("path");
|
|
2988
3129
|
actionCache = new FileActionCache({
|
|
2989
|
-
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,
|
|
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(` ${
|
|
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");
|
|
@@ -3083,6 +3224,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
3083
3224
|
resolvedHooks: hooksContext?.resolvedHooks,
|
|
3084
3225
|
sandboxOptions: hooksContext?.sandboxOptions,
|
|
3085
3226
|
logCapture: suiteLogCapture,
|
|
3227
|
+
accessibility: config.services?.accessibility,
|
|
3086
3228
|
screenshotSize: plannerCfg.screenshotSize,
|
|
3087
3229
|
effectiveResolution: plannerCfg.effectiveResolution,
|
|
3088
3230
|
memoryProvider: suiteMemoryProvider,
|
|
@@ -3147,19 +3289,19 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
3147
3289
|
const batchOutcome = await flushSuiteParents();
|
|
3148
3290
|
if (batchOutcome.cancelled || shutdownRequested) break;
|
|
3149
3291
|
if (opts.bail && batchOutcome.failed) {
|
|
3150
|
-
console.log(
|
|
3292
|
+
console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failed suite"));
|
|
3151
3293
|
break;
|
|
3152
3294
|
}
|
|
3153
3295
|
const outcome = await runOneSuiteFile(suiteFile, suite);
|
|
3154
3296
|
if (outcome.cancelled || shutdownRequested) break;
|
|
3155
3297
|
if (opts.bail && outcome.failed) {
|
|
3156
|
-
console.log(
|
|
3298
|
+
console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failed suite"));
|
|
3157
3299
|
break;
|
|
3158
3300
|
}
|
|
3159
3301
|
}
|
|
3160
3302
|
const finalBatchOutcome = await flushSuiteParents();
|
|
3161
3303
|
if (opts.bail && finalBatchOutcome.failed) {
|
|
3162
|
-
console.log(
|
|
3304
|
+
console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failed suite"));
|
|
3163
3305
|
}
|
|
3164
3306
|
process.removeListener("SIGINT", handleShutdown);
|
|
3165
3307
|
process.removeListener("SIGTERM", handleShutdown);
|
|
@@ -3167,12 +3309,12 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
3167
3309
|
return suiteResults.some((r) => r.status === "failed" || r.status === "cancelled") || shutdownRequested;
|
|
3168
3310
|
}
|
|
3169
3311
|
function createRunCommand() {
|
|
3170
|
-
const cmd = new
|
|
3312
|
+
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
3313
|
let _crashDb;
|
|
3172
3314
|
try {
|
|
3173
3315
|
const modeFlags = [opts.test, opts.suite, opts.all].filter(Boolean).length;
|
|
3174
3316
|
if (modeFlags > 1) {
|
|
3175
|
-
console.error(
|
|
3317
|
+
console.error(import_picocolors7.default.red("Error: --test, --suite, and --all are mutually exclusive."));
|
|
3176
3318
|
process.exit(1);
|
|
3177
3319
|
}
|
|
3178
3320
|
const program2 = command.parent;
|
|
@@ -3188,8 +3330,8 @@ function createRunCommand() {
|
|
|
3188
3330
|
flags: flagOverrides,
|
|
3189
3331
|
loadAuthPlugins: !(opts.dryRun || opts.listTests)
|
|
3190
3332
|
});
|
|
3191
|
-
const configFilePath = (0,
|
|
3192
|
-
const workspacePaths = (0,
|
|
3333
|
+
const configFilePath = (0, import_node_path9.resolve)(globalOpts.config ?? "agent-qa.config.yaml");
|
|
3334
|
+
const workspacePaths = (0, import_agent_qa_core8.resolveWorkspacePaths)({
|
|
3193
3335
|
config,
|
|
3194
3336
|
configPath: configFilePath
|
|
3195
3337
|
});
|
|
@@ -3207,13 +3349,13 @@ function createRunCommand() {
|
|
|
3207
3349
|
const inheritedRunAttributes = inheritedContextRunId ? readRunAttributesFromEnv() : void 0;
|
|
3208
3350
|
let userRunAttributes;
|
|
3209
3351
|
try {
|
|
3210
|
-
const parsedRunAttrs = (0,
|
|
3352
|
+
const parsedRunAttrs = (0, import_agent_qa_core7.parseRunAttrFlags)(opts.runAttr);
|
|
3211
3353
|
userRunAttributes = parsedRunAttrs.attributes;
|
|
3212
3354
|
for (const duplicateKey of parsedRunAttrs.duplicateKeys) {
|
|
3213
3355
|
console.warn(`Warning: duplicate --run-attr key "${duplicateKey}"; using last value`);
|
|
3214
3356
|
}
|
|
3215
3357
|
} catch (err) {
|
|
3216
|
-
console.error(
|
|
3358
|
+
console.error(import_picocolors7.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3217
3359
|
process.exit(2);
|
|
3218
3360
|
}
|
|
3219
3361
|
const inlineVars = {};
|
|
@@ -3229,29 +3371,29 @@ function createRunCommand() {
|
|
|
3229
3371
|
let resolvedEnvFilePath = null;
|
|
3230
3372
|
resolvedEnvFilePath = workspacePaths.envFile.absolutePath;
|
|
3231
3373
|
if (!existsSync8(workspacePaths.envFile.absolutePath)) {
|
|
3232
|
-
console.error(
|
|
3374
|
+
console.error(import_picocolors7.default.red(`Error: Env file not found: ${workspacePaths.envFile.absolutePath}`));
|
|
3233
3375
|
process.exit(1);
|
|
3234
3376
|
}
|
|
3235
3377
|
try {
|
|
3236
3378
|
rawEnvFileContent = readFileSync5(workspacePaths.envFile.absolutePath, "utf-8");
|
|
3237
3379
|
envFileVars = parseEnvFile(rawEnvFileContent);
|
|
3238
3380
|
} catch {
|
|
3239
|
-
console.error(
|
|
3381
|
+
console.error(import_picocolors7.default.red(`Error: Env file could not be read: ${workspacePaths.envFile.absolutePath}`));
|
|
3240
3382
|
process.exit(1);
|
|
3241
3383
|
}
|
|
3242
3384
|
let resolvedHooks;
|
|
3243
3385
|
let sandboxOptions;
|
|
3244
3386
|
const hooksFilePath = workspacePaths.hooksFile.absolutePath;
|
|
3245
3387
|
if (!existsSync8(hooksFilePath)) {
|
|
3246
|
-
console.error(
|
|
3388
|
+
console.error(import_picocolors7.default.red(`Error: Hooks file not found: ${hooksFilePath}`));
|
|
3247
3389
|
process.exit(1);
|
|
3248
3390
|
}
|
|
3249
3391
|
try {
|
|
3250
3392
|
const { parseHooksFile } = await import("@vostride/agent-qa-core");
|
|
3251
3393
|
const { hooks, errors } = await parseHooksFile(hooksFilePath);
|
|
3252
3394
|
if (errors.length > 0) {
|
|
3253
|
-
console.error(
|
|
3254
|
-
for (const err of errors) console.error(
|
|
3395
|
+
console.error(import_picocolors7.default.red("Hooks configuration errors:"));
|
|
3396
|
+
for (const err of errors) console.error(import_picocolors7.default.red(` - ${err}`));
|
|
3255
3397
|
process.exit(1);
|
|
3256
3398
|
}
|
|
3257
3399
|
resolvedHooks = new Map(hooks.map((h) => [h.id, h]));
|
|
@@ -3260,8 +3402,8 @@ function createRunCommand() {
|
|
|
3260
3402
|
secretRedactor: runtimeSecrets.secretRedactor
|
|
3261
3403
|
};
|
|
3262
3404
|
} catch (err) {
|
|
3263
|
-
console.error(
|
|
3264
|
-
if (err instanceof Error && err.message) console.error(
|
|
3405
|
+
console.error(import_picocolors7.default.red(`Error: Hooks file could not be read: ${hooksFilePath}`));
|
|
3406
|
+
if (err instanceof Error && err.message) console.error(import_picocolors7.default.dim(err.message));
|
|
3265
3407
|
process.exit(1);
|
|
3266
3408
|
}
|
|
3267
3409
|
let agentRulesContent;
|
|
@@ -3269,7 +3411,7 @@ function createRunCommand() {
|
|
|
3269
3411
|
try {
|
|
3270
3412
|
agentRulesContent = await readFileAsync(workspacePaths.agentRules.absolutePath, "utf-8");
|
|
3271
3413
|
} catch {
|
|
3272
|
-
console.error(
|
|
3414
|
+
console.error(import_picocolors7.default.red(`Error: Agent rules file not found: ${workspacePaths.agentRules.absolutePath}`));
|
|
3273
3415
|
process.exit(1);
|
|
3274
3416
|
}
|
|
3275
3417
|
const effectiveLogLevel = globalOpts.logLevel ?? (globalOpts.verbose ? "debug" : void 0) ?? (globalOpts.quiet ? "silent" : void 0) ?? config.services?.logging?.level ?? "warn";
|
|
@@ -3281,41 +3423,41 @@ function createRunCommand() {
|
|
|
3281
3423
|
discoveredTestFiles = selected.tests;
|
|
3282
3424
|
discoveredSuiteFiles = selected.suites;
|
|
3283
3425
|
} catch (err) {
|
|
3284
|
-
console.error(
|
|
3426
|
+
console.error(import_picocolors7.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3285
3427
|
process.exit(1);
|
|
3286
3428
|
}
|
|
3287
3429
|
if (discoveredTestFiles.length === 0 && discoveredSuiteFiles.length === 0) {
|
|
3288
3430
|
const patternKind = patterns.every((pattern) => classifyRunPattern(pattern) === "suite") ? "suite" : "test";
|
|
3289
|
-
console.error(
|
|
3290
|
-
for (const p of patterns) console.error(
|
|
3431
|
+
console.error(import_picocolors7.default.red(`Error: No ${patternKind} files found matching patterns:`));
|
|
3432
|
+
for (const p of patterns) console.error(import_picocolors7.default.red(` ${p}`));
|
|
3291
3433
|
process.exit(2);
|
|
3292
3434
|
}
|
|
3293
3435
|
} else if (opts.suite) {
|
|
3294
|
-
discoveredSuiteFiles = (await (0,
|
|
3436
|
+
discoveredSuiteFiles = (await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "suite" })).map((record) => record.absolutePath);
|
|
3295
3437
|
if (discoveredSuiteFiles.length === 0) {
|
|
3296
|
-
console.error(
|
|
3438
|
+
console.error(import_picocolors7.default.red("Error: No suite files found matching workspace.suiteMatch"));
|
|
3297
3439
|
for (const p of workspacePaths.suiteMatch) {
|
|
3298
|
-
console.error(
|
|
3440
|
+
console.error(import_picocolors7.default.red(` ${p}`));
|
|
3299
3441
|
}
|
|
3300
3442
|
process.exit(2);
|
|
3301
3443
|
}
|
|
3302
3444
|
} else if (opts.all) {
|
|
3303
|
-
discoveredTestFiles = (await (0,
|
|
3304
|
-
discoveredSuiteFiles = (await (0,
|
|
3445
|
+
discoveredTestFiles = (await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "test" })).map((record) => record.absolutePath);
|
|
3446
|
+
discoveredSuiteFiles = (await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "suite" })).map((record) => record.absolutePath);
|
|
3305
3447
|
if (discoveredTestFiles.length === 0 && discoveredSuiteFiles.length === 0) {
|
|
3306
|
-
console.error(
|
|
3448
|
+
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
3449
|
process.exit(2);
|
|
3308
3450
|
}
|
|
3309
3451
|
} else {
|
|
3310
3452
|
if (!workspacePaths.testMatch || workspacePaths.testMatch.length === 0) {
|
|
3311
|
-
console.error(
|
|
3453
|
+
console.error(import_picocolors7.default.red("Error: No testMatch patterns in config. Add workspace.testMatch to agent-qa.config.yaml."));
|
|
3312
3454
|
process.exit(2);
|
|
3313
3455
|
}
|
|
3314
|
-
discoveredTestFiles = (await (0,
|
|
3456
|
+
discoveredTestFiles = (await (0, import_agent_qa_core8.discoverWorkspaceFiles)({ workspace: workspacePaths, kind: "test" })).map((record) => record.absolutePath);
|
|
3315
3457
|
if (discoveredTestFiles.length === 0) {
|
|
3316
|
-
console.error(
|
|
3458
|
+
console.error(import_picocolors7.default.red("Error: No test files found matching workspace.testMatch"));
|
|
3317
3459
|
for (const p of workspacePaths.testMatch) {
|
|
3318
|
-
console.error(
|
|
3460
|
+
console.error(import_picocolors7.default.red(` ${p}`));
|
|
3319
3461
|
}
|
|
3320
3462
|
process.exit(2);
|
|
3321
3463
|
}
|
|
@@ -3330,7 +3472,7 @@ function createRunCommand() {
|
|
|
3330
3472
|
}
|
|
3331
3473
|
const files = discoveredTestFiles;
|
|
3332
3474
|
if (opts.listTests) {
|
|
3333
|
-
console.log(
|
|
3475
|
+
console.log(import_picocolors7.default.bold(`
|
|
3334
3476
|
Discovered ${files.length} test file(s):
|
|
3335
3477
|
`));
|
|
3336
3478
|
for (const file of files) {
|
|
@@ -3342,7 +3484,7 @@ Discovered ${files.length} test file(s):
|
|
|
3342
3484
|
const dashboardCfg2 = config.services?.dashboard;
|
|
3343
3485
|
const reporterSelectionResult = resolveReporterSelection(opts, Boolean(dashboardCfg2));
|
|
3344
3486
|
if (reporterSelectionResult.error) {
|
|
3345
|
-
console.error(
|
|
3487
|
+
console.error(import_picocolors7.default.red(`Error: ${reporterSelectionResult.error}`));
|
|
3346
3488
|
process.exit(2);
|
|
3347
3489
|
}
|
|
3348
3490
|
const reporterSelection = reporterSelectionResult.selection;
|
|
@@ -3359,13 +3501,13 @@ Discovered ${files.length} test file(s):
|
|
|
3359
3501
|
logStorage = dashboardDb;
|
|
3360
3502
|
_crashDb = dashboardDb;
|
|
3361
3503
|
} catch (err) {
|
|
3362
|
-
console.warn(
|
|
3504
|
+
console.warn(import_picocolors7.default.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
|
|
3363
3505
|
}
|
|
3364
3506
|
}
|
|
3365
3507
|
const { parseAllTests, formatParseError } = await import("@vostride/agent-qa-core");
|
|
3366
3508
|
const parseResult = await parseAllTests(files);
|
|
3367
3509
|
if (parseResult.errors.length > 0) {
|
|
3368
|
-
console.error(
|
|
3510
|
+
console.error(import_picocolors7.default.red(`Found ${parseResult.errors.length} parse error(s):
|
|
3369
3511
|
`));
|
|
3370
3512
|
for (const err of parseResult.errors) {
|
|
3371
3513
|
console.error(formatParseError(err));
|
|
@@ -3461,17 +3603,17 @@ Discovered ${files.length} test file(s):
|
|
|
3461
3603
|
}
|
|
3462
3604
|
const filtered = parseResult.tests;
|
|
3463
3605
|
if (filtered.length === 0) {
|
|
3464
|
-
console.log(
|
|
3606
|
+
console.log(import_picocolors7.default.yellow("No tests match filters"));
|
|
3465
3607
|
process.exit(0);
|
|
3466
3608
|
}
|
|
3467
3609
|
if (opts.dryRun) {
|
|
3468
|
-
console.log(
|
|
3610
|
+
console.log(import_picocolors7.default.bold(`
|
|
3469
3611
|
Found ${filtered.length} test(s):
|
|
3470
3612
|
`));
|
|
3471
3613
|
for (const test of filtered) {
|
|
3472
|
-
console.log(` ${
|
|
3614
|
+
console.log(` ${import_picocolors7.default.green("\u25CF")} ${test.name}`);
|
|
3473
3615
|
if (opts.configDebug) {
|
|
3474
|
-
console.log(
|
|
3616
|
+
console.log(import_picocolors7.default.bold(`
|
|
3475
3617
|
Config for: ${test.name}`));
|
|
3476
3618
|
console.log(formatConfigDebug(config, test.use, flagOverrides));
|
|
3477
3619
|
console.log();
|
|
@@ -3509,11 +3651,11 @@ Found ${filtered.length} test(s):
|
|
|
3509
3651
|
if (reporterSelection.dashboard && dashboardDb) {
|
|
3510
3652
|
try {
|
|
3511
3653
|
const { DashboardReporter } = await import("@vostride/agent-qa-dashboard");
|
|
3512
|
-
const { resolve:
|
|
3654
|
+
const { resolve: resolve9 } = await import("path");
|
|
3513
3655
|
const dashArtifactsDir = config.services?.dashboard?.artifactsDir ?? RUNTIME_ARTIFACTS_DIR;
|
|
3514
3656
|
reporters.push(new DashboardReporter({
|
|
3515
3657
|
db: dashboardDb,
|
|
3516
|
-
artifactsDir:
|
|
3658
|
+
artifactsDir: resolve9(configDir, dashArtifactsDir),
|
|
3517
3659
|
redactor: runtimeSecrets.secretRedactor,
|
|
3518
3660
|
onRunCreated: (runId) => {
|
|
3519
3661
|
currentRunId = runId;
|
|
@@ -3521,7 +3663,7 @@ Found ${filtered.length} test(s):
|
|
|
3521
3663
|
}
|
|
3522
3664
|
}));
|
|
3523
3665
|
} catch (err) {
|
|
3524
|
-
console.warn(
|
|
3666
|
+
console.warn(import_picocolors7.default.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
|
|
3525
3667
|
}
|
|
3526
3668
|
}
|
|
3527
3669
|
const analyticsReporter = createAnalyticsRunReporter({ config, surface: "cli" });
|
|
@@ -3535,9 +3677,9 @@ Found ${filtered.length} test(s):
|
|
|
3535
3677
|
});
|
|
3536
3678
|
if (opts.cache !== false) {
|
|
3537
3679
|
const { FileActionCache } = await import("@vostride/agent-qa-core");
|
|
3538
|
-
const { resolve:
|
|
3680
|
+
const { resolve: resolve9 } = await import("path");
|
|
3539
3681
|
actionCache = new FileActionCache({
|
|
3540
|
-
dir:
|
|
3682
|
+
dir: resolve9(configDir, config.services?.cache?.dir ?? RUNTIME_CACHE_DIR),
|
|
3541
3683
|
ttl: config.services?.cache?.ttl ?? "7d",
|
|
3542
3684
|
logger: logger.createScopedLogger("cache")
|
|
3543
3685
|
});
|
|
@@ -3550,7 +3692,7 @@ Found ${filtered.length} test(s):
|
|
|
3550
3692
|
let circuitBreaker;
|
|
3551
3693
|
let circuitBreakerTripped = false;
|
|
3552
3694
|
const memoryGlobalConfig = config.services?.memory;
|
|
3553
|
-
const runtimeMemoryRoot = (0,
|
|
3695
|
+
const runtimeMemoryRoot = (0, import_agent_qa_core7.resolveMemoryRoot)(config, configDir);
|
|
3554
3696
|
if (opts.memory !== false && memoryGlobalConfig?.circuitBreakerEnabled !== false && memoryGlobalConfig?.enabled !== false) {
|
|
3555
3697
|
const { CircuitBreaker } = await import("@vostride/agent-qa-core");
|
|
3556
3698
|
circuitBreaker = new CircuitBreaker({
|
|
@@ -3590,7 +3732,7 @@ Found ${filtered.length} test(s):
|
|
|
3590
3732
|
(f) => parseResult.tests.some((t) => t.name === test.name)
|
|
3591
3733
|
) ?? "unknown";
|
|
3592
3734
|
analyticsFilePath = filePath;
|
|
3593
|
-
const runId = process.env.AGENT_QA_RUN_ID ?? (0,
|
|
3735
|
+
const runId = process.env.AGENT_QA_RUN_ID ?? (0, import_agent_qa_core7.generateRunId)();
|
|
3594
3736
|
analyticsRunId = runId;
|
|
3595
3737
|
analyticsStartedAt = Date.now();
|
|
3596
3738
|
currentRunId = runId;
|
|
@@ -3615,7 +3757,7 @@ Found ${filtered.length} test(s):
|
|
|
3615
3757
|
const testMergedConfig = { ...config, use: mergedUse };
|
|
3616
3758
|
const effectiveCacheEnabled = opts.cache !== false && mergedUse.cache !== false;
|
|
3617
3759
|
if (opts.configDebug) {
|
|
3618
|
-
console.log(
|
|
3760
|
+
console.log(import_picocolors7.default.bold(`
|
|
3619
3761
|
Config for: ${test.name}`));
|
|
3620
3762
|
console.log(formatConfigDebug(config, test.use, testFlagOverrides));
|
|
3621
3763
|
console.log();
|
|
@@ -3642,7 +3784,7 @@ Config for: ${test.name}`));
|
|
|
3642
3784
|
useDeviceName: testMergedUse.device,
|
|
3643
3785
|
appState: testMergedUse.mobile?.appState,
|
|
3644
3786
|
localBindings: testLocalBindings,
|
|
3645
|
-
configFilePath: (0,
|
|
3787
|
+
configFilePath: (0, import_node_path9.resolve)(configFilePath),
|
|
3646
3788
|
localConfigFilePath: testLocalBindings?.filePath,
|
|
3647
3789
|
appiumUrl: process.env.AGENT_QA_APPIUM_URL
|
|
3648
3790
|
});
|
|
@@ -3688,7 +3830,7 @@ Config for: ${test.name}`));
|
|
|
3688
3830
|
testMobileResolved = { ...testMobileResolved, appium: { url: appiumUrl, managed: !process.env.AGENT_QA_APPIUM_URL } };
|
|
3689
3831
|
}
|
|
3690
3832
|
} catch (err) {
|
|
3691
|
-
throw new
|
|
3833
|
+
throw new import_agent_qa_core7.MobileSetupError({
|
|
3692
3834
|
category: "appium-startup",
|
|
3693
3835
|
message: `Could not auto-start Appium: ${err instanceof Error ? err.message : String(err)}`,
|
|
3694
3836
|
platform: testPlatform,
|
|
@@ -3725,7 +3867,7 @@ Config for: ${test.name}`));
|
|
|
3725
3867
|
});
|
|
3726
3868
|
}
|
|
3727
3869
|
if (effectiveLogLevel === "debug") {
|
|
3728
|
-
console.log(
|
|
3870
|
+
console.log(import_picocolors7.default.dim(` Platform: ${testPlatform}${useFarm ? ` (farm: ${testResolvedDevice?.transport})` : ""}${testPlatform !== "web" ? " (adapter re-created)" : ""}`));
|
|
3729
3871
|
}
|
|
3730
3872
|
}
|
|
3731
3873
|
const mergedForLLM = { registry: config.registry, use: testMergedConfig.use ?? config.use };
|
|
@@ -3736,7 +3878,7 @@ Config for: ${test.name}`));
|
|
|
3736
3878
|
process.env.AGENT_QA_LLM_MODEL = plannerCfg.model;
|
|
3737
3879
|
process.env.AGENT_QA_LLM_PROVIDER = plannerCfg.provider;
|
|
3738
3880
|
if (effectiveLogLevel === "debug") {
|
|
3739
|
-
console.log(
|
|
3881
|
+
console.log(import_picocolors7.default.dim(` Model: ${plannerCfg.model} (${plannerCfg.provider})`));
|
|
3740
3882
|
}
|
|
3741
3883
|
const plannerModel = await createModel(plannerModelConfig);
|
|
3742
3884
|
const verifierModel = await createModel(verifierModelConfig);
|
|
@@ -3901,7 +4043,7 @@ Config for: ${test.name}`));
|
|
|
3901
4043
|
const testId = testDef["test-id"] ?? testDef.name;
|
|
3902
4044
|
memoryInitParams = { product, testId, memoryRoot };
|
|
3903
4045
|
} catch (err) {
|
|
3904
|
-
console.warn(` ${
|
|
4046
|
+
console.warn(` ${import_picocolors7.default.dim("Memory:")} init error -- ${err.message}`);
|
|
3905
4047
|
}
|
|
3906
4048
|
}
|
|
3907
4049
|
const result = await runTestWithRetry(testDef, {
|
|
@@ -3931,6 +4073,7 @@ Config for: ${test.name}`));
|
|
|
3931
4073
|
inlineHookDefs: resolvedHooks,
|
|
3932
4074
|
inlineHookSandboxOptions: sandboxOptions,
|
|
3933
4075
|
logCapture: testLogCapture,
|
|
4076
|
+
accessibility: config.services?.accessibility,
|
|
3934
4077
|
screenshotSize: plannerCfg.screenshotSize,
|
|
3935
4078
|
effectiveResolution: plannerCfg.effectiveResolution,
|
|
3936
4079
|
contextWindow: plannerCfg.contextWindow,
|
|
@@ -3995,7 +4138,7 @@ Config for: ${test.name}`));
|
|
|
3995
4138
|
try {
|
|
3996
4139
|
const { shouldAblate, collectAllInjectedIds, deprecateOnFailure } = await import("@vostride/agent-qa-core");
|
|
3997
4140
|
if (shouldAblate(result, memoryProvider)) {
|
|
3998
|
-
console.log(` ${
|
|
4141
|
+
console.log(` ${import_picocolors7.default.dim("Memory:")} ablation retry -- re-running without memory...`);
|
|
3999
4142
|
const ablationAdapter = await createPlatformAdapter(testPlatform);
|
|
4000
4143
|
const mergedBrowser = mergedUse.browser;
|
|
4001
4144
|
const ablationPlatformConfig = buildPlatformConfig(testPlatform, testResolvedDevice, testTimeoutsForSetup, mergedBrowser ?? config.use?.browser, testMergedConfig.use?.logCapture ?? config.use?.logCapture, testFarmSession, testMobileResolved);
|
|
@@ -4032,6 +4175,7 @@ Config for: ${test.name}`));
|
|
|
4032
4175
|
inlineHookDefs: resolvedHooks,
|
|
4033
4176
|
inlineHookSandboxOptions: sandboxOptions,
|
|
4034
4177
|
logCapture: testLogCapture,
|
|
4178
|
+
accessibility: config.services?.accessibility,
|
|
4035
4179
|
screenshotSize: plannerCfg.screenshotSize,
|
|
4036
4180
|
effectiveResolution: plannerCfg.effectiveResolution,
|
|
4037
4181
|
contextWindow: plannerCfg.contextWindow
|
|
@@ -4040,7 +4184,7 @@ Config for: ${test.name}`));
|
|
|
4040
4184
|
circuitBreaker.record({ withMemory: false, passed: ablationResult.status === "passed" });
|
|
4041
4185
|
}
|
|
4042
4186
|
if (ablationResult.status === "passed") {
|
|
4043
|
-
console.log(` ${
|
|
4187
|
+
console.log(` ${import_picocolors7.default.dim("Memory:")} ablation confirmed memory caused failure -- penalizing observations`);
|
|
4044
4188
|
const injectedMap = collectAllInjectedIds(result, memoryProvider);
|
|
4045
4189
|
await deprecateOnFailure({
|
|
4046
4190
|
testResult: result,
|
|
@@ -4051,14 +4195,14 @@ Config for: ${test.name}`));
|
|
|
4051
4195
|
});
|
|
4052
4196
|
ablationHandledDeprecation = true;
|
|
4053
4197
|
} else {
|
|
4054
|
-
console.log(` ${
|
|
4198
|
+
console.log(` ${import_picocolors7.default.dim("Memory:")} ablation retry also failed -- memory not the cause`);
|
|
4055
4199
|
}
|
|
4056
4200
|
} finally {
|
|
4057
4201
|
await ablationAdapter.cleanup();
|
|
4058
4202
|
}
|
|
4059
4203
|
}
|
|
4060
4204
|
} catch (err) {
|
|
4061
|
-
console.warn(` ${
|
|
4205
|
+
console.warn(` ${import_picocolors7.default.dim("Memory:")} ablation error -- ${err.message}`);
|
|
4062
4206
|
}
|
|
4063
4207
|
}
|
|
4064
4208
|
if (memoryProvider && memoryConfig?.curatorEnabled !== false && !ablationHandledDeprecation) {
|
|
@@ -4092,7 +4236,7 @@ Config for: ${test.name}`));
|
|
|
4092
4236
|
}
|
|
4093
4237
|
}
|
|
4094
4238
|
} catch (err) {
|
|
4095
|
-
console.warn(` ${
|
|
4239
|
+
console.warn(` ${import_picocolors7.default.dim("Memory:")} curator error -- ${err.message}`);
|
|
4096
4240
|
}
|
|
4097
4241
|
}
|
|
4098
4242
|
results.push(result);
|
|
@@ -4103,13 +4247,13 @@ Config for: ${test.name}`));
|
|
|
4103
4247
|
}
|
|
4104
4248
|
if (circuitBreaker?.isTripped() && !circuitBreakerTripped) {
|
|
4105
4249
|
circuitBreakerTripped = true;
|
|
4106
|
-
console.log(` ${
|
|
4250
|
+
console.log(` ${import_picocolors7.default.yellow("Memory: circuit breaker tripped -- disabling memory injection for remaining tests")}`);
|
|
4107
4251
|
}
|
|
4108
4252
|
if (result.status === "failed" && actionCache && effectiveCacheEnabled && result.steps.length > 0) {
|
|
4109
4253
|
const { hashStepInstruction: hashStepInstruction2 } = await import("@vostride/agent-qa-core");
|
|
4110
4254
|
const { rm: rm3 } = await import("fs/promises");
|
|
4111
|
-
const { resolve:
|
|
4112
|
-
const cacheDir =
|
|
4255
|
+
const { resolve: resolve9, join: join8 } = await import("path");
|
|
4256
|
+
const cacheDir = resolve9(configDir, config.services?.cache?.dir ?? RUNTIME_CACHE_DIR);
|
|
4113
4257
|
const cacheLog = logger.createScopedLogger("cache");
|
|
4114
4258
|
let purged = 0;
|
|
4115
4259
|
for (let i = 0; i < result.steps.length; i++) {
|
|
@@ -4188,13 +4332,13 @@ Config for: ${test.name}`));
|
|
|
4188
4332
|
}
|
|
4189
4333
|
const parallelBatchFailed = await flushParallelRuns();
|
|
4190
4334
|
if (opts.bail && parallelBatchFailed) {
|
|
4191
|
-
console.log(
|
|
4335
|
+
console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failure"));
|
|
4192
4336
|
bailTriggered = true;
|
|
4193
4337
|
break;
|
|
4194
4338
|
}
|
|
4195
4339
|
const outcome = await runOneDirectTest(test);
|
|
4196
4340
|
if (opts.bail && outcome.failed) {
|
|
4197
|
-
console.log(
|
|
4341
|
+
console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failure"));
|
|
4198
4342
|
bailTriggered = true;
|
|
4199
4343
|
break;
|
|
4200
4344
|
}
|
|
@@ -4202,7 +4346,7 @@ Config for: ${test.name}`));
|
|
|
4202
4346
|
if (!bailTriggered) {
|
|
4203
4347
|
const parallelBatchFailed = await flushParallelRuns();
|
|
4204
4348
|
if (opts.bail && parallelBatchFailed) {
|
|
4205
|
-
console.log(
|
|
4349
|
+
console.log(import_picocolors7.default.yellow("\nBailing out \u2014 stopping after first failure"));
|
|
4206
4350
|
}
|
|
4207
4351
|
}
|
|
4208
4352
|
releaseAppium();
|
|
@@ -4221,7 +4365,7 @@ Config for: ${test.name}`));
|
|
|
4221
4365
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4222
4366
|
const errStack = err instanceof Error ? err.stack ?? "" : "";
|
|
4223
4367
|
const formattedError = formatFrameworkError(errMsg);
|
|
4224
|
-
console.error(
|
|
4368
|
+
console.error(import_picocolors7.default.red(formattedError.displayMessage));
|
|
4225
4369
|
if (_crashDb) {
|
|
4226
4370
|
const fullError = `${errMsg}
|
|
4227
4371
|
${errStack}`;
|
|
@@ -4272,11 +4416,11 @@ ${errStack}`;
|
|
|
4272
4416
|
}
|
|
4273
4417
|
|
|
4274
4418
|
// src/commands/dashboard.ts
|
|
4275
|
-
var
|
|
4276
|
-
var
|
|
4419
|
+
var import_commander7 = require("commander");
|
|
4420
|
+
var import_node_path10 = require("path");
|
|
4277
4421
|
var import_node_module2 = require("module");
|
|
4278
|
-
var
|
|
4279
|
-
var
|
|
4422
|
+
var import_picocolors8 = __toESM(require("picocolors"), 1);
|
|
4423
|
+
var import_agent_qa_core10 = require("@vostride/agent-qa-core");
|
|
4280
4424
|
init_config();
|
|
4281
4425
|
init_llm_utils();
|
|
4282
4426
|
init_model_defaults();
|
|
@@ -4288,9 +4432,9 @@ async function startDashboardBackedServices(opts, command, labels) {
|
|
|
4288
4432
|
const config = await resolveConfig({
|
|
4289
4433
|
configPath: globalOpts.config
|
|
4290
4434
|
});
|
|
4291
|
-
const configFilePath = (0,
|
|
4292
|
-
const configDir = (0,
|
|
4293
|
-
const workspacePaths = (0,
|
|
4435
|
+
const configFilePath = (0, import_node_path10.resolve)(globalOpts.config ?? "agent-qa.config.yaml");
|
|
4436
|
+
const configDir = (0, import_node_path10.dirname)(configFilePath);
|
|
4437
|
+
const workspacePaths = (0, import_agent_qa_core10.resolveWorkspacePaths)({
|
|
4294
4438
|
config,
|
|
4295
4439
|
configPath: configFilePath,
|
|
4296
4440
|
requireExistingFiles: true
|
|
@@ -4329,17 +4473,17 @@ async function startDashboardBackedServices(opts, command, labels) {
|
|
|
4329
4473
|
try {
|
|
4330
4474
|
const require2 = (0, import_node_module2.createRequire)(import_meta3.url);
|
|
4331
4475
|
const pkgPath = require2.resolve("@vostride/agent-qa-dashboard-ui/package.json");
|
|
4332
|
-
uiDir = (0,
|
|
4476
|
+
uiDir = (0, import_node_path10.resolve)((0, import_node_path10.dirname)(pkgPath), "dist");
|
|
4333
4477
|
} catch {
|
|
4334
4478
|
if (globalOpts.verbose) {
|
|
4335
|
-
console.log(
|
|
4479
|
+
console.log(import_picocolors8.default.yellow("Warning: @vostride/agent-qa-dashboard-ui not found \u2014 API-only mode"));
|
|
4336
4480
|
}
|
|
4337
4481
|
}
|
|
4338
4482
|
const { close } = await startServer({
|
|
4339
4483
|
db,
|
|
4340
4484
|
port,
|
|
4341
4485
|
uiDir,
|
|
4342
|
-
artifactsDir: (0,
|
|
4486
|
+
artifactsDir: (0, import_node_path10.resolve)(configDir, artifactsDir),
|
|
4343
4487
|
workspacePaths,
|
|
4344
4488
|
configPath: configFilePath,
|
|
4345
4489
|
mcp: mcpConfig,
|
|
@@ -4355,22 +4499,22 @@ async function startDashboardBackedServices(opts, command, labels) {
|
|
|
4355
4499
|
const shutdown = async () => {
|
|
4356
4500
|
if (shuttingDown) return;
|
|
4357
4501
|
shuttingDown = true;
|
|
4358
|
-
console.log(
|
|
4502
|
+
console.log(import_picocolors8.default.dim(`
|
|
4359
4503
|
Shutting down ${labels.shutdown}...`));
|
|
4360
4504
|
close();
|
|
4361
|
-
await (0,
|
|
4505
|
+
await (0, import_agent_qa_core10.flushAnalytics)({ config }).catch(() => {
|
|
4362
4506
|
});
|
|
4363
4507
|
process.exit(0);
|
|
4364
4508
|
};
|
|
4365
4509
|
process.on("SIGINT", shutdown);
|
|
4366
4510
|
process.on("SIGTERM", shutdown);
|
|
4367
4511
|
} catch (err) {
|
|
4368
|
-
console.error(
|
|
4512
|
+
console.error(import_picocolors8.default.red(`${labels.error} error: ${err instanceof Error ? err.message : String(err)}`));
|
|
4369
4513
|
process.exit(2);
|
|
4370
4514
|
}
|
|
4371
4515
|
}
|
|
4372
4516
|
function createDashboardCommand() {
|
|
4373
|
-
const cmd = new
|
|
4517
|
+
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
4518
|
await startDashboardBackedServices(opts, command, {
|
|
4375
4519
|
error: "Dashboard",
|
|
4376
4520
|
shutdown: "dashboard"
|
|
@@ -4379,7 +4523,7 @@ function createDashboardCommand() {
|
|
|
4379
4523
|
return cmd;
|
|
4380
4524
|
}
|
|
4381
4525
|
function createServeCommand() {
|
|
4382
|
-
const cmd = new
|
|
4526
|
+
const cmd = new import_commander7.Command("serve").description("Start configured local agent-qa services").action(async (_opts, command) => {
|
|
4383
4527
|
await startDashboardBackedServices({}, command, {
|
|
4384
4528
|
error: "Serve",
|
|
4385
4529
|
shutdown: "local services"
|
|
@@ -4389,12 +4533,12 @@ function createServeCommand() {
|
|
|
4389
4533
|
}
|
|
4390
4534
|
|
|
4391
4535
|
// src/commands/mcp.ts
|
|
4392
|
-
var
|
|
4393
|
-
var
|
|
4394
|
-
var
|
|
4536
|
+
var import_commander8 = require("commander");
|
|
4537
|
+
var import_picocolors9 = __toESM(require("picocolors"), 1);
|
|
4538
|
+
var import_agent_qa_core11 = require("@vostride/agent-qa-core");
|
|
4395
4539
|
init_config();
|
|
4396
4540
|
function createMcpCommand() {
|
|
4397
|
-
const cmd = new
|
|
4541
|
+
const cmd = new import_commander8.Command("mcp").description("Start MCP (Model Context Protocol) server over stdio").action(async (command) => {
|
|
4398
4542
|
try {
|
|
4399
4543
|
const program2 = command.parent;
|
|
4400
4544
|
const globalOpts = program2?.opts() ?? {};
|
|
@@ -4408,11 +4552,11 @@ function createMcpCommand() {
|
|
|
4408
4552
|
try {
|
|
4409
4553
|
await startMcpServer({ analyticsConfig: config });
|
|
4410
4554
|
} finally {
|
|
4411
|
-
await (0,
|
|
4555
|
+
await (0, import_agent_qa_core11.flushAnalytics)(config ? { config } : void 0).catch(() => {
|
|
4412
4556
|
});
|
|
4413
4557
|
}
|
|
4414
4558
|
} catch (err) {
|
|
4415
|
-
console.error(
|
|
4559
|
+
console.error(import_picocolors9.default.red("Failed to start MCP server:"), err instanceof Error ? err.message : String(err));
|
|
4416
4560
|
process.exitCode = 1;
|
|
4417
4561
|
}
|
|
4418
4562
|
});
|
|
@@ -4420,8 +4564,8 @@ function createMcpCommand() {
|
|
|
4420
4564
|
}
|
|
4421
4565
|
|
|
4422
4566
|
// src/commands/config.ts
|
|
4423
|
-
var
|
|
4424
|
-
var
|
|
4567
|
+
var import_commander9 = require("commander");
|
|
4568
|
+
var import_picocolors10 = __toESM(require("picocolors"), 1);
|
|
4425
4569
|
var import_yaml5 = require("yaml");
|
|
4426
4570
|
var import_promises3 = require("fs/promises");
|
|
4427
4571
|
var import_node_readline2 = require("readline");
|
|
@@ -4451,10 +4595,10 @@ async function readStdin() {
|
|
|
4451
4595
|
}
|
|
4452
4596
|
function plainPrompt(question) {
|
|
4453
4597
|
const rl = (0, import_node_readline2.createInterface)({ input: process.stdin, output: process.stdout });
|
|
4454
|
-
return new Promise((
|
|
4598
|
+
return new Promise((resolve9) => {
|
|
4455
4599
|
rl.question(question, (answer) => {
|
|
4456
4600
|
rl.close();
|
|
4457
|
-
|
|
4601
|
+
resolve9(answer.trim());
|
|
4458
4602
|
});
|
|
4459
4603
|
});
|
|
4460
4604
|
}
|
|
@@ -4467,14 +4611,14 @@ async function getValue(key, providedValue) {
|
|
|
4467
4611
|
}
|
|
4468
4612
|
async function setAction(key, value) {
|
|
4469
4613
|
if (isSensitiveKey(key)) {
|
|
4470
|
-
console.error(
|
|
4614
|
+
console.error(import_picocolors10.default.red("Credential values are not written by config set."));
|
|
4471
4615
|
console.error("Use agent-qa auth set --config <name> --type api-key|bearer-token for compatible or Gemini configs.");
|
|
4472
4616
|
console.error("Use plugins.auth plus the dashboard auth flow for subscription configs.");
|
|
4473
4617
|
return;
|
|
4474
4618
|
}
|
|
4475
4619
|
const resolved = await getValue(key, value);
|
|
4476
4620
|
if (!resolved) {
|
|
4477
|
-
console.error(
|
|
4621
|
+
console.error(import_picocolors10.default.red("No value provided."));
|
|
4478
4622
|
return;
|
|
4479
4623
|
}
|
|
4480
4624
|
const configPath = "agent-qa.config.yaml";
|
|
@@ -4491,7 +4635,7 @@ async function setAction(key, value) {
|
|
|
4491
4635
|
}
|
|
4492
4636
|
doc.setIn(key.split("."), resolved);
|
|
4493
4637
|
await (0, import_promises3.writeFile)(configPath, doc.toString());
|
|
4494
|
-
console.log(
|
|
4638
|
+
console.log(import_picocolors10.default.green(`\u2713 ${key} = ${resolved} saved to agent-qa.config.yaml`));
|
|
4495
4639
|
}
|
|
4496
4640
|
async function showAction() {
|
|
4497
4641
|
const fileConfig = await loadConfigFile("agent-qa.config.yaml");
|
|
@@ -4499,20 +4643,20 @@ async function showAction() {
|
|
|
4499
4643
|
const { mergeConfigs: mergeConfigs2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4500
4644
|
const globalConfig = mergeConfigs2(fileConfig, envOverrides, {});
|
|
4501
4645
|
const table = formatConfigDebug(globalConfig, {}, {});
|
|
4502
|
-
console.log(
|
|
4646
|
+
console.log(import_picocolors10.default.bold("\nResolved Configuration:\n"));
|
|
4503
4647
|
console.log(table);
|
|
4504
4648
|
console.log();
|
|
4505
4649
|
}
|
|
4506
4650
|
function createConfigCommand() {
|
|
4507
|
-
const cmd = new
|
|
4651
|
+
const cmd = new import_commander9.Command("config").description("Manage configuration");
|
|
4508
4652
|
cmd.command("set <key> [value]").description("Set a config value").action(setAction);
|
|
4509
4653
|
cmd.command("show").description("Show resolved config with source attribution").action(showAction);
|
|
4510
4654
|
return cmd;
|
|
4511
4655
|
}
|
|
4512
4656
|
|
|
4513
4657
|
// src/commands/queue.ts
|
|
4514
|
-
var
|
|
4515
|
-
var
|
|
4658
|
+
var import_commander10 = require("commander");
|
|
4659
|
+
var import_picocolors11 = __toESM(require("picocolors"), 1);
|
|
4516
4660
|
init_config();
|
|
4517
4661
|
function timeAgo(dateStr) {
|
|
4518
4662
|
const diff = Date.now() - new Date(dateStr).getTime();
|
|
@@ -4536,7 +4680,7 @@ async function resolveServerUrl(opts, globalOpts) {
|
|
|
4536
4680
|
}
|
|
4537
4681
|
}
|
|
4538
4682
|
function createQueueCommand() {
|
|
4539
|
-
const cmd = new
|
|
4683
|
+
const cmd = new import_commander10.Command("queue").description("Manage the test execution queue");
|
|
4540
4684
|
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
4685
|
try {
|
|
4542
4686
|
const program2 = command.parent.parent;
|
|
@@ -4547,13 +4691,13 @@ function createQueueCommand() {
|
|
|
4547
4691
|
try {
|
|
4548
4692
|
const response = await fetch(endpoint);
|
|
4549
4693
|
if (!response.ok) {
|
|
4550
|
-
console.error(
|
|
4694
|
+
console.error(import_picocolors11.default.red(`Server returned ${response.status}: ${response.statusText}`));
|
|
4551
4695
|
process.exit(1);
|
|
4552
4696
|
}
|
|
4553
4697
|
data = await response.json();
|
|
4554
4698
|
} catch {
|
|
4555
|
-
console.error(
|
|
4556
|
-
console.error(
|
|
4699
|
+
console.error(import_picocolors11.default.red(`Could not connect to dashboard server at ${serverUrl}`));
|
|
4700
|
+
console.error(import_picocolors11.default.dim("Make sure the dashboard is running: agent-qa dashboard"));
|
|
4557
4701
|
process.exit(1);
|
|
4558
4702
|
}
|
|
4559
4703
|
if (opts.json) {
|
|
@@ -4561,19 +4705,19 @@ function createQueueCommand() {
|
|
|
4561
4705
|
return;
|
|
4562
4706
|
}
|
|
4563
4707
|
console.log();
|
|
4564
|
-
console.log(
|
|
4565
|
-
console.log(` Concurrency: ${
|
|
4708
|
+
console.log(import_picocolors11.default.bold("Queue Status"));
|
|
4709
|
+
console.log(` Concurrency: ${import_picocolors11.default.blue(String(data.concurrency))} slots (${import_picocolors11.default.yellow(String(data.activeSlots))} active)`);
|
|
4566
4710
|
console.log();
|
|
4567
4711
|
const pending = data.pending?.jobs ?? [];
|
|
4568
4712
|
const running = data.running?.jobs ?? [];
|
|
4569
4713
|
if (pending.length === 0 && running.length === 0) {
|
|
4570
|
-
console.log(
|
|
4714
|
+
console.log(import_picocolors11.default.dim(" Queue is empty \u2014 no pending or running jobs"));
|
|
4571
4715
|
console.log();
|
|
4572
4716
|
return;
|
|
4573
4717
|
}
|
|
4574
4718
|
if (pending.length > 0) {
|
|
4575
|
-
console.log(
|
|
4576
|
-
console.log(
|
|
4719
|
+
console.log(import_picocolors11.default.bold(`Pending (${pending.length} jobs):`));
|
|
4720
|
+
console.log(import_picocolors11.default.dim(" # ID Name Priority Source Queued"));
|
|
4577
4721
|
for (let i = 0; i < pending.length; i++) {
|
|
4578
4722
|
const job = pending[i];
|
|
4579
4723
|
const num = String(i + 1).padEnd(3);
|
|
@@ -4582,37 +4726,37 @@ function createQueueCommand() {
|
|
|
4582
4726
|
const priority = String(job.priority ?? 0).padEnd(8);
|
|
4583
4727
|
const source = (job.source ?? "").padEnd(10);
|
|
4584
4728
|
const queued = job.createdAt ? timeAgo(job.createdAt) : "";
|
|
4585
|
-
console.log(` ${num} ${
|
|
4729
|
+
console.log(` ${num} ${import_picocolors11.default.dim(id)} ${name} ${priority} ${source} ${import_picocolors11.default.dim(queued)}`);
|
|
4586
4730
|
}
|
|
4587
4731
|
console.log();
|
|
4588
4732
|
}
|
|
4589
4733
|
if (running.length > 0) {
|
|
4590
|
-
console.log(
|
|
4591
|
-
console.log(
|
|
4734
|
+
console.log(import_picocolors11.default.bold(`Running (${running.length} jobs):`));
|
|
4735
|
+
console.log(import_picocolors11.default.dim(" ID Name Source Started"));
|
|
4592
4736
|
for (const job of running) {
|
|
4593
4737
|
const id = (job.id ?? "").slice(0, 12).padEnd(12);
|
|
4594
4738
|
const name = (job.name ?? "").slice(0, 18).padEnd(18);
|
|
4595
4739
|
const source = (job.source ?? "").padEnd(10);
|
|
4596
4740
|
const started = job.startedAt ? timeAgo(job.startedAt) : "";
|
|
4597
|
-
console.log(` ${
|
|
4741
|
+
console.log(` ${import_picocolors11.default.green(id)} ${name} ${source} ${import_picocolors11.default.dim(started)}`);
|
|
4598
4742
|
}
|
|
4599
4743
|
console.log();
|
|
4600
4744
|
}
|
|
4601
4745
|
if (opts.all && data.recent?.length > 0) {
|
|
4602
|
-
console.log(
|
|
4603
|
-
console.log(
|
|
4746
|
+
console.log(import_picocolors11.default.bold(`Recent (${data.recent.length} jobs):`));
|
|
4747
|
+
console.log(import_picocolors11.default.dim(" ID Name Status Duration"));
|
|
4604
4748
|
for (const job of data.recent) {
|
|
4605
4749
|
const id = (job.id ?? "").slice(0, 12).padEnd(12);
|
|
4606
4750
|
const name = (job.name ?? "").slice(0, 18).padEnd(18);
|
|
4607
4751
|
const status = (job.status ?? "").padEnd(8);
|
|
4608
4752
|
const duration = job.duration ? `${Math.round(job.duration / 1e3)}s` : "";
|
|
4609
|
-
const statusColor = job.status === "passed" ?
|
|
4610
|
-
console.log(` ${
|
|
4753
|
+
const statusColor = job.status === "passed" ? import_picocolors11.default.green : job.status === "failed" ? import_picocolors11.default.red : import_picocolors11.default.dim;
|
|
4754
|
+
console.log(` ${import_picocolors11.default.dim(id)} ${name} ${statusColor(status)} ${import_picocolors11.default.dim(duration)}`);
|
|
4611
4755
|
}
|
|
4612
4756
|
console.log();
|
|
4613
4757
|
}
|
|
4614
4758
|
} catch (err) {
|
|
4615
|
-
console.error(
|
|
4759
|
+
console.error(import_picocolors11.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
4616
4760
|
process.exit(1);
|
|
4617
4761
|
}
|
|
4618
4762
|
});
|
|
@@ -4625,25 +4769,25 @@ function createQueueCommand() {
|
|
|
4625
4769
|
try {
|
|
4626
4770
|
response = await fetch(`${serverUrl}/api/runs/${runId}/cancel`, { method: "POST" });
|
|
4627
4771
|
} catch {
|
|
4628
|
-
console.error(
|
|
4629
|
-
console.error(
|
|
4772
|
+
console.error(import_picocolors11.default.red(`Could not connect to dashboard server at ${serverUrl}`));
|
|
4773
|
+
console.error(import_picocolors11.default.dim("Make sure the dashboard is running: agent-qa dashboard"));
|
|
4630
4774
|
process.exit(1);
|
|
4631
4775
|
}
|
|
4632
4776
|
if (response.ok) {
|
|
4633
4777
|
const data = await response.json();
|
|
4634
4778
|
if (data.cancelled) {
|
|
4635
|
-
console.log(
|
|
4779
|
+
console.log(import_picocolors11.default.green(`\u2713 Cancelled job ${runId}`));
|
|
4636
4780
|
} else {
|
|
4637
|
-
console.log(
|
|
4781
|
+
console.log(import_picocolors11.default.red(`Failed to cancel: ${data.error ?? "Unknown error"}`));
|
|
4638
4782
|
}
|
|
4639
4783
|
} else if (response.status === 404) {
|
|
4640
|
-
console.log(
|
|
4784
|
+
console.log(import_picocolors11.default.red(`Job not found: ${runId}`));
|
|
4641
4785
|
} else {
|
|
4642
4786
|
const data = await response.json().catch(() => ({}));
|
|
4643
|
-
console.log(
|
|
4787
|
+
console.log(import_picocolors11.default.red(`Failed to cancel: ${data.error ?? response.statusText}`));
|
|
4644
4788
|
}
|
|
4645
4789
|
} catch (err) {
|
|
4646
|
-
console.error(
|
|
4790
|
+
console.error(import_picocolors11.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
4647
4791
|
process.exit(1);
|
|
4648
4792
|
}
|
|
4649
4793
|
});
|
|
@@ -4651,24 +4795,24 @@ function createQueueCommand() {
|
|
|
4651
4795
|
}
|
|
4652
4796
|
|
|
4653
4797
|
// src/commands/validate.ts
|
|
4654
|
-
var
|
|
4655
|
-
var
|
|
4798
|
+
var import_commander11 = require("commander");
|
|
4799
|
+
var import_agent_qa_core12 = require("@vostride/agent-qa-core");
|
|
4656
4800
|
init_config();
|
|
4657
4801
|
function createValidateCommand() {
|
|
4658
|
-
const cmd = new
|
|
4802
|
+
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
4803
|
const configPath = command.parent?.opts().config ?? "agent-qa.config.yaml";
|
|
4660
4804
|
let workspace;
|
|
4661
4805
|
try {
|
|
4662
4806
|
const config = await resolveConfig({ configPath });
|
|
4663
|
-
workspace = (0,
|
|
4807
|
+
workspace = (0, import_agent_qa_core12.resolveWorkspacePaths)({ config, configPath });
|
|
4664
4808
|
} catch {
|
|
4665
4809
|
}
|
|
4666
|
-
const result = await (0,
|
|
4810
|
+
const result = await (0, import_agent_qa_core12.validateProject)({
|
|
4667
4811
|
configPath,
|
|
4668
4812
|
files: files.length > 0 ? files : void 0,
|
|
4669
4813
|
workspace
|
|
4670
4814
|
});
|
|
4671
|
-
const output = (0,
|
|
4815
|
+
const output = (0, import_agent_qa_core12.formatDiagnostics)(result);
|
|
4672
4816
|
process.stdout.write(output);
|
|
4673
4817
|
if (result.errorCount > 0) {
|
|
4674
4818
|
process.exitCode = 1;
|
|
@@ -4678,9 +4822,9 @@ function createValidateCommand() {
|
|
|
4678
4822
|
}
|
|
4679
4823
|
|
|
4680
4824
|
// src/commands/auth.ts
|
|
4681
|
-
var
|
|
4825
|
+
var import_commander12 = require("commander");
|
|
4682
4826
|
var import_node_child_process4 = require("child_process");
|
|
4683
|
-
var
|
|
4827
|
+
var import_picocolors12 = __toESM(require("picocolors"), 1);
|
|
4684
4828
|
var import_input2 = __toESM(require("@inquirer/input"), 1);
|
|
4685
4829
|
var import_password = __toESM(require("@inquirer/password"), 1);
|
|
4686
4830
|
var PROVIDER_LABELS = {
|
|
@@ -4715,10 +4859,10 @@ function getGlobalConfigPath2(command) {
|
|
|
4715
4859
|
return command.parent?.parent?.opts().config ?? command.parent?.opts().config;
|
|
4716
4860
|
}
|
|
4717
4861
|
function printCredentialGuidance(configName, provider) {
|
|
4718
|
-
console.log(
|
|
4719
|
-
console.log(
|
|
4862
|
+
console.log(import_picocolors12.default.red(`Config ${configName} uses ${providerLabel(provider)} credentials.`));
|
|
4863
|
+
console.log(import_picocolors12.default.dim("Use agent-qa auth set --config <name> --type api-key"));
|
|
4720
4864
|
if (provider === "anthropic-compatible") {
|
|
4721
|
-
console.log(
|
|
4865
|
+
console.log(import_picocolors12.default.dim("or --type bearer-token"));
|
|
4722
4866
|
}
|
|
4723
4867
|
}
|
|
4724
4868
|
function applyAuthToModelConfig(llmConfig, auth) {
|
|
@@ -4738,15 +4882,15 @@ function applyAuthToModelConfig(llmConfig, auth) {
|
|
|
4738
4882
|
function statusForResolvedAuth(auth) {
|
|
4739
4883
|
switch (auth.kind) {
|
|
4740
4884
|
case "api-key":
|
|
4741
|
-
return
|
|
4885
|
+
return import_picocolors12.default.green("Saved API key");
|
|
4742
4886
|
case "bearer-token":
|
|
4743
|
-
return
|
|
4887
|
+
return import_picocolors12.default.green("Saved bearer token");
|
|
4744
4888
|
case "auth-fetch":
|
|
4745
|
-
return Number(auth.expires ?? Date.now() + 1) <= Date.now() ?
|
|
4889
|
+
return Number(auth.expires ?? Date.now() + 1) <= Date.now() ? import_picocolors12.default.red("Expired") : import_picocolors12.default.green("OAuth connected");
|
|
4746
4890
|
case "unauthenticated":
|
|
4747
|
-
return
|
|
4891
|
+
return import_picocolors12.default.dim("No credential");
|
|
4748
4892
|
case "missing":
|
|
4749
|
-
return
|
|
4893
|
+
return import_picocolors12.default.red("Missing credential");
|
|
4750
4894
|
}
|
|
4751
4895
|
}
|
|
4752
4896
|
function extractProviderError(err) {
|
|
@@ -4777,16 +4921,16 @@ function openAuthorizationUrl(url) {
|
|
|
4777
4921
|
});
|
|
4778
4922
|
}
|
|
4779
4923
|
function printMissingSubscriptionPluginGuidance(configName, provider) {
|
|
4780
|
-
console.log(
|
|
4781
|
-
console.log(
|
|
4782
|
-
console.log(
|
|
4783
|
-
console.log(
|
|
4784
|
-
console.log(
|
|
4785
|
-
console.log(
|
|
4786
|
-
console.log(
|
|
4787
|
-
console.log(
|
|
4788
|
-
console.log(
|
|
4789
|
-
console.log(
|
|
4924
|
+
console.log(import_picocolors12.default.red(`Provider "${provider}" is configured for "${configName}", but no auth plugin is registered.`));
|
|
4925
|
+
console.log(import_picocolors12.default.dim(`Config: ${configName}`));
|
|
4926
|
+
console.log(import_picocolors12.default.dim(`Provider: ${provider}`));
|
|
4927
|
+
console.log(import_picocolors12.default.dim("Add the subscription auth plugin to package.json and install with your package manager:"));
|
|
4928
|
+
console.log(import_picocolors12.default.dim(' "devDependencies": { "@vostride/agent-qa-subscription-auth": "<agent-qa version>" }'));
|
|
4929
|
+
console.log(import_picocolors12.default.dim("Declare it in agent-qa.config.yaml under plugins.auth:"));
|
|
4930
|
+
console.log(import_picocolors12.default.dim(" plugins:"));
|
|
4931
|
+
console.log(import_picocolors12.default.dim(" auth:"));
|
|
4932
|
+
console.log(import_picocolors12.default.dim(" - package: @vostride/agent-qa-subscription-auth"));
|
|
4933
|
+
console.log(import_picocolors12.default.dim("Then authenticate from agent-qa dashboard or rerun this command."));
|
|
4790
4934
|
}
|
|
4791
4935
|
async function readTokensFromPlugin(provider, plugin, started) {
|
|
4792
4936
|
if (started.waitForTokens) {
|
|
@@ -4794,19 +4938,19 @@ async function readTokensFromPlugin(provider, plugin, started) {
|
|
|
4794
4938
|
}
|
|
4795
4939
|
if (plugin.dashboardAuth.mode === "manual-code") {
|
|
4796
4940
|
if (!plugin.exchangeCode) {
|
|
4797
|
-
console.log(
|
|
4941
|
+
console.log(import_picocolors12.default.red(`Provider "${provider}" does not support CLI code exchange. Use agent-qa dashboard.`));
|
|
4798
4942
|
process.exitCode = 1;
|
|
4799
4943
|
return null;
|
|
4800
4944
|
}
|
|
4801
4945
|
const code = (await (0, import_input2.default)({ message: "Authorization code:" })).trim();
|
|
4802
4946
|
if (!code) {
|
|
4803
|
-
console.log(
|
|
4947
|
+
console.log(import_picocolors12.default.red("Authorization code is required"));
|
|
4804
4948
|
process.exitCode = 1;
|
|
4805
4949
|
return null;
|
|
4806
4950
|
}
|
|
4807
4951
|
return await plugin.exchangeCode({ code, sessionState: started.sessionState });
|
|
4808
4952
|
}
|
|
4809
|
-
console.log(
|
|
4953
|
+
console.log(import_picocolors12.default.red(`Provider "${provider}" does not support CLI auth login. Use agent-qa dashboard.`));
|
|
4810
4954
|
process.exitCode = 1;
|
|
4811
4955
|
return null;
|
|
4812
4956
|
}
|
|
@@ -4819,14 +4963,14 @@ async function runSubscriptionPluginLogin(configName, provider) {
|
|
|
4819
4963
|
return;
|
|
4820
4964
|
}
|
|
4821
4965
|
if (!plugin.startAuth) {
|
|
4822
|
-
console.log(
|
|
4966
|
+
console.log(import_picocolors12.default.red(`Provider "${provider}" does not support CLI auth login. Use agent-qa dashboard.`));
|
|
4823
4967
|
process.exitCode = 1;
|
|
4824
4968
|
return;
|
|
4825
4969
|
}
|
|
4826
4970
|
let started;
|
|
4827
4971
|
try {
|
|
4828
4972
|
started = await plugin.startAuth({ configName });
|
|
4829
|
-
console.log(`Open this URL to authenticate: ${
|
|
4973
|
+
console.log(`Open this URL to authenticate: ${import_picocolors12.default.cyan(started.authorizeUrl)}`);
|
|
4830
4974
|
openAuthorizationUrl(started.authorizeUrl);
|
|
4831
4975
|
const tokens = await readTokensFromPlugin(provider, plugin, started);
|
|
4832
4976
|
if (!tokens) return;
|
|
@@ -4835,18 +4979,18 @@ async function runSubscriptionPluginLogin(configName, provider) {
|
|
|
4835
4979
|
provider: plugin.credentialProviderId,
|
|
4836
4980
|
tokens
|
|
4837
4981
|
});
|
|
4838
|
-
console.log(
|
|
4982
|
+
console.log(import_picocolors12.default.green(`Authenticated ${configName} with ${plugin.label}`));
|
|
4839
4983
|
} catch (err) {
|
|
4840
|
-
console.log(
|
|
4984
|
+
console.log(import_picocolors12.default.red(err instanceof Error ? err.message : String(err)));
|
|
4841
4985
|
process.exitCode = 1;
|
|
4842
4986
|
} finally {
|
|
4843
4987
|
started?.cleanup?.();
|
|
4844
4988
|
}
|
|
4845
4989
|
}
|
|
4846
4990
|
function createAuthCommand() {
|
|
4847
|
-
const cmd = new
|
|
4991
|
+
const cmd = new import_commander12.Command("auth").description("Manage LLM config credentials");
|
|
4848
4992
|
cmd.addCommand(
|
|
4849
|
-
new
|
|
4993
|
+
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
4994
|
const { resolveNamedConfig: resolveNamedConfig2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
|
|
4851
4995
|
const configPath = getGlobalConfigPath2(command);
|
|
4852
4996
|
let provider;
|
|
@@ -4854,13 +4998,13 @@ function createAuthCommand() {
|
|
|
4854
4998
|
try {
|
|
4855
4999
|
const requestedName = normalizeConfigName(opts.config);
|
|
4856
5000
|
if (!requestedName) {
|
|
4857
|
-
console.log(
|
|
5001
|
+
console.log(import_picocolors12.default.red("configName is required"));
|
|
4858
5002
|
return;
|
|
4859
5003
|
}
|
|
4860
5004
|
const resolved = await resolveNamedConfig2(requestedName, configPath);
|
|
4861
5005
|
const resolvedName = normalizeConfigName(resolved.config.name);
|
|
4862
5006
|
if (!resolvedName) {
|
|
4863
|
-
console.log(
|
|
5007
|
+
console.log(import_picocolors12.default.red("Config name is invalid"));
|
|
4864
5008
|
return;
|
|
4865
5009
|
}
|
|
4866
5010
|
configName = resolvedName;
|
|
@@ -4871,17 +5015,17 @@ function createAuthCommand() {
|
|
|
4871
5015
|
}
|
|
4872
5016
|
provider = configProvider;
|
|
4873
5017
|
} catch (err) {
|
|
4874
|
-
console.log(
|
|
5018
|
+
console.log(import_picocolors12.default.red(err instanceof Error ? err.message : String(err)));
|
|
4875
5019
|
return;
|
|
4876
5020
|
}
|
|
4877
5021
|
await runSubscriptionPluginLogin(configName, provider);
|
|
4878
5022
|
})
|
|
4879
5023
|
);
|
|
4880
5024
|
cmd.addCommand(
|
|
4881
|
-
new
|
|
5025
|
+
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
5026
|
const credentialType = opts.type.trim();
|
|
4883
5027
|
if (credentialType !== "api-key" && credentialType !== "bearer-token") {
|
|
4884
|
-
console.log(
|
|
5028
|
+
console.log(import_picocolors12.default.red("type must be api-key or bearer-token"));
|
|
4885
5029
|
return;
|
|
4886
5030
|
}
|
|
4887
5031
|
const { resolveNamedConfig: resolveNamedConfig2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
|
|
@@ -4890,44 +5034,44 @@ function createAuthCommand() {
|
|
|
4890
5034
|
try {
|
|
4891
5035
|
resolved = await resolveNamedConfig2(opts.config, configPath);
|
|
4892
5036
|
} catch (err) {
|
|
4893
|
-
console.log(
|
|
5037
|
+
console.log(import_picocolors12.default.red(err instanceof Error ? err.message : String(err)));
|
|
4894
5038
|
return;
|
|
4895
5039
|
}
|
|
4896
5040
|
const configName = resolved.config.name ?? opts.config;
|
|
4897
5041
|
const provider = resolved.config.provider;
|
|
4898
5042
|
if (SUBSCRIPTION_PROVIDERS2.has(provider)) {
|
|
4899
|
-
console.log(
|
|
5043
|
+
console.log(import_picocolors12.default.red("Subscription providers use OAuth login"));
|
|
4900
5044
|
return;
|
|
4901
5045
|
}
|
|
4902
5046
|
if (credentialType === "bearer-token" && provider !== "anthropic-compatible") {
|
|
4903
|
-
console.log(
|
|
5047
|
+
console.log(import_picocolors12.default.red("bearer-token credentials are only supported for anthropic-compatible configs"));
|
|
4904
5048
|
return;
|
|
4905
5049
|
}
|
|
4906
5050
|
if (credentialType === "api-key" && !API_KEY_CREDENTIAL_PROVIDERS.has(provider)) {
|
|
4907
|
-
console.log(
|
|
5051
|
+
console.log(import_picocolors12.default.red("api-key credentials are not supported for this provider"));
|
|
4908
5052
|
return;
|
|
4909
5053
|
}
|
|
4910
5054
|
const secret = (secretArg ?? await (0, import_password.default)({ message: "Secret:" })).trim();
|
|
4911
5055
|
if (!secret) {
|
|
4912
|
-
console.log(
|
|
5056
|
+
console.log(import_picocolors12.default.red("Secret is required"));
|
|
4913
5057
|
return;
|
|
4914
5058
|
}
|
|
4915
5059
|
const { writeAuth } = await import("@vostride/agent-qa-core");
|
|
4916
5060
|
if (credentialType === "bearer-token") {
|
|
4917
5061
|
await writeAuth(configName, { type: "bearer", provider: "anthropic-compatible", token: secret });
|
|
4918
|
-
console.log(
|
|
5062
|
+
console.log(import_picocolors12.default.green(`Saved bearer token for ${configName}`));
|
|
4919
5063
|
} else {
|
|
4920
5064
|
await writeAuth(configName, { type: "api", provider, key: secret });
|
|
4921
|
-
console.log(
|
|
5065
|
+
console.log(import_picocolors12.default.green(`Saved API key for ${configName}`));
|
|
4922
5066
|
}
|
|
4923
5067
|
})
|
|
4924
5068
|
);
|
|
4925
5069
|
cmd.addCommand(
|
|
4926
|
-
new
|
|
5070
|
+
new import_commander12.Command("status").description("Show credential status for configured LLMs").action(async function() {
|
|
4927
5071
|
const { loadAuthPluginsForRawConfig: loadAuthPluginsForRawConfig2, resolveModelAuth: resolveModelAuth2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
|
|
4928
5072
|
const { loadConfigFile: loadConfigFile2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4929
5073
|
const configPath = getGlobalConfigPath2(this) ?? "agent-qa.config.yaml";
|
|
4930
|
-
console.log(
|
|
5074
|
+
console.log(import_picocolors12.default.bold("Auth status:\n"));
|
|
4931
5075
|
let llms;
|
|
4932
5076
|
let defaultLLM;
|
|
4933
5077
|
try {
|
|
@@ -4938,25 +5082,25 @@ function createAuthCommand() {
|
|
|
4938
5082
|
} catch {
|
|
4939
5083
|
}
|
|
4940
5084
|
if (!llms?.length) {
|
|
4941
|
-
console.log(
|
|
5085
|
+
console.log(import_picocolors12.default.dim(" No LLM configs found"));
|
|
4942
5086
|
return;
|
|
4943
5087
|
}
|
|
4944
5088
|
const maxNameLen = Math.max(...llms.map((c) => String(c.name ?? "").length), 4) + 12;
|
|
4945
5089
|
for (const llm of llms) {
|
|
4946
5090
|
const name = String(llm.name ?? "");
|
|
4947
5091
|
const isDefault = name === defaultLLM;
|
|
4948
|
-
const label = isDefault ? `${name} ${
|
|
5092
|
+
const label = isDefault ? `${name} ${import_picocolors12.default.dim("(default)")}` : name;
|
|
4949
5093
|
const rawLabel = isDefault ? `${name} (default)` : name;
|
|
4950
5094
|
const padLen = Math.max(0, maxNameLen - rawLabel.length);
|
|
4951
5095
|
const dots = ".".repeat(padLen + 2);
|
|
4952
5096
|
const provider = llm.provider;
|
|
4953
5097
|
const status = statusForResolvedAuth(await resolveModelAuth2(name, llm));
|
|
4954
|
-
console.log(` ${label} ${
|
|
5098
|
+
console.log(` ${label} ${import_picocolors12.default.dim(`[${providerLabel(provider)}]`)} ${import_picocolors12.default.dim(dots)} ${status}`);
|
|
4955
5099
|
}
|
|
4956
5100
|
})
|
|
4957
5101
|
);
|
|
4958
5102
|
cmd.addCommand(
|
|
4959
|
-
new
|
|
5103
|
+
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
5104
|
const { removeAuth } = await import("@vostride/agent-qa-core");
|
|
4961
5105
|
const { resolveNamedConfig: resolveNamedConfig2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
|
|
4962
5106
|
const configPath = getGlobalConfigPath2(command);
|
|
@@ -4968,16 +5112,16 @@ function createAuthCommand() {
|
|
|
4968
5112
|
const resolved = await resolveNamedConfig2(void 0, configPath);
|
|
4969
5113
|
storeKey = resolved.config.name;
|
|
4970
5114
|
} catch {
|
|
4971
|
-
console.log(
|
|
5115
|
+
console.log(import_picocolors12.default.red("Specify --config <name> or run with a config file"));
|
|
4972
5116
|
return;
|
|
4973
5117
|
}
|
|
4974
5118
|
}
|
|
4975
5119
|
await removeAuth(storeKey);
|
|
4976
|
-
console.log(
|
|
5120
|
+
console.log(import_picocolors12.default.green(`Logged out from ${storeKey}`));
|
|
4977
5121
|
})
|
|
4978
5122
|
);
|
|
4979
5123
|
cmd.addCommand(
|
|
4980
|
-
new
|
|
5124
|
+
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
5125
|
const { resolveModelAuth: resolveModelAuth2, resolveNamedConfig: resolveNamedConfig2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
|
|
4982
5126
|
const configPath = getGlobalConfigPath2(command);
|
|
4983
5127
|
let planner;
|
|
@@ -4988,19 +5132,19 @@ function createAuthCommand() {
|
|
|
4988
5132
|
planner = modelFields;
|
|
4989
5133
|
configName = name;
|
|
4990
5134
|
} catch (err) {
|
|
4991
|
-
console.log(
|
|
5135
|
+
console.log(import_picocolors12.default.red(err instanceof Error ? err.message : String(err)));
|
|
4992
5136
|
return;
|
|
4993
5137
|
}
|
|
4994
5138
|
if (opts.provider) planner = { ...planner, provider: opts.provider };
|
|
4995
5139
|
if (opts.model) planner = { ...planner, model: opts.model };
|
|
4996
|
-
console.log(
|
|
5140
|
+
console.log(import_picocolors12.default.dim(`Testing config: ${configName}...`));
|
|
4997
5141
|
const auth = await resolveModelAuth2(configName, planner);
|
|
4998
5142
|
if (auth.kind === "missing") {
|
|
4999
|
-
console.log(
|
|
5143
|
+
console.log(import_picocolors12.default.red(auth.message));
|
|
5000
5144
|
return;
|
|
5001
5145
|
}
|
|
5002
5146
|
if (auth.kind === "unauthenticated") {
|
|
5003
|
-
console.log(
|
|
5147
|
+
console.log(import_picocolors12.default.dim(auth.message));
|
|
5004
5148
|
}
|
|
5005
5149
|
const { createModel, getProviderOptions } = await import("@vostride/agent-qa-core");
|
|
5006
5150
|
const { generateText } = await import("ai");
|
|
@@ -5018,24 +5162,24 @@ function createAuthCommand() {
|
|
|
5018
5162
|
...providerOpts ? { providerOptions: providerOpts } : {}
|
|
5019
5163
|
});
|
|
5020
5164
|
const elapsed = Date.now() - start;
|
|
5021
|
-
console.log(
|
|
5165
|
+
console.log(import_picocolors12.default.green(`Connected to ${planner.provider}/${planner.model} (${elapsed}ms)`));
|
|
5022
5166
|
} catch (err) {
|
|
5023
5167
|
const { message, statusCode } = extractProviderError(err);
|
|
5024
5168
|
const fullContext = `${message} ${statusCode ?? ""}`;
|
|
5025
5169
|
if (/auth|unauthorized|401|invalid.*key|permission/i.test(fullContext)) {
|
|
5026
|
-
console.log(
|
|
5170
|
+
console.log(import_picocolors12.default.red("Authentication failed. Check the saved credential for this config."));
|
|
5027
5171
|
} else if (/model.*not.*support|not found|404|does not exist/i.test(fullContext)) {
|
|
5028
|
-
console.log(
|
|
5172
|
+
console.log(import_picocolors12.default.red("Model not found. Check the model name."));
|
|
5029
5173
|
} else if (/rate|429|quota|limit/i.test(fullContext)) {
|
|
5030
|
-
console.log(
|
|
5174
|
+
console.log(import_picocolors12.default.red("Rate limited. Try again later."));
|
|
5031
5175
|
} else if (/ECONNREFUSED|ENOTFOUND|timeout|abort|fetch failed|network/i.test(fullContext)) {
|
|
5032
|
-
console.log(
|
|
5176
|
+
console.log(import_picocolors12.default.red("Network error. Check the exact base URL and try again."));
|
|
5033
5177
|
} else if (/invalid_request|400|bad request/i.test(fullContext)) {
|
|
5034
|
-
console.log(
|
|
5178
|
+
console.log(import_picocolors12.default.red("Invalid request. Check model and provider settings."));
|
|
5035
5179
|
} else {
|
|
5036
|
-
console.log(
|
|
5180
|
+
console.log(import_picocolors12.default.red(`Connection failed. ${message}`));
|
|
5037
5181
|
}
|
|
5038
|
-
console.log(
|
|
5182
|
+
console.log(import_picocolors12.default.dim(message));
|
|
5039
5183
|
} finally {
|
|
5040
5184
|
clearTimeout(timeout);
|
|
5041
5185
|
}
|
|
@@ -5045,36 +5189,36 @@ function createAuthCommand() {
|
|
|
5045
5189
|
}
|
|
5046
5190
|
|
|
5047
5191
|
// src/commands/devices.ts
|
|
5048
|
-
var
|
|
5192
|
+
var import_commander13 = require("commander");
|
|
5049
5193
|
var import_node_child_process5 = require("child_process");
|
|
5050
5194
|
var import_node_fs4 = require("fs");
|
|
5051
|
-
var
|
|
5195
|
+
var import_node_path11 = require("path");
|
|
5052
5196
|
var import_yaml6 = require("yaml");
|
|
5053
|
-
var
|
|
5197
|
+
var import_picocolors13 = __toESM(require("picocolors"), 1);
|
|
5054
5198
|
init_config();
|
|
5055
5199
|
function formatStatus2(transport, localBinding) {
|
|
5056
5200
|
if (transport !== "local") {
|
|
5057
|
-
return
|
|
5201
|
+
return import_picocolors13.default.green("Ready") + import_picocolors13.default.dim(" (no local binding required)");
|
|
5058
5202
|
}
|
|
5059
5203
|
if (!localBinding) {
|
|
5060
|
-
return
|
|
5204
|
+
return import_picocolors13.default.red("UNBOUND") + import_picocolors13.default.yellow(" - run `agent-qa devices init`");
|
|
5061
5205
|
}
|
|
5062
5206
|
const keys = Object.entries(localBinding).slice(0, 2);
|
|
5063
5207
|
const details = keys.map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
5064
|
-
return
|
|
5208
|
+
return import_picocolors13.default.green("Bound") + import_picocolors13.default.dim(` (${details})`);
|
|
5065
5209
|
}
|
|
5066
5210
|
async function listDevices() {
|
|
5067
5211
|
let config;
|
|
5068
5212
|
try {
|
|
5069
5213
|
config = await resolveConfig({});
|
|
5070
5214
|
} catch (err) {
|
|
5071
|
-
console.error(
|
|
5215
|
+
console.error(import_picocolors13.default.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
|
|
5072
5216
|
process.exitCode = 1;
|
|
5073
5217
|
return;
|
|
5074
5218
|
}
|
|
5075
5219
|
const devices = config.registry?.devices;
|
|
5076
5220
|
if (!devices || Object.keys(devices).length === 0) {
|
|
5077
|
-
console.log(
|
|
5221
|
+
console.log(import_picocolors13.default.yellow("No devices configured in registry.devices"));
|
|
5078
5222
|
return;
|
|
5079
5223
|
}
|
|
5080
5224
|
const localBindings = loadLocalBindings();
|
|
@@ -5082,13 +5226,13 @@ async function listDevices() {
|
|
|
5082
5226
|
const platWidth = 10;
|
|
5083
5227
|
const transWidth = 14;
|
|
5084
5228
|
console.log("");
|
|
5085
|
-
console.log(
|
|
5229
|
+
console.log(import_picocolors13.default.bold(" Configured Devices"));
|
|
5086
5230
|
console.log("");
|
|
5087
5231
|
console.log(
|
|
5088
|
-
` ${
|
|
5232
|
+
` ${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
5233
|
);
|
|
5090
5234
|
console.log(
|
|
5091
|
-
` ${
|
|
5235
|
+
` ${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
5236
|
);
|
|
5093
5237
|
for (const [name, profile] of Object.entries(devices)) {
|
|
5094
5238
|
const localBinding = localBindings?.devices?.[name] ?? null;
|
|
@@ -5139,7 +5283,7 @@ function scanSimctlDevices() {
|
|
|
5139
5283
|
}
|
|
5140
5284
|
}
|
|
5141
5285
|
function appendGitignoreEntry(dir, entry) {
|
|
5142
|
-
const gitignorePath = (0,
|
|
5286
|
+
const gitignorePath = (0, import_node_path11.join)(dir, ".gitignore");
|
|
5143
5287
|
let existing = "";
|
|
5144
5288
|
if ((0, import_node_fs4.existsSync)(gitignorePath)) {
|
|
5145
5289
|
existing = (0, import_node_fs4.readFileSync)(gitignorePath, "utf-8");
|
|
@@ -5150,30 +5294,30 @@ function appendGitignoreEntry(dir, entry) {
|
|
|
5150
5294
|
}
|
|
5151
5295
|
async function initDevices() {
|
|
5152
5296
|
const dir = process.cwd();
|
|
5153
|
-
const localFilePath = (0,
|
|
5297
|
+
const localFilePath = (0, import_node_path11.join)(dir, "agent-qa.local.yaml");
|
|
5154
5298
|
if ((0, import_node_fs4.existsSync)(localFilePath)) {
|
|
5155
|
-
console.log(
|
|
5299
|
+
console.log(import_picocolors13.default.yellow("agent-qa.local.yaml already exists. Skipping generation."));
|
|
5156
5300
|
return;
|
|
5157
5301
|
}
|
|
5158
|
-
console.log(
|
|
5302
|
+
console.log(import_picocolors13.default.bold("\n Scanning for connected devices...\n"));
|
|
5159
5303
|
const adbDevices = scanAdbDevices();
|
|
5160
5304
|
const simctlDevices = scanSimctlDevices();
|
|
5161
5305
|
if (adbDevices.length > 0) {
|
|
5162
|
-
console.log(` ${
|
|
5306
|
+
console.log(` ${import_picocolors13.default.green("Android:")} found ${adbDevices.length} device(s)`);
|
|
5163
5307
|
for (const d of adbDevices) {
|
|
5164
|
-
console.log(` ${
|
|
5308
|
+
console.log(` ${import_picocolors13.default.dim("\u2022")} ${d.model} (${d.serial})`);
|
|
5165
5309
|
}
|
|
5166
5310
|
} else {
|
|
5167
|
-
console.log(` ${
|
|
5311
|
+
console.log(` ${import_picocolors13.default.dim("Android:")} no devices found (adb not available or no devices connected)`);
|
|
5168
5312
|
}
|
|
5169
5313
|
if (simctlDevices.length > 0) {
|
|
5170
5314
|
const booted = simctlDevices.filter((d) => d.state === "Booted");
|
|
5171
|
-
console.log(` ${
|
|
5315
|
+
console.log(` ${import_picocolors13.default.green("iOS:")} found ${simctlDevices.length} simulator(s) (${booted.length} booted)`);
|
|
5172
5316
|
for (const d of booted) {
|
|
5173
|
-
console.log(` ${
|
|
5317
|
+
console.log(` ${import_picocolors13.default.dim("\u2022")} ${d.name} (${d.udid})`);
|
|
5174
5318
|
}
|
|
5175
5319
|
} else {
|
|
5176
|
-
console.log(` ${
|
|
5320
|
+
console.log(` ${import_picocolors13.default.dim("iOS:")} no simulators found (simctl not available or none installed)`);
|
|
5177
5321
|
}
|
|
5178
5322
|
let config;
|
|
5179
5323
|
try {
|
|
@@ -5207,20 +5351,20 @@ async function initDevices() {
|
|
|
5207
5351
|
(0, import_node_fs4.writeFileSync)(localFilePath, yamlContent);
|
|
5208
5352
|
appendGitignoreEntry(dir, "agent-qa.local.yaml");
|
|
5209
5353
|
console.log("");
|
|
5210
|
-
console.log(
|
|
5211
|
-
console.log(
|
|
5354
|
+
console.log(import_picocolors13.default.green(` Generated agent-qa.local.yaml with ${bindCount} device binding(s)`));
|
|
5355
|
+
console.log(import_picocolors13.default.dim(" Added agent-qa.local.yaml to .gitignore"));
|
|
5212
5356
|
console.log("");
|
|
5213
5357
|
}
|
|
5214
5358
|
function createDevicesCommand() {
|
|
5215
|
-
const cmd = new
|
|
5359
|
+
const cmd = new import_commander13.Command("devices").description("Manage device profiles and local bindings");
|
|
5216
5360
|
cmd.command("list").description("Show configured devices with merged shared + local view").action(listDevices);
|
|
5217
5361
|
cmd.command("init").description("Scan connected devices and generate agent-qa.local.yaml").action(initDevices);
|
|
5218
5362
|
return cmd;
|
|
5219
5363
|
}
|
|
5220
5364
|
|
|
5221
5365
|
// src/commands/ids.ts
|
|
5222
|
-
var
|
|
5223
|
-
var
|
|
5366
|
+
var import_commander14 = require("commander");
|
|
5367
|
+
var import_picocolors14 = __toESM(require("picocolors"), 1);
|
|
5224
5368
|
var import_agent_qa_ids = require("@vostride/agent-qa-ids");
|
|
5225
5369
|
function parseEntityType(value) {
|
|
5226
5370
|
if (import_agent_qa_ids.ENTITY_ID_TYPES.includes(value)) {
|
|
@@ -5229,7 +5373,7 @@ function parseEntityType(value) {
|
|
|
5229
5373
|
throw new Error(`Unknown ID type "${value}". Expected one of: ${import_agent_qa_ids.ENTITY_ID_TYPES.join(", ")}`);
|
|
5230
5374
|
}
|
|
5231
5375
|
function createIdsCommand() {
|
|
5232
|
-
const cmd = new
|
|
5376
|
+
const cmd = new import_commander14.Command("ids").description("Generate and validate canonical agent-qa entity IDs");
|
|
5233
5377
|
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
5378
|
try {
|
|
5235
5379
|
const type = parseEntityType(typeValue);
|
|
@@ -5241,7 +5385,7 @@ function createIdsCommand() {
|
|
|
5241
5385
|
console.log(id);
|
|
5242
5386
|
}
|
|
5243
5387
|
} catch (err) {
|
|
5244
|
-
console.error(
|
|
5388
|
+
console.error(import_picocolors14.default.red(err instanceof Error ? err.message : String(err)));
|
|
5245
5389
|
process.exitCode = 1;
|
|
5246
5390
|
}
|
|
5247
5391
|
});
|
|
@@ -5260,15 +5404,15 @@ function createIdsCommand() {
|
|
|
5260
5404
|
if (opts.json) {
|
|
5261
5405
|
console.log(JSON.stringify(result, null, 2));
|
|
5262
5406
|
} else if (valid) {
|
|
5263
|
-
console.log(
|
|
5407
|
+
console.log(import_picocolors14.default.green(result.message));
|
|
5264
5408
|
} else {
|
|
5265
|
-
console.error(
|
|
5409
|
+
console.error(import_picocolors14.default.red(result.message));
|
|
5266
5410
|
}
|
|
5267
5411
|
if (!valid) {
|
|
5268
5412
|
process.exitCode = 1;
|
|
5269
5413
|
}
|
|
5270
5414
|
} catch (err) {
|
|
5271
|
-
console.error(
|
|
5415
|
+
console.error(import_picocolors14.default.red(err instanceof Error ? err.message : String(err)));
|
|
5272
5416
|
process.exitCode = 1;
|
|
5273
5417
|
}
|
|
5274
5418
|
});
|
|
@@ -5276,18 +5420,18 @@ function createIdsCommand() {
|
|
|
5276
5420
|
}
|
|
5277
5421
|
|
|
5278
5422
|
// src/commands/create-test.ts
|
|
5279
|
-
var
|
|
5423
|
+
var import_commander15 = require("commander");
|
|
5280
5424
|
var import_agent_qa_ids2 = require("@vostride/agent-qa-ids");
|
|
5281
5425
|
var import_yaml7 = require("yaml");
|
|
5282
5426
|
var import_promises4 = require("fs/promises");
|
|
5283
5427
|
var import_node_fs5 = require("fs");
|
|
5284
|
-
var
|
|
5285
|
-
var
|
|
5428
|
+
var import_node_path12 = require("path");
|
|
5429
|
+
var import_picocolors15 = __toESM(require("picocolors"), 1);
|
|
5286
5430
|
function createCreateTestCommand() {
|
|
5287
|
-
const cmd = new
|
|
5288
|
-
const resolved = (0,
|
|
5431
|
+
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) => {
|
|
5432
|
+
const resolved = (0, import_node_path12.resolve)(path);
|
|
5289
5433
|
if ((0, import_node_fs5.existsSync)(resolved)) {
|
|
5290
|
-
console.error(
|
|
5434
|
+
console.error(import_picocolors15.default.red("Error: File already exists: " + resolved));
|
|
5291
5435
|
process.exitCode = 1;
|
|
5292
5436
|
return;
|
|
5293
5437
|
}
|
|
@@ -5299,27 +5443,27 @@ function createCreateTestCommand() {
|
|
|
5299
5443
|
steps: ["Describe your first test step here"]
|
|
5300
5444
|
};
|
|
5301
5445
|
const yamlContent = (0, import_yaml7.stringify)(template);
|
|
5302
|
-
await (0, import_promises4.mkdir)((0,
|
|
5446
|
+
await (0, import_promises4.mkdir)((0, import_node_path12.dirname)(resolved), { recursive: true });
|
|
5303
5447
|
await (0, import_promises4.writeFile)(resolved, yamlContent, "utf-8");
|
|
5304
|
-
console.log(
|
|
5305
|
-
console.log(
|
|
5448
|
+
console.log(import_picocolors15.default.green("Created " + resolved));
|
|
5449
|
+
console.log(import_picocolors15.default.dim(" test-id: " + testId));
|
|
5306
5450
|
});
|
|
5307
5451
|
return cmd;
|
|
5308
5452
|
}
|
|
5309
5453
|
|
|
5310
5454
|
// src/commands/create-suite.ts
|
|
5311
|
-
var
|
|
5455
|
+
var import_commander16 = require("commander");
|
|
5312
5456
|
var import_agent_qa_ids3 = require("@vostride/agent-qa-ids");
|
|
5313
5457
|
var import_yaml8 = require("yaml");
|
|
5314
5458
|
var import_promises5 = require("fs/promises");
|
|
5315
5459
|
var import_node_fs6 = require("fs");
|
|
5316
|
-
var
|
|
5317
|
-
var
|
|
5460
|
+
var import_node_path13 = require("path");
|
|
5461
|
+
var import_picocolors16 = __toESM(require("picocolors"), 1);
|
|
5318
5462
|
function createCreateSuiteCommand() {
|
|
5319
|
-
const cmd = new
|
|
5320
|
-
const resolved = (0,
|
|
5463
|
+
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) => {
|
|
5464
|
+
const resolved = (0, import_node_path13.resolve)(path);
|
|
5321
5465
|
if ((0, import_node_fs6.existsSync)(resolved)) {
|
|
5322
|
-
console.error(
|
|
5466
|
+
console.error(import_picocolors16.default.red("Error: File already exists: " + resolved));
|
|
5323
5467
|
process.exitCode = 1;
|
|
5324
5468
|
return;
|
|
5325
5469
|
}
|
|
@@ -5333,22 +5477,22 @@ function createCreateSuiteCommand() {
|
|
|
5333
5477
|
"tests: []",
|
|
5334
5478
|
"tests: []\n # - test: ./path/to/test.yaml\n # id: my-test-id"
|
|
5335
5479
|
);
|
|
5336
|
-
await (0, import_promises5.mkdir)((0,
|
|
5480
|
+
await (0, import_promises5.mkdir)((0, import_node_path13.dirname)(resolved), { recursive: true });
|
|
5337
5481
|
await (0, import_promises5.writeFile)(resolved, yamlContent, "utf-8");
|
|
5338
|
-
console.log(
|
|
5339
|
-
console.log(
|
|
5482
|
+
console.log(import_picocolors16.default.green("Created " + resolved));
|
|
5483
|
+
console.log(import_picocolors16.default.dim(" suite-id: " + suiteId));
|
|
5340
5484
|
});
|
|
5341
5485
|
return cmd;
|
|
5342
5486
|
}
|
|
5343
5487
|
|
|
5344
5488
|
// src/commands/clean-memory.ts
|
|
5345
|
-
var
|
|
5346
|
-
var
|
|
5489
|
+
var import_commander17 = require("commander");
|
|
5490
|
+
var import_picocolors17 = __toESM(require("picocolors"), 1);
|
|
5347
5491
|
var import_node_readline3 = require("readline");
|
|
5348
5492
|
var import_promises6 = require("fs/promises");
|
|
5349
5493
|
var import_node_fs7 = require("fs");
|
|
5350
|
-
var
|
|
5351
|
-
var
|
|
5494
|
+
var import_node_path14 = require("path");
|
|
5495
|
+
var import_agent_qa_core13 = require("@vostride/agent-qa-core");
|
|
5352
5496
|
init_config();
|
|
5353
5497
|
function formatSize(bytes) {
|
|
5354
5498
|
if (bytes === 0) return "0 B";
|
|
@@ -5356,13 +5500,13 @@ function formatSize(bytes) {
|
|
|
5356
5500
|
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
5357
5501
|
}
|
|
5358
5502
|
async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
5359
|
-
const scopePath = (0,
|
|
5360
|
-
const obsFiles = await (0,
|
|
5503
|
+
const scopePath = (0, import_node_path14.join)(memoryRoot, tier, scope);
|
|
5504
|
+
const obsFiles = await (0, import_agent_qa_core13.listObservations)(scopePath);
|
|
5361
5505
|
let newest = null;
|
|
5362
5506
|
for (const filename of obsFiles) {
|
|
5363
5507
|
try {
|
|
5364
|
-
const content = await (0, import_promises6.readFile)((0,
|
|
5365
|
-
const { data } = (0,
|
|
5508
|
+
const content = await (0, import_promises6.readFile)((0, import_node_path14.join)(scopePath, filename), "utf-8");
|
|
5509
|
+
const { data } = (0, import_agent_qa_core13.parseObservation)(content, filename);
|
|
5366
5510
|
if (data?.last_confirmed) {
|
|
5367
5511
|
if (!newest || data.last_confirmed > newest) {
|
|
5368
5512
|
newest = data.last_confirmed;
|
|
@@ -5376,7 +5520,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5376
5520
|
const entries = await (0, import_promises6.readdir)(scopePath);
|
|
5377
5521
|
for (const entry of entries) {
|
|
5378
5522
|
try {
|
|
5379
|
-
const s = await (0, import_promises6.stat)((0,
|
|
5523
|
+
const s = await (0, import_promises6.stat)((0, import_node_path14.join)(scopePath, entry));
|
|
5380
5524
|
if (s.isFile()) totalSize += s.size;
|
|
5381
5525
|
} catch {
|
|
5382
5526
|
}
|
|
@@ -5384,7 +5528,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5384
5528
|
} catch {
|
|
5385
5529
|
}
|
|
5386
5530
|
return {
|
|
5387
|
-
path: (0,
|
|
5531
|
+
path: (0, import_node_path14.join)(tier, scope),
|
|
5388
5532
|
tier,
|
|
5389
5533
|
scope,
|
|
5390
5534
|
fileCount: obsFiles.length,
|
|
@@ -5393,21 +5537,21 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5393
5537
|
};
|
|
5394
5538
|
}
|
|
5395
5539
|
function createCleanMemoryCommand() {
|
|
5396
|
-
const cmd = new
|
|
5540
|
+
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
5541
|
const opts = command.opts();
|
|
5398
5542
|
const configPath = command.parent?.opts()?.config ?? "agent-qa.config.yaml";
|
|
5399
5543
|
const config = await resolveConfig({ configPath });
|
|
5400
|
-
const workspace = (0,
|
|
5544
|
+
const workspace = (0, import_agent_qa_core13.resolveWorkspacePaths)({ config, configPath });
|
|
5401
5545
|
const expectedProducts = /* @__PURE__ */ new Set();
|
|
5402
5546
|
const targets = config.registry?.targets ?? {};
|
|
5403
5547
|
for (const [name, entry] of Object.entries(targets)) {
|
|
5404
5548
|
expectedProducts.add(entry?.product ?? name);
|
|
5405
5549
|
}
|
|
5406
5550
|
const expectedTests = /* @__PURE__ */ new Set();
|
|
5407
|
-
for (const file of await (0,
|
|
5551
|
+
for (const file of await (0, import_agent_qa_core13.discoverWorkspaceFiles)({ workspace, kind: "test" })) {
|
|
5408
5552
|
try {
|
|
5409
5553
|
const content = await (0, import_promises6.readFile)(file.absolutePath, "utf-8");
|
|
5410
|
-
const result = (0,
|
|
5554
|
+
const result = (0, import_agent_qa_core13.parseTestFile)(content, file.workspaceRelativePath);
|
|
5411
5555
|
for (const t of result.tests) {
|
|
5412
5556
|
if (t["test-id"]) expectedTests.add(t["test-id"]);
|
|
5413
5557
|
expectedTests.add(t.name);
|
|
@@ -5416,17 +5560,17 @@ function createCleanMemoryCommand() {
|
|
|
5416
5560
|
}
|
|
5417
5561
|
}
|
|
5418
5562
|
const expectedSuites = /* @__PURE__ */ new Set();
|
|
5419
|
-
for (const file of await (0,
|
|
5563
|
+
for (const file of await (0, import_agent_qa_core13.discoverWorkspaceFiles)({ workspace, kind: "suite" })) {
|
|
5420
5564
|
try {
|
|
5421
|
-
const suite = await (0,
|
|
5565
|
+
const suite = await (0, import_agent_qa_core13.parseSuiteFile)(file.absolutePath);
|
|
5422
5566
|
if (suite["suite-id"]) expectedSuites.add(suite["suite-id"]);
|
|
5423
5567
|
expectedSuites.add(suite.name);
|
|
5424
5568
|
} catch {
|
|
5425
5569
|
}
|
|
5426
5570
|
}
|
|
5427
|
-
const memoryRoot = (0,
|
|
5571
|
+
const memoryRoot = (0, import_agent_qa_core13.resolveMemoryRoot)(config, workspace.configDir);
|
|
5428
5572
|
if (!(0, import_node_fs7.existsSync)(memoryRoot)) {
|
|
5429
|
-
console.log(
|
|
5573
|
+
console.log(import_picocolors17.default.dim("No memory directory found."));
|
|
5430
5574
|
return;
|
|
5431
5575
|
}
|
|
5432
5576
|
const TIERS = [
|
|
@@ -5436,7 +5580,7 @@ function createCleanMemoryCommand() {
|
|
|
5436
5580
|
];
|
|
5437
5581
|
const orphans = [];
|
|
5438
5582
|
for (const tier of TIERS) {
|
|
5439
|
-
const tierPath = (0,
|
|
5583
|
+
const tierPath = (0, import_node_path14.join)(memoryRoot, tier.dir);
|
|
5440
5584
|
let entries;
|
|
5441
5585
|
try {
|
|
5442
5586
|
entries = await (0, import_promises6.readdir)(tierPath);
|
|
@@ -5445,7 +5589,7 @@ function createCleanMemoryCommand() {
|
|
|
5445
5589
|
}
|
|
5446
5590
|
for (const entry of entries) {
|
|
5447
5591
|
try {
|
|
5448
|
-
const s = await (0, import_promises6.stat)((0,
|
|
5592
|
+
const s = await (0, import_promises6.stat)((0, import_node_path14.join)(tierPath, entry));
|
|
5449
5593
|
if (!s.isDirectory()) continue;
|
|
5450
5594
|
} catch {
|
|
5451
5595
|
continue;
|
|
@@ -5456,17 +5600,17 @@ function createCleanMemoryCommand() {
|
|
|
5456
5600
|
}
|
|
5457
5601
|
}
|
|
5458
5602
|
if (orphans.length === 0) {
|
|
5459
|
-
console.log(
|
|
5603
|
+
console.log(import_picocolors17.default.dim("No orphaned memory directories found."));
|
|
5460
5604
|
return;
|
|
5461
5605
|
}
|
|
5462
5606
|
const maxPathLen = Math.max("Path".length, ...orphans.map((o) => o.path.length));
|
|
5463
5607
|
console.log("");
|
|
5464
|
-
console.log(
|
|
5608
|
+
console.log(import_picocolors17.default.bold("Orphaned memory directories:"));
|
|
5465
5609
|
console.log("");
|
|
5466
|
-
console.log(" " +
|
|
5467
|
-
console.log(" " +
|
|
5610
|
+
console.log(" " + import_picocolors17.default.dim("Path".padEnd(maxPathLen + 2) + "Files".padStart(7) + " " + "Last Confirmed".padEnd(20) + "Size".padStart(10)));
|
|
5611
|
+
console.log(" " + import_picocolors17.default.dim("-".repeat(maxPathLen + 2 + 7 + 2 + 20 + 10)));
|
|
5468
5612
|
for (const o of orphans) {
|
|
5469
|
-
const confirmed = o.lastConfirmed ??
|
|
5613
|
+
const confirmed = o.lastConfirmed ?? import_picocolors17.default.dim("never");
|
|
5470
5614
|
console.log(" " + o.path.padEnd(maxPathLen + 2) + String(o.fileCount).padStart(7) + " " + String(confirmed).padEnd(20) + formatSize(o.size).padStart(10));
|
|
5471
5615
|
}
|
|
5472
5616
|
console.log("");
|
|
@@ -5482,23 +5626,23 @@ function createCleanMemoryCommand() {
|
|
|
5482
5626
|
}
|
|
5483
5627
|
}
|
|
5484
5628
|
for (const orphan of orphans) {
|
|
5485
|
-
await (0, import_promises6.rm)((0,
|
|
5629
|
+
await (0, import_promises6.rm)((0, import_node_path14.join)(memoryRoot, orphan.tier, orphan.scope), { recursive: true, force: true });
|
|
5486
5630
|
}
|
|
5487
|
-
console.log(
|
|
5631
|
+
console.log(import_picocolors17.default.green(`Deleted ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}.`));
|
|
5488
5632
|
});
|
|
5489
5633
|
return cmd;
|
|
5490
5634
|
}
|
|
5491
5635
|
|
|
5492
5636
|
// src/commands/skills.ts
|
|
5493
|
-
var
|
|
5637
|
+
var import_commander18 = require("commander");
|
|
5494
5638
|
var import_node_fs8 = require("fs");
|
|
5495
|
-
var
|
|
5639
|
+
var import_node_path15 = require("path");
|
|
5496
5640
|
var import_node_url = require("url");
|
|
5497
5641
|
var import_meta4 = {};
|
|
5498
5642
|
function findPackageRoot(startDir) {
|
|
5499
|
-
let current = (0,
|
|
5643
|
+
let current = (0, import_node_path15.resolve)(startDir);
|
|
5500
5644
|
while (true) {
|
|
5501
|
-
const packagePath = (0,
|
|
5645
|
+
const packagePath = (0, import_node_path15.join)(current, "package.json");
|
|
5502
5646
|
if ((0, import_node_fs8.existsSync)(packagePath)) {
|
|
5503
5647
|
try {
|
|
5504
5648
|
const pkg = JSON.parse((0, import_node_fs8.readFileSync)(packagePath, "utf-8"));
|
|
@@ -5508,9 +5652,9 @@ function findPackageRoot(startDir) {
|
|
|
5508
5652
|
} catch {
|
|
5509
5653
|
}
|
|
5510
5654
|
}
|
|
5511
|
-
const parent = (0,
|
|
5655
|
+
const parent = (0, import_node_path15.dirname)(current);
|
|
5512
5656
|
if (parent === current) {
|
|
5513
|
-
return (0,
|
|
5657
|
+
return (0, import_node_path15.resolve)(startDir);
|
|
5514
5658
|
}
|
|
5515
5659
|
current = parent;
|
|
5516
5660
|
}
|
|
@@ -5519,20 +5663,20 @@ function hasSkillDirectories(dir) {
|
|
|
5519
5663
|
if (!(0, import_node_fs8.existsSync)(dir)) return false;
|
|
5520
5664
|
try {
|
|
5521
5665
|
return (0, import_node_fs8.readdirSync)(dir).some((entry) => {
|
|
5522
|
-
const skillDir = (0,
|
|
5523
|
-
return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0,
|
|
5666
|
+
const skillDir = (0, import_node_path15.join)(dir, entry);
|
|
5667
|
+
return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0, import_node_path15.join)(skillDir, "SKILL.md"));
|
|
5524
5668
|
});
|
|
5525
5669
|
} catch {
|
|
5526
5670
|
return false;
|
|
5527
5671
|
}
|
|
5528
5672
|
}
|
|
5529
|
-
function resolveAgentQaSkillsDirectory(startDir = (0,
|
|
5673
|
+
function resolveAgentQaSkillsDirectory(startDir = (0, import_node_path15.dirname)((0, import_node_url.fileURLToPath)(import_meta4.url))) {
|
|
5530
5674
|
const packageRoot = findPackageRoot(startDir);
|
|
5531
|
-
const packagedSkills = (0,
|
|
5675
|
+
const packagedSkills = (0, import_node_path15.join)(packageRoot, "skills");
|
|
5532
5676
|
if (hasSkillDirectories(packagedSkills)) {
|
|
5533
5677
|
return { path: packagedSkills, source: "package" };
|
|
5534
5678
|
}
|
|
5535
|
-
const sourceSkills = (0,
|
|
5679
|
+
const sourceSkills = (0, import_node_path15.resolve)(packageRoot, "../..", "skills");
|
|
5536
5680
|
if (hasSkillDirectories(sourceSkills)) {
|
|
5537
5681
|
return { path: sourceSkills, source: "source" };
|
|
5538
5682
|
}
|
|
@@ -5546,21 +5690,21 @@ function parseSkillDescription(skillPath) {
|
|
|
5546
5690
|
function listAgentQaSkills(skillsDir) {
|
|
5547
5691
|
if (!hasSkillDirectories(skillsDir)) return [];
|
|
5548
5692
|
return (0, import_node_fs8.readdirSync)(skillsDir).filter((entry) => {
|
|
5549
|
-
const skillDir = (0,
|
|
5550
|
-
return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0,
|
|
5693
|
+
const skillDir = (0, import_node_path15.join)(skillsDir, entry);
|
|
5694
|
+
return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0, import_node_path15.join)(skillDir, "SKILL.md"));
|
|
5551
5695
|
}).sort().map((entry) => {
|
|
5552
|
-
const skillDir = (0,
|
|
5696
|
+
const skillDir = (0, import_node_path15.join)(skillsDir, entry);
|
|
5553
5697
|
return {
|
|
5554
5698
|
name: entry,
|
|
5555
5699
|
path: skillDir,
|
|
5556
|
-
description: parseSkillDescription((0,
|
|
5700
|
+
description: parseSkillDescription((0, import_node_path15.join)(skillDir, "SKILL.md"))
|
|
5557
5701
|
};
|
|
5558
5702
|
});
|
|
5559
5703
|
}
|
|
5560
5704
|
function createSkillsCommand(options = {}) {
|
|
5561
5705
|
const stdout = options.stdout ?? process.stdout;
|
|
5562
5706
|
const stderr = options.stderr ?? process.stderr;
|
|
5563
|
-
return new
|
|
5707
|
+
return new import_commander18.Command("skills").description("List packaged agent-qa skills").option("--json", "print machine-readable output").action((opts) => {
|
|
5564
5708
|
const resolved = resolveAgentQaSkillsDirectory(options.startDir);
|
|
5565
5709
|
const skills = listAgentQaSkills(resolved.path);
|
|
5566
5710
|
if (resolved.source === "missing" || skills.length === 0) {
|
|
@@ -5641,13 +5785,14 @@ function rejectRemovedCliSurface(input3 = {}) {
|
|
|
5641
5785
|
|
|
5642
5786
|
// src/cli.ts
|
|
5643
5787
|
rejectRemovedCliSurface();
|
|
5644
|
-
var program = new
|
|
5788
|
+
var program = new import_commander19.Command();
|
|
5645
5789
|
program.enablePositionalOptions();
|
|
5646
5790
|
program.name("agent-qa").description("AI-powered testing framework").version(VERSION);
|
|
5647
5791
|
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
5792
|
program.addCommand(createRunCommand());
|
|
5649
5793
|
program.addCommand(createInitCommand());
|
|
5650
5794
|
program.addCommand(createInstallBrowsersCommand());
|
|
5795
|
+
program.addCommand(createInstallMobileDriversCommand());
|
|
5651
5796
|
program.addCommand(createDoctorCommand());
|
|
5652
5797
|
program.addCommand(createDashboardCommand());
|
|
5653
5798
|
program.addCommand(createServeCommand());
|