agent-qa 0.1.13 → 0.1.15
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/README.md +70 -0
- package/dist/cli.cjs +506 -127
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +477 -89
- package/dist/cli.js.map +1 -1
- package/package.json +32 -10
package/dist/cli.cjs
CHANGED
|
@@ -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_commander20 = require("commander");
|
|
359
359
|
|
|
360
360
|
// src/index.ts
|
|
361
361
|
var index_exports = {};
|
|
@@ -520,6 +520,7 @@ var import_agent_qa_core4 = require("@vostride/agent-qa-core");
|
|
|
520
520
|
init_model_defaults();
|
|
521
521
|
var import_meta = {};
|
|
522
522
|
var INIT_CACHE_DIR = import_agent_qa_core4.DEFAULT_AGENT_QA_CACHE_DIR || ".agent-qa/cache";
|
|
523
|
+
var INIT_AUTH_STATES_DIR = import_agent_qa_core4.DEFAULT_AGENT_QA_AUTH_STATES_DIR || ".agent-qa/auth-states";
|
|
523
524
|
var INIT_RUNTIME_DIR = import_agent_qa_core4.DEFAULT_AGENT_QA_RUNTIME_DIR || ".agent-qa";
|
|
524
525
|
var SUBSCRIPTION_AUTH_PACKAGE = "@vostride/agent-qa-subscription-auth";
|
|
525
526
|
var PACKAGE_JSON_FILE = "package.json";
|
|
@@ -852,6 +853,7 @@ function buildDefaultConfig(platform, providerOrLLMs, model, baseURL) {
|
|
|
852
853
|
dashboard: { port: 3100, artifactsDir: ".agent-qa/artifacts" },
|
|
853
854
|
mcp: { enabled: true, transport: "http", host: "127.0.0.1", port: 3471, path: "/mcp" },
|
|
854
855
|
cache: { dir: INIT_CACHE_DIR, ttl: "7d" },
|
|
856
|
+
authState: { dir: INIT_AUTH_STATES_DIR },
|
|
855
857
|
...hasWeb(platform) ? {
|
|
856
858
|
accessibility: {
|
|
857
859
|
enabled: true,
|
|
@@ -893,7 +895,7 @@ function addYamlComments(yamlStr) {
|
|
|
893
895
|
if (line.startsWith("workspace:")) {
|
|
894
896
|
result += "# File discovery and project settings\n";
|
|
895
897
|
} else if (line.startsWith("services:")) {
|
|
896
|
-
result += "\n# Infrastructure services (dashboard, MCP, cache, logging)\n";
|
|
898
|
+
result += "\n# Infrastructure services (dashboard, MCP, cache, auth state, logging)\n";
|
|
897
899
|
} else if (line.startsWith("registry:")) {
|
|
898
900
|
result += "\n# Named resource definitions (LLM configs, app targets)\n";
|
|
899
901
|
} else if (line.startsWith("plugins:")) {
|
|
@@ -948,7 +950,19 @@ var DEFAULT_AGENT_RULES_FILE = "agent-rules.md";
|
|
|
948
950
|
var DEFAULT_ENV_FILE = ".env";
|
|
949
951
|
var DEFAULT_SECRETS_FILE = ".env.secrets.local";
|
|
950
952
|
var DEFAULT_LOCAL_CONFIG_FILE = "agent-qa.local.yaml";
|
|
951
|
-
|
|
953
|
+
function directoryGitignoreEntry(value) {
|
|
954
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
955
|
+
}
|
|
956
|
+
function buildGitignoreEntries() {
|
|
957
|
+
return [
|
|
958
|
+
directoryGitignoreEntry(INIT_RUNTIME_DIR),
|
|
959
|
+
directoryGitignoreEntry(INIT_AUTH_STATES_DIR),
|
|
960
|
+
"node_modules/",
|
|
961
|
+
DEFAULT_LOCAL_CONFIG_FILE,
|
|
962
|
+
DEFAULT_ENV_FILE,
|
|
963
|
+
DEFAULT_SECRETS_FILE
|
|
964
|
+
];
|
|
965
|
+
}
|
|
952
966
|
function buildLocalConfigTemplate() {
|
|
953
967
|
return `# This file is for machine-specific device, app, and provider bindings.
|
|
954
968
|
# Keep it out of git.
|
|
@@ -977,7 +991,10 @@ function appendGitignore(dir) {
|
|
|
977
991
|
if ((0, import_node_fs.existsSync)(gitignorePath)) {
|
|
978
992
|
existing = (0, import_node_fs.readFileSync)(gitignorePath, "utf-8");
|
|
979
993
|
}
|
|
980
|
-
const
|
|
994
|
+
const existingEntries = new Set(
|
|
995
|
+
existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
996
|
+
);
|
|
997
|
+
const missing = buildGitignoreEntries().filter((entry) => !existingEntries.has(entry));
|
|
981
998
|
if (missing.length === 0) return;
|
|
982
999
|
const append = (existing.length > 0 && !existing.endsWith("\n") ? "\n" : "") + missing.join("\n") + "\n";
|
|
983
1000
|
(0, import_node_fs.writeFileSync)(gitignorePath, existing + append);
|
|
@@ -2588,7 +2605,7 @@ async function loadRuntimeSecrets(input3) {
|
|
|
2588
2605
|
}
|
|
2589
2606
|
}
|
|
2590
2607
|
function buildRunArtifactContext(input3) {
|
|
2591
|
-
return {
|
|
2608
|
+
return (0, import_agent_qa_core7.redactAuthStateValue)({
|
|
2592
2609
|
artifact: {
|
|
2593
2610
|
kind: input3.kind,
|
|
2594
2611
|
config: {
|
|
@@ -2622,7 +2639,7 @@ function buildRunArtifactContext(input3) {
|
|
|
2622
2639
|
attributes: input3.attributes
|
|
2623
2640
|
}
|
|
2624
2641
|
}
|
|
2625
|
-
};
|
|
2642
|
+
});
|
|
2626
2643
|
}
|
|
2627
2644
|
async function resolveDeviceAndFarmSession(config, deviceName, testPlatform, testName, testTimeout, preResolvedDevice, localBindingsOverride, mobileResolved) {
|
|
2628
2645
|
if (!deviceName || testPlatform === "web") return {};
|
|
@@ -2700,6 +2717,82 @@ function resolveMobileRunOrThrow(input3) {
|
|
|
2700
2717
|
wrapMobileSetupError(err);
|
|
2701
2718
|
}
|
|
2702
2719
|
}
|
|
2720
|
+
function getSelectedAuthStateUse(use) {
|
|
2721
|
+
return (0, import_agent_qa_core7.normalizeAuthStateUse)(use);
|
|
2722
|
+
}
|
|
2723
|
+
async function resolveSelectedAuthStateForRun(input3) {
|
|
2724
|
+
if (!input3.authState || input3.authState.load === false) return void 0;
|
|
2725
|
+
try {
|
|
2726
|
+
return await (0, import_agent_qa_core7.resolveAuthStateForRun)({
|
|
2727
|
+
configDir: input3.configDir,
|
|
2728
|
+
authStateDir: input3.config.services?.authState?.dir,
|
|
2729
|
+
targetName: input3.resolvedTarget.name,
|
|
2730
|
+
stateName: input3.authState.name,
|
|
2731
|
+
target: { platform: input3.resolvedTarget.platform }
|
|
2732
|
+
});
|
|
2733
|
+
} catch (err) {
|
|
2734
|
+
console.error(import_picocolors7.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2735
|
+
process.exit(2);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
function resolveAuthStateCapturePathsForRun(input3) {
|
|
2739
|
+
if (input3.authState?.capture !== true) return void 0;
|
|
2740
|
+
try {
|
|
2741
|
+
return (0, import_agent_qa_core7.resolveAuthStatePaths)({
|
|
2742
|
+
configDir: input3.configDir,
|
|
2743
|
+
authStateDir: input3.config.services?.authState?.dir,
|
|
2744
|
+
targetName: input3.resolvedTarget.name,
|
|
2745
|
+
stateName: input3.authState.name,
|
|
2746
|
+
target: { platform: input3.resolvedTarget.platform }
|
|
2747
|
+
});
|
|
2748
|
+
} catch (err) {
|
|
2749
|
+
console.error(import_picocolors7.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2750
|
+
process.exit(2);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
function formatAuthStateCaptureFailure(paths) {
|
|
2754
|
+
return `Could not save auth state "${paths.stateName}" for target "${paths.targetName}".`;
|
|
2755
|
+
}
|
|
2756
|
+
function getStorageStateContext(adapter) {
|
|
2757
|
+
const maybeGetPage = adapter.getPage;
|
|
2758
|
+
if (typeof maybeGetPage !== "function") return null;
|
|
2759
|
+
const page = maybeGetPage.call(adapter);
|
|
2760
|
+
if (!page || typeof page !== "object") return null;
|
|
2761
|
+
const maybeContext = page.context;
|
|
2762
|
+
if (typeof maybeContext !== "function") return null;
|
|
2763
|
+
const context = maybeContext.call(page);
|
|
2764
|
+
if (!context || typeof context !== "object") return null;
|
|
2765
|
+
const storageState = context.storageState;
|
|
2766
|
+
if (typeof storageState !== "function") return null;
|
|
2767
|
+
return {
|
|
2768
|
+
storageState: storageState.bind(context)
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
async function captureAuthStateForRun(input3) {
|
|
2772
|
+
const context = getStorageStateContext(input3.adapter);
|
|
2773
|
+
if (!context) {
|
|
2774
|
+
throw new Error("Web auth-state capture requires a Playwright browser context.");
|
|
2775
|
+
}
|
|
2776
|
+
const payload = await context.storageState({ indexedDB: true });
|
|
2777
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2778
|
+
const metadata = {
|
|
2779
|
+
version: import_agent_qa_core7.AUTH_STATE_SCHEMA_VERSION,
|
|
2780
|
+
kind: "web",
|
|
2781
|
+
target: input3.paths.targetName,
|
|
2782
|
+
name: input3.paths.stateName,
|
|
2783
|
+
capturedAt
|
|
2784
|
+
};
|
|
2785
|
+
const { writeAuthStateFiles: writeAuthStateFiles2 } = await import("@vostride/agent-qa-core");
|
|
2786
|
+
await writeAuthStateFiles2(input3.paths, { payload, metadata });
|
|
2787
|
+
return {
|
|
2788
|
+
version: metadata.version,
|
|
2789
|
+
kind: metadata.kind,
|
|
2790
|
+
targetName: metadata.target,
|
|
2791
|
+
stateName: metadata.name,
|
|
2792
|
+
capturedAt: metadata.capturedAt,
|
|
2793
|
+
storageStatePath: input3.paths.payloadPath
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2703
2796
|
async function executeSuites(suiteFiles, opts, config, globalOpts, configContent, effectiveLogLevel, variableContext, hooksContext) {
|
|
2704
2797
|
const {
|
|
2705
2798
|
parseSuiteFile: parseSuiteFile2,
|
|
@@ -2800,6 +2893,13 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
2800
2893
|
suiteFileContent = await readFile5(suiteFile, "utf-8");
|
|
2801
2894
|
} catch {
|
|
2802
2895
|
}
|
|
2896
|
+
const mergedUse = mergeUseBlocks(
|
|
2897
|
+
config.use,
|
|
2898
|
+
suite.use,
|
|
2899
|
+
void 0,
|
|
2900
|
+
{}
|
|
2901
|
+
);
|
|
2902
|
+
const suiteAuthStateUse = getSelectedAuthStateUse(mergedUse);
|
|
2803
2903
|
const testEntries = [];
|
|
2804
2904
|
const suiteMembers = [];
|
|
2805
2905
|
for (const { test: relativePath, id: suiteEntryId } of suite.tests) {
|
|
@@ -2919,6 +3019,11 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
2919
3019
|
process.exit(2);
|
|
2920
3020
|
}
|
|
2921
3021
|
const testDef = parseResult.tests[0];
|
|
3022
|
+
const childAuthStateUse = getSelectedAuthStateUse(testDef.use);
|
|
3023
|
+
if (suiteAuthStateUse && childAuthStateUse && childAuthStateUse.name !== suiteAuthStateUse.name) {
|
|
3024
|
+
console.error(import_picocolors7.default.red(`Suite auth state "${suiteAuthStateUse.name}" conflicts with child test auth state "${childAuthStateUse.name}". Use one primary auth state per suite.`));
|
|
3025
|
+
process.exit(2);
|
|
3026
|
+
}
|
|
2922
3027
|
if (testDef["test-id"] && testDef["test-id"] !== suiteEntryId) {
|
|
2923
3028
|
const mismatchMsg = `ID mismatch in suite "${suite.name}": test file ${relativePath} has test-id "${testDef["test-id"]}" but suite entry specifies id "${suiteEntryId}"`;
|
|
2924
3029
|
console.error(import_picocolors7.default.red(mismatchMsg));
|
|
@@ -2970,12 +3075,18 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
2970
3075
|
}
|
|
2971
3076
|
const suiteResolvedTarget = suite.target ? resolveTarget(config, suite.target) : resolveTarget(config, testEntries[0][0].target);
|
|
2972
3077
|
const suitePlatform = suiteResolvedTarget.platform;
|
|
2973
|
-
const
|
|
2974
|
-
config
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
);
|
|
3078
|
+
const suiteAuthState = await resolveSelectedAuthStateForRun({
|
|
3079
|
+
config,
|
|
3080
|
+
configDir,
|
|
3081
|
+
resolvedTarget: suiteResolvedTarget,
|
|
3082
|
+
authState: suiteAuthStateUse
|
|
3083
|
+
});
|
|
3084
|
+
const suiteAuthStateCapturePaths = resolveAuthStateCapturePathsForRun({
|
|
3085
|
+
config,
|
|
3086
|
+
configDir,
|
|
3087
|
+
resolvedTarget: suiteResolvedTarget,
|
|
3088
|
+
authState: suiteAuthStateUse
|
|
3089
|
+
});
|
|
2979
3090
|
const suiteEffectiveCacheEnabled = opts.cache !== false && mergedUse.cache !== false;
|
|
2980
3091
|
let suiteDeviceName = opts.device ?? mergedUse.device;
|
|
2981
3092
|
if ((suitePlatform === "android" || suitePlatform === "ios") && !suiteDeviceName) {
|
|
@@ -3059,6 +3170,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
3059
3170
|
const suiteBrowser = mergedUse.browser ?? config.use?.browser;
|
|
3060
3171
|
const suiteLogCapture = mergedUse.logCapture ?? config.use?.logCapture;
|
|
3061
3172
|
const platformConfig = buildPlatformConfig(suitePlatform, resolvedDevice, testTimeouts, suiteBrowser, suiteLogCapture, farmSession, mobileResolved);
|
|
3173
|
+
platformConfig.authState = suiteAuthState;
|
|
3062
3174
|
platformConfig.verbose = effectiveLogLevel === "debug";
|
|
3063
3175
|
if (opts.headless !== void 0 && platformConfig.browser) {
|
|
3064
3176
|
platformConfig.browser.headless = opts.headless;
|
|
@@ -3249,6 +3361,10 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
|
|
|
3249
3361
|
memoryRoot: suiteMemoryRoot,
|
|
3250
3362
|
circuitBreaker: suiteCircuitBreaker,
|
|
3251
3363
|
product: resolvedTarget?.product,
|
|
3364
|
+
authStateCapture: suiteAuthStateCapturePaths ? {
|
|
3365
|
+
capture: () => captureAuthStateForRun({ adapter, paths: suiteAuthStateCapturePaths }),
|
|
3366
|
+
failureSummary: formatAuthStateCaptureFailure(suiteAuthStateCapturePaths)
|
|
3367
|
+
} : void 0,
|
|
3252
3368
|
createAdapter: () => createPlatformAdapter(suitePlatform),
|
|
3253
3369
|
resolveUrl: (targetName) => {
|
|
3254
3370
|
try {
|
|
@@ -3578,7 +3694,7 @@ Discovered ${files.length} test file(s):
|
|
|
3578
3694
|
dashboardDb.insertRunArtifact({
|
|
3579
3695
|
runId: artifactRunId,
|
|
3580
3696
|
kind: "test",
|
|
3581
|
-
payload: {
|
|
3697
|
+
payload: (0, import_agent_qa_core7.redactAuthStateValue)({
|
|
3582
3698
|
config: {
|
|
3583
3699
|
rawConfigContent: configContent,
|
|
3584
3700
|
parsedConfig: config,
|
|
@@ -3609,7 +3725,7 @@ Discovered ${files.length} test file(s):
|
|
|
3609
3725
|
message: error.message,
|
|
3610
3726
|
details: error
|
|
3611
3727
|
}))
|
|
3612
|
-
}
|
|
3728
|
+
})
|
|
3613
3729
|
});
|
|
3614
3730
|
dashboardDb.finalizeRunArtifact(artifactRunId);
|
|
3615
3731
|
dashboardDb.close();
|
|
@@ -3781,6 +3897,20 @@ Config for: ${test.name}`));
|
|
|
3781
3897
|
}
|
|
3782
3898
|
const testPlatform = testResolvedTarget.platform;
|
|
3783
3899
|
analyticsPlatform = testPlatform;
|
|
3900
|
+
const testAuthStateUse = getSelectedAuthStateUse(mergedUse);
|
|
3901
|
+
const testAuthState = await resolveSelectedAuthStateForRun({
|
|
3902
|
+
config,
|
|
3903
|
+
configDir,
|
|
3904
|
+
resolvedTarget: testResolvedTarget,
|
|
3905
|
+
authState: testAuthStateUse
|
|
3906
|
+
});
|
|
3907
|
+
const testAuthStateCapturePaths = resolveAuthStateCapturePathsForRun({
|
|
3908
|
+
config,
|
|
3909
|
+
configDir,
|
|
3910
|
+
resolvedTarget: testResolvedTarget,
|
|
3911
|
+
authState: testAuthStateUse
|
|
3912
|
+
});
|
|
3913
|
+
let activeTestAuthState = testAuthState;
|
|
3784
3914
|
const testMergedUse = testMergedConfig.use ?? config.use ?? {};
|
|
3785
3915
|
const testDeviceName = opts.device ?? testMergedUse.device;
|
|
3786
3916
|
const testTimeoutsForSetup = {
|
|
@@ -3860,6 +3990,7 @@ Config for: ${test.name}`));
|
|
|
3860
3990
|
adapter = await createPlatformAdapter(testPlatform);
|
|
3861
3991
|
const mergedBrowser = mergedUse.browser;
|
|
3862
3992
|
const platformConfig = buildPlatformConfig(testPlatform, testResolvedDevice, testTimeoutsForSetup, mergedBrowser ?? config.use?.browser, testMergedConfig.use?.logCapture ?? config.use?.logCapture, testFarmSession, testMobileResolved);
|
|
3993
|
+
platformConfig.authState = activeTestAuthState;
|
|
3863
3994
|
platformConfig.verbose = effectiveLogLevel === "debug";
|
|
3864
3995
|
if (opts.headless !== void 0) {
|
|
3865
3996
|
if (platformConfig.browser) {
|
|
@@ -3978,10 +4109,12 @@ Config for: ${test.name}`));
|
|
|
3978
4109
|
runId,
|
|
3979
4110
|
artifact: artifactContext.artifact
|
|
3980
4111
|
});
|
|
4112
|
+
const getAuthAwareSandboxOptions = () => sandboxOptions && activeTestAuthState ? { ...sandboxOptions, authState: activeTestAuthState } : sandboxOptions;
|
|
4113
|
+
const authAwareSandboxOptions = getAuthAwareSandboxOptions();
|
|
3981
4114
|
let perTestSetupVars = {};
|
|
3982
4115
|
let testStartTime = Date.now();
|
|
3983
4116
|
let setupHookFailed = false;
|
|
3984
|
-
if (resolvedHooks &&
|
|
4117
|
+
if (resolvedHooks && authAwareSandboxOptions && testDef.setup?.length) {
|
|
3985
4118
|
const { runHooks } = await import("@vostride/agent-qa-core");
|
|
3986
4119
|
const hookDefs = [];
|
|
3987
4120
|
let hookSetupError;
|
|
@@ -4001,7 +4134,7 @@ Config for: ${test.name}`));
|
|
|
4001
4134
|
for (const hookDef of hookDefs) {
|
|
4002
4135
|
const hookExecId = (0, import_node_crypto.randomUUID)();
|
|
4003
4136
|
await multiReporter.onHookStart({ hookId: hookDef.id, hookName: hookDef.name, phase: "setup", hookExecutionId: hookExecId, runId });
|
|
4004
|
-
const hookResult = await runHooks([hookDef], { ...
|
|
4137
|
+
const hookResult = await runHooks([hookDef], { ...authAwareSandboxOptions, envVars: { ...authAwareSandboxOptions.envVars, ...allVars, ...perTestSetupVars } });
|
|
4005
4138
|
const hr = hookResult.results.get(hookDef.name);
|
|
4006
4139
|
await multiReporter.onHookEnd({
|
|
4007
4140
|
hookId: hookDef.id,
|
|
@@ -4088,7 +4221,7 @@ Config for: ${test.name}`));
|
|
|
4088
4221
|
cliVars,
|
|
4089
4222
|
hookSetupVars: perTestSetupVars,
|
|
4090
4223
|
inlineHookDefs: resolvedHooks,
|
|
4091
|
-
inlineHookSandboxOptions:
|
|
4224
|
+
inlineHookSandboxOptions: authAwareSandboxOptions,
|
|
4092
4225
|
logCapture: testLogCapture,
|
|
4093
4226
|
accessibility: config.services?.accessibility,
|
|
4094
4227
|
accessibilityCheck: runWebAccessibilityCheck,
|
|
@@ -4099,11 +4232,28 @@ Config for: ${test.name}`));
|
|
|
4099
4232
|
memoryInitParams,
|
|
4100
4233
|
circuitBreaker,
|
|
4101
4234
|
runId,
|
|
4102
|
-
skipReporterOnTestStart: true
|
|
4235
|
+
skipReporterOnTestStart: true,
|
|
4236
|
+
skipReporterOnTestEnd: testAuthStateUse?.capture === true
|
|
4103
4237
|
}, filePath);
|
|
4104
4238
|
if (!result.runId) result.runId = runId;
|
|
4105
4239
|
completedResult = result;
|
|
4106
|
-
if (
|
|
4240
|
+
if (testAuthStateCapturePaths && result.status === "passed") {
|
|
4241
|
+
try {
|
|
4242
|
+
activeTestAuthState = await captureAuthStateForRun({
|
|
4243
|
+
adapter,
|
|
4244
|
+
paths: testAuthStateCapturePaths
|
|
4245
|
+
});
|
|
4246
|
+
} catch {
|
|
4247
|
+
result.status = "failed";
|
|
4248
|
+
result.failureSummary = formatAuthStateCaptureFailure(testAuthStateCapturePaths);
|
|
4249
|
+
result.metadata = {
|
|
4250
|
+
...result.metadata,
|
|
4251
|
+
phase: "auth-state-capture"
|
|
4252
|
+
};
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
const teardownSandboxOptions = getAuthAwareSandboxOptions();
|
|
4256
|
+
if (resolvedHooks && teardownSandboxOptions && testDef.teardown?.length) {
|
|
4107
4257
|
const { runHooks } = await import("@vostride/agent-qa-core");
|
|
4108
4258
|
for (const hookId of testDef.teardown) {
|
|
4109
4259
|
const hook = resolvedHooks.get(hookId);
|
|
@@ -4113,7 +4263,7 @@ Config for: ${test.name}`));
|
|
|
4113
4263
|
await multiReporter.onHookStart({ hookId: hook.id, hookName: hook.name, phase: "teardown", hookExecutionId: hookExecId, runId });
|
|
4114
4264
|
const allVars = { ...envFileVars, ...perTestSetupVars };
|
|
4115
4265
|
if (cliVars) Object.assign(allVars, cliVars);
|
|
4116
|
-
const hookResult = await runHooks([hook], { ...
|
|
4266
|
+
const hookResult = await runHooks([hook], { ...teardownSandboxOptions, envVars: { ...teardownSandboxOptions.envVars, ...allVars } });
|
|
4117
4267
|
const hr = hookResult.results.get(hook.name);
|
|
4118
4268
|
await multiReporter.onHookEnd({
|
|
4119
4269
|
hookId: hook.id,
|
|
@@ -4152,7 +4302,8 @@ Config for: ${test.name}`));
|
|
|
4152
4302
|
};
|
|
4153
4303
|
}
|
|
4154
4304
|
let ablationHandledDeprecation = false;
|
|
4155
|
-
|
|
4305
|
+
const authStateCaptureFailed = result.metadata?.phase === "auth-state-capture";
|
|
4306
|
+
if (memoryProvider && memoryConfig?.ablationEnabled !== false && result.status === "failed" && !authStateCaptureFailed) {
|
|
4156
4307
|
try {
|
|
4157
4308
|
const { shouldAblate, collectAllInjectedIds, deprecateOnFailure } = await import("@vostride/agent-qa-core");
|
|
4158
4309
|
if (shouldAblate(result, memoryProvider)) {
|
|
@@ -4160,6 +4311,7 @@ Config for: ${test.name}`));
|
|
|
4160
4311
|
const ablationAdapter = await createPlatformAdapter(testPlatform);
|
|
4161
4312
|
const mergedBrowser = mergedUse.browser;
|
|
4162
4313
|
const ablationPlatformConfig = buildPlatformConfig(testPlatform, testResolvedDevice, testTimeoutsForSetup, mergedBrowser ?? config.use?.browser, testMergedConfig.use?.logCapture ?? config.use?.logCapture, testFarmSession, testMobileResolved);
|
|
4314
|
+
ablationPlatformConfig.authState = activeTestAuthState;
|
|
4163
4315
|
ablationPlatformConfig.verbose = effectiveLogLevel === "debug";
|
|
4164
4316
|
if (opts.headless !== void 0 && ablationPlatformConfig.browser) {
|
|
4165
4317
|
ablationPlatformConfig.browser.headless = opts.headless;
|
|
@@ -4191,7 +4343,7 @@ Config for: ${test.name}`));
|
|
|
4191
4343
|
cliVars,
|
|
4192
4344
|
hookSetupVars: perTestSetupVars,
|
|
4193
4345
|
inlineHookDefs: resolvedHooks,
|
|
4194
|
-
inlineHookSandboxOptions:
|
|
4346
|
+
inlineHookSandboxOptions: authAwareSandboxOptions,
|
|
4195
4347
|
logCapture: testLogCapture,
|
|
4196
4348
|
accessibility: config.services?.accessibility,
|
|
4197
4349
|
accessibilityCheck: runWebAccessibilityCheck,
|
|
@@ -4258,6 +4410,9 @@ Config for: ${test.name}`));
|
|
|
4258
4410
|
console.warn(` ${import_picocolors7.default.dim("Memory:")} curator error -- ${err.message}`);
|
|
4259
4411
|
}
|
|
4260
4412
|
}
|
|
4413
|
+
if (testAuthStateUse?.capture === true) {
|
|
4414
|
+
await multiReporter.onTestEnd(result);
|
|
4415
|
+
}
|
|
4261
4416
|
results.push(result);
|
|
4262
4417
|
printRunAttributes(testAttributes);
|
|
4263
4418
|
if (circuitBreaker && memoryProvider) {
|
|
@@ -4268,7 +4423,7 @@ Config for: ${test.name}`));
|
|
|
4268
4423
|
circuitBreakerTripped = true;
|
|
4269
4424
|
console.log(` ${import_picocolors7.default.yellow("Memory: circuit breaker tripped -- disabling memory injection for remaining tests")}`);
|
|
4270
4425
|
}
|
|
4271
|
-
if (result.status === "failed" && actionCache && effectiveCacheEnabled && result.steps.length > 0) {
|
|
4426
|
+
if (result.status === "failed" && !authStateCaptureFailed && actionCache && effectiveCacheEnabled && result.steps.length > 0) {
|
|
4272
4427
|
const { hashStepInstruction: hashStepInstruction2 } = await import("@vostride/agent-qa-core");
|
|
4273
4428
|
const { rm: rm3 } = await import("fs/promises");
|
|
4274
4429
|
const { resolve: resolve9, join: join8 } = await import("path");
|
|
@@ -4399,7 +4554,7 @@ ${errStack}`;
|
|
|
4399
4554
|
status: finalStatus,
|
|
4400
4555
|
duration: run.startedAt ? Date.now() - new Date(run.startedAt).getTime() : 0,
|
|
4401
4556
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4402
|
-
failureSummary: isBrowserClose ? "Browser closed by user" : formattedError.failureSummary
|
|
4557
|
+
failureSummary: isBrowserClose ? "Browser closed by user" : (0, import_agent_qa_core7.redactAuthStateValue)(formattedError.failureSummary)
|
|
4403
4558
|
});
|
|
4404
4559
|
try {
|
|
4405
4560
|
const artifact = _crashDb.getRunArtifact(run.id);
|
|
@@ -4412,7 +4567,7 @@ ${errStack}`;
|
|
|
4412
4567
|
errors: [{
|
|
4413
4568
|
code: isBrowserClose ? "browser-disconnect" : "process-close",
|
|
4414
4569
|
phase: "process-close",
|
|
4415
|
-
message: isBrowserClose ? "Browser closed by user" : formattedError.failureSummary
|
|
4570
|
+
message: isBrowserClose ? "Browser closed by user" : (0, import_agent_qa_core7.redactAuthStateValue)(formattedError.failureSummary)
|
|
4416
4571
|
}]
|
|
4417
4572
|
});
|
|
4418
4573
|
}
|
|
@@ -4569,7 +4724,8 @@ function createMcpCommand() {
|
|
|
4569
4724
|
}
|
|
4570
4725
|
const { startMcpServer } = await import("@vostride/agent-qa-mcp");
|
|
4571
4726
|
try {
|
|
4572
|
-
|
|
4727
|
+
const startupOptions = { analyticsConfig: config, startupOutput: process.stderr };
|
|
4728
|
+
await startMcpServer(startupOptions);
|
|
4573
4729
|
} finally {
|
|
4574
4730
|
await (0, import_agent_qa_core11.flushAnalytics)(config ? { config } : void 0).catch(() => {
|
|
4575
4731
|
});
|
|
@@ -5207,37 +5363,259 @@ function createAuthCommand() {
|
|
|
5207
5363
|
return cmd;
|
|
5208
5364
|
}
|
|
5209
5365
|
|
|
5210
|
-
// src/commands/
|
|
5366
|
+
// src/commands/auth-state.ts
|
|
5367
|
+
var import_promises4 = require("readline/promises");
|
|
5368
|
+
var import_node_path11 = require("path");
|
|
5211
5369
|
var import_commander13 = require("commander");
|
|
5370
|
+
var import_picocolors13 = __toESM(require("picocolors"), 1);
|
|
5371
|
+
var import_agent_qa_core13 = require("@vostride/agent-qa-core");
|
|
5372
|
+
init_config();
|
|
5373
|
+
function getGlobalConfigPath3(command) {
|
|
5374
|
+
return command.parent?.parent?.opts().config ?? command.parent?.opts().config;
|
|
5375
|
+
}
|
|
5376
|
+
function isBrowserName(value) {
|
|
5377
|
+
return value === "chromium" || value === "firefox" || value === "webkit";
|
|
5378
|
+
}
|
|
5379
|
+
function resolveBrowserName(config) {
|
|
5380
|
+
const name = config.use?.browser?.name;
|
|
5381
|
+
return isBrowserName(name) ? name : "chromium";
|
|
5382
|
+
}
|
|
5383
|
+
async function loadPlaywrightLaunchers() {
|
|
5384
|
+
const { chromium, firefox, webkit } = await import("playwright-core");
|
|
5385
|
+
return { chromium, firefox, webkit };
|
|
5386
|
+
}
|
|
5387
|
+
async function waitForCaptureConfirmation(input3) {
|
|
5388
|
+
console.log(input3.message);
|
|
5389
|
+
const rl = (0, import_promises4.createInterface)({ input: process.stdin, output: process.stdout });
|
|
5390
|
+
return await new Promise((resolve9) => {
|
|
5391
|
+
let settled = false;
|
|
5392
|
+
const finish = (result) => {
|
|
5393
|
+
if (settled) return;
|
|
5394
|
+
settled = true;
|
|
5395
|
+
process.off("SIGINT", onSigint);
|
|
5396
|
+
input3.browser.off?.("disconnected", onDisconnected);
|
|
5397
|
+
rl.close();
|
|
5398
|
+
resolve9(result);
|
|
5399
|
+
};
|
|
5400
|
+
const onSigint = () => finish("cancelled");
|
|
5401
|
+
const onDisconnected = () => finish("browser-closed");
|
|
5402
|
+
process.once("SIGINT", onSigint);
|
|
5403
|
+
input3.browser.on("disconnected", onDisconnected);
|
|
5404
|
+
rl.question("").then(() => finish("confirmed")).catch(() => finish("cancelled"));
|
|
5405
|
+
});
|
|
5406
|
+
}
|
|
5407
|
+
async function authStateExists(paths) {
|
|
5408
|
+
try {
|
|
5409
|
+
await (0, import_agent_qa_core13.readAuthStateMetadata)(paths);
|
|
5410
|
+
return true;
|
|
5411
|
+
} catch {
|
|
5412
|
+
return false;
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
function buildConfirmationMessage(targetName, stateName, exists) {
|
|
5416
|
+
const replace = exists ? " Existing auth state will be replaced." : "";
|
|
5417
|
+
return `Log in in the opened browser, then press Enter to save auth state "${stateName}" for target "${targetName}".${replace} Press Ctrl+C to cancel.`;
|
|
5418
|
+
}
|
|
5419
|
+
async function loadCommandContext(command) {
|
|
5420
|
+
const configPath = getGlobalConfigPath3(command) ?? "agent-qa.config.yaml";
|
|
5421
|
+
const configDir = (0, import_node_path11.dirname)((0, import_node_path11.resolve)(configPath));
|
|
5422
|
+
const config = await resolveConfig({ configPath, loadAuthPlugins: false });
|
|
5423
|
+
return { configPath, configDir, config };
|
|
5424
|
+
}
|
|
5425
|
+
function isSafeAuthStateError(message) {
|
|
5426
|
+
return message.includes("auth state is only supported for web targets") || message.includes("use.mobile.appState: preserve") || message.includes("Auth state name must match") || message.includes("Target name must match");
|
|
5427
|
+
}
|
|
5428
|
+
function printSafeAuthStateError(error, fallback) {
|
|
5429
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5430
|
+
console.error(import_picocolors13.default.red(isSafeAuthStateError(message) ? message : fallback));
|
|
5431
|
+
}
|
|
5432
|
+
function assertWebAuthStateTarget(input3) {
|
|
5433
|
+
(0, import_agent_qa_core13.resolveAuthStatePaths)({
|
|
5434
|
+
configDir: input3.configDir,
|
|
5435
|
+
authStateDir: input3.config.services?.authState?.dir,
|
|
5436
|
+
targetName: input3.target.name,
|
|
5437
|
+
stateName: "placeholder",
|
|
5438
|
+
target: { platform: input3.target.platform }
|
|
5439
|
+
});
|
|
5440
|
+
}
|
|
5441
|
+
function printAuthStateList(metadata, targetName) {
|
|
5442
|
+
if (metadata.length === 0) {
|
|
5443
|
+
console.log(targetName ? `No auth states saved for target "${targetName}".` : "No auth states saved.");
|
|
5444
|
+
return;
|
|
5445
|
+
}
|
|
5446
|
+
console.log("Target Name Captured Kind");
|
|
5447
|
+
for (const entry of metadata) {
|
|
5448
|
+
console.log(`${entry.target} ${entry.name} ${entry.capturedAt} ${entry.kind}`);
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
async function closeBrowser(browser) {
|
|
5452
|
+
if (!browser) return;
|
|
5453
|
+
try {
|
|
5454
|
+
await browser.close();
|
|
5455
|
+
} catch {
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
function createAuthStateCommand(deps = {}) {
|
|
5459
|
+
const cmd = new import_commander13.Command("auth-state").description("Manage product auth state");
|
|
5460
|
+
cmd.addCommand(
|
|
5461
|
+
new import_commander13.Command("list").description("List saved auth states").option("--target <target>", "target name from agent-qa config").action(async (opts, command) => {
|
|
5462
|
+
try {
|
|
5463
|
+
const { configDir, config } = await loadCommandContext(command);
|
|
5464
|
+
let targetName;
|
|
5465
|
+
if (opts.target) {
|
|
5466
|
+
const target = resolveTarget(config, opts.target);
|
|
5467
|
+
assertWebAuthStateTarget({ configDir, config, target });
|
|
5468
|
+
targetName = target.name;
|
|
5469
|
+
}
|
|
5470
|
+
const metadata = await (0, import_agent_qa_core13.listAuthStateMetadata)({
|
|
5471
|
+
configDir,
|
|
5472
|
+
authStateDir: config.services?.authState?.dir,
|
|
5473
|
+
targetName
|
|
5474
|
+
});
|
|
5475
|
+
printAuthStateList(metadata, targetName);
|
|
5476
|
+
} catch (error) {
|
|
5477
|
+
printSafeAuthStateError(error, "Could not list auth states.");
|
|
5478
|
+
process.exitCode = 1;
|
|
5479
|
+
}
|
|
5480
|
+
})
|
|
5481
|
+
);
|
|
5482
|
+
cmd.addCommand(
|
|
5483
|
+
new import_commander13.Command("remove").description("Remove saved auth state files for a target").requiredOption("--target <target>", "target name from agent-qa config").option("--name <name>", "logical auth-state name").action(async (opts, command) => {
|
|
5484
|
+
let resolvedTargetName = opts.target;
|
|
5485
|
+
try {
|
|
5486
|
+
const { configDir, config } = await loadCommandContext(command);
|
|
5487
|
+
const target = resolveTarget(config, opts.target);
|
|
5488
|
+
resolvedTargetName = target.name;
|
|
5489
|
+
if (opts.name) {
|
|
5490
|
+
await (0, import_agent_qa_core13.removeAuthStateFiles)({
|
|
5491
|
+
configDir,
|
|
5492
|
+
authStateDir: config.services?.authState?.dir,
|
|
5493
|
+
targetName: target.name,
|
|
5494
|
+
stateName: opts.name,
|
|
5495
|
+
target: { platform: target.platform }
|
|
5496
|
+
});
|
|
5497
|
+
console.log(`Removed auth state "${opts.name}" for target "${target.name}".`);
|
|
5498
|
+
return;
|
|
5499
|
+
}
|
|
5500
|
+
await (0, import_agent_qa_core13.removeAuthStateTarget)({
|
|
5501
|
+
configDir,
|
|
5502
|
+
authStateDir: config.services?.authState?.dir,
|
|
5503
|
+
targetName: target.name,
|
|
5504
|
+
target: { platform: target.platform }
|
|
5505
|
+
});
|
|
5506
|
+
console.log(`Removed auth states for target "${target.name}".`);
|
|
5507
|
+
} catch (error) {
|
|
5508
|
+
printSafeAuthStateError(
|
|
5509
|
+
error,
|
|
5510
|
+
opts.name ? `Could not remove auth state "${opts.name}" for target "${resolvedTargetName}".` : `Could not remove auth states for target "${resolvedTargetName}".`
|
|
5511
|
+
);
|
|
5512
|
+
process.exitCode = 1;
|
|
5513
|
+
}
|
|
5514
|
+
})
|
|
5515
|
+
);
|
|
5516
|
+
cmd.addCommand(
|
|
5517
|
+
new import_commander13.Command("capture").description("Capture a named web auth state for a target").requiredOption("--target <target>", "target name from agent-qa config").requiredOption("--name <name>", "logical auth-state name").action(async (opts, command) => {
|
|
5518
|
+
let browser;
|
|
5519
|
+
try {
|
|
5520
|
+
const { configDir, config } = await loadCommandContext(command);
|
|
5521
|
+
const target = resolveTarget(config, opts.target);
|
|
5522
|
+
const paths = (0, import_agent_qa_core13.resolveAuthStatePaths)({
|
|
5523
|
+
configDir,
|
|
5524
|
+
authStateDir: config.services?.authState?.dir,
|
|
5525
|
+
targetName: target.name,
|
|
5526
|
+
stateName: opts.name,
|
|
5527
|
+
target: { platform: target.platform }
|
|
5528
|
+
});
|
|
5529
|
+
if (!target.url) {
|
|
5530
|
+
console.error(import_picocolors13.default.red(`Target "${target.name}" must define a web URL for auth-state capture.`));
|
|
5531
|
+
process.exitCode = 1;
|
|
5532
|
+
return;
|
|
5533
|
+
}
|
|
5534
|
+
const launchers = deps.launchers ?? await loadPlaywrightLaunchers();
|
|
5535
|
+
const browserName = resolveBrowserName(config);
|
|
5536
|
+
browser = await launchers[browserName].launch({ headless: false });
|
|
5537
|
+
const context = await browser.newContext();
|
|
5538
|
+
const page = await context.newPage();
|
|
5539
|
+
await page.goto(target.url, { waitUntil: "domcontentloaded" });
|
|
5540
|
+
const exists = await authStateExists(paths);
|
|
5541
|
+
const waitForConfirmation = deps.waitForConfirmation ?? waitForCaptureConfirmation;
|
|
5542
|
+
const confirmation = await waitForConfirmation({
|
|
5543
|
+
browser,
|
|
5544
|
+
message: buildConfirmationMessage(target.name, opts.name, exists)
|
|
5545
|
+
});
|
|
5546
|
+
if (confirmation === "cancelled") {
|
|
5547
|
+
await closeBrowser(browser);
|
|
5548
|
+
console.log("Auth-state capture cancelled.");
|
|
5549
|
+
process.exitCode = 1;
|
|
5550
|
+
return;
|
|
5551
|
+
}
|
|
5552
|
+
if (confirmation === "browser-closed") {
|
|
5553
|
+
await closeBrowser(browser);
|
|
5554
|
+
console.error(import_picocolors13.default.red("Browser was closed before auth state was saved."));
|
|
5555
|
+
process.exitCode = 1;
|
|
5556
|
+
return;
|
|
5557
|
+
}
|
|
5558
|
+
const payload = await context.storageState({ indexedDB: true });
|
|
5559
|
+
try {
|
|
5560
|
+
await (0, import_agent_qa_core13.writeAuthStateFiles)(paths, {
|
|
5561
|
+
payload,
|
|
5562
|
+
metadata: {
|
|
5563
|
+
version: import_agent_qa_core13.AUTH_STATE_SCHEMA_VERSION,
|
|
5564
|
+
kind: "web",
|
|
5565
|
+
target: target.name,
|
|
5566
|
+
name: opts.name,
|
|
5567
|
+
capturedAt: (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
5568
|
+
}
|
|
5569
|
+
});
|
|
5570
|
+
} catch {
|
|
5571
|
+
await closeBrowser(browser);
|
|
5572
|
+
console.error(import_picocolors13.default.red(`Could not save auth state "${opts.name}" for target "${target.name}".`));
|
|
5573
|
+
process.exitCode = 1;
|
|
5574
|
+
return;
|
|
5575
|
+
}
|
|
5576
|
+
await closeBrowser(browser);
|
|
5577
|
+
console.log(`Saved auth state "${opts.name}" for target "${target.name}".`);
|
|
5578
|
+
} catch (error) {
|
|
5579
|
+
await closeBrowser(browser);
|
|
5580
|
+
console.error(import_picocolors13.default.red(error instanceof Error ? error.message : String(error)));
|
|
5581
|
+
process.exitCode = 1;
|
|
5582
|
+
}
|
|
5583
|
+
})
|
|
5584
|
+
);
|
|
5585
|
+
return cmd;
|
|
5586
|
+
}
|
|
5587
|
+
|
|
5588
|
+
// src/commands/devices.ts
|
|
5589
|
+
var import_commander14 = require("commander");
|
|
5212
5590
|
var import_node_child_process5 = require("child_process");
|
|
5213
5591
|
var import_node_fs4 = require("fs");
|
|
5214
|
-
var
|
|
5592
|
+
var import_node_path12 = require("path");
|
|
5215
5593
|
var import_yaml6 = require("yaml");
|
|
5216
|
-
var
|
|
5594
|
+
var import_picocolors14 = __toESM(require("picocolors"), 1);
|
|
5217
5595
|
init_config();
|
|
5218
5596
|
function formatStatus2(transport, localBinding) {
|
|
5219
5597
|
if (transport !== "local") {
|
|
5220
|
-
return
|
|
5598
|
+
return import_picocolors14.default.green("Ready") + import_picocolors14.default.dim(" (no local binding required)");
|
|
5221
5599
|
}
|
|
5222
5600
|
if (!localBinding) {
|
|
5223
|
-
return
|
|
5601
|
+
return import_picocolors14.default.red("UNBOUND") + import_picocolors14.default.yellow(" - run `agent-qa devices init`");
|
|
5224
5602
|
}
|
|
5225
5603
|
const keys = Object.entries(localBinding).slice(0, 2);
|
|
5226
5604
|
const details = keys.map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
5227
|
-
return
|
|
5605
|
+
return import_picocolors14.default.green("Bound") + import_picocolors14.default.dim(` (${details})`);
|
|
5228
5606
|
}
|
|
5229
5607
|
async function listDevices() {
|
|
5230
5608
|
let config;
|
|
5231
5609
|
try {
|
|
5232
5610
|
config = await resolveConfig({});
|
|
5233
5611
|
} catch (err) {
|
|
5234
|
-
console.error(
|
|
5612
|
+
console.error(import_picocolors14.default.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
|
|
5235
5613
|
process.exitCode = 1;
|
|
5236
5614
|
return;
|
|
5237
5615
|
}
|
|
5238
5616
|
const devices = config.registry?.devices;
|
|
5239
5617
|
if (!devices || Object.keys(devices).length === 0) {
|
|
5240
|
-
console.log(
|
|
5618
|
+
console.log(import_picocolors14.default.yellow("No devices configured in registry.devices"));
|
|
5241
5619
|
return;
|
|
5242
5620
|
}
|
|
5243
5621
|
const localBindings = loadLocalBindings();
|
|
@@ -5245,13 +5623,13 @@ async function listDevices() {
|
|
|
5245
5623
|
const platWidth = 10;
|
|
5246
5624
|
const transWidth = 14;
|
|
5247
5625
|
console.log("");
|
|
5248
|
-
console.log(
|
|
5626
|
+
console.log(import_picocolors14.default.bold(" Configured Devices"));
|
|
5249
5627
|
console.log("");
|
|
5250
5628
|
console.log(
|
|
5251
|
-
` ${
|
|
5629
|
+
` ${import_picocolors14.default.dim("Name".padEnd(nameWidth))} ${import_picocolors14.default.dim("Platform".padEnd(platWidth))} ${import_picocolors14.default.dim("Transport".padEnd(transWidth))} ${import_picocolors14.default.dim("Status")}`
|
|
5252
5630
|
);
|
|
5253
5631
|
console.log(
|
|
5254
|
-
` ${
|
|
5632
|
+
` ${import_picocolors14.default.dim("\u2500".repeat(nameWidth))} ${import_picocolors14.default.dim("\u2500".repeat(platWidth))} ${import_picocolors14.default.dim("\u2500".repeat(transWidth))} ${import_picocolors14.default.dim("\u2500".repeat(30))}`
|
|
5255
5633
|
);
|
|
5256
5634
|
for (const [name, profile] of Object.entries(devices)) {
|
|
5257
5635
|
const localBinding = localBindings?.devices?.[name] ?? null;
|
|
@@ -5302,7 +5680,7 @@ function scanSimctlDevices() {
|
|
|
5302
5680
|
}
|
|
5303
5681
|
}
|
|
5304
5682
|
function appendGitignoreEntry(dir, entry) {
|
|
5305
|
-
const gitignorePath = (0,
|
|
5683
|
+
const gitignorePath = (0, import_node_path12.join)(dir, ".gitignore");
|
|
5306
5684
|
let existing = "";
|
|
5307
5685
|
if ((0, import_node_fs4.existsSync)(gitignorePath)) {
|
|
5308
5686
|
existing = (0, import_node_fs4.readFileSync)(gitignorePath, "utf-8");
|
|
@@ -5313,30 +5691,30 @@ function appendGitignoreEntry(dir, entry) {
|
|
|
5313
5691
|
}
|
|
5314
5692
|
async function initDevices() {
|
|
5315
5693
|
const dir = process.cwd();
|
|
5316
|
-
const localFilePath = (0,
|
|
5694
|
+
const localFilePath = (0, import_node_path12.join)(dir, "agent-qa.local.yaml");
|
|
5317
5695
|
if ((0, import_node_fs4.existsSync)(localFilePath)) {
|
|
5318
|
-
console.log(
|
|
5696
|
+
console.log(import_picocolors14.default.yellow("agent-qa.local.yaml already exists. Skipping generation."));
|
|
5319
5697
|
return;
|
|
5320
5698
|
}
|
|
5321
|
-
console.log(
|
|
5699
|
+
console.log(import_picocolors14.default.bold("\n Scanning for connected devices...\n"));
|
|
5322
5700
|
const adbDevices = scanAdbDevices();
|
|
5323
5701
|
const simctlDevices = scanSimctlDevices();
|
|
5324
5702
|
if (adbDevices.length > 0) {
|
|
5325
|
-
console.log(` ${
|
|
5703
|
+
console.log(` ${import_picocolors14.default.green("Android:")} found ${adbDevices.length} device(s)`);
|
|
5326
5704
|
for (const d of adbDevices) {
|
|
5327
|
-
console.log(` ${
|
|
5705
|
+
console.log(` ${import_picocolors14.default.dim("\u2022")} ${d.model} (${d.serial})`);
|
|
5328
5706
|
}
|
|
5329
5707
|
} else {
|
|
5330
|
-
console.log(` ${
|
|
5708
|
+
console.log(` ${import_picocolors14.default.dim("Android:")} no devices found (adb not available or no devices connected)`);
|
|
5331
5709
|
}
|
|
5332
5710
|
if (simctlDevices.length > 0) {
|
|
5333
5711
|
const booted = simctlDevices.filter((d) => d.state === "Booted");
|
|
5334
|
-
console.log(` ${
|
|
5712
|
+
console.log(` ${import_picocolors14.default.green("iOS:")} found ${simctlDevices.length} simulator(s) (${booted.length} booted)`);
|
|
5335
5713
|
for (const d of booted) {
|
|
5336
|
-
console.log(` ${
|
|
5714
|
+
console.log(` ${import_picocolors14.default.dim("\u2022")} ${d.name} (${d.udid})`);
|
|
5337
5715
|
}
|
|
5338
5716
|
} else {
|
|
5339
|
-
console.log(` ${
|
|
5717
|
+
console.log(` ${import_picocolors14.default.dim("iOS:")} no simulators found (simctl not available or none installed)`);
|
|
5340
5718
|
}
|
|
5341
5719
|
let config;
|
|
5342
5720
|
try {
|
|
@@ -5370,20 +5748,20 @@ async function initDevices() {
|
|
|
5370
5748
|
(0, import_node_fs4.writeFileSync)(localFilePath, yamlContent);
|
|
5371
5749
|
appendGitignoreEntry(dir, "agent-qa.local.yaml");
|
|
5372
5750
|
console.log("");
|
|
5373
|
-
console.log(
|
|
5374
|
-
console.log(
|
|
5751
|
+
console.log(import_picocolors14.default.green(` Generated agent-qa.local.yaml with ${bindCount} device binding(s)`));
|
|
5752
|
+
console.log(import_picocolors14.default.dim(" Added agent-qa.local.yaml to .gitignore"));
|
|
5375
5753
|
console.log("");
|
|
5376
5754
|
}
|
|
5377
5755
|
function createDevicesCommand() {
|
|
5378
|
-
const cmd = new
|
|
5756
|
+
const cmd = new import_commander14.Command("devices").description("Manage device profiles and local bindings");
|
|
5379
5757
|
cmd.command("list").description("Show configured devices with merged shared + local view").action(listDevices);
|
|
5380
5758
|
cmd.command("init").description("Scan connected devices and generate agent-qa.local.yaml").action(initDevices);
|
|
5381
5759
|
return cmd;
|
|
5382
5760
|
}
|
|
5383
5761
|
|
|
5384
5762
|
// src/commands/ids.ts
|
|
5385
|
-
var
|
|
5386
|
-
var
|
|
5763
|
+
var import_commander15 = require("commander");
|
|
5764
|
+
var import_picocolors15 = __toESM(require("picocolors"), 1);
|
|
5387
5765
|
var import_agent_qa_ids = require("@vostride/agent-qa-ids");
|
|
5388
5766
|
function parseEntityType(value) {
|
|
5389
5767
|
if (import_agent_qa_ids.ENTITY_ID_TYPES.includes(value)) {
|
|
@@ -5392,7 +5770,7 @@ function parseEntityType(value) {
|
|
|
5392
5770
|
throw new Error(`Unknown ID type "${value}". Expected one of: ${import_agent_qa_ids.ENTITY_ID_TYPES.join(", ")}`);
|
|
5393
5771
|
}
|
|
5394
5772
|
function createIdsCommand() {
|
|
5395
|
-
const cmd = new
|
|
5773
|
+
const cmd = new import_commander15.Command("ids").description("Generate and validate canonical agent-qa entity IDs");
|
|
5396
5774
|
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) => {
|
|
5397
5775
|
try {
|
|
5398
5776
|
const type = parseEntityType(typeValue);
|
|
@@ -5404,7 +5782,7 @@ function createIdsCommand() {
|
|
|
5404
5782
|
console.log(id);
|
|
5405
5783
|
}
|
|
5406
5784
|
} catch (err) {
|
|
5407
|
-
console.error(
|
|
5785
|
+
console.error(import_picocolors15.default.red(err instanceof Error ? err.message : String(err)));
|
|
5408
5786
|
process.exitCode = 1;
|
|
5409
5787
|
}
|
|
5410
5788
|
});
|
|
@@ -5423,15 +5801,15 @@ function createIdsCommand() {
|
|
|
5423
5801
|
if (opts.json) {
|
|
5424
5802
|
console.log(JSON.stringify(result, null, 2));
|
|
5425
5803
|
} else if (valid) {
|
|
5426
|
-
console.log(
|
|
5804
|
+
console.log(import_picocolors15.default.green(result.message));
|
|
5427
5805
|
} else {
|
|
5428
|
-
console.error(
|
|
5806
|
+
console.error(import_picocolors15.default.red(result.message));
|
|
5429
5807
|
}
|
|
5430
5808
|
if (!valid) {
|
|
5431
5809
|
process.exitCode = 1;
|
|
5432
5810
|
}
|
|
5433
5811
|
} catch (err) {
|
|
5434
|
-
console.error(
|
|
5812
|
+
console.error(import_picocolors15.default.red(err instanceof Error ? err.message : String(err)));
|
|
5435
5813
|
process.exitCode = 1;
|
|
5436
5814
|
}
|
|
5437
5815
|
});
|
|
@@ -5439,18 +5817,18 @@ function createIdsCommand() {
|
|
|
5439
5817
|
}
|
|
5440
5818
|
|
|
5441
5819
|
// src/commands/create-test.ts
|
|
5442
|
-
var
|
|
5820
|
+
var import_commander16 = require("commander");
|
|
5443
5821
|
var import_agent_qa_ids2 = require("@vostride/agent-qa-ids");
|
|
5444
5822
|
var import_yaml7 = require("yaml");
|
|
5445
|
-
var
|
|
5823
|
+
var import_promises5 = require("fs/promises");
|
|
5446
5824
|
var import_node_fs5 = require("fs");
|
|
5447
|
-
var
|
|
5448
|
-
var
|
|
5825
|
+
var import_node_path13 = require("path");
|
|
5826
|
+
var import_picocolors16 = __toESM(require("picocolors"), 1);
|
|
5449
5827
|
function createCreateTestCommand() {
|
|
5450
|
-
const cmd = new
|
|
5451
|
-
const resolved = (0,
|
|
5828
|
+
const cmd = new import_commander16.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) => {
|
|
5829
|
+
const resolved = (0, import_node_path13.resolve)(path);
|
|
5452
5830
|
if ((0, import_node_fs5.existsSync)(resolved)) {
|
|
5453
|
-
console.error(
|
|
5831
|
+
console.error(import_picocolors16.default.red("Error: File already exists: " + resolved));
|
|
5454
5832
|
process.exitCode = 1;
|
|
5455
5833
|
return;
|
|
5456
5834
|
}
|
|
@@ -5462,27 +5840,27 @@ function createCreateTestCommand() {
|
|
|
5462
5840
|
steps: ["Describe your first test step here"]
|
|
5463
5841
|
};
|
|
5464
5842
|
const yamlContent = (0, import_yaml7.stringify)(template);
|
|
5465
|
-
await (0,
|
|
5466
|
-
await (0,
|
|
5467
|
-
console.log(
|
|
5468
|
-
console.log(
|
|
5843
|
+
await (0, import_promises5.mkdir)((0, import_node_path13.dirname)(resolved), { recursive: true });
|
|
5844
|
+
await (0, import_promises5.writeFile)(resolved, yamlContent, "utf-8");
|
|
5845
|
+
console.log(import_picocolors16.default.green("Created " + resolved));
|
|
5846
|
+
console.log(import_picocolors16.default.dim(" test-id: " + testId));
|
|
5469
5847
|
});
|
|
5470
5848
|
return cmd;
|
|
5471
5849
|
}
|
|
5472
5850
|
|
|
5473
5851
|
// src/commands/create-suite.ts
|
|
5474
|
-
var
|
|
5852
|
+
var import_commander17 = require("commander");
|
|
5475
5853
|
var import_agent_qa_ids3 = require("@vostride/agent-qa-ids");
|
|
5476
5854
|
var import_yaml8 = require("yaml");
|
|
5477
|
-
var
|
|
5855
|
+
var import_promises6 = require("fs/promises");
|
|
5478
5856
|
var import_node_fs6 = require("fs");
|
|
5479
|
-
var
|
|
5480
|
-
var
|
|
5857
|
+
var import_node_path14 = require("path");
|
|
5858
|
+
var import_picocolors17 = __toESM(require("picocolors"), 1);
|
|
5481
5859
|
function createCreateSuiteCommand() {
|
|
5482
|
-
const cmd = new
|
|
5483
|
-
const resolved = (0,
|
|
5860
|
+
const cmd = new import_commander17.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) => {
|
|
5861
|
+
const resolved = (0, import_node_path14.resolve)(path);
|
|
5484
5862
|
if ((0, import_node_fs6.existsSync)(resolved)) {
|
|
5485
|
-
console.error(
|
|
5863
|
+
console.error(import_picocolors17.default.red("Error: File already exists: " + resolved));
|
|
5486
5864
|
process.exitCode = 1;
|
|
5487
5865
|
return;
|
|
5488
5866
|
}
|
|
@@ -5496,22 +5874,22 @@ function createCreateSuiteCommand() {
|
|
|
5496
5874
|
"tests: []",
|
|
5497
5875
|
"tests: []\n # - test: ./path/to/test.yaml\n # id: my-test-id"
|
|
5498
5876
|
);
|
|
5499
|
-
await (0,
|
|
5500
|
-
await (0,
|
|
5501
|
-
console.log(
|
|
5502
|
-
console.log(
|
|
5877
|
+
await (0, import_promises6.mkdir)((0, import_node_path14.dirname)(resolved), { recursive: true });
|
|
5878
|
+
await (0, import_promises6.writeFile)(resolved, yamlContent, "utf-8");
|
|
5879
|
+
console.log(import_picocolors17.default.green("Created " + resolved));
|
|
5880
|
+
console.log(import_picocolors17.default.dim(" suite-id: " + suiteId));
|
|
5503
5881
|
});
|
|
5504
5882
|
return cmd;
|
|
5505
5883
|
}
|
|
5506
5884
|
|
|
5507
5885
|
// src/commands/clean-memory.ts
|
|
5508
|
-
var
|
|
5509
|
-
var
|
|
5886
|
+
var import_commander18 = require("commander");
|
|
5887
|
+
var import_picocolors18 = __toESM(require("picocolors"), 1);
|
|
5510
5888
|
var import_node_readline3 = require("readline");
|
|
5511
|
-
var
|
|
5889
|
+
var import_promises7 = require("fs/promises");
|
|
5512
5890
|
var import_node_fs7 = require("fs");
|
|
5513
|
-
var
|
|
5514
|
-
var
|
|
5891
|
+
var import_node_path15 = require("path");
|
|
5892
|
+
var import_agent_qa_core14 = require("@vostride/agent-qa-core");
|
|
5515
5893
|
init_config();
|
|
5516
5894
|
function formatSize(bytes) {
|
|
5517
5895
|
if (bytes === 0) return "0 B";
|
|
@@ -5519,13 +5897,13 @@ function formatSize(bytes) {
|
|
|
5519
5897
|
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
5520
5898
|
}
|
|
5521
5899
|
async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
5522
|
-
const scopePath = (0,
|
|
5523
|
-
const obsFiles = await (0,
|
|
5900
|
+
const scopePath = (0, import_node_path15.join)(memoryRoot, tier, scope);
|
|
5901
|
+
const obsFiles = await (0, import_agent_qa_core14.listObservations)(scopePath);
|
|
5524
5902
|
let newest = null;
|
|
5525
5903
|
for (const filename of obsFiles) {
|
|
5526
5904
|
try {
|
|
5527
|
-
const content = await (0,
|
|
5528
|
-
const { data } = (0,
|
|
5905
|
+
const content = await (0, import_promises7.readFile)((0, import_node_path15.join)(scopePath, filename), "utf-8");
|
|
5906
|
+
const { data } = (0, import_agent_qa_core14.parseObservation)(content, filename);
|
|
5529
5907
|
if (data?.last_confirmed) {
|
|
5530
5908
|
if (!newest || data.last_confirmed > newest) {
|
|
5531
5909
|
newest = data.last_confirmed;
|
|
@@ -5536,10 +5914,10 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5536
5914
|
}
|
|
5537
5915
|
let totalSize = 0;
|
|
5538
5916
|
try {
|
|
5539
|
-
const entries = await (0,
|
|
5917
|
+
const entries = await (0, import_promises7.readdir)(scopePath);
|
|
5540
5918
|
for (const entry of entries) {
|
|
5541
5919
|
try {
|
|
5542
|
-
const s = await (0,
|
|
5920
|
+
const s = await (0, import_promises7.stat)((0, import_node_path15.join)(scopePath, entry));
|
|
5543
5921
|
if (s.isFile()) totalSize += s.size;
|
|
5544
5922
|
} catch {
|
|
5545
5923
|
}
|
|
@@ -5547,7 +5925,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5547
5925
|
} catch {
|
|
5548
5926
|
}
|
|
5549
5927
|
return {
|
|
5550
|
-
path: (0,
|
|
5928
|
+
path: (0, import_node_path15.join)(tier, scope),
|
|
5551
5929
|
tier,
|
|
5552
5930
|
scope,
|
|
5553
5931
|
fileCount: obsFiles.length,
|
|
@@ -5556,21 +5934,21 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5556
5934
|
};
|
|
5557
5935
|
}
|
|
5558
5936
|
function createCleanMemoryCommand() {
|
|
5559
|
-
const cmd = new
|
|
5937
|
+
const cmd = new import_commander18.Command("clean-memory").description("Remove orphaned memory observation directories").option("-y, --yes", "skip confirmation prompt").action(async (_opts, command) => {
|
|
5560
5938
|
const opts = command.opts();
|
|
5561
5939
|
const configPath = command.parent?.opts()?.config ?? "agent-qa.config.yaml";
|
|
5562
5940
|
const config = await resolveConfig({ configPath });
|
|
5563
|
-
const workspace = (0,
|
|
5941
|
+
const workspace = (0, import_agent_qa_core14.resolveWorkspacePaths)({ config, configPath });
|
|
5564
5942
|
const expectedProducts = /* @__PURE__ */ new Set();
|
|
5565
5943
|
const targets = config.registry?.targets ?? {};
|
|
5566
5944
|
for (const [name, entry] of Object.entries(targets)) {
|
|
5567
5945
|
expectedProducts.add(entry?.product ?? name);
|
|
5568
5946
|
}
|
|
5569
5947
|
const expectedTests = /* @__PURE__ */ new Set();
|
|
5570
|
-
for (const file of await (0,
|
|
5948
|
+
for (const file of await (0, import_agent_qa_core14.discoverWorkspaceFiles)({ workspace, kind: "test" })) {
|
|
5571
5949
|
try {
|
|
5572
|
-
const content = await (0,
|
|
5573
|
-
const result = (0,
|
|
5950
|
+
const content = await (0, import_promises7.readFile)(file.absolutePath, "utf-8");
|
|
5951
|
+
const result = (0, import_agent_qa_core14.parseTestFile)(content, file.workspaceRelativePath);
|
|
5574
5952
|
for (const t of result.tests) {
|
|
5575
5953
|
if (t["test-id"]) expectedTests.add(t["test-id"]);
|
|
5576
5954
|
expectedTests.add(t.name);
|
|
@@ -5579,17 +5957,17 @@ function createCleanMemoryCommand() {
|
|
|
5579
5957
|
}
|
|
5580
5958
|
}
|
|
5581
5959
|
const expectedSuites = /* @__PURE__ */ new Set();
|
|
5582
|
-
for (const file of await (0,
|
|
5960
|
+
for (const file of await (0, import_agent_qa_core14.discoverWorkspaceFiles)({ workspace, kind: "suite" })) {
|
|
5583
5961
|
try {
|
|
5584
|
-
const suite = await (0,
|
|
5962
|
+
const suite = await (0, import_agent_qa_core14.parseSuiteFile)(file.absolutePath);
|
|
5585
5963
|
if (suite["suite-id"]) expectedSuites.add(suite["suite-id"]);
|
|
5586
5964
|
expectedSuites.add(suite.name);
|
|
5587
5965
|
} catch {
|
|
5588
5966
|
}
|
|
5589
5967
|
}
|
|
5590
|
-
const memoryRoot = (0,
|
|
5968
|
+
const memoryRoot = (0, import_agent_qa_core14.resolveMemoryRoot)(config, workspace.configDir);
|
|
5591
5969
|
if (!(0, import_node_fs7.existsSync)(memoryRoot)) {
|
|
5592
|
-
console.log(
|
|
5970
|
+
console.log(import_picocolors18.default.dim("No memory directory found."));
|
|
5593
5971
|
return;
|
|
5594
5972
|
}
|
|
5595
5973
|
const TIERS = [
|
|
@@ -5599,16 +5977,16 @@ function createCleanMemoryCommand() {
|
|
|
5599
5977
|
];
|
|
5600
5978
|
const orphans = [];
|
|
5601
5979
|
for (const tier of TIERS) {
|
|
5602
|
-
const tierPath = (0,
|
|
5980
|
+
const tierPath = (0, import_node_path15.join)(memoryRoot, tier.dir);
|
|
5603
5981
|
let entries;
|
|
5604
5982
|
try {
|
|
5605
|
-
entries = await (0,
|
|
5983
|
+
entries = await (0, import_promises7.readdir)(tierPath);
|
|
5606
5984
|
} catch {
|
|
5607
5985
|
continue;
|
|
5608
5986
|
}
|
|
5609
5987
|
for (const entry of entries) {
|
|
5610
5988
|
try {
|
|
5611
|
-
const s = await (0,
|
|
5989
|
+
const s = await (0, import_promises7.stat)((0, import_node_path15.join)(tierPath, entry));
|
|
5612
5990
|
if (!s.isDirectory()) continue;
|
|
5613
5991
|
} catch {
|
|
5614
5992
|
continue;
|
|
@@ -5619,17 +5997,17 @@ function createCleanMemoryCommand() {
|
|
|
5619
5997
|
}
|
|
5620
5998
|
}
|
|
5621
5999
|
if (orphans.length === 0) {
|
|
5622
|
-
console.log(
|
|
6000
|
+
console.log(import_picocolors18.default.dim("No orphaned memory directories found."));
|
|
5623
6001
|
return;
|
|
5624
6002
|
}
|
|
5625
6003
|
const maxPathLen = Math.max("Path".length, ...orphans.map((o) => o.path.length));
|
|
5626
6004
|
console.log("");
|
|
5627
|
-
console.log(
|
|
6005
|
+
console.log(import_picocolors18.default.bold("Orphaned memory directories:"));
|
|
5628
6006
|
console.log("");
|
|
5629
|
-
console.log(" " +
|
|
5630
|
-
console.log(" " +
|
|
6007
|
+
console.log(" " + import_picocolors18.default.dim("Path".padEnd(maxPathLen + 2) + "Files".padStart(7) + " " + "Last Confirmed".padEnd(20) + "Size".padStart(10)));
|
|
6008
|
+
console.log(" " + import_picocolors18.default.dim("-".repeat(maxPathLen + 2 + 7 + 2 + 20 + 10)));
|
|
5631
6009
|
for (const o of orphans) {
|
|
5632
|
-
const confirmed = o.lastConfirmed ??
|
|
6010
|
+
const confirmed = o.lastConfirmed ?? import_picocolors18.default.dim("never");
|
|
5633
6011
|
console.log(" " + o.path.padEnd(maxPathLen + 2) + String(o.fileCount).padStart(7) + " " + String(confirmed).padEnd(20) + formatSize(o.size).padStart(10));
|
|
5634
6012
|
}
|
|
5635
6013
|
console.log("");
|
|
@@ -5645,23 +6023,23 @@ function createCleanMemoryCommand() {
|
|
|
5645
6023
|
}
|
|
5646
6024
|
}
|
|
5647
6025
|
for (const orphan of orphans) {
|
|
5648
|
-
await (0,
|
|
6026
|
+
await (0, import_promises7.rm)((0, import_node_path15.join)(memoryRoot, orphan.tier, orphan.scope), { recursive: true, force: true });
|
|
5649
6027
|
}
|
|
5650
|
-
console.log(
|
|
6028
|
+
console.log(import_picocolors18.default.green(`Deleted ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}.`));
|
|
5651
6029
|
});
|
|
5652
6030
|
return cmd;
|
|
5653
6031
|
}
|
|
5654
6032
|
|
|
5655
6033
|
// src/commands/skills.ts
|
|
5656
|
-
var
|
|
6034
|
+
var import_commander19 = require("commander");
|
|
5657
6035
|
var import_node_fs8 = require("fs");
|
|
5658
|
-
var
|
|
6036
|
+
var import_node_path16 = require("path");
|
|
5659
6037
|
var import_node_url = require("url");
|
|
5660
6038
|
var import_meta4 = {};
|
|
5661
6039
|
function findPackageRoot(startDir) {
|
|
5662
|
-
let current = (0,
|
|
6040
|
+
let current = (0, import_node_path16.resolve)(startDir);
|
|
5663
6041
|
while (true) {
|
|
5664
|
-
const packagePath = (0,
|
|
6042
|
+
const packagePath = (0, import_node_path16.join)(current, "package.json");
|
|
5665
6043
|
if ((0, import_node_fs8.existsSync)(packagePath)) {
|
|
5666
6044
|
try {
|
|
5667
6045
|
const pkg = JSON.parse((0, import_node_fs8.readFileSync)(packagePath, "utf-8"));
|
|
@@ -5671,9 +6049,9 @@ function findPackageRoot(startDir) {
|
|
|
5671
6049
|
} catch {
|
|
5672
6050
|
}
|
|
5673
6051
|
}
|
|
5674
|
-
const parent = (0,
|
|
6052
|
+
const parent = (0, import_node_path16.dirname)(current);
|
|
5675
6053
|
if (parent === current) {
|
|
5676
|
-
return (0,
|
|
6054
|
+
return (0, import_node_path16.resolve)(startDir);
|
|
5677
6055
|
}
|
|
5678
6056
|
current = parent;
|
|
5679
6057
|
}
|
|
@@ -5682,20 +6060,20 @@ function hasSkillDirectories(dir) {
|
|
|
5682
6060
|
if (!(0, import_node_fs8.existsSync)(dir)) return false;
|
|
5683
6061
|
try {
|
|
5684
6062
|
return (0, import_node_fs8.readdirSync)(dir).some((entry) => {
|
|
5685
|
-
const skillDir = (0,
|
|
5686
|
-
return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0,
|
|
6063
|
+
const skillDir = (0, import_node_path16.join)(dir, entry);
|
|
6064
|
+
return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0, import_node_path16.join)(skillDir, "SKILL.md"));
|
|
5687
6065
|
});
|
|
5688
6066
|
} catch {
|
|
5689
6067
|
return false;
|
|
5690
6068
|
}
|
|
5691
6069
|
}
|
|
5692
|
-
function resolveAgentQaSkillsDirectory(startDir = (0,
|
|
6070
|
+
function resolveAgentQaSkillsDirectory(startDir = (0, import_node_path16.dirname)((0, import_node_url.fileURLToPath)(import_meta4.url))) {
|
|
5693
6071
|
const packageRoot = findPackageRoot(startDir);
|
|
5694
|
-
const packagedSkills = (0,
|
|
6072
|
+
const packagedSkills = (0, import_node_path16.join)(packageRoot, "skills");
|
|
5695
6073
|
if (hasSkillDirectories(packagedSkills)) {
|
|
5696
6074
|
return { path: packagedSkills, source: "package" };
|
|
5697
6075
|
}
|
|
5698
|
-
const sourceSkills = (0,
|
|
6076
|
+
const sourceSkills = (0, import_node_path16.resolve)(packageRoot, "../..", "skills");
|
|
5699
6077
|
if (hasSkillDirectories(sourceSkills)) {
|
|
5700
6078
|
return { path: sourceSkills, source: "source" };
|
|
5701
6079
|
}
|
|
@@ -5709,21 +6087,21 @@ function parseSkillDescription(skillPath) {
|
|
|
5709
6087
|
function listAgentQaSkills(skillsDir) {
|
|
5710
6088
|
if (!hasSkillDirectories(skillsDir)) return [];
|
|
5711
6089
|
return (0, import_node_fs8.readdirSync)(skillsDir).filter((entry) => {
|
|
5712
|
-
const skillDir = (0,
|
|
5713
|
-
return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0,
|
|
6090
|
+
const skillDir = (0, import_node_path16.join)(skillsDir, entry);
|
|
6091
|
+
return (0, import_node_fs8.statSync)(skillDir).isDirectory() && (0, import_node_fs8.existsSync)((0, import_node_path16.join)(skillDir, "SKILL.md"));
|
|
5714
6092
|
}).sort().map((entry) => {
|
|
5715
|
-
const skillDir = (0,
|
|
6093
|
+
const skillDir = (0, import_node_path16.join)(skillsDir, entry);
|
|
5716
6094
|
return {
|
|
5717
6095
|
name: entry,
|
|
5718
6096
|
path: skillDir,
|
|
5719
|
-
description: parseSkillDescription((0,
|
|
6097
|
+
description: parseSkillDescription((0, import_node_path16.join)(skillDir, "SKILL.md"))
|
|
5720
6098
|
};
|
|
5721
6099
|
});
|
|
5722
6100
|
}
|
|
5723
6101
|
function createSkillsCommand(options = {}) {
|
|
5724
6102
|
const stdout = options.stdout ?? process.stdout;
|
|
5725
6103
|
const stderr = options.stderr ?? process.stderr;
|
|
5726
|
-
return new
|
|
6104
|
+
return new import_commander19.Command("skills").description("List packaged agent-qa skills").option("--json", "print machine-readable output").action((opts) => {
|
|
5727
6105
|
const resolved = resolveAgentQaSkillsDirectory(options.startDir);
|
|
5728
6106
|
const skills = listAgentQaSkills(resolved.path);
|
|
5729
6107
|
if (resolved.source === "missing" || skills.length === 0) {
|
|
@@ -5804,7 +6182,7 @@ function rejectRemovedCliSurface(input3 = {}) {
|
|
|
5804
6182
|
|
|
5805
6183
|
// src/cli.ts
|
|
5806
6184
|
rejectRemovedCliSurface();
|
|
5807
|
-
var program = new
|
|
6185
|
+
var program = new import_commander20.Command();
|
|
5808
6186
|
program.enablePositionalOptions();
|
|
5809
6187
|
program.name("agent-qa").description("AI-powered testing framework").version(VERSION);
|
|
5810
6188
|
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");
|
|
@@ -5821,6 +6199,7 @@ program.addCommand(createQueueCommand());
|
|
|
5821
6199
|
program.addCommand(createCacheCommand());
|
|
5822
6200
|
program.addCommand(createValidateCommand());
|
|
5823
6201
|
program.addCommand(createAuthCommand());
|
|
6202
|
+
program.addCommand(createAuthStateCommand());
|
|
5824
6203
|
program.addCommand(createDevicesCommand());
|
|
5825
6204
|
program.addCommand(createIdsCommand());
|
|
5826
6205
|
program.addCommand(createCreateTestCommand());
|