agent-qa 0.1.14 → 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/dist/cli.cjs +504 -126
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +475 -88
- package/dist/cli.js.map +1 -1
- package/package.json +10 -9
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
|
}
|
|
@@ -5208,37 +5363,259 @@ function createAuthCommand() {
|
|
|
5208
5363
|
return cmd;
|
|
5209
5364
|
}
|
|
5210
5365
|
|
|
5211
|
-
// src/commands/
|
|
5366
|
+
// src/commands/auth-state.ts
|
|
5367
|
+
var import_promises4 = require("readline/promises");
|
|
5368
|
+
var import_node_path11 = require("path");
|
|
5212
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");
|
|
5213
5590
|
var import_node_child_process5 = require("child_process");
|
|
5214
5591
|
var import_node_fs4 = require("fs");
|
|
5215
|
-
var
|
|
5592
|
+
var import_node_path12 = require("path");
|
|
5216
5593
|
var import_yaml6 = require("yaml");
|
|
5217
|
-
var
|
|
5594
|
+
var import_picocolors14 = __toESM(require("picocolors"), 1);
|
|
5218
5595
|
init_config();
|
|
5219
5596
|
function formatStatus2(transport, localBinding) {
|
|
5220
5597
|
if (transport !== "local") {
|
|
5221
|
-
return
|
|
5598
|
+
return import_picocolors14.default.green("Ready") + import_picocolors14.default.dim(" (no local binding required)");
|
|
5222
5599
|
}
|
|
5223
5600
|
if (!localBinding) {
|
|
5224
|
-
return
|
|
5601
|
+
return import_picocolors14.default.red("UNBOUND") + import_picocolors14.default.yellow(" - run `agent-qa devices init`");
|
|
5225
5602
|
}
|
|
5226
5603
|
const keys = Object.entries(localBinding).slice(0, 2);
|
|
5227
5604
|
const details = keys.map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
5228
|
-
return
|
|
5605
|
+
return import_picocolors14.default.green("Bound") + import_picocolors14.default.dim(` (${details})`);
|
|
5229
5606
|
}
|
|
5230
5607
|
async function listDevices() {
|
|
5231
5608
|
let config;
|
|
5232
5609
|
try {
|
|
5233
5610
|
config = await resolveConfig({});
|
|
5234
5611
|
} catch (err) {
|
|
5235
|
-
console.error(
|
|
5612
|
+
console.error(import_picocolors14.default.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
|
|
5236
5613
|
process.exitCode = 1;
|
|
5237
5614
|
return;
|
|
5238
5615
|
}
|
|
5239
5616
|
const devices = config.registry?.devices;
|
|
5240
5617
|
if (!devices || Object.keys(devices).length === 0) {
|
|
5241
|
-
console.log(
|
|
5618
|
+
console.log(import_picocolors14.default.yellow("No devices configured in registry.devices"));
|
|
5242
5619
|
return;
|
|
5243
5620
|
}
|
|
5244
5621
|
const localBindings = loadLocalBindings();
|
|
@@ -5246,13 +5623,13 @@ async function listDevices() {
|
|
|
5246
5623
|
const platWidth = 10;
|
|
5247
5624
|
const transWidth = 14;
|
|
5248
5625
|
console.log("");
|
|
5249
|
-
console.log(
|
|
5626
|
+
console.log(import_picocolors14.default.bold(" Configured Devices"));
|
|
5250
5627
|
console.log("");
|
|
5251
5628
|
console.log(
|
|
5252
|
-
` ${
|
|
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")}`
|
|
5253
5630
|
);
|
|
5254
5631
|
console.log(
|
|
5255
|
-
` ${
|
|
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))}`
|
|
5256
5633
|
);
|
|
5257
5634
|
for (const [name, profile] of Object.entries(devices)) {
|
|
5258
5635
|
const localBinding = localBindings?.devices?.[name] ?? null;
|
|
@@ -5303,7 +5680,7 @@ function scanSimctlDevices() {
|
|
|
5303
5680
|
}
|
|
5304
5681
|
}
|
|
5305
5682
|
function appendGitignoreEntry(dir, entry) {
|
|
5306
|
-
const gitignorePath = (0,
|
|
5683
|
+
const gitignorePath = (0, import_node_path12.join)(dir, ".gitignore");
|
|
5307
5684
|
let existing = "";
|
|
5308
5685
|
if ((0, import_node_fs4.existsSync)(gitignorePath)) {
|
|
5309
5686
|
existing = (0, import_node_fs4.readFileSync)(gitignorePath, "utf-8");
|
|
@@ -5314,30 +5691,30 @@ function appendGitignoreEntry(dir, entry) {
|
|
|
5314
5691
|
}
|
|
5315
5692
|
async function initDevices() {
|
|
5316
5693
|
const dir = process.cwd();
|
|
5317
|
-
const localFilePath = (0,
|
|
5694
|
+
const localFilePath = (0, import_node_path12.join)(dir, "agent-qa.local.yaml");
|
|
5318
5695
|
if ((0, import_node_fs4.existsSync)(localFilePath)) {
|
|
5319
|
-
console.log(
|
|
5696
|
+
console.log(import_picocolors14.default.yellow("agent-qa.local.yaml already exists. Skipping generation."));
|
|
5320
5697
|
return;
|
|
5321
5698
|
}
|
|
5322
|
-
console.log(
|
|
5699
|
+
console.log(import_picocolors14.default.bold("\n Scanning for connected devices...\n"));
|
|
5323
5700
|
const adbDevices = scanAdbDevices();
|
|
5324
5701
|
const simctlDevices = scanSimctlDevices();
|
|
5325
5702
|
if (adbDevices.length > 0) {
|
|
5326
|
-
console.log(` ${
|
|
5703
|
+
console.log(` ${import_picocolors14.default.green("Android:")} found ${adbDevices.length} device(s)`);
|
|
5327
5704
|
for (const d of adbDevices) {
|
|
5328
|
-
console.log(` ${
|
|
5705
|
+
console.log(` ${import_picocolors14.default.dim("\u2022")} ${d.model} (${d.serial})`);
|
|
5329
5706
|
}
|
|
5330
5707
|
} else {
|
|
5331
|
-
console.log(` ${
|
|
5708
|
+
console.log(` ${import_picocolors14.default.dim("Android:")} no devices found (adb not available or no devices connected)`);
|
|
5332
5709
|
}
|
|
5333
5710
|
if (simctlDevices.length > 0) {
|
|
5334
5711
|
const booted = simctlDevices.filter((d) => d.state === "Booted");
|
|
5335
|
-
console.log(` ${
|
|
5712
|
+
console.log(` ${import_picocolors14.default.green("iOS:")} found ${simctlDevices.length} simulator(s) (${booted.length} booted)`);
|
|
5336
5713
|
for (const d of booted) {
|
|
5337
|
-
console.log(` ${
|
|
5714
|
+
console.log(` ${import_picocolors14.default.dim("\u2022")} ${d.name} (${d.udid})`);
|
|
5338
5715
|
}
|
|
5339
5716
|
} else {
|
|
5340
|
-
console.log(` ${
|
|
5717
|
+
console.log(` ${import_picocolors14.default.dim("iOS:")} no simulators found (simctl not available or none installed)`);
|
|
5341
5718
|
}
|
|
5342
5719
|
let config;
|
|
5343
5720
|
try {
|
|
@@ -5371,20 +5748,20 @@ async function initDevices() {
|
|
|
5371
5748
|
(0, import_node_fs4.writeFileSync)(localFilePath, yamlContent);
|
|
5372
5749
|
appendGitignoreEntry(dir, "agent-qa.local.yaml");
|
|
5373
5750
|
console.log("");
|
|
5374
|
-
console.log(
|
|
5375
|
-
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"));
|
|
5376
5753
|
console.log("");
|
|
5377
5754
|
}
|
|
5378
5755
|
function createDevicesCommand() {
|
|
5379
|
-
const cmd = new
|
|
5756
|
+
const cmd = new import_commander14.Command("devices").description("Manage device profiles and local bindings");
|
|
5380
5757
|
cmd.command("list").description("Show configured devices with merged shared + local view").action(listDevices);
|
|
5381
5758
|
cmd.command("init").description("Scan connected devices and generate agent-qa.local.yaml").action(initDevices);
|
|
5382
5759
|
return cmd;
|
|
5383
5760
|
}
|
|
5384
5761
|
|
|
5385
5762
|
// src/commands/ids.ts
|
|
5386
|
-
var
|
|
5387
|
-
var
|
|
5763
|
+
var import_commander15 = require("commander");
|
|
5764
|
+
var import_picocolors15 = __toESM(require("picocolors"), 1);
|
|
5388
5765
|
var import_agent_qa_ids = require("@vostride/agent-qa-ids");
|
|
5389
5766
|
function parseEntityType(value) {
|
|
5390
5767
|
if (import_agent_qa_ids.ENTITY_ID_TYPES.includes(value)) {
|
|
@@ -5393,7 +5770,7 @@ function parseEntityType(value) {
|
|
|
5393
5770
|
throw new Error(`Unknown ID type "${value}". Expected one of: ${import_agent_qa_ids.ENTITY_ID_TYPES.join(", ")}`);
|
|
5394
5771
|
}
|
|
5395
5772
|
function createIdsCommand() {
|
|
5396
|
-
const cmd = new
|
|
5773
|
+
const cmd = new import_commander15.Command("ids").description("Generate and validate canonical agent-qa entity IDs");
|
|
5397
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) => {
|
|
5398
5775
|
try {
|
|
5399
5776
|
const type = parseEntityType(typeValue);
|
|
@@ -5405,7 +5782,7 @@ function createIdsCommand() {
|
|
|
5405
5782
|
console.log(id);
|
|
5406
5783
|
}
|
|
5407
5784
|
} catch (err) {
|
|
5408
|
-
console.error(
|
|
5785
|
+
console.error(import_picocolors15.default.red(err instanceof Error ? err.message : String(err)));
|
|
5409
5786
|
process.exitCode = 1;
|
|
5410
5787
|
}
|
|
5411
5788
|
});
|
|
@@ -5424,15 +5801,15 @@ function createIdsCommand() {
|
|
|
5424
5801
|
if (opts.json) {
|
|
5425
5802
|
console.log(JSON.stringify(result, null, 2));
|
|
5426
5803
|
} else if (valid) {
|
|
5427
|
-
console.log(
|
|
5804
|
+
console.log(import_picocolors15.default.green(result.message));
|
|
5428
5805
|
} else {
|
|
5429
|
-
console.error(
|
|
5806
|
+
console.error(import_picocolors15.default.red(result.message));
|
|
5430
5807
|
}
|
|
5431
5808
|
if (!valid) {
|
|
5432
5809
|
process.exitCode = 1;
|
|
5433
5810
|
}
|
|
5434
5811
|
} catch (err) {
|
|
5435
|
-
console.error(
|
|
5812
|
+
console.error(import_picocolors15.default.red(err instanceof Error ? err.message : String(err)));
|
|
5436
5813
|
process.exitCode = 1;
|
|
5437
5814
|
}
|
|
5438
5815
|
});
|
|
@@ -5440,18 +5817,18 @@ function createIdsCommand() {
|
|
|
5440
5817
|
}
|
|
5441
5818
|
|
|
5442
5819
|
// src/commands/create-test.ts
|
|
5443
|
-
var
|
|
5820
|
+
var import_commander16 = require("commander");
|
|
5444
5821
|
var import_agent_qa_ids2 = require("@vostride/agent-qa-ids");
|
|
5445
5822
|
var import_yaml7 = require("yaml");
|
|
5446
|
-
var
|
|
5823
|
+
var import_promises5 = require("fs/promises");
|
|
5447
5824
|
var import_node_fs5 = require("fs");
|
|
5448
|
-
var
|
|
5449
|
-
var
|
|
5825
|
+
var import_node_path13 = require("path");
|
|
5826
|
+
var import_picocolors16 = __toESM(require("picocolors"), 1);
|
|
5450
5827
|
function createCreateTestCommand() {
|
|
5451
|
-
const cmd = new
|
|
5452
|
-
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);
|
|
5453
5830
|
if ((0, import_node_fs5.existsSync)(resolved)) {
|
|
5454
|
-
console.error(
|
|
5831
|
+
console.error(import_picocolors16.default.red("Error: File already exists: " + resolved));
|
|
5455
5832
|
process.exitCode = 1;
|
|
5456
5833
|
return;
|
|
5457
5834
|
}
|
|
@@ -5463,27 +5840,27 @@ function createCreateTestCommand() {
|
|
|
5463
5840
|
steps: ["Describe your first test step here"]
|
|
5464
5841
|
};
|
|
5465
5842
|
const yamlContent = (0, import_yaml7.stringify)(template);
|
|
5466
|
-
await (0,
|
|
5467
|
-
await (0,
|
|
5468
|
-
console.log(
|
|
5469
|
-
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));
|
|
5470
5847
|
});
|
|
5471
5848
|
return cmd;
|
|
5472
5849
|
}
|
|
5473
5850
|
|
|
5474
5851
|
// src/commands/create-suite.ts
|
|
5475
|
-
var
|
|
5852
|
+
var import_commander17 = require("commander");
|
|
5476
5853
|
var import_agent_qa_ids3 = require("@vostride/agent-qa-ids");
|
|
5477
5854
|
var import_yaml8 = require("yaml");
|
|
5478
|
-
var
|
|
5855
|
+
var import_promises6 = require("fs/promises");
|
|
5479
5856
|
var import_node_fs6 = require("fs");
|
|
5480
|
-
var
|
|
5481
|
-
var
|
|
5857
|
+
var import_node_path14 = require("path");
|
|
5858
|
+
var import_picocolors17 = __toESM(require("picocolors"), 1);
|
|
5482
5859
|
function createCreateSuiteCommand() {
|
|
5483
|
-
const cmd = new
|
|
5484
|
-
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);
|
|
5485
5862
|
if ((0, import_node_fs6.existsSync)(resolved)) {
|
|
5486
|
-
console.error(
|
|
5863
|
+
console.error(import_picocolors17.default.red("Error: File already exists: " + resolved));
|
|
5487
5864
|
process.exitCode = 1;
|
|
5488
5865
|
return;
|
|
5489
5866
|
}
|
|
@@ -5497,22 +5874,22 @@ function createCreateSuiteCommand() {
|
|
|
5497
5874
|
"tests: []",
|
|
5498
5875
|
"tests: []\n # - test: ./path/to/test.yaml\n # id: my-test-id"
|
|
5499
5876
|
);
|
|
5500
|
-
await (0,
|
|
5501
|
-
await (0,
|
|
5502
|
-
console.log(
|
|
5503
|
-
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));
|
|
5504
5881
|
});
|
|
5505
5882
|
return cmd;
|
|
5506
5883
|
}
|
|
5507
5884
|
|
|
5508
5885
|
// src/commands/clean-memory.ts
|
|
5509
|
-
var
|
|
5510
|
-
var
|
|
5886
|
+
var import_commander18 = require("commander");
|
|
5887
|
+
var import_picocolors18 = __toESM(require("picocolors"), 1);
|
|
5511
5888
|
var import_node_readline3 = require("readline");
|
|
5512
|
-
var
|
|
5889
|
+
var import_promises7 = require("fs/promises");
|
|
5513
5890
|
var import_node_fs7 = require("fs");
|
|
5514
|
-
var
|
|
5515
|
-
var
|
|
5891
|
+
var import_node_path15 = require("path");
|
|
5892
|
+
var import_agent_qa_core14 = require("@vostride/agent-qa-core");
|
|
5516
5893
|
init_config();
|
|
5517
5894
|
function formatSize(bytes) {
|
|
5518
5895
|
if (bytes === 0) return "0 B";
|
|
@@ -5520,13 +5897,13 @@ function formatSize(bytes) {
|
|
|
5520
5897
|
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
5521
5898
|
}
|
|
5522
5899
|
async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
5523
|
-
const scopePath = (0,
|
|
5524
|
-
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);
|
|
5525
5902
|
let newest = null;
|
|
5526
5903
|
for (const filename of obsFiles) {
|
|
5527
5904
|
try {
|
|
5528
|
-
const content = await (0,
|
|
5529
|
-
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);
|
|
5530
5907
|
if (data?.last_confirmed) {
|
|
5531
5908
|
if (!newest || data.last_confirmed > newest) {
|
|
5532
5909
|
newest = data.last_confirmed;
|
|
@@ -5537,10 +5914,10 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5537
5914
|
}
|
|
5538
5915
|
let totalSize = 0;
|
|
5539
5916
|
try {
|
|
5540
|
-
const entries = await (0,
|
|
5917
|
+
const entries = await (0, import_promises7.readdir)(scopePath);
|
|
5541
5918
|
for (const entry of entries) {
|
|
5542
5919
|
try {
|
|
5543
|
-
const s = await (0,
|
|
5920
|
+
const s = await (0, import_promises7.stat)((0, import_node_path15.join)(scopePath, entry));
|
|
5544
5921
|
if (s.isFile()) totalSize += s.size;
|
|
5545
5922
|
} catch {
|
|
5546
5923
|
}
|
|
@@ -5548,7 +5925,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5548
5925
|
} catch {
|
|
5549
5926
|
}
|
|
5550
5927
|
return {
|
|
5551
|
-
path: (0,
|
|
5928
|
+
path: (0, import_node_path15.join)(tier, scope),
|
|
5552
5929
|
tier,
|
|
5553
5930
|
scope,
|
|
5554
5931
|
fileCount: obsFiles.length,
|
|
@@ -5557,21 +5934,21 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
|
|
|
5557
5934
|
};
|
|
5558
5935
|
}
|
|
5559
5936
|
function createCleanMemoryCommand() {
|
|
5560
|
-
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) => {
|
|
5561
5938
|
const opts = command.opts();
|
|
5562
5939
|
const configPath = command.parent?.opts()?.config ?? "agent-qa.config.yaml";
|
|
5563
5940
|
const config = await resolveConfig({ configPath });
|
|
5564
|
-
const workspace = (0,
|
|
5941
|
+
const workspace = (0, import_agent_qa_core14.resolveWorkspacePaths)({ config, configPath });
|
|
5565
5942
|
const expectedProducts = /* @__PURE__ */ new Set();
|
|
5566
5943
|
const targets = config.registry?.targets ?? {};
|
|
5567
5944
|
for (const [name, entry] of Object.entries(targets)) {
|
|
5568
5945
|
expectedProducts.add(entry?.product ?? name);
|
|
5569
5946
|
}
|
|
5570
5947
|
const expectedTests = /* @__PURE__ */ new Set();
|
|
5571
|
-
for (const file of await (0,
|
|
5948
|
+
for (const file of await (0, import_agent_qa_core14.discoverWorkspaceFiles)({ workspace, kind: "test" })) {
|
|
5572
5949
|
try {
|
|
5573
|
-
const content = await (0,
|
|
5574
|
-
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);
|
|
5575
5952
|
for (const t of result.tests) {
|
|
5576
5953
|
if (t["test-id"]) expectedTests.add(t["test-id"]);
|
|
5577
5954
|
expectedTests.add(t.name);
|
|
@@ -5580,17 +5957,17 @@ function createCleanMemoryCommand() {
|
|
|
5580
5957
|
}
|
|
5581
5958
|
}
|
|
5582
5959
|
const expectedSuites = /* @__PURE__ */ new Set();
|
|
5583
|
-
for (const file of await (0,
|
|
5960
|
+
for (const file of await (0, import_agent_qa_core14.discoverWorkspaceFiles)({ workspace, kind: "suite" })) {
|
|
5584
5961
|
try {
|
|
5585
|
-
const suite = await (0,
|
|
5962
|
+
const suite = await (0, import_agent_qa_core14.parseSuiteFile)(file.absolutePath);
|
|
5586
5963
|
if (suite["suite-id"]) expectedSuites.add(suite["suite-id"]);
|
|
5587
5964
|
expectedSuites.add(suite.name);
|
|
5588
5965
|
} catch {
|
|
5589
5966
|
}
|
|
5590
5967
|
}
|
|
5591
|
-
const memoryRoot = (0,
|
|
5968
|
+
const memoryRoot = (0, import_agent_qa_core14.resolveMemoryRoot)(config, workspace.configDir);
|
|
5592
5969
|
if (!(0, import_node_fs7.existsSync)(memoryRoot)) {
|
|
5593
|
-
console.log(
|
|
5970
|
+
console.log(import_picocolors18.default.dim("No memory directory found."));
|
|
5594
5971
|
return;
|
|
5595
5972
|
}
|
|
5596
5973
|
const TIERS = [
|
|
@@ -5600,16 +5977,16 @@ function createCleanMemoryCommand() {
|
|
|
5600
5977
|
];
|
|
5601
5978
|
const orphans = [];
|
|
5602
5979
|
for (const tier of TIERS) {
|
|
5603
|
-
const tierPath = (0,
|
|
5980
|
+
const tierPath = (0, import_node_path15.join)(memoryRoot, tier.dir);
|
|
5604
5981
|
let entries;
|
|
5605
5982
|
try {
|
|
5606
|
-
entries = await (0,
|
|
5983
|
+
entries = await (0, import_promises7.readdir)(tierPath);
|
|
5607
5984
|
} catch {
|
|
5608
5985
|
continue;
|
|
5609
5986
|
}
|
|
5610
5987
|
for (const entry of entries) {
|
|
5611
5988
|
try {
|
|
5612
|
-
const s = await (0,
|
|
5989
|
+
const s = await (0, import_promises7.stat)((0, import_node_path15.join)(tierPath, entry));
|
|
5613
5990
|
if (!s.isDirectory()) continue;
|
|
5614
5991
|
} catch {
|
|
5615
5992
|
continue;
|
|
@@ -5620,17 +5997,17 @@ function createCleanMemoryCommand() {
|
|
|
5620
5997
|
}
|
|
5621
5998
|
}
|
|
5622
5999
|
if (orphans.length === 0) {
|
|
5623
|
-
console.log(
|
|
6000
|
+
console.log(import_picocolors18.default.dim("No orphaned memory directories found."));
|
|
5624
6001
|
return;
|
|
5625
6002
|
}
|
|
5626
6003
|
const maxPathLen = Math.max("Path".length, ...orphans.map((o) => o.path.length));
|
|
5627
6004
|
console.log("");
|
|
5628
|
-
console.log(
|
|
6005
|
+
console.log(import_picocolors18.default.bold("Orphaned memory directories:"));
|
|
5629
6006
|
console.log("");
|
|
5630
|
-
console.log(" " +
|
|
5631
|
-
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)));
|
|
5632
6009
|
for (const o of orphans) {
|
|
5633
|
-
const confirmed = o.lastConfirmed ??
|
|
6010
|
+
const confirmed = o.lastConfirmed ?? import_picocolors18.default.dim("never");
|
|
5634
6011
|
console.log(" " + o.path.padEnd(maxPathLen + 2) + String(o.fileCount).padStart(7) + " " + String(confirmed).padEnd(20) + formatSize(o.size).padStart(10));
|
|
5635
6012
|
}
|
|
5636
6013
|
console.log("");
|
|
@@ -5646,23 +6023,23 @@ function createCleanMemoryCommand() {
|
|
|
5646
6023
|
}
|
|
5647
6024
|
}
|
|
5648
6025
|
for (const orphan of orphans) {
|
|
5649
|
-
await (0,
|
|
6026
|
+
await (0, import_promises7.rm)((0, import_node_path15.join)(memoryRoot, orphan.tier, orphan.scope), { recursive: true, force: true });
|
|
5650
6027
|
}
|
|
5651
|
-
console.log(
|
|
6028
|
+
console.log(import_picocolors18.default.green(`Deleted ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}.`));
|
|
5652
6029
|
});
|
|
5653
6030
|
return cmd;
|
|
5654
6031
|
}
|
|
5655
6032
|
|
|
5656
6033
|
// src/commands/skills.ts
|
|
5657
|
-
var
|
|
6034
|
+
var import_commander19 = require("commander");
|
|
5658
6035
|
var import_node_fs8 = require("fs");
|
|
5659
|
-
var
|
|
6036
|
+
var import_node_path16 = require("path");
|
|
5660
6037
|
var import_node_url = require("url");
|
|
5661
6038
|
var import_meta4 = {};
|
|
5662
6039
|
function findPackageRoot(startDir) {
|
|
5663
|
-
let current = (0,
|
|
6040
|
+
let current = (0, import_node_path16.resolve)(startDir);
|
|
5664
6041
|
while (true) {
|
|
5665
|
-
const packagePath = (0,
|
|
6042
|
+
const packagePath = (0, import_node_path16.join)(current, "package.json");
|
|
5666
6043
|
if ((0, import_node_fs8.existsSync)(packagePath)) {
|
|
5667
6044
|
try {
|
|
5668
6045
|
const pkg = JSON.parse((0, import_node_fs8.readFileSync)(packagePath, "utf-8"));
|
|
@@ -5672,9 +6049,9 @@ function findPackageRoot(startDir) {
|
|
|
5672
6049
|
} catch {
|
|
5673
6050
|
}
|
|
5674
6051
|
}
|
|
5675
|
-
const parent = (0,
|
|
6052
|
+
const parent = (0, import_node_path16.dirname)(current);
|
|
5676
6053
|
if (parent === current) {
|
|
5677
|
-
return (0,
|
|
6054
|
+
return (0, import_node_path16.resolve)(startDir);
|
|
5678
6055
|
}
|
|
5679
6056
|
current = parent;
|
|
5680
6057
|
}
|
|
@@ -5683,20 +6060,20 @@ function hasSkillDirectories(dir) {
|
|
|
5683
6060
|
if (!(0, import_node_fs8.existsSync)(dir)) return false;
|
|
5684
6061
|
try {
|
|
5685
6062
|
return (0, import_node_fs8.readdirSync)(dir).some((entry) => {
|
|
5686
|
-
const skillDir = (0,
|
|
5687
|
-
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"));
|
|
5688
6065
|
});
|
|
5689
6066
|
} catch {
|
|
5690
6067
|
return false;
|
|
5691
6068
|
}
|
|
5692
6069
|
}
|
|
5693
|
-
function resolveAgentQaSkillsDirectory(startDir = (0,
|
|
6070
|
+
function resolveAgentQaSkillsDirectory(startDir = (0, import_node_path16.dirname)((0, import_node_url.fileURLToPath)(import_meta4.url))) {
|
|
5694
6071
|
const packageRoot = findPackageRoot(startDir);
|
|
5695
|
-
const packagedSkills = (0,
|
|
6072
|
+
const packagedSkills = (0, import_node_path16.join)(packageRoot, "skills");
|
|
5696
6073
|
if (hasSkillDirectories(packagedSkills)) {
|
|
5697
6074
|
return { path: packagedSkills, source: "package" };
|
|
5698
6075
|
}
|
|
5699
|
-
const sourceSkills = (0,
|
|
6076
|
+
const sourceSkills = (0, import_node_path16.resolve)(packageRoot, "../..", "skills");
|
|
5700
6077
|
if (hasSkillDirectories(sourceSkills)) {
|
|
5701
6078
|
return { path: sourceSkills, source: "source" };
|
|
5702
6079
|
}
|
|
@@ -5710,21 +6087,21 @@ function parseSkillDescription(skillPath) {
|
|
|
5710
6087
|
function listAgentQaSkills(skillsDir) {
|
|
5711
6088
|
if (!hasSkillDirectories(skillsDir)) return [];
|
|
5712
6089
|
return (0, import_node_fs8.readdirSync)(skillsDir).filter((entry) => {
|
|
5713
|
-
const skillDir = (0,
|
|
5714
|
-
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"));
|
|
5715
6092
|
}).sort().map((entry) => {
|
|
5716
|
-
const skillDir = (0,
|
|
6093
|
+
const skillDir = (0, import_node_path16.join)(skillsDir, entry);
|
|
5717
6094
|
return {
|
|
5718
6095
|
name: entry,
|
|
5719
6096
|
path: skillDir,
|
|
5720
|
-
description: parseSkillDescription((0,
|
|
6097
|
+
description: parseSkillDescription((0, import_node_path16.join)(skillDir, "SKILL.md"))
|
|
5721
6098
|
};
|
|
5722
6099
|
});
|
|
5723
6100
|
}
|
|
5724
6101
|
function createSkillsCommand(options = {}) {
|
|
5725
6102
|
const stdout = options.stdout ?? process.stdout;
|
|
5726
6103
|
const stderr = options.stderr ?? process.stderr;
|
|
5727
|
-
return new
|
|
6104
|
+
return new import_commander19.Command("skills").description("List packaged agent-qa skills").option("--json", "print machine-readable output").action((opts) => {
|
|
5728
6105
|
const resolved = resolveAgentQaSkillsDirectory(options.startDir);
|
|
5729
6106
|
const skills = listAgentQaSkills(resolved.path);
|
|
5730
6107
|
if (resolved.source === "missing" || skills.length === 0) {
|
|
@@ -5805,7 +6182,7 @@ function rejectRemovedCliSurface(input3 = {}) {
|
|
|
5805
6182
|
|
|
5806
6183
|
// src/cli.ts
|
|
5807
6184
|
rejectRemovedCliSurface();
|
|
5808
|
-
var program = new
|
|
6185
|
+
var program = new import_commander20.Command();
|
|
5809
6186
|
program.enablePositionalOptions();
|
|
5810
6187
|
program.name("agent-qa").description("AI-powered testing framework").version(VERSION);
|
|
5811
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");
|
|
@@ -5822,6 +6199,7 @@ program.addCommand(createQueueCommand());
|
|
|
5822
6199
|
program.addCommand(createCacheCommand());
|
|
5823
6200
|
program.addCommand(createValidateCommand());
|
|
5824
6201
|
program.addCommand(createAuthCommand());
|
|
6202
|
+
program.addCommand(createAuthStateCommand());
|
|
5825
6203
|
program.addCommand(createDevicesCommand());
|
|
5826
6204
|
program.addCommand(createIdsCommand());
|
|
5827
6205
|
program.addCommand(createCreateTestCommand());
|