agent-qa 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
  }
@@ -4560,7 +4716,8 @@ function createMcpCommand() {
4560
4716
  }
4561
4717
  const { startMcpServer } = await import("@vostride/agent-qa-mcp");
4562
4718
  try {
4563
- await startMcpServer({ analyticsConfig: config });
4719
+ const startupOptions = { analyticsConfig: config, startupOutput: process.stderr };
4720
+ await startMcpServer(startupOptions);
4564
4721
  } finally {
4565
4722
  await flushAnalytics2(config ? { config } : void 0).catch(() => {
4566
4723
  });
@@ -5198,37 +5355,267 @@ function createAuthCommand() {
5198
5355
  return cmd;
5199
5356
  }
5200
5357
 
5201
- // src/commands/devices.ts
5358
+ // src/commands/auth-state.ts
5202
5359
  init_config();
5360
+ import { createInterface as createInterface3 } from "readline/promises";
5361
+ import { dirname as dirname7, resolve as resolvePath4 } from "path";
5203
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";
5204
5591
  import { execSync as execSync2 } from "child_process";
5205
5592
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
5206
5593
  import { join as join5 } from "path";
5207
5594
  import { stringify as stringify2 } from "yaml";
5208
- import pc13 from "picocolors";
5595
+ import pc14 from "picocolors";
5209
5596
  function formatStatus2(transport, localBinding) {
5210
5597
  if (transport !== "local") {
5211
- return pc13.green("Ready") + pc13.dim(" (no local binding required)");
5598
+ return pc14.green("Ready") + pc14.dim(" (no local binding required)");
5212
5599
  }
5213
5600
  if (!localBinding) {
5214
- return pc13.red("UNBOUND") + pc13.yellow(" - run `agent-qa devices init`");
5601
+ return pc14.red("UNBOUND") + pc14.yellow(" - run `agent-qa devices init`");
5215
5602
  }
5216
5603
  const keys = Object.entries(localBinding).slice(0, 2);
5217
5604
  const details = keys.map(([k, v]) => `${k}: ${v}`).join(", ");
5218
- return pc13.green("Bound") + pc13.dim(` (${details})`);
5605
+ return pc14.green("Bound") + pc14.dim(` (${details})`);
5219
5606
  }
5220
5607
  async function listDevices() {
5221
5608
  let config;
5222
5609
  try {
5223
5610
  config = await resolveConfig({});
5224
5611
  } catch (err) {
5225
- 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)}`));
5226
5613
  process.exitCode = 1;
5227
5614
  return;
5228
5615
  }
5229
5616
  const devices = config.registry?.devices;
5230
5617
  if (!devices || Object.keys(devices).length === 0) {
5231
- console.log(pc13.yellow("No devices configured in registry.devices"));
5618
+ console.log(pc14.yellow("No devices configured in registry.devices"));
5232
5619
  return;
5233
5620
  }
5234
5621
  const localBindings = loadLocalBindings();
@@ -5236,13 +5623,13 @@ async function listDevices() {
5236
5623
  const platWidth = 10;
5237
5624
  const transWidth = 14;
5238
5625
  console.log("");
5239
- console.log(pc13.bold(" Configured Devices"));
5626
+ console.log(pc14.bold(" Configured Devices"));
5240
5627
  console.log("");
5241
5628
  console.log(
5242
- ` ${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")}`
5243
5630
  );
5244
5631
  console.log(
5245
- ` ${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))}`
5246
5633
  );
5247
5634
  for (const [name, profile] of Object.entries(devices)) {
5248
5635
  const localBinding = localBindings?.devices?.[name] ?? null;
@@ -5306,28 +5693,28 @@ async function initDevices() {
5306
5693
  const dir = process.cwd();
5307
5694
  const localFilePath = join5(dir, "agent-qa.local.yaml");
5308
5695
  if (existsSync3(localFilePath)) {
5309
- 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."));
5310
5697
  return;
5311
5698
  }
5312
- console.log(pc13.bold("\n Scanning for connected devices...\n"));
5699
+ console.log(pc14.bold("\n Scanning for connected devices...\n"));
5313
5700
  const adbDevices = scanAdbDevices();
5314
5701
  const simctlDevices = scanSimctlDevices();
5315
5702
  if (adbDevices.length > 0) {
5316
- console.log(` ${pc13.green("Android:")} found ${adbDevices.length} device(s)`);
5703
+ console.log(` ${pc14.green("Android:")} found ${adbDevices.length} device(s)`);
5317
5704
  for (const d of adbDevices) {
5318
- console.log(` ${pc13.dim("\u2022")} ${d.model} (${d.serial})`);
5705
+ console.log(` ${pc14.dim("\u2022")} ${d.model} (${d.serial})`);
5319
5706
  }
5320
5707
  } else {
5321
- 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)`);
5322
5709
  }
5323
5710
  if (simctlDevices.length > 0) {
5324
5711
  const booted = simctlDevices.filter((d) => d.state === "Booted");
5325
- 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)`);
5326
5713
  for (const d of booted) {
5327
- console.log(` ${pc13.dim("\u2022")} ${d.name} (${d.udid})`);
5714
+ console.log(` ${pc14.dim("\u2022")} ${d.name} (${d.udid})`);
5328
5715
  }
5329
5716
  } else {
5330
- 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)`);
5331
5718
  }
5332
5719
  let config;
5333
5720
  try {
@@ -5361,20 +5748,20 @@ async function initDevices() {
5361
5748
  writeFileSync2(localFilePath, yamlContent);
5362
5749
  appendGitignoreEntry(dir, "agent-qa.local.yaml");
5363
5750
  console.log("");
5364
- console.log(pc13.green(` Generated agent-qa.local.yaml with ${bindCount} device binding(s)`));
5365
- 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"));
5366
5753
  console.log("");
5367
5754
  }
5368
5755
  function createDevicesCommand() {
5369
- 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");
5370
5757
  cmd.command("list").description("Show configured devices with merged shared + local view").action(listDevices);
5371
5758
  cmd.command("init").description("Scan connected devices and generate agent-qa.local.yaml").action(initDevices);
5372
5759
  return cmd;
5373
5760
  }
5374
5761
 
5375
5762
  // src/commands/ids.ts
5376
- import { Command as Command14 } from "commander";
5377
- import pc14 from "picocolors";
5763
+ import { Command as Command15 } from "commander";
5764
+ import pc15 from "picocolors";
5378
5765
  import {
5379
5766
  ENTITY_ID_TYPES,
5380
5767
  generateCanonicalId,
@@ -5388,7 +5775,7 @@ function parseEntityType(value) {
5388
5775
  throw new Error(`Unknown ID type "${value}". Expected one of: ${ENTITY_ID_TYPES.join(", ")}`);
5389
5776
  }
5390
5777
  function createIdsCommand() {
5391
- 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");
5392
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) => {
5393
5780
  try {
5394
5781
  const type = parseEntityType(typeValue);
@@ -5400,7 +5787,7 @@ function createIdsCommand() {
5400
5787
  console.log(id);
5401
5788
  }
5402
5789
  } catch (err) {
5403
- console.error(pc14.red(err instanceof Error ? err.message : String(err)));
5790
+ console.error(pc15.red(err instanceof Error ? err.message : String(err)));
5404
5791
  process.exitCode = 1;
5405
5792
  }
5406
5793
  });
@@ -5419,15 +5806,15 @@ function createIdsCommand() {
5419
5806
  if (opts.json) {
5420
5807
  console.log(JSON.stringify(result, null, 2));
5421
5808
  } else if (valid) {
5422
- console.log(pc14.green(result.message));
5809
+ console.log(pc15.green(result.message));
5423
5810
  } else {
5424
- console.error(pc14.red(result.message));
5811
+ console.error(pc15.red(result.message));
5425
5812
  }
5426
5813
  if (!valid) {
5427
5814
  process.exitCode = 1;
5428
5815
  }
5429
5816
  } catch (err) {
5430
- console.error(pc14.red(err instanceof Error ? err.message : String(err)));
5817
+ console.error(pc15.red(err instanceof Error ? err.message : String(err)));
5431
5818
  process.exitCode = 1;
5432
5819
  }
5433
5820
  });
@@ -5435,18 +5822,18 @@ function createIdsCommand() {
5435
5822
  }
5436
5823
 
5437
5824
  // src/commands/create-test.ts
5438
- import { Command as Command15 } from "commander";
5825
+ import { Command as Command16 } from "commander";
5439
5826
  import { generateTestId } from "@vostride/agent-qa-ids";
5440
5827
  import { stringify as stringify3 } from "yaml";
5441
5828
  import { writeFile as writeFile2, mkdir } from "fs/promises";
5442
5829
  import { existsSync as existsSync4 } from "fs";
5443
- import { resolve as resolve6, dirname as dirname7 } from "path";
5444
- import pc15 from "picocolors";
5830
+ import { resolve as resolve6, dirname as dirname8 } from "path";
5831
+ import pc16 from "picocolors";
5445
5832
  function createCreateTestCommand() {
5446
- 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) => {
5447
5834
  const resolved = resolve6(path);
5448
5835
  if (existsSync4(resolved)) {
5449
- console.error(pc15.red("Error: File already exists: " + resolved));
5836
+ console.error(pc16.red("Error: File already exists: " + resolved));
5450
5837
  process.exitCode = 1;
5451
5838
  return;
5452
5839
  }
@@ -5458,27 +5845,27 @@ function createCreateTestCommand() {
5458
5845
  steps: ["Describe your first test step here"]
5459
5846
  };
5460
5847
  const yamlContent = stringify3(template);
5461
- await mkdir(dirname7(resolved), { recursive: true });
5848
+ await mkdir(dirname8(resolved), { recursive: true });
5462
5849
  await writeFile2(resolved, yamlContent, "utf-8");
5463
- console.log(pc15.green("Created " + resolved));
5464
- console.log(pc15.dim(" test-id: " + testId));
5850
+ console.log(pc16.green("Created " + resolved));
5851
+ console.log(pc16.dim(" test-id: " + testId));
5465
5852
  });
5466
5853
  return cmd;
5467
5854
  }
5468
5855
 
5469
5856
  // src/commands/create-suite.ts
5470
- import { Command as Command16 } from "commander";
5857
+ import { Command as Command17 } from "commander";
5471
5858
  import { generateSuiteId } from "@vostride/agent-qa-ids";
5472
5859
  import { stringify as stringify4 } from "yaml";
5473
5860
  import { writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
5474
5861
  import { existsSync as existsSync5 } from "fs";
5475
- import { resolve as resolve7, dirname as dirname8 } from "path";
5476
- import pc16 from "picocolors";
5862
+ import { resolve as resolve7, dirname as dirname9 } from "path";
5863
+ import pc17 from "picocolors";
5477
5864
  function createCreateSuiteCommand() {
5478
- 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) => {
5479
5866
  const resolved = resolve7(path);
5480
5867
  if (existsSync5(resolved)) {
5481
- console.error(pc16.red("Error: File already exists: " + resolved));
5868
+ console.error(pc17.red("Error: File already exists: " + resolved));
5482
5869
  process.exitCode = 1;
5483
5870
  return;
5484
5871
  }
@@ -5492,19 +5879,19 @@ function createCreateSuiteCommand() {
5492
5879
  "tests: []",
5493
5880
  "tests: []\n # - test: ./path/to/test.yaml\n # id: my-test-id"
5494
5881
  );
5495
- await mkdir2(dirname8(resolved), { recursive: true });
5882
+ await mkdir2(dirname9(resolved), { recursive: true });
5496
5883
  await writeFile3(resolved, yamlContent, "utf-8");
5497
- console.log(pc16.green("Created " + resolved));
5498
- console.log(pc16.dim(" suite-id: " + suiteId));
5884
+ console.log(pc17.green("Created " + resolved));
5885
+ console.log(pc17.dim(" suite-id: " + suiteId));
5499
5886
  });
5500
5887
  return cmd;
5501
5888
  }
5502
5889
 
5503
5890
  // src/commands/clean-memory.ts
5504
5891
  init_config();
5505
- import { Command as Command17 } from "commander";
5506
- import pc17 from "picocolors";
5507
- 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";
5508
5895
  import { readFile as readFile4, readdir as readdir2, rm as rm2, stat as stat2 } from "fs/promises";
5509
5896
  import { existsSync as existsSync6 } from "fs";
5510
5897
  import { join as join6 } from "path";
@@ -5560,7 +5947,7 @@ async function getOrphanInfo(memoryRoot, tier, scope) {
5560
5947
  };
5561
5948
  }
5562
5949
  function createCleanMemoryCommand() {
5563
- 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) => {
5564
5951
  const opts = command.opts();
5565
5952
  const configPath = command.parent?.opts()?.config ?? "agent-qa.config.yaml";
5566
5953
  const config = await resolveConfig({ configPath });
@@ -5593,7 +5980,7 @@ function createCleanMemoryCommand() {
5593
5980
  }
5594
5981
  const memoryRoot = resolveMemoryRoot2(config, workspace.configDir);
5595
5982
  if (!existsSync6(memoryRoot)) {
5596
- console.log(pc17.dim("No memory directory found."));
5983
+ console.log(pc18.dim("No memory directory found."));
5597
5984
  return;
5598
5985
  }
5599
5986
  const TIERS = [
@@ -5623,22 +6010,22 @@ function createCleanMemoryCommand() {
5623
6010
  }
5624
6011
  }
5625
6012
  if (orphans.length === 0) {
5626
- console.log(pc17.dim("No orphaned memory directories found."));
6013
+ console.log(pc18.dim("No orphaned memory directories found."));
5627
6014
  return;
5628
6015
  }
5629
6016
  const maxPathLen = Math.max("Path".length, ...orphans.map((o) => o.path.length));
5630
6017
  console.log("");
5631
- console.log(pc17.bold("Orphaned memory directories:"));
6018
+ console.log(pc18.bold("Orphaned memory directories:"));
5632
6019
  console.log("");
5633
- console.log(" " + pc17.dim("Path".padEnd(maxPathLen + 2) + "Files".padStart(7) + " " + "Last Confirmed".padEnd(20) + "Size".padStart(10)));
5634
- 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)));
5635
6022
  for (const o of orphans) {
5636
- const confirmed = o.lastConfirmed ?? pc17.dim("never");
6023
+ const confirmed = o.lastConfirmed ?? pc18.dim("never");
5637
6024
  console.log(" " + o.path.padEnd(maxPathLen + 2) + String(o.fileCount).padStart(7) + " " + String(confirmed).padEnd(20) + formatSize(o.size).padStart(10));
5638
6025
  }
5639
6026
  console.log("");
5640
6027
  if (!opts.yes) {
5641
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
6028
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
5642
6029
  const answer = await new Promise((res) => {
5643
6030
  rl.question(`Delete ${orphans.length} orphaned director${orphans.length === 1 ? "y" : "ies"}? (y/N) `, res);
5644
6031
  });
@@ -5651,15 +6038,15 @@ function createCleanMemoryCommand() {
5651
6038
  for (const orphan of orphans) {
5652
6039
  await rm2(join6(memoryRoot, orphan.tier, orphan.scope), { recursive: true, force: true });
5653
6040
  }
5654
- 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"}.`));
5655
6042
  });
5656
6043
  return cmd;
5657
6044
  }
5658
6045
 
5659
6046
  // src/commands/skills.ts
5660
- import { Command as Command18 } from "commander";
6047
+ import { Command as Command19 } from "commander";
5661
6048
  import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync4, statSync } from "fs";
5662
- 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";
5663
6050
  import { fileURLToPath } from "url";
5664
6051
  function findPackageRoot(startDir) {
5665
6052
  let current = resolve8(startDir);
@@ -5674,7 +6061,7 @@ function findPackageRoot(startDir) {
5674
6061
  } catch {
5675
6062
  }
5676
6063
  }
5677
- const parent = dirname9(current);
6064
+ const parent = dirname10(current);
5678
6065
  if (parent === current) {
5679
6066
  return resolve8(startDir);
5680
6067
  }
@@ -5692,7 +6079,7 @@ function hasSkillDirectories(dir) {
5692
6079
  return false;
5693
6080
  }
5694
6081
  }
5695
- function resolveAgentQaSkillsDirectory(startDir = dirname9(fileURLToPath(import.meta.url))) {
6082
+ function resolveAgentQaSkillsDirectory(startDir = dirname10(fileURLToPath(import.meta.url))) {
5696
6083
  const packageRoot = findPackageRoot(startDir);
5697
6084
  const packagedSkills = join7(packageRoot, "skills");
5698
6085
  if (hasSkillDirectories(packagedSkills)) {
@@ -5726,7 +6113,7 @@ function listAgentQaSkills(skillsDir) {
5726
6113
  function createSkillsCommand(options = {}) {
5727
6114
  const stdout = options.stdout ?? process.stdout;
5728
6115
  const stderr = options.stderr ?? process.stderr;
5729
- 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) => {
5730
6117
  const resolved = resolveAgentQaSkillsDirectory(options.startDir);
5731
6118
  const skills = listAgentQaSkills(resolved.path);
5732
6119
  if (resolved.source === "missing" || skills.length === 0) {
@@ -5807,7 +6194,7 @@ function rejectRemovedCliSurface(input3 = {}) {
5807
6194
 
5808
6195
  // src/cli.ts
5809
6196
  rejectRemovedCliSurface();
5810
- var program = new Command19();
6197
+ var program = new Command20();
5811
6198
  program.enablePositionalOptions();
5812
6199
  program.name("agent-qa").description("AI-powered testing framework").version(VERSION);
5813
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");
@@ -5824,6 +6211,7 @@ program.addCommand(createQueueCommand());
5824
6211
  program.addCommand(createCacheCommand());
5825
6212
  program.addCommand(createValidateCommand());
5826
6213
  program.addCommand(createAuthCommand());
6214
+ program.addCommand(createAuthStateCommand());
5827
6215
  program.addCommand(createDevicesCommand());
5828
6216
  program.addCommand(createIdsCommand());
5829
6217
  program.addCommand(createCreateTestCommand());