agent-qa 0.1.14 → 0.1.15

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