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