agent-qa 0.1.18 → 0.1.20
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 +465 -355
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +365 -255
- package/dist/cli.js.map +1 -1
- package/package.json +9 -9
package/dist/cli.js
CHANGED
|
@@ -598,10 +598,14 @@ function buildExampleTest(platform, variant) {
|
|
|
598
598
|
const expectedUrl = variant === "pass" ? "https://www.iana.org/help/example-domains" : "https://www.iana.org/example-domains";
|
|
599
599
|
const testId = variant === "pass" ? "t_amber-birch-coral-delta-ember-falcon-garden-harbor-island-jungle" : "t_bronze-cedar-dune-ember-fjord-glade-harbor-ivory-jade-kelp";
|
|
600
600
|
const name = variant === "pass" ? "Example passing test" : "Example failing test";
|
|
601
|
+
const browserOverride = variant === "fail" && hasWeb(platform) ? `use:
|
|
602
|
+
browser:
|
|
603
|
+
headless: false
|
|
604
|
+
` : "";
|
|
601
605
|
return `test-id: ${testId}
|
|
602
606
|
name: ${name}
|
|
603
607
|
target: ${defaultExampleTargetName(platform)}
|
|
604
|
-
steps:
|
|
608
|
+
${browserOverride}steps:
|
|
605
609
|
- Verify the page says "Example Domain"
|
|
606
610
|
- Click on "Learn More"
|
|
607
611
|
- Verify the page url is "${expectedUrl}"
|
|
@@ -2113,7 +2117,7 @@ init_config();
|
|
|
2113
2117
|
import { randomUUID } from "crypto";
|
|
2114
2118
|
import { resolve as resolvePath3 } from "path";
|
|
2115
2119
|
import { Command as Command6 } from "commander";
|
|
2116
|
-
import
|
|
2120
|
+
import pc8 from "picocolors";
|
|
2117
2121
|
import { ATTR_RUNNER, ATTR_TRIGGER, AUTH_STATE_SCHEMA_VERSION, buildInternalRunAttributes, DEFAULT_AGENT_QA_ARTIFACTS_DIR, DEFAULT_AGENT_QA_CACHE_DIR as DEFAULT_AGENT_QA_CACHE_DIR3, DEFAULT_AGENT_QA_VIDEOS_DIR, formatRunAttributesBlock, generateRunId, mergeRunAttributes, MobileSetupError, normalizeAuthStateUse, parseRunAttrFlags, redactAuthStateValue, resolveAuthStateForRun, resolveAuthStatePaths, resolveMemoryRoot, resolveMobileRunConfig, validateTrustedRunAttributes } from "@vostride/agent-qa-core";
|
|
2118
2122
|
import { discoverWorkspaceFiles as discoverWorkspaceFiles2, isWorkspacePathMatch, resolveWorkspaceFileTarget, resolveWorkspacePaths as resolveWorkspacePaths2 } from "@vostride/agent-qa-core";
|
|
2119
2123
|
|
|
@@ -2209,6 +2213,88 @@ function resolveProviderCredentials(transport, localBindings) {
|
|
|
2209
2213
|
// src/commands/run.ts
|
|
2210
2214
|
init_llm_utils();
|
|
2211
2215
|
import { isPathInsideDir } from "@vostride/agent-qa-core";
|
|
2216
|
+
|
|
2217
|
+
// src/version-notice.ts
|
|
2218
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
2219
|
+
import { join as join5 } from "path";
|
|
2220
|
+
import pc7 from "picocolors";
|
|
2221
|
+
import { getAgentQaUpdateStatus } from "@vostride/agent-qa-core";
|
|
2222
|
+
var RELEASES_URL = "https://github.com/vostride/agent-qa/releases";
|
|
2223
|
+
var UPDATE_COMMANDS = {
|
|
2224
|
+
pnpm: "pnpm add -D agent-qa@latest",
|
|
2225
|
+
npm: "npm install --save-dev agent-qa@latest",
|
|
2226
|
+
yarn: "yarn add --dev agent-qa@latest",
|
|
2227
|
+
bun: "bun add --dev agent-qa@latest"
|
|
2228
|
+
};
|
|
2229
|
+
var LOCKFILES = [
|
|
2230
|
+
{ file: "pnpm-lock.yaml", packageManager: "pnpm" },
|
|
2231
|
+
{ file: "package-lock.json", packageManager: "npm" },
|
|
2232
|
+
{ file: "npm-shrinkwrap.json", packageManager: "npm" },
|
|
2233
|
+
{ file: "yarn.lock", packageManager: "yarn" },
|
|
2234
|
+
{ file: "bun.lock", packageManager: "bun" },
|
|
2235
|
+
{ file: "bun.lockb", packageManager: "bun" }
|
|
2236
|
+
];
|
|
2237
|
+
function shouldPrintAgentQaUpdateNotice(context) {
|
|
2238
|
+
return Boolean(
|
|
2239
|
+
context.reporterSelection.console && !context.reporterSelection.stdoutLive && context.liveEvents !== "true" && context.effectiveLogLevel !== "quiet" && context.effectiveLogLevel !== "silent"
|
|
2240
|
+
);
|
|
2241
|
+
}
|
|
2242
|
+
function inferAgentQaDevDependencyUpdateCommand(cwd = process.cwd()) {
|
|
2243
|
+
try {
|
|
2244
|
+
const packageJsonPath = join5(cwd, "package.json");
|
|
2245
|
+
if (!existsSync3(packageJsonPath)) return void 0;
|
|
2246
|
+
const manifest = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
|
|
2247
|
+
if (!isRecord(manifest) || !hasAgentQaDevDependency(manifest)) return void 0;
|
|
2248
|
+
const packageManager = inferPackageManager(manifest, cwd);
|
|
2249
|
+
return packageManager ? UPDATE_COMMANDS[packageManager] : void 0;
|
|
2250
|
+
} catch {
|
|
2251
|
+
return void 0;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
async function printAgentQaUpdateNoticeIfNeeded(context) {
|
|
2255
|
+
try {
|
|
2256
|
+
if (!shouldPrintAgentQaUpdateNotice(context)) return;
|
|
2257
|
+
const status = await getAgentQaUpdateStatus();
|
|
2258
|
+
const installedVersion = status.installedVersion.trim();
|
|
2259
|
+
const latestVersion = typeof status.latestVersion === "string" ? status.latestVersion.trim() : "";
|
|
2260
|
+
if (!status.updateAvailable || !latestVersion) return;
|
|
2261
|
+
const command = inferAgentQaDevDependencyUpdateCommand(context.cwd);
|
|
2262
|
+
const log = context.log ?? console.log;
|
|
2263
|
+
log();
|
|
2264
|
+
log(`${pc7.bold("Update available:")} agent-qa v${latestVersion}`);
|
|
2265
|
+
if (installedVersion) {
|
|
2266
|
+
log(pc7.dim(`Current version: v${installedVersion}`));
|
|
2267
|
+
}
|
|
2268
|
+
log(`Releases: ${pc7.cyan(RELEASES_URL)}`);
|
|
2269
|
+
if (command) {
|
|
2270
|
+
log(`Update: ${pc7.cyan(command)}`);
|
|
2271
|
+
}
|
|
2272
|
+
} catch {
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
function hasAgentQaDevDependency(manifest) {
|
|
2276
|
+
const devDependencies = manifest.devDependencies;
|
|
2277
|
+
return isRecord(devDependencies) && Object.prototype.hasOwnProperty.call(devDependencies, "agent-qa");
|
|
2278
|
+
}
|
|
2279
|
+
function inferPackageManager(manifest, cwd) {
|
|
2280
|
+
if (typeof manifest.packageManager === "string") {
|
|
2281
|
+
return packageManagerFromSpecifier(manifest.packageManager);
|
|
2282
|
+
}
|
|
2283
|
+
const matchingLockfiles = LOCKFILES.filter(({ file }) => existsSync3(join5(cwd, file)));
|
|
2284
|
+
return matchingLockfiles.length === 1 ? matchingLockfiles[0].packageManager : void 0;
|
|
2285
|
+
}
|
|
2286
|
+
function packageManagerFromSpecifier(specifier) {
|
|
2287
|
+
const name = specifier.split("@")[0];
|
|
2288
|
+
if (name === "pnpm" || name === "npm" || name === "yarn" || name === "bun") {
|
|
2289
|
+
return name;
|
|
2290
|
+
}
|
|
2291
|
+
return void 0;
|
|
2292
|
+
}
|
|
2293
|
+
function isRecord(value) {
|
|
2294
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
// src/commands/run.ts
|
|
2212
2298
|
var RUNTIME_ARTIFACTS_DIR = DEFAULT_AGENT_QA_ARTIFACTS_DIR || ".agent-qa/artifacts";
|
|
2213
2299
|
var RUNTIME_CACHE_DIR = DEFAULT_AGENT_QA_CACHE_DIR3 || ".agent-qa/cache";
|
|
2214
2300
|
var RUNTIME_VIDEOS_DIR = DEFAULT_AGENT_QA_VIDEOS_DIR || ".agent-qa/artifacts/videos";
|
|
@@ -2440,6 +2526,13 @@ async function flushAnalyticsRunReporter(reporter) {
|
|
|
2440
2526
|
} catch {
|
|
2441
2527
|
}
|
|
2442
2528
|
}
|
|
2529
|
+
async function maybePrintPostRunUpdateNotice(context) {
|
|
2530
|
+
try {
|
|
2531
|
+
if (!shouldPrintAgentQaUpdateNotice(context)) return;
|
|
2532
|
+
await printAgentQaUpdateNoticeIfNeeded(context);
|
|
2533
|
+
} catch {
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2443
2536
|
async function captureSingleAnalyticsResult(reporter, result) {
|
|
2444
2537
|
if (!reporter?.onRunEnd) {
|
|
2445
2538
|
await flushAnalyticsRunReporter(reporter);
|
|
@@ -2460,14 +2553,14 @@ async function captureSingleAnalyticsResult(reporter, result) {
|
|
|
2460
2553
|
function formatParseErrors(errors) {
|
|
2461
2554
|
for (const err of errors) {
|
|
2462
2555
|
const location = `${err.file}:${err.line}:${err.column}`;
|
|
2463
|
-
const severity = err.severity === "error" ?
|
|
2556
|
+
const severity = err.severity === "error" ? pc8.red("error") : pc8.yellow("warning");
|
|
2464
2557
|
console.error(`${severity}: ${err.message}`);
|
|
2465
|
-
console.error(` ${
|
|
2558
|
+
console.error(` ${pc8.blue("-->")} ${location}`);
|
|
2466
2559
|
if (err.source) {
|
|
2467
|
-
console.error(` ${
|
|
2560
|
+
console.error(` ${pc8.blue("|")} ${err.source}`);
|
|
2468
2561
|
}
|
|
2469
2562
|
if (err.suggestion) {
|
|
2470
|
-
console.error(` ${
|
|
2563
|
+
console.error(` ${pc8.blue("=")} ${pc8.bold("help")}: ${err.suggestion}`);
|
|
2471
2564
|
}
|
|
2472
2565
|
console.error();
|
|
2473
2566
|
}
|
|
@@ -2483,14 +2576,14 @@ function printRunId(runId) {
|
|
|
2483
2576
|
}
|
|
2484
2577
|
function printMemoryStatus(log) {
|
|
2485
2578
|
const parts = [];
|
|
2486
|
-
if (log.added > 0) parts.push(
|
|
2579
|
+
if (log.added > 0) parts.push(pc8.green(`${log.added} added`));
|
|
2487
2580
|
if (log.confirmed > 0) parts.push(`${log.confirmed} confirmed`);
|
|
2488
|
-
if (log.deprecated > 0) parts.push(
|
|
2581
|
+
if (log.deprecated > 0) parts.push(pc8.yellow(`${log.deprecated} deprecated`));
|
|
2489
2582
|
if (parts.length === 0) parts.push("no changes");
|
|
2490
2583
|
const secs = log.curatorDuration >= 1e3 ? `${Math.round(log.curatorDuration / 1e3)}s` : `${log.curatorDuration}ms`;
|
|
2491
|
-
console.log(` ${
|
|
2584
|
+
console.log(` ${pc8.dim("Memory:")} ${parts.join(", ")} ${pc8.dim(`(${secs})`)}`);
|
|
2492
2585
|
for (const err of log.errors) {
|
|
2493
|
-
console.log(` ${
|
|
2586
|
+
console.log(` ${pc8.dim("Memory:")} ${pc8.yellow(`warning -- ${err}`)}`);
|
|
2494
2587
|
}
|
|
2495
2588
|
}
|
|
2496
2589
|
function isGlobLikePattern(value) {
|
|
@@ -2562,21 +2655,21 @@ function buildHooksArtifact(hooks) {
|
|
|
2562
2655
|
async function loadRuntimeSecrets(input3) {
|
|
2563
2656
|
const configuredSecretsFile = input3.config.workspace?.secretsFile;
|
|
2564
2657
|
if (typeof configuredSecretsFile !== "string" || configuredSecretsFile.trim().length === 0) {
|
|
2565
|
-
console.error(
|
|
2658
|
+
console.error(pc8.red("Error: workspace.secretsFile is required. Run `agent-qa init` or set it in agent-qa.config.yaml."));
|
|
2566
2659
|
process.exit(1);
|
|
2567
2660
|
}
|
|
2568
2661
|
const secretsPath = input3.secretsFilePath ?? resolvePath3(input3.configDir, configuredSecretsFile);
|
|
2569
2662
|
if (!input3.existsSync(secretsPath)) {
|
|
2570
|
-
console.error(
|
|
2571
|
-
console.error(
|
|
2663
|
+
console.error(pc8.red(`Error: Secrets file not found: ${secretsPath}`));
|
|
2664
|
+
console.error(pc8.dim("Create the file before running tests. It may be empty, but it must exist."));
|
|
2572
2665
|
process.exit(1);
|
|
2573
2666
|
}
|
|
2574
2667
|
let secretsContent;
|
|
2575
2668
|
try {
|
|
2576
2669
|
secretsContent = input3.readFileSync(secretsPath, "utf-8");
|
|
2577
2670
|
} catch {
|
|
2578
|
-
console.error(
|
|
2579
|
-
console.error(
|
|
2671
|
+
console.error(pc8.red(`Error: Secrets file could not be read: ${secretsPath}`));
|
|
2672
|
+
console.error(pc8.dim("Check the path and file permissions before running tests."));
|
|
2580
2673
|
process.exit(1);
|
|
2581
2674
|
}
|
|
2582
2675
|
try {
|
|
@@ -2593,7 +2686,7 @@ async function loadRuntimeSecrets(input3) {
|
|
|
2593
2686
|
};
|
|
2594
2687
|
} catch (err) {
|
|
2595
2688
|
const message = err instanceof Error ? err.message : String(err);
|
|
2596
|
-
console.error(
|
|
2689
|
+
console.error(pc8.red(`Error: Secrets file is invalid: ${message}`));
|
|
2597
2690
|
process.exit(1);
|
|
2598
2691
|
}
|
|
2599
2692
|
}
|
|
@@ -2724,7 +2817,7 @@ async function resolveSelectedAuthStateForRun(input3) {
|
|
|
2724
2817
|
target: { platform: input3.resolvedTarget.platform }
|
|
2725
2818
|
});
|
|
2726
2819
|
} catch (err) {
|
|
2727
|
-
console.error(
|
|
2820
|
+
console.error(pc8.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2728
2821
|
process.exit(2);
|
|
2729
2822
|
}
|
|
2730
2823
|
}
|
|
@@ -2739,7 +2832,7 @@ function resolveAuthStateCapturePathsForRun(input3) {
|
|
|
2739
2832
|
target: { platform: input3.resolvedTarget.platform }
|
|
2740
2833
|
});
|
|
2741
2834
|
} catch (err) {
|
|
2742
|
-
console.error(
|
|
2835
|
+
console.error(pc8.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2743
2836
|
process.exit(2);
|
|
2744
2837
|
}
|
|
2745
2838
|
}
|
|
@@ -2832,7 +2925,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
2832
2925
|
const dashboardCfg = config.services?.dashboard;
|
|
2833
2926
|
const reporterSelectionResult = resolveReporterSelection(opts, Boolean(dashboardCfg));
|
|
2834
2927
|
if (reporterSelectionResult.error) {
|
|
2835
|
-
console.error(
|
|
2928
|
+
console.error(pc8.red(`Error: ${reporterSelectionResult.error}`));
|
|
2836
2929
|
process.exit(2);
|
|
2837
2930
|
}
|
|
2838
2931
|
const reporterSelection = reporterSelectionResult.selection;
|
|
@@ -2848,7 +2941,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
2848
2941
|
activeSuiteDbs.add(dashboardDb);
|
|
2849
2942
|
return { dashboardDb, logStorage: dashboardDb };
|
|
2850
2943
|
} catch (err) {
|
|
2851
|
-
console.warn(
|
|
2944
|
+
console.warn(pc8.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
|
|
2852
2945
|
return {};
|
|
2853
2946
|
}
|
|
2854
2947
|
};
|
|
@@ -2897,7 +2990,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
2897
2990
|
const suiteMembers = [];
|
|
2898
2991
|
for (const { test: relativePath, id: suiteEntryId } of suite.tests) {
|
|
2899
2992
|
if (!isPathInsideDir(relativePath, configDir)) {
|
|
2900
|
-
console.error(
|
|
2993
|
+
console.error(pc8.red(`Path traversal rejected: ${relativePath}`));
|
|
2901
2994
|
const runId = createSuiteRunId();
|
|
2902
2995
|
if (dashboardDb) {
|
|
2903
2996
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2935,7 +3028,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
2935
3028
|
const fileContent = await readFile5(absolutePath, "utf-8");
|
|
2936
3029
|
const parseResult = parseTestFile2(fileContent, absolutePath);
|
|
2937
3030
|
if (parseResult.errors.length > 0) {
|
|
2938
|
-
console.error(
|
|
3031
|
+
console.error(pc8.red(`Parse errors in ${relativePath}:`));
|
|
2939
3032
|
formatParseErrors(parseResult.errors);
|
|
2940
3033
|
const runId = createSuiteRunId();
|
|
2941
3034
|
if (dashboardDb) {
|
|
@@ -2977,7 +3070,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
2977
3070
|
process.exit(2);
|
|
2978
3071
|
}
|
|
2979
3072
|
if (parseResult.tests.length === 0) {
|
|
2980
|
-
console.error(
|
|
3073
|
+
console.error(pc8.red(`No test definition found in ${relativePath}`));
|
|
2981
3074
|
const runId = createSuiteRunId();
|
|
2982
3075
|
if (dashboardDb) {
|
|
2983
3076
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3014,12 +3107,12 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
3014
3107
|
const testDef = parseResult.tests[0];
|
|
3015
3108
|
const childAuthStateUse = getSelectedAuthStateUse(testDef.use);
|
|
3016
3109
|
if (suiteAuthStateUse && childAuthStateUse && childAuthStateUse.name !== suiteAuthStateUse.name) {
|
|
3017
|
-
console.error(
|
|
3110
|
+
console.error(pc8.red(`Suite auth state "${suiteAuthStateUse.name}" conflicts with child test auth state "${childAuthStateUse.name}". Use one primary auth state per suite.`));
|
|
3018
3111
|
process.exit(2);
|
|
3019
3112
|
}
|
|
3020
3113
|
if (testDef["test-id"] && testDef["test-id"] !== suiteEntryId) {
|
|
3021
3114
|
const mismatchMsg = `ID mismatch in suite "${suite.name}": test file ${relativePath} has test-id "${testDef["test-id"]}" but suite entry specifies id "${suiteEntryId}"`;
|
|
3022
|
-
console.error(
|
|
3115
|
+
console.error(pc8.red(mismatchMsg));
|
|
3023
3116
|
const runId = createSuiteRunId();
|
|
3024
3117
|
if (dashboardDb) {
|
|
3025
3118
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3270,7 +3363,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
3270
3363
|
curatorLockTimeout: suiteMemoryConfig?.curatorLockTimeout
|
|
3271
3364
|
});
|
|
3272
3365
|
} catch (err) {
|
|
3273
|
-
console.warn(` ${
|
|
3366
|
+
console.warn(` ${pc8.dim("Memory:")} init error -- ${err.message}`);
|
|
3274
3367
|
}
|
|
3275
3368
|
if (suiteMemoryConfig?.circuitBreakerEnabled !== false) {
|
|
3276
3369
|
const { CircuitBreaker } = await import("@vostride/agent-qa-core");
|
|
@@ -3415,19 +3508,19 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
3415
3508
|
const batchOutcome = await flushSuiteParents();
|
|
3416
3509
|
if (batchOutcome.cancelled || shutdownRequested) break;
|
|
3417
3510
|
if (opts.bail && batchOutcome.failed) {
|
|
3418
|
-
console.log(
|
|
3511
|
+
console.log(pc8.yellow("\nBailing out \u2014 stopping after first failed suite"));
|
|
3419
3512
|
break;
|
|
3420
3513
|
}
|
|
3421
3514
|
const outcome = await runOneSuiteFile(suiteFile, suite);
|
|
3422
3515
|
if (outcome.cancelled || shutdownRequested) break;
|
|
3423
3516
|
if (opts.bail && outcome.failed) {
|
|
3424
|
-
console.log(
|
|
3517
|
+
console.log(pc8.yellow("\nBailing out \u2014 stopping after first failed suite"));
|
|
3425
3518
|
break;
|
|
3426
3519
|
}
|
|
3427
3520
|
}
|
|
3428
3521
|
const finalBatchOutcome = await flushSuiteParents();
|
|
3429
3522
|
if (opts.bail && finalBatchOutcome.failed) {
|
|
3430
|
-
console.log(
|
|
3523
|
+
console.log(pc8.yellow("\nBailing out \u2014 stopping after first failed suite"));
|
|
3431
3524
|
}
|
|
3432
3525
|
process.removeListener("SIGINT", handleShutdown);
|
|
3433
3526
|
process.removeListener("SIGTERM", handleShutdown);
|
|
@@ -3440,7 +3533,7 @@ function createRunCommand() {
|
|
|
3440
3533
|
try {
|
|
3441
3534
|
const modeFlags = [opts.test, opts.suite, opts.all].filter(Boolean).length;
|
|
3442
3535
|
if (modeFlags > 1) {
|
|
3443
|
-
console.error(
|
|
3536
|
+
console.error(pc8.red("Error: --test, --suite, and --all are mutually exclusive."));
|
|
3444
3537
|
process.exit(1);
|
|
3445
3538
|
}
|
|
3446
3539
|
const program2 = command.parent;
|
|
@@ -3468,7 +3561,7 @@ function createRunCommand() {
|
|
|
3468
3561
|
} catch {
|
|
3469
3562
|
}
|
|
3470
3563
|
const { parseEnvFile } = await import("@vostride/agent-qa-core");
|
|
3471
|
-
const { readFileSync:
|
|
3564
|
+
const { readFileSync: readFileSync6, existsSync: existsSync9 } = await import("fs");
|
|
3472
3565
|
const configDir = workspacePaths.configDir;
|
|
3473
3566
|
const cliVars = parseVarFlags(opts.var);
|
|
3474
3567
|
const inheritedContextRunId = process.env.AGENT_QA_RUN_ID ?? process.env.AGENT_QA_SUITE_QUEUE_ID ?? process.env.AGENT_QA_PARENT_RUN_ID;
|
|
@@ -3481,7 +3574,7 @@ function createRunCommand() {
|
|
|
3481
3574
|
console.warn(`Warning: duplicate --run-attr key "${duplicateKey}"; using last value`);
|
|
3482
3575
|
}
|
|
3483
3576
|
} catch (err) {
|
|
3484
|
-
console.error(
|
|
3577
|
+
console.error(pc8.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3485
3578
|
process.exit(2);
|
|
3486
3579
|
}
|
|
3487
3580
|
const inlineVars = {};
|
|
@@ -3489,37 +3582,37 @@ function createRunCommand() {
|
|
|
3489
3582
|
config,
|
|
3490
3583
|
configDir,
|
|
3491
3584
|
secretsFilePath: workspacePaths.secretsFile.absolutePath,
|
|
3492
|
-
readFileSync:
|
|
3493
|
-
existsSync:
|
|
3585
|
+
readFileSync: readFileSync6,
|
|
3586
|
+
existsSync: existsSync9
|
|
3494
3587
|
});
|
|
3495
3588
|
let envFileVars = {};
|
|
3496
3589
|
let rawEnvFileContent = null;
|
|
3497
3590
|
let resolvedEnvFilePath = null;
|
|
3498
3591
|
resolvedEnvFilePath = workspacePaths.envFile.absolutePath;
|
|
3499
|
-
if (!
|
|
3500
|
-
console.error(
|
|
3592
|
+
if (!existsSync9(workspacePaths.envFile.absolutePath)) {
|
|
3593
|
+
console.error(pc8.red(`Error: Env file not found: ${workspacePaths.envFile.absolutePath}`));
|
|
3501
3594
|
process.exit(1);
|
|
3502
3595
|
}
|
|
3503
3596
|
try {
|
|
3504
|
-
rawEnvFileContent =
|
|
3597
|
+
rawEnvFileContent = readFileSync6(workspacePaths.envFile.absolutePath, "utf-8");
|
|
3505
3598
|
envFileVars = parseEnvFile(rawEnvFileContent);
|
|
3506
3599
|
} catch {
|
|
3507
|
-
console.error(
|
|
3600
|
+
console.error(pc8.red(`Error: Env file could not be read: ${workspacePaths.envFile.absolutePath}`));
|
|
3508
3601
|
process.exit(1);
|
|
3509
3602
|
}
|
|
3510
3603
|
let resolvedHooks;
|
|
3511
3604
|
let sandboxOptions;
|
|
3512
3605
|
const hooksFilePath = workspacePaths.hooksFile.absolutePath;
|
|
3513
|
-
if (!
|
|
3514
|
-
console.error(
|
|
3606
|
+
if (!existsSync9(hooksFilePath)) {
|
|
3607
|
+
console.error(pc8.red(`Error: Hooks file not found: ${hooksFilePath}`));
|
|
3515
3608
|
process.exit(1);
|
|
3516
3609
|
}
|
|
3517
3610
|
try {
|
|
3518
3611
|
const { parseHooksFile } = await import("@vostride/agent-qa-core");
|
|
3519
3612
|
const { hooks, errors } = await parseHooksFile(hooksFilePath);
|
|
3520
3613
|
if (errors.length > 0) {
|
|
3521
|
-
console.error(
|
|
3522
|
-
for (const err of errors) console.error(
|
|
3614
|
+
console.error(pc8.red("Hooks configuration errors:"));
|
|
3615
|
+
for (const err of errors) console.error(pc8.red(` - ${err}`));
|
|
3523
3616
|
process.exit(1);
|
|
3524
3617
|
}
|
|
3525
3618
|
resolvedHooks = new Map(hooks.map((h) => [h.id, h]));
|
|
@@ -3528,8 +3621,8 @@ function createRunCommand() {
|
|
|
3528
3621
|
secretRedactor: runtimeSecrets.secretRedactor
|
|
3529
3622
|
};
|
|
3530
3623
|
} catch (err) {
|
|
3531
|
-
console.error(
|
|
3532
|
-
if (err instanceof Error && err.message) console.error(
|
|
3624
|
+
console.error(pc8.red(`Error: Hooks file could not be read: ${hooksFilePath}`));
|
|
3625
|
+
if (err instanceof Error && err.message) console.error(pc8.dim(err.message));
|
|
3533
3626
|
process.exit(1);
|
|
3534
3627
|
}
|
|
3535
3628
|
let agentRulesContent;
|
|
@@ -3537,7 +3630,7 @@ function createRunCommand() {
|
|
|
3537
3630
|
try {
|
|
3538
3631
|
agentRulesContent = await readFileAsync(workspacePaths.agentRules.absolutePath, "utf-8");
|
|
3539
3632
|
} catch {
|
|
3540
|
-
console.error(
|
|
3633
|
+
console.error(pc8.red(`Error: Agent rules file not found: ${workspacePaths.agentRules.absolutePath}`));
|
|
3541
3634
|
process.exit(1);
|
|
3542
3635
|
}
|
|
3543
3636
|
const effectiveLogLevel = globalOpts.logLevel ?? (globalOpts.verbose ? "debug" : void 0) ?? (globalOpts.quiet ? "silent" : void 0) ?? config.services?.logging?.level ?? "warn";
|
|
@@ -3549,21 +3642,21 @@ function createRunCommand() {
|
|
|
3549
3642
|
discoveredTestFiles = selected.tests;
|
|
3550
3643
|
discoveredSuiteFiles = selected.suites;
|
|
3551
3644
|
} catch (err) {
|
|
3552
|
-
console.error(
|
|
3645
|
+
console.error(pc8.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3553
3646
|
process.exit(1);
|
|
3554
3647
|
}
|
|
3555
3648
|
if (discoveredTestFiles.length === 0 && discoveredSuiteFiles.length === 0) {
|
|
3556
3649
|
const patternKind = patterns.every((pattern) => classifyRunPattern(pattern) === "suite") ? "suite" : "test";
|
|
3557
|
-
console.error(
|
|
3558
|
-
for (const p of patterns) console.error(
|
|
3650
|
+
console.error(pc8.red(`Error: No ${patternKind} files found matching patterns:`));
|
|
3651
|
+
for (const p of patterns) console.error(pc8.red(` ${p}`));
|
|
3559
3652
|
process.exit(2);
|
|
3560
3653
|
}
|
|
3561
3654
|
} else if (opts.suite) {
|
|
3562
3655
|
discoveredSuiteFiles = (await discoverWorkspaceFiles2({ workspace: workspacePaths, kind: "suite" })).map((record) => record.absolutePath);
|
|
3563
3656
|
if (discoveredSuiteFiles.length === 0) {
|
|
3564
|
-
console.error(
|
|
3657
|
+
console.error(pc8.red("Error: No suite files found matching workspace.suiteMatch"));
|
|
3565
3658
|
for (const p of workspacePaths.suiteMatch) {
|
|
3566
|
-
console.error(
|
|
3659
|
+
console.error(pc8.red(` ${p}`));
|
|
3567
3660
|
}
|
|
3568
3661
|
process.exit(2);
|
|
3569
3662
|
}
|
|
@@ -3571,26 +3664,36 @@ function createRunCommand() {
|
|
|
3571
3664
|
discoveredTestFiles = (await discoverWorkspaceFiles2({ workspace: workspacePaths, kind: "test" })).map((record) => record.absolutePath);
|
|
3572
3665
|
discoveredSuiteFiles = (await discoverWorkspaceFiles2({ workspace: workspacePaths, kind: "suite" })).map((record) => record.absolutePath);
|
|
3573
3666
|
if (discoveredTestFiles.length === 0 && discoveredSuiteFiles.length === 0) {
|
|
3574
|
-
console.error(
|
|
3667
|
+
console.error(pc8.red("Error: No test or suite files found. Check workspace.testMatch and workspace.suiteMatch in agent-qa.config.yaml"));
|
|
3575
3668
|
process.exit(2);
|
|
3576
3669
|
}
|
|
3577
3670
|
} else {
|
|
3578
3671
|
if (!workspacePaths.testMatch || workspacePaths.testMatch.length === 0) {
|
|
3579
|
-
console.error(
|
|
3672
|
+
console.error(pc8.red("Error: No testMatch patterns in config. Add workspace.testMatch to agent-qa.config.yaml."));
|
|
3580
3673
|
process.exit(2);
|
|
3581
3674
|
}
|
|
3582
3675
|
discoveredTestFiles = (await discoverWorkspaceFiles2({ workspace: workspacePaths, kind: "test" })).map((record) => record.absolutePath);
|
|
3583
3676
|
if (discoveredTestFiles.length === 0) {
|
|
3584
|
-
console.error(
|
|
3677
|
+
console.error(pc8.red("Error: No test files found matching workspace.testMatch"));
|
|
3585
3678
|
for (const p of workspacePaths.testMatch) {
|
|
3586
|
-
console.error(
|
|
3679
|
+
console.error(pc8.red(` ${p}`));
|
|
3587
3680
|
}
|
|
3588
3681
|
process.exit(2);
|
|
3589
3682
|
}
|
|
3590
3683
|
}
|
|
3591
3684
|
if (discoveredSuiteFiles.length > 0 && discoveredTestFiles.length === 0) {
|
|
3592
3685
|
const hasFailed2 = await executeSuites(discoveredSuiteFiles, opts, config, globalOpts, configContent, effectiveLogLevel, { envFileVars, inlineVars, cliVars, rawEnvFileContent, resolvedEnvFilePath, userRunAttributes, inheritedRunAttributes, ...runtimeSecrets }, resolvedHooks && sandboxOptions ? { resolvedHooks, sandboxOptions } : void 0);
|
|
3593
|
-
|
|
3686
|
+
const exitCode2 = hasFailed2 ? 1 : 0;
|
|
3687
|
+
const suiteReporterSelection = resolveReporterSelection(opts, Boolean(config.services?.dashboard)).selection;
|
|
3688
|
+
if (suiteReporterSelection) {
|
|
3689
|
+
await maybePrintPostRunUpdateNotice({
|
|
3690
|
+
reporterSelection: suiteReporterSelection,
|
|
3691
|
+
effectiveLogLevel,
|
|
3692
|
+
liveEvents: process.env.AGENT_QA_LIVE_EVENTS,
|
|
3693
|
+
cwd: process.cwd()
|
|
3694
|
+
});
|
|
3695
|
+
}
|
|
3696
|
+
process.exit(exitCode2);
|
|
3594
3697
|
}
|
|
3595
3698
|
let suitesFailed = false;
|
|
3596
3699
|
if (discoveredSuiteFiles.length > 0 && discoveredTestFiles.length > 0) {
|
|
@@ -3598,7 +3701,7 @@ function createRunCommand() {
|
|
|
3598
3701
|
}
|
|
3599
3702
|
const files = discoveredTestFiles;
|
|
3600
3703
|
if (opts.listTests) {
|
|
3601
|
-
console.log(
|
|
3704
|
+
console.log(pc8.bold(`
|
|
3602
3705
|
Discovered ${files.length} test file(s):
|
|
3603
3706
|
`));
|
|
3604
3707
|
for (const file of files) {
|
|
@@ -3610,7 +3713,7 @@ Discovered ${files.length} test file(s):
|
|
|
3610
3713
|
const dashboardCfg2 = config.services?.dashboard;
|
|
3611
3714
|
const reporterSelectionResult = resolveReporterSelection(opts, Boolean(dashboardCfg2));
|
|
3612
3715
|
if (reporterSelectionResult.error) {
|
|
3613
|
-
console.error(
|
|
3716
|
+
console.error(pc8.red(`Error: ${reporterSelectionResult.error}`));
|
|
3614
3717
|
process.exit(2);
|
|
3615
3718
|
}
|
|
3616
3719
|
const reporterSelection = reporterSelectionResult.selection;
|
|
@@ -3627,13 +3730,13 @@ Discovered ${files.length} test file(s):
|
|
|
3627
3730
|
logStorage = dashboardDb;
|
|
3628
3731
|
_crashDb = dashboardDb;
|
|
3629
3732
|
} catch (err) {
|
|
3630
|
-
console.warn(
|
|
3733
|
+
console.warn(pc8.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
|
|
3631
3734
|
}
|
|
3632
3735
|
}
|
|
3633
3736
|
const { parseAllTests, formatParseError } = await import("@vostride/agent-qa-core");
|
|
3634
3737
|
const parseResult = await parseAllTests(files);
|
|
3635
3738
|
if (parseResult.errors.length > 0) {
|
|
3636
|
-
console.error(
|
|
3739
|
+
console.error(pc8.red(`Found ${parseResult.errors.length} parse error(s):
|
|
3637
3740
|
`));
|
|
3638
3741
|
for (const err of parseResult.errors) {
|
|
3639
3742
|
console.error(formatParseError(err));
|
|
@@ -3644,8 +3747,8 @@ Discovered ${files.length} test file(s):
|
|
|
3644
3747
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3645
3748
|
const { basename: basename2 } = await import("path");
|
|
3646
3749
|
const errorText = parseResult.errors.map((e) => formatParseError(e)).join("\n").replace(/\x1b\[[0-9;]*m/g, "");
|
|
3647
|
-
const { readFileSync:
|
|
3648
|
-
const rawContent =
|
|
3750
|
+
const { readFileSync: readFileSync7 } = await import("fs");
|
|
3751
|
+
const rawContent = readFileSync7(files[0], "utf-8");
|
|
3649
3752
|
const nameMatch = rawContent.match(/^\s*name:\s*(.+)$/m);
|
|
3650
3753
|
const targetMatch = rawContent.match(/^\s*target:\s*(.+)$/m);
|
|
3651
3754
|
let detectedPlatform = "web";
|
|
@@ -3729,17 +3832,17 @@ Discovered ${files.length} test file(s):
|
|
|
3729
3832
|
}
|
|
3730
3833
|
const filtered = parseResult.tests;
|
|
3731
3834
|
if (filtered.length === 0) {
|
|
3732
|
-
console.log(
|
|
3835
|
+
console.log(pc8.yellow("No tests match filters"));
|
|
3733
3836
|
process.exit(0);
|
|
3734
3837
|
}
|
|
3735
3838
|
if (opts.dryRun) {
|
|
3736
|
-
console.log(
|
|
3839
|
+
console.log(pc8.bold(`
|
|
3737
3840
|
Found ${filtered.length} test(s):
|
|
3738
3841
|
`));
|
|
3739
3842
|
for (const test of filtered) {
|
|
3740
|
-
console.log(` ${
|
|
3843
|
+
console.log(` ${pc8.green("\u25CF")} ${test.name}`);
|
|
3741
3844
|
if (opts.configDebug) {
|
|
3742
|
-
console.log(
|
|
3845
|
+
console.log(pc8.bold(`
|
|
3743
3846
|
Config for: ${test.name}`));
|
|
3744
3847
|
console.log(formatConfigDebug(config, test.use, flagOverrides));
|
|
3745
3848
|
console.log();
|
|
@@ -3789,7 +3892,7 @@ Found ${filtered.length} test(s):
|
|
|
3789
3892
|
}
|
|
3790
3893
|
}));
|
|
3791
3894
|
} catch (err) {
|
|
3792
|
-
console.warn(
|
|
3895
|
+
console.warn(pc8.yellow(`Warning: Could not load dashboard reporter: ${err instanceof Error ? err.message : String(err)}`));
|
|
3793
3896
|
}
|
|
3794
3897
|
}
|
|
3795
3898
|
const analyticsReporter = createAnalyticsRunReporter({ config, surface: "cli" });
|
|
@@ -3883,7 +3986,7 @@ Found ${filtered.length} test(s):
|
|
|
3883
3986
|
const testMergedConfig = { ...config, use: mergedUse };
|
|
3884
3987
|
const effectiveCacheEnabled = opts.cache !== false && mergedUse.cache !== false;
|
|
3885
3988
|
if (opts.configDebug) {
|
|
3886
|
-
console.log(
|
|
3989
|
+
console.log(pc8.bold(`
|
|
3887
3990
|
Config for: ${test.name}`));
|
|
3888
3991
|
console.log(formatConfigDebug(config, test.use, testFlagOverrides));
|
|
3889
3992
|
console.log();
|
|
@@ -4008,7 +4111,7 @@ Config for: ${test.name}`));
|
|
|
4008
4111
|
});
|
|
4009
4112
|
}
|
|
4010
4113
|
if (effectiveLogLevel === "debug") {
|
|
4011
|
-
console.log(
|
|
4114
|
+
console.log(pc8.dim(` Platform: ${testPlatform}${useFarm ? ` (farm: ${testResolvedDevice?.transport})` : ""}${testPlatform !== "web" ? " (adapter re-created)" : ""}`));
|
|
4012
4115
|
}
|
|
4013
4116
|
}
|
|
4014
4117
|
const mergedForLLM = { registry: config.registry, use: testMergedConfig.use ?? config.use };
|
|
@@ -4019,7 +4122,7 @@ Config for: ${test.name}`));
|
|
|
4019
4122
|
process.env.AGENT_QA_LLM_MODEL = plannerCfg.model;
|
|
4020
4123
|
process.env.AGENT_QA_LLM_PROVIDER = plannerCfg.provider;
|
|
4021
4124
|
if (effectiveLogLevel === "debug") {
|
|
4022
|
-
console.log(
|
|
4125
|
+
console.log(pc8.dim(` Model: ${plannerCfg.model} (${plannerCfg.provider})`));
|
|
4023
4126
|
}
|
|
4024
4127
|
const plannerModel = await createModel(plannerModelConfig);
|
|
4025
4128
|
const verifierModel = await createModel(verifierModelConfig);
|
|
@@ -4186,7 +4289,7 @@ Config for: ${test.name}`));
|
|
|
4186
4289
|
const testId = testDef["test-id"] ?? testDef.name;
|
|
4187
4290
|
memoryInitParams = { product, testId, memoryRoot };
|
|
4188
4291
|
} catch (err) {
|
|
4189
|
-
console.warn(` ${
|
|
4292
|
+
console.warn(` ${pc8.dim("Memory:")} init error -- ${err.message}`);
|
|
4190
4293
|
}
|
|
4191
4294
|
}
|
|
4192
4295
|
const result = await runTestWithRetry(testDef, {
|
|
@@ -4300,7 +4403,7 @@ Config for: ${test.name}`));
|
|
|
4300
4403
|
try {
|
|
4301
4404
|
const { shouldAblate, collectAllInjectedIds, deprecateOnFailure } = await import("@vostride/agent-qa-core");
|
|
4302
4405
|
if (shouldAblate(result, memoryProvider)) {
|
|
4303
|
-
console.log(` ${
|
|
4406
|
+
console.log(` ${pc8.dim("Memory:")} ablation retry -- re-running without memory...`);
|
|
4304
4407
|
const ablationAdapter = await createPlatformAdapter(testPlatform);
|
|
4305
4408
|
const mergedBrowser = mergedUse.browser;
|
|
4306
4409
|
const ablationPlatformConfig = buildPlatformConfig(testPlatform, testResolvedDevice, testTimeoutsForSetup, mergedBrowser ?? config.use?.browser, testMergedConfig.use?.logCapture ?? config.use?.logCapture, testFarmSession, testMobileResolved);
|
|
@@ -4348,7 +4451,7 @@ Config for: ${test.name}`));
|
|
|
4348
4451
|
circuitBreaker.record({ withMemory: false, passed: ablationResult.status === "passed" });
|
|
4349
4452
|
}
|
|
4350
4453
|
if (ablationResult.status === "passed") {
|
|
4351
|
-
console.log(` ${
|
|
4454
|
+
console.log(` ${pc8.dim("Memory:")} ablation confirmed memory caused failure -- penalizing observations`);
|
|
4352
4455
|
const injectedMap = collectAllInjectedIds(result, memoryProvider);
|
|
4353
4456
|
await deprecateOnFailure({
|
|
4354
4457
|
testResult: result,
|
|
@@ -4359,14 +4462,14 @@ Config for: ${test.name}`));
|
|
|
4359
4462
|
});
|
|
4360
4463
|
ablationHandledDeprecation = true;
|
|
4361
4464
|
} else {
|
|
4362
|
-
console.log(` ${
|
|
4465
|
+
console.log(` ${pc8.dim("Memory:")} ablation retry also failed -- memory not the cause`);
|
|
4363
4466
|
}
|
|
4364
4467
|
} finally {
|
|
4365
4468
|
await ablationAdapter.cleanup();
|
|
4366
4469
|
}
|
|
4367
4470
|
}
|
|
4368
4471
|
} catch (err) {
|
|
4369
|
-
console.warn(` ${
|
|
4472
|
+
console.warn(` ${pc8.dim("Memory:")} ablation error -- ${err.message}`);
|
|
4370
4473
|
}
|
|
4371
4474
|
}
|
|
4372
4475
|
if (memoryProvider && memoryConfig?.curatorEnabled !== false && !ablationHandledDeprecation) {
|
|
@@ -4400,7 +4503,7 @@ Config for: ${test.name}`));
|
|
|
4400
4503
|
}
|
|
4401
4504
|
}
|
|
4402
4505
|
} catch (err) {
|
|
4403
|
-
console.warn(` ${
|
|
4506
|
+
console.warn(` ${pc8.dim("Memory:")} curator error -- ${err.message}`);
|
|
4404
4507
|
}
|
|
4405
4508
|
}
|
|
4406
4509
|
if (testAuthStateUse?.capture === true) {
|
|
@@ -4414,12 +4517,12 @@ Config for: ${test.name}`));
|
|
|
4414
4517
|
}
|
|
4415
4518
|
if (circuitBreaker?.isTripped() && !circuitBreakerTripped) {
|
|
4416
4519
|
circuitBreakerTripped = true;
|
|
4417
|
-
console.log(` ${
|
|
4520
|
+
console.log(` ${pc8.yellow("Memory: circuit breaker tripped -- disabling memory injection for remaining tests")}`);
|
|
4418
4521
|
}
|
|
4419
4522
|
if (result.status === "failed" && !authStateCaptureFailed && actionCache && effectiveCacheEnabled && result.steps.length > 0) {
|
|
4420
4523
|
const { hashStepInstruction: hashStepInstruction2 } = await import("@vostride/agent-qa-core");
|
|
4421
4524
|
const { rm: rm3 } = await import("fs/promises");
|
|
4422
|
-
const { resolve: resolve9, join:
|
|
4525
|
+
const { resolve: resolve9, join: join9 } = await import("path");
|
|
4423
4526
|
const cacheDir = resolve9(configDir, config.services?.cache?.dir ?? RUNTIME_CACHE_DIR);
|
|
4424
4527
|
const cacheLog = logger.createScopedLogger("cache");
|
|
4425
4528
|
let purged = 0;
|
|
@@ -4427,7 +4530,7 @@ Config for: ${test.name}`));
|
|
|
4427
4530
|
const step = result.steps[i];
|
|
4428
4531
|
const stepHash = hashStepInstruction2(step.name, testPlatform, configContent, testFileContent, i);
|
|
4429
4532
|
try {
|
|
4430
|
-
await rm3(
|
|
4533
|
+
await rm3(join9(cacheDir, stepHash), { recursive: true, force: true });
|
|
4431
4534
|
purged++;
|
|
4432
4535
|
} catch {
|
|
4433
4536
|
}
|
|
@@ -4499,13 +4602,13 @@ Config for: ${test.name}`));
|
|
|
4499
4602
|
}
|
|
4500
4603
|
const parallelBatchFailed = await flushParallelRuns();
|
|
4501
4604
|
if (opts.bail && parallelBatchFailed) {
|
|
4502
|
-
console.log(
|
|
4605
|
+
console.log(pc8.yellow("\nBailing out \u2014 stopping after first failure"));
|
|
4503
4606
|
bailTriggered = true;
|
|
4504
4607
|
break;
|
|
4505
4608
|
}
|
|
4506
4609
|
const outcome = await runOneDirectTest(test);
|
|
4507
4610
|
if (opts.bail && outcome.failed) {
|
|
4508
|
-
console.log(
|
|
4611
|
+
console.log(pc8.yellow("\nBailing out \u2014 stopping after first failure"));
|
|
4509
4612
|
bailTriggered = true;
|
|
4510
4613
|
break;
|
|
4511
4614
|
}
|
|
@@ -4513,7 +4616,7 @@ Config for: ${test.name}`));
|
|
|
4513
4616
|
if (!bailTriggered) {
|
|
4514
4617
|
const parallelBatchFailed = await flushParallelRuns();
|
|
4515
4618
|
if (opts.bail && parallelBatchFailed) {
|
|
4516
|
-
console.log(
|
|
4619
|
+
console.log(pc8.yellow("\nBailing out \u2014 stopping after first failure"));
|
|
4517
4620
|
}
|
|
4518
4621
|
}
|
|
4519
4622
|
releaseAppium();
|
|
@@ -4527,12 +4630,19 @@ Config for: ${test.name}`));
|
|
|
4527
4630
|
dashboardDb.close();
|
|
4528
4631
|
}
|
|
4529
4632
|
const hasFailed = results.some((r) => r.status === "failed") || suitesFailed;
|
|
4530
|
-
|
|
4633
|
+
const exitCode = hasFailed ? 1 : 0;
|
|
4634
|
+
await maybePrintPostRunUpdateNotice({
|
|
4635
|
+
reporterSelection,
|
|
4636
|
+
effectiveLogLevel,
|
|
4637
|
+
liveEvents: process.env.AGENT_QA_LIVE_EVENTS,
|
|
4638
|
+
cwd: process.cwd()
|
|
4639
|
+
});
|
|
4640
|
+
process.exit(exitCode);
|
|
4531
4641
|
} catch (err) {
|
|
4532
4642
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4533
4643
|
const errStack = err instanceof Error ? err.stack ?? "" : "";
|
|
4534
4644
|
const formattedError = formatFrameworkError(errMsg);
|
|
4535
|
-
console.error(
|
|
4645
|
+
console.error(pc8.red(formattedError.displayMessage));
|
|
4536
4646
|
if (_crashDb) {
|
|
4537
4647
|
const fullError = `${errMsg}
|
|
4538
4648
|
${errStack}`;
|
|
@@ -4589,7 +4699,7 @@ init_model_defaults();
|
|
|
4589
4699
|
import { Command as Command7 } from "commander";
|
|
4590
4700
|
import { resolve as resolve5, dirname as dirname6 } from "path";
|
|
4591
4701
|
import { createRequire as createRequire2 } from "module";
|
|
4592
|
-
import
|
|
4702
|
+
import pc9 from "picocolors";
|
|
4593
4703
|
import { flushAnalytics, resolveWorkspacePaths as resolveWorkspacePaths3 } from "@vostride/agent-qa-core";
|
|
4594
4704
|
async function startDashboardBackedServices(opts, command, labels) {
|
|
4595
4705
|
try {
|
|
@@ -4642,7 +4752,7 @@ async function startDashboardBackedServices(opts, command, labels) {
|
|
|
4642
4752
|
uiDir = resolve5(dirname6(pkgPath), "dist");
|
|
4643
4753
|
} catch {
|
|
4644
4754
|
if (globalOpts.verbose) {
|
|
4645
|
-
console.log(
|
|
4755
|
+
console.log(pc9.yellow("Warning: @vostride/agent-qa-dashboard-ui not found \u2014 API-only mode"));
|
|
4646
4756
|
}
|
|
4647
4757
|
}
|
|
4648
4758
|
const { close } = await startServer({
|
|
@@ -4665,7 +4775,7 @@ async function startDashboardBackedServices(opts, command, labels) {
|
|
|
4665
4775
|
const shutdown = async () => {
|
|
4666
4776
|
if (shuttingDown) return;
|
|
4667
4777
|
shuttingDown = true;
|
|
4668
|
-
console.log(
|
|
4778
|
+
console.log(pc9.dim(`
|
|
4669
4779
|
Shutting down ${labels.shutdown}...`));
|
|
4670
4780
|
close();
|
|
4671
4781
|
await flushAnalytics({ config }).catch(() => {
|
|
@@ -4675,7 +4785,7 @@ Shutting down ${labels.shutdown}...`));
|
|
|
4675
4785
|
process.on("SIGINT", shutdown);
|
|
4676
4786
|
process.on("SIGTERM", shutdown);
|
|
4677
4787
|
} catch (err) {
|
|
4678
|
-
console.error(
|
|
4788
|
+
console.error(pc9.red(`${labels.error} error: ${err instanceof Error ? err.message : String(err)}`));
|
|
4679
4789
|
process.exit(2);
|
|
4680
4790
|
}
|
|
4681
4791
|
}
|
|
@@ -4701,7 +4811,7 @@ function createServeCommand() {
|
|
|
4701
4811
|
// src/commands/mcp.ts
|
|
4702
4812
|
init_config();
|
|
4703
4813
|
import { Command as Command8 } from "commander";
|
|
4704
|
-
import
|
|
4814
|
+
import pc10 from "picocolors";
|
|
4705
4815
|
import { flushAnalytics as flushAnalytics2 } from "@vostride/agent-qa-core";
|
|
4706
4816
|
function createMcpCommand() {
|
|
4707
4817
|
const cmd = new Command8("mcp").description("Start MCP (Model Context Protocol) server over stdio").action(async (command) => {
|
|
@@ -4723,7 +4833,7 @@ function createMcpCommand() {
|
|
|
4723
4833
|
});
|
|
4724
4834
|
}
|
|
4725
4835
|
} catch (err) {
|
|
4726
|
-
console.error(
|
|
4836
|
+
console.error(pc10.red("Failed to start MCP server:"), err instanceof Error ? err.message : String(err));
|
|
4727
4837
|
process.exitCode = 1;
|
|
4728
4838
|
}
|
|
4729
4839
|
});
|
|
@@ -4733,7 +4843,7 @@ function createMcpCommand() {
|
|
|
4733
4843
|
// src/commands/config.ts
|
|
4734
4844
|
init_config();
|
|
4735
4845
|
import { Command as Command9 } from "commander";
|
|
4736
|
-
import
|
|
4846
|
+
import pc11 from "picocolors";
|
|
4737
4847
|
import { parseDocument, Document } from "yaml";
|
|
4738
4848
|
import { readFile as readFile3, writeFile } from "fs/promises";
|
|
4739
4849
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -4778,14 +4888,14 @@ async function getValue(key, providedValue) {
|
|
|
4778
4888
|
}
|
|
4779
4889
|
async function setAction(key, value) {
|
|
4780
4890
|
if (isSensitiveKey(key)) {
|
|
4781
|
-
console.error(
|
|
4891
|
+
console.error(pc11.red("Credential values are not written by config set."));
|
|
4782
4892
|
console.error("Use agent-qa auth set --config <name> --type api-key|bearer-token for compatible or Gemini configs.");
|
|
4783
4893
|
console.error("Use plugins.auth plus the dashboard auth flow for subscription configs.");
|
|
4784
4894
|
return;
|
|
4785
4895
|
}
|
|
4786
4896
|
const resolved = await getValue(key, value);
|
|
4787
4897
|
if (!resolved) {
|
|
4788
|
-
console.error(
|
|
4898
|
+
console.error(pc11.red("No value provided."));
|
|
4789
4899
|
return;
|
|
4790
4900
|
}
|
|
4791
4901
|
const configPath = "agent-qa.config.yaml";
|
|
@@ -4802,7 +4912,7 @@ async function setAction(key, value) {
|
|
|
4802
4912
|
}
|
|
4803
4913
|
doc.setIn(key.split("."), resolved);
|
|
4804
4914
|
await writeFile(configPath, doc.toString());
|
|
4805
|
-
console.log(
|
|
4915
|
+
console.log(pc11.green(`\u2713 ${key} = ${resolved} saved to agent-qa.config.yaml`));
|
|
4806
4916
|
}
|
|
4807
4917
|
async function showAction() {
|
|
4808
4918
|
const fileConfig = await loadConfigFile("agent-qa.config.yaml");
|
|
@@ -4810,7 +4920,7 @@ async function showAction() {
|
|
|
4810
4920
|
const { mergeConfigs: mergeConfigs2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4811
4921
|
const globalConfig = mergeConfigs2(fileConfig, envOverrides, {});
|
|
4812
4922
|
const table = formatConfigDebug(globalConfig, {}, {});
|
|
4813
|
-
console.log(
|
|
4923
|
+
console.log(pc11.bold("\nResolved Configuration:\n"));
|
|
4814
4924
|
console.log(table);
|
|
4815
4925
|
console.log();
|
|
4816
4926
|
}
|
|
@@ -4824,7 +4934,7 @@ function createConfigCommand() {
|
|
|
4824
4934
|
// src/commands/queue.ts
|
|
4825
4935
|
init_config();
|
|
4826
4936
|
import { Command as Command10 } from "commander";
|
|
4827
|
-
import
|
|
4937
|
+
import pc12 from "picocolors";
|
|
4828
4938
|
function timeAgo(dateStr) {
|
|
4829
4939
|
const diff = Date.now() - new Date(dateStr).getTime();
|
|
4830
4940
|
const seconds = Math.floor(diff / 1e3);
|
|
@@ -4858,13 +4968,13 @@ function createQueueCommand() {
|
|
|
4858
4968
|
try {
|
|
4859
4969
|
const response = await fetch(endpoint);
|
|
4860
4970
|
if (!response.ok) {
|
|
4861
|
-
console.error(
|
|
4971
|
+
console.error(pc12.red(`Server returned ${response.status}: ${response.statusText}`));
|
|
4862
4972
|
process.exit(1);
|
|
4863
4973
|
}
|
|
4864
4974
|
data = await response.json();
|
|
4865
4975
|
} catch {
|
|
4866
|
-
console.error(
|
|
4867
|
-
console.error(
|
|
4976
|
+
console.error(pc12.red(`Could not connect to dashboard server at ${serverUrl}`));
|
|
4977
|
+
console.error(pc12.dim("Make sure the dashboard is running: agent-qa dashboard"));
|
|
4868
4978
|
process.exit(1);
|
|
4869
4979
|
}
|
|
4870
4980
|
if (opts.json) {
|
|
@@ -4872,19 +4982,19 @@ function createQueueCommand() {
|
|
|
4872
4982
|
return;
|
|
4873
4983
|
}
|
|
4874
4984
|
console.log();
|
|
4875
|
-
console.log(
|
|
4876
|
-
console.log(` Concurrency: ${
|
|
4985
|
+
console.log(pc12.bold("Queue Status"));
|
|
4986
|
+
console.log(` Concurrency: ${pc12.blue(String(data.concurrency))} slots (${pc12.yellow(String(data.activeSlots))} active)`);
|
|
4877
4987
|
console.log();
|
|
4878
4988
|
const pending = data.pending?.jobs ?? [];
|
|
4879
4989
|
const running = data.running?.jobs ?? [];
|
|
4880
4990
|
if (pending.length === 0 && running.length === 0) {
|
|
4881
|
-
console.log(
|
|
4991
|
+
console.log(pc12.dim(" Queue is empty \u2014 no pending or running jobs"));
|
|
4882
4992
|
console.log();
|
|
4883
4993
|
return;
|
|
4884
4994
|
}
|
|
4885
4995
|
if (pending.length > 0) {
|
|
4886
|
-
console.log(
|
|
4887
|
-
console.log(
|
|
4996
|
+
console.log(pc12.bold(`Pending (${pending.length} jobs):`));
|
|
4997
|
+
console.log(pc12.dim(" # ID Name Priority Source Queued"));
|
|
4888
4998
|
for (let i = 0; i < pending.length; i++) {
|
|
4889
4999
|
const job = pending[i];
|
|
4890
5000
|
const num = String(i + 1).padEnd(3);
|
|
@@ -4893,37 +5003,37 @@ function createQueueCommand() {
|
|
|
4893
5003
|
const priority = String(job.priority ?? 0).padEnd(8);
|
|
4894
5004
|
const source = (job.source ?? "").padEnd(10);
|
|
4895
5005
|
const queued = job.createdAt ? timeAgo(job.createdAt) : "";
|
|
4896
|
-
console.log(` ${num} ${
|
|
5006
|
+
console.log(` ${num} ${pc12.dim(id)} ${name} ${priority} ${source} ${pc12.dim(queued)}`);
|
|
4897
5007
|
}
|
|
4898
5008
|
console.log();
|
|
4899
5009
|
}
|
|
4900
5010
|
if (running.length > 0) {
|
|
4901
|
-
console.log(
|
|
4902
|
-
console.log(
|
|
5011
|
+
console.log(pc12.bold(`Running (${running.length} jobs):`));
|
|
5012
|
+
console.log(pc12.dim(" ID Name Source Started"));
|
|
4903
5013
|
for (const job of running) {
|
|
4904
5014
|
const id = (job.id ?? "").slice(0, 12).padEnd(12);
|
|
4905
5015
|
const name = (job.name ?? "").slice(0, 18).padEnd(18);
|
|
4906
5016
|
const source = (job.source ?? "").padEnd(10);
|
|
4907
5017
|
const started = job.startedAt ? timeAgo(job.startedAt) : "";
|
|
4908
|
-
console.log(` ${
|
|
5018
|
+
console.log(` ${pc12.green(id)} ${name} ${source} ${pc12.dim(started)}`);
|
|
4909
5019
|
}
|
|
4910
5020
|
console.log();
|
|
4911
5021
|
}
|
|
4912
5022
|
if (opts.all && data.recent?.length > 0) {
|
|
4913
|
-
console.log(
|
|
4914
|
-
console.log(
|
|
5023
|
+
console.log(pc12.bold(`Recent (${data.recent.length} jobs):`));
|
|
5024
|
+
console.log(pc12.dim(" ID Name Status Duration"));
|
|
4915
5025
|
for (const job of data.recent) {
|
|
4916
5026
|
const id = (job.id ?? "").slice(0, 12).padEnd(12);
|
|
4917
5027
|
const name = (job.name ?? "").slice(0, 18).padEnd(18);
|
|
4918
5028
|
const status = (job.status ?? "").padEnd(8);
|
|
4919
5029
|
const duration = job.duration ? `${Math.round(job.duration / 1e3)}s` : "";
|
|
4920
|
-
const statusColor = job.status === "passed" ?
|
|
4921
|
-
console.log(` ${
|
|
5030
|
+
const statusColor = job.status === "passed" ? pc12.green : job.status === "failed" ? pc12.red : pc12.dim;
|
|
5031
|
+
console.log(` ${pc12.dim(id)} ${name} ${statusColor(status)} ${pc12.dim(duration)}`);
|
|
4922
5032
|
}
|
|
4923
5033
|
console.log();
|
|
4924
5034
|
}
|
|
4925
5035
|
} catch (err) {
|
|
4926
|
-
console.error(
|
|
5036
|
+
console.error(pc12.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
4927
5037
|
process.exit(1);
|
|
4928
5038
|
}
|
|
4929
5039
|
});
|
|
@@ -4936,25 +5046,25 @@ function createQueueCommand() {
|
|
|
4936
5046
|
try {
|
|
4937
5047
|
response = await fetch(`${serverUrl}/api/runs/${runId}/cancel`, { method: "POST" });
|
|
4938
5048
|
} catch {
|
|
4939
|
-
console.error(
|
|
4940
|
-
console.error(
|
|
5049
|
+
console.error(pc12.red(`Could not connect to dashboard server at ${serverUrl}`));
|
|
5050
|
+
console.error(pc12.dim("Make sure the dashboard is running: agent-qa dashboard"));
|
|
4941
5051
|
process.exit(1);
|
|
4942
5052
|
}
|
|
4943
5053
|
if (response.ok) {
|
|
4944
5054
|
const data = await response.json();
|
|
4945
5055
|
if (data.cancelled) {
|
|
4946
|
-
console.log(
|
|
5056
|
+
console.log(pc12.green(`\u2713 Cancelled job ${runId}`));
|
|
4947
5057
|
} else {
|
|
4948
|
-
console.log(
|
|
5058
|
+
console.log(pc12.red(`Failed to cancel: ${data.error ?? "Unknown error"}`));
|
|
4949
5059
|
}
|
|
4950
5060
|
} else if (response.status === 404) {
|
|
4951
|
-
console.log(
|
|
5061
|
+
console.log(pc12.red(`Job not found: ${runId}`));
|
|
4952
5062
|
} else {
|
|
4953
5063
|
const data = await response.json().catch(() => ({}));
|
|
4954
|
-
console.log(
|
|
5064
|
+
console.log(pc12.red(`Failed to cancel: ${data.error ?? response.statusText}`));
|
|
4955
5065
|
}
|
|
4956
5066
|
} catch (err) {
|
|
4957
|
-
console.error(
|
|
5067
|
+
console.error(pc12.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
4958
5068
|
process.exit(1);
|
|
4959
5069
|
}
|
|
4960
5070
|
});
|
|
@@ -4991,7 +5101,7 @@ function createValidateCommand() {
|
|
|
4991
5101
|
// src/commands/auth.ts
|
|
4992
5102
|
import { Command as Command12 } from "commander";
|
|
4993
5103
|
import { exec } from "child_process";
|
|
4994
|
-
import
|
|
5104
|
+
import pc13 from "picocolors";
|
|
4995
5105
|
import input2 from "@inquirer/input";
|
|
4996
5106
|
import password from "@inquirer/password";
|
|
4997
5107
|
var PROVIDER_LABELS = {
|
|
@@ -5026,10 +5136,10 @@ function getGlobalConfigPath2(command) {
|
|
|
5026
5136
|
return command.parent?.parent?.opts().config ?? command.parent?.opts().config;
|
|
5027
5137
|
}
|
|
5028
5138
|
function printCredentialGuidance(configName, provider) {
|
|
5029
|
-
console.log(
|
|
5030
|
-
console.log(
|
|
5139
|
+
console.log(pc13.red(`Config ${configName} uses ${providerLabel(provider)} credentials.`));
|
|
5140
|
+
console.log(pc13.dim("Use agent-qa auth set --config <name> --type api-key"));
|
|
5031
5141
|
if (provider === "anthropic-compatible") {
|
|
5032
|
-
console.log(
|
|
5142
|
+
console.log(pc13.dim("or --type bearer-token"));
|
|
5033
5143
|
}
|
|
5034
5144
|
}
|
|
5035
5145
|
function applyAuthToModelConfig(llmConfig, auth) {
|
|
@@ -5049,15 +5159,15 @@ function applyAuthToModelConfig(llmConfig, auth) {
|
|
|
5049
5159
|
function statusForResolvedAuth(auth) {
|
|
5050
5160
|
switch (auth.kind) {
|
|
5051
5161
|
case "api-key":
|
|
5052
|
-
return
|
|
5162
|
+
return pc13.green("Saved API key");
|
|
5053
5163
|
case "bearer-token":
|
|
5054
|
-
return
|
|
5164
|
+
return pc13.green("Saved bearer token");
|
|
5055
5165
|
case "auth-fetch":
|
|
5056
|
-
return Number(auth.expires ?? Date.now() + 1) <= Date.now() ?
|
|
5166
|
+
return Number(auth.expires ?? Date.now() + 1) <= Date.now() ? pc13.red("Expired") : pc13.green("OAuth connected");
|
|
5057
5167
|
case "unauthenticated":
|
|
5058
|
-
return
|
|
5168
|
+
return pc13.dim("No credential");
|
|
5059
5169
|
case "missing":
|
|
5060
|
-
return
|
|
5170
|
+
return pc13.red("Missing credential");
|
|
5061
5171
|
}
|
|
5062
5172
|
}
|
|
5063
5173
|
function extractProviderError(err) {
|
|
@@ -5088,16 +5198,16 @@ function openAuthorizationUrl(url) {
|
|
|
5088
5198
|
});
|
|
5089
5199
|
}
|
|
5090
5200
|
function printMissingSubscriptionPluginGuidance(configName, provider) {
|
|
5091
|
-
console.log(
|
|
5092
|
-
console.log(
|
|
5093
|
-
console.log(
|
|
5094
|
-
console.log(
|
|
5095
|
-
console.log(
|
|
5096
|
-
console.log(
|
|
5097
|
-
console.log(
|
|
5098
|
-
console.log(
|
|
5099
|
-
console.log(
|
|
5100
|
-
console.log(
|
|
5201
|
+
console.log(pc13.red(`Provider "${provider}" is configured for "${configName}", but no auth plugin is registered.`));
|
|
5202
|
+
console.log(pc13.dim(`Config: ${configName}`));
|
|
5203
|
+
console.log(pc13.dim(`Provider: ${provider}`));
|
|
5204
|
+
console.log(pc13.dim("Add the subscription auth plugin to package.json and install with your package manager:"));
|
|
5205
|
+
console.log(pc13.dim(' "devDependencies": { "@vostride/agent-qa-subscription-auth": "<agent-qa version>" }'));
|
|
5206
|
+
console.log(pc13.dim("Declare it in agent-qa.config.yaml under plugins.auth:"));
|
|
5207
|
+
console.log(pc13.dim(" plugins:"));
|
|
5208
|
+
console.log(pc13.dim(" auth:"));
|
|
5209
|
+
console.log(pc13.dim(" - package: @vostride/agent-qa-subscription-auth"));
|
|
5210
|
+
console.log(pc13.dim("Then authenticate from agent-qa dashboard or rerun this command."));
|
|
5101
5211
|
}
|
|
5102
5212
|
async function readTokensFromPlugin(provider, plugin, started) {
|
|
5103
5213
|
if (started.waitForTokens) {
|
|
@@ -5105,19 +5215,19 @@ async function readTokensFromPlugin(provider, plugin, started) {
|
|
|
5105
5215
|
}
|
|
5106
5216
|
if (plugin.dashboardAuth.mode === "manual-code") {
|
|
5107
5217
|
if (!plugin.exchangeCode) {
|
|
5108
|
-
console.log(
|
|
5218
|
+
console.log(pc13.red(`Provider "${provider}" does not support CLI code exchange. Use agent-qa dashboard.`));
|
|
5109
5219
|
process.exitCode = 1;
|
|
5110
5220
|
return null;
|
|
5111
5221
|
}
|
|
5112
5222
|
const code = (await input2({ message: "Authorization code:" })).trim();
|
|
5113
5223
|
if (!code) {
|
|
5114
|
-
console.log(
|
|
5224
|
+
console.log(pc13.red("Authorization code is required"));
|
|
5115
5225
|
process.exitCode = 1;
|
|
5116
5226
|
return null;
|
|
5117
5227
|
}
|
|
5118
5228
|
return await plugin.exchangeCode({ code, sessionState: started.sessionState });
|
|
5119
5229
|
}
|
|
5120
|
-
console.log(
|
|
5230
|
+
console.log(pc13.red(`Provider "${provider}" does not support CLI auth login. Use agent-qa dashboard.`));
|
|
5121
5231
|
process.exitCode = 1;
|
|
5122
5232
|
return null;
|
|
5123
5233
|
}
|
|
@@ -5130,14 +5240,14 @@ async function runSubscriptionPluginLogin(configName, provider) {
|
|
|
5130
5240
|
return;
|
|
5131
5241
|
}
|
|
5132
5242
|
if (!plugin.startAuth) {
|
|
5133
|
-
console.log(
|
|
5243
|
+
console.log(pc13.red(`Provider "${provider}" does not support CLI auth login. Use agent-qa dashboard.`));
|
|
5134
5244
|
process.exitCode = 1;
|
|
5135
5245
|
return;
|
|
5136
5246
|
}
|
|
5137
5247
|
let started;
|
|
5138
5248
|
try {
|
|
5139
5249
|
started = await plugin.startAuth({ configName });
|
|
5140
|
-
console.log(`Open this URL to authenticate: ${
|
|
5250
|
+
console.log(`Open this URL to authenticate: ${pc13.cyan(started.authorizeUrl)}`);
|
|
5141
5251
|
openAuthorizationUrl(started.authorizeUrl);
|
|
5142
5252
|
const tokens = await readTokensFromPlugin(provider, plugin, started);
|
|
5143
5253
|
if (!tokens) return;
|
|
@@ -5146,9 +5256,9 @@ async function runSubscriptionPluginLogin(configName, provider) {
|
|
|
5146
5256
|
provider: plugin.credentialProviderId,
|
|
5147
5257
|
tokens
|
|
5148
5258
|
});
|
|
5149
|
-
console.log(
|
|
5259
|
+
console.log(pc13.green(`Authenticated ${configName} with ${plugin.label}`));
|
|
5150
5260
|
} catch (err) {
|
|
5151
|
-
console.log(
|
|
5261
|
+
console.log(pc13.red(err instanceof Error ? err.message : String(err)));
|
|
5152
5262
|
process.exitCode = 1;
|
|
5153
5263
|
} finally {
|
|
5154
5264
|
started?.cleanup?.();
|
|
@@ -5165,13 +5275,13 @@ function createAuthCommand() {
|
|
|
5165
5275
|
try {
|
|
5166
5276
|
const requestedName = normalizeConfigName(opts.config);
|
|
5167
5277
|
if (!requestedName) {
|
|
5168
|
-
console.log(
|
|
5278
|
+
console.log(pc13.red("configName is required"));
|
|
5169
5279
|
return;
|
|
5170
5280
|
}
|
|
5171
5281
|
const resolved = await resolveNamedConfig2(requestedName, configPath);
|
|
5172
5282
|
const resolvedName = normalizeConfigName(resolved.config.name);
|
|
5173
5283
|
if (!resolvedName) {
|
|
5174
|
-
console.log(
|
|
5284
|
+
console.log(pc13.red("Config name is invalid"));
|
|
5175
5285
|
return;
|
|
5176
5286
|
}
|
|
5177
5287
|
configName = resolvedName;
|
|
@@ -5182,7 +5292,7 @@ function createAuthCommand() {
|
|
|
5182
5292
|
}
|
|
5183
5293
|
provider = configProvider;
|
|
5184
5294
|
} catch (err) {
|
|
5185
|
-
console.log(
|
|
5295
|
+
console.log(pc13.red(err instanceof Error ? err.message : String(err)));
|
|
5186
5296
|
return;
|
|
5187
5297
|
}
|
|
5188
5298
|
await runSubscriptionPluginLogin(configName, provider);
|
|
@@ -5192,7 +5302,7 @@ function createAuthCommand() {
|
|
|
5192
5302
|
new Command12("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) => {
|
|
5193
5303
|
const credentialType = opts.type.trim();
|
|
5194
5304
|
if (credentialType !== "api-key" && credentialType !== "bearer-token") {
|
|
5195
|
-
console.log(
|
|
5305
|
+
console.log(pc13.red("type must be api-key or bearer-token"));
|
|
5196
5306
|
return;
|
|
5197
5307
|
}
|
|
5198
5308
|
const { resolveNamedConfig: resolveNamedConfig2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
|
|
@@ -5201,35 +5311,35 @@ function createAuthCommand() {
|
|
|
5201
5311
|
try {
|
|
5202
5312
|
resolved = await resolveNamedConfig2(opts.config, configPath);
|
|
5203
5313
|
} catch (err) {
|
|
5204
|
-
console.log(
|
|
5314
|
+
console.log(pc13.red(err instanceof Error ? err.message : String(err)));
|
|
5205
5315
|
return;
|
|
5206
5316
|
}
|
|
5207
5317
|
const configName = resolved.config.name ?? opts.config;
|
|
5208
5318
|
const provider = resolved.config.provider;
|
|
5209
5319
|
if (SUBSCRIPTION_PROVIDERS2.has(provider)) {
|
|
5210
|
-
console.log(
|
|
5320
|
+
console.log(pc13.red("Subscription providers use OAuth login"));
|
|
5211
5321
|
return;
|
|
5212
5322
|
}
|
|
5213
5323
|
if (credentialType === "bearer-token" && provider !== "anthropic-compatible") {
|
|
5214
|
-
console.log(
|
|
5324
|
+
console.log(pc13.red("bearer-token credentials are only supported for anthropic-compatible configs"));
|
|
5215
5325
|
return;
|
|
5216
5326
|
}
|
|
5217
5327
|
if (credentialType === "api-key" && !API_KEY_CREDENTIAL_PROVIDERS.has(provider)) {
|
|
5218
|
-
console.log(
|
|
5328
|
+
console.log(pc13.red("api-key credentials are not supported for this provider"));
|
|
5219
5329
|
return;
|
|
5220
5330
|
}
|
|
5221
5331
|
const secret = (secretArg ?? await password({ message: "Secret:" })).trim();
|
|
5222
5332
|
if (!secret) {
|
|
5223
|
-
console.log(
|
|
5333
|
+
console.log(pc13.red("Secret is required"));
|
|
5224
5334
|
return;
|
|
5225
5335
|
}
|
|
5226
5336
|
const { writeAuth } = await import("@vostride/agent-qa-core");
|
|
5227
5337
|
if (credentialType === "bearer-token") {
|
|
5228
5338
|
await writeAuth(configName, { type: "bearer", provider: "anthropic-compatible", token: secret });
|
|
5229
|
-
console.log(
|
|
5339
|
+
console.log(pc13.green(`Saved bearer token for ${configName}`));
|
|
5230
5340
|
} else {
|
|
5231
5341
|
await writeAuth(configName, { type: "api", provider, key: secret });
|
|
5232
|
-
console.log(
|
|
5342
|
+
console.log(pc13.green(`Saved API key for ${configName}`));
|
|
5233
5343
|
}
|
|
5234
5344
|
})
|
|
5235
5345
|
);
|
|
@@ -5238,7 +5348,7 @@ function createAuthCommand() {
|
|
|
5238
5348
|
const { loadAuthPluginsForRawConfig: loadAuthPluginsForRawConfig2, resolveModelAuth: resolveModelAuth2 } = await Promise.resolve().then(() => (init_llm_utils(), llm_utils_exports));
|
|
5239
5349
|
const { loadConfigFile: loadConfigFile2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
5240
5350
|
const configPath = getGlobalConfigPath2(this) ?? "agent-qa.config.yaml";
|
|
5241
|
-
console.log(
|
|
5351
|
+
console.log(pc13.bold("Auth status:\n"));
|
|
5242
5352
|
let llms;
|
|
5243
5353
|
let defaultLLM;
|
|
5244
5354
|
try {
|
|
@@ -5249,20 +5359,20 @@ function createAuthCommand() {
|
|
|
5249
5359
|
} catch {
|
|
5250
5360
|
}
|
|
5251
5361
|
if (!llms?.length) {
|
|
5252
|
-
console.log(
|
|
5362
|
+
console.log(pc13.dim(" No LLM configs found"));
|
|
5253
5363
|
return;
|
|
5254
5364
|
}
|
|
5255
5365
|
const maxNameLen = Math.max(...llms.map((c) => String(c.name ?? "").length), 4) + 12;
|
|
5256
5366
|
for (const llm of llms) {
|
|
5257
5367
|
const name = String(llm.name ?? "");
|
|
5258
5368
|
const isDefault = name === defaultLLM;
|
|
5259
|
-
const label = isDefault ? `${name} ${
|
|
5369
|
+
const label = isDefault ? `${name} ${pc13.dim("(default)")}` : name;
|
|
5260
5370
|
const rawLabel = isDefault ? `${name} (default)` : name;
|
|
5261
5371
|
const padLen = Math.max(0, maxNameLen - rawLabel.length);
|
|
5262
5372
|
const dots = ".".repeat(padLen + 2);
|
|
5263
5373
|
const provider = llm.provider;
|
|
5264
5374
|
const status = statusForResolvedAuth(await resolveModelAuth2(name, llm));
|
|
5265
|
-
console.log(` ${label} ${
|
|
5375
|
+
console.log(` ${label} ${pc13.dim(`[${providerLabel(provider)}]`)} ${pc13.dim(dots)} ${status}`);
|
|
5266
5376
|
}
|
|
5267
5377
|
})
|
|
5268
5378
|
);
|
|
@@ -5279,12 +5389,12 @@ function createAuthCommand() {
|
|
|
5279
5389
|
const resolved = await resolveNamedConfig2(void 0, configPath);
|
|
5280
5390
|
storeKey = resolved.config.name;
|
|
5281
5391
|
} catch {
|
|
5282
|
-
console.log(
|
|
5392
|
+
console.log(pc13.red("Specify --config <name> or run with a config file"));
|
|
5283
5393
|
return;
|
|
5284
5394
|
}
|
|
5285
5395
|
}
|
|
5286
5396
|
await removeAuth(storeKey);
|
|
5287
|
-
console.log(
|
|
5397
|
+
console.log(pc13.green(`Logged out from ${storeKey}`));
|
|
5288
5398
|
})
|
|
5289
5399
|
);
|
|
5290
5400
|
cmd.addCommand(
|
|
@@ -5299,19 +5409,19 @@ function createAuthCommand() {
|
|
|
5299
5409
|
planner = modelFields;
|
|
5300
5410
|
configName = name;
|
|
5301
5411
|
} catch (err) {
|
|
5302
|
-
console.log(
|
|
5412
|
+
console.log(pc13.red(err instanceof Error ? err.message : String(err)));
|
|
5303
5413
|
return;
|
|
5304
5414
|
}
|
|
5305
5415
|
if (opts.provider) planner = { ...planner, provider: opts.provider };
|
|
5306
5416
|
if (opts.model) planner = { ...planner, model: opts.model };
|
|
5307
|
-
console.log(
|
|
5417
|
+
console.log(pc13.dim(`Testing config: ${configName}...`));
|
|
5308
5418
|
const auth = await resolveModelAuth2(configName, planner);
|
|
5309
5419
|
if (auth.kind === "missing") {
|
|
5310
|
-
console.log(
|
|
5420
|
+
console.log(pc13.red(auth.message));
|
|
5311
5421
|
return;
|
|
5312
5422
|
}
|
|
5313
5423
|
if (auth.kind === "unauthenticated") {
|
|
5314
|
-
console.log(
|
|
5424
|
+
console.log(pc13.dim(auth.message));
|
|
5315
5425
|
}
|
|
5316
5426
|
const { createModel, getProviderOptions } = await import("@vostride/agent-qa-core");
|
|
5317
5427
|
const { generateText } = await import("ai");
|
|
@@ -5329,24 +5439,24 @@ function createAuthCommand() {
|
|
|
5329
5439
|
...providerOpts ? { providerOptions: providerOpts } : {}
|
|
5330
5440
|
});
|
|
5331
5441
|
const elapsed = Date.now() - start;
|
|
5332
|
-
console.log(
|
|
5442
|
+
console.log(pc13.green(`Connected to ${planner.provider}/${planner.model} (${elapsed}ms)`));
|
|
5333
5443
|
} catch (err) {
|
|
5334
5444
|
const { message, statusCode } = extractProviderError(err);
|
|
5335
5445
|
const fullContext = `${message} ${statusCode ?? ""}`;
|
|
5336
5446
|
if (/auth|unauthorized|401|invalid.*key|permission/i.test(fullContext)) {
|
|
5337
|
-
console.log(
|
|
5447
|
+
console.log(pc13.red("Authentication failed. Check the saved credential for this config."));
|
|
5338
5448
|
} else if (/model.*not.*support|not found|404|does not exist/i.test(fullContext)) {
|
|
5339
|
-
console.log(
|
|
5449
|
+
console.log(pc13.red("Model not found. Check the model name."));
|
|
5340
5450
|
} else if (/rate|429|quota|limit/i.test(fullContext)) {
|
|
5341
|
-
console.log(
|
|
5451
|
+
console.log(pc13.red("Rate limited. Try again later."));
|
|
5342
5452
|
} else if (/ECONNREFUSED|ENOTFOUND|timeout|abort|fetch failed|network/i.test(fullContext)) {
|
|
5343
|
-
console.log(
|
|
5453
|
+
console.log(pc13.red("Network error. Check the exact base URL and try again."));
|
|
5344
5454
|
} else if (/invalid_request|400|bad request/i.test(fullContext)) {
|
|
5345
|
-
console.log(
|
|
5455
|
+
console.log(pc13.red("Invalid request. Check model and provider settings."));
|
|
5346
5456
|
} else {
|
|
5347
|
-
console.log(
|
|
5457
|
+
console.log(pc13.red(`Connection failed. ${message}`));
|
|
5348
5458
|
}
|
|
5349
|
-
console.log(
|
|
5459
|
+
console.log(pc13.dim(message));
|
|
5350
5460
|
} finally {
|
|
5351
5461
|
clearTimeout(timeout);
|
|
5352
5462
|
}
|
|
@@ -5360,7 +5470,7 @@ init_config();
|
|
|
5360
5470
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
5361
5471
|
import { dirname as dirname7, resolve as resolvePath4 } from "path";
|
|
5362
5472
|
import { Command as Command13 } from "commander";
|
|
5363
|
-
import
|
|
5473
|
+
import pc14 from "picocolors";
|
|
5364
5474
|
import {
|
|
5365
5475
|
AUTH_STATE_SCHEMA_VERSION as AUTH_STATE_SCHEMA_VERSION2,
|
|
5366
5476
|
listAuthStateMetadata,
|
|
@@ -5427,7 +5537,7 @@ function isSafeAuthStateError(message) {
|
|
|
5427
5537
|
}
|
|
5428
5538
|
function printSafeAuthStateError(error, fallback) {
|
|
5429
5539
|
const message = error instanceof Error ? error.message : String(error);
|
|
5430
|
-
console.error(
|
|
5540
|
+
console.error(pc14.red(isSafeAuthStateError(message) ? message : fallback));
|
|
5431
5541
|
}
|
|
5432
5542
|
function assertWebAuthStateTarget(input3) {
|
|
5433
5543
|
resolveAuthStatePaths2({
|
|
@@ -5527,7 +5637,7 @@ function createAuthStateCommand(deps = {}) {
|
|
|
5527
5637
|
target: { platform: target.platform }
|
|
5528
5638
|
});
|
|
5529
5639
|
if (!target.url) {
|
|
5530
|
-
console.error(
|
|
5640
|
+
console.error(pc14.red(`Target "${target.name}" must define a web URL for auth-state capture.`));
|
|
5531
5641
|
process.exitCode = 1;
|
|
5532
5642
|
return;
|
|
5533
5643
|
}
|
|
@@ -5551,7 +5661,7 @@ function createAuthStateCommand(deps = {}) {
|
|
|
5551
5661
|
}
|
|
5552
5662
|
if (confirmation === "browser-closed") {
|
|
5553
5663
|
await closeBrowser(browser);
|
|
5554
|
-
console.error(
|
|
5664
|
+
console.error(pc14.red("Browser was closed before auth state was saved."));
|
|
5555
5665
|
process.exitCode = 1;
|
|
5556
5666
|
return;
|
|
5557
5667
|
}
|
|
@@ -5569,7 +5679,7 @@ function createAuthStateCommand(deps = {}) {
|
|
|
5569
5679
|
});
|
|
5570
5680
|
} catch {
|
|
5571
5681
|
await closeBrowser(browser);
|
|
5572
|
-
console.error(
|
|
5682
|
+
console.error(pc14.red(`Could not save auth state "${opts.name}" for target "${target.name}".`));
|
|
5573
5683
|
process.exitCode = 1;
|
|
5574
5684
|
return;
|
|
5575
5685
|
}
|
|
@@ -5577,7 +5687,7 @@ function createAuthStateCommand(deps = {}) {
|
|
|
5577
5687
|
console.log(`Saved auth state "${opts.name}" for target "${target.name}".`);
|
|
5578
5688
|
} catch (error) {
|
|
5579
5689
|
await closeBrowser(browser);
|
|
5580
|
-
console.error(
|
|
5690
|
+
console.error(pc14.red(error instanceof Error ? error.message : String(error)));
|
|
5581
5691
|
process.exitCode = 1;
|
|
5582
5692
|
}
|
|
5583
5693
|
})
|
|
@@ -5589,33 +5699,33 @@ function createAuthStateCommand(deps = {}) {
|
|
|
5589
5699
|
init_config();
|
|
5590
5700
|
import { Command as Command14 } from "commander";
|
|
5591
5701
|
import { execSync as execSync2 } from "child_process";
|
|
5592
|
-
import { existsSync as
|
|
5593
|
-
import { join as
|
|
5702
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
5703
|
+
import { join as join6 } from "path";
|
|
5594
5704
|
import { stringify as stringify2 } from "yaml";
|
|
5595
|
-
import
|
|
5705
|
+
import pc15 from "picocolors";
|
|
5596
5706
|
function formatStatus2(transport, localBinding) {
|
|
5597
5707
|
if (transport !== "local") {
|
|
5598
|
-
return
|
|
5708
|
+
return pc15.green("Ready") + pc15.dim(" (no local binding required)");
|
|
5599
5709
|
}
|
|
5600
5710
|
if (!localBinding) {
|
|
5601
|
-
return
|
|
5711
|
+
return pc15.red("UNBOUND") + pc15.yellow(" - run `agent-qa devices init`");
|
|
5602
5712
|
}
|
|
5603
5713
|
const keys = Object.entries(localBinding).slice(0, 2);
|
|
5604
5714
|
const details = keys.map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
5605
|
-
return
|
|
5715
|
+
return pc15.green("Bound") + pc15.dim(` (${details})`);
|
|
5606
5716
|
}
|
|
5607
5717
|
async function listDevices() {
|
|
5608
5718
|
let config;
|
|
5609
5719
|
try {
|
|
5610
5720
|
config = await resolveConfig({});
|
|
5611
5721
|
} catch (err) {
|
|
5612
|
-
console.error(
|
|
5722
|
+
console.error(pc15.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
|
|
5613
5723
|
process.exitCode = 1;
|
|
5614
5724
|
return;
|
|
5615
5725
|
}
|
|
5616
5726
|
const devices = config.registry?.devices;
|
|
5617
5727
|
if (!devices || Object.keys(devices).length === 0) {
|
|
5618
|
-
console.log(
|
|
5728
|
+
console.log(pc15.yellow("No devices configured in registry.devices"));
|
|
5619
5729
|
return;
|
|
5620
5730
|
}
|
|
5621
5731
|
const localBindings = loadLocalBindings();
|
|
@@ -5623,13 +5733,13 @@ async function listDevices() {
|
|
|
5623
5733
|
const platWidth = 10;
|
|
5624
5734
|
const transWidth = 14;
|
|
5625
5735
|
console.log("");
|
|
5626
|
-
console.log(
|
|
5736
|
+
console.log(pc15.bold(" Configured Devices"));
|
|
5627
5737
|
console.log("");
|
|
5628
5738
|
console.log(
|
|
5629
|
-
` ${
|
|
5739
|
+
` ${pc15.dim("Name".padEnd(nameWidth))} ${pc15.dim("Platform".padEnd(platWidth))} ${pc15.dim("Transport".padEnd(transWidth))} ${pc15.dim("Status")}`
|
|
5630
5740
|
);
|
|
5631
5741
|
console.log(
|
|
5632
|
-
` ${
|
|
5742
|
+
` ${pc15.dim("\u2500".repeat(nameWidth))} ${pc15.dim("\u2500".repeat(platWidth))} ${pc15.dim("\u2500".repeat(transWidth))} ${pc15.dim("\u2500".repeat(30))}`
|
|
5633
5743
|
);
|
|
5634
5744
|
for (const [name, profile] of Object.entries(devices)) {
|
|
5635
5745
|
const localBinding = localBindings?.devices?.[name] ?? null;
|
|
@@ -5680,10 +5790,10 @@ function scanSimctlDevices() {
|
|
|
5680
5790
|
}
|
|
5681
5791
|
}
|
|
5682
5792
|
function appendGitignoreEntry(dir, entry) {
|
|
5683
|
-
const gitignorePath =
|
|
5793
|
+
const gitignorePath = join6(dir, ".gitignore");
|
|
5684
5794
|
let existing = "";
|
|
5685
|
-
if (
|
|
5686
|
-
existing =
|
|
5795
|
+
if (existsSync4(gitignorePath)) {
|
|
5796
|
+
existing = readFileSync4(gitignorePath, "utf-8");
|
|
5687
5797
|
}
|
|
5688
5798
|
if (existing.includes(entry)) return;
|
|
5689
5799
|
const append = (existing.length > 0 && !existing.endsWith("\n") ? "\n" : "") + entry + "\n";
|
|
@@ -5691,30 +5801,30 @@ function appendGitignoreEntry(dir, entry) {
|
|
|
5691
5801
|
}
|
|
5692
5802
|
async function initDevices() {
|
|
5693
5803
|
const dir = process.cwd();
|
|
5694
|
-
const localFilePath =
|
|
5695
|
-
if (
|
|
5696
|
-
console.log(
|
|
5804
|
+
const localFilePath = join6(dir, "agent-qa.local.yaml");
|
|
5805
|
+
if (existsSync4(localFilePath)) {
|
|
5806
|
+
console.log(pc15.yellow("agent-qa.local.yaml already exists. Skipping generation."));
|
|
5697
5807
|
return;
|
|
5698
5808
|
}
|
|
5699
|
-
console.log(
|
|
5809
|
+
console.log(pc15.bold("\n Scanning for connected devices...\n"));
|
|
5700
5810
|
const adbDevices = scanAdbDevices();
|
|
5701
5811
|
const simctlDevices = scanSimctlDevices();
|
|
5702
5812
|
if (adbDevices.length > 0) {
|
|
5703
|
-
console.log(` ${
|
|
5813
|
+
console.log(` ${pc15.green("Android:")} found ${adbDevices.length} device(s)`);
|
|
5704
5814
|
for (const d of adbDevices) {
|
|
5705
|
-
console.log(` ${
|
|
5815
|
+
console.log(` ${pc15.dim("\u2022")} ${d.model} (${d.serial})`);
|
|
5706
5816
|
}
|
|
5707
5817
|
} else {
|
|
5708
|
-
console.log(` ${
|
|
5818
|
+
console.log(` ${pc15.dim("Android:")} no devices found (adb not available or no devices connected)`);
|
|
5709
5819
|
}
|
|
5710
5820
|
if (simctlDevices.length > 0) {
|
|
5711
5821
|
const booted = simctlDevices.filter((d) => d.state === "Booted");
|
|
5712
|
-
console.log(` ${
|
|
5822
|
+
console.log(` ${pc15.green("iOS:")} found ${simctlDevices.length} simulator(s) (${booted.length} booted)`);
|
|
5713
5823
|
for (const d of booted) {
|
|
5714
|
-
console.log(` ${
|
|
5824
|
+
console.log(` ${pc15.dim("\u2022")} ${d.name} (${d.udid})`);
|
|
5715
5825
|
}
|
|
5716
5826
|
} else {
|
|
5717
|
-
console.log(` ${
|
|
5827
|
+
console.log(` ${pc15.dim("iOS:")} no simulators found (simctl not available or none installed)`);
|
|
5718
5828
|
}
|
|
5719
5829
|
let config;
|
|
5720
5830
|
try {
|
|
@@ -5748,8 +5858,8 @@ async function initDevices() {
|
|
|
5748
5858
|
writeFileSync2(localFilePath, yamlContent);
|
|
5749
5859
|
appendGitignoreEntry(dir, "agent-qa.local.yaml");
|
|
5750
5860
|
console.log("");
|
|
5751
|
-
console.log(
|
|
5752
|
-
console.log(
|
|
5861
|
+
console.log(pc15.green(` Generated agent-qa.local.yaml with ${bindCount} device binding(s)`));
|
|
5862
|
+
console.log(pc15.dim(" Added agent-qa.local.yaml to .gitignore"));
|
|
5753
5863
|
console.log("");
|
|
5754
5864
|
}
|
|
5755
5865
|
function createDevicesCommand() {
|
|
@@ -5761,7 +5871,7 @@ function createDevicesCommand() {
|
|
|
5761
5871
|
|
|
5762
5872
|
// src/commands/ids.ts
|
|
5763
5873
|
import { Command as Command15 } from "commander";
|
|
5764
|
-
import
|
|
5874
|
+
import pc16 from "picocolors";
|
|
5765
5875
|
import {
|
|
5766
5876
|
ENTITY_ID_TYPES,
|
|
5767
5877
|
generateCanonicalId,
|
|
@@ -5787,7 +5897,7 @@ function createIdsCommand() {
|
|
|
5787
5897
|
console.log(id);
|
|
5788
5898
|
}
|
|
5789
5899
|
} catch (err) {
|
|
5790
|
-
console.error(
|
|
5900
|
+
console.error(pc16.red(err instanceof Error ? err.message : String(err)));
|
|
5791
5901
|
process.exitCode = 1;
|
|
5792
5902
|
}
|
|
5793
5903
|
});
|
|
@@ -5806,15 +5916,15 @@ function createIdsCommand() {
|
|
|
5806
5916
|
if (opts.json) {
|
|
5807
5917
|
console.log(JSON.stringify(result, null, 2));
|
|
5808
5918
|
} else if (valid) {
|
|
5809
|
-
console.log(
|
|
5919
|
+
console.log(pc16.green(result.message));
|
|
5810
5920
|
} else {
|
|
5811
|
-
console.error(
|
|
5921
|
+
console.error(pc16.red(result.message));
|
|
5812
5922
|
}
|
|
5813
5923
|
if (!valid) {
|
|
5814
5924
|
process.exitCode = 1;
|
|
5815
5925
|
}
|
|
5816
5926
|
} catch (err) {
|
|
5817
|
-
console.error(
|
|
5927
|
+
console.error(pc16.red(err instanceof Error ? err.message : String(err)));
|
|
5818
5928
|
process.exitCode = 1;
|
|
5819
5929
|
}
|
|
5820
5930
|
});
|
|
@@ -5826,14 +5936,14 @@ import { Command as Command16 } from "commander";
|
|
|
5826
5936
|
import { generateTestId } from "@vostride/agent-qa-ids";
|
|
5827
5937
|
import { stringify as stringify3 } from "yaml";
|
|
5828
5938
|
import { writeFile as writeFile2, mkdir } from "fs/promises";
|
|
5829
|
-
import { existsSync as
|
|
5939
|
+
import { existsSync as existsSync5 } from "fs";
|
|
5830
5940
|
import { resolve as resolve6, dirname as dirname8 } from "path";
|
|
5831
|
-
import
|
|
5941
|
+
import pc17 from "picocolors";
|
|
5832
5942
|
function createCreateTestCommand() {
|
|
5833
5943
|
const cmd = new Command16("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) => {
|
|
5834
5944
|
const resolved = resolve6(path);
|
|
5835
|
-
if (
|
|
5836
|
-
console.error(
|
|
5945
|
+
if (existsSync5(resolved)) {
|
|
5946
|
+
console.error(pc17.red("Error: File already exists: " + resolved));
|
|
5837
5947
|
process.exitCode = 1;
|
|
5838
5948
|
return;
|
|
5839
5949
|
}
|
|
@@ -5847,8 +5957,8 @@ function createCreateTestCommand() {
|
|
|
5847
5957
|
const yamlContent = stringify3(template);
|
|
5848
5958
|
await mkdir(dirname8(resolved), { recursive: true });
|
|
5849
5959
|
await writeFile2(resolved, yamlContent, "utf-8");
|
|
5850
|
-
console.log(
|
|
5851
|
-
console.log(
|
|
5960
|
+
console.log(pc17.green("Created " + resolved));
|
|
5961
|
+
console.log(pc17.dim(" test-id: " + testId));
|
|
5852
5962
|
});
|
|
5853
5963
|
return cmd;
|
|
5854
5964
|
}
|
|
@@ -5858,14 +5968,14 @@ import { Command as Command17 } from "commander";
|
|
|
5858
5968
|
import { generateSuiteId } from "@vostride/agent-qa-ids";
|
|
5859
5969
|
import { stringify as stringify4 } from "yaml";
|
|
5860
5970
|
import { writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
|
|
5861
|
-
import { existsSync as
|
|
5971
|
+
import { existsSync as existsSync6 } from "fs";
|
|
5862
5972
|
import { resolve as resolve7, dirname as dirname9 } from "path";
|
|
5863
|
-
import
|
|
5973
|
+
import pc18 from "picocolors";
|
|
5864
5974
|
function createCreateSuiteCommand() {
|
|
5865
5975
|
const cmd = new Command17("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) => {
|
|
5866
5976
|
const resolved = resolve7(path);
|
|
5867
|
-
if (
|
|
5868
|
-
console.error(
|
|
5977
|
+
if (existsSync6(resolved)) {
|
|
5978
|
+
console.error(pc18.red("Error: File already exists: " + resolved));
|
|
5869
5979
|
process.exitCode = 1;
|
|
5870
5980
|
return;
|
|
5871
5981
|
}
|
|
@@ -5881,8 +5991,8 @@ function createCreateSuiteCommand() {
|
|
|
5881
5991
|
);
|
|
5882
5992
|
await mkdir2(dirname9(resolved), { recursive: true });
|
|
5883
5993
|
await writeFile3(resolved, yamlContent, "utf-8");
|
|
5884
|
-
console.log(
|
|
5885
|
-
console.log(
|
|
5994
|
+
console.log(pc18.green("Created " + resolved));
|
|
5995
|
+
console.log(pc18.dim(" suite-id: " + suiteId));
|
|
5886
5996
|
});
|
|
5887
5997
|
return cmd;
|
|
5888
5998
|
}
|
|
@@ -5890,11 +6000,11 @@ function createCreateSuiteCommand() {
|
|
|
5890
6000
|
// src/commands/clean-memory.ts
|
|
5891
6001
|
init_config();
|
|
5892
6002
|
import { Command as Command18 } from "commander";
|
|
5893
|
-
import
|
|
6003
|
+
import pc19 from "picocolors";
|
|
5894
6004
|
import { createInterface as createInterface4 } from "readline";
|
|
5895
6005
|
import { readFile as readFile4, readdir as readdir2, rm as rm2, stat as stat2 } from "fs/promises";
|
|
5896
|
-
import { existsSync as
|
|
5897
|
-
import { join as
|
|
6006
|
+
import { existsSync as existsSync7 } from "fs";
|
|
6007
|
+
import { join as join7 } from "path";
|
|
5898
6008
|
import {
|
|
5899
6009
|
discoverWorkspaceFiles as discoverWorkspaceFiles3,
|
|
5900
6010
|
parseObservation,
|
|
@@ -5910,12 +6020,12 @@ function formatSize(bytes) {
|
|
|
5910
6020
|
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
5911
6021
|
}
|
|
5912
6022
|
async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
5913
|
-
const scopePath =
|
|
6023
|
+
const scopePath = join7(memoryRoot, tier, scope);
|
|
5914
6024
|
const obsFiles = await listObservations(scopePath);
|
|
5915
6025
|
let newest = null;
|
|
5916
6026
|
for (const filename of obsFiles) {
|
|
5917
6027
|
try {
|
|
5918
|
-
const content = await readFile4(
|
|
6028
|
+
const content = await readFile4(join7(scopePath, filename), "utf-8");
|
|
5919
6029
|
const { data } = parseObservation(content, filename);
|
|
5920
6030
|
if (data?.last_confirmed) {
|
|
5921
6031
|
if (!newest || data.last_confirmed > newest) {
|
|
@@ -5930,7 +6040,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5930
6040
|
const entries = await readdir2(scopePath);
|
|
5931
6041
|
for (const entry of entries) {
|
|
5932
6042
|
try {
|
|
5933
|
-
const s = await stat2(
|
|
6043
|
+
const s = await stat2(join7(scopePath, entry));
|
|
5934
6044
|
if (s.isFile()) totalSize += s.size;
|
|
5935
6045
|
} catch {
|
|
5936
6046
|
}
|
|
@@ -5938,7 +6048,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5938
6048
|
} catch {
|
|
5939
6049
|
}
|
|
5940
6050
|
return {
|
|
5941
|
-
path:
|
|
6051
|
+
path: join7(tier, scope),
|
|
5942
6052
|
tier,
|
|
5943
6053
|
scope,
|
|
5944
6054
|
fileCount: obsFiles.length,
|
|
@@ -5979,8 +6089,8 @@ function createCleanMemoryCommand() {
|
|
|
5979
6089
|
}
|
|
5980
6090
|
}
|
|
5981
6091
|
const memoryRoot = resolveMemoryRoot2(config, workspace.configDir);
|
|
5982
|
-
if (!
|
|
5983
|
-
console.log(
|
|
6092
|
+
if (!existsSync7(memoryRoot)) {
|
|
6093
|
+
console.log(pc19.dim("No memory directory found."));
|
|
5984
6094
|
return;
|
|
5985
6095
|
}
|
|
5986
6096
|
const TIERS = [
|
|
@@ -5990,7 +6100,7 @@ function createCleanMemoryCommand() {
|
|
|
5990
6100
|
];
|
|
5991
6101
|
const orphans = [];
|
|
5992
6102
|
for (const tier of TIERS) {
|
|
5993
|
-
const tierPath =
|
|
6103
|
+
const tierPath = join7(memoryRoot, tier.dir);
|
|
5994
6104
|
let entries;
|
|
5995
6105
|
try {
|
|
5996
6106
|
entries = await readdir2(tierPath);
|
|
@@ -5999,7 +6109,7 @@ function createCleanMemoryCommand() {
|
|
|
5999
6109
|
}
|
|
6000
6110
|
for (const entry of entries) {
|
|
6001
6111
|
try {
|
|
6002
|
-
const s = await stat2(
|
|
6112
|
+
const s = await stat2(join7(tierPath, entry));
|
|
6003
6113
|
if (!s.isDirectory()) continue;
|
|
6004
6114
|
} catch {
|
|
6005
6115
|
continue;
|
|
@@ -6010,17 +6120,17 @@ function createCleanMemoryCommand() {
|
|
|
6010
6120
|
}
|
|
6011
6121
|
}
|
|
6012
6122
|
if (orphans.length === 0) {
|
|
6013
|
-
console.log(
|
|
6123
|
+
console.log(pc19.dim("No orphaned memory directories found."));
|
|
6014
6124
|
return;
|
|
6015
6125
|
}
|
|
6016
6126
|
const maxPathLen = Math.max("Path".length, ...orphans.map((o) => o.path.length));
|
|
6017
6127
|
console.log("");
|
|
6018
|
-
console.log(
|
|
6128
|
+
console.log(pc19.bold("Orphaned memory directories:"));
|
|
6019
6129
|
console.log("");
|
|
6020
|
-
console.log(" " +
|
|
6021
|
-
console.log(" " +
|
|
6130
|
+
console.log(" " + pc19.dim("Path".padEnd(maxPathLen + 2) + "Files".padStart(7) + " " + "Last Confirmed".padEnd(20) + "Size".padStart(10)));
|
|
6131
|
+
console.log(" " + pc19.dim("-".repeat(maxPathLen + 2 + 7 + 2 + 20 + 10)));
|
|
6022
6132
|
for (const o of orphans) {
|
|
6023
|
-
const confirmed = o.lastConfirmed ??
|
|
6133
|
+
const confirmed = o.lastConfirmed ?? pc19.dim("never");
|
|
6024
6134
|
console.log(" " + o.path.padEnd(maxPathLen + 2) + String(o.fileCount).padStart(7) + " " + String(confirmed).padEnd(20) + formatSize(o.size).padStart(10));
|
|
6025
6135
|
}
|
|
6026
6136
|
console.log("");
|
|
@@ -6036,25 +6146,25 @@ function createCleanMemoryCommand() {
|
|
|
6036
6146
|
}
|
|
6037
6147
|
}
|
|
6038
6148
|
for (const orphan of orphans) {
|
|
6039
|
-
await rm2(
|
|
6149
|
+
await rm2(join7(memoryRoot, orphan.tier, orphan.scope), { recursive: true, force: true });
|
|
6040
6150
|
}
|
|
6041
|
-
console.log(
|
|
6151
|
+
console.log(pc19.green(`Deleted ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}.`));
|
|
6042
6152
|
});
|
|
6043
6153
|
return cmd;
|
|
6044
6154
|
}
|
|
6045
6155
|
|
|
6046
6156
|
// src/commands/skills.ts
|
|
6047
6157
|
import { Command as Command19 } from "commander";
|
|
6048
|
-
import { existsSync as
|
|
6049
|
-
import { dirname as dirname10, join as
|
|
6158
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync5, statSync } from "fs";
|
|
6159
|
+
import { dirname as dirname10, join as join8, resolve as resolve8 } from "path";
|
|
6050
6160
|
import { fileURLToPath } from "url";
|
|
6051
6161
|
function findPackageRoot(startDir) {
|
|
6052
6162
|
let current = resolve8(startDir);
|
|
6053
6163
|
while (true) {
|
|
6054
|
-
const packagePath =
|
|
6055
|
-
if (
|
|
6164
|
+
const packagePath = join8(current, "package.json");
|
|
6165
|
+
if (existsSync8(packagePath)) {
|
|
6056
6166
|
try {
|
|
6057
|
-
const pkg = JSON.parse(
|
|
6167
|
+
const pkg = JSON.parse(readFileSync5(packagePath, "utf-8"));
|
|
6058
6168
|
if (pkg.name === "agent-qa") {
|
|
6059
6169
|
return current;
|
|
6060
6170
|
}
|
|
@@ -6069,11 +6179,11 @@ function findPackageRoot(startDir) {
|
|
|
6069
6179
|
}
|
|
6070
6180
|
}
|
|
6071
6181
|
function hasSkillDirectories(dir) {
|
|
6072
|
-
if (!
|
|
6182
|
+
if (!existsSync8(dir)) return false;
|
|
6073
6183
|
try {
|
|
6074
6184
|
return readdirSync(dir).some((entry) => {
|
|
6075
|
-
const skillDir =
|
|
6076
|
-
return statSync(skillDir).isDirectory() &&
|
|
6185
|
+
const skillDir = join8(dir, entry);
|
|
6186
|
+
return statSync(skillDir).isDirectory() && existsSync8(join8(skillDir, "SKILL.md"));
|
|
6077
6187
|
});
|
|
6078
6188
|
} catch {
|
|
6079
6189
|
return false;
|
|
@@ -6081,7 +6191,7 @@ function hasSkillDirectories(dir) {
|
|
|
6081
6191
|
}
|
|
6082
6192
|
function resolveAgentQaSkillsDirectory(startDir = dirname10(fileURLToPath(import.meta.url))) {
|
|
6083
6193
|
const packageRoot = findPackageRoot(startDir);
|
|
6084
|
-
const packagedSkills =
|
|
6194
|
+
const packagedSkills = join8(packageRoot, "skills");
|
|
6085
6195
|
if (hasSkillDirectories(packagedSkills)) {
|
|
6086
6196
|
return { path: packagedSkills, source: "package" };
|
|
6087
6197
|
}
|
|
@@ -6092,21 +6202,21 @@ function resolveAgentQaSkillsDirectory(startDir = dirname10(fileURLToPath(import
|
|
|
6092
6202
|
return { path: packagedSkills, source: "missing" };
|
|
6093
6203
|
}
|
|
6094
6204
|
function parseSkillDescription(skillPath) {
|
|
6095
|
-
const body =
|
|
6205
|
+
const body = readFileSync5(skillPath, "utf-8");
|
|
6096
6206
|
const description = body.match(/^description:\s*(.+)$/m)?.[1]?.trim();
|
|
6097
6207
|
return description?.replace(/^["']|["']$/g, "");
|
|
6098
6208
|
}
|
|
6099
6209
|
function listAgentQaSkills(skillsDir) {
|
|
6100
6210
|
if (!hasSkillDirectories(skillsDir)) return [];
|
|
6101
6211
|
return readdirSync(skillsDir).filter((entry) => {
|
|
6102
|
-
const skillDir =
|
|
6103
|
-
return statSync(skillDir).isDirectory() &&
|
|
6212
|
+
const skillDir = join8(skillsDir, entry);
|
|
6213
|
+
return statSync(skillDir).isDirectory() && existsSync8(join8(skillDir, "SKILL.md"));
|
|
6104
6214
|
}).sort().map((entry) => {
|
|
6105
|
-
const skillDir =
|
|
6215
|
+
const skillDir = join8(skillsDir, entry);
|
|
6106
6216
|
return {
|
|
6107
6217
|
name: entry,
|
|
6108
6218
|
path: skillDir,
|
|
6109
|
-
description: parseSkillDescription(
|
|
6219
|
+
description: parseSkillDescription(join8(skillDir, "SKILL.md"))
|
|
6110
6220
|
};
|
|
6111
6221
|
});
|
|
6112
6222
|
}
|