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.js CHANGED
@@ -337,7 +337,7 @@ var init_llm_utils = __esm({
337
337
  });
338
338
 
339
339
  // src/cli.ts
340
- import { Command as Command19 } from "commander";
340
+ import { Command as Command20 } from "commander";
341
341
 
342
342
  // src/index.ts
343
343
  var index_exports = {};
@@ -501,10 +501,12 @@ import select from "@inquirer/select";
501
501
  import input from "@inquirer/input";
502
502
  import checkbox from "@inquirer/checkbox";
503
503
  import {
504
+ DEFAULT_AGENT_QA_AUTH_STATES_DIR,
504
505
  DEFAULT_AGENT_QA_CACHE_DIR as DEFAULT_AGENT_QA_CACHE_DIR2,
505
506
  DEFAULT_AGENT_QA_RUNTIME_DIR
506
507
  } from "@vostride/agent-qa-core";
507
508
  var INIT_CACHE_DIR = DEFAULT_AGENT_QA_CACHE_DIR2 || ".agent-qa/cache";
509
+ var INIT_AUTH_STATES_DIR = DEFAULT_AGENT_QA_AUTH_STATES_DIR || ".agent-qa/auth-states";
508
510
  var INIT_RUNTIME_DIR = DEFAULT_AGENT_QA_RUNTIME_DIR || ".agent-qa";
509
511
  var SUBSCRIPTION_AUTH_PACKAGE = "@vostride/agent-qa-subscription-auth";
510
512
  var PACKAGE_JSON_FILE = "package.json";
@@ -837,6 +839,7 @@ function buildDefaultConfig(platform, providerOrLLMs, model, baseURL) {
837
839
  dashboard: { port: 3100, artifactsDir: ".agent-qa/artifacts" },
838
840
  mcp: { enabled: true, transport: "http", host: "127.0.0.1", port: 3471, path: "/mcp" },
839
841
  cache: { dir: INIT_CACHE_DIR, ttl: "7d" },
842
+ authState: { dir: INIT_AUTH_STATES_DIR },
840
843
  ...hasWeb(platform) ? {
841
844
  accessibility: {
842
845
  enabled: true,
@@ -878,7 +881,7 @@ function addYamlComments(yamlStr) {
878
881
  if (line.startsWith("workspace:")) {
879
882
  result += "# File discovery and project settings\n";
880
883
  } else if (line.startsWith("services:")) {
881
- result += "\n# Infrastructure services (dashboard, MCP, cache, logging)\n";
884
+ result += "\n# Infrastructure services (dashboard, MCP, cache, auth state, logging)\n";
882
885
  } else if (line.startsWith("registry:")) {
883
886
  result += "\n# Named resource definitions (LLM configs, app targets)\n";
884
887
  } else if (line.startsWith("plugins:")) {
@@ -933,7 +936,19 @@ var DEFAULT_AGENT_RULES_FILE = "agent-rules.md";
933
936
  var DEFAULT_ENV_FILE = ".env";
934
937
  var DEFAULT_SECRETS_FILE = ".env.secrets.local";
935
938
  var DEFAULT_LOCAL_CONFIG_FILE = "agent-qa.local.yaml";
936
- var GITIGNORE_ENTRIES = [`${INIT_RUNTIME_DIR}/`, "node_modules/", DEFAULT_LOCAL_CONFIG_FILE, DEFAULT_ENV_FILE, DEFAULT_SECRETS_FILE];
939
+ function directoryGitignoreEntry(value) {
940
+ return value.endsWith("/") ? value : `${value}/`;
941
+ }
942
+ function buildGitignoreEntries() {
943
+ return [
944
+ directoryGitignoreEntry(INIT_RUNTIME_DIR),
945
+ directoryGitignoreEntry(INIT_AUTH_STATES_DIR),
946
+ "node_modules/",
947
+ DEFAULT_LOCAL_CONFIG_FILE,
948
+ DEFAULT_ENV_FILE,
949
+ DEFAULT_SECRETS_FILE
950
+ ];
951
+ }
937
952
  function buildLocalConfigTemplate() {
938
953
  return `# This file is for machine-specific device, app, and provider bindings.
939
954
  # Keep it out of git.
@@ -962,7 +977,10 @@ function appendGitignore(dir) {
962
977
  if (existsSync(gitignorePath)) {
963
978
  existing = readFileSync(gitignorePath, "utf-8");
964
979
  }
965
- const missing = GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
980
+ const existingEntries = new Set(
981
+ existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
982
+ );
983
+ const missing = buildGitignoreEntries().filter((entry) => !existingEntries.has(entry));
966
984
  if (missing.length === 0) return;
967
985
  const append = (existing.length > 0 && !existing.endsWith("\n") ? "\n" : "") + missing.join("\n") + "\n";
968
986
  writeFileSync(gitignorePath, existing + append);
@@ -2096,7 +2114,7 @@ import { randomUUID } from "crypto";
2096
2114
  import { resolve as resolvePath3 } from "path";
2097
2115
  import { Command as Command6 } from "commander";
2098
2116
  import pc7 from "picocolors";
2099
- import { ATTR_RUNNER, ATTR_TRIGGER, buildInternalRunAttributes, DEFAULT_AGENT_QA_ARTIFACTS_DIR, DEFAULT_AGENT_QA_CACHE_DIR as DEFAULT_AGENT_QA_CACHE_DIR3, DEFAULT_AGENT_QA_VIDEOS_DIR, formatRunAttributesBlock, generateRunId, mergeRunAttributes, MobileSetupError, parseRunAttrFlags, resolveMemoryRoot, resolveMobileRunConfig, validateTrustedRunAttributes } from "@vostride/agent-qa-core";
2117
+ import { ATTR_RUNNER, ATTR_TRIGGER, AUTH_STATE_SCHEMA_VERSION, buildInternalRunAttributes, DEFAULT_AGENT_QA_ARTIFACTS_DIR, DEFAULT_AGENT_QA_CACHE_DIR as DEFAULT_AGENT_QA_CACHE_DIR3, DEFAULT_AGENT_QA_VIDEOS_DIR, formatRunAttributesBlock, generateRunId, mergeRunAttributes, MobileSetupError, normalizeAuthStateUse, parseRunAttrFlags, redactAuthStateValue, resolveAuthStateForRun, resolveAuthStatePaths, resolveMemoryRoot, resolveMobileRunConfig, validateTrustedRunAttributes } from "@vostride/agent-qa-core";
2100
2118
  import { discoverWorkspaceFiles as discoverWorkspaceFiles2, isWorkspacePathMatch, resolveWorkspaceFileTarget, resolveWorkspacePaths as resolveWorkspacePaths2 } from "@vostride/agent-qa-core";
2101
2119
 
2102
2120
  // src/devices.ts
@@ -2580,7 +2598,7 @@ async function loadRuntimeSecrets(input3) {
2580
2598
  }
2581
2599
  }
2582
2600
  function buildRunArtifactContext(input3) {
2583
- return {
2601
+ return redactAuthStateValue({
2584
2602
  artifact: {
2585
2603
  kind: input3.kind,
2586
2604
  config: {
@@ -2614,7 +2632,7 @@ function buildRunArtifactContext(input3) {
2614
2632
  attributes: input3.attributes
2615
2633
  }
2616
2634
  }
2617
- };
2635
+ });
2618
2636
  }
2619
2637
  async function resolveDeviceAndFarmSession(config, deviceName, testPlatform, testName, testTimeout, preResolvedDevice, localBindingsOverride, mobileResolved) {
2620
2638
  if (!deviceName || testPlatform === "web") return {};
@@ -2692,6 +2710,82 @@ function resolveMobileRunOrThrow(input3) {
2692
2710
  wrapMobileSetupError(err);
2693
2711
  }
2694
2712
  }
2713
+ function getSelectedAuthStateUse(use) {
2714
+ return normalizeAuthStateUse(use);
2715
+ }
2716
+ async function resolveSelectedAuthStateForRun(input3) {
2717
+ if (!input3.authState || input3.authState.load === false) return void 0;
2718
+ try {
2719
+ return await resolveAuthStateForRun({
2720
+ configDir: input3.configDir,
2721
+ authStateDir: input3.config.services?.authState?.dir,
2722
+ targetName: input3.resolvedTarget.name,
2723
+ stateName: input3.authState.name,
2724
+ target: { platform: input3.resolvedTarget.platform }
2725
+ });
2726
+ } catch (err) {
2727
+ console.error(pc7.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
2728
+ process.exit(2);
2729
+ }
2730
+ }
2731
+ function resolveAuthStateCapturePathsForRun(input3) {
2732
+ if (input3.authState?.capture !== true) return void 0;
2733
+ try {
2734
+ return resolveAuthStatePaths({
2735
+ configDir: input3.configDir,
2736
+ authStateDir: input3.config.services?.authState?.dir,
2737
+ targetName: input3.resolvedTarget.name,
2738
+ stateName: input3.authState.name,
2739
+ target: { platform: input3.resolvedTarget.platform }
2740
+ });
2741
+ } catch (err) {
2742
+ console.error(pc7.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
2743
+ process.exit(2);
2744
+ }
2745
+ }
2746
+ function formatAuthStateCaptureFailure(paths) {
2747
+ return `Could not save auth state "${paths.stateName}" for target "${paths.targetName}".`;
2748
+ }
2749
+ function getStorageStateContext(adapter) {
2750
+ const maybeGetPage = adapter.getPage;
2751
+ if (typeof maybeGetPage !== "function") return null;
2752
+ const page = maybeGetPage.call(adapter);
2753
+ if (!page || typeof page !== "object") return null;
2754
+ const maybeContext = page.context;
2755
+ if (typeof maybeContext !== "function") return null;
2756
+ const context = maybeContext.call(page);
2757
+ if (!context || typeof context !== "object") return null;
2758
+ const storageState = context.storageState;
2759
+ if (typeof storageState !== "function") return null;
2760
+ return {
2761
+ storageState: storageState.bind(context)
2762
+ };
2763
+ }
2764
+ async function captureAuthStateForRun(input3) {
2765
+ const context = getStorageStateContext(input3.adapter);
2766
+ if (!context) {
2767
+ throw new Error("Web auth-state capture requires a Playwright browser context.");
2768
+ }
2769
+ const payload = await context.storageState({ indexedDB: true });
2770
+ const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
2771
+ const metadata = {
2772
+ version: AUTH_STATE_SCHEMA_VERSION,
2773
+ kind: "web",
2774
+ target: input3.paths.targetName,
2775
+ name: input3.paths.stateName,
2776
+ capturedAt
2777
+ };
2778
+ const { writeAuthStateFiles: writeAuthStateFiles2 } = await import("@vostride/agent-qa-core");
2779
+ await writeAuthStateFiles2(input3.paths, { payload, metadata });
2780
+ return {
2781
+ version: metadata.version,
2782
+ kind: metadata.kind,
2783
+ targetName: metadata.target,
2784
+ stateName: metadata.name,
2785
+ capturedAt: metadata.capturedAt,
2786
+ storageStatePath: input3.paths.payloadPath
2787
+ };
2788
+ }
2695
2789
  async function executeSuites(suiteFiles, opts, config, globalOpts, configContent, effectiveLogLevel, variableContext, hooksContext) {
2696
2790
  const {
2697
2791
  parseSuiteFile: parseSuiteFile2,
@@ -2792,6 +2886,13 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2792
2886
  suiteFileContent = await readFile5(suiteFile, "utf-8");
2793
2887
  } catch {
2794
2888
  }
2889
+ const mergedUse = mergeUseBlocks(
2890
+ config.use,
2891
+ suite.use,
2892
+ void 0,
2893
+ {}
2894
+ );
2895
+ const suiteAuthStateUse = getSelectedAuthStateUse(mergedUse);
2795
2896
  const testEntries = [];
2796
2897
  const suiteMembers = [];
2797
2898
  for (const { test: relativePath, id: suiteEntryId } of suite.tests) {
@@ -2911,6 +3012,11 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2911
3012
  process.exit(2);
2912
3013
  }
2913
3014
  const testDef = parseResult.tests[0];
3015
+ const childAuthStateUse = getSelectedAuthStateUse(testDef.use);
3016
+ if (suiteAuthStateUse && childAuthStateUse && childAuthStateUse.name !== suiteAuthStateUse.name) {
3017
+ console.error(pc7.red(`Suite auth state "${suiteAuthStateUse.name}" conflicts with child test auth state "${childAuthStateUse.name}". Use one primary auth state per suite.`));
3018
+ process.exit(2);
3019
+ }
2914
3020
  if (testDef["test-id"] && testDef["test-id"] !== suiteEntryId) {
2915
3021
  const mismatchMsg = `ID mismatch in suite "${suite.name}": test file ${relativePath} has test-id "${testDef["test-id"]}" but suite entry specifies id "${suiteEntryId}"`;
2916
3022
  console.error(pc7.red(mismatchMsg));
@@ -2962,12 +3068,18 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
2962
3068
  }
2963
3069
  const suiteResolvedTarget = suite.target ? resolveTarget(config, suite.target) : resolveTarget(config, testEntries[0][0].target);
2964
3070
  const suitePlatform = suiteResolvedTarget.platform;
2965
- const mergedUse = mergeUseBlocks(
2966
- config.use,
2967
- suite.use,
2968
- void 0,
2969
- {}
2970
- );
3071
+ const suiteAuthState = await resolveSelectedAuthStateForRun({
3072
+ config,
3073
+ configDir,
3074
+ resolvedTarget: suiteResolvedTarget,
3075
+ authState: suiteAuthStateUse
3076
+ });
3077
+ const suiteAuthStateCapturePaths = resolveAuthStateCapturePathsForRun({
3078
+ config,
3079
+ configDir,
3080
+ resolvedTarget: suiteResolvedTarget,
3081
+ authState: suiteAuthStateUse
3082
+ });
2971
3083
  const suiteEffectiveCacheEnabled = opts.cache !== false && mergedUse.cache !== false;
2972
3084
  let suiteDeviceName = opts.device ?? mergedUse.device;
2973
3085
  if ((suitePlatform === "android" || suitePlatform === "ios") && !suiteDeviceName) {
@@ -3051,6 +3163,7 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
3051
3163
  const suiteBrowser = mergedUse.browser ?? config.use?.browser;
3052
3164
  const suiteLogCapture = mergedUse.logCapture ?? config.use?.logCapture;
3053
3165
  const platformConfig = buildPlatformConfig(suitePlatform, resolvedDevice, testTimeouts, suiteBrowser, suiteLogCapture, farmSession, mobileResolved);
3166
+ platformConfig.authState = suiteAuthState;
3054
3167
  platformConfig.verbose = effectiveLogLevel === "debug";
3055
3168
  if (opts.headless !== void 0 && platformConfig.browser) {
3056
3169
  platformConfig.browser.headless = opts.headless;
@@ -3241,6 +3354,10 @@ async function executeSuites(suiteFiles, opts, config, globalOpts, configContent
3241
3354
  memoryRoot: suiteMemoryRoot,
3242
3355
  circuitBreaker: suiteCircuitBreaker,
3243
3356
  product: resolvedTarget?.product,
3357
+ authStateCapture: suiteAuthStateCapturePaths ? {
3358
+ capture: () => captureAuthStateForRun({ adapter, paths: suiteAuthStateCapturePaths }),
3359
+ failureSummary: formatAuthStateCaptureFailure(suiteAuthStateCapturePaths)
3360
+ } : void 0,
3244
3361
  createAdapter: () => createPlatformAdapter(suitePlatform),
3245
3362
  resolveUrl: (targetName) => {
3246
3363
  try {
@@ -3570,7 +3687,7 @@ Discovered ${files.length} test file(s):
3570
3687
  dashboardDb.insertRunArtifact({
3571
3688
  runId: artifactRunId,
3572
3689
  kind: "test",
3573
- payload: {
3690
+ payload: redactAuthStateValue({
3574
3691
  config: {
3575
3692
  rawConfigContent: configContent,
3576
3693
  parsedConfig: config,
@@ -3601,7 +3718,7 @@ Discovered ${files.length} test file(s):
3601
3718
  message: error.message,
3602
3719
  details: error
3603
3720
  }))
3604
- }
3721
+ })
3605
3722
  });
3606
3723
  dashboardDb.finalizeRunArtifact(artifactRunId);
3607
3724
  dashboardDb.close();
@@ -3773,6 +3890,20 @@ Config for: ${test.name}`));
3773
3890
  }
3774
3891
  const testPlatform = testResolvedTarget.platform;
3775
3892
  analyticsPlatform = testPlatform;
3893
+ const testAuthStateUse = getSelectedAuthStateUse(mergedUse);
3894
+ const testAuthState = await resolveSelectedAuthStateForRun({
3895
+ config,
3896
+ configDir,
3897
+ resolvedTarget: testResolvedTarget,
3898
+ authState: testAuthStateUse
3899
+ });
3900
+ const testAuthStateCapturePaths = resolveAuthStateCapturePathsForRun({
3901
+ config,
3902
+ configDir,
3903
+ resolvedTarget: testResolvedTarget,
3904
+ authState: testAuthStateUse
3905
+ });
3906
+ let activeTestAuthState = testAuthState;
3776
3907
  const testMergedUse = testMergedConfig.use ?? config.use ?? {};
3777
3908
  const testDeviceName = opts.device ?? testMergedUse.device;
3778
3909
  const testTimeoutsForSetup = {
@@ -3852,6 +3983,7 @@ Config for: ${test.name}`));
3852
3983
  adapter = await createPlatformAdapter(testPlatform);
3853
3984
  const mergedBrowser = mergedUse.browser;
3854
3985
  const platformConfig = buildPlatformConfig(testPlatform, testResolvedDevice, testTimeoutsForSetup, mergedBrowser ?? config.use?.browser, testMergedConfig.use?.logCapture ?? config.use?.logCapture, testFarmSession, testMobileResolved);
3986
+ platformConfig.authState = activeTestAuthState;
3855
3987
  platformConfig.verbose = effectiveLogLevel === "debug";
3856
3988
  if (opts.headless !== void 0) {
3857
3989
  if (platformConfig.browser) {
@@ -3970,10 +4102,12 @@ Config for: ${test.name}`));
3970
4102
  runId,
3971
4103
  artifact: artifactContext.artifact
3972
4104
  });
4105
+ const getAuthAwareSandboxOptions = () => sandboxOptions && activeTestAuthState ? { ...sandboxOptions, authState: activeTestAuthState } : sandboxOptions;
4106
+ const authAwareSandboxOptions = getAuthAwareSandboxOptions();
3973
4107
  let perTestSetupVars = {};
3974
4108
  let testStartTime = Date.now();
3975
4109
  let setupHookFailed = false;
3976
- if (resolvedHooks && sandboxOptions && testDef.setup?.length) {
4110
+ if (resolvedHooks && authAwareSandboxOptions && testDef.setup?.length) {
3977
4111
  const { runHooks } = await import("@vostride/agent-qa-core");
3978
4112
  const hookDefs = [];
3979
4113
  let hookSetupError;
@@ -3993,7 +4127,7 @@ Config for: ${test.name}`));
3993
4127
  for (const hookDef of hookDefs) {
3994
4128
  const hookExecId = randomUUID();
3995
4129
  await multiReporter.onHookStart({ hookId: hookDef.id, hookName: hookDef.name, phase: "setup", hookExecutionId: hookExecId, runId });
3996
- const hookResult = await runHooks([hookDef], { ...sandboxOptions, envVars: { ...sandboxOptions.envVars, ...allVars, ...perTestSetupVars } });
4130
+ const hookResult = await runHooks([hookDef], { ...authAwareSandboxOptions, envVars: { ...authAwareSandboxOptions.envVars, ...allVars, ...perTestSetupVars } });
3997
4131
  const hr = hookResult.results.get(hookDef.name);
3998
4132
  await multiReporter.onHookEnd({
3999
4133
  hookId: hookDef.id,
@@ -4080,7 +4214,7 @@ Config for: ${test.name}`));
4080
4214
  cliVars,
4081
4215
  hookSetupVars: perTestSetupVars,
4082
4216
  inlineHookDefs: resolvedHooks,
4083
- inlineHookSandboxOptions: sandboxOptions,
4217
+ inlineHookSandboxOptions: authAwareSandboxOptions,
4084
4218
  logCapture: testLogCapture,
4085
4219
  accessibility: config.services?.accessibility,
4086
4220
  accessibilityCheck: runWebAccessibilityCheck,
@@ -4091,11 +4225,28 @@ Config for: ${test.name}`));
4091
4225
  memoryInitParams,
4092
4226
  circuitBreaker,
4093
4227
  runId,
4094
- skipReporterOnTestStart: true
4228
+ skipReporterOnTestStart: true,
4229
+ skipReporterOnTestEnd: testAuthStateUse?.capture === true
4095
4230
  }, filePath);
4096
4231
  if (!result.runId) result.runId = runId;
4097
4232
  completedResult = result;
4098
- if (resolvedHooks && sandboxOptions && testDef.teardown?.length) {
4233
+ if (testAuthStateCapturePaths && result.status === "passed") {
4234
+ try {
4235
+ activeTestAuthState = await captureAuthStateForRun({
4236
+ adapter,
4237
+ paths: testAuthStateCapturePaths
4238
+ });
4239
+ } catch {
4240
+ result.status = "failed";
4241
+ result.failureSummary = formatAuthStateCaptureFailure(testAuthStateCapturePaths);
4242
+ result.metadata = {
4243
+ ...result.metadata,
4244
+ phase: "auth-state-capture"
4245
+ };
4246
+ }
4247
+ }
4248
+ const teardownSandboxOptions = getAuthAwareSandboxOptions();
4249
+ if (resolvedHooks && teardownSandboxOptions && testDef.teardown?.length) {
4099
4250
  const { runHooks } = await import("@vostride/agent-qa-core");
4100
4251
  for (const hookId of testDef.teardown) {
4101
4252
  const hook = resolvedHooks.get(hookId);
@@ -4105,7 +4256,7 @@ Config for: ${test.name}`));
4105
4256
  await multiReporter.onHookStart({ hookId: hook.id, hookName: hook.name, phase: "teardown", hookExecutionId: hookExecId, runId });
4106
4257
  const allVars = { ...envFileVars, ...perTestSetupVars };
4107
4258
  if (cliVars) Object.assign(allVars, cliVars);
4108
- const hookResult = await runHooks([hook], { ...sandboxOptions, envVars: { ...sandboxOptions.envVars, ...allVars } });
4259
+ const hookResult = await runHooks([hook], { ...teardownSandboxOptions, envVars: { ...teardownSandboxOptions.envVars, ...allVars } });
4109
4260
  const hr = hookResult.results.get(hook.name);
4110
4261
  await multiReporter.onHookEnd({
4111
4262
  hookId: hook.id,
@@ -4144,7 +4295,8 @@ Config for: ${test.name}`));
4144
4295
  };
4145
4296
  }
4146
4297
  let ablationHandledDeprecation = false;
4147
- if (memoryProvider && memoryConfig?.ablationEnabled !== false && result.status === "failed") {
4298
+ const authStateCaptureFailed = result.metadata?.phase === "auth-state-capture";
4299
+ if (memoryProvider && memoryConfig?.ablationEnabled !== false && result.status === "failed" && !authStateCaptureFailed) {
4148
4300
  try {
4149
4301
  const { shouldAblate, collectAllInjectedIds, deprecateOnFailure } = await import("@vostride/agent-qa-core");
4150
4302
  if (shouldAblate(result, memoryProvider)) {
@@ -4152,6 +4304,7 @@ Config for: ${test.name}`));
4152
4304
  const ablationAdapter = await createPlatformAdapter(testPlatform);
4153
4305
  const mergedBrowser = mergedUse.browser;
4154
4306
  const ablationPlatformConfig = buildPlatformConfig(testPlatform, testResolvedDevice, testTimeoutsForSetup, mergedBrowser ?? config.use?.browser, testMergedConfig.use?.logCapture ?? config.use?.logCapture, testFarmSession, testMobileResolved);
4307
+ ablationPlatformConfig.authState = activeTestAuthState;
4155
4308
  ablationPlatformConfig.verbose = effectiveLogLevel === "debug";
4156
4309
  if (opts.headless !== void 0 && ablationPlatformConfig.browser) {
4157
4310
  ablationPlatformConfig.browser.headless = opts.headless;
@@ -4183,7 +4336,7 @@ Config for: ${test.name}`));
4183
4336
  cliVars,
4184
4337
  hookSetupVars: perTestSetupVars,
4185
4338
  inlineHookDefs: resolvedHooks,
4186
- inlineHookSandboxOptions: sandboxOptions,
4339
+ inlineHookSandboxOptions: authAwareSandboxOptions,
4187
4340
  logCapture: testLogCapture,
4188
4341
  accessibility: config.services?.accessibility,
4189
4342
  accessibilityCheck: runWebAccessibilityCheck,
@@ -4250,6 +4403,9 @@ Config for: ${test.name}`));
4250
4403
  console.warn(` ${pc7.dim("Memory:")} curator error -- ${err.message}`);
4251
4404
  }
4252
4405
  }
4406
+ if (testAuthStateUse?.capture === true) {
4407
+ await multiReporter.onTestEnd(result);
4408
+ }
4253
4409
  results.push(result);
4254
4410
  printRunAttributes(testAttributes);
4255
4411
  if (circuitBreaker && memoryProvider) {
@@ -4260,7 +4416,7 @@ Config for: ${test.name}`));
4260
4416
  circuitBreakerTripped = true;
4261
4417
  console.log(` ${pc7.yellow("Memory: circuit breaker tripped -- disabling memory injection for remaining tests")}`);
4262
4418
  }
4263
- if (result.status === "failed" && actionCache && effectiveCacheEnabled && result.steps.length > 0) {
4419
+ if (result.status === "failed" && !authStateCaptureFailed && actionCache && effectiveCacheEnabled && result.steps.length > 0) {
4264
4420
  const { hashStepInstruction: hashStepInstruction2 } = await import("@vostride/agent-qa-core");
4265
4421
  const { rm: rm3 } = await import("fs/promises");
4266
4422
  const { resolve: resolve9, join: join8 } = await import("path");
@@ -4391,7 +4547,7 @@ ${errStack}`;
4391
4547
  status: finalStatus,
4392
4548
  duration: run.startedAt ? Date.now() - new Date(run.startedAt).getTime() : 0,
4393
4549
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
4394
- failureSummary: isBrowserClose ? "Browser closed by user" : formattedError.failureSummary
4550
+ failureSummary: isBrowserClose ? "Browser closed by user" : redactAuthStateValue(formattedError.failureSummary)
4395
4551
  });
4396
4552
  try {
4397
4553
  const artifact = _crashDb.getRunArtifact(run.id);
@@ -4404,7 +4560,7 @@ ${errStack}`;
4404
4560
  errors: [{
4405
4561
  code: isBrowserClose ? "browser-disconnect" : "process-close",
4406
4562
  phase: "process-close",
4407
- message: isBrowserClose ? "Browser closed by user" : formattedError.failureSummary
4563
+ message: isBrowserClose ? "Browser closed by user" : redactAuthStateValue(formattedError.failureSummary)
4408
4564
  }]
4409
4565
  });
4410
4566
  }
@@ -5199,37 +5355,267 @@ function createAuthCommand() {
5199
5355
  return cmd;
5200
5356
  }
5201
5357
 
5202
- // src/commands/devices.ts
5358
+ // src/commands/auth-state.ts
5203
5359
  init_config();
5360
+ import { createInterface as createInterface3 } from "readline/promises";
5361
+ import { dirname as dirname7, resolve as resolvePath4 } from "path";
5204
5362
  import { Command as Command13 } from "commander";
5363
+ import pc13 from "picocolors";
5364
+ import {
5365
+ AUTH_STATE_SCHEMA_VERSION as AUTH_STATE_SCHEMA_VERSION2,
5366
+ listAuthStateMetadata,
5367
+ readAuthStateMetadata,
5368
+ removeAuthStateFiles,
5369
+ removeAuthStateTarget,
5370
+ resolveAuthStatePaths as resolveAuthStatePaths2,
5371
+ writeAuthStateFiles
5372
+ } from "@vostride/agent-qa-core";
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 = createInterface3({ 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 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 = dirname7(resolvePath4(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(pc13.red(isSafeAuthStateError(message) ? message : fallback));
5431
+ }
5432
+ function assertWebAuthStateTarget(input3) {
5433
+ resolveAuthStatePaths2({
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 Command13("auth-state").description("Manage product auth state");
5460
+ cmd.addCommand(
5461
+ new Command13("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 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 Command13("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 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 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 Command13("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 = resolveAuthStatePaths2({
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(pc13.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(pc13.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 writeAuthStateFiles(paths, {
5561
+ payload,
5562
+ metadata: {
5563
+ version: AUTH_STATE_SCHEMA_VERSION2,
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(pc13.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(pc13.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
+ init_config();
5590
+ import { Command as Command14 } from "commander";
5205
5591
  import { execSync as execSync2 } from "child_process";
5206
5592
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
5207
5593
  import { join as join5 } from "path";
5208
5594
  import { stringify as stringify2 } from "yaml";
5209
- import pc13 from "picocolors";
5595
+ import pc14 from "picocolors";
5210
5596
  function formatStatus2(transport, localBinding) {
5211
5597
  if (transport !== "local") {
5212
- return pc13.green("Ready") + pc13.dim(" (no local binding required)");
5598
+ return pc14.green("Ready") + pc14.dim(" (no local binding required)");
5213
5599
  }
5214
5600
  if (!localBinding) {
5215
- return pc13.red("UNBOUND") + pc13.yellow(" - run `agent-qa devices init`");
5601
+ return pc14.red("UNBOUND") + pc14.yellow(" - run `agent-qa devices init`");
5216
5602
  }
5217
5603
  const keys = Object.entries(localBinding).slice(0, 2);
5218
5604
  const details = keys.map(([k, v]) => `${k}: ${v}`).join(", ");
5219
- return pc13.green("Bound") + pc13.dim(` (${details})`);
5605
+ return pc14.green("Bound") + pc14.dim(` (${details})`);
5220
5606
  }
5221
5607
  async function listDevices() {
5222
5608
  let config;
5223
5609
  try {
5224
5610
  config = await resolveConfig({});
5225
5611
  } catch (err) {
5226
- console.error(pc13.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
5612
+ console.error(pc14.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
5227
5613
  process.exitCode = 1;
5228
5614
  return;
5229
5615
  }
5230
5616
  const devices = config.registry?.devices;
5231
5617
  if (!devices || Object.keys(devices).length === 0) {
5232
- console.log(pc13.yellow("No devices configured in registry.devices"));
5618
+ console.log(pc14.yellow("No devices configured in registry.devices"));
5233
5619
  return;
5234
5620
  }
5235
5621
  const localBindings = loadLocalBindings();
@@ -5237,13 +5623,13 @@ async function listDevices() {
5237
5623
  const platWidth = 10;
5238
5624
  const transWidth = 14;
5239
5625
  console.log("");
5240
- console.log(pc13.bold(" Configured Devices"));
5626
+ console.log(pc14.bold(" Configured Devices"));
5241
5627
  console.log("");
5242
5628
  console.log(
5243
- ` ${pc13.dim("Name".padEnd(nameWidth))} ${pc13.dim("Platform".padEnd(platWidth))} ${pc13.dim("Transport".padEnd(transWidth))} ${pc13.dim("Status")}`
5629
+ ` ${pc14.dim("Name".padEnd(nameWidth))} ${pc14.dim("Platform".padEnd(platWidth))} ${pc14.dim("Transport".padEnd(transWidth))} ${pc14.dim("Status")}`
5244
5630
  );
5245
5631
  console.log(
5246
- ` ${pc13.dim("\u2500".repeat(nameWidth))} ${pc13.dim("\u2500".repeat(platWidth))} ${pc13.dim("\u2500".repeat(transWidth))} ${pc13.dim("\u2500".repeat(30))}`
5632
+ ` ${pc14.dim("\u2500".repeat(nameWidth))} ${pc14.dim("\u2500".repeat(platWidth))} ${pc14.dim("\u2500".repeat(transWidth))} ${pc14.dim("\u2500".repeat(30))}`
5247
5633
  );
5248
5634
  for (const [name, profile] of Object.entries(devices)) {
5249
5635
  const localBinding = localBindings?.devices?.[name] ?? null;
@@ -5307,28 +5693,28 @@ async function initDevices() {
5307
5693
  const dir = process.cwd();
5308
5694
  const localFilePath = join5(dir, "agent-qa.local.yaml");
5309
5695
  if (existsSync3(localFilePath)) {
5310
- console.log(pc13.yellow("agent-qa.local.yaml already exists. Skipping generation."));
5696
+ console.log(pc14.yellow("agent-qa.local.yaml already exists. Skipping generation."));
5311
5697
  return;
5312
5698
  }
5313
- console.log(pc13.bold("\n Scanning for connected devices...\n"));
5699
+ console.log(pc14.bold("\n Scanning for connected devices...\n"));
5314
5700
  const adbDevices = scanAdbDevices();
5315
5701
  const simctlDevices = scanSimctlDevices();
5316
5702
  if (adbDevices.length > 0) {
5317
- console.log(` ${pc13.green("Android:")} found ${adbDevices.length} device(s)`);
5703
+ console.log(` ${pc14.green("Android:")} found ${adbDevices.length} device(s)`);
5318
5704
  for (const d of adbDevices) {
5319
- console.log(` ${pc13.dim("\u2022")} ${d.model} (${d.serial})`);
5705
+ console.log(` ${pc14.dim("\u2022")} ${d.model} (${d.serial})`);
5320
5706
  }
5321
5707
  } else {
5322
- console.log(` ${pc13.dim("Android:")} no devices found (adb not available or no devices connected)`);
5708
+ console.log(` ${pc14.dim("Android:")} no devices found (adb not available or no devices connected)`);
5323
5709
  }
5324
5710
  if (simctlDevices.length > 0) {
5325
5711
  const booted = simctlDevices.filter((d) => d.state === "Booted");
5326
- console.log(` ${pc13.green("iOS:")} found ${simctlDevices.length} simulator(s) (${booted.length} booted)`);
5712
+ console.log(` ${pc14.green("iOS:")} found ${simctlDevices.length} simulator(s) (${booted.length} booted)`);
5327
5713
  for (const d of booted) {
5328
- console.log(` ${pc13.dim("\u2022")} ${d.name} (${d.udid})`);
5714
+ console.log(` ${pc14.dim("\u2022")} ${d.name} (${d.udid})`);
5329
5715
  }
5330
5716
  } else {
5331
- console.log(` ${pc13.dim("iOS:")} no simulators found (simctl not available or none installed)`);
5717
+ console.log(` ${pc14.dim("iOS:")} no simulators found (simctl not available or none installed)`);
5332
5718
  }
5333
5719
  let config;
5334
5720
  try {
@@ -5362,20 +5748,20 @@ async function initDevices() {
5362
5748
  writeFileSync2(localFilePath, yamlContent);
5363
5749
  appendGitignoreEntry(dir, "agent-qa.local.yaml");
5364
5750
  console.log("");
5365
- console.log(pc13.green(` Generated agent-qa.local.yaml with ${bindCount} device binding(s)`));
5366
- console.log(pc13.dim(" Added agent-qa.local.yaml to .gitignore"));
5751
+ console.log(pc14.green(` Generated agent-qa.local.yaml with ${bindCount} device binding(s)`));
5752
+ console.log(pc14.dim(" Added agent-qa.local.yaml to .gitignore"));
5367
5753
  console.log("");
5368
5754
  }
5369
5755
  function createDevicesCommand() {
5370
- const cmd = new Command13("devices").description("Manage device profiles and local bindings");
5756
+ const cmd = new Command14("devices").description("Manage device profiles and local bindings");
5371
5757
  cmd.command("list").description("Show configured devices with merged shared + local view").action(listDevices);
5372
5758
  cmd.command("init").description("Scan connected devices and generate agent-qa.local.yaml").action(initDevices);
5373
5759
  return cmd;
5374
5760
  }
5375
5761
 
5376
5762
  // src/commands/ids.ts
5377
- import { Command as Command14 } from "commander";
5378
- import pc14 from "picocolors";
5763
+ import { Command as Command15 } from "commander";
5764
+ import pc15 from "picocolors";
5379
5765
  import {
5380
5766
  ENTITY_ID_TYPES,
5381
5767
  generateCanonicalId,
@@ -5389,7 +5775,7 @@ function parseEntityType(value) {
5389
5775
  throw new Error(`Unknown ID type "${value}". Expected one of: ${ENTITY_ID_TYPES.join(", ")}`);
5390
5776
  }
5391
5777
  function createIdsCommand() {
5392
- const cmd = new Command14("ids").description("Generate and validate canonical agent-qa entity IDs");
5778
+ const cmd = new Command15("ids").description("Generate and validate canonical agent-qa entity IDs");
5393
5779
  cmd.command("generate").description("Generate a canonical agent-qa ID using id-agent").argument("<type>", `entity type: ${ENTITY_ID_TYPES.join("|")}`).option("--json", "print structured JSON").action((typeValue, opts) => {
5394
5780
  try {
5395
5781
  const type = parseEntityType(typeValue);
@@ -5401,7 +5787,7 @@ function createIdsCommand() {
5401
5787
  console.log(id);
5402
5788
  }
5403
5789
  } catch (err) {
5404
- console.error(pc14.red(err instanceof Error ? err.message : String(err)));
5790
+ console.error(pc15.red(err instanceof Error ? err.message : String(err)));
5405
5791
  process.exitCode = 1;
5406
5792
  }
5407
5793
  });
@@ -5420,15 +5806,15 @@ function createIdsCommand() {
5420
5806
  if (opts.json) {
5421
5807
  console.log(JSON.stringify(result, null, 2));
5422
5808
  } else if (valid) {
5423
- console.log(pc14.green(result.message));
5809
+ console.log(pc15.green(result.message));
5424
5810
  } else {
5425
- console.error(pc14.red(result.message));
5811
+ console.error(pc15.red(result.message));
5426
5812
  }
5427
5813
  if (!valid) {
5428
5814
  process.exitCode = 1;
5429
5815
  }
5430
5816
  } catch (err) {
5431
- console.error(pc14.red(err instanceof Error ? err.message : String(err)));
5817
+ console.error(pc15.red(err instanceof Error ? err.message : String(err)));
5432
5818
  process.exitCode = 1;
5433
5819
  }
5434
5820
  });
@@ -5436,18 +5822,18 @@ function createIdsCommand() {
5436
5822
  }
5437
5823
 
5438
5824
  // src/commands/create-test.ts
5439
- import { Command as Command15 } from "commander";
5825
+ import { Command as Command16 } from "commander";
5440
5826
  import { generateTestId } from "@vostride/agent-qa-ids";
5441
5827
  import { stringify as stringify3 } from "yaml";
5442
5828
  import { writeFile as writeFile2, mkdir } from "fs/promises";
5443
5829
  import { existsSync as existsSync4 } from "fs";
5444
- import { resolve as resolve6, dirname as dirname7 } from "path";
5445
- import pc15 from "picocolors";
5830
+ import { resolve as resolve6, dirname as dirname8 } from "path";
5831
+ import pc16 from "picocolors";
5446
5832
  function createCreateTestCommand() {
5447
- const cmd = new Command15("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) => {
5833
+ const cmd = new Command16("create-test").description("Scaffold a new test YAML file with auto-generated ID").argument("<path>", "output file path for the test YAML").action(async (path) => {
5448
5834
  const resolved = resolve6(path);
5449
5835
  if (existsSync4(resolved)) {
5450
- console.error(pc15.red("Error: File already exists: " + resolved));
5836
+ console.error(pc16.red("Error: File already exists: " + resolved));
5451
5837
  process.exitCode = 1;
5452
5838
  return;
5453
5839
  }
@@ -5459,27 +5845,27 @@ function createCreateTestCommand() {
5459
5845
  steps: ["Describe your first test step here"]
5460
5846
  };
5461
5847
  const yamlContent = stringify3(template);
5462
- await mkdir(dirname7(resolved), { recursive: true });
5848
+ await mkdir(dirname8(resolved), { recursive: true });
5463
5849
  await writeFile2(resolved, yamlContent, "utf-8");
5464
- console.log(pc15.green("Created " + resolved));
5465
- console.log(pc15.dim(" test-id: " + testId));
5850
+ console.log(pc16.green("Created " + resolved));
5851
+ console.log(pc16.dim(" test-id: " + testId));
5466
5852
  });
5467
5853
  return cmd;
5468
5854
  }
5469
5855
 
5470
5856
  // src/commands/create-suite.ts
5471
- import { Command as Command16 } from "commander";
5857
+ import { Command as Command17 } from "commander";
5472
5858
  import { generateSuiteId } from "@vostride/agent-qa-ids";
5473
5859
  import { stringify as stringify4 } from "yaml";
5474
5860
  import { writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
5475
5861
  import { existsSync as existsSync5 } from "fs";
5476
- import { resolve as resolve7, dirname as dirname8 } from "path";
5477
- import pc16 from "picocolors";
5862
+ import { resolve as resolve7, dirname as dirname9 } from "path";
5863
+ import pc17 from "picocolors";
5478
5864
  function createCreateSuiteCommand() {
5479
- const cmd = new Command16("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) => {
5865
+ const cmd = new Command17("create-suite").description("Scaffold a new suite YAML file with auto-generated ID").argument("<path>", "output file path for the suite YAML").action(async (path) => {
5480
5866
  const resolved = resolve7(path);
5481
5867
  if (existsSync5(resolved)) {
5482
- console.error(pc16.red("Error: File already exists: " + resolved));
5868
+ console.error(pc17.red("Error: File already exists: " + resolved));
5483
5869
  process.exitCode = 1;
5484
5870
  return;
5485
5871
  }
@@ -5493,19 +5879,19 @@ function createCreateSuiteCommand() {
5493
5879
  "tests: []",
5494
5880
  "tests: []\n # - test: ./path/to/test.yaml\n # id: my-test-id"
5495
5881
  );
5496
- await mkdir2(dirname8(resolved), { recursive: true });
5882
+ await mkdir2(dirname9(resolved), { recursive: true });
5497
5883
  await writeFile3(resolved, yamlContent, "utf-8");
5498
- console.log(pc16.green("Created " + resolved));
5499
- console.log(pc16.dim(" suite-id: " + suiteId));
5884
+ console.log(pc17.green("Created " + resolved));
5885
+ console.log(pc17.dim(" suite-id: " + suiteId));
5500
5886
  });
5501
5887
  return cmd;
5502
5888
  }
5503
5889
 
5504
5890
  // src/commands/clean-memory.ts
5505
5891
  init_config();
5506
- import { Command as Command17 } from "commander";
5507
- import pc17 from "picocolors";
5508
- import { createInterface as createInterface3 } from "readline";
5892
+ import { Command as Command18 } from "commander";
5893
+ import pc18 from "picocolors";
5894
+ import { createInterface as createInterface4 } from "readline";
5509
5895
  import { readFile as readFile4, readdir as readdir2, rm as rm2, stat as stat2 } from "fs/promises";
5510
5896
  import { existsSync as existsSync6 } from "fs";
5511
5897
  import { join as join6 } from "path";
@@ -5561,7 +5947,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
5561
5947
  };
5562
5948
  }
5563
5949
  function createCleanMemoryCommand() {
5564
- const cmd = new Command17("clean-memory").description("Remove orphaned memory observation directories").option("-y, --yes", "skip confirmation prompt").action(async (_opts, command) => {
5950
+ const cmd = new Command18("clean-memory").description("Remove orphaned memory observation directories").option("-y, --yes", "skip confirmation prompt").action(async (_opts, command) => {
5565
5951
  const opts = command.opts();
5566
5952
  const configPath = command.parent?.opts()?.config ?? "agent-qa.config.yaml";
5567
5953
  const config = await resolveConfig({ configPath });
@@ -5594,7 +5980,7 @@ function createCleanMemoryCommand() {
5594
5980
  }
5595
5981
  const memoryRoot = resolveMemoryRoot2(config, workspace.configDir);
5596
5982
  if (!existsSync6(memoryRoot)) {
5597
- console.log(pc17.dim("No memory directory found."));
5983
+ console.log(pc18.dim("No memory directory found."));
5598
5984
  return;
5599
5985
  }
5600
5986
  const TIERS = [
@@ -5624,22 +6010,22 @@ function createCleanMemoryCommand() {
5624
6010
  }
5625
6011
  }
5626
6012
  if (orphans.length === 0) {
5627
- console.log(pc17.dim("No orphaned memory directories found."));
6013
+ console.log(pc18.dim("No orphaned memory directories found."));
5628
6014
  return;
5629
6015
  }
5630
6016
  const maxPathLen = Math.max("Path".length, ...orphans.map((o) => o.path.length));
5631
6017
  console.log("");
5632
- console.log(pc17.bold("Orphaned memory directories:"));
6018
+ console.log(pc18.bold("Orphaned memory directories:"));
5633
6019
  console.log("");
5634
- console.log(" " + pc17.dim("Path".padEnd(maxPathLen + 2) + "Files".padStart(7) + " " + "Last Confirmed".padEnd(20) + "Size".padStart(10)));
5635
- console.log(" " + pc17.dim("-".repeat(maxPathLen + 2 + 7 + 2 + 20 + 10)));
6020
+ console.log(" " + pc18.dim("Path".padEnd(maxPathLen + 2) + "Files".padStart(7) + " " + "Last Confirmed".padEnd(20) + "Size".padStart(10)));
6021
+ console.log(" " + pc18.dim("-".repeat(maxPathLen + 2 + 7 + 2 + 20 + 10)));
5636
6022
  for (const o of orphans) {
5637
- const confirmed = o.lastConfirmed ?? pc17.dim("never");
6023
+ const confirmed = o.lastConfirmed ?? pc18.dim("never");
5638
6024
  console.log(" " + o.path.padEnd(maxPathLen + 2) + String(o.fileCount).padStart(7) + " " + String(confirmed).padEnd(20) + formatSize(o.size).padStart(10));
5639
6025
  }
5640
6026
  console.log("");
5641
6027
  if (!opts.yes) {
5642
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
6028
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
5643
6029
  const answer = await new Promise((res) => {
5644
6030
  rl.question(`Delete ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}? (y/N) `, res);
5645
6031
  });
@@ -5652,15 +6038,15 @@ function createCleanMemoryCommand() {
5652
6038
  for (const orphan of orphans) {
5653
6039
  await rm2(join6(memoryRoot, orphan.tier, orphan.scope), { recursive: true, force: true });
5654
6040
  }
5655
- console.log(pc17.green(`Deleted ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}.`));
6041
+ console.log(pc18.green(`Deleted ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}.`));
5656
6042
  });
5657
6043
  return cmd;
5658
6044
  }
5659
6045
 
5660
6046
  // src/commands/skills.ts
5661
- import { Command as Command18 } from "commander";
6047
+ import { Command as Command19 } from "commander";
5662
6048
  import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync4, statSync } from "fs";
5663
- import { dirname as dirname9, join as join7, resolve as resolve8 } from "path";
6049
+ import { dirname as dirname10, join as join7, resolve as resolve8 } from "path";
5664
6050
  import { fileURLToPath } from "url";
5665
6051
  function findPackageRoot(startDir) {
5666
6052
  let current = resolve8(startDir);
@@ -5675,7 +6061,7 @@ function findPackageRoot(startDir) {
5675
6061
  } catch {
5676
6062
  }
5677
6063
  }
5678
- const parent = dirname9(current);
6064
+ const parent = dirname10(current);
5679
6065
  if (parent === current) {
5680
6066
  return resolve8(startDir);
5681
6067
  }
@@ -5693,7 +6079,7 @@ function hasSkillDirectories(dir) {
5693
6079
  return false;
5694
6080
  }
5695
6081
  }
5696
- function resolveAgentQaSkillsDirectory(startDir = dirname9(fileURLToPath(import.meta.url))) {
6082
+ function resolveAgentQaSkillsDirectory(startDir = dirname10(fileURLToPath(import.meta.url))) {
5697
6083
  const packageRoot = findPackageRoot(startDir);
5698
6084
  const packagedSkills = join7(packageRoot, "skills");
5699
6085
  if (hasSkillDirectories(packagedSkills)) {
@@ -5727,7 +6113,7 @@ function listAgentQaSkills(skillsDir) {
5727
6113
  function createSkillsCommand(options = {}) {
5728
6114
  const stdout = options.stdout ?? process.stdout;
5729
6115
  const stderr = options.stderr ?? process.stderr;
5730
- return new Command18("skills").description("List packaged agent-qa skills").option("--json", "print machine-readable output").action((opts) => {
6116
+ return new Command19("skills").description("List packaged agent-qa skills").option("--json", "print machine-readable output").action((opts) => {
5731
6117
  const resolved = resolveAgentQaSkillsDirectory(options.startDir);
5732
6118
  const skills = listAgentQaSkills(resolved.path);
5733
6119
  if (resolved.source === "missing" || skills.length === 0) {
@@ -5808,7 +6194,7 @@ function rejectRemovedCliSurface(input3 = {}) {
5808
6194
 
5809
6195
  // src/cli.ts
5810
6196
  rejectRemovedCliSurface();
5811
- var program = new Command19();
6197
+ var program = new Command20();
5812
6198
  program.enablePositionalOptions();
5813
6199
  program.name("agent-qa").description("AI-powered testing framework").version(VERSION);
5814
6200
  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");
@@ -5825,6 +6211,7 @@ program.addCommand(createQueueCommand());
5825
6211
  program.addCommand(createCacheCommand());
5826
6212
  program.addCommand(createValidateCommand());
5827
6213
  program.addCommand(createAuthCommand());
6214
+ program.addCommand(createAuthStateCommand());
5828
6215
  program.addCommand(createDevicesCommand());
5829
6216
  program.addCommand(createIdsCommand());
5830
6217
  program.addCommand(createCreateTestCommand());