@drip-apex/apex-cli 0.2.1 → 0.3.0

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
@@ -1159,8 +1159,8 @@ var require_command = __commonJS({
1159
1159
  "node_modules/commander/lib/command.js"(exports2) {
1160
1160
  var EventEmitter = require("node:events").EventEmitter;
1161
1161
  var childProcess2 = require("node:child_process");
1162
- var path20 = require("node:path");
1163
- var fs24 = require("node:fs");
1162
+ var path21 = require("node:path");
1163
+ var fs25 = require("node:fs");
1164
1164
  var process16 = require("node:process");
1165
1165
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1166
1166
  var { CommanderError: CommanderError2 } = require_error();
@@ -2141,7 +2141,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2141
2141
  * @param {string} subcommandName
2142
2142
  */
2143
2143
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2144
- if (fs24.existsSync(executableFile)) return;
2144
+ if (fs25.existsSync(executableFile)) return;
2145
2145
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2146
2146
  const executableMissing = `'${executableFile}' does not exist
2147
2147
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2159,11 +2159,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2159
2159
  let launchWithNode = false;
2160
2160
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2161
2161
  function findFile(baseDir, baseName) {
2162
- const localBin = path20.resolve(baseDir, baseName);
2163
- if (fs24.existsSync(localBin)) return localBin;
2164
- if (sourceExt.includes(path20.extname(baseName))) return void 0;
2162
+ const localBin = path21.resolve(baseDir, baseName);
2163
+ if (fs25.existsSync(localBin)) return localBin;
2164
+ if (sourceExt.includes(path21.extname(baseName))) return void 0;
2165
2165
  const foundExt = sourceExt.find(
2166
- (ext) => fs24.existsSync(`${localBin}${ext}`)
2166
+ (ext) => fs25.existsSync(`${localBin}${ext}`)
2167
2167
  );
2168
2168
  if (foundExt) return `${localBin}${foundExt}`;
2169
2169
  return void 0;
@@ -2175,21 +2175,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2175
2175
  if (this._scriptPath) {
2176
2176
  let resolvedScriptPath;
2177
2177
  try {
2178
- resolvedScriptPath = fs24.realpathSync(this._scriptPath);
2178
+ resolvedScriptPath = fs25.realpathSync(this._scriptPath);
2179
2179
  } catch {
2180
2180
  resolvedScriptPath = this._scriptPath;
2181
2181
  }
2182
- executableDir = path20.resolve(
2183
- path20.dirname(resolvedScriptPath),
2182
+ executableDir = path21.resolve(
2183
+ path21.dirname(resolvedScriptPath),
2184
2184
  executableDir
2185
2185
  );
2186
2186
  }
2187
2187
  if (executableDir) {
2188
2188
  let localFile = findFile(executableDir, executableFile);
2189
2189
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2190
- const legacyName = path20.basename(
2190
+ const legacyName = path21.basename(
2191
2191
  this._scriptPath,
2192
- path20.extname(this._scriptPath)
2192
+ path21.extname(this._scriptPath)
2193
2193
  );
2194
2194
  if (legacyName !== this._name) {
2195
2195
  localFile = findFile(
@@ -2200,7 +2200,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2200
2200
  }
2201
2201
  executableFile = localFile || executableFile;
2202
2202
  }
2203
- launchWithNode = sourceExt.includes(path20.extname(executableFile));
2203
+ launchWithNode = sourceExt.includes(path21.extname(executableFile));
2204
2204
  let proc;
2205
2205
  if (process16.platform !== "win32") {
2206
2206
  if (launchWithNode) {
@@ -3047,7 +3047,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3047
3047
  * @return {Command}
3048
3048
  */
3049
3049
  nameFromFilename(filename) {
3050
- this._name = path20.basename(filename, path20.extname(filename));
3050
+ this._name = path21.basename(filename, path21.extname(filename));
3051
3051
  return this;
3052
3052
  }
3053
3053
  /**
@@ -3061,9 +3061,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3061
3061
  * @param {string} [path]
3062
3062
  * @return {(string|null|Command)}
3063
3063
  */
3064
- executableDir(path21) {
3065
- if (path21 === void 0) return this._executableDir;
3066
- this._executableDir = path21;
3064
+ executableDir(path22) {
3065
+ if (path22 === void 0) return this._executableDir;
3066
+ this._executableDir = path22;
3067
3067
  return this;
3068
3068
  }
3069
3069
  /**
@@ -4588,15 +4588,15 @@ var init_mjs = __esm({
4588
4588
  this.listeners[ev].push(fn);
4589
4589
  }
4590
4590
  removeListener(ev, fn) {
4591
- const list12 = this.listeners[ev];
4592
- const i = list12.indexOf(fn);
4591
+ const list15 = this.listeners[ev];
4592
+ const i = list15.indexOf(fn);
4593
4593
  if (i === -1) {
4594
4594
  return;
4595
4595
  }
4596
- if (i === 0 && list12.length === 1) {
4597
- list12.length = 0;
4596
+ if (i === 0 && list15.length === 1) {
4597
+ list15.length = 0;
4598
4598
  } else {
4599
- list12.splice(i, 1);
4599
+ list15.splice(i, 1);
4600
4600
  }
4601
4601
  }
4602
4602
  emit(ev, code, signal) {
@@ -6743,16 +6743,16 @@ __export(ora_exports, {
6743
6743
  function ora(options) {
6744
6744
  return new Ora(options);
6745
6745
  }
6746
- async function oraPromise(action25, options) {
6747
- const actionIsFunction = typeof action25 === "function";
6748
- const actionIsPromise = typeof action25.then === "function";
6746
+ async function oraPromise(action68, options) {
6747
+ const actionIsFunction = typeof action68 === "function";
6748
+ const actionIsPromise = typeof action68.then === "function";
6749
6749
  if (!actionIsFunction && !actionIsPromise) {
6750
6750
  throw new TypeError("Parameter `action` must be a Function or a Promise");
6751
6751
  }
6752
6752
  const { successText, failText } = typeof options === "object" ? options : { successText: void 0, failText: void 0 };
6753
6753
  const spinner = ora(options).start();
6754
6754
  try {
6755
- const promise = actionIsFunction ? action25(spinner) : action25;
6755
+ const promise = actionIsFunction ? action68(spinner) : action68;
6756
6756
  const result = await promise;
6757
6757
  spinner.succeed(
6758
6758
  successText === void 0 ? void 0 : typeof successText === "string" ? successText : successText(result)
@@ -7126,6 +7126,7 @@ var commandName = "apex";
7126
7126
  var wroteEnvelope = false;
7127
7127
  var stdoutLines = [];
7128
7128
  var originalConsoleLog = null;
7129
+ var deprecationMarker = null;
7129
7130
  function findPackageVersion() {
7130
7131
  const candidates = [
7131
7132
  import_node_path.default.resolve(__dirname, "../package.json"),
@@ -7182,12 +7183,16 @@ function restoreConsoleLog() {
7182
7183
  console.log = originalConsoleLog;
7183
7184
  originalConsoleLog = null;
7184
7185
  }
7186
+ function markDeprecatedInvocation(marker) {
7187
+ deprecationMarker = marker;
7188
+ }
7185
7189
  function writeEnvelope(data, command = commandName) {
7186
7190
  const envelope = {
7187
7191
  ok: true,
7188
7192
  version: CLI_VERSION,
7189
7193
  command,
7190
- data
7194
+ data,
7195
+ ...deprecationMarker ? { deprecated: deprecationMarker } : {}
7191
7196
  };
7192
7197
  wroteEnvelope = true;
7193
7198
  restoreConsoleLog();
@@ -7359,6 +7364,8 @@ function findConfigPath() {
7359
7364
  var import_node_crypto = require("node:crypto");
7360
7365
  var mockEnvironmentPatchAttempts = 0;
7361
7366
  var mockEnvironmentReads = 0;
7367
+ var mockDomainsPatchAttempts = 0;
7368
+ var mockScreenshotPolls = 0;
7362
7369
  function extractErrorMessage(json, status2) {
7363
7370
  if (!json) return `HTTP ${status2}`;
7364
7371
  const err = json.error;
@@ -7390,6 +7397,13 @@ function extractErrorDetails(json) {
7390
7397
  return Object.keys(details).length > 0 ? details : void 0;
7391
7398
  }
7392
7399
  function mockExperiment() {
7400
+ const collidingVariation = process.env.APEX_CLI_MOCK_SCREENSHOTS === "collision" ? [{
7401
+ id: "deadbeef-0000-4000-8000-000000000002",
7402
+ name: "Trust-Badge",
7403
+ weight: 0,
7404
+ is_control: false,
7405
+ mutations: []
7406
+ }] : [];
7393
7407
  return {
7394
7408
  id: "00000000-0000-4000-8000-000000000001",
7395
7409
  slug: "fixture-test",
@@ -7398,6 +7412,7 @@ function mockExperiment() {
7398
7412
  traffic_allocation: 1,
7399
7413
  store_url: "https://example.com/products/demo",
7400
7414
  created_at: "2026-07-04T00:00:00.000Z",
7415
+ archived_at: null,
7401
7416
  variations: [
7402
7417
  {
7403
7418
  id: "00000000-0000-4000-8000-0000000000c0",
@@ -7407,9 +7422,10 @@ function mockExperiment() {
7407
7422
  is_control: true,
7408
7423
  mutations: []
7409
7424
  },
7425
+ ...collidingVariation,
7410
7426
  {
7411
7427
  id: "00000000-0000-4000-8000-0000000000b1",
7412
- slug: "badge",
7428
+ slug: process.env.APEX_CLI_MOCK_SCREENSHOTS === "collision" ? void 0 : "badge",
7413
7429
  name: "Trust Badge",
7414
7430
  weight: 0.5,
7415
7431
  is_control: false,
@@ -7418,6 +7434,18 @@ function mockExperiment() {
7418
7434
  ]
7419
7435
  };
7420
7436
  }
7437
+ var SECOND_PAGE_GOAL_ID = "00000000-0000-4000-8000-0000000000f2";
7438
+ function mockFunnel() {
7439
+ return {
7440
+ id: "00000000-0000-4000-8000-0000000000aa",
7441
+ name: "Fixture funnel",
7442
+ steps: [
7443
+ { order: 1, label: "Collection", url_pattern: "/collections/*" },
7444
+ { order: 2, label: "Product", url_pattern: "/products/*" }
7445
+ ],
7446
+ created_at: "2026-07-04T00:00:00.000Z"
7447
+ };
7448
+ }
7421
7449
  function mockGoal() {
7422
7450
  return {
7423
7451
  id: "00000000-0000-4000-8000-0000000000f1",
@@ -7427,11 +7455,24 @@ function mockGoal() {
7427
7455
  selector: null,
7428
7456
  text_match: null,
7429
7457
  url_pattern: null,
7458
+ page_id: null,
7430
7459
  event_name: "purchase",
7431
7460
  capture: null,
7432
7461
  created_at: "2026-07-04T00:00:00.000Z"
7433
7462
  };
7434
7463
  }
7464
+ var PAGE_SCOPED_GOAL_ID = "00000000-0000-4000-8000-0000000000f3";
7465
+ function mockPageScopedGoal() {
7466
+ return {
7467
+ ...mockGoal(),
7468
+ id: PAGE_SCOPED_GOAL_ID,
7469
+ name: "Fixture page-scoped pageview",
7470
+ type: "pageview",
7471
+ event_name: null,
7472
+ url_pattern: "bestellung-abgeschlossen",
7473
+ page_id: "00000000-0000-4000-8000-0000000000e1"
7474
+ };
7475
+ }
7435
7476
  function mockBrandContext() {
7436
7477
  if (process.env.APEX_CLI_MOCK_BRAND_PROFILE === "none") {
7437
7478
  return {
@@ -7481,11 +7522,144 @@ function mockBrandContext() {
7481
7522
  ].join("\n")
7482
7523
  };
7483
7524
  }
7484
- function mockResponse(method, path20, body5) {
7525
+ function mockResponse(method, path21, body5) {
7485
7526
  if (process.env.APEX_CLI_MOCK_API !== "1") return null;
7486
- const url = new URL(path20, "https://mock.apex.local");
7527
+ const url = new URL(path21, "https://mock.apex.local");
7487
7528
  const exp = mockExperiment();
7529
+ if (method === "POST" && url.pathname === `/api/v1/experiments/${exp.id}/screenshots`) {
7530
+ mockScreenshotPolls = 0;
7531
+ return { ok: true, status: 202, data: { job_id: "screenshot_job_1", status: "queued", poll_url: "/api/v1/screenshots/screenshot_job_1" } };
7532
+ }
7533
+ if (method === "GET" && url.pathname === "/api/v1/screenshots/screenshot_job_1") {
7534
+ mockScreenshotPolls += 1;
7535
+ if (process.env.APEX_CLI_MOCK_SCREENSHOTS === "transient" && mockScreenshotPolls === 1) {
7536
+ return { ok: false, status: 503, error: "Service temporarily unavailable" };
7537
+ }
7538
+ if (process.env.APEX_CLI_MOCK_SCREENSHOTS === "timeout" || process.env.APEX_CLI_MOCK_SCREENSHOTS === "deadline-processing" || mockScreenshotPolls === 1 && process.env.APEX_CLI_MOCK_SCREENSHOTS !== "deadline-completed") {
7539
+ return { ok: true, status: 200, data: { job_id: "screenshot_job_1", experiment_id: exp.id, status: "processing", devices: ["desktop_chrome"], variation_ids: exp.variations.map((variation) => variation.id) } };
7540
+ }
7541
+ const results2 = {
7542
+ [exp.variations[0].id]: { desktop_chrome: "data:image/png;base64,iVBORw0KGgo=" }
7543
+ };
7544
+ if (process.env.APEX_CLI_MOCK_SCREENSHOTS !== "partial") {
7545
+ for (const variation of exp.variations.slice(1)) {
7546
+ results2[variation.id] = { desktop_chrome: "data:image/png;base64,iVBORw0KGgo=" };
7547
+ }
7548
+ }
7549
+ return { ok: true, status: 200, data: { job_id: "screenshot_job_1", experiment_id: exp.id, status: "completed", devices: ["desktop_chrome"], variation_ids: exp.variations.map((variation) => variation.id), results: results2 } };
7550
+ }
7551
+ if (url.pathname.startsWith("/api/v1/webhooks")) {
7552
+ const input = body5 ?? {};
7553
+ const webhook = {
7554
+ id: url.pathname.split("/")[4] || "webhook_1",
7555
+ url: String(input.url ?? "https://hooks.example.test/apex"),
7556
+ events: Array.isArray(input.events) ? input.events.map(String) : ["project.published"],
7557
+ active: input.active === void 0 ? true : input.active === true,
7558
+ hasSecret: true,
7559
+ consecutiveFailures: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? 3 : 1,
7560
+ disabledAt: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? "2026-08-05T12:00:00.000Z" : null,
7561
+ created_at: "2026-08-05T00:00:00.000Z"
7562
+ };
7563
+ const availableEvents = ["experiment.started", "experiment.completed", "experiment.significant", "experiment.guardian_triggered", "experiment.guardian_paused", "variation.winning", "screenshots.completed", "project.published"];
7564
+ if (method === "GET" && url.pathname === "/api/v1/webhooks") return { ok: true, status: 200, data: { data: [webhook], availableEvents, pagination: { has_more: false, next_cursor: null } } };
7565
+ if (method === "POST" && url.pathname === "/api/v1/webhooks") return { ok: true, status: 201, data: { ...webhook, secret: "whsec_mock_secret" } };
7566
+ if (method === "PATCH") return { ok: true, status: 200, data: webhook };
7567
+ if (method === "DELETE") return { ok: true, status: 200, data: { deleted: true, id: webhook.id } };
7568
+ if (method === "GET" && url.pathname.endsWith("/deliveries")) return { ok: true, status: 200, data: { webhook: { id: webhook.id, active: webhook.active, consecutiveFailures: webhook.consecutiveFailures, disabledAt: webhook.disabledAt }, data: [{ id: "delivery_1", webhook_id: webhook.id, event: "project.published", status: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? "dead" : "delivered", attempts: 1, response_status: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? 500 : 200, last_error: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? "Webhook responded with 500" : null, next_attempt_at: null, processing_started_at: null, delivered_at: "2026-08-05T00:01:00.000Z", created_at: "2026-08-05T00:00:00.000Z", updated_at: "2026-08-05T00:01:00.000Z" }], pagination: { has_more: false, next_cursor: null } } };
7569
+ if (method === "POST" && url.pathname.endsWith("/test")) return { ok: true, status: 200, data: { webhook: { id: webhook.id, active: webhook.active, consecutiveFailures: webhook.consecutiveFailures, disabledAt: webhook.disabledAt }, delivery: { id: "delivery_test", webhook_id: webhook.id, event: "test", status: "delivered", attempts: 1, response_status: 200, last_error: null, next_attempt_at: null, processing_started_at: null, delivered_at: "2026-08-05T00:01:00.000Z", created_at: "2026-08-05T00:00:00.000Z", updated_at: "2026-08-05T00:01:00.000Z" } } };
7570
+ }
7571
+ const forcedStatus = Number(process.env.APEX_CLI_MOCK_ERROR_STATUS ?? 0);
7572
+ if (forcedStatus > 0) {
7573
+ return {
7574
+ ok: false,
7575
+ status: forcedStatus,
7576
+ error: forcedStatus === 401 ? "Invalid API key" : "Resource not found",
7577
+ errorDetails: { mocked: true }
7578
+ };
7579
+ }
7580
+ if (method === "POST" && url.pathname.endsWith("/build") && process.env.APEX_CLI_MOCK_WORKSPACE_BUILD === "blocked-evidence") {
7581
+ return {
7582
+ ok: false,
7583
+ status: 400,
7584
+ error: "Generated custom JavaScript includes an external network or asset call.",
7585
+ data: {
7586
+ ok: false,
7587
+ error: "Generated custom JavaScript includes an external network or asset call.",
7588
+ blockingIssues: [
7589
+ {
7590
+ code: "external_network_call",
7591
+ severity: "blocking",
7592
+ field: "custom_js",
7593
+ message: "Generated custom JavaScript includes an external network or asset call.",
7594
+ evidence: {
7595
+ construct: "String-capable timer execution (fail-closed)",
7596
+ snippet: "setTimeout(unknownHandler, 10)",
7597
+ line: 7,
7598
+ column: 2
7599
+ }
7600
+ },
7601
+ {
7602
+ code: "external_network_call",
7603
+ severity: "blocking",
7604
+ field: "mutations[1].html",
7605
+ message: "Generated HTML includes an external network or asset URL.",
7606
+ evidence: {
7607
+ construct: "External HTML asset URL",
7608
+ url: "https://assets.example.test/pixel.png",
7609
+ matchIndex: 14
7610
+ }
7611
+ },
7612
+ {
7613
+ code: "invalid_custom_code_type",
7614
+ severity: "blocking",
7615
+ field: "custom_css",
7616
+ message: "custom_css must be a string."
7617
+ },
7618
+ {
7619
+ code: "price_node_mutation",
7620
+ severity: "blocking",
7621
+ field: ".product-price",
7622
+ message: "This mutation replaces a price-display node.",
7623
+ evidence: {
7624
+ selector: ".product-price"
7625
+ }
7626
+ },
7627
+ {
7628
+ code: "unknown_validation_family",
7629
+ severity: "blocking",
7630
+ field: "future_guardrail_field",
7631
+ message: "A future guardrail blocked this workspace."
7632
+ }
7633
+ ]
7634
+ }
7635
+ };
7636
+ }
7637
+ if (method === "GET" && url.pathname === "/api/v1/roadmap/approved") {
7638
+ return {
7639
+ ok: true,
7640
+ status: 200,
7641
+ data: {
7642
+ data: [{ id: "roadmap_1", title: "Fixture roadmap idea", linked_experiments: [] }],
7643
+ pagination: { has_more: false, next_cursor: null }
7644
+ }
7645
+ };
7646
+ }
7647
+ if (method === "GET" && url.pathname === "/api/v1/roadmap/items/roadmap_1/handoff") {
7648
+ return {
7649
+ ok: true,
7650
+ status: 200,
7651
+ data: {
7652
+ id: "roadmap_1",
7653
+ title: "Fixture roadmap idea",
7654
+ hypothesis: "The fixture improves conversion",
7655
+ instructions: "Build the fixture"
7656
+ }
7657
+ };
7658
+ }
7488
7659
  if (method === "GET" && url.pathname === `/api/v1/experiments/${exp.id}/filesystem`) {
7660
+ if (process.env.APEX_CLI_MOCK_FILESYSTEM_REFRESH === "failed") {
7661
+ throw new Error("Mock filesystem refresh failed");
7662
+ }
7489
7663
  const fixtures = [
7490
7664
  ["experiment.json", '{"schemaVersion":1,"name":"Fixture Test"}\n'],
7491
7665
  ["targeting.json", '{"url":[],"segments":[]}\n'],
@@ -7497,6 +7671,7 @@ function mockResponse(method, path20, body5) {
7497
7671
  ["launch.json", '{"status":"draft"}\n'],
7498
7672
  ["variations/control/variation.json", '{"slug":"control","isControl":true}\n'],
7499
7673
  ["variations/control/mutations.json", "[]\n"],
7674
+ ["variations/control/url-blocks.json", "[]\n"],
7500
7675
  ["variations/control/index.css", ".control { color: inherit; }\n"],
7501
7676
  ["variations/control/index.js", "window.__apexControl = true;\n"],
7502
7677
  ["variations/control/reset.js", "delete window.__apexControl;\n"],
@@ -7524,6 +7699,36 @@ function mockResponse(method, path20, body5) {
7524
7699
  }
7525
7700
  };
7526
7701
  }
7702
+ const workspaceMatch = url.pathname.match(
7703
+ /^\/api\/v1\/experiments\/([^/]+)\/variations\/([^/]+)\/workspace$/
7704
+ );
7705
+ if (method === "GET" && workspaceMatch) {
7706
+ const content = "window.__apexOperator = true;\n";
7707
+ return {
7708
+ ok: true,
7709
+ status: 200,
7710
+ data: {
7711
+ workspace: {
7712
+ id: "workspace_1",
7713
+ schemaVersion: 1,
7714
+ status: "draft",
7715
+ currentRevisionId: "revision_1",
7716
+ updatedAt: "2026-08-05T00:00:00.000Z"
7717
+ },
7718
+ files: [
7719
+ {
7720
+ path: "src/index.js",
7721
+ kind: "custom_js",
7722
+ contentType: "application/javascript",
7723
+ content,
7724
+ sha256: (0, import_node_crypto.createHash)("sha256").update(content).digest("hex"),
7725
+ sizeBytes: Buffer.byteLength(content),
7726
+ updatedAt: "2026-08-05T00:00:00.000Z"
7727
+ }
7728
+ ]
7729
+ }
7730
+ };
7731
+ }
7527
7732
  const configMatch = url.pathname.match(
7528
7733
  /^\/api\/v1\/(assets|landing-pages)(?:\/([^/]+))?$/
7529
7734
  );
@@ -7703,16 +7908,16 @@ function mockResponse(method, path20, body5) {
7703
7908
  /^\/api\/v1\/(flags|personalizations)(?:\/([^/]+))?(?:\/(activate|pause|archive|status))?$/
7704
7909
  );
7705
7910
  if (managedMatch) {
7706
- const [, kind5, id = `${kind5.slice(0, -1)}_1`, action25] = managedMatch;
7911
+ const [, kind5, id = `${kind5.slice(0, -1)}_1`, action68] = managedMatch;
7707
7912
  const input = body5 ?? {};
7708
7913
  const item = {
7709
7914
  id,
7710
7915
  key: String(input.key ?? `fixture-${kind5.slice(0, -1)}`),
7711
7916
  name: String(input.name ?? `Fixture ${kind5.slice(0, -1)}`),
7712
- status: action25 === "activate" ? "active" : action25 === "pause" ? "paused" : action25 === "archive" ? "archived" : "draft",
7917
+ status: action68 === "activate" ? "active" : action68 === "pause" ? "paused" : action68 === "archive" ? "archived" : "draft",
7713
7918
  updated_at: "2026-07-19T00:00:00.000Z"
7714
7919
  };
7715
- if (kind5 === "flags" && action25 === "status")
7920
+ if (kind5 === "flags" && action68 === "status")
7716
7921
  return {
7717
7922
  ok: true,
7718
7923
  status: 200,
@@ -7745,11 +7950,11 @@ function mockResponse(method, path20, body5) {
7745
7950
  /^\/api\/v1\/(backlog|signals)(?:\/([^/]+))?(?:\/(archive|restore))?$/
7746
7951
  );
7747
7952
  if (insightMatch) {
7748
- const [, kind5, id = `${kind5}_1`, action25] = insightMatch;
7953
+ const [, kind5, id = `${kind5}_1`, action68] = insightMatch;
7749
7954
  const input = body5 ?? {};
7750
- if (action25 && (kind5 !== "backlog" || method !== "POST"))
7751
- return { ok: false, status: 405, error: `${action25} requires POST` };
7752
- const archived = action25 === "archive" || method === "DELETE" || url.searchParams.get("include_archived") === "true";
7955
+ if (action68 && (kind5 !== "backlog" || method !== "POST"))
7956
+ return { ok: false, status: 405, error: `${action68} requires POST` };
7957
+ const archived = action68 === "archive" || method === "DELETE" || url.searchParams.get("include_archived") === "true";
7753
7958
  const item = {
7754
7959
  id,
7755
7960
  ...kind5 === "backlog" ? {
@@ -7775,20 +7980,24 @@ function mockResponse(method, path20, body5) {
7775
7980
  return { ok: true, status: 200, data: { ok: true } };
7776
7981
  return {
7777
7982
  ok: true,
7778
- status: method === "POST" && !action25 ? 201 : 200,
7983
+ status: method === "POST" && !action68 ? 201 : 200,
7779
7984
  data: item
7780
7985
  };
7781
7986
  }
7782
7987
  if (method === "GET" && url.pathname === "/api/v1/experiments") {
7988
+ const experiment = url.searchParams.get("archived") === "1" ? { ...exp, archived_at: "2026-07-20T00:00:00.000Z" } : { ...exp, archived_at: null };
7783
7989
  return {
7784
7990
  ok: true,
7785
7991
  status: 200,
7786
7992
  data: {
7787
- data: [exp],
7993
+ data: [experiment],
7788
7994
  pagination: { has_more: false, next_cursor: null }
7789
7995
  }
7790
7996
  };
7791
7997
  }
7998
+ if (method === "POST" && url.pathname === `/api/v1/experiments/${exp.id}/start`) {
7999
+ return { ok: true, status: 200, data: { ...exp, status: "running" } };
8000
+ }
7792
8001
  if (method === "POST" && url.pathname === "/api/v1/experiments") {
7793
8002
  const input = body5 ?? {};
7794
8003
  if (input.experiment_type === "redirect" && process.env.APEX_CLI_MOCK_REDIRECT_DISABLED === "1") {
@@ -7825,6 +8034,7 @@ function mockResponse(method, path20, body5) {
7825
8034
  return { ok: true, status: 200, data: exp };
7826
8035
  }
7827
8036
  if (method === "GET" && url.pathname === `/api/v1/experiments/${exp.id}/results`) {
8037
+ const withheld = process.env.APEX_CLI_MOCK_RESULTS_STATE === "untrustworthy";
7828
8038
  return {
7829
8039
  ok: true,
7830
8040
  status: 200,
@@ -7852,14 +8062,62 @@ function mockResponse(method, path20, body5) {
7852
8062
  }
7853
8063
  ],
7854
8064
  decision: {
7855
- state: "collecting",
8065
+ state: withheld ? "untrustworthy" : "collecting",
7856
8066
  treatmentName: "Trust Badge",
7857
8067
  lift: 25,
7858
- sequentialConfidence: 62.5
8068
+ sequentialConfidence: withheld ? 0 : 62.5,
8069
+ fixedConfidence: withheld ? 0 : 84,
8070
+ judgedConfidence: withheld ? 0 : 84,
8071
+ basis: withheld ? "sequential" : "fixed_horizon",
8072
+ blockingGates: withheld ? ["mixed-currency"] : []
8073
+ },
8074
+ effective_outcome: {
8075
+ outcome: withheld ? "needs_readout" : "winner",
8076
+ statisticsOutcome: withheld ? "needs_readout" : "collecting",
8077
+ provenance: withheld ? "statistics" : "manager_decided"
8078
+ },
8079
+ manager_decision: withheld ? null : {
8080
+ outcome: "winner",
8081
+ reason: "The business case is strong enough to ship.",
8082
+ decided_by: { id: "user_1", name: "Maya Manager" },
8083
+ decided_at: "2026-07-31T09:00:00.000Z",
8084
+ version: 1
7859
8085
  },
7860
8086
  metrics: [],
7861
8087
  series: [],
7862
- warnings: []
8088
+ warnings: withheld ? [{ code: "mixed-currency", message: "Mixed currencies block the verdict." }] : []
8089
+ }
8090
+ };
8091
+ }
8092
+ if (method === "GET" && url.pathname === `/api/v1/experiments/${exp.id}/qa/run`) {
8093
+ return {
8094
+ ok: true,
8095
+ status: 200,
8096
+ data: {
8097
+ run: {
8098
+ id: "qa_run_1",
8099
+ status: "in_review",
8100
+ checklist: [],
8101
+ urls: [],
8102
+ automatedEvidence: null,
8103
+ acceptanceCriteriaSummary: null,
8104
+ goalVerification: null,
8105
+ summary: {
8106
+ status: "in_review",
8107
+ required: 0,
8108
+ completedRequired: 0,
8109
+ failedRequired: 0,
8110
+ optional: 0,
8111
+ completedOptional: 0,
8112
+ blockers: []
8113
+ },
8114
+ notes: null,
8115
+ createdAt: "2026-07-04T00:00:00.000Z",
8116
+ updatedAt: "2026-07-04T00:00:00.000Z",
8117
+ approvedAt: null,
8118
+ approvedBy: null,
8119
+ completedAt: null
8120
+ }
7863
8121
  }
7864
8122
  };
7865
8123
  }
@@ -7894,6 +8152,29 @@ function mockResponse(method, path20, body5) {
7894
8152
  }
7895
8153
  };
7896
8154
  }
8155
+ if (process.env.APEX_CLI_MOCK_FILESYSTEM_PLAN === "blocking-only") {
8156
+ return {
8157
+ ok: true,
8158
+ status: 200,
8159
+ data: {
8160
+ ok: false,
8161
+ experimentId: exp.id,
8162
+ status: "invalid",
8163
+ changes: [],
8164
+ errors: [],
8165
+ warnings: [],
8166
+ blockingIssues: [{
8167
+ code: "custom_js_parse_error",
8168
+ severity: "blocking",
8169
+ message: "Custom JavaScript does not parse.",
8170
+ path: "variations/control/index.js",
8171
+ variation: "control"
8172
+ }],
8173
+ invalidatesQa: false,
8174
+ precompileRequired: false
8175
+ }
8176
+ };
8177
+ }
7897
8178
  const observedPaths = files.map((file) => file.path).filter((filePath) => typeof filePath === "string");
7898
8179
  return {
7899
8180
  ok: true,
@@ -7999,6 +8280,49 @@ function mockResponse(method, path20, body5) {
7999
8280
  }
8000
8281
  };
8001
8282
  }
8283
+ if (url.pathname === "/api/v1/consent" && (method === "GET" || method === "PATCH")) {
8284
+ const patch = body5;
8285
+ const enabled = typeof patch?.consentMode?.enabled === "boolean" ? patch.consentMode.enabled : true;
8286
+ const provider = typeof patch?.consentMode?.provider === "string" ? patch.consentMode.provider : "cookiescript";
8287
+ const requiredGroup = typeof patch?.consentMode?.requiredGroup === "string" ? patch.consentMode.requiredGroup : "analytics";
8288
+ const getter = typeof patch?.consentMode?.getter === "string" ? patch.consentMode.getter : "";
8289
+ if (method === "PATCH" && patch?.applyCanonicalSplit === false) {
8290
+ return { ok: false, status: 403, error: "applyCanonicalSplit: false is not permitted with the consent:write scope \u2014 clear those prerequisites in the dashboard (shop:settings)." };
8291
+ }
8292
+ return { ok: true, status: 200, data: {
8293
+ consentMode: { enabled, provider, requiredGroup, getter },
8294
+ prerequisites: {
8295
+ showVariationsRule: "return true;",
8296
+ startTrackingRule: "return drip.get('trackingConsentEnabled');"
8297
+ },
8298
+ ...method === "PATCH" ? { precompile: { status: "ok", summary: "Mock edge precompile succeeded" } } : {}
8299
+ } };
8300
+ }
8301
+ if (url.pathname === "/api/v1/install-domains" && (method === "GET" || method === "PATCH")) {
8302
+ const conflictMode = process.env.APEX_CLI_MOCK_DOMAINS_CONFLICT;
8303
+ const serverDomains = conflictMode === "once" && mockDomainsPatchAttempts >= 1 ? ["shop.example.com", "peer.example.com"] : ["shop.example.com"];
8304
+ if (method === "PATCH") {
8305
+ mockDomainsPatchAttempts += 1;
8306
+ if (conflictMode === "always" || conflictMode === "once" && mockDomainsPatchAttempts === 1) {
8307
+ return { ok: false, status: 409, error: "Install domains changed since read \u2014 re-read and retry" };
8308
+ }
8309
+ }
8310
+ const domains = body5?.domains ?? serverDomains;
8311
+ const additionalDomains = method === "PATCH" ? domains : serverDomains;
8312
+ return { ok: true, status: 200, data: {
8313
+ domain: "example.com",
8314
+ additionalDomains,
8315
+ storeOrigins: ["https://example.com", "https://www.example.com", ...additionalDomains.map((domain) => `https://${domain}`)],
8316
+ revision: method === "PATCH" ? "rev_patched" : `rev_${serverDomains.length}`,
8317
+ ...method === "PATCH" ? { precompile: { status: "ok", summary: "Mock edge precompile succeeded" } } : {}
8318
+ } };
8319
+ }
8320
+ if (url.pathname === "/api/v1/external-tracking" && (method === "GET" || method === "PATCH")) {
8321
+ const current = { domains: ["www.example.com", "shop.example.com"], handoverQueryParam: "drip_bucket", handoverTtlSeconds: 120, handoverMintTimeoutMs: 250, signed: true };
8322
+ const patch = body5?.externalTracking;
8323
+ const next = { ...current, ...patch ?? {} };
8324
+ return { ok: true, status: 200, data: { externalTracking: next, revision: "2026-08-05T10:00:00.000Z", ...method === "PATCH" ? { precompile: { status: "ok", summary: "Mock edge precompile succeeded" } } : {} } };
8325
+ }
8002
8326
  const analyticsMatch = url.pathname.match(/^\/api\/v1\/analytics\/(stats|timeseries|traffic|pages|geo|sessions|web-vitals|realtime|entry-exit)$/);
8003
8327
  if (method === "GET" && analyticsMatch) {
8004
8328
  const fixtures = {
@@ -8014,12 +8338,119 @@ function mockResponse(method, path20, body5) {
8014
8338
  };
8015
8339
  return { ok: true, status: 200, data: { data: fixtures[analyticsMatch[1]] } };
8016
8340
  }
8341
+ if (method === "GET" && url.pathname === "/api/v1/analytics/url-inventory") {
8342
+ return { ok: true, status: 200, data: { items: [
8343
+ { host: "getkong.test", path: "/bestellung-abgeschlossen", pageviews: 12, visitors: 10 },
8344
+ { host: "getkong.test", path: "/products/demo", pageviews: 7, visitors: 5 }
8345
+ ], total: 2, truncated: false } };
8346
+ }
8347
+ if (method === "POST" && url.pathname === "/api/v1/analytics/suggest-goals") {
8348
+ return { ok: true, status: 200, data: { goals: [{ name: "Purchase", type: "pageview", urlPattern: "/bestellung-abgeschlossen", explanation: "Fixture" }], funnels: [], triggers: [] } };
8349
+ }
8350
+ if (method === "GET" && url.pathname === "/api/v1/funnels") {
8351
+ return { ok: true, status: 200, data: { data: [mockFunnel()] } };
8352
+ }
8353
+ if (method === "POST" && url.pathname === "/api/v1/funnels") {
8354
+ return { ok: true, status: 201, data: { ...mockFunnel(), ...body5 } };
8355
+ }
8356
+ if (method === "GET" && url.pathname === `/api/v1/funnels/${mockFunnel().id}`) {
8357
+ return { ok: true, status: 200, data: mockFunnel() };
8358
+ }
8359
+ if (method === "PATCH" && url.pathname === `/api/v1/funnels/${mockFunnel().id}`) {
8360
+ return { ok: true, status: 200, data: { ...mockFunnel(), ...body5 } };
8361
+ }
8362
+ if (method === "DELETE" && url.pathname === `/api/v1/funnels/${mockFunnel().id}`) {
8363
+ return { ok: true, status: 200, data: { ok: true } };
8364
+ }
8365
+ if (method === "GET" && url.pathname === `/api/v1/funnels/${mockFunnel().id}/run`) {
8366
+ return { ok: true, status: 200, data: { total_sessions_analyzed: 2, steps: [{ step: 1, label: "Collection", url_pattern: "/collections/*", visitors: 2, drop_off_rate: 0, matched_pageviews: 2 }] } };
8367
+ }
8368
+ if (method === "GET" && url.pathname.endsWith("/workspace")) {
8369
+ const files = [
8370
+ ["experiment.config.json", JSON.stringify({ name: exp.name, storeUrl: exp.store_url, experimentSlug: exp.slug, variationSlug: "badge" }, null, 2) + "\n", "application/json", "config"],
8371
+ ["metrics.json", '{"primary":null,"secondary":[]}\n', "application/json", "metrics"],
8372
+ ["targeting.json", '{"url":[],"segments":[]}\n', "application/json", "targeting"],
8373
+ ["src/index.js", "window.__apexOperatorFixture = true;\n", "application/javascript", "custom_js"],
8374
+ ["src/index.scss", ".apex-operator-fixture { display: block; }\n", "text/x-scss", "custom_css"],
8375
+ ["src/mutations.json", "[]\n", "application/json", "mutations"],
8376
+ ["dev-doc.md", "# Fixture workspace\n", "text/markdown", "doc"],
8377
+ ["brand.md", "# Fixture brand\n", "text/markdown", "brand"]
8378
+ ];
8379
+ return {
8380
+ ok: true,
8381
+ status: 200,
8382
+ data: {
8383
+ workspace: {
8384
+ id: "workspace_1",
8385
+ schemaVersion: 1,
8386
+ status: "draft",
8387
+ currentRevisionId: "revision_1",
8388
+ updatedAt: "2026-07-30T00:00:00.000Z"
8389
+ },
8390
+ files: files.map(([filePath, content, contentType, kind5]) => ({
8391
+ path: filePath,
8392
+ kind: kind5,
8393
+ contentType,
8394
+ content,
8395
+ sha256: (0, import_node_crypto.createHash)("sha256").update(content).digest("hex"),
8396
+ sizeBytes: Buffer.byteLength(content, "utf8"),
8397
+ updatedAt: "2026-07-30T00:00:00.000Z"
8398
+ }))
8399
+ }
8400
+ };
8401
+ }
8402
+ if (method === "PUT" && url.pathname.includes("/variations/") && url.pathname.includes("/files/")) {
8403
+ return { ok: true, status: 200, data: { ok: true } };
8404
+ }
8405
+ if (method === "POST" && url.pathname.endsWith("/build")) {
8406
+ return {
8407
+ ok: true,
8408
+ status: 200,
8409
+ data: { ok: true, mutationCount: 0, customCssBytes: 42, customJsBytes: 58 }
8410
+ };
8411
+ }
8412
+ if (method === "POST" && url.pathname === "/api/v1/harness/sessions") {
8413
+ return { ok: true, status: 201, data: { sessionId: "harness_1", sessionToken: "token", experimentId: exp.id, variationId: exp.variations?.[1]?.id ?? "variation_1", expiresAt: "2026-07-30T01:00:00.000Z", previewUrl: "https://preview.drip-apex.com/api/preview?pst=harness_1" } };
8414
+ }
8415
+ if (method === "POST" && url.pathname === "/api/v1/harness/sessions/harness_1/versions") {
8416
+ return { ok: true, status: 201, data: { sessionId: "harness_1", version: 1, expiresAt: "2026-07-30T01:00:00.000Z", jsArtifactId: "asset_js", cssArtifactId: "asset_css" } };
8417
+ }
8418
+ if (method === "POST" && url.pathname === "/api/v1/harness/sessions/harness_1/publish") {
8419
+ return { ok: true, status: 200, data: { ok: true } };
8420
+ }
8017
8421
  if (method === "GET" && url.pathname === "/api/v1/goals") {
8422
+ if (process.env.APEX_CLI_MOCK_GOAL_PAGES === "2") {
8423
+ if (!url.searchParams.get("cursor")) {
8424
+ return {
8425
+ ok: true,
8426
+ status: 200,
8427
+ data: {
8428
+ data: [mockGoal()],
8429
+ pagination: { has_more: true, next_cursor: "goals_page_2" }
8430
+ }
8431
+ };
8432
+ }
8433
+ return {
8434
+ ok: true,
8435
+ status: 200,
8436
+ data: {
8437
+ data: [{
8438
+ ...mockGoal(),
8439
+ id: SECOND_PAGE_GOAL_ID,
8440
+ name: "Fixture pageview",
8441
+ type: "pageview",
8442
+ event_name: null,
8443
+ url_pattern: "bestellung-abgeschlossen"
8444
+ }],
8445
+ pagination: { has_more: false, next_cursor: null }
8446
+ }
8447
+ };
8448
+ }
8018
8449
  return {
8019
8450
  ok: true,
8020
8451
  status: 200,
8021
8452
  data: {
8022
- data: [mockGoal()],
8453
+ data: [mockGoal(), mockPageScopedGoal()],
8023
8454
  pagination: { has_more: false, next_cursor: null }
8024
8455
  }
8025
8456
  };
@@ -8195,8 +8626,8 @@ function getUserKeyIdentity(creds2) {
8195
8626
  "/api/v1/me"
8196
8627
  );
8197
8628
  }
8198
- function userRequest(creds2, method, path20, body5) {
8199
- return request({ ...creds2, apiKey: creds2.userApiKey ?? "", baseUrl: creds2.userBaseUrl ?? creds2.baseUrl }, method, path20, body5);
8629
+ function userRequest(creds2, method, path21, body5) {
8630
+ return request({ ...creds2, apiKey: creds2.userApiKey ?? "", baseUrl: creds2.userBaseUrl ?? creds2.baseUrl }, method, path21, body5);
8200
8631
  }
8201
8632
  function getAccountProfile(creds2) {
8202
8633
  return userRequest(creds2, "GET", "/api/v1/account/profile");
@@ -8213,24 +8644,39 @@ function createUserManagedKey(creds2, body5) {
8213
8644
  function revokeUserManagedKey(creds2, id, shopId) {
8214
8645
  return userRequest(creds2, "DELETE", `/api/v1/keys/${encodeURIComponent(id)}?shop_id=${encodeURIComponent(shopId)}`);
8215
8646
  }
8216
- async function request(creds2, method, path20, body5) {
8217
- const mock = mockResponse(method, path20, body5);
8647
+ async function request(creds2, method, path21, body5, requestTimeoutMs) {
8648
+ const mock = mockResponse(method, path21, body5);
8218
8649
  if (mock) return mock;
8219
- const url = `${creds2.baseUrl}${path20}`;
8650
+ const url = `${creds2.baseUrl}${path21}`;
8651
+ const configuredTimeout = Number(process.env.APEX_TIMEOUT_MS ?? "30000");
8652
+ const defaultTimeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : 3e4;
8653
+ const timeoutMs = requestTimeoutMs !== void 0 ? Math.max(1, Math.min(defaultTimeoutMs, requestTimeoutMs)) : defaultTimeoutMs;
8220
8654
  const headers = {
8221
8655
  Authorization: `Bearer ${creds2.apiKey}`,
8222
8656
  "Content-Type": "application/json"
8223
8657
  };
8224
- const res = await fetch(url, {
8225
- method,
8226
- headers,
8227
- body: body5 ? JSON.stringify(body5) : void 0
8228
- });
8658
+ let res;
8659
+ try {
8660
+ res = await fetch(url, {
8661
+ method,
8662
+ headers,
8663
+ body: body5 ? JSON.stringify(body5) : void 0,
8664
+ signal: AbortSignal.timeout(timeoutMs)
8665
+ });
8666
+ } catch (error) {
8667
+ const seconds = timeoutMs / 1e3;
8668
+ throw new CliError(
8669
+ `Could not reach ${creds2.baseUrl}: check DRIP_BASE_URL / network; server did not respond within ${seconds}s.`,
8670
+ EXIT_CODES.general,
8671
+ { baseUrl: creds2.baseUrl, timeoutMs, cause: error instanceof Error ? error.message : String(error) }
8672
+ );
8673
+ }
8229
8674
  const json = await res.json().catch(() => null);
8230
8675
  if (!res.ok) {
8231
8676
  return {
8232
8677
  ok: false,
8233
8678
  status: res.status,
8679
+ data: json,
8234
8680
  error: extractErrorMessage(json, res.status),
8235
8681
  errorDetails: extractErrorDetails(json),
8236
8682
  requestId: res.headers.get("x-request-id"),
@@ -8249,15 +8695,28 @@ async function request(creds2, method, path20, body5) {
8249
8695
  data: json
8250
8696
  };
8251
8697
  }
8252
- async function requestForm(creds2, method, path20, body5) {
8253
- const mock = mockResponse(method, path20, body5);
8698
+ async function requestForm(creds2, method, path21, body5) {
8699
+ const mock = mockResponse(method, path21, body5);
8254
8700
  if (mock) return mock;
8255
- const url = `${creds2.baseUrl}${path20}`;
8256
- const res = await fetch(url, {
8257
- method,
8258
- headers: { Authorization: `Bearer ${creds2.apiKey}` },
8259
- body: body5
8260
- });
8701
+ const url = `${creds2.baseUrl}${path21}`;
8702
+ const configuredTimeout = Number(process.env.APEX_TIMEOUT_MS ?? "120000");
8703
+ const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : 12e4;
8704
+ let res;
8705
+ try {
8706
+ res = await fetch(url, {
8707
+ method,
8708
+ headers: { Authorization: `Bearer ${creds2.apiKey}` },
8709
+ body: body5,
8710
+ signal: AbortSignal.timeout(timeoutMs)
8711
+ });
8712
+ } catch (error) {
8713
+ const seconds = timeoutMs / 1e3;
8714
+ throw new CliError(
8715
+ `Could not reach ${creds2.baseUrl}: check DRIP_BASE_URL / network; server did not respond within ${seconds}s.`,
8716
+ EXIT_CODES.general,
8717
+ { baseUrl: creds2.baseUrl, timeoutMs, cause: error instanceof Error ? error.message : String(error) }
8718
+ );
8719
+ }
8261
8720
  const json = await res.json().catch(() => null);
8262
8721
  if (!res.ok) {
8263
8722
  return {
@@ -8280,6 +8739,7 @@ function listExperiments(creds2, opts) {
8280
8739
  if (opts?.status) params.set("status", opts.status);
8281
8740
  if (opts?.limit) params.set("limit", String(opts.limit));
8282
8741
  if (opts?.cursor) params.set("cursor", opts.cursor);
8742
+ if (opts?.includeArchived) params.set("archived", "1");
8283
8743
  const qs = params.toString();
8284
8744
  return request(
8285
8745
  creds2,
@@ -8341,6 +8801,15 @@ function getExperimentResults(creds2, id, opts) {
8341
8801
  `/api/v1/experiments/${id}/results${qs ? `?${qs}` : ""}`
8342
8802
  );
8343
8803
  }
8804
+ function createExperimentScreenshots(creds2, id, body5) {
8805
+ return request(creds2, "POST", `/api/v1/experiments/${id}/screenshots`, body5);
8806
+ }
8807
+ async function getScreenshotJob(creds2, jobId, timeoutMs) {
8808
+ if (process.env.APEX_CLI_MOCK_API === "1" && ["deadline-processing", "deadline-completed"].includes(process.env.APEX_CLI_MOCK_SCREENSHOTS ?? "")) {
8809
+ await new Promise((resolve) => setTimeout(resolve, 5));
8810
+ }
8811
+ return request(creds2, "GET", `/api/v1/screenshots/${jobId}`, void 0, timeoutMs);
8812
+ }
8344
8813
  function planExperimentFilesystem(creds2, id, body5) {
8345
8814
  return request(
8346
8815
  creds2,
@@ -8417,13 +8886,32 @@ function putOperatorWorkspaceFile(creds2, experimentId, variationId, filePath, b
8417
8886
  body5
8418
8887
  );
8419
8888
  }
8420
- function buildOperatorWorkspace(creds2, experimentId, variationId) {
8889
+ function buildOperatorWorkspace(creds2, experimentId, variationId, body5) {
8421
8890
  return request(
8422
8891
  creds2,
8423
8892
  "POST",
8424
- `/api/v1/experiments/${experimentId}/variations/${variationId}/build`
8893
+ `/api/v1/experiments/${experimentId}/variations/${variationId}/build`,
8894
+ body5
8425
8895
  );
8426
8896
  }
8897
+ function listFunnels(creds2) {
8898
+ return request(creds2, "GET", "/api/v1/funnels");
8899
+ }
8900
+ function createFunnel(creds2, body5) {
8901
+ return request(creds2, "POST", "/api/v1/funnels", body5);
8902
+ }
8903
+ function updateFunnel(creds2, id, body5) {
8904
+ return request(creds2, "PATCH", `/api/v1/funnels/${encodeURIComponent(id)}`, body5);
8905
+ }
8906
+ function deleteFunnel(creds2, id) {
8907
+ return request(creds2, "DELETE", `/api/v1/funnels/${encodeURIComponent(id)}`);
8908
+ }
8909
+ function runFunnel(creds2, id, days) {
8910
+ const params = new URLSearchParams();
8911
+ if (days) params.set("days", String(days));
8912
+ const qs = params.toString();
8913
+ return request(creds2, "GET", `/api/v1/funnels/${encodeURIComponent(id)}/run${qs ? `?${qs}` : ""}`);
8914
+ }
8427
8915
  function listGoals(creds2, opts) {
8428
8916
  const params = new URLSearchParams();
8429
8917
  if (opts?.limit) params.set("limit", String(opts.limit));
@@ -8442,6 +8930,14 @@ function updateGoal(creds2, id, body5) {
8442
8930
  body5
8443
8931
  );
8444
8932
  }
8933
+ function getUrlInventory(creds2, opts) {
8934
+ const params = new URLSearchParams();
8935
+ for (const [key, value] of Object.entries(opts)) if (value !== void 0) params.set(key, String(value));
8936
+ return request(creds2, "GET", `/api/v1/analytics/url-inventory?${params.toString()}`);
8937
+ }
8938
+ function suggestGoals(creds2) {
8939
+ return request(creds2, "POST", "/api/v1/analytics/suggest-goals", {});
8940
+ }
8445
8941
  function getBrandContext(creds2) {
8446
8942
  return request(creds2, "GET", "/api/v1/brand-context");
8447
8943
  }
@@ -8456,6 +8952,24 @@ function updateEnvironments(creds2, body5) {
8456
8952
  body5
8457
8953
  );
8458
8954
  }
8955
+ function getConsent(creds2) {
8956
+ return request(creds2, "GET", "/api/v1/consent");
8957
+ }
8958
+ function updateConsent(creds2, body5) {
8959
+ return request(creds2, "PATCH", "/api/v1/consent", body5);
8960
+ }
8961
+ function getInstallDomains(creds2) {
8962
+ return request(creds2, "GET", "/api/v1/install-domains");
8963
+ }
8964
+ function updateInstallDomains(creds2, body5) {
8965
+ return request(creds2, "PATCH", "/api/v1/install-domains", body5);
8966
+ }
8967
+ function getExternalTracking(creds2) {
8968
+ return request(creds2, "GET", "/api/v1/external-tracking");
8969
+ }
8970
+ function updateExternalTracking(creds2, body5) {
8971
+ return request(creds2, "PATCH", "/api/v1/external-tracking", body5);
8972
+ }
8459
8973
  function verifyTracking(creds2, opts) {
8460
8974
  const params = new URLSearchParams();
8461
8975
  if (opts?.minutes) params.set("minutes", String(opts.minutes));
@@ -8521,7 +9035,22 @@ function listAuditLogs(creds2, opts) {
8521
9035
  if (opts.entityId) p.set("entity_id", opts.entityId);
8522
9036
  if (opts.limit) p.set("limit", String(opts.limit));
8523
9037
  if (opts.offset) p.set("offset", String(opts.offset));
8524
- return request(creds2, "GET", `/api/v1/audit-logs${p.size ? `?${p}` : ""}`);
9038
+ const query = p.size ? `?${p}` : "";
9039
+ return request(creds2, "GET", `/api/v1/audit-logs${query}`).then(async (shopResult) => {
9040
+ if (shopResult.ok || shopResult.status !== 401 && shopResult.status !== 403) return shopResult;
9041
+ const shopFailure = `Shop key failed: missing audit_logs:read; create a secret shop key with audit_logs:read.`;
9042
+ if (!creds2.userApiKey) return { ...shopResult, error: shopFailure };
9043
+ if (!opts.shopId) return { ...shopResult, error: `${shopFailure} User key was not tried: pass --shop <shop-id> to select an authorized shop.` };
9044
+ const userResult = await userRequest(creds2, "GET", `/api/v1/shops/${encodeURIComponent(opts.shopId)}/audit-logs${query}`);
9045
+ if (userResult.ok) return userResult;
9046
+ const userPathFailure = `User-key shop audit-log path failed: ${userResult.error}`;
9047
+ if (userResult.status === 403 || userResult.status === 404) {
9048
+ return { ...userResult, error: `${shopFailure} ${userPathFailure} Re-run \`apex login --user\`, select the audit_logs:read scope, and use a member of shop ${opts.shopId}.` };
9049
+ }
9050
+ const staleKeyGuidance = userResult.status === 401 ? " Re-run `apex login --user` and select the audit_logs:read scope." : "";
9051
+ const separator = userResult.status === 401 && !/[.!?]$/.test(userPathFailure) ? "." : "";
9052
+ return { ...userResult, error: `${userPathFailure}${separator}${staleKeyGuidance}` };
9053
+ });
8525
9054
  }
8526
9055
  function listInbox(creds2, experimentId) {
8527
9056
  const q = experimentId ? `?experiment_id=${encodeURIComponent(experimentId)}` : "";
@@ -8568,11 +9097,11 @@ function requestTargetingResource(creds2, method, kind5, id, body5, limit, curso
8568
9097
  if (limit) params.set("limit", String(limit));
8569
9098
  if (cursor) params.set("cursor", cursor);
8570
9099
  const query = params.toString();
8571
- const path20 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
8572
- return request(creds2, method, path20, body5);
9100
+ const path21 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
9101
+ return request(creds2, method, path21, body5);
8573
9102
  }
8574
- function managedPath(kind5, id, action25) {
8575
- return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action25 ? `/${action25}` : ""}`;
9103
+ function managedPath(kind5, id, action68) {
9104
+ return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action68 ? `/${action68}` : ""}`;
8576
9105
  }
8577
9106
  function listManagedDeliveries(creds2, kind5, limit) {
8578
9107
  const q = limit ? `?limit=${limit}` : "";
@@ -8594,8 +9123,8 @@ function createManagedDelivery(creds2, kind5, body5) {
8594
9123
  function updateManagedDelivery(creds2, kind5, id, body5) {
8595
9124
  return request(creds2, "PATCH", managedPath(kind5, id), body5);
8596
9125
  }
8597
- function transitionManagedDelivery(creds2, kind5, id, action25) {
8598
- return request(creds2, "POST", managedPath(kind5, id, action25));
9126
+ function transitionManagedDelivery(creds2, kind5, id, action68) {
9127
+ return request(creds2, "POST", managedPath(kind5, id, action68));
8599
9128
  }
8600
9129
  function listConfigResources(creds2, family, opts) {
8601
9130
  const params = new URLSearchParams();
@@ -8763,6 +9292,24 @@ var postShopArea = (c, id, area, body5) => userRequest(
8763
9292
  `/api/v1/shops/${id}/${area}`,
8764
9293
  body5
8765
9294
  );
9295
+ function listWebhooks(creds2) {
9296
+ return request(creds2, "GET", "/api/v1/webhooks");
9297
+ }
9298
+ function createWebhook(creds2, body5) {
9299
+ return request(creds2, "POST", "/api/v1/webhooks", body5);
9300
+ }
9301
+ function updateWebhook(creds2, id, body5) {
9302
+ return request(creds2, "PATCH", `/api/v1/webhooks/${encodeURIComponent(id)}`, body5);
9303
+ }
9304
+ function deleteWebhook(creds2, id) {
9305
+ return request(creds2, "DELETE", `/api/v1/webhooks/${encodeURIComponent(id)}`);
9306
+ }
9307
+ function getWebhookDeliveries(creds2, id, limit) {
9308
+ return request(creds2, "GET", `/api/v1/webhooks/${encodeURIComponent(id)}/deliveries?limit=${encodeURIComponent(String(limit))}`);
9309
+ }
9310
+ function testWebhook(creds2, id) {
9311
+ return request(creds2, "POST", `/api/v1/webhooks/${encodeURIComponent(id)}/test`);
9312
+ }
8766
9313
 
8767
9314
  // src/ui.ts
8768
9315
  init_source();
@@ -8835,8 +9382,8 @@ function prompt(question, hide = false) {
8835
9382
  }
8836
9383
  });
8837
9384
  }
8838
- function printValidationFailure(res) {
8839
- printError(`${res.error ?? `HTTP ${res.status}`} (HTTP ${res.status})`);
9385
+ function printValidationFailure(res, remedy = "") {
9386
+ printError(`${res.error ?? `HTTP ${res.status}`} (HTTP ${res.status})${remedy}`);
8840
9387
  if (res.status === 429) {
8841
9388
  printWarning(
8842
9389
  "The API key reached Apex, but the validation request was rate-limited. If this happens on first login, check the server-side rate-limit backend/configuration."
@@ -9021,7 +9568,7 @@ var loginCommand = new Command("login").description("Authenticate with Apex").op
9021
9568
  spinner.fail(
9022
9569
  res.status === 401 ? "Authentication failed" : "API key validation failed"
9023
9570
  );
9024
- printValidationFailure(res);
9571
+ printValidationFailure(res, " Run `apex login`.");
9025
9572
  fail(this, res.error ?? "API key validation failed", classifyStatus(res.status), res.errorDetails);
9026
9573
  }
9027
9574
  spinner.succeed("Authenticated successfully");
@@ -9029,7 +9576,13 @@ var loginCommand = new Command("login").description("Authenticate with Apex").op
9029
9576
  output(this, { baseUrl, credentialsFile: "~/.drip/credentials.json" }, () => {
9030
9577
  printSuccess(`Credentials saved to ~/.drip/credentials.json`);
9031
9578
  });
9032
- });
9579
+ }).addHelpText("after", `
9580
+ Examples:
9581
+ apex login
9582
+ apex login --user
9583
+ apex login --api-key apx_your_key_here
9584
+ apex login --base-url https://app.drip-apex.com
9585
+ `);
9033
9586
  var logoutCommand = new Command("logout").description("Remove stored credentials").option("--user", "Remove only the stored user API key").action(function action2(opts) {
9034
9587
  const existing = getStoredCredentials();
9035
9588
  if (!existing) {
@@ -9072,7 +9625,7 @@ var whoamiCommand = new Command("whoami").description("Show current authenticati
9072
9625
  if (res?.ok) {
9073
9626
  printSuccess("Key is valid");
9074
9627
  } else {
9075
- if (res) printValidationFailure(res);
9628
+ if (res) printValidationFailure(res, " Run `apex login`.");
9076
9629
  }
9077
9630
  } else {
9078
9631
  console.log("API Key: none (user credential only)");
@@ -9080,7 +9633,7 @@ var whoamiCommand = new Command("whoami").description("Show current authenticati
9080
9633
  if (creds2.userApiKey) {
9081
9634
  console.log(`User Key: ${source_default.dim(creds2.userApiKey.slice(0, 11) + "..." + creds2.userApiKey.slice(-4))}`);
9082
9635
  if (userRes?.ok) console.log(`User Org: ${source_default.cyan(userRes.data.data.org_id)} (${userRes.data.data.org_role})`);
9083
- else printWarning("User key could not be verified.");
9636
+ else printWarning("User key could not be verified. Run `apex login --user`.");
9084
9637
  }
9085
9638
  });
9086
9639
  });
@@ -9119,22 +9672,40 @@ function printExperiment(exp) {
9119
9672
  function formatPct(value, digits = 1) {
9120
9673
  return typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(digits)}%` : "-";
9121
9674
  }
9675
+ function confidenceBasisLabel(value) {
9676
+ if (value === "fixed_horizon") return "fixed-horizon";
9677
+ if (value === "sequential") return "always-valid";
9678
+ return "unknown";
9679
+ }
9680
+ function statisticsStateLabel(value) {
9681
+ return value === "untrustworthy" ? "verdict withheld" : String(value ?? "unknown");
9682
+ }
9122
9683
  function printExperimentResults(results2) {
9123
9684
  const experiment = results2.experiment ?? {};
9124
9685
  const totals = results2.totals ?? {};
9125
9686
  const decision = results2.decision ?? {};
9687
+ const effectiveOutcome = results2.effective_outcome ?? {};
9688
+ const outcomeProvenance = effectiveOutcome.provenance === "manager_decided" ? " (manager decided)" : "";
9689
+ const verdictWithheld = decision.state === "untrustworthy";
9690
+ const statisticsState = verdictWithheld ? decision.state : effectiveOutcome.statisticsOutcome ?? decision.state ?? "unknown";
9691
+ const presentedOutcome = verdictWithheld && effectiveOutcome.provenance !== "manager_decided" ? decision.state : effectiveOutcome.outcome ?? decision.state ?? "unknown";
9126
9692
  console.log();
9127
9693
  console.log(source_default.bold(String(experiment.name ?? experiment.id ?? "Experiment results")));
9128
9694
  console.log(` Status: ${statusBadge(String(experiment.status ?? "unknown"))}`);
9129
9695
  console.log(` Visitors: ${String(totals.visitors ?? 0)}`);
9130
9696
  console.log(` Conversions: ${String(totals.primaryConversions ?? 0)}`);
9131
9697
  console.log(` Revenue: ${String(totals.revenue ?? 0)}`);
9132
- console.log(` Decision: ${String(decision.state ?? "unknown")}`);
9698
+ console.log(` Outcome: ${statisticsStateLabel(presentedOutcome)}${outcomeProvenance}`);
9699
+ console.log(
9700
+ ` Statistics: ${statisticsStateLabel(statisticsState)}`
9701
+ );
9133
9702
  if (decision.treatmentName) {
9134
9703
  console.log(` Treatment: ${String(decision.treatmentName)}`);
9135
9704
  }
9136
- console.log(` Lift: ${formatPct(decision.lift)}`);
9137
- console.log(` Confidence: ${formatPct(decision.sequentialConfidence)}`);
9705
+ console.log(` Stats lift: ${formatPct(decision.lift)}`);
9706
+ console.log(
9707
+ ` Confidence (${confidenceBasisLabel(decision.basis)}): ${verdictWithheld ? "withheld \u2014 not decision-grade" : formatPct(decision.judgedConfidence)}`
9708
+ );
9138
9709
  const arms = Array.isArray(results2.arms) ? results2.arms : [];
9139
9710
  if (arms.length > 0) {
9140
9711
  console.log();
@@ -9164,6 +9735,7 @@ var EXPERIMENT_FILESYSTEM_PATH_SCHEMA = {
9164
9735
  variation: [
9165
9736
  "variation.json",
9166
9737
  "mutations.json",
9738
+ "url-blocks.json",
9167
9739
  "index.css",
9168
9740
  "index.js",
9169
9741
  "reset.js",
@@ -9204,6 +9776,10 @@ function writeFilesystemSession(folder, session) {
9204
9776
  function readFilesystemSession(folder) {
9205
9777
  const filePath = filesystemSessionPath(folder);
9206
9778
  if (!import_node_fs6.default.existsSync(filePath)) {
9779
+ const workspaceMarker = import_node_path4.default.join(filesystemApexDir(folder), "session.json");
9780
+ if (import_node_fs6.default.existsSync(workspaceMarker)) {
9781
+ throw new Error("This folder is an operator workspace (apex workspace \u2026), not an experiment filesystem \u2014 run `apex workspace status` here, or `apex experiments pull` into a different folder.");
9782
+ }
9207
9783
  throw new Error(`Missing ${filePath}. Run apex experiments pull first.`);
9208
9784
  }
9209
9785
  return JSON.parse(import_node_fs6.default.readFileSync(filePath, "utf8"));
@@ -9269,47 +9845,50 @@ function printFilesystemPlan(plan) {
9269
9845
  console.log(source_default.yellow("\nWarnings"));
9270
9846
  plan.warnings.forEach((warning) => console.log(` - ${warning}`));
9271
9847
  }
9848
+ if ((plan.blockingIssues?.length ?? 0) > 0) {
9849
+ console.log(source_default.red("\nBlocking issues"));
9850
+ (plan.blockingIssues ?? []).forEach((issue) => {
9851
+ const context = [issue.path, issue.variation ? `variation: ${issue.variation}` : null].filter(Boolean).join(", ");
9852
+ console.log(` - [${issue.code}] ${issue.message}${context ? ` (${context})` : ""}`);
9853
+ });
9854
+ }
9272
9855
  console.log(source_default.bold("\nChanges"));
9273
9856
  if (plan.changes.length === 0) {
9274
9857
  console.log(source_default.dim(" No changes."));
9275
9858
  } else {
9276
9859
  plan.changes.forEach((change) => {
9277
9860
  const pathLabel = change.path ? source_default.dim(` ${change.path}`) : "";
9278
- const qa2 = change.invalidatesQa ? source_default.yellow(" invalidates QA") : "";
9279
- console.log(` ${source_default.cyan(change.action)} ${change.entity}${pathLabel} \u2014 ${change.summary}${qa2}`);
9861
+ const qa3 = change.invalidatesQa ? source_default.yellow(" invalidates QA") : "";
9862
+ console.log(` ${source_default.cyan(change.action)} ${change.entity}${pathLabel} \u2014 ${change.summary}${qa3}`);
9280
9863
  });
9281
9864
  }
9282
9865
  console.log();
9283
9866
  console.log(`QA invalidated: ${plan.invalidatesQa ? source_default.yellow("yes") : "no"}`);
9284
9867
  console.log(`Precompile: ${plan.precompileRequired ? "yes" : "no"}`);
9285
9868
  }
9286
- function printQaRun(run2, json = false) {
9287
- if (json) {
9288
- console.log(JSON.stringify({ run: run2 }, null, 2));
9289
- return;
9290
- }
9869
+ function printQaRun(run3) {
9291
9870
  console.log();
9292
- console.log(source_default.bold(`QA run ${run2.id}`));
9293
- console.log(` Status: ${statusBadge(run2.summary.status)}`);
9871
+ console.log(source_default.bold(`QA run ${run3.id}`));
9872
+ console.log(` Status: ${statusBadge(run3.summary.status)}`);
9294
9873
  console.log(
9295
- ` Required: ${run2.summary.completedRequired}/${run2.summary.required}`
9874
+ ` Required: ${run3.summary.completedRequired}/${run3.summary.required}`
9296
9875
  );
9297
- if (run2.approvedAt) console.log(` Approved: ${run2.approvedAt}`);
9298
- if (run2.completedAt) console.log(` Complete: ${run2.completedAt}`);
9299
- if (run2.summary.blockers.length > 0) {
9876
+ if (run3.approvedAt) console.log(` Approved: ${run3.approvedAt}`);
9877
+ if (run3.completedAt) console.log(` Complete: ${run3.completedAt}`);
9878
+ if (run3.summary.blockers.length > 0) {
9300
9879
  console.log(source_default.yellow("\nBlockers"));
9301
- run2.summary.blockers.forEach((blocker) => console.log(` - ${blocker}`));
9880
+ run3.summary.blockers.forEach((blocker) => console.log(` - ${blocker}`));
9302
9881
  }
9303
9882
  console.log(source_default.bold("\nChecklist"));
9304
- for (const item of run2.checklist) {
9883
+ for (const item of run3.checklist) {
9305
9884
  const required = item.required ? source_default.yellow(" required") : "";
9306
9885
  const notes = item.notes ? source_default.dim(` \u2014 ${item.notes}`) : "";
9307
9886
  console.log(` ${statusBadge(item.status)} ${item.id}${required}`);
9308
9887
  console.log(` ${item.title}${notes}`);
9309
9888
  }
9310
- if (run2.urls.length > 0) {
9889
+ if (run3.urls.length > 0) {
9311
9890
  console.log(source_default.bold("\nQA URLs"));
9312
- run2.urls.forEach((url) => {
9891
+ run3.urls.forEach((url) => {
9313
9892
  console.log(` - ${url.label}: ${source_default.cyan(url.url)}`);
9314
9893
  });
9315
9894
  }
@@ -9363,18 +9942,21 @@ async function resolveExperimentForFilesystemOrId(value) {
9363
9942
  }
9364
9943
  async function refreshExperimentFilesystemFolderIfNeeded(creds2, folder, experimentId) {
9365
9944
  if (!folder) return;
9366
- const refreshed = await getExperimentFilesystem(creds2, experimentId);
9367
- if (refreshed.ok && refreshed.data) {
9368
- writeExperimentFilesystemFiles(folder, refreshed.data.files);
9369
- writeFilesystemSession(folder, {
9370
- schemaVersion: 1,
9371
- baseUrl: creds2.baseUrl,
9372
- experimentId,
9373
- pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
9374
- files: Object.fromEntries(
9375
- refreshed.data.files.map((file) => [file.path, { sha256: file.sha256 }])
9376
- )
9377
- });
9945
+ try {
9946
+ const refreshed = await getExperimentFilesystem(creds2, experimentId);
9947
+ if (refreshed.ok && refreshed.data) {
9948
+ writeExperimentFilesystemFiles(folder, refreshed.data.files);
9949
+ writeFilesystemSession(folder, {
9950
+ schemaVersion: 1,
9951
+ baseUrl: creds2.baseUrl,
9952
+ experimentId,
9953
+ pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
9954
+ files: Object.fromEntries(
9955
+ refreshed.data.files.map((file) => [file.path, { sha256: file.sha256 }])
9956
+ )
9957
+ });
9958
+ }
9959
+ } catch {
9378
9960
  }
9379
9961
  }
9380
9962
  async function resolveExperiment(idOrSlug) {
@@ -9382,20 +9964,29 @@ async function resolveExperiment(idOrSlug) {
9382
9964
  if (/^[0-9a-f-]{36}$/i.test(idOrSlug)) {
9383
9965
  const res2 = await getExperiment(creds2, idOrSlug);
9384
9966
  if (res2.ok) return res2.data;
9385
- return null;
9967
+ throw new CliError(
9968
+ res2.error ?? `Experiment "${idOrSlug}" not found`,
9969
+ classifyStatus(res2.status),
9970
+ res2.errorDetails
9971
+ );
9386
9972
  }
9387
9973
  const slug = idOrSlug.startsWith("slug:") ? idOrSlug.slice(5) : idOrSlug;
9388
9974
  const res = await getExperimentBySlug(creds2, slug);
9389
9975
  if (res.ok) return res.data;
9390
- return null;
9976
+ throw new CliError(
9977
+ res.error ?? `Experiment "${idOrSlug}" not found`,
9978
+ classifyStatus(res.status),
9979
+ res.errorDetails
9980
+ );
9391
9981
  }
9392
- var list = new Command("list").alias("ls").description("List experiments").option("-s, --status <status>", "Filter by status (draft|running|paused|completed)").option("-n, --limit <n>", "Max results", "20").action(async function action4(opts) {
9982
+ var list = new Command("list").alias("ls").description("List experiments (archived experiments are hidden by default)").option("-s, --status <status>", "Filter by status (draft|running|paused|completed)").option("-n, --limit <n>", "Max results", "20").option("--include-archived", "Include archived experiments").action(async function action4(opts) {
9393
9983
  const creds2 = requireCredentials();
9394
9984
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9395
9985
  const spinner = process.stderr.isTTY ? ora2("Fetching experiments...").start() : null;
9396
9986
  const res = await listExperiments(creds2, {
9397
9987
  status: opts.status,
9398
- limit: parseInt(opts.limit, 10)
9988
+ limit: parseInt(opts.limit, 10),
9989
+ includeArchived: opts.includeArchived
9399
9990
  });
9400
9991
  if (!res.ok) {
9401
9992
  spinner?.fail("Failed to fetch experiments");
@@ -9409,15 +10000,16 @@ var list = new Command("list").alias("ls").description("List experiments").optio
9409
10000
  return;
9410
10001
  }
9411
10002
  printTable(
9412
- ["NAME", "SLUG", "STATUS", "TRAFFIC", "CREATED"],
10003
+ ["NAME", "SLUG", "STATUS", "ARCHIVED", "TRAFFIC", "CREATED"],
9413
10004
  experiments.map((e) => [
9414
10005
  e.name.slice(0, 40),
9415
10006
  e.slug ?? source_default.dim("-"),
9416
10007
  statusBadge(e.status),
10008
+ e.archived_at ? formatDate(e.archived_at) : source_default.dim("-"),
9417
10009
  `${(e.traffic_allocation * 100).toFixed(0)}%`,
9418
10010
  formatDate(e.created_at)
9419
10011
  ]),
9420
- [40, 25, 12, 8, 15]
10012
+ [40, 25, 12, 15, 8, 15]
9421
10013
  );
9422
10014
  if (res.data.pagination.has_more) {
9423
10015
  console.log(source_default.dim(`
@@ -9587,66 +10179,66 @@ var create = new Command("create").description("Create a new experiment").requir
9587
10179
  spinner?.succeed("Experiment created");
9588
10180
  output(this, { experiment: res.data }, () => printExperiment(res.data));
9589
10181
  });
9590
- var start = new Command("start").description("Start an experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async (idOrSlug) => {
10182
+ var start = new Command("start").description("Start an experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function action8(idOrSlug) {
9591
10183
  const creds2 = requireCredentials();
9592
10184
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9593
10185
  const spinner = ora2("Starting experiment...").start();
9594
10186
  const exp = await resolveExperiment(idOrSlug);
9595
10187
  if (!exp) {
9596
10188
  spinner.fail(`Experiment "${idOrSlug}" not found`);
9597
- process.exit(1);
10189
+ fail(this, `Experiment "${idOrSlug}" not found`);
9598
10190
  }
9599
10191
  const res = await startExperiment(creds2, exp.id);
9600
10192
  if (!res.ok) {
9601
10193
  spinner.fail("Failed to start experiment");
9602
- printError(res.error);
9603
- process.exit(1);
10194
+ fail(this, res.error ?? "Failed to start experiment", classifyStatus(res.status), res.errorDetails);
9604
10195
  }
9605
- spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("running")}`);
10196
+ output(this, { experiment: { id: exp.id, name: exp.name, status: "running" } }, () => {
10197
+ spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("running")}`);
10198
+ });
9606
10199
  });
9607
- var pause = new Command("pause").description("Pause a running experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async (idOrSlug) => {
10200
+ var pause = new Command("pause").description("Pause a running experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function action9(idOrSlug) {
9608
10201
  const creds2 = requireCredentials();
9609
10202
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9610
10203
  const spinner = ora2("Pausing experiment...").start();
9611
10204
  const exp = await resolveExperiment(idOrSlug);
9612
10205
  if (!exp) {
9613
10206
  spinner.fail(`Experiment "${idOrSlug}" not found`);
9614
- process.exit(1);
10207
+ fail(this, `Experiment "${idOrSlug}" not found`);
9615
10208
  }
9616
10209
  const res = await pauseExperiment(creds2, exp.id);
9617
10210
  if (!res.ok) {
9618
10211
  spinner.fail("Failed to pause experiment");
9619
- printError(res.error);
9620
- process.exit(1);
10212
+ fail(this, res.error ?? "Failed to pause experiment", classifyStatus(res.status), res.errorDetails);
9621
10213
  }
9622
- spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("paused")}`);
10214
+ output(this, { experiment: { id: exp.id, name: exp.name, status: "paused" } }, () => {
10215
+ spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("paused")}`);
10216
+ });
9623
10217
  });
9624
- var complete = new Command("complete").description("Complete/stop an experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async (idOrSlug) => {
10218
+ var complete = new Command("complete").description("Complete/stop an experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function action10(idOrSlug) {
9625
10219
  const creds2 = requireCredentials();
9626
10220
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9627
10221
  const spinner = ora2("Completing experiment...").start();
9628
10222
  const exp = await resolveExperiment(idOrSlug);
9629
10223
  if (!exp) {
9630
10224
  spinner.fail(`Experiment "${idOrSlug}" not found`);
9631
- process.exit(1);
10225
+ fail(this, `Experiment "${idOrSlug}" not found`);
9632
10226
  }
9633
10227
  const res = await completeExperiment(creds2, exp.id);
9634
10228
  if (!res.ok) {
9635
10229
  spinner.fail("Failed to complete experiment");
9636
- printError(res.error);
9637
- process.exit(1);
10230
+ fail(this, res.error ?? "Failed to complete experiment", classifyStatus(res.status), res.errorDetails);
9638
10231
  }
9639
- spinner.succeed(
9640
- `Experiment "${exp.name}" is now ${statusBadge("completed")}`
9641
- );
10232
+ output(this, { experiment: { id: exp.id, name: exp.name, status: "completed" } }, () => {
10233
+ spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("completed")}`);
10234
+ });
9642
10235
  });
9643
- var del = new Command("delete").alias("rm").description("Delete an experiment").argument("<id-or-slug>", "Experiment ID or slug").option("-f, --force", "Skip confirmation").action(async function action8(idOrSlug, opts) {
10236
+ var del = new Command("delete").alias("rm").description("Delete an experiment").argument("<id-or-slug>", "Experiment ID or slug").option("-f, --force", "Skip confirmation").action(async function action11(idOrSlug, opts) {
9644
10237
  const creds2 = requireCredentials();
9645
10238
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9646
10239
  const exp = await resolveExperiment(idOrSlug);
9647
10240
  if (!exp) {
9648
- printError(`Experiment "${idOrSlug}" not found.`);
9649
- process.exit(1);
10241
+ fail(this, `Experiment "${idOrSlug}" not found.`);
9650
10242
  }
9651
10243
  if (!opts.force) {
9652
10244
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
@@ -9679,21 +10271,20 @@ var del = new Command("delete").alias("rm").description("Delete an experiment").
9679
10271
  const res = await deleteExperiment(creds2, exp.id);
9680
10272
  if (!res.ok) {
9681
10273
  spinner.fail("Failed to delete experiment");
9682
- printError(res.error);
9683
- process.exit(1);
10274
+ fail(this, res.error ?? "Failed to delete experiment", classifyStatus(res.status), res.errorDetails);
9684
10275
  }
9685
- spinner.succeed(`Experiment "${exp.name}" deleted.`);
10276
+ output(this, { experiment: { id: exp.id, name: exp.name }, deleted: true }, () => spinner.succeed(`Experiment "${exp.name}" deleted.`));
9686
10277
  });
9687
- var pullFilesystem = new Command("pull").description("Pull an experiment as an editable filesystem").argument("<id-or-slug>", "Experiment ID or slug").option("--dir <folder>", "Target folder").action(async (idOrSlug, opts) => {
10278
+ var pullFilesystem = new Command("pull").description("Pull an experiment as an editable filesystem").argument("<id-or-slug>", "Experiment ID or slug").option("--out <path>", "Target folder").addOption(new Option("--dir <folder>", "Legacy alias for --out").hideHelp()).action(async function action12(idOrSlug, opts) {
9688
10279
  const creds2 = requireCredentials();
9689
10280
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9690
10281
  const spinner = ora2("Pulling experiment filesystem...").start();
9691
10282
  try {
9692
10283
  const exp = await resolveExperiment(idOrSlug);
9693
- if (!exp) throw new Error(`Experiment "${idOrSlug}" not found.`);
10284
+ if (!exp) throw new CliError(`Experiment "${idOrSlug}" not found.`);
9694
10285
  const res = await getExperimentFilesystem(creds2, exp.id);
9695
- if (!res.ok || !res.data) throw new Error(res.error ?? "Failed to pull experiment filesystem");
9696
- const folder = folderPath(opts.dir ?? res.data.rootName);
10286
+ if (!res.ok || !res.data) throw new CliError(res.error ?? "Failed to pull experiment filesystem", classifyStatus(res.status), res.errorDetails);
10287
+ const folder = folderPath(opts.out ?? opts.dir ?? res.data.rootName);
9697
10288
  writeExperimentFilesystemFiles(folder, res.data.files);
9698
10289
  writeFilesystemSession(folder, {
9699
10290
  schemaVersion: 1,
@@ -9704,15 +10295,16 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
9704
10295
  res.data.files.map((file) => [file.path, { sha256: file.sha256 }])
9705
10296
  )
9706
10297
  });
9707
- spinner.succeed(`Pulled ${exp.name}`);
9708
- console.log(source_default.cyan(folder));
10298
+ output(this, { folder, experimentId: exp.id, files: res.data.files.map((file) => import_node_path4.default.join(folder, file.path)) }, () => {
10299
+ spinner.succeed(`Pulled ${exp.name}`);
10300
+ console.log(source_default.cyan(folder));
10301
+ });
9709
10302
  } catch (error) {
9710
10303
  spinner.fail("Failed to pull experiment filesystem");
9711
- printError(error instanceof Error ? error.message : String(error));
9712
- process.exit(1);
10304
+ throw error;
9713
10305
  }
9714
10306
  });
9715
- var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function action9(folderArg) {
10307
+ var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function action13(folderArg) {
9716
10308
  const creds2 = requireCredentials();
9717
10309
  try {
9718
10310
  const folder = folderPath(folderArg);
@@ -9723,7 +10315,9 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
9723
10315
  fail(this, res.error ?? "Failed to plan experiment filesystem", classifyStatus(res.status), res.errorDetails);
9724
10316
  }
9725
10317
  if (!res.data.ok) {
9726
- fail(this, "Experiment filesystem plan has errors.", EXIT_CODES.validation, {
10318
+ if (!shouldUseJson(this)) printFilesystemPlan(res.data);
10319
+ const reasons = res.data.blockingIssues?.map((issue) => issue.message) ?? [];
10320
+ fail(this, ["Experiment filesystem plan has errors.", ...reasons].join(" "), EXIT_CODES.validation, {
9727
10321
  errors: res.data.errors,
9728
10322
  plan: res.data
9729
10323
  });
@@ -9734,7 +10328,7 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
9734
10328
  fail(this, error instanceof Error ? error.message : String(error));
9735
10329
  }
9736
10330
  });
9737
- var applyFilesystem = new Command("apply").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option("-f, --force", "Skip confirmation when QA will be invalidated").action(async function action10(folderArg, opts) {
10331
+ var applyFilesystem = new Command("apply").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option("-f, --force", "Skip confirmation when QA will be invalidated").action(async function action14(folderArg, opts) {
9738
10332
  const creds2 = requireCredentials();
9739
10333
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9740
10334
  try {
@@ -9746,7 +10340,9 @@ var applyFilesystem = new Command("apply").description("Apply experiment filesys
9746
10340
  fail(this, planRes.error ?? "Failed to plan experiment filesystem", classifyStatus(planRes.status), planRes.errorDetails);
9747
10341
  }
9748
10342
  if (!planRes.data.ok) {
9749
- fail(this, "Experiment filesystem plan has errors.", EXIT_CODES.validation, {
10343
+ if (!shouldUseJson(this)) printFilesystemPlan(planRes.data);
10344
+ const reasons = planRes.data.blockingIssues?.map((issue) => issue.message) ?? [];
10345
+ fail(this, ["Experiment filesystem plan has errors.", ...reasons].join(" "), EXIT_CODES.validation, {
9750
10346
  errors: planRes.data.errors,
9751
10347
  plan: planRes.data
9752
10348
  });
@@ -9785,19 +10381,11 @@ var applyFilesystem = new Command("apply").description("Apply experiment filesys
9785
10381
  });
9786
10382
  }
9787
10383
  spinner?.succeed("Applied experiment filesystem");
9788
- const refreshed = await getExperimentFilesystem(creds2, session.experimentId);
9789
- if (refreshed.ok && refreshed.data) {
9790
- writeExperimentFilesystemFiles(folder, refreshed.data.files);
9791
- writeFilesystemSession(folder, {
9792
- schemaVersion: 1,
9793
- baseUrl: creds2.baseUrl,
9794
- experimentId: session.experimentId,
9795
- pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
9796
- files: Object.fromEntries(
9797
- refreshed.data.files.map((file) => [file.path, { sha256: file.sha256 }])
9798
- )
9799
- });
9800
- }
10384
+ await refreshExperimentFilesystemFolderIfNeeded(
10385
+ creds2,
10386
+ folder,
10387
+ session.experimentId
10388
+ );
9801
10389
  output(this, { folder, experimentId: session.experimentId, applied: true, plan: planRes.data, result: res.data }, () => {
9802
10390
  printFilesystemPlan(planRes.data);
9803
10391
  });
@@ -9806,29 +10394,28 @@ var applyFilesystem = new Command("apply").description("Apply experiment filesys
9806
10394
  fail(this, error instanceof Error ? error.message : String(error));
9807
10395
  }
9808
10396
  });
9809
- var qaStatus = new Command("status").description("Show or create the latest experiment QA run").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").option("--json", "Print raw QA run JSON").action(async (target, opts) => {
10397
+ var qaStatus = new Command("status").description("Show or create the latest experiment QA run").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").option("--json", "Print the QA run in the standard JSON envelope").action(async function action15(target) {
9810
10398
  const creds2 = requireCredentials();
9811
10399
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9812
10400
  const spinner = ora2("Fetching QA run...").start();
9813
10401
  try {
9814
10402
  const resolved = await resolveExperimentForFilesystemOrId(target);
9815
10403
  const res = await getExperimentQaRun(creds2, resolved.experimentId);
9816
- if (!res.ok || !res.data) throw new Error(res.error ?? "Failed to fetch QA run");
10404
+ if (!res.ok || !res.data) throw new CliError(res.error ?? "Failed to fetch QA run", classifyStatus(res.status), res.errorDetails);
9817
10405
  spinner.stop();
9818
- printQaRun(res.data.run, opts.json === true);
9819
10406
  await refreshExperimentFilesystemFolderIfNeeded(
9820
10407
  creds2,
9821
10408
  resolved.folder,
9822
10409
  resolved.experimentId
9823
10410
  );
10411
+ output(this, { run: res.data.run }, () => printQaRun(res.data.run));
9824
10412
  } catch (error) {
9825
10413
  spinner.fail("Failed to fetch QA run");
9826
- printError(error instanceof Error ? error.message : String(error));
9827
- process.exit(1);
10414
+ throw error;
9828
10415
  }
9829
10416
  });
9830
10417
  var qaCheck = new Command("check").description("Update one QA checklist item with status, notes, and evidence").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").requiredOption("--item <id>", "Checklist item id").requiredOption("--status <status>", "pending|pass|warning|fail|accepted|skipped").option("--notes <text>", "Checklist notes").option("--evidence <json-or-path>", "Evidence JSON object or @path/to/evidence.json").option("--json", "Print raw QA run JSON").action(
9831
- async (target, opts) => {
10418
+ async function action16(target, opts) {
9832
10419
  const creds2 = requireCredentials();
9833
10420
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9834
10421
  const spinner = ora2("Updating QA checklist...").start();
@@ -9849,23 +10436,25 @@ var qaCheck = new Command("check").description("Update one QA checklist item wit
9849
10436
  notes: opts.notes,
9850
10437
  evidence
9851
10438
  });
9852
- if (!res.ok || !res.data) throw new Error(res.error ?? "Failed to update QA run");
9853
- spinner.succeed("Updated QA checklist");
9854
- printQaRun(res.data.run, opts.json === true);
10439
+ if (!res.ok || !res.data) throw new CliError(res.error ?? "Failed to update QA run", classifyStatus(res.status), res.errorDetails);
10440
+ const run3 = res.data.run;
9855
10441
  await refreshExperimentFilesystemFolderIfNeeded(
9856
10442
  creds2,
9857
10443
  resolved.folder,
9858
10444
  resolved.experimentId
9859
10445
  );
10446
+ output(this, { run: run3 }, () => {
10447
+ spinner.succeed("Updated QA checklist");
10448
+ printQaRun(run3);
10449
+ });
9860
10450
  } catch (error) {
9861
10451
  spinner.fail("Failed to update QA checklist");
9862
- printError(error instanceof Error ? error.message : String(error));
9863
- process.exit(1);
10452
+ throw error;
9864
10453
  }
9865
10454
  }
9866
10455
  );
9867
10456
  var qaNotes = new Command("notes").description("Update QA run notes or acceptance criteria summary").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").option("--notes <text>", "QA run notes").option("--acceptance <text>", "Acceptance criteria summary").option("--json", "Print raw QA run JSON").action(
9868
- async (target, opts) => {
10457
+ async function action17(target, opts) {
9869
10458
  const creds2 = requireCredentials();
9870
10459
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9871
10460
  const spinner = ora2("Updating QA notes...").start();
@@ -9883,64 +10472,175 @@ var qaNotes = new Command("notes").description("Update QA run notes or acceptanc
9883
10472
  acceptanceCriteriaSummary: opts.acceptance ?? null
9884
10473
  };
9885
10474
  const res = await updateExperimentQaRun(creds2, resolved.experimentId, body5);
9886
- if (!res.ok || !res.data) throw new Error(res.error ?? "Failed to update QA notes");
9887
- spinner.succeed("Updated QA notes");
9888
- printQaRun(res.data.run, opts.json === true);
10475
+ if (!res.ok || !res.data) throw new CliError(res.error ?? "Failed to update QA notes", classifyStatus(res.status), res.errorDetails);
10476
+ const run3 = res.data.run;
9889
10477
  await refreshExperimentFilesystemFolderIfNeeded(
9890
10478
  creds2,
9891
10479
  resolved.folder,
9892
10480
  resolved.experimentId
9893
10481
  );
10482
+ output(this, { run: run3 }, () => {
10483
+ spinner.succeed("Updated QA notes");
10484
+ printQaRun(run3);
10485
+ });
9894
10486
  } catch (error) {
9895
10487
  spinner.fail("Failed to update QA notes");
9896
- printError(error instanceof Error ? error.message : String(error));
9897
- process.exit(1);
10488
+ throw error;
9898
10489
  }
9899
10490
  }
9900
10491
  );
9901
- var qaApprove = new Command("approve").description("Mark QA done after the checklist gates are ready").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").action(async (target) => {
10492
+ var qaApprove = new Command("approve").description("Mark QA done after the checklist gates are ready").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").action(async function action18(target) {
9902
10493
  const creds2 = requireCredentials();
9903
10494
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9904
10495
  const spinner = ora2("Approving QA...").start();
9905
10496
  try {
9906
10497
  const resolved = await resolveExperimentForFilesystemOrId(target);
9907
10498
  const res = await approveExperimentQa(creds2, resolved.experimentId);
9908
- if (!res.ok) throw new Error(res.error ?? "Failed to approve QA");
9909
- spinner.succeed(`QA marked done for ${resolved.label}`);
10499
+ if (!res.ok) throw new CliError(res.error ?? "Failed to approve QA", classifyStatus(res.status), res.errorDetails);
9910
10500
  await refreshExperimentFilesystemFolderIfNeeded(
9911
10501
  creds2,
9912
10502
  resolved.folder,
9913
10503
  resolved.experimentId
9914
10504
  );
10505
+ output(this, { experimentId: resolved.experimentId, qaStatus: "done" }, () => spinner.succeed(`QA marked done for ${resolved.label}`));
9915
10506
  } catch (error) {
9916
10507
  spinner.fail("Failed to approve QA");
9917
- printError(error instanceof Error ? error.message : String(error));
9918
- process.exit(1);
10508
+ throw error;
9919
10509
  }
9920
10510
  });
9921
- var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled launch state").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").action(async (target) => {
10511
+ var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled launch state").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").action(async function action19(target) {
9922
10512
  const creds2 = requireCredentials();
9923
10513
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
9924
10514
  const spinner = ora2("Reopening QA...").start();
9925
10515
  try {
9926
10516
  const resolved = await resolveExperimentForFilesystemOrId(target);
9927
10517
  const res = await reopenExperimentQa(creds2, resolved.experimentId);
9928
- if (!res.ok) throw new Error(res.error ?? "Failed to reopen QA");
9929
- spinner.succeed(`QA reopened for ${resolved.label}`);
10518
+ if (!res.ok) throw new CliError(res.error ?? "Failed to reopen QA", classifyStatus(res.status), res.errorDetails);
9930
10519
  await refreshExperimentFilesystemFolderIfNeeded(
9931
10520
  creds2,
9932
10521
  resolved.folder,
9933
10522
  resolved.experimentId
9934
10523
  );
10524
+ output(this, { experimentId: resolved.experimentId, qaStatus: "open" }, () => spinner.succeed(`QA reopened for ${resolved.label}`));
9935
10525
  } catch (error) {
9936
10526
  spinner.fail("Failed to reopen QA");
9937
- printError(error instanceof Error ? error.message : String(error));
9938
- process.exit(1);
10527
+ throw error;
9939
10528
  }
9940
10529
  });
9941
10530
  var qa = new Command("qa").description("Manage experiment QA runs from the CLI").addCommand(qaStatus).addCommand(qaCheck).addCommand(qaNotes).addCommand(qaApprove).addCommand(qaReopen);
9942
- var experimentsCommand = new Command("experiments").alias("exp").description("Manage experiments").addCommand(list).addCommand(get).addCommand(results).addCommand(create).addCommand(start).addCommand(pause).addCommand(complete).addCommand(del).addCommand(pullFilesystem).addCommand(planFilesystem).addCommand(applyFilesystem).addCommand(qa);
9943
-
10531
+ var SCREENSHOT_DEVICES = ["desktop_chrome", "iphone_15_pro", "galaxy_s24", "ipad_pro", "desktop_safari"];
10532
+ var safeFilenamePart = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "screenshot";
10533
+ var commaList = (value) => value.split(",").map((item) => item.trim()).filter(Boolean);
10534
+ var screenshots = new Command("screenshots").description("Force-assign every capture, including control, to its variation server-side for deterministic screenshots").argument("<experimentRef>", "Experiment ID or slug").option("--url <target>", "Target URL override (not supported by the current v1 API)").option("--devices <list>", `Comma-separated devices (default: ${SCREENSHOT_DEVICES.join(",")})`).option("--variations <list>", "Comma-separated treatment IDs, slugs, or names; control is always included and force-assigned").option("--out <dir>", "Download directory", ".").option("--json", "Print the completed raw job payload with signed URLs").option("--timeout <seconds>", "Maximum wait for completion; finished results are always downloaded", "300").action(async function action20(experimentRef, opts) {
10535
+ if (opts.url) fail(this, "--url is not supported by the current screenshot v1 API.", EXIT_CODES.validation);
10536
+ const timeoutSeconds = Number(opts.timeout);
10537
+ if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) fail(this, "--timeout must be a positive number of seconds.", EXIT_CODES.validation);
10538
+ const experiment = await resolveExperiment(experimentRef);
10539
+ if (!experiment?.variations?.length) fail(this, "Experiment has no variations to capture.", EXIT_CODES.validation);
10540
+ const devices = opts.devices ? commaList(opts.devices) : SCREENSHOT_DEVICES;
10541
+ if (!devices.length) fail(this, "--devices must contain at least one device.", EXIT_CODES.validation);
10542
+ const unknownDevices = devices.filter((device) => !SCREENSHOT_DEVICES.includes(device));
10543
+ if (unknownDevices.length) fail(this, `Unknown screenshot device(s): ${unknownDevices.join(", ")}`, EXIT_CODES.validation);
10544
+ const requested = opts.variations ? commaList(opts.variations) : [];
10545
+ const control = experiment.variations.find((variation) => variation.is_control);
10546
+ if (!control) fail(this, "Experiment has no control variation.", EXIT_CODES.validation);
10547
+ const matchedById = /* @__PURE__ */ new Map();
10548
+ const unmatched = [];
10549
+ for (const ref of requested) {
10550
+ const variation = experiment.variations.find((candidate) => ref === candidate.id || ref === candidate.slug || ref.toLowerCase() === candidate.name.toLowerCase());
10551
+ if (variation) matchedById.set(variation.id, variation);
10552
+ else unmatched.push(ref);
10553
+ }
10554
+ if (unmatched.length) fail(this, `Unknown --variations value(s): ${unmatched.join(", ")}`, EXIT_CODES.validation);
10555
+ const selected = requested.length ? [...matchedById.values()] : experiment.variations;
10556
+ const variationIds = [.../* @__PURE__ */ new Set([control.id, ...selected.map((variation) => variation.id)])];
10557
+ const creds2 = requireCredentials();
10558
+ const created = await createExperimentScreenshots(creds2, experiment.id, { devices, variation_ids: variationIds });
10559
+ if (!created.ok || !created.data) fail(this, created.error ?? "Failed to create screenshot job", classifyStatus(created.status), created.errorDetails);
10560
+ let job = created.data;
10561
+ const deadline = Date.now() + timeoutSeconds * 1e3;
10562
+ let delayMs = process.env.APEX_CLI_MOCK_API === "1" ? 1 : 1e3;
10563
+ let consecutiveTransientFailures = 0;
10564
+ while (job.status === "queued" || job.status === "processing") {
10565
+ if (Date.now() >= deadline) fail(this, `Screenshot job ${job.job_id} timed out after ${timeoutSeconds}s.`);
10566
+ await new Promise((resolve) => setTimeout(resolve, Math.min(delayMs, Math.max(0, deadline - Date.now()))));
10567
+ if (Date.now() >= deadline) fail(this, `Screenshot job ${job.job_id} timed out after ${timeoutSeconds}s.`);
10568
+ let polled;
10569
+ try {
10570
+ polled = await getScreenshotJob(creds2, job.job_id, Math.max(1, deadline - Date.now()));
10571
+ } catch (error) {
10572
+ if (Date.now() >= deadline) fail(this, `Screenshot job ${job.job_id} timed out after ${timeoutSeconds}s.`);
10573
+ consecutiveTransientFailures += 1;
10574
+ if (consecutiveTransientFailures > 3) throw error;
10575
+ delayMs = Math.min(Math.round(delayMs * 1.6), 1e4);
10576
+ continue;
10577
+ }
10578
+ const polledToTerminalState = polled.ok && polled.data && polled.data.status !== "queued" && polled.data.status !== "processing";
10579
+ if (!polledToTerminalState && Date.now() >= deadline) {
10580
+ fail(this, `Screenshot job ${job.job_id} timed out after ${timeoutSeconds}s.`);
10581
+ }
10582
+ if (!polled.ok || !polled.data) {
10583
+ const transient = polled.status === 429 || polled.status >= 500;
10584
+ if (!transient || ++consecutiveTransientFailures > 3) {
10585
+ fail(this, polled.error ?? "Failed to poll screenshot job", classifyStatus(polled.status), polled.errorDetails);
10586
+ }
10587
+ delayMs = Math.min(Math.round(delayMs * 1.6), 1e4);
10588
+ continue;
10589
+ }
10590
+ consecutiveTransientFailures = 0;
10591
+ job = polled.data;
10592
+ delayMs = Math.min(Math.round(delayMs * 1.6), 1e4);
10593
+ }
10594
+ if (job.status === "failed") fail(this, `Screenshot job failed: ${job.error ?? "unknown error"}`);
10595
+ const outputDir = import_node_path4.default.resolve(process.cwd(), opts.out);
10596
+ import_node_fs6.default.mkdirSync(outputDir, { recursive: true });
10597
+ const rows = [];
10598
+ const errors = [];
10599
+ const usedFilenames = /* @__PURE__ */ new Set();
10600
+ for (const variationId of variationIds) {
10601
+ const variation = experiment.variations.find((item) => item.id === variationId);
10602
+ for (const device of devices) {
10603
+ const url = job.results?.[variationId]?.[device];
10604
+ if (!url) {
10605
+ errors.push(`${variation.name}/${device}: no screenshot returned`);
10606
+ continue;
10607
+ }
10608
+ const baseFilename = `${safeFilenamePart(experiment.slug ?? experiment.name)}-${safeFilenamePart(variation.slug ?? variation.name)}-${safeFilenamePart(device)}`;
10609
+ let filename = `${baseFilename}.png`;
10610
+ if (usedFilenames.has(filename)) {
10611
+ const suffix = variation.id.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase();
10612
+ filename = `${baseFilename}-${suffix}.png`;
10613
+ let collisionIndex = 2;
10614
+ while (usedFilenames.has(filename)) filename = `${baseFilename}-${suffix}-${collisionIndex++}.png`;
10615
+ }
10616
+ usedFilenames.add(filename);
10617
+ try {
10618
+ const response = await fetch(url);
10619
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
10620
+ import_node_fs6.default.writeFileSync(import_node_path4.default.join(outputDir, filename), Buffer.from(await response.arrayBuffer()));
10621
+ rows.push([variation.name, device, filename]);
10622
+ } catch (error) {
10623
+ errors.push(`${variation.name}/${device}: ${error instanceof Error ? error.message : String(error)}`);
10624
+ }
10625
+ }
10626
+ }
10627
+ if (!rows.length) fail(this, `No screenshots succeeded. ${errors.join("; ")}`);
10628
+ if (errors.length) {
10629
+ console.error(source_default.yellow(`Partial result: ${rows.length} saved, ${errors.length} failed.`));
10630
+ errors.forEach((error) => console.error(` - ${error}`));
10631
+ }
10632
+ output(this, job, () => {
10633
+ printTable(["VARIATION", "DEVICE", "FILE"], rows, [24, 20, 48]);
10634
+ });
10635
+ });
10636
+ var experimentsCommand = new Command("experiments").alias("exp").description("Manage experiments").addCommand(list).addCommand(get).addCommand(results).addCommand(create).addCommand(start).addCommand(pause).addCommand(complete).addCommand(del).addCommand(pullFilesystem).addCommand(planFilesystem).addCommand(applyFilesystem).addCommand(screenshots).addCommand(qa).addHelpText("after", `
10637
+ Examples:
10638
+ apex experiments list --status running
10639
+ apex experiments get checkout-banner
10640
+ apex experiments pull checkout-banner --out ./checkout-banner
10641
+ apex experiments plan ./checkout-banner --json
10642
+ `);
10643
+
9944
10644
  // src/commands/push.ts
9945
10645
  init_source();
9946
10646
  var import_node_fs7 = __toESM(require("node:fs"));
@@ -10014,7 +10714,7 @@ function isRecord(value) {
10014
10714
  }
10015
10715
  var pushCommand = new Command("push").description(
10016
10716
  "Push experiment definitions from config file to the API (idempotent by slug)"
10017
- ).option("-c, --config <path>", "Config file path").option("--dry-run", "Show what would be pushed without making changes").action(async function action11(opts) {
10717
+ ).option("-c, --config <path>", "Config file path").option("--dry-run", "Show what would be pushed without making changes").action(async function action21(opts) {
10018
10718
  const creds2 = requireCredentials();
10019
10719
  const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
10020
10720
  const configPath = opts.config ?? findConfigPath();
@@ -10154,7 +10854,7 @@ var previewCommand = new Command("preview").description("Open QA preview URL for
10154
10854
  }
10155
10855
  const data = created.data;
10156
10856
  output(previewCommand, data, () => console.log(source_default.cyan(data.preview_url)));
10157
- if (opts.open !== false) {
10857
+ if (opts.open !== false && !shouldUseJson(previewCommand)) {
10158
10858
  try {
10159
10859
  const open2 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
10160
10860
  await open2(data.preview_url);
@@ -10170,7 +10870,7 @@ var previewCommand = new Command("preview").description("Open QA preview URL for
10170
10870
  const res = await getExperiment(creds2, idOrSlug);
10171
10871
  if (!res.ok) {
10172
10872
  spinner.fail(`Experiment "${idOrSlug}" not found`);
10173
- process.exit(1);
10873
+ fail(previewCommand, res.error ?? `Experiment "${idOrSlug}" not found`, classifyStatus(res.status), res.errorDetails);
10174
10874
  }
10175
10875
  exp = res.data;
10176
10876
  } else {
@@ -10178,13 +10878,13 @@ var previewCommand = new Command("preview").description("Open QA preview URL for
10178
10878
  const res = await getExperimentBySlug(creds2, slug);
10179
10879
  if (!res.ok) {
10180
10880
  spinner.fail(`Experiment "${slug}" not found`);
10181
- process.exit(1);
10881
+ fail(previewCommand, res.error ?? `Experiment "${slug}" not found`, classifyStatus(res.status), res.errorDetails);
10182
10882
  }
10183
10883
  exp = res.data;
10184
10884
  }
10185
10885
  if (!exp.variations || exp.variations.length === 0) {
10186
10886
  spinner.fail("Experiment has no variations");
10187
- process.exit(1);
10887
+ fail(previewCommand, "Experiment has no variations");
10188
10888
  }
10189
10889
  let variation;
10190
10890
  if (variationId) {
@@ -10193,12 +10893,9 @@ var previewCommand = new Command("preview").description("Open QA preview URL for
10193
10893
  );
10194
10894
  if (!variation) {
10195
10895
  spinner.fail(`Variation "${variationId}" not found`);
10196
- console.log(
10197
- source_default.dim(
10198
- `Available: ${exp.variations.map((v) => `${v.name} (${v.id})`).join(", ")}`
10199
- )
10200
- );
10201
- process.exit(1);
10896
+ fail(previewCommand, `Variation "${variationId}" not found`, void 0, {
10897
+ availableVariations: exp.variations.map((v) => ({ id: v.id, name: v.name, slug: v.slug }))
10898
+ });
10202
10899
  }
10203
10900
  } else {
10204
10901
  variation = exp.variations.find((v) => !v.is_control) ?? exp.variations[0];
@@ -10206,30 +10903,28 @@ var previewCommand = new Command("preview").description("Open QA preview URL for
10206
10903
  spinner.stop();
10207
10904
  const storeUrl = exp.store_url ?? opts?.storeUrl;
10208
10905
  if (!storeUrl) {
10209
- printError(
10210
- 'Experiment is missing "store_url". Set it on the experiment or pass --store-url <url>.'
10211
- );
10212
- process.exit(1);
10906
+ fail(previewCommand, 'Experiment is missing "store_url". Set it on the experiment or pass --store-url <url>.');
10213
10907
  }
10214
10908
  let previewUrl;
10215
10909
  try {
10216
10910
  previewUrl = new URL(storeUrl);
10217
10911
  } catch {
10218
- printError('Invalid store URL. Pass an absolute URL like "https://example.com/products/item".');
10219
- process.exit(1);
10912
+ fail(previewCommand, 'Invalid store URL. Pass an absolute URL like "https://example.com/products/item".');
10220
10913
  }
10221
10914
  previewUrl.searchParams.set("drip_force", `${exp.id}:${variation.id}`);
10222
10915
  previewUrl.searchParams.set("drip_experiment_id", exp.id);
10223
10916
  previewUrl.searchParams.set("drip_variation_id", variation.id);
10224
10917
  previewUrl.searchParams.set("drip_qa", "1");
10225
10918
  previewUrl.searchParams.set("drip_debug", "1");
10226
- console.log();
10227
- console.log(source_default.bold(`Preview: ${exp.name}`));
10228
- console.log(`Variation: ${variation.name}${variation.is_control ? " (control)" : ""}`);
10229
- console.log();
10230
- console.log(source_default.cyan(previewUrl.toString()));
10231
- console.log();
10232
- if (opts?.open !== false) {
10919
+ output(previewCommand, { experimentId: exp.id, variationId: variation.id, previewUrl: previewUrl.toString() }, () => {
10920
+ console.log();
10921
+ console.log(source_default.bold(`Preview: ${exp.name}`));
10922
+ console.log(`Variation: ${variation.name}${variation.is_control ? " (control)" : ""}`);
10923
+ console.log();
10924
+ console.log(source_default.cyan(previewUrl.toString()));
10925
+ console.log();
10926
+ });
10927
+ if (opts?.open !== false && !shouldUseJson(previewCommand)) {
10233
10928
  try {
10234
10929
  const open2 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
10235
10930
  await open2(previewUrl.toString());
@@ -10344,7 +11039,7 @@ async function uploadVersion(params) {
10344
11039
  })
10345
11040
  );
10346
11041
  const res = await uploadHarnessVersion(creds2, params.session.sessionId, form);
10347
- if (!res.ok) throw new Error(res.error ?? "Failed to upload harness version");
11042
+ if (!res.ok) throw new CliError(res.error ?? "Failed to upload harness version", classifyStatus(res.status), res.errorDetails);
10348
11043
  return res.data;
10349
11044
  }
10350
11045
  async function createSession(folder) {
@@ -10362,40 +11057,43 @@ async function createSession(folder) {
10362
11057
  resetJs: cleanupResetJs(experiment),
10363
11058
  antiFlickerSelectors: apex.antiFlickerSelectors ?? []
10364
11059
  });
10365
- if (!res.ok) throw new Error(res.error ?? "Failed to create harness session");
11060
+ if (!res.ok) throw new CliError(res.error ?? "Failed to create harness session", classifyStatus(res.status), res.errorDetails);
10366
11061
  return { session: res.data, apex, experiment };
10367
11062
  }
10368
11063
  var harnessCommand = new Command("harness").description("Develop AB Test Harness projects through Apex live QA sessions");
10369
- harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action((folderArg, opts) => {
11064
+ harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action(function action22(folderArg, opts) {
10370
11065
  const folder = resolveFolder(folderArg);
10371
11066
  const targetPath = apexConfigPath(folder);
10372
11067
  if (import_node_fs8.default.existsSync(targetPath) && !opts.force) {
10373
- printError(`${targetPath} already exists. Use --force to overwrite.`);
10374
- process.exit(1);
11068
+ fail(this, `${targetPath} already exists. Use --force to overwrite.`, EXIT_CODES.conflict);
10375
11069
  }
10376
11070
  const { apex } = loadHarnessConfig(folder);
10377
11071
  import_node_fs8.default.writeFileSync(targetPath, `${JSON.stringify(apex, null, 2)}
10378
11072
  `, "utf8");
10379
- printSuccess(`Created ${targetPath}`);
11073
+ output(this, { folder, configPath: targetPath, experiment: apex.experimentSlug, variation: apex.variationSlug }, () => printSuccess(`Created ${targetPath}`));
10380
11074
  });
10381
- harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment folder").option("--no-open", "Print preview URL instead of opening it").action(async (folderArg, opts) => {
11075
+ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment folder").option("--no-open", "Print preview URL instead of opening it").action(async function action23(folderArg, opts) {
10382
11076
  const folder = resolveFolder(folderArg);
10383
11077
  const created = await createSession(folder);
10384
11078
  await runAbHarness(["build", folder], folder);
10385
11079
  const version = await uploadVersion({ ...created, folder });
10386
- console.log(source_default.bold("\nApex Harness Session"));
10387
- console.log(`Experiment: ${created.session.experimentId}`);
10388
- console.log(`Variation: ${created.session.variationId}`);
10389
- console.log(`Version: ${version.version}`);
10390
- console.log(source_default.cyan(created.session.previewUrl));
10391
- console.log();
11080
+ output(this, { folder, sessionId: created.session.sessionId, experimentId: created.session.experimentId, variationId: created.session.variationId, version: version.version, previewUrl: created.session.previewUrl }, () => {
11081
+ console.log(source_default.bold("\nApex Harness Session"));
11082
+ console.log(`Experiment: ${created.session.experimentId}`);
11083
+ console.log(`Variation: ${created.session.variationId}`);
11084
+ console.log(`Version: ${version.version}`);
11085
+ console.log(source_default.cyan(created.session.previewUrl));
11086
+ console.log();
11087
+ });
10392
11088
  if (opts.open !== false) {
10393
11089
  const open2 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
10394
11090
  await open2(created.session.previewUrl);
10395
11091
  }
10396
11092
  let uploading = false;
10397
11093
  let queued = false;
11094
+ let closing = false;
10398
11095
  const rebuild = async () => {
11096
+ if (closing) return;
10399
11097
  if (uploading) {
10400
11098
  queued = true;
10401
11099
  return;
@@ -10404,12 +11102,12 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
10404
11102
  try {
10405
11103
  await runAbHarness(["build", folder], folder);
10406
11104
  const next = await uploadVersion({ ...created, folder });
10407
- console.log(source_default.dim(`[harness] uploaded version ${next.version}`));
11105
+ console.error(source_default.dim(`[harness] uploaded version ${next.version}`));
10408
11106
  } catch (error) {
10409
11107
  printWarning(error instanceof Error ? error.message : String(error));
10410
11108
  } finally {
10411
11109
  uploading = false;
10412
- if (queued) {
11110
+ if (queued && !closing) {
10413
11111
  queued = false;
10414
11112
  void rebuild();
10415
11113
  }
@@ -10423,19 +11121,22 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
10423
11121
  debounce = setTimeout(() => void rebuild(), 300);
10424
11122
  });
10425
11123
  process.on("SIGINT", () => {
11124
+ closing = true;
11125
+ queued = false;
11126
+ if (debounce) clearTimeout(debounce);
10426
11127
  watcher.close();
10427
- process.exit(0);
11128
+ process.exitCode = 0;
10428
11129
  });
10429
11130
  });
10430
- harnessCommand.command("publish").argument("<folder>", "AB Test Harness experiment folder").action(async (folderArg) => {
11131
+ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experiment folder").action(async function action24(folderArg) {
10431
11132
  const folder = resolveFolder(folderArg);
10432
11133
  const created = await createSession(folder);
10433
11134
  await runAbHarness(["verify", folder], folder);
10434
11135
  await runAbHarness(["build", folder], folder);
10435
11136
  await uploadVersion({ ...created, folder });
10436
11137
  const res = await publishHarnessSession(requireCredentials(), created.session.sessionId);
10437
- if (!res.ok) throw new Error(res.error ?? "Failed to publish harness session");
10438
- printSuccess("Published latest harness artifact to the Apex draft variation.");
11138
+ if (!res.ok) throw new CliError(res.error ?? "Failed to publish harness session", classifyStatus(res.status), res.errorDetails);
11139
+ output(this, { folder, sessionId: created.session.sessionId, experimentId: created.session.experimentId, variationId: created.session.variationId, published: true }, () => printSuccess("Published latest harness artifact to the Apex draft variation."));
10439
11140
  });
10440
11141
 
10441
11142
  // src/commands/operator.ts
@@ -10444,6 +11145,47 @@ var import_node_crypto4 = __toESM(require("node:crypto"));
10444
11145
  var import_node_fs9 = __toESM(require("node:fs"));
10445
11146
  var import_node_path7 = __toESM(require("node:path"));
10446
11147
  var CANONICAL_FILE_RE = /^(src\/(?:index\.js|index\.scss|info\.js|mutations\.json)|experiment\.config\.json|metrics\.json|targeting\.json|dev-doc\.md|brand\.md|goals\/[a-zA-Z0-9_.-]+\.json|segments\/[a-zA-Z0-9_.-]+\.json|network\/[a-zA-Z0-9_.-]+\.recipe\.json)$/;
11148
+ function workspaceIssueFile(field, evidence) {
11149
+ if (field === "custom_js" || field === "reset_js") return "src/index.js";
11150
+ if (field === "custom_css") return "src/index.css";
11151
+ if (field === "mutations" || field.startsWith("mutations[")) {
11152
+ return "src/mutations.json";
11153
+ }
11154
+ if (typeof evidence.selector === "string" && evidence.selector.trim()) {
11155
+ return "src/mutations.json";
11156
+ }
11157
+ if (field === "anti_flicker_selectors") return "experiment.config.json";
11158
+ return null;
11159
+ }
11160
+ function formatWorkspaceBlockingIssues(value) {
11161
+ if (!value || typeof value !== "object") return null;
11162
+ const issues = value.blockingIssues;
11163
+ if (!Array.isArray(issues) || issues.length === 0) return null;
11164
+ return issues.map((rawIssue, index) => {
11165
+ const issue = rawIssue && typeof rawIssue === "object" ? rawIssue : {};
11166
+ const evidence = issue.evidence && typeof issue.evidence === "object" ? issue.evidence : {};
11167
+ const field = typeof issue.field === "string" ? issue.field : "";
11168
+ const file = workspaceIssueFile(field, evidence);
11169
+ const selector = typeof evidence.selector === "string" && evidence.selector.trim() ? evidence.selector.trim() : null;
11170
+ const lines = [
11171
+ `[${index + 1}] ${typeof issue.message === "string" ? issue.message : "Workspace build blocked."}`,
11172
+ file ? ` File: ${file}${field && !selector ? ` (${field})` : ""}` : ` Field: ${field || "workspace"}`
11173
+ ];
11174
+ if (selector) lines.push(` Selector: ${selector}`);
11175
+ const construct = typeof evidence.construct === "string" ? evidence.construct : null;
11176
+ const snippet2 = typeof evidence.snippet === "string" ? evidence.snippet : typeof evidence.url === "string" ? evidence.url : null;
11177
+ if (construct) lines.push(` Match: ${construct}`);
11178
+ if (snippet2) lines.push(` Evidence: ${snippet2}`);
11179
+ if (typeof evidence.line === "number") {
11180
+ lines.push(
11181
+ ` Location: line ${evidence.line}, column ${typeof evidence.column === "number" ? evidence.column : 0}`
11182
+ );
11183
+ } else if (typeof evidence.matchIndex === "number") {
11184
+ lines.push(` Location: mutation character ${evidence.matchIndex}`);
11185
+ }
11186
+ return lines.join("\n");
11187
+ }).join("\n");
11188
+ }
10447
11189
  function sha256Text2(value) {
10448
11190
  return import_node_crypto4.default.createHash("sha256").update(value).digest("hex");
10449
11191
  }
@@ -10475,9 +11217,11 @@ function writeJsonFile(filePath, value) {
10475
11217
  function readSession(folder) {
10476
11218
  const session = readJsonFile(sessionPath(folder));
10477
11219
  if (!session) {
10478
- throw new Error(
10479
- `Missing ${sessionPath(folder)}. Run apex workspace checkout first.`
10480
- );
11220
+ const filesystemMarker = import_node_path7.default.join(apexDir(folder), "experiment-filesystem.json");
11221
+ if (import_node_fs9.default.existsSync(filesystemMarker)) {
11222
+ throw new Error("This folder is an experiment filesystem (apex experiments \u2026), not an operator workspace \u2014 run `apex experiments plan` here, or `apex workspace checkout` into a different folder.");
11223
+ }
11224
+ throw new Error(`Missing ${sessionPath(folder)}. Run apex workspace checkout first.`);
10481
11225
  }
10482
11226
  return session;
10483
11227
  }
@@ -10611,7 +11355,7 @@ async function createExactHarnessSession(folder, session) {
10611
11355
  targetUrl,
10612
11356
  antiFlickerSelectors: []
10613
11357
  });
10614
- if (!res.ok || !res.data) throw new Error(res.error ?? "Failed to create harness session");
11358
+ if (!res.ok || !res.data) throw new CliError(res.error ?? "Failed to create harness session", classifyStatus(res.status), res.errorDetails);
10615
11359
  return res.data;
10616
11360
  }
10617
11361
  async function uploadCurrentArtifactVersion(folder, sessionId) {
@@ -10638,7 +11382,7 @@ async function uploadCurrentArtifactVersion(folder, sessionId) {
10638
11382
  }
10639
11383
  form.append("manifest", JSON.stringify(manifest));
10640
11384
  const res = await uploadHarnessVersion(creds2, sessionId, form);
10641
- if (!res.ok || !res.data) throw new Error(res.error ?? "Failed to upload harness artifact");
11385
+ if (!res.ok || !res.data) throw new CliError(res.error ?? "Failed to upload harness artifact", classifyStatus(res.status), res.errorDetails);
10642
11386
  return res.data;
10643
11387
  }
10644
11388
  async function syncChangedFiles(folder, session) {
@@ -10662,18 +11406,28 @@ async function syncChangedFiles(folder, session) {
10662
11406
  title: `CLI sync ${file.path}`
10663
11407
  }
10664
11408
  );
10665
- if (!res.ok) throw new Error(res.error ?? `Failed to upload ${file.path}`);
11409
+ if (!res.ok) throw new CliError(res.error ?? `Failed to upload ${file.path}`, classifyStatus(res.status), res.errorDetails);
10666
11410
  }
10667
11411
  return { localFiles, changed };
10668
11412
  }
10669
- async function runPublish(folderArg) {
11413
+ async function runPublish(folderArg, opts = {}) {
10670
11414
  const folder = folderPath2(folderArg);
10671
11415
  const session = readSession(folder);
10672
11416
  const { localFiles, changed } = await syncChangedFiles(folder, session);
10673
11417
  const creds2 = requireCredentials();
10674
- const build = await buildOperatorWorkspace(creds2, session.experimentId, session.variationId);
11418
+ const build = await buildOperatorWorkspace(
11419
+ creds2,
11420
+ session.experimentId,
11421
+ session.variationId,
11422
+ opts.allowGoalWrites ? { allowGoalWrites: true } : void 0
11423
+ );
10675
11424
  if (!build.ok || !build.data?.ok) {
10676
- throw new Error(build.error ?? JSON.stringify(build.data ?? { error: "Workspace build failed" }, null, 2));
11425
+ const issueDetails = formatWorkspaceBlockingIssues(build.data);
11426
+ throw new CliError(
11427
+ issueDetails ?? build.error ?? JSON.stringify(build.data ?? { error: "Workspace build failed" }, null, 2),
11428
+ classifyStatus(build.status),
11429
+ build.errorDetails
11430
+ );
10677
11431
  }
10678
11432
  let external = null;
10679
11433
  if (shouldUseExternalDelivery(folder)) {
@@ -10698,32 +11452,50 @@ async function runPublish(folderArg) {
10698
11452
  }
10699
11453
  return { build: build.data, changed, external };
10700
11454
  }
10701
- var operatorCommand = new Command("workspace").alias("operator").description("Check out, preview, and publish Apex draft workspace files");
10702
- operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment ID").requiredOption("--variation <id-or-slug>", "Variation ID, slug, or name").option("--dir <folder>", "Target folder").action(async (experimentId, opts) => {
11455
+ var WORKSPACE_CHECKOUT_DIR_REMOVAL_VERSION = "0.4.0";
11456
+ var operatorCommand = new Command("workspace").alias("operator").description("Check out, preview, and publish Apex draft workspace files").addHelpText("after", `
11457
+ Examples:
11458
+ apex workspace checkout <experiment-id> --variation treatment --out ./workspace
11459
+ apex workspace status ./workspace --json
11460
+ apex workspace dev ./workspace --no-open
11461
+ apex workspace publish ./workspace
11462
+ `);
11463
+ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment ID").requiredOption("--variation <id-or-slug>", "Variation ID, slug, or name").option("--out <path>", "Target folder").addOption(new Option("--dir <folder>", "Legacy alias for --out").hideHelp()).action(async function action25(experimentId, opts) {
10703
11464
  try {
10704
11465
  const creds2 = requireCredentials();
10705
11466
  const expRes = await getExperiment(creds2, experimentId);
10706
- if (!expRes.ok || !expRes.data) throw new Error(expRes.error ?? "Experiment not found");
10707
- const variation = resolveVariation(expRes.data, opts.variation);
10708
- const folder = folderPath2(opts.dir ?? slugify2(`${expRes.data.name}-${variation.name}`, "apex-operator"));
10709
- const workspace = await getOperatorWorkspace(creds2, expRes.data.id, variation.id);
10710
- if (!workspace.ok || !workspace.data) throw new Error(workspace.error ?? "Could not load workspace");
11467
+ if (!expRes.ok || !expRes.data) throw new CliError(expRes.error ?? "Experiment not found", classifyStatus(expRes.status), expRes.errorDetails);
11468
+ const experiment = expRes.data;
11469
+ const variation = resolveVariation(experiment, opts.variation);
11470
+ if (opts.dir !== void 0) {
11471
+ process.stderr.write(`Warning: --dir is deprecated for apex workspace checkout; use --out instead. --dir will be removed in v${WORKSPACE_CHECKOUT_DIR_REMOVAL_VERSION}.
11472
+ `);
11473
+ markDeprecatedInvocation({
11474
+ option: "--dir",
11475
+ replacement: "--out",
11476
+ removalVersion: WORKSPACE_CHECKOUT_DIR_REMOVAL_VERSION
11477
+ });
11478
+ }
11479
+ const folder = folderPath2(opts.out ?? opts.dir ?? slugify2(`${experiment.name}-${variation.name}`, "apex-operator"));
11480
+ const workspace = await getOperatorWorkspace(creds2, experiment.id, variation.id);
11481
+ if (!workspace.ok || !workspace.data) throw new CliError(workspace.error ?? "Could not load workspace", classifyStatus(workspace.status), workspace.errorDetails);
10711
11482
  import_node_fs9.default.mkdirSync(folder, { recursive: true });
10712
11483
  writeWorkspaceFiles(folder, workspace.data);
10713
11484
  writeSession(folder, sessionFromWorkspace({
10714
11485
  credsBaseUrl: creds2.baseUrl,
10715
- experimentId: expRes.data.id,
11486
+ experimentId: experiment.id,
10716
11487
  variationId: variation.id,
10717
11488
  workspace: workspace.data
10718
11489
  }));
10719
- printSuccess(`Checked out ${expRes.data.name} / ${variation.name}`);
10720
- console.log(source_default.cyan(folder));
11490
+ output(this, { folder, experimentId: experiment.id, variationId: variation.id, files: workspace.data.files.map((file) => file.path) }, () => {
11491
+ printSuccess(`Checked out ${experiment.name} / ${variation.name}`);
11492
+ console.log(source_default.cyan(folder));
11493
+ });
10721
11494
  } catch (error) {
10722
- printError(error instanceof Error ? error.message : String(error));
10723
- process.exit(1);
11495
+ throw error;
10724
11496
  }
10725
11497
  });
10726
- operatorCommand.command("status").argument("[folder]", "Operator workspace folder", ".").action(function action12(folderArg) {
11498
+ operatorCommand.command("status").argument("[folder]", "Operator workspace folder", ".").action(function action26(folderArg) {
10727
11499
  try {
10728
11500
  const folder = folderPath2(folderArg);
10729
11501
  const session = readSession(folder);
@@ -10751,18 +11523,12 @@ operatorCommand.command("status").argument("[folder]", "Operator workspace folde
10751
11523
  fail(this, error instanceof Error ? error.message : String(error));
10752
11524
  }
10753
11525
  });
10754
- operatorCommand.command("dev").argument("[folder]", "Operator workspace folder", ".").option("--no-open", "Print preview URL instead of opening it").option("--once", "Upload one preview version and exit").action(async (folderArg, opts) => {
11526
+ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder", ".").option("--no-open", "Print preview URL instead of opening it").option("--once", "Upload one preview version and exit").action(async function action27(folderArg, opts) {
10755
11527
  try {
10756
11528
  const folder = folderPath2(folderArg);
10757
11529
  const session = readSession(folder);
10758
11530
  const harnessSession = await createExactHarnessSession(folder, session);
10759
11531
  const version = await uploadCurrentArtifactVersion(folder, harnessSession.sessionId);
10760
- console.log(source_default.bold("\nApex Workspace Preview"));
10761
- console.log(`Experiment: ${harnessSession.experimentId}`);
10762
- console.log(`Variation: ${harnessSession.variationId}`);
10763
- console.log(`Version: ${version.version}`);
10764
- console.log(source_default.cyan(harnessSession.previewUrl));
10765
- console.log();
10766
11532
  import_node_fs9.default.mkdirSync(apexDir(folder), { recursive: true });
10767
11533
  writeJsonFile(import_node_path7.default.join(apexDir(folder), "harness-session.json"), {
10768
11534
  sessionId: harnessSession.sessionId,
@@ -10772,6 +11538,14 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
10772
11538
  previewUrl: harnessSession.previewUrl,
10773
11539
  lastVersion: version.version
10774
11540
  });
11541
+ output(this, { folder, experimentId: harnessSession.experimentId, variationId: harnessSession.variationId, version: version.version, previewUrl: harnessSession.previewUrl }, () => {
11542
+ console.log(source_default.bold("\nApex Workspace Preview"));
11543
+ console.log(`Experiment: ${harnessSession.experimentId}`);
11544
+ console.log(`Variation: ${harnessSession.variationId}`);
11545
+ console.log(`Version: ${version.version}`);
11546
+ console.log(source_default.cyan(harnessSession.previewUrl));
11547
+ console.log();
11548
+ });
10775
11549
  if (opts.open !== false) {
10776
11550
  const open2 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
10777
11551
  await open2(harnessSession.previewUrl);
@@ -10779,7 +11553,9 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
10779
11553
  if (opts.once) return;
10780
11554
  let uploading = false;
10781
11555
  let queued = false;
11556
+ let closing = false;
10782
11557
  const upload2 = async () => {
11558
+ if (closing) return;
10783
11559
  if (uploading) {
10784
11560
  queued = true;
10785
11561
  return;
@@ -10787,12 +11563,12 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
10787
11563
  uploading = true;
10788
11564
  try {
10789
11565
  const next = await uploadCurrentArtifactVersion(folder, harnessSession.sessionId);
10790
- console.log(source_default.dim(`[operator] uploaded preview version ${next.version}`));
11566
+ console.error(source_default.dim(`[operator] uploaded preview version ${next.version}`));
10791
11567
  } catch (error) {
10792
11568
  printWarning(error instanceof Error ? error.message : String(error));
10793
11569
  } finally {
10794
11570
  uploading = false;
10795
- if (queued) {
11571
+ if (queued && !closing) {
10796
11572
  queued = false;
10797
11573
  void upload2();
10798
11574
  }
@@ -10806,28 +11582,29 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
10806
11582
  timer = setTimeout(() => void upload2(), 300);
10807
11583
  });
10808
11584
  process.on("SIGINT", () => {
11585
+ closing = true;
11586
+ queued = false;
11587
+ if (timer) clearTimeout(timer);
10809
11588
  watcher.close();
10810
- process.exit(0);
11589
+ process.exitCode = 0;
10811
11590
  });
10812
11591
  } catch (error) {
10813
- printError(error instanceof Error ? error.message : String(error));
10814
- process.exit(1);
11592
+ throw error;
10815
11593
  }
10816
11594
  });
10817
- operatorCommand.command("publish").argument("[folder]", "Operator workspace folder", ".").action(async (folderArg) => {
11595
+ operatorCommand.command("publish").argument("[folder]", "Operator workspace folder", ".").option("--allow-goal-writes", "Allow metrics.json to create or update shared shop-level goals").action(async function action28(folderArg, opts) {
10818
11596
  try {
10819
- const result = await runPublish(folderArg);
10820
- printSuccess(`Published ${result.changed.length} changed file(s) to Apex`);
11597
+ const result = await runPublish(folderArg, opts);
10821
11598
  const build = result.build;
10822
- console.log(`Mutations: ${build.mutationCount ?? 0}`);
10823
- console.log(`CSS bytes: ${build.customCssBytes ?? 0}`);
10824
- console.log(`JS bytes: ${build.customJsBytes ?? 0}`);
10825
- if (result.external) {
10826
- console.log(`External artifacts: version ${result.external.version}`);
10827
- }
11599
+ output(this, { changed: result.changed, build, external: result.external ?? null }, () => {
11600
+ printSuccess(`Published ${result.changed.length} changed file(s) to Apex`);
11601
+ console.log(`Mutations: ${build.mutationCount ?? 0}`);
11602
+ console.log(`CSS bytes: ${build.customCssBytes ?? 0}`);
11603
+ console.log(`JS bytes: ${build.customJsBytes ?? 0}`);
11604
+ if (result.external) console.log(`External artifacts: version ${result.external.version}`);
11605
+ });
10828
11606
  } catch (error) {
10829
- printError(error instanceof Error ? error.message : String(error));
10830
- process.exit(1);
11607
+ throw error;
10831
11608
  }
10832
11609
  });
10833
11610
 
@@ -10858,7 +11635,7 @@ function briefLine(item) {
10858
11635
  ].filter(Boolean);
10859
11636
  return parts.join("\n");
10860
11637
  }
10861
- var approved = new Command("approved").description("List approved roadmap ideas ready to build in Apex").option("-n, --limit <n>", "Max results", "20").option("--cursor <id>", "Pagination cursor").option("-q, --query <text>", "Search title, hypothesis, brief, and notes").option("--unbuilt", "Only show approved ideas without a linked experiment").option("--built", "Only show approved ideas with a linked experiment").option("--json", "Print raw JSON").option("--brief", "Print each item with hypothesis and target URL").action(async (opts) => {
11638
+ var approved = new Command("approved").description("List approved roadmap ideas ready to build in Apex").option("-n, --limit <n>", "Max results", "20").option("--cursor <id>", "Pagination cursor").option("-q, --query <text>", "Search title, hypothesis, brief, and notes").option("--unbuilt", "Only show approved ideas without a linked experiment").option("--built", "Only show approved ideas with a linked experiment").option("--json", "Print raw JSON").option("--brief", "Print each item with hypothesis and target URL").action(async function action29(opts) {
10862
11639
  const creds2 = requireCredentials();
10863
11640
  const limit = parseInt(opts.limit, 10);
10864
11641
  const hasExperiment = opts.unbuilt ? false : opts.built ? true : void 0;
@@ -10868,63 +11645,56 @@ var approved = new Command("approved").description("List approved roadmap ideas
10868
11645
  q: opts.query,
10869
11646
  hasExperiment
10870
11647
  });
10871
- if (!res.ok) {
10872
- printError(res.error ?? "Failed to fetch approved roadmap items");
10873
- process.exit(1);
10874
- }
10875
- if (opts.json) {
10876
- console.log(JSON.stringify(res.data, null, 2));
10877
- return;
10878
- }
11648
+ if (!res.ok) fail(this, res.error ?? "Failed to fetch approved roadmap items", classifyStatus(res.status), res.errorDetails);
10879
11649
  const items = res.data?.data ?? [];
10880
- if (items.length === 0) {
10881
- console.log(source_default.dim("No approved roadmap items found."));
10882
- return;
10883
- }
10884
- printTable(
10885
- ["ID", "SCORE", "TYPE", "EXPERIMENT", "TITLE"],
10886
- items.map((item) => [
10887
- item.id,
10888
- score(item.computed_score),
10889
- truncate(item.action_type, 14),
10890
- truncate(experimentState(item), 18),
10891
- truncate(item.title, 54)
10892
- ]),
10893
- [36, 6, 14, 18, 54]
10894
- );
10895
- if (opts.brief) {
10896
- console.log();
10897
- for (const item of items) {
10898
- console.log(source_default.bold(item.title));
10899
- const detail5 = briefLine(item);
10900
- if (detail5) console.log(detail5);
10901
- console.log(source_default.dim(`apex roadmap handoff ${item.id}`));
11650
+ output(this, res.data ?? { data: [], pagination: { has_more: false, next_cursor: null } }, () => {
11651
+ if (items.length === 0) {
11652
+ console.log(source_default.dim("No approved roadmap items found."));
11653
+ return;
11654
+ }
11655
+ printTable(
11656
+ ["ID", "SCORE", "TYPE", "EXPERIMENT", "TITLE"],
11657
+ items.map((item) => [
11658
+ item.id,
11659
+ score(item.computed_score),
11660
+ truncate(item.action_type, 14),
11661
+ truncate(experimentState(item), 18),
11662
+ truncate(item.title, 54)
11663
+ ]),
11664
+ [36, 6, 14, 18, 54]
11665
+ );
11666
+ if (opts.brief) {
10902
11667
  console.log();
11668
+ for (const item of items) {
11669
+ console.log(source_default.bold(item.title));
11670
+ const detail5 = briefLine(item);
11671
+ if (detail5) console.log(detail5);
11672
+ console.log(source_default.dim(`apex roadmap handoff ${item.id}`));
11673
+ console.log();
11674
+ }
10903
11675
  }
10904
- }
10905
- if (res.data?.pagination.has_more) {
10906
- console.log(source_default.dim(`
11676
+ if (res.data?.pagination.has_more) {
11677
+ console.log(source_default.dim(`
10907
11678
  More results: --cursor ${res.data.pagination.next_cursor}`));
10908
- }
11679
+ }
11680
+ });
10909
11681
  });
10910
- var handoff = new Command("handoff").description("Get the full build brief for an approved roadmap item").argument("<id>", "Roadmap/backlog item ID").option("-o, --output <path>", "Write handoff JSON to a file").option("--instructions", "Print only the build instructions").action(async (id, opts) => {
11682
+ var handoff = new Command("handoff").description("Get the full build brief for an approved roadmap item").argument("<id>", "Roadmap/backlog item ID").option("--out <path>", "Write handoff JSON to a file").addOption(new Option("-o, --output <path>", "Legacy alias for --out").hideHelp()).option("--instructions", "Print only the build instructions").action(async function action30(id, opts) {
10911
11683
  const creds2 = requireCredentials();
10912
11684
  const res = await getRoadmapItemHandoff(creds2, id);
10913
- if (!res.ok) {
10914
- printError(res.error ?? "Failed to fetch roadmap handoff");
10915
- process.exit(1);
10916
- }
11685
+ if (!res.ok) fail(this, res.error ?? "Failed to fetch roadmap handoff", classifyStatus(res.status), res.errorDetails);
10917
11686
  const payload = res.data ?? {};
10918
- const output2 = opts.instructions ? String(payload.instructions ?? "") : `${JSON.stringify(payload, null, 2)}
11687
+ const rendered = opts.instructions ? String(payload.instructions ?? "") : `${JSON.stringify(payload, null, 2)}
10919
11688
  `;
10920
- if (opts.output) {
10921
- const target = import_node_path8.default.resolve(process.cwd(), opts.output);
11689
+ const outputData = opts.instructions ? { roadmapItemId: id, instructions: String(payload.instructions ?? "") } : payload;
11690
+ if (opts.out || opts.output) {
11691
+ const target = import_node_path8.default.resolve(process.cwd(), opts.out ?? opts.output);
10922
11692
  import_node_fs10.default.mkdirSync(import_node_path8.default.dirname(target), { recursive: true });
10923
- import_node_fs10.default.writeFileSync(target, output2, "utf8");
10924
- printSuccess(`Wrote ${target}`);
11693
+ import_node_fs10.default.writeFileSync(target, rendered, "utf8");
11694
+ output(this, { path: target, roadmapItemId: id }, () => printSuccess(`Wrote ${target}`));
10925
11695
  return;
10926
11696
  }
10927
- console.log(output2);
11697
+ output(this, outputData, () => console.log(rendered));
10928
11698
  });
10929
11699
  var roadmapCommand = new Command("roadmap").description("Work with approved roadmap ideas and build handoffs");
10930
11700
  roadmapCommand.addCommand(approved);
@@ -11104,7 +11874,7 @@ trackingCommand.command("qa").description("Print the QA checklist for a tracking
11104
11874
  console.log(`${index + 1}. ${check}`);
11105
11875
  });
11106
11876
  });
11107
- trackingCommand.command("verify").description("Verify live tracking events for the current shop").option("--minutes <n>", "Lookback window in minutes", "120").option("--limit <n>", "Recent events to include", "20").option("--experiment <id>", "Filter to one experiment ID").option("--event-type <type>", "Filter to one event type; repeat with comma-separated values").action(async function action13(opts) {
11877
+ trackingCommand.command("verify").description("Verify live tracking events for the current shop").option("--minutes <n>", "Lookback window in minutes", "120").option("--limit <n>", "Recent events to include", "20").option("--experiment <id>", "Filter to one experiment ID").option("--event-type <type>", "Filter to one event type; repeat with comma-separated values").action(async function action31(opts) {
11108
11878
  const creds2 = requireCredentials();
11109
11879
  const res = await verifyTracking(creds2, {
11110
11880
  minutes: Number.parseInt(opts.minutes, 10),
@@ -11144,6 +11914,151 @@ trackingCommand.command("verify").description("Verify live tracking events for t
11144
11914
  init_source();
11145
11915
  var import_node_fs12 = __toESM(require("node:fs"));
11146
11916
  var import_node_path10 = __toESM(require("node:path"));
11917
+
11918
+ // src/lib/sdk-url-match.ts
11919
+ var MAX_PATTERN_LENGTH = 1024;
11920
+ var MAX_CACHE_ENTRIES = 256;
11921
+ var regexCache = /* @__PURE__ */ new Map();
11922
+ function cacheRegex(pattern, compiled) {
11923
+ if (!regexCache.has(pattern) && regexCache.size >= MAX_CACHE_ENTRIES) {
11924
+ const oldest = regexCache.keys().next().value;
11925
+ if (oldest) regexCache.delete(oldest);
11926
+ }
11927
+ regexCache.set(pattern, compiled);
11928
+ }
11929
+ function safeRegexTest(pattern, input) {
11930
+ if (!pattern || pattern.length > MAX_PATTERN_LENGTH) return false;
11931
+ if (regexCache.has(pattern)) {
11932
+ const cached = regexCache.get(pattern);
11933
+ return cached ? cached.test(input) : false;
11934
+ }
11935
+ try {
11936
+ const compiled = new RegExp(pattern);
11937
+ cacheRegex(pattern, compiled);
11938
+ return compiled.test(input);
11939
+ } catch {
11940
+ cacheRegex(pattern, null);
11941
+ return false;
11942
+ }
11943
+ }
11944
+
11945
+ // src/lib/sdk-goal-url-match.ts
11946
+ function sdkPageviewGoalMatch(urlPattern, href) {
11947
+ if (!urlPattern) return true;
11948
+ return safeRegexTest(urlPattern, href);
11949
+ }
11950
+
11951
+ // src/commands/goals.ts
11952
+ var INVENTORY_PAGE_SIZE = 100;
11953
+ var GOALS_PAGE_SIZE = 100;
11954
+ var MAX_GOAL_PAGES = 100;
11955
+ var NON_QUANTIFIABLE = /* @__PURE__ */ new Set(["(", "|", "^", "$"]);
11956
+ function urlPatternDependence(pattern) {
11957
+ let fragment = false;
11958
+ let inClass = false;
11959
+ let quantifiable = false;
11960
+ for (let i = 0; i < pattern.length; i += 1) {
11961
+ const ch = pattern[i];
11962
+ if (ch === "\\") {
11963
+ const next = pattern[i + 1];
11964
+ i += 1;
11965
+ if (next === void 0) break;
11966
+ if (next === "?" || next === "&") return "query";
11967
+ if (next === "#") fragment = true;
11968
+ quantifiable = true;
11969
+ continue;
11970
+ }
11971
+ if (inClass) {
11972
+ if (ch === "]") {
11973
+ inClass = false;
11974
+ quantifiable = true;
11975
+ continue;
11976
+ }
11977
+ if (ch === "?" || ch === "&") return "query";
11978
+ if (ch === "#") fragment = true;
11979
+ continue;
11980
+ }
11981
+ if (ch === "[") {
11982
+ inClass = true;
11983
+ quantifiable = false;
11984
+ continue;
11985
+ }
11986
+ if (ch === "&") return "query";
11987
+ if (ch === "#") {
11988
+ fragment = true;
11989
+ quantifiable = true;
11990
+ continue;
11991
+ }
11992
+ if (ch === "?") {
11993
+ if (!quantifiable && pattern[i - 1] !== "(") return "query";
11994
+ quantifiable = true;
11995
+ continue;
11996
+ }
11997
+ quantifiable = !NON_QUANTIFIABLE.has(ch);
11998
+ }
11999
+ return fragment ? "fragment" : "none";
12000
+ }
12001
+ var PAGE_SCOPED_REASON = "page-scoped goal: matching is decided by the assigned Page, not url_pattern \u2014 inventory dry-run does not apply";
12002
+ function goalPageId(goal) {
12003
+ const raw = goal.page_id ?? goal.pageId ?? null;
12004
+ return typeof raw === "string" && raw.length > 0 ? raw : null;
12005
+ }
12006
+ var DEPENDENCE_REASON = {
12007
+ query: "URL inventory is path-level only, so query-string-dependent patterns cannot be counted safely.",
12008
+ fragment: "URL fragments are never sent to the server, so fragment-dependent patterns cannot be counted safely."
12009
+ };
12010
+ function inventoryHref(item) {
12011
+ try {
12012
+ return new URL(item.path, `https://${item.host}`).href;
12013
+ } catch {
12014
+ return null;
12015
+ }
12016
+ }
12017
+ async function findGoalById(command, creds2, goalId) {
12018
+ let cursor;
12019
+ for (let page = 0; page < MAX_GOAL_PAGES; page += 1) {
12020
+ const res = await listGoals(creds2, { limit: GOALS_PAGE_SIZE, cursor });
12021
+ if (!res.ok || !res.data) fail(command, res.error ?? "Failed to read goals", classifyStatus(res.status), res.errorDetails);
12022
+ const match = res.data.data.find((item) => item.id === goalId);
12023
+ if (match) return match;
12024
+ const next = res.data.pagination?.next_cursor ?? null;
12025
+ if (!res.data.pagination?.has_more || !next || next === cursor) return null;
12026
+ cursor = next;
12027
+ }
12028
+ return null;
12029
+ }
12030
+ async function readAllInventory(command, creds2, days) {
12031
+ const items = [];
12032
+ let offset = 0;
12033
+ let total = 0;
12034
+ let truncated = false;
12035
+ do {
12036
+ const res = await getUrlInventory(creds2, { days, limit: INVENTORY_PAGE_SIZE, offset });
12037
+ if (!res.ok || !res.data) fail(command, res.error ?? "Failed to read URL inventory", classifyStatus(res.status), res.errorDetails);
12038
+ items.push(...res.data.items);
12039
+ total = res.data.total;
12040
+ truncated = truncated || res.data.truncated;
12041
+ offset += res.data.items.length;
12042
+ if (res.data.items.length === 0) break;
12043
+ } while (offset < total);
12044
+ return { items, total, truncated };
12045
+ }
12046
+ function printSuggestions(data) {
12047
+ if (data.goals.length) {
12048
+ console.log(source_default.bold("Suggested goals"));
12049
+ printTable(["NAME", "TYPE", "PATTERN/SELECTOR/EVENT"], data.goals.map((goal) => [goal.name.slice(0, 36), goal.type, String(goal.urlPattern ?? goal.selector ?? goal.eventName ?? "-").slice(0, 56)]), [36, 10, 56]);
12050
+ } else console.log(source_default.dim("No suggested goals."));
12051
+ if (data.funnels.length) {
12052
+ console.log();
12053
+ console.log(source_default.bold("Suggested funnels"));
12054
+ for (const funnel of data.funnels) console.log(`- ${funnel.name}: ${funnel.steps.join(" -> ")}`);
12055
+ }
12056
+ if (data.triggers.length) {
12057
+ console.log();
12058
+ console.log(source_default.bold("Suggested test triggers"));
12059
+ for (const trigger of data.triggers) console.log(`- ${trigger.page}: ${trigger.description}`);
12060
+ }
12061
+ }
11147
12062
  function parseCapture(command, value) {
11148
12063
  if (!value) return void 0;
11149
12064
  const source = value.startsWith("@") ? value.slice(1) : value;
@@ -11172,7 +12087,7 @@ function printGoal(goal) {
11172
12087
  console.log(` Count once: ${goal.count_once ? "yes" : "no"}`);
11173
12088
  console.log();
11174
12089
  }
11175
- var list2 = new Command("list").alias("ls").description("List goals").option("-n, --limit <n>", "Max results", "50").action(async function action14(opts) {
12090
+ var list2 = new Command("list").alias("ls").description("List goals").option("-n, --limit <n>", "Max results", "50").action(async function action32(opts) {
11176
12091
  const creds2 = requireCredentials();
11177
12092
  const res = await listGoals(creds2, { limit: Number.parseInt(opts.limit, 10) });
11178
12093
  if (!res.ok || !res.data) {
@@ -11194,7 +12109,7 @@ var list2 = new Command("list").alias("ls").description("List goals").option("-n
11194
12109
  );
11195
12110
  });
11196
12111
  });
11197
- var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action15(opts) {
12112
+ var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action33(opts) {
11198
12113
  const creds2 = requireCredentials();
11199
12114
  const body5 = cleanBody({
11200
12115
  name: opts.name,
@@ -11212,7 +12127,7 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
11212
12127
  }
11213
12128
  output(this, { goal: res.data }, () => printGoal(res.data));
11214
12129
  });
11215
- var update = new Command("update").description("Update a goal").argument("<id>", "Goal ID").option("--name <name>", "Goal name").option("--type <type>", "click|pageview|custom|revenue").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--no-count-once", "Do not count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action16(id, opts) {
12130
+ var update = new Command("update").description("Update a goal").argument("<id>", "Goal ID").option("--name <name>", "Goal name").option("--type <type>", "click|pageview|custom|revenue").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--no-count-once", "Do not count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action34(id, opts) {
11216
12131
  const creds2 = requireCredentials();
11217
12132
  const body5 = cleanBody({
11218
12133
  name: opts.name,
@@ -11233,7 +12148,137 @@ var update = new Command("update").description("Update a goal").argument("<id>",
11233
12148
  }
11234
12149
  output(this, { goal: res.data }, () => printGoal(res.data));
11235
12150
  });
11236
- var goalsCommand = new Command("goals").description("Manage conversion and revenue goals").addCommand(list2).addCommand(create2).addCommand(update);
12151
+ var test = new Command("test").description("Dry run a goal URL pattern against traffic").option("--pattern <regex>", "URL regex pattern to test (matched against the full URL, as the SDK matches pageview goals)").option("--goal <id>", "Existing goal ID whose URL pattern should be tested").option("--days <n>", "Traffic lookback days", "14").action(async function action35(opts) {
12152
+ if (!opts.pattern && !opts.goal) fail(this, "Provide --pattern or --goal.", EXIT_CODES.validation);
12153
+ if (opts.pattern && opts.goal) fail(this, "Use only one of --pattern or --goal.", EXIT_CODES.validation);
12154
+ const creds2 = requireCredentials();
12155
+ let pattern = opts.pattern;
12156
+ if (opts.goal) {
12157
+ const goal = await findGoalById(this, creds2, opts.goal);
12158
+ if (!goal) fail(this, `Goal not found: ${opts.goal}`, EXIT_CODES.validation);
12159
+ const pageId = goalPageId(goal);
12160
+ if (pageId) {
12161
+ output(this, { verdict: "indeterminate", scope: "page", goal: goal.id, page_id: pageId, pattern: goal.url_pattern ?? null, reason: PAGE_SCOPED_REASON }, () => {
12162
+ console.log(source_default.yellow("INDETERMINATE"));
12163
+ console.log(PAGE_SCOPED_REASON);
12164
+ });
12165
+ return;
12166
+ }
12167
+ pattern = goal.url_pattern ?? void 0;
12168
+ if (!pattern) fail(this, `Goal ${opts.goal} has no URL pattern.`, EXIT_CODES.validation);
12169
+ }
12170
+ const days = Math.max(1, Number.parseInt(opts.days, 10) || 14);
12171
+ const dependence = urlPatternDependence(pattern);
12172
+ if (dependence !== "none") {
12173
+ const reason = DEPENDENCE_REASON[dependence];
12174
+ output(this, { verdict: "indeterminate", pattern, dependence, reason }, () => {
12175
+ console.log(source_default.yellow("INDETERMINATE"));
12176
+ console.log(reason);
12177
+ });
12178
+ return;
12179
+ }
12180
+ const inventory = await readAllInventory(this, creds2, days);
12181
+ const matches = inventory.items.filter((item) => {
12182
+ const href = inventoryHref(item);
12183
+ return href !== null && sdkPageviewGoalMatch(pattern, href);
12184
+ });
12185
+ const totalPageviews = matches.reduce((sum, item) => sum + item.pageviews, 0);
12186
+ output(this, { verdict: "determinate", pattern, days, matches, total_pageviews: totalPageviews, inventory_total: inventory.total, truncated: inventory.truncated, visitor_note: "Per-URL visitor counts are not unioned client-side.", match_note: "Patterns are tested against the full URL (https://host/path), matching the SDK pageview-goal path." }, () => {
12187
+ console.log(source_default.bold(`Matched pageviews: ${totalPageviews}`));
12188
+ console.log(source_default.dim("Per-URL visitor counts are shown; total visitors are not unioned client-side."));
12189
+ if (!matches.length) {
12190
+ console.log(source_default.dim("No matching URLs."));
12191
+ return;
12192
+ }
12193
+ printTable(["URL", "PAGEVIEWS", "VISITORS"], matches.map((item) => [`${item.host}${item.path}`.slice(0, 64), String(item.pageviews), String(item.visitors)]), [64, 10, 10]);
12194
+ });
12195
+ });
12196
+ var suggest = new Command("suggest").description("Suggest goals and funnels from analytics").option("--create", "Create selected goals after confirmation").option("--yes", "Skip prompts when used with create").action(async function action36(opts) {
12197
+ const creds2 = requireCredentials();
12198
+ const res = await suggestGoals(creds2);
12199
+ if (!res.ok || !res.data) fail(this, res.error ?? "Failed to suggest goals", classifyStatus(res.status), res.errorDetails);
12200
+ output(this, res.data, () => printSuggestions(res.data));
12201
+ if (!opts.create) return;
12202
+ for (const goal of res.data.goals) {
12203
+ console.error(`Would create ${goal.type} goal: ${goal.name}`);
12204
+ if (!opts.yes) {
12205
+ if (!process.stdin.isTTY || !process.stdout.isTTY) fail(this, "Confirmation required; rerun with --yes to create suggested goals.", EXIT_CODES.confirmationRequired);
12206
+ const readline2 = await import("node:readline");
12207
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
12208
+ const answer = await new Promise((resolve) => rl.question(source_default.yellow("Create this goal? [y/N] "), resolve));
12209
+ rl.close();
12210
+ if (answer.toLowerCase() !== "y") continue;
12211
+ }
12212
+ const created = await createGoal(creds2, cleanBody({ name: goal.name, type: goal.type, selector: goal.selector, url_pattern: goal.urlPattern, event_name: goal.eventName }));
12213
+ if (!created.ok || !created.data) fail(this, created.error ?? "Failed to create suggested goal", classifyStatus(created.status), created.errorDetails);
12214
+ }
12215
+ });
12216
+ var goalsCommand = new Command("goals").description("Manage conversion and revenue goals").addCommand(list2).addCommand(create2).addCommand(update).addCommand(test).addCommand(suggest).addHelpText("after", `
12217
+ Examples:
12218
+ apex goals list --limit 20
12219
+ apex goals create --name "Checkout click" --type click --selector "button[name=checkout]"
12220
+ apex goals create --name "Purchase" --type revenue --count-once
12221
+ `);
12222
+
12223
+ // src/commands/funnels.ts
12224
+ init_source();
12225
+ var import_node_fs13 = __toESM(require("node:fs"));
12226
+ var import_node_path11 = __toESM(require("node:path"));
12227
+ function readSteps(command, file) {
12228
+ let parsed;
12229
+ try {
12230
+ parsed = JSON.parse(import_node_fs13.default.readFileSync(import_node_path11.default.resolve(process.cwd(), file), "utf8"));
12231
+ } catch (error) {
12232
+ fail(command, `Invalid --steps-file JSON: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODES.validation);
12233
+ }
12234
+ if (!Array.isArray(parsed) || parsed.length === 0) fail(command, "steps must be a non-empty array", EXIT_CODES.validation);
12235
+ return parsed.map((step, index) => {
12236
+ if (!step || typeof step !== "object" || typeof step.label !== "string" || typeof step.pattern !== "string") {
12237
+ fail(command, `step ${index + 1} must have label and pattern strings`, EXIT_CODES.validation);
12238
+ }
12239
+ return { order: index + 1, label: step.label, url_pattern: step.pattern };
12240
+ });
12241
+ }
12242
+ function printFunnel(funnel) {
12243
+ console.log();
12244
+ console.log(source_default.bold(funnel.name));
12245
+ console.log(` ID: ${source_default.dim(funnel.id)}`);
12246
+ for (const step of [...funnel.steps].sort((a, b) => a.order - b.order)) console.log(` ${step.order}. ${step.label} \u2014 ${step.url_pattern}`);
12247
+ console.log();
12248
+ }
12249
+ var list3 = new Command("list").alias("ls").description("List funnels").action(async function action37() {
12250
+ const res = await listFunnels(requireCredentials());
12251
+ if (!res.ok || !res.data) fail(this, res.error ?? "Failed to list funnels", classifyStatus(res.status), res.errorDetails);
12252
+ output(this, { funnels: res.data.data }, () => printTable(["NAME", "ID", "STEPS"], res.data.data.map((f) => [f.name.slice(0, 36), f.id, String(f.steps.length)]), [36, 36, 5]));
12253
+ });
12254
+ var create3 = new Command("create").description("Create a funnel").requiredOption("--name <name>", "Funnel name").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(async function action38(opts) {
12255
+ const res = await createFunnel(requireCredentials(), { name: opts.name, steps: readSteps(this, opts.stepsFile) });
12256
+ if (!res.ok || !res.data) fail(this, res.error ?? "Failed to create funnel", classifyStatus(res.status), res.errorDetails);
12257
+ output(this, { funnel: res.data }, () => printFunnel(res.data));
12258
+ });
12259
+ var update2 = new Command("update").description("Replace a funnel's steps").argument("<id>", "Funnel ID").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(async function action39(id, opts) {
12260
+ const res = await updateFunnel(requireCredentials(), id, { steps: readSteps(this, opts.stepsFile) });
12261
+ if (!res.ok || !res.data) fail(this, res.error ?? "Failed to update funnel", classifyStatus(res.status), res.errorDetails);
12262
+ output(this, { funnel: res.data }, () => printFunnel(res.data));
12263
+ });
12264
+ var del2 = new Command("delete").description("Delete a funnel").argument("<id>", "Funnel ID").action(async function action40(id) {
12265
+ const res = await deleteFunnel(requireCredentials(), id);
12266
+ if (!res.ok) fail(this, res.error ?? "Failed to delete funnel", classifyStatus(res.status), res.errorDetails);
12267
+ output(this, { deleted: true, id }, () => console.log(`Deleted funnel ${id}`));
12268
+ });
12269
+ var run = new Command("run").description("Run funnel analysis").argument("<id>", "Funnel ID").option("--days <n>", "Analysis window", "30").action(async function action41(id, opts) {
12270
+ if (!/^[1-9]\d*$/.test(opts.days)) fail(this, "--days must be a positive integer", EXIT_CODES.validation);
12271
+ const days = Number.parseInt(opts.days, 10);
12272
+ if (!Number.isSafeInteger(days)) fail(this, "--days must be a positive integer", EXIT_CODES.validation);
12273
+ const res = await runFunnel(requireCredentials(), id, days);
12274
+ if (!res.ok || !res.data) fail(this, res.error ?? "Failed to run funnel", classifyStatus(res.status), res.errorDetails);
12275
+ output(this, { run: res.data }, () => printTable(["STEP", "LABEL", "VISITORS", "DROP-OFF"], res.data.steps.map((s) => [String(s.step), s.label, String(s.visitors), `${s.drop_off_rate}%`]), [5, 36, 8, 8]));
12276
+ });
12277
+ var validate2 = new Command("validate").description("Validate funnel steps without writing").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(function action42(opts) {
12278
+ const steps = readSteps(this, opts.stepsFile);
12279
+ output(this, { valid: true, steps }, () => console.log(`Valid funnel steps: ${steps.length}`));
12280
+ });
12281
+ var funnelsCommand = new Command("funnels").description("Manage analytics funnels").addCommand(list3).addCommand(create3).addCommand(update2).addCommand(del2).addCommand(run).addCommand(validate2);
11237
12282
 
11238
12283
  // src/commands/environments.ts
11239
12284
  init_source();
@@ -11286,11 +12331,11 @@ async function load2(command) {
11286
12331
  }
11287
12332
  return result.data;
11288
12333
  }
11289
- var list3 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function action17() {
12334
+ var list4 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function action43() {
11290
12335
  const { environments } = await load2(this);
11291
12336
  output(this, { environments }, () => printEnvironments(environments.items));
11292
12337
  });
11293
- var setDomain = new Command("set-domain").description("Map a domain to a runtime environment").argument("<domain>", "Domain to map").requiredOption("--environment <env>", "Environment key").action(async function action18(domain, opts) {
12338
+ var setDomain = new Command("set-domain").description("Map a domain to a runtime environment").argument("<domain>", "Domain to map").requiredOption("--environment <env>", "Environment key").action(async function action44(domain, opts) {
11294
12339
  const normalizedDomain = normalizeEnvironmentDomain(domain);
11295
12340
  if (!normalizedDomain) {
11296
12341
  fail(this, "Domain must be a valid hostname containing a dot", EXIT_CODES.validation);
@@ -11333,7 +12378,7 @@ var setDomain = new Command("set-domain").description("Map a domain to a runtime
11333
12378
  () => printEnvironments(result.data.environments.items)
11334
12379
  );
11335
12380
  });
11336
- var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function action19(domain) {
12381
+ var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function action45(domain) {
11337
12382
  const normalizedDomain = normalizeEnvironmentDomain(domain);
11338
12383
  if (!normalizedDomain) {
11339
12384
  fail(this, "Domain must be a valid hostname containing a dot", EXIT_CODES.validation);
@@ -11366,7 +12411,200 @@ var removeDomain = new Command("remove-domain").description("Remove a domain map
11366
12411
  () => printEnvironments(result.data.environments.items)
11367
12412
  );
11368
12413
  });
11369
- var environmentsCommand = new Command("environments").description("Manage runtime environments and domain mappings").addCommand(list3).addCommand(setDomain).addCommand(removeDomain);
12414
+ var environmentsCommand = new Command("environments").description("Manage runtime environments and domain mappings").addCommand(list4).addCommand(setDomain).addCommand(removeDomain);
12415
+
12416
+ // src/commands/consent.ts
12417
+ init_source();
12418
+ function warn(command, precompile) {
12419
+ if (!shouldUseJson(command) && precompile && precompile.status !== "ok") {
12420
+ console.error(source_default.yellow(`Warning (${precompile.status}): ${precompile.summary}`));
12421
+ }
12422
+ }
12423
+ async function load3(command) {
12424
+ const result = await getConsent(requireCredentials());
12425
+ if (!result.ok || !result.data) fail(command, result.error ?? "Failed to load consent settings", classifyStatus(result.status), result.errorDetails);
12426
+ return result.data;
12427
+ }
12428
+ function printConsent(data) {
12429
+ console.log(`Consent: ${data.consentMode.enabled ? "on" : "off"}`);
12430
+ console.log(`Provider: ${data.consentMode.provider || "-"}`);
12431
+ console.log(`Group: ${data.consentMode.requiredGroup || "-"}`);
12432
+ console.log(`Getter: ${data.consentMode.getter || "-"}`);
12433
+ console.log(`Split: ${data.prerequisites.showVariationsRule && data.prerequisites.startTrackingRule ? "canonical" : "off"}`);
12434
+ }
12435
+ var get2 = new Command("get").description("Show consent settings").action(async function action46() {
12436
+ const data = await load3(this);
12437
+ output(this, data, () => printConsent(data));
12438
+ });
12439
+ var set = new Command("set").description("Enable and configure consent gating").requiredOption("--provider <provider>", "Consent provider").requiredOption("--group <group>", "Required consent group").option("--getter <path>", "Custom consent getter path").action(async function action47(opts) {
12440
+ const consentMode = { enabled: true, provider: opts.provider, requiredGroup: opts.group };
12441
+ if (opts.getter !== void 0) consentMode.getter = opts.getter;
12442
+ const result = await updateConsent(requireCredentials(), { consentMode, applyCanonicalSplit: true });
12443
+ if (!result.ok || !result.data) fail(this, result.error ?? "Failed to update consent settings", classifyStatus(result.status), result.errorDetails);
12444
+ warn(this, result.data.precompile);
12445
+ output(this, result.data, () => printConsent(result.data));
12446
+ });
12447
+ var off = new Command("off").description("Disable consent gating").action(async function action48() {
12448
+ const result = await updateConsent(requireCredentials(), { consentMode: { enabled: false } });
12449
+ if (!result.ok || !result.data) fail(this, result.error ?? "Failed to disable consent settings", classifyStatus(result.status), result.errorDetails);
12450
+ warn(this, result.data.precompile);
12451
+ output(this, result.data, () => printConsent(result.data));
12452
+ });
12453
+ var consentCommand = new Command("consent").description("Manage consent gating").addCommand(get2).addCommand(set).addCommand(off);
12454
+
12455
+ // src/commands/domains.ts
12456
+ init_source();
12457
+ function normalizeDomain(value) {
12458
+ return value.trim().replace(/^https?:\/\//i, "").replace(/[/?#].*$/, "").replace(/\.$/, "").toLowerCase();
12459
+ }
12460
+ function warn2(command, precompile) {
12461
+ if (!shouldUseJson(command) && precompile && precompile.status !== "ok") console.error(source_default.yellow(`Warning (${precompile.status}): ${precompile.summary}`));
12462
+ }
12463
+ function printDomains(data) {
12464
+ printTable(["TYPE", "DOMAIN"], [["primary", data.domain], ...data.additionalDomains.map((d) => ["additional", d])], [12, 64]);
12465
+ }
12466
+ async function load4(command) {
12467
+ const result = await getInstallDomains(requireCredentials());
12468
+ if (!result.ok || !result.data) fail(command, result.error ?? "Failed to load install domains", classifyStatus(result.status), result.errorDetails);
12469
+ return result.data;
12470
+ }
12471
+ async function patchWithRetry(command, build) {
12472
+ let result;
12473
+ for (let attempt = 0; attempt < 2; attempt += 1) {
12474
+ const current = await load4(command);
12475
+ result = await updateInstallDomains(requireCredentials(), { domains: build(current), ifRevision: current.revision });
12476
+ if (result.status !== 409 || attempt === 1) break;
12477
+ }
12478
+ if (!result?.ok || !result.data) fail(command, result?.error ?? "Failed to update install domains", classifyStatus(result?.status ?? 0), result?.errorDetails);
12479
+ warn2(command, result.data.precompile);
12480
+ return result.data;
12481
+ }
12482
+ var list5 = new Command("list").alias("ls").description("List install domains").action(async function action49() {
12483
+ const data = await load4(this);
12484
+ output(this, data, () => printDomains(data));
12485
+ });
12486
+ var add = new Command("add").description("Add install domains").argument("<domain...>").action(async function action50(domain) {
12487
+ const added = domain.map(normalizeDomain);
12488
+ const data = await patchWithRetry(this, (current) => Array.from(/* @__PURE__ */ new Set([...current.additionalDomains, ...added])));
12489
+ output(this, data, () => printDomains(data));
12490
+ });
12491
+ var remove = new Command("remove").description("Remove install domains").argument("<domain...>").action(async function action51(domain) {
12492
+ const removeSet = new Set(domain.map(normalizeDomain));
12493
+ const data = await patchWithRetry(this, (current) => current.additionalDomains.filter((d) => !removeSet.has(normalizeDomain(d))));
12494
+ output(this, data, () => printDomains(data));
12495
+ });
12496
+ var set2 = new Command("set").description("Replace install domains").argument("<domain...>").action(async function action52(domain) {
12497
+ const replacement = domain.map(normalizeDomain);
12498
+ const data = await patchWithRetry(this, () => replacement);
12499
+ output(this, data, () => printDomains(data));
12500
+ });
12501
+ var domainsCommand = new Command("domains").description("Manage install domains").addCommand(list5).addCommand(add).addCommand(remove).addCommand(set2);
12502
+
12503
+ // src/commands/handover.ts
12504
+ init_source();
12505
+ function normalizeDomain2(value) {
12506
+ try {
12507
+ const candidate = value.trim().toLowerCase();
12508
+ const parsed = new URL(/^[a-z][a-z0-9+.-]*:\/\//.test(candidate) ? candidate : `http://${candidate}`);
12509
+ const hostname = parsed.hostname.toLowerCase().replace(/\.$/, "");
12510
+ return hostname.includes(".") && /^[a-z0-9.-]+$/.test(hostname) ? hostname : "";
12511
+ } catch {
12512
+ return "";
12513
+ }
12514
+ }
12515
+ var QA_FETCH_TIMEOUT_MS = 12e3;
12516
+ var NAMED_HTML_ENTITIES = {
12517
+ amp: "&",
12518
+ lt: "<",
12519
+ gt: ">",
12520
+ quot: '"',
12521
+ apos: "'",
12522
+ nbsp: "\xA0"
12523
+ };
12524
+ function decodeHtmlEntities(value) {
12525
+ return value.replace(/&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z]+);/g, (match, entity) => {
12526
+ if (entity.startsWith("#")) {
12527
+ const hex = entity[1] === "x" || entity[1] === "X";
12528
+ const code = Number.parseInt(hex ? entity.slice(2) : entity.slice(1), hex ? 16 : 10);
12529
+ return Number.isInteger(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : match;
12530
+ }
12531
+ return NAMED_HTML_ENTITIES[entity.toLowerCase()] ?? match;
12532
+ });
12533
+ }
12534
+ function printSettings(settings2) {
12535
+ printTable(["DOMAINS", "PARAM", "TTL", "MINT TIMEOUT", "SIGNED"], [[settings2.domains.join(", ") || "off", settings2.handoverQueryParam, `${settings2.handoverTtlSeconds}s`, `${settings2.handoverMintTimeoutMs}ms`, settings2.signed ? "yes" : "no"]], [48, 20, 8, 14, 8]);
12536
+ }
12537
+ async function load5(command) {
12538
+ const result = await getExternalTracking(requireCredentials());
12539
+ if (!result.ok || !result.data) fail(command, result.error ?? "Failed to load handover settings", classifyStatus(result.status), result.errorDetails);
12540
+ return result.data;
12541
+ }
12542
+ async function patchWithRetry2(command, build) {
12543
+ let result;
12544
+ for (let attempt = 0; attempt < 2; attempt += 1) {
12545
+ const current = await load5(command);
12546
+ result = await updateExternalTracking(requireCredentials(), build(current));
12547
+ if (result.status !== 409 || attempt === 1) break;
12548
+ }
12549
+ if (!result || !result.ok || !result.data) fail(command, result?.error ?? "Failed to update handover settings", classifyStatus(result?.status ?? 0), result?.errorDetails);
12550
+ if (!shouldUseJson(command) && result.data.precompile?.status && result.data.precompile.status !== "ok") console.error(source_default.yellow(`Warning (${result.data.precompile.status}): ${result.data.precompile.summary}. Edge config may be stale until republish succeeds.`));
12551
+ return result.data;
12552
+ }
12553
+ var get3 = new Command("get").description("Show cross-domain handover settings").action(async function action53() {
12554
+ const data = await load5(this);
12555
+ output(this, data, () => printSettings(data.externalTracking));
12556
+ });
12557
+ var set3 = new Command("set").description("Enable cross-domain identity handover").requiredOption("--domain <domain...>", "Bound handover domain(s)").option("--param <param>", "Handover query parameter").option("--signed", "Require signed handover tokens").option("--allow-unsigned", "Disable signing using the audited escape hatch").action(async function action54(opts) {
12558
+ if (opts.signed && opts.allowUnsigned) fail(this, "--signed and --allow-unsigned cannot be used together", EXIT_CODES.validation);
12559
+ const domains = opts.domain.map(normalizeDomain2);
12560
+ if (domains.some((domain) => !domain)) fail(this, "Every --domain value must be a valid hostname containing a dot", EXIT_CODES.validation);
12561
+ if (opts.param && !/^[a-z_][a-z0-9_]{0,31}$/.test(opts.param)) fail(this, "--param must match ^[a-z_][a-z0-9_]{0,31}$", EXIT_CODES.validation);
12562
+ const data = await patchWithRetry2(this, (current) => ({ externalTracking: { domains: Array.from(new Set(domains)), handoverQueryParam: opts.param ?? current.externalTracking.handoverQueryParam, signed: opts.allowUnsigned ? false : true, ...opts.allowUnsigned ? { allowUnsigned: true } : {} }, ifRevision: current.revision }));
12563
+ output(this, data, () => printSettings(data.externalTracking));
12564
+ });
12565
+ var off2 = new Command("off").description("Disable cross-domain handover").action(async function action55() {
12566
+ const data = await patchWithRetry2(this, (current) => ({ externalTracking: { domains: [], signed: true }, ifRevision: current.revision }));
12567
+ output(this, data, () => printSettings(data.externalTracking));
12568
+ });
12569
+ var qa2 = new Command("qa").description("Check static link/form handover parameter carriage").requiredOption("--from <url>", "Page URL to inspect").requiredOption("--to-domain <domain>", "Target domain that should carry the handover parameter").action(async function action56(opts) {
12570
+ const config = await load5(this);
12571
+ const target = normalizeDomain2(opts.toDomain);
12572
+ if (!target) fail(this, "--to-domain must be a valid hostname", EXIT_CODES.validation);
12573
+ let response;
12574
+ let html;
12575
+ try {
12576
+ response = await fetch(opts.from, { signal: AbortSignal.timeout(QA_FETCH_TIMEOUT_MS), redirect: "follow" });
12577
+ if (!response.ok) fail(this, `Fetching ${opts.from} returned HTTP ${response.status}`, classifyStatus(response.status));
12578
+ html = await response.text();
12579
+ } catch (error) {
12580
+ if (error instanceof CliError) throw error;
12581
+ const reason = error instanceof Error ? error.message : String(error);
12582
+ fail(this, `Could not fetch ${opts.from}: ${reason}`, EXIT_CODES.general);
12583
+ }
12584
+ const param = config.externalTracking.handoverQueryParam;
12585
+ const baseUrl = response.url || opts.from;
12586
+ const hrefs = [...html.matchAll(/<(a|form)\b[^>]*(?:href|action)\s*=\s*["']([^"']+)["'][^>]*>/gi)].map((m) => decodeHtmlEntities(m[2]));
12587
+ const targetUrls = hrefs.map((raw) => {
12588
+ try {
12589
+ return new URL(raw, baseUrl);
12590
+ } catch {
12591
+ return null;
12592
+ }
12593
+ }).filter((url) => url !== null && url.hostname.toLowerCase().replace(/^www\./, "") === target.replace(/^www\./, ""));
12594
+ const carrying = targetUrls.filter((url) => url.searchParams.has(param));
12595
+ const warn3 = targetUrls.length === 0 ? "WARN: no static links/forms found for target domain; run the ab-harness browser check for JS-driven navigation." : void 0;
12596
+ const data = { ok: targetUrls.length > 0 && carrying.length === targetUrls.length, from: opts.from, toDomain: target, param, checked: targetUrls.length, carrying: carrying.length, warning: warn3 };
12597
+ const printVerdict = () => {
12598
+ console.log(data.ok ? source_default.green("PASS handover parameter is carried") : source_default.red("FAIL handover parameter is not carried by every target link/form"));
12599
+ if (warn3) console.error(source_default.yellow(warn3));
12600
+ };
12601
+ if (targetUrls.length > 0 && carrying.length !== targetUrls.length) {
12602
+ if (!shouldUseJson(this)) printVerdict();
12603
+ fail(this, `Handover parameter "${param}" is missing on ${targetUrls.length - carrying.length} of ${targetUrls.length} ${target} links/forms`, EXIT_CODES.validation, data);
12604
+ }
12605
+ output(this, data, printVerdict);
12606
+ });
12607
+ var handoverCommand = new Command("handover").description("Manage cross-domain identity handover").addCommand(get3).addCommand(set3).addCommand(off2).addCommand(qa2);
11370
12608
 
11371
12609
  // src/commands/analytics.ts
11372
12610
  function display(value) {
@@ -11401,7 +12639,7 @@ function analyticsSubcommand(name, description, defaultDays) {
11401
12639
  command.option("--planning-days <n>", "Planning window in days", "180");
11402
12640
  command.option("--limit <n>", "Maximum page rows", "50");
11403
12641
  }
11404
- return command.action(async function action25(opts) {
12642
+ return command.action(async function action68(opts) {
11405
12643
  const result = await getAnalytics(requireCredentials(), name, {
11406
12644
  days: positiveInteger(this, opts.days, "--days"),
11407
12645
  planningDays: positiveInteger(this, opts.planningDays, "--planning-days"),
@@ -11413,12 +12651,17 @@ function analyticsSubcommand(name, description, defaultDays) {
11413
12651
  output(this, { data: result.data.data }, () => printAnalytics(result.data.data));
11414
12652
  });
11415
12653
  }
11416
- var analyticsCommand = new Command("analytics").description("Read shop analytics").addCommand(analyticsSubcommand("stats", "Show dashboard analytics stats", 7)).addCommand(analyticsSubcommand("timeseries", "Show analytics timeseries", 30)).addCommand(analyticsSubcommand("traffic", "Show traffic sources", 30)).addCommand(analyticsSubcommand("pages", "Show page analytics", 30)).addCommand(analyticsSubcommand("geo", "Show geography and device analytics", 30)).addCommand(analyticsSubcommand("sessions", "Show session analytics", 30)).addCommand(analyticsSubcommand("web-vitals", "Show web vitals analytics", 7)).addCommand(analyticsSubcommand("realtime", "Show realtime analytics")).addCommand(analyticsSubcommand("entry-exit", "Show entry and exit page analytics", 30));
12654
+ var analyticsCommand = new Command("analytics").description("Read shop analytics").addCommand(analyticsSubcommand("stats", "Show dashboard analytics stats", 7)).addCommand(analyticsSubcommand("timeseries", "Show analytics timeseries", 30)).addCommand(analyticsSubcommand("traffic", "Show traffic sources", 30)).addCommand(analyticsSubcommand("pages", "Show page analytics", 30)).addCommand(analyticsSubcommand("geo", "Show geography and device analytics", 30)).addCommand(analyticsSubcommand("sessions", "Show session analytics", 30)).addCommand(analyticsSubcommand("web-vitals", "Show web vitals analytics", 7)).addCommand(analyticsSubcommand("realtime", "Show realtime analytics")).addCommand(analyticsSubcommand("entry-exit", "Show entry and exit page analytics", 30)).addHelpText("after", `
12655
+ Examples:
12656
+ apex analytics stats --days 7
12657
+ apex analytics pages --planning-days 180 --limit 25
12658
+ apex analytics realtime --json
12659
+ `);
11417
12660
 
11418
12661
  // src/commands/flags.ts
11419
12662
  init_source();
11420
- var import_node_fs13 = __toESM(require("node:fs"));
11421
- var import_node_path11 = __toESM(require("node:path"));
12663
+ var import_node_fs14 = __toESM(require("node:fs"));
12664
+ var import_node_path12 = __toESM(require("node:path"));
11422
12665
 
11423
12666
  // src/edge-status.ts
11424
12667
  function getEdgeDeliveryStatus(deliveryType, expectedWorstCaseSeconds = 420) {
@@ -11460,7 +12703,7 @@ function body(command, value, opts) {
11460
12703
  let parsed = {};
11461
12704
  if (value) {
11462
12705
  const source = value.startsWith("@") ? value.slice(1) : value;
11463
- const text = value.startsWith("@") || import_node_fs13.default.existsSync(source) ? import_node_fs13.default.readFileSync(import_node_path11.default.resolve(process.cwd(), source), "utf8") : value;
12706
+ const text = value.startsWith("@") || import_node_fs14.default.existsSync(source) ? import_node_fs14.default.readFileSync(import_node_path12.default.resolve(process.cwd(), source), "utf8") : value;
11464
12707
  try {
11465
12708
  parsed = JSON.parse(text);
11466
12709
  } catch (e) {
@@ -11490,7 +12733,7 @@ function detail(x) {
11490
12733
  console.log(` State: ${x.status}`);
11491
12734
  console.log();
11492
12735
  }
11493
- var list4 = new Command("list").alias("ls").description("List flags").option("-n, --limit <n>", "Max results", "50").action(async function(o) {
12736
+ var list6 = new Command("list").alias("ls").description("List flags").option("-n, --limit <n>", "Max results", "50").action(async function(o) {
11494
12737
  const r = await listManagedDeliveries(
11495
12738
  requireCredentials(),
11496
12739
  kind,
@@ -11517,7 +12760,7 @@ var list4 = new Command("list").alias("ls").description("List flags").option("-n
11517
12760
  )
11518
12761
  );
11519
12762
  });
11520
- var get2 = new Command("get").description("Get a flag").argument("<id>").action(async function(id) {
12763
+ var get4 = new Command("get").description("Get a flag").argument("<id>").action(async function(id) {
11521
12764
  const r = await getManagedDelivery(requireCredentials(), kind, id);
11522
12765
  if (!r.ok || !r.data)
11523
12766
  fail(
@@ -11588,29 +12831,35 @@ function write(name) {
11588
12831
  });
11589
12832
  return c;
11590
12833
  }
11591
- function lifecycle(action25) {
11592
- return new Command(action25).description(`${action25[0].toUpperCase()}${action25.slice(1)} a flag`).argument("<id>").action(async function(id) {
12834
+ function lifecycle(action68) {
12835
+ return new Command(action68).description(`${action68[0].toUpperCase()}${action68.slice(1)} a flag`).argument("<id>").action(async function(id) {
11593
12836
  const r = await transitionManagedDelivery(
11594
12837
  requireCredentials(),
11595
12838
  kind,
11596
12839
  id,
11597
- action25
12840
+ action68
11598
12841
  );
11599
12842
  if (!r.ok || !r.data)
11600
12843
  fail(
11601
12844
  this,
11602
- r.error ?? `Failed to ${action25}`,
12845
+ r.error ?? `Failed to ${action68}`,
11603
12846
  classifyStatus(r.status),
11604
12847
  r.errorDetails
11605
12848
  );
11606
12849
  output(this, { flag: r.data }, () => detail(r.data));
11607
12850
  });
11608
12851
  }
11609
- var flagsCommand = new Command("flags").description("Manage flags").addCommand(list4).addCommand(get2).addCommand(status).addCommand(write("create")).addCommand(write("update")).addCommand(lifecycle("activate")).addCommand(lifecycle("pause")).addCommand(lifecycle("archive"));
12852
+ var flagsCommand = new Command("flags").description("Manage flags").addCommand(list6).addCommand(get4).addCommand(status).addCommand(write("create")).addCommand(write("update")).addCommand(lifecycle("activate")).addCommand(lifecycle("pause")).addCommand(lifecycle("archive")).addHelpText("after", `
12853
+ Examples:
12854
+ apex flags list --limit 20
12855
+ apex flags get checkout-redesign
12856
+ apex flags create --key checkout-redesign --name "Checkout redesign"
12857
+ apex flags activate <id>
12858
+ `);
11610
12859
 
11611
12860
  // src/commands/backlog.ts
11612
- var import_node_fs14 = __toESM(require("node:fs"));
11613
- var import_node_path12 = __toESM(require("node:path"));
12861
+ var import_node_fs15 = __toESM(require("node:fs"));
12862
+ var import_node_path13 = __toESM(require("node:path"));
11614
12863
  init_source();
11615
12864
  var kind2 = "backlog";
11616
12865
  var singular = "backlog item";
@@ -11618,7 +12867,7 @@ function parse(command, value) {
11618
12867
  if (!value) return {};
11619
12868
  try {
11620
12869
  const source = value.startsWith("@") ? value.slice(1) : value;
11621
- const text = value.startsWith("@") || import_node_fs14.default.existsSync(source) ? import_node_fs14.default.readFileSync(import_node_path12.default.resolve(process.cwd(), source), "utf8") : value;
12870
+ const text = value.startsWith("@") || import_node_fs15.default.existsSync(source) ? import_node_fs15.default.readFileSync(import_node_path13.default.resolve(process.cwd(), source), "utf8") : value;
11622
12871
  const body5 = JSON.parse(text);
11623
12872
  if (!body5 || typeof body5 !== "object" || Array.isArray(body5)) throw new Error("body must be an object");
11624
12873
  return body5;
@@ -11633,28 +12882,28 @@ function detail2(x) {
11633
12882
  if (x.status) console.log(` State: ${x.status}`);
11634
12883
  console.log();
11635
12884
  }
11636
- var list5 = new Command("list").alias("ls").description("List backlog").option("-n, --limit <n>", "Max results", "50").option("--cursor <cursor>", "Continue from cursor").option("--include-archived", "Include archived backlog items").action(async function(o) {
12885
+ var list7 = new Command("list").alias("ls").description("List backlog").option("-n, --limit <n>", "Max results", "50").option("--cursor <cursor>", "Continue from cursor").option("--include-archived", "Include archived backlog items").action(async function(o) {
11637
12886
  const r = await listInsights(requireCredentials(), kind2, Number(o.limit), o.cursor, o.includeArchived);
11638
12887
  if (!r.ok || !r.data) fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
11639
12888
  output(this, { backlog: r.data.data, pagination: r.data.pagination }, () => printTable(["ID", "NAME", "STATE"], r.data.data.map((x) => [x.id, String(x.title ?? x.label ?? x.key ?? "-"), x.status ?? "-"]), [36, 40, 12]));
11640
12889
  });
11641
- var get3 = new Command("get").description("Get a backlog item").argument("<id>", "backlog item ID").action(async function(id) {
12890
+ var get5 = new Command("get").description("Get a backlog item").argument("<id>", "backlog item ID").action(async function(id) {
11642
12891
  const r = await getInsight(requireCredentials(), kind2, id);
11643
12892
  if (!r.ok || !r.data) fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
11644
12893
  output(this, { backlog_item: r.data }, () => detail2(r.data));
11645
12894
  });
11646
- function write2(action25) {
11647
- const c = new Command(action25).description(`${action25 === "create" ? "Create" : "Update"} a ${singular}`);
11648
- if (action25 === "update") c.argument("<id>", `${singular} ID`);
12895
+ function write2(action68) {
12896
+ const c = new Command(action68).description(`${action68 === "create" ? "Create" : "Update"} a ${singular}`);
12897
+ if (action68 === "update") c.argument("<id>", `${singular} ID`);
11649
12898
  c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file");
11650
12899
  c.action(async function(...args) {
11651
12900
  const o = this.opts();
11652
- const id = action25 === "update" ? String(args[0]) : void 0;
11653
- const raw = o.file ? `@${o.file}` : String(args[action25 === "update" ? 1 : 0] ?? "") || void 0;
12901
+ const id = action68 === "update" ? String(args[0]) : void 0;
12902
+ const raw = o.file ? `@${o.file}` : String(args[action68 === "update" ? 1 : 0] ?? "") || void 0;
11654
12903
  const body5 = parse(this, raw);
11655
12904
  if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
11656
- const r = action25 === "create" ? await createInsight(requireCredentials(), kind2, body5) : await updateInsight(requireCredentials(), kind2, id, body5);
11657
- if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${action25}`, classifyStatus(r.status), r.errorDetails);
12905
+ const r = action68 === "create" ? await createInsight(requireCredentials(), kind2, body5) : await updateInsight(requireCredentials(), kind2, id, body5);
12906
+ if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${action68}`, classifyStatus(r.status), r.errorDetails);
11658
12907
  output(this, { backlog_item: r.data }, () => detail2(r.data));
11659
12908
  });
11660
12909
  return c;
@@ -11670,7 +12919,7 @@ var restore = new Command("restore").description("Restore an archived backlog it
11670
12919
  if (!r.ok || !r.data) fail(this, r.error ?? "Failed to restore", classifyStatus(r.status), r.errorDetails);
11671
12920
  output(this, { restored: true, id }, () => console.log(`Restored ${id}`));
11672
12921
  });
11673
- var remove = new Command("delete").description("Delete a backlog item").argument("<id>", "backlog item ID").option("-f, --force", "Skip confirmation").action(async function(id, opts) {
12922
+ var remove2 = new Command("delete").description("Delete a backlog item").argument("<id>", "backlog item ID").option("-f, --force", "Skip confirmation").action(async function(id, opts) {
11674
12923
  if (!opts.force) {
11675
12924
  if (!process.stdin.isTTY || !process.stdout.isTTY) confirmationRequired(this, "Deleting a backlog item requires confirmation in non-TTY mode.", `apex backlog delete ${id} --force`);
11676
12925
  const readline2 = await import("node:readline");
@@ -11686,11 +12935,11 @@ var remove = new Command("delete").description("Delete a backlog item").argument
11686
12935
  if (!r.ok || !r.data) fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
11687
12936
  output(this, { deleted: true, id }, () => console.log(`Deleted ${id}`));
11688
12937
  });
11689
- var backlogCommand = new Command("backlog").description("Manage backlog").addCommand(list5).addCommand(get3).addCommand(write2("create")).addCommand(write2("update")).addCommand(archive).addCommand(restore).addCommand(remove);
12938
+ var backlogCommand = new Command("backlog").description("Manage backlog").addCommand(list7).addCommand(get5).addCommand(write2("create")).addCommand(write2("update")).addCommand(archive).addCommand(restore).addCommand(remove2);
11690
12939
 
11691
12940
  // src/commands/signals.ts
11692
- var import_node_fs15 = __toESM(require("node:fs"));
11693
- var import_node_path13 = __toESM(require("node:path"));
12941
+ var import_node_fs16 = __toESM(require("node:fs"));
12942
+ var import_node_path14 = __toESM(require("node:path"));
11694
12943
  init_source();
11695
12944
  var kind3 = "signals";
11696
12945
  var singular2 = "signal";
@@ -11698,7 +12947,7 @@ function parse2(command, value) {
11698
12947
  if (!value) return {};
11699
12948
  try {
11700
12949
  const source = value.startsWith("@") ? value.slice(1) : value;
11701
- const text = value.startsWith("@") || import_node_fs15.default.existsSync(source) ? import_node_fs15.default.readFileSync(import_node_path13.default.resolve(process.cwd(), source), "utf8") : value;
12950
+ const text = value.startsWith("@") || import_node_fs16.default.existsSync(source) ? import_node_fs16.default.readFileSync(import_node_path14.default.resolve(process.cwd(), source), "utf8") : value;
11702
12951
  const body5 = JSON.parse(text);
11703
12952
  if (!body5 || typeof body5 !== "object" || Array.isArray(body5)) throw new Error("body must be an object");
11704
12953
  return body5;
@@ -11713,49 +12962,49 @@ function detail3(x) {
11713
12962
  if (x.status) console.log(` State: ${x.status}`);
11714
12963
  console.log();
11715
12964
  }
11716
- var list6 = new Command("list").alias("ls").description("List signals").option("-n, --limit <n>", "Max results", "50").option("--cursor <cursor>", "Continue from cursor").option("--include-archived", "Include archived signals").action(async function(o) {
12965
+ var list8 = new Command("list").alias("ls").description("List signals").option("-n, --limit <n>", "Max results", "50").option("--cursor <cursor>", "Continue from cursor").option("--include-archived", "Include archived signals").action(async function(o) {
11717
12966
  const r = await listInsights(requireCredentials(), kind3, Number(o.limit), o.cursor, o.includeArchived);
11718
12967
  if (!r.ok || !r.data) fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
11719
12968
  output(this, { signals: r.data.data, pagination: r.data.pagination }, () => printTable(["ID", "NAME", "STATE"], r.data.data.map((x) => [x.id, String(x.title ?? x.label ?? x.key ?? "-"), x.status ?? "-"]), [36, 40, 12]));
11720
12969
  });
11721
- var get4 = new Command("get").description("Get a signal").argument("<id>", "signal ID").action(async function(id) {
12970
+ var get6 = new Command("get").description("Get a signal").argument("<id>", "signal ID").action(async function(id) {
11722
12971
  const r = await getInsight(requireCredentials(), kind3, id);
11723
12972
  if (!r.ok || !r.data) fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
11724
12973
  output(this, { signal: r.data }, () => detail3(r.data));
11725
12974
  });
11726
- function write3(action25) {
11727
- const c = new Command(action25).description(`${action25 === "create" ? "Create" : "Update"} a ${singular2}`);
11728
- if (action25 === "update") c.argument("<id>", `${singular2} ID`);
12975
+ function write3(action68) {
12976
+ const c = new Command(action68).description(`${action68 === "create" ? "Create" : "Update"} a ${singular2}`);
12977
+ if (action68 === "update") c.argument("<id>", `${singular2} ID`);
11729
12978
  c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file");
11730
12979
  c.action(async function(...args) {
11731
12980
  const o = this.opts();
11732
- const id = action25 === "update" ? String(args[0]) : void 0;
11733
- const raw = o.file ? `@${o.file}` : String(args[action25 === "update" ? 1 : 0] ?? "") || void 0;
12981
+ const id = action68 === "update" ? String(args[0]) : void 0;
12982
+ const raw = o.file ? `@${o.file}` : String(args[action68 === "update" ? 1 : 0] ?? "") || void 0;
11734
12983
  const body5 = parse2(this, raw);
11735
12984
  if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
11736
- const r = action25 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
11737
- if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${action25}`, classifyStatus(r.status), r.errorDetails);
12985
+ const r = action68 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
12986
+ if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${action68}`, classifyStatus(r.status), r.errorDetails);
11738
12987
  output(this, { signal: r.data }, () => detail3(r.data));
11739
12988
  });
11740
12989
  return c;
11741
12990
  }
11742
- var remove2 = new Command("delete").alias("archive").description("Delete or archive a signal").argument("<id>", "signal ID").action(async function(id) {
12991
+ var remove3 = new Command("delete").alias("archive").description("Delete or archive a signal").argument("<id>", "signal ID").action(async function(id) {
11743
12992
  const r = await deleteInsight(requireCredentials(), kind3, id);
11744
12993
  if (!r.ok || !r.data) fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
11745
12994
  output(this, { deleted: true, id }, () => console.log(`Deleted ${id}`));
11746
12995
  });
11747
- var signalsCommand = new Command("signals").description("Manage signals").addCommand(list6).addCommand(get4).addCommand(write3("create")).addCommand(write3("update")).addCommand(remove2);
12996
+ var signalsCommand = new Command("signals").description("Manage signals").addCommand(list8).addCommand(get6).addCommand(write3("create")).addCommand(write3("update")).addCommand(remove3);
11748
12997
 
11749
12998
  // src/commands/personalizations.ts
11750
12999
  init_source();
11751
- var import_node_fs16 = __toESM(require("node:fs"));
11752
- var import_node_path14 = __toESM(require("node:path"));
13000
+ var import_node_fs17 = __toESM(require("node:fs"));
13001
+ var import_node_path15 = __toESM(require("node:path"));
11753
13002
  var kind4 = "personalizations";
11754
13003
  function body2(command, value, opts) {
11755
13004
  let parsed = {};
11756
13005
  if (value) {
11757
13006
  const source = value.startsWith("@") ? value.slice(1) : value;
11758
- const text = value.startsWith("@") || import_node_fs16.default.existsSync(source) ? import_node_fs16.default.readFileSync(import_node_path14.default.resolve(process.cwd(), source), "utf8") : value;
13007
+ const text = value.startsWith("@") || import_node_fs17.default.existsSync(source) ? import_node_fs17.default.readFileSync(import_node_path15.default.resolve(process.cwd(), source), "utf8") : value;
11759
13008
  try {
11760
13009
  parsed = JSON.parse(text);
11761
13010
  } catch (e) {
@@ -11785,7 +13034,7 @@ function detail4(x) {
11785
13034
  console.log(` State: ${x.status}`);
11786
13035
  console.log();
11787
13036
  }
11788
- var list7 = new Command("list").alias("ls").description("List personalizations").option("-n, --limit <n>", "Max results", "50").action(async function(o) {
13037
+ var list9 = new Command("list").alias("ls").description("List personalizations").option("-n, --limit <n>", "Max results", "50").action(async function(o) {
11789
13038
  const r = await listManagedDeliveries(
11790
13039
  requireCredentials(),
11791
13040
  kind4,
@@ -11812,7 +13061,7 @@ var list7 = new Command("list").alias("ls").description("List personalizations")
11812
13061
  )
11813
13062
  );
11814
13063
  });
11815
- var get5 = new Command("get").description("Get a personalization").argument("<id>").action(async function(id) {
13064
+ var get7 = new Command("get").description("Get a personalization").argument("<id>").action(async function(id) {
11816
13065
  const r = await getManagedDelivery(requireCredentials(), kind4, id);
11817
13066
  if (!r.ok || !r.data)
11818
13067
  fail(
@@ -11852,37 +13101,37 @@ function write4(name) {
11852
13101
  });
11853
13102
  return c;
11854
13103
  }
11855
- function lifecycle2(action25) {
11856
- return new Command(action25).description(
11857
- `${action25[0].toUpperCase()}${action25.slice(1)} a personalization`
13104
+ function lifecycle2(action68) {
13105
+ return new Command(action68).description(
13106
+ `${action68[0].toUpperCase()}${action68.slice(1)} a personalization`
11858
13107
  ).argument("<id>").action(async function(id) {
11859
13108
  const r = await transitionManagedDelivery(
11860
13109
  requireCredentials(),
11861
13110
  kind4,
11862
13111
  id,
11863
- action25
13112
+ action68
11864
13113
  );
11865
13114
  if (!r.ok || !r.data)
11866
13115
  fail(
11867
13116
  this,
11868
- r.error ?? `Failed to ${action25}`,
13117
+ r.error ?? `Failed to ${action68}`,
11869
13118
  classifyStatus(r.status),
11870
13119
  r.errorDetails
11871
13120
  );
11872
13121
  output(this, { personalization: r.data }, () => detail4(r.data));
11873
13122
  });
11874
13123
  }
11875
- var personalizationsCommand = new Command("personalizations").description("Manage personalizations").addCommand(list7).addCommand(get5).addCommand(write4("create")).addCommand(write4("update")).addCommand(lifecycle2("activate")).addCommand(lifecycle2("pause")).addCommand(lifecycle2("archive"));
13124
+ var personalizationsCommand = new Command("personalizations").description("Manage personalizations").addCommand(list9).addCommand(get7).addCommand(write4("create")).addCommand(write4("update")).addCommand(lifecycle2("activate")).addCommand(lifecycle2("pause")).addCommand(lifecycle2("archive"));
11876
13125
 
11877
13126
  // src/commands/targeting-resources.ts
11878
13127
  init_source();
11879
- var import_node_fs17 = __toESM(require("node:fs"));
11880
- var import_node_path15 = __toESM(require("node:path"));
13128
+ var import_node_fs18 = __toESM(require("node:fs"));
13129
+ var import_node_path16 = __toESM(require("node:path"));
11881
13130
  function body3(command, value) {
11882
13131
  if (!value) return {};
11883
13132
  const source = value.startsWith("@") ? value.slice(1) : value;
11884
13133
  try {
11885
- const text = value.startsWith("@") || import_node_fs17.default.existsSync(source) ? import_node_fs17.default.readFileSync(import_node_path15.default.resolve(process.cwd(), source), "utf8") : value;
13134
+ const text = value.startsWith("@") || import_node_fs18.default.existsSync(source) ? import_node_fs18.default.readFileSync(import_node_path16.default.resolve(process.cwd(), source), "utf8") : value;
11886
13135
  return JSON.parse(text);
11887
13136
  } catch (error) {
11888
13137
  fail(
@@ -11961,17 +13210,17 @@ function targetingResourceCommand(kind5) {
11961
13210
  );
11962
13211
  })
11963
13212
  );
11964
- for (const action25 of ["create", "update"]) {
11965
- const command = new Command(action25).description(
11966
- `${action25 === "create" ? "Create" : "Update"} a ${singular3}`
13213
+ for (const action68 of ["create", "update"]) {
13214
+ const command = new Command(action68).description(
13215
+ `${action68 === "create" ? "Create" : "Update"} a ${singular3}`
11967
13216
  );
11968
- if (action25 === "update") command.argument("<id>");
13217
+ if (action68 === "update") command.argument("<id>");
11969
13218
  command.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").action(async function(...args) {
11970
13219
  const opts = this.opts();
11971
- const id = action25 === "update" ? String(args[0]) : void 0;
11972
- const value = opts.file ? `@${opts.file}` : String(args[action25 === "update" ? 1 : 0] ?? "") || void 0;
13220
+ const id = action68 === "update" ? String(args[0]) : void 0;
13221
+ const value = opts.file ? `@${opts.file}` : String(args[action68 === "update" ? 1 : 0] ?? "") || void 0;
11973
13222
  const requestBody = body3(this, value);
11974
- if (action25 === "update" && typeof requestBody === "object" && requestBody !== null && !Array.isArray(requestBody) && Object.keys(requestBody).length === 0)
13223
+ if (action68 === "update" && typeof requestBody === "object" && requestBody !== null && !Array.isArray(requestBody) && Object.keys(requestBody).length === 0)
11975
13224
  fail(
11976
13225
  this,
11977
13226
  `Updating a ${singular3} requires at least one field.`,
@@ -11979,7 +13228,7 @@ function targetingResourceCommand(kind5) {
11979
13228
  );
11980
13229
  const response = await requestTargetingResource(
11981
13230
  requireCredentials(),
11982
- action25 === "create" ? "POST" : "PATCH",
13231
+ action68 === "create" ? "POST" : "PATCH",
11983
13232
  kind5,
11984
13233
  id,
11985
13234
  requestBody
@@ -11987,7 +13236,7 @@ function targetingResourceCommand(kind5) {
11987
13236
  if (!response.ok || !response.data)
11988
13237
  fail(
11989
13238
  this,
11990
- response.error ?? `Failed to ${action25}`,
13239
+ response.error ?? `Failed to ${action68}`,
11991
13240
  classifyStatus(response.status),
11992
13241
  response.errorDetails
11993
13242
  );
@@ -11996,7 +13245,7 @@ function targetingResourceCommand(kind5) {
11996
13245
  {
11997
13246
  [kind5 === "exclusion-groups" ? "exclusion_group" : singular3]: response.data
11998
13247
  },
11999
- () => console.log(source_default.green(`${action25}d ${singular3}`))
13248
+ () => console.log(source_default.green(`${action68}d ${singular3}`))
12000
13249
  );
12001
13250
  });
12002
13251
  root.addCommand(command);
@@ -12083,7 +13332,7 @@ var exclusionGroupsCommand = targetingResourceCommand("exclusion-groups");
12083
13332
  // src/commands/brand.ts
12084
13333
  init_source();
12085
13334
  var NO_PROFILE_MESSAGE = "No brand profile yet \u2014 run onboarding or a site check";
12086
- var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function action20() {
13335
+ var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function action57() {
12087
13336
  const creds2 = requireCredentials();
12088
13337
  const res = await getBrandContext(creds2);
12089
13338
  if (res.status === 404) {
@@ -12116,7 +13365,7 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
12116
13365
 
12117
13366
  // src/commands/targeting.ts
12118
13367
  init_source();
12119
- var test = new Command("test").description("Evaluate experiment URL targeting for a page").argument("<url>", "URL to evaluate").option("--status <status>", "Comma-separated statuses to include, or all", "all").action(async function action21(url, opts) {
13368
+ var test2 = new Command("test").description("Evaluate experiment URL targeting for a page").argument("<url>", "URL to evaluate").option("--status <status>", "Comma-separated statuses to include, or all", "all").action(async function action58(url, opts) {
12120
13369
  const creds2 = requireCredentials();
12121
13370
  const res = await testTargeting(creds2, url, { status: opts.status });
12122
13371
  if (!res.ok || !res.data) {
@@ -12152,17 +13401,17 @@ var test = new Command("test").description("Evaluate experiment URL targeting fo
12152
13401
  }
12153
13402
  });
12154
13403
  });
12155
- var targetingCommand = new Command("targeting").description("Test experiment targeting").addCommand(test);
13404
+ var targetingCommand = new Command("targeting").description("Test experiment targeting").addCommand(test2);
12156
13405
 
12157
13406
  // src/commands/sos.ts
12158
13407
  init_source();
12159
- var import_node_fs18 = __toESM(require("node:fs"));
12160
- var import_node_path16 = __toESM(require("node:path"));
13408
+ var import_node_fs19 = __toESM(require("node:fs"));
13409
+ var import_node_path17 = __toESM(require("node:path"));
12161
13410
  var SOS_TYPES = /* @__PURE__ */ new Set(["full_handover", "design", "development", "qa", "config", "bug"]);
12162
13411
  function readText(value) {
12163
13412
  if (!value) return void 0;
12164
13413
  if (!value.startsWith("@")) return value;
12165
- return import_node_fs18.default.readFileSync(import_node_path16.default.resolve(process.cwd(), value.slice(1)), "utf8");
13414
+ return import_node_fs19.default.readFileSync(import_node_path17.default.resolve(process.cwd(), value.slice(1)), "utf8");
12166
13415
  }
12167
13416
  function printTicket(ticket, reused) {
12168
13417
  console.log();
@@ -12173,7 +13422,7 @@ function printTicket(ticket, reused) {
12173
13422
  console.log(` Status: ${ticket.my_status ?? ticket.mapped_status}`);
12174
13423
  console.log();
12175
13424
  }
12176
- var create3 = new Command("create").description("Create an Apex SOS ticket").option("--type <type>", "full_handover|design|development|qa|config|bug", "qa").option("--loom <url>", "Loom URL with reproduction or QA context").option("--experiment <id>", "Experiment ID").option("--sub-class <text>", "SOS sub-classification").option("--description <text-or-@file>", "Description text or @path").argument("[description]", "Description text").action(async function action22(descriptionArg, opts) {
13425
+ var create4 = new Command("create").description("Create an Apex SOS ticket").option("--type <type>", "full_handover|design|development|qa|config|bug", "qa").option("--loom <url>", "Loom URL with reproduction or QA context").option("--experiment <id>", "Experiment ID").option("--sub-class <text>", "SOS sub-classification").option("--description <text-or-@file>", "Description text or @path").argument("[description]", "Description text").action(async function action59(descriptionArg, opts) {
12177
13426
  if (!SOS_TYPES.has(opts.type)) {
12178
13427
  fail(this, "Invalid SOS type. Use full_handover, design, development, qa, config, or bug.", 3);
12179
13428
  }
@@ -12191,7 +13440,7 @@ var create3 = new Command("create").description("Create an Apex SOS ticket").opt
12191
13440
  }
12192
13441
  output(this, res.data, () => printTicket(res.data.ticket, res.data.reused));
12193
13442
  });
12194
- var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function action23() {
13443
+ var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function action60() {
12195
13444
  const creds2 = requireCredentials();
12196
13445
  const res = await listMySos(creds2);
12197
13446
  if (!res.ok || !res.data) {
@@ -12215,18 +13464,18 @@ var mine = new Command("mine").description("List SOS tickets created by this API
12215
13464
  );
12216
13465
  });
12217
13466
  });
12218
- var sosCommand = new Command("sos").description("Create and inspect Apex SOS tickets").addCommand(create3).addCommand(mine);
13467
+ var sosCommand = new Command("sos").description("Create and inspect Apex SOS tickets").addCommand(create4).addCommand(mine);
12219
13468
 
12220
13469
  // src/commands/audit-log.ts
12221
- var list8 = new Command("list").alias("ls").option("--entity-type <type>").option("--entity-id <id>").option("-n, --limit <n>", "Max results", "50").option("--offset <n>", "Pagination offset", "0").action(async function(opts) {
12222
- const res = await listAuditLogs(requireCredentials(), { entityType: opts.entityType, entityId: opts.entityId, limit: Number.parseInt(opts.limit, 10), offset: Number.parseInt(opts.offset, 10) });
13470
+ var list10 = new Command("list").alias("ls").option("--entity-type <type>").option("--entity-id <id>").option("--shop <id>", "Shop ID for user-key fallback").option("-n, --limit <n>", "Max results", "50").option("--offset <n>", "Pagination offset", "0").action(async function(opts) {
13471
+ const res = await listAuditLogs(requireCredentials(), { entityType: opts.entityType, entityId: opts.entityId, shopId: opts.shop, limit: Number.parseInt(opts.limit, 10), offset: Number.parseInt(opts.offset, 10) });
12223
13472
  if (!res.ok || !res.data) fail(this, res.error ?? "Failed to list audit logs", classifyStatus(res.status));
12224
13473
  output(this, res.data, () => printTable(["TIME", "ACTION", "ENTITY", "ENTITY_ID", "ACTOR"], res.data.data.map((x) => [x.created_at, x.action, x.entity_type, x.entity_id, x.user_id])));
12225
13474
  });
12226
- var auditLogCommand = new Command("audit-log").description("Inspect audit logs").addCommand(list8);
13475
+ var auditLogCommand = new Command("audit-log").description("Inspect audit logs").addCommand(list10);
12227
13476
 
12228
13477
  // src/commands/inbox.ts
12229
- var list9 = new Command("list").option("--experiment <id>").action(async function(opts) {
13478
+ var list11 = new Command("list").option("--experiment <id>").action(async function(opts) {
12230
13479
  const r = await listInbox(requireCredentials(), opts.experiment);
12231
13480
  if (!r.ok || !r.data) fail(this, r.error ?? "Failed to list inbox", classifyStatus(r.status));
12232
13481
  output(this, r.data, () => printTable(["TIME", "ROLE", "MESSAGE"], r.data.messages.map((m) => [m.created_at, m.role, m.body])));
@@ -12241,16 +13490,16 @@ var markRead = new Command("mark-read").option("--channel <key>", "Mark one chan
12241
13490
  if (!r.ok || !r.data) fail(this, r.error ?? "Failed to mark inbox read", classifyStatus(r.status));
12242
13491
  output(this, r.data, () => console.log(`Marked ${r.data.channelKeys.length} channel(s) read`));
12243
13492
  });
12244
- var inboxCommand = new Command("inbox").description("Read and send inbox messages").addCommand(list9).addCommand(send).addCommand(markRead);
13493
+ var inboxCommand = new Command("inbox").description("Read and send inbox messages").addCommand(list11).addCommand(send).addCommand(markRead);
12245
13494
 
12246
13495
  // src/commands/shops.ts
12247
- var import_node_fs19 = __toESM(require("node:fs"));
12248
- var import_node_path17 = __toESM(require("node:path"));
13496
+ var import_node_fs20 = __toESM(require("node:fs"));
13497
+ var import_node_path18 = __toESM(require("node:path"));
12249
13498
  function body4(command, raw) {
12250
13499
  if (!raw) return {};
12251
13500
  try {
12252
13501
  const source = raw.startsWith("@") ? raw.slice(1) : raw;
12253
- const text = raw.startsWith("@") || import_node_fs19.default.existsSync(source) ? import_node_fs19.default.readFileSync(import_node_path17.default.resolve(source), "utf8") : raw;
13502
+ const text = raw.startsWith("@") || import_node_fs20.default.existsSync(source) ? import_node_fs20.default.readFileSync(import_node_path18.default.resolve(source), "utf8") : raw;
12254
13503
  const value = JSON.parse(text);
12255
13504
  if (!value || typeof value !== "object" || Array.isArray(value))
12256
13505
  throw new Error("body must be an object");
@@ -12271,9 +13520,10 @@ function done(command, r, label) {
12271
13520
  classifyStatus(r.status),
12272
13521
  r.errorDetails
12273
13522
  );
12274
- output(command, r.data, () => console.log(JSON.stringify(r.data, null, 2)));
13523
+ output(command, r.data, () => process.stdout.write(`${JSON.stringify(r.data, null, 2)}
13524
+ `));
12275
13525
  }
12276
- var list10 = new Command("list").alias("ls").description("List organization shops").action(async function() {
13526
+ var list12 = new Command("list").alias("ls").description("List organization shops").action(async function() {
12277
13527
  const r = await listShops(requireUserCredentials());
12278
13528
  if (!r.ok || !r.data)
12279
13529
  fail(
@@ -12297,10 +13547,10 @@ var list10 = new Command("list").alias("ls").description("List organization shop
12297
13547
  )
12298
13548
  );
12299
13549
  });
12300
- var get6 = new Command("get").description("Get a shop").argument("<id>").action(async function(id) {
13550
+ var get8 = new Command("get").description("Get a shop").argument("<id>").action(async function(id) {
12301
13551
  done(this, await getShop(requireUserCredentials(), id), "get shop");
12302
13552
  });
12303
- var create4 = new Command("create").description("Create a shop").argument("[json]", "Inline JSON, @file, or file path").option("--name <name>", "Shop name").option("--domain <domain>", "Public shop domain").option("--origin <origin>", "Backend origin URL").option("--currency <code>", "Three-letter currency code").action(async function(raw, o) {
13553
+ var create5 = new Command("create").description("Create a shop").argument("[json]", "Inline JSON, @file, or file path").option("--name <name>", "Shop name").option("--domain <domain>", "Public shop domain").option("--origin <origin>", "Backend origin URL").option("--currency <code>", "Three-letter currency code").action(async function(raw, o) {
12304
13554
  const flags = {
12305
13555
  ...o.name ? { name: o.name } : {},
12306
13556
  ...o.domain ? { domain: o.domain } : {},
@@ -12313,7 +13563,7 @@ var create4 = new Command("create").description("Create a shop").argument("[json
12313
13563
  const r = await createShop(requireUserCredentials(), payload);
12314
13564
  done(this, r, "create shop");
12315
13565
  });
12316
- var update2 = new Command("update").description("Update a shop").argument("<id>").argument("[json]", "Inline JSON, @file, or file path").option("--holdout-pct <percent>", "Global holdout percent (0-10)").option("--holdout-enabled", "Enable global holdout v1").option("--holdout-disabled", "Disable global holdout v1").option("--confirm-tier", "Confirm a commercially sensitive tier change").action(async function(id, raw, o) {
13566
+ var update3 = new Command("update").description("Update a shop").argument("<id>").argument("[json]", "Inline JSON, @file, or file path").option("--holdout-pct <percent>", "Global holdout percent (0-10)").option("--holdout-enabled", "Enable global holdout v1").option("--holdout-disabled", "Disable global holdout v1").option("--confirm-tier", "Confirm a commercially sensitive tier change").action(async function(id, raw, o) {
12317
13567
  const b = raw === void 0 ? {} : body4(this, raw);
12318
13568
  if (o.holdoutEnabled && o.holdoutDisabled)
12319
13569
  fail(this, "Use only one of --holdout-enabled or --holdout-disabled", EXIT_CODES.validation);
@@ -12352,10 +13602,10 @@ function settings(name, description, toggle) {
12352
13602
  async function(id, raw) {
12353
13603
  const opts = this.opts();
12354
13604
  const on = toggle ? Boolean(opts[toggle.on]) : false;
12355
- const off = toggle ? Boolean(opts[toggle.off]) : false;
12356
- if (on && off)
13605
+ const off3 = toggle ? Boolean(opts[toggle.off]) : false;
13606
+ if (on && off3)
12357
13607
  fail(this, `Use only one of --${toggle.on} or --${toggle.off}`, EXIT_CODES.validation);
12358
- const flagBody = on || off ? { enabled: on } : void 0;
13608
+ const flagBody = on || off3 ? { enabled: on } : void 0;
12359
13609
  const patchBody = raw !== void 0 || flagBody !== void 0 ? { ...raw !== void 0 ? body4(this, raw) : {}, ...flagBody ?? {} } : void 0;
12360
13610
  const r = patchBody !== void 0 ? await patchShopArea(requireUserCredentials(), id, name, patchBody) : await getShopArea(requireUserCredentials(), id, name);
12361
13611
  if (!r.ok || !r.data)
@@ -12365,7 +13615,8 @@ function settings(name, description, toggle) {
12365
13615
  classifyStatus(r.status),
12366
13616
  r.errorDetails
12367
13617
  );
12368
- output(this, r.data, () => console.log(JSON.stringify(r.data, null, 2)));
13618
+ output(this, r.data, () => process.stdout.write(`${JSON.stringify(r.data, null, 2)}
13619
+ `));
12369
13620
  }
12370
13621
  );
12371
13622
  }
@@ -12406,7 +13657,8 @@ var reviewFact = new Command("review-fact").argument("<id>").argument("<fact-id>
12406
13657
  classifyStatus(r.status),
12407
13658
  r.errorDetails
12408
13659
  );
12409
- output(this, r.data, () => console.log(JSON.stringify(r.data, null, 2)));
13660
+ output(this, r.data, () => process.stdout.write(`${JSON.stringify(r.data, null, 2)}
13661
+ `));
12410
13662
  });
12411
13663
  var reviewFinding = new Command("review-finding").argument("<id>").argument("<json>").action(async function(id, raw) {
12412
13664
  const r = await postShopArea(
@@ -12422,7 +13674,8 @@ var reviewFinding = new Command("review-finding").argument("<id>").argument("<js
12422
13674
  classifyStatus(r.status),
12423
13675
  r.errorDetails
12424
13676
  );
12425
- output(this, r.data, () => console.log(JSON.stringify(r.data, null, 2)));
13677
+ output(this, r.data, () => process.stdout.write(`${JSON.stringify(r.data, null, 2)}
13678
+ `));
12426
13679
  });
12427
13680
  var startLearningRun = new Command("start-learning-run").argument("<id>").argument("[json]").action(async function(id, raw) {
12428
13681
  const r = await postShopArea(
@@ -12438,7 +13691,8 @@ var startLearningRun = new Command("start-learning-run").argument("<id>").argume
12438
13691
  classifyStatus(r.status),
12439
13692
  r.errorDetails
12440
13693
  );
12441
- output(this, r.data, () => console.log(JSON.stringify(r.data, null, 2)));
13694
+ output(this, r.data, () => process.stdout.write(`${JSON.stringify(r.data, null, 2)}
13695
+ `));
12442
13696
  });
12443
13697
  profile.addCommand(reviewFact).addCommand(reviewFinding).addCommand(startLearningRun);
12444
13698
  var shopware = new Command("shopware").description("Manage Shopware bindings");
@@ -12455,7 +13709,7 @@ shopware.addCommand(
12455
13709
  output(this, result.data, () => console.log(`Pre-bound ${options.shopUrl} to ${id}`));
12456
13710
  })
12457
13711
  );
12458
- var shopsCommand = new Command("shops").description("Manage organization shops").addCommand(list10).addCommand(get6).addCommand(create4).addCommand(update2).addCommand(settings("auto-stop", "Read or update auto-stop defaults", { on: "enable", off: "disable" })).addCommand(settings("testing-defaults", "Read or update testing defaults")).addCommand(settings("guardian", "Read or update guardian defaults", { on: "on", off: "off" })).addCommand(profile).addCommand(shopware);
13712
+ var shopsCommand = new Command("shops").description("Manage organization shops").addCommand(list12).addCommand(get8).addCommand(create5).addCommand(update3).addCommand(settings("auto-stop", "Read or update auto-stop defaults", { on: "enable", off: "disable" })).addCommand(settings("testing-defaults", "Read or update testing defaults")).addCommand(settings("guardian", "Read or update guardian defaults", { on: "on", off: "off" })).addCommand(profile).addCommand(shopware);
12459
13713
 
12460
13714
  // src/commands/schema.ts
12461
13715
  function optionSchema(option) {
@@ -12486,25 +13740,26 @@ function commandSchema(command) {
12486
13740
  };
12487
13741
  }
12488
13742
  function createSchemaCommand(program3) {
12489
- return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function action25() {
13743
+ return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function action68() {
12490
13744
  const schema = commandSchema(program3);
12491
13745
  output(this, schema, () => {
12492
- console.log(JSON.stringify(schema, null, 2));
13746
+ process.stdout.write(`${JSON.stringify(schema, null, 2)}
13747
+ `);
12493
13748
  });
12494
13749
  });
12495
13750
  }
12496
13751
 
12497
13752
  // src/commands/skills.ts
12498
- var import_node_fs20 = __toESM(require("node:fs"));
12499
- var import_node_path18 = __toESM(require("node:path"));
13753
+ var import_node_fs21 = __toESM(require("node:fs"));
13754
+ var import_node_path19 = __toESM(require("node:path"));
12500
13755
  function packageRoot() {
12501
13756
  const candidates = [
12502
- import_node_path18.default.resolve(__dirname, ".."),
12503
- import_node_path18.default.resolve(import_node_path18.default.dirname(process.argv[1] ?? process.cwd()), ".."),
12504
- import_node_path18.default.resolve(process.cwd(), "packages/cli")
13757
+ import_node_path19.default.resolve(__dirname, ".."),
13758
+ import_node_path19.default.resolve(import_node_path19.default.dirname(process.argv[1] ?? process.cwd()), ".."),
13759
+ import_node_path19.default.resolve(process.cwd(), "packages/cli")
12505
13760
  ];
12506
13761
  for (const candidate of candidates) {
12507
- if (import_node_fs20.default.existsSync(import_node_path18.default.join(candidate, "AGENTS.md"))) return candidate;
13762
+ if (import_node_fs21.default.existsSync(import_node_path19.default.join(candidate, "AGENTS.md"))) return candidate;
12508
13763
  }
12509
13764
  throw new Error(
12510
13765
  `Apex CLI package files not found. Looked for AGENTS.md in: ${candidates.join(", ")}`
@@ -12513,24 +13768,24 @@ function packageRoot() {
12513
13768
  function skillPaths() {
12514
13769
  const root = packageRoot();
12515
13770
  return {
12516
- agents: import_node_path18.default.join(root, "AGENTS.md"),
12517
- skill: import_node_path18.default.join(root, "skills", "apex-cli", "SKILL.md")
13771
+ agents: import_node_path19.default.join(root, "AGENTS.md"),
13772
+ skill: import_node_path19.default.join(root, "skills", "apex-cli", "SKILL.md")
12518
13773
  };
12519
13774
  }
12520
13775
  function installSkill(paths, targetRoot) {
12521
- const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs20.default.existsSync(filePath));
13776
+ const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs21.default.existsSync(filePath));
12522
13777
  if (missing.length > 0) {
12523
13778
  throw new Error(
12524
13779
  `Apex CLI package files not found. Missing: ${missing.join(", ")}`
12525
13780
  );
12526
13781
  }
12527
- const target = import_node_path18.default.resolve(targetRoot, "apex-cli");
12528
- import_node_fs20.default.mkdirSync(target, { recursive: true });
12529
- import_node_fs20.default.copyFileSync(paths.skill, import_node_path18.default.join(target, "SKILL.md"));
12530
- import_node_fs20.default.copyFileSync(paths.agents, import_node_path18.default.join(target, "AGENTS.md"));
13782
+ const target = import_node_path19.default.resolve(targetRoot, "apex-cli");
13783
+ import_node_fs21.default.mkdirSync(target, { recursive: true });
13784
+ import_node_fs21.default.copyFileSync(paths.skill, import_node_path19.default.join(target, "SKILL.md"));
13785
+ import_node_fs21.default.copyFileSync(paths.agents, import_node_path19.default.join(target, "AGENTS.md"));
12531
13786
  return target;
12532
13787
  }
12533
- var skillsCommand = new Command("skills").description("Print or install the Apex CLI agent skill files").option("--install", "Copy the Apex CLI skill into ./.claude/skills/apex-cli").option("--target <dir>", "Install target skills directory", ".claude/skills").option("--json", "Print JSON").action(function action24(opts) {
13788
+ var skillsCommand = new Command("skills").description("Print or install the Apex CLI agent skill files").option("--install", "Copy the Apex CLI skill into ./.claude/skills/apex-cli").option("--target <dir>", "Install target skills directory", ".claude/skills").option("--json", "Print JSON").action(function action61(opts) {
12534
13789
  let paths;
12535
13790
  try {
12536
13791
  paths = skillPaths();
@@ -12564,7 +13819,7 @@ var settingsGet = new Command("settings").description("Show platform operator se
12564
13819
  if (!res.ok || !res.data) fail(this, res.error ?? "Failed to read settings", classifyStatus(res.status));
12565
13820
  output(this, res.data, () => printTable(["SETTING", "VALUE"], Object.entries(res.data.data).map(([k, v]) => [k, v ? "on" : "off"])));
12566
13821
  });
12567
- var set = new Command("set").description("Set platform kill switches").option("--guardian <on|off>").option("--srm-check <on|off>").option("--confirm", "Confirm immediate production impact").action(async function(opts) {
13822
+ var set4 = new Command("set").description("Set platform kill switches").option("--guardian <on|off>").option("--srm-check <on|off>").option("--confirm", "Confirm immediate production impact").action(async function(opts) {
12568
13823
  if (!opts.guardian && !opts.srmCheck) fail(this, "Provide --guardian or --srm-check", 3);
12569
13824
  let body5 = {};
12570
13825
  try {
@@ -12581,7 +13836,7 @@ var set = new Command("set").description("Set platform kill switches").option("-
12581
13836
  if (!res.ok || !res.data) fail(this, res.error ?? "Failed to update settings", classifyStatus(res.status));
12582
13837
  output(this, res.data, () => console.log("Platform settings updated."));
12583
13838
  });
12584
- settingsGet.addCommand(set);
13839
+ settingsGet.addCommand(set4);
12585
13840
  var quarantine = new Command("quarantine").description("List tracking quarantine events").action(async function() {
12586
13841
  const res = await getAdminQuarantine(requireCredentials());
12587
13842
  if (!res.ok || !res.data) fail(this, res.error ?? "Failed to read quarantine", classifyStatus(res.status));
@@ -12591,10 +13846,11 @@ var adminCommand = new Command("admin").description("Operate allowlisted platfor
12591
13846
 
12592
13847
  // src/commands/membership.ts
12593
13848
  init_source();
12594
- async function run(c, p, label) {
13849
+ async function run2(c, p, label) {
12595
13850
  const r = await p;
12596
13851
  if (!r.ok) fail(c, r.error ?? `Failed to ${label}`, classifyStatus(r.status), r.errorDetails);
12597
- output(c, r.data ?? {}, () => console.log(JSON.stringify(r.data, null, 2)));
13852
+ output(c, r.data ?? {}, () => process.stdout.write(`${JSON.stringify(r.data, null, 2)}
13853
+ `));
12598
13854
  }
12599
13855
  async function confirm(c, force, label, retry) {
12600
13856
  if (force) return true;
@@ -12611,43 +13867,43 @@ async function confirm(c, force, label, retry) {
12611
13867
  }
12612
13868
  var orgCommand = new Command("org").description("Manage the current organization");
12613
13869
  orgCommand.command("get").action(async function() {
12614
- await run(this, getOrg(requireUserCredentials()), "get organization");
13870
+ await run2(this, getOrg(requireUserCredentials()), "get organization");
12615
13871
  });
12616
13872
  orgCommand.command("update").option("--name <name>").option("--slug <slug>").option("--billing-email <email>").action(async function(o) {
12617
- await run(this, updateOrg(requireUserCredentials(), { ...o.name ? { name: o.name } : {}, ...o.slug ? { slug: o.slug } : {}, ...o.billingEmail ? { billing_email: o.billingEmail } : {} }), "update organization");
13873
+ await run2(this, updateOrg(requireUserCredentials(), { ...o.name ? { name: o.name } : {}, ...o.slug ? { slug: o.slug } : {}, ...o.billingEmail ? { billing_email: o.billingEmail } : {} }), "update organization");
12618
13874
  });
12619
13875
  orgCommand.command("members").action(async function() {
12620
- await run(this, listOrgMembers(requireUserCredentials()), "list members");
13876
+ await run2(this, listOrgMembers(requireUserCredentials()), "list members");
12621
13877
  });
12622
13878
  orgCommand.command("invite").argument("<email>").option("--role <role>", "Alias for --org-role").option("--org-role <role>", "Organization role", "member").option("--default-shop-role <role>", "Default shop role", "viewer").action(async function(email, o) {
12623
- await run(this, inviteOrg(requireUserCredentials(), email, o.role ?? o.orgRole, o.defaultShopRole), "invite member");
13879
+ await run2(this, inviteOrg(requireUserCredentials(), email, o.role ?? o.orgRole, o.defaultShopRole), "invite member");
12624
13880
  });
12625
13881
  orgCommand.command("revoke-invite").argument("<id>").action(async function(id) {
12626
- await run(this, revokeOrgInvite(requireUserCredentials(), id), "revoke invitation");
13882
+ await run2(this, revokeOrgInvite(requireUserCredentials(), id), "revoke invitation");
12627
13883
  });
12628
13884
  orgCommand.command("set-role").argument("<memberId>").argument("<role>").option("--default-shop-role <role>").action(async function(id, role, o) {
12629
- await run(this, setOrgMember(requireUserCredentials(), id, { role, ...o.defaultShopRole ? { default_shop_role: o.defaultShopRole } : {} }), "set role");
13885
+ await run2(this, setOrgMember(requireUserCredentials(), id, { role, ...o.defaultShopRole ? { default_shop_role: o.defaultShopRole } : {} }), "set role");
12630
13886
  });
12631
13887
  orgCommand.command("remove-member").argument("<memberId>").option("-f, --force").action(async function(id, o) {
12632
13888
  if (!await confirm(this, o.force, `Remove organization member ${id}`, `apex org remove-member ${id} --force`)) return;
12633
- await run(this, removeOrgMember(requireUserCredentials(), id), "remove member");
13889
+ await run2(this, removeOrgMember(requireUserCredentials(), id), "remove member");
12634
13890
  });
12635
13891
  var teamCommand = new Command("team").description("Manage a shop team");
12636
13892
  teamCommand.command("members").requiredOption("--shop <id>").action(async function(o) {
12637
- await run(this, listTeamMembers(requireUserCredentials(), o.shop), "list team");
13893
+ await run2(this, listTeamMembers(requireUserCredentials(), o.shop), "list team");
12638
13894
  });
12639
13895
  teamCommand.command("invite").argument("<email>").requiredOption("--shop <id>").requiredOption("--role <role>").action(async function(email, o) {
12640
- await run(this, inviteTeam(requireUserCredentials(), email, o.shop, o.role), "invite team member");
13896
+ await run2(this, inviteTeam(requireUserCredentials(), email, o.shop, o.role), "invite team member");
12641
13897
  });
12642
13898
  teamCommand.command("revoke-invite").argument("<id>").requiredOption("--shop <id>").action(async function(id, o) {
12643
- await run(this, revokeTeamInvite(requireUserCredentials(), id, o.shop), "revoke invitation");
13899
+ await run2(this, revokeTeamInvite(requireUserCredentials(), id, o.shop), "revoke invitation");
12644
13900
  });
12645
13901
  teamCommand.command("set-role").argument("<memberId>").argument("<role>").requiredOption("--shop <id>").action(async function(id, role, o) {
12646
- await run(this, setTeamMember(requireUserCredentials(), id, o.shop, role), "set role");
13902
+ await run2(this, setTeamMember(requireUserCredentials(), id, o.shop, role), "set role");
12647
13903
  });
12648
13904
  teamCommand.command("remove").argument("<memberId>").requiredOption("--shop <id>").option("-f, --force").action(async function(id, o) {
12649
13905
  if (!await confirm(this, o.force, `Remove team member ${id}`, `apex team remove ${id} --shop ${o.shop} --force`)) return;
12650
- await run(this, removeTeamMember(requireUserCredentials(), id, o.shop), "remove member");
13906
+ await run2(this, removeTeamMember(requireUserCredentials(), id, o.shop), "remove member");
12651
13907
  });
12652
13908
 
12653
13909
  // src/commands/account.ts
@@ -12656,7 +13912,7 @@ function userCredentials2() {
12656
13912
  if (!creds2.userApiKey) throw new CliError('User authentication required. Run "apex login --user".', EXIT_CODES.auth);
12657
13913
  return creds2;
12658
13914
  }
12659
- var set2 = new Command("set").option("--name <text>", "Display name").option("--avatar <url>", "Avatar data URL").action(async function(opts) {
13915
+ var set5 = new Command("set").option("--name <text>", "Display name").option("--avatar <url>", "Avatar data URL").action(async function(opts) {
12660
13916
  if (opts.name === void 0 && opts.avatar === void 0) throw new CliError("At least one of --name or --avatar is required", EXIT_CODES.validation);
12661
13917
  const res = await setAccountProfile(userCredentials2(), { ...opts.name !== void 0 ? { displayName: opts.name } : {}, ...opts.avatar !== void 0 ? { avatarDataUrl: opts.avatar } : {} });
12662
13918
  if (!res.ok || !res.data) fail(this, res.error ?? "Failed to update account profile", classifyStatus(res.status));
@@ -12667,7 +13923,7 @@ var profile2 = new Command("profile").description("Show the current account prof
12667
13923
  if (!res.ok || !res.data) fail(this, res.error ?? "Failed to get account profile", classifyStatus(res.status));
12668
13924
  output(this, res.data, () => console.log(`${res.data.data.displayName ?? "(no name)"}
12669
13925
  ${res.data.data.avatarDataUrl ?? "(no avatar)"}`));
12670
- }).addCommand(set2);
13926
+ }).addCommand(set5);
12671
13927
  var accountCommand = new Command("account").description("Manage your Apex account").addCommand(profile2);
12672
13928
 
12673
13929
  // src/commands/keys.ts
@@ -12676,12 +13932,12 @@ function creds() {
12676
13932
  if (!value.userApiKey) throw new CliError('User authentication required. Run "apex login --user".', EXIT_CODES.auth);
12677
13933
  return value;
12678
13934
  }
12679
- var list11 = new Command("list").requiredOption("--shop <id>").action(async function(opts) {
13935
+ var list13 = new Command("list").requiredOption("--shop <id>").action(async function(opts) {
12680
13936
  const res = await listUserManagedKeys(creds(), opts.shop);
12681
13937
  if (!res.ok || !res.data) fail(this, res.error ?? "Failed to list API keys", classifyStatus(res.status));
12682
13938
  output(this, res.data, () => printTable(["ID", "NAME", "PREFIX", "TYPE"], res.data.data.map((key) => [key.id, key.name, key.key_prefix, key.type])));
12683
13939
  });
12684
- var create5 = new Command("create").requiredOption("--shop <id>").requiredOption("--name <name>").requiredOption("--type <type>").option("--permissions <csv>").action(async function(opts) {
13940
+ var create6 = new Command("create").requiredOption("--shop <id>").requiredOption("--name <name>").requiredOption("--type <type>").option("--permissions <csv>").action(async function(opts) {
12685
13941
  if (!["live_secret", "test_secret", "publishable"].includes(opts.type)) throw new CliError("--type must be live_secret, test_secret, or publishable", EXIT_CODES.validation);
12686
13942
  const permissions = opts.permissions?.split(",").map((item) => item.trim()).filter(Boolean);
12687
13943
  const res = await createUserManagedKey(creds(), { shop_id: opts.shop, name: opts.name, type: opts.type, ...permissions?.length ? { permissions } : {} });
@@ -12696,13 +13952,311 @@ var revoke = new Command("revoke").argument("<keyId>").requiredOption("--shop <i
12696
13952
  if (!res.ok || !res.data) fail(this, res.error ?? "Failed to revoke API key", classifyStatus(res.status));
12697
13953
  output(this, res.data, () => console.log(`Revoked ${keyId}`));
12698
13954
  });
12699
- var keysCommand = new Command("keys").description("Manage shop API keys").addCommand(list11).addCommand(create5).addCommand(revoke);
13955
+ var keysCommand = new Command("keys").description("Manage shop API keys").addCommand(list13).addCommand(create6).addCommand(revoke);
13956
+
13957
+ // src/commands/prospect.ts
13958
+ init_source();
13959
+
13960
+ // src/lib/detect-markers.ts
13961
+ var DETECTION_MARKERS = [
13962
+ { id: "cloudflare_challenge", kind: "protected", verdict: "Cloudflare challenge", match: /cf-browser-verification|cf-chl-|cdn-cgi\/challenge-platform/i },
13963
+ { id: "shopify_cart_js", kind: "platform", verdict: "Shopify", confidence: "high", recipe: { placement: "Shopify theme.liquid before </head> or the customer-deployed edge worker for headless themes." }, match: /__apex_secondary_shopify_cart_js__/i },
13964
+ { id: "shopify", kind: "platform", verdict: "Shopify", confidence: "high", recipe: { placement: "Shopify theme.liquid before </head> or the customer-deployed edge worker for headless themes." }, match: /cdn\.shopify\.com|Shopify\.theme|window\.Shopify|\/cart\.js/i },
13965
+ { id: "woocommerce", kind: "platform", verdict: "WooCommerce", confidence: "high", recipe: { placement: "WordPress header or WooCommerce plugin template before </head>." }, match: /woocommerce|wp-content\/plugins\/woocommerce|wc-cart-fragments/i },
13966
+ { id: "shopware", kind: "platform", verdict: "Shopware", confidence: "high", recipe: { placement: "Shopware storefront base template before </head>." }, match: /shopware|sw-?storefront|data-shopware/i },
13967
+ { id: "next_headless", kind: "platform", verdict: "Next.js headless", confidence: "high", recipe: { placement: "Headless app document/root layout before </head> so Apex loads on every route." }, match: /__NEXT_DATA__|\/_next\//i },
13968
+ { id: "webflow", kind: "platform", verdict: "Webflow", confidence: "high", recipe: { placement: "Webflow Project Settings \u2192 Custom Code \u2192 Head Code." }, match: /data-wf-page|data-wf-site|webflow\.js|assets\.website-files\.com/i },
13969
+ { id: "cookiescript", kind: "cmp", verdict: "CookieScript", recipe: { consentProvider: "cookiescript", consentGroup: "targeting" }, match: /cookie-script\.com|CookieScript|cookiescript/i },
13970
+ { id: "usercentrics", kind: "cmp", verdict: "Usercentrics", recipe: { consentProvider: "usercentrics", consentGroup: "marketing" }, match: /app\.usercentrics\.eu|usercentrics\.eu|UC_UI|usercentrics/i },
13971
+ { id: "cookiebot", kind: "cmp", verdict: "Cookiebot", recipe: { consentProvider: "cookiebot", consentGroup: "marketing" }, match: /consent\.cookiebot\.com|Cookiebot/i },
13972
+ { id: "ccm19", kind: "cmp", verdict: "CCM19", recipe: { consentProvider: "ccm19", consentGroup: "targeting" }, match: /ccm19\.de|CCM\.|ccm19/i },
13973
+ { id: "onetrust", kind: "cmp", verdict: "OneTrust", recipe: { consentProvider: "onetrust", consentGroup: "C0004" }, match: /cdn\.cookielaw\.org|Optanon|OneTrust/i },
13974
+ { id: "pandectes", kind: "cmp", verdict: "Pandectes", recipe: { consentProvider: "pandectes", consentGroup: "marketing" }, match: /pandectes|Pandectes/i },
13975
+ { id: "gtm", kind: "analytics", verdict: "Google Tag Manager", match: /googletagmanager\.com\/gtm\.js|GTM-[A-Z0-9]+/i },
13976
+ { id: "ga4", kind: "analytics", verdict: "Google Analytics", match: /googletagmanager\.com\/gtag\/js\?id=|\bG-[A-Z0-9]{6,}\b|google-analytics\.com/ },
13977
+ { id: "meta_pixel", kind: "analytics", verdict: "Meta Pixel", match: /fbevents\.js|fbq\(/i },
13978
+ { id: "next_spa", kind: "spa", verdict: "Next.js", recipe: { runtimeHint: "applyTiming=after_hydration" }, match: /__NEXT_DATA__|\/_next\//i },
13979
+ { id: "react", kind: "spa", verdict: "React", recipe: { runtimeHint: "applyTiming=after_hydration" }, match: /data-reactroot|react-dom|__REACT_DEVTOOLS_GLOBAL_HOOK__/i },
13980
+ { id: "vue", kind: "spa", verdict: "Vue", recipe: { runtimeHint: "applyTiming=after_hydration" }, match: /data-v-|__VUE__|vue\.runtime/i },
13981
+ { id: "apex_snippet", kind: "apex", verdict: "Apex snippet", match: /sdk\.drip-apex\.com\/drip\.js|events\.drip-apex\.com|edge\.drip-apex\.com|Drip\.init|data-apex/i }
13982
+ ];
13983
+ function matchDetectionMarkers(source, kind5) {
13984
+ return DETECTION_MARKERS.filter((marker) => (!kind5 || marker.kind === kind5) && marker.match.test(source));
13985
+ }
13986
+
13987
+ // src/commands/prospect.ts
13988
+ var DESKTOP_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36 ApexProspectAudit/1.0";
13989
+ var MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
13990
+ async function readResponseText(res) {
13991
+ if (!res.body) return "";
13992
+ const reader = res.body.getReader();
13993
+ const chunks = [];
13994
+ let totalBytes = 0;
13995
+ try {
13996
+ while (totalBytes < MAX_RESPONSE_BYTES) {
13997
+ const { done: done2, value } = await reader.read();
13998
+ if (done2) break;
13999
+ const remainingBytes = MAX_RESPONSE_BYTES - totalBytes;
14000
+ if (value.byteLength >= remainingBytes) {
14001
+ chunks.push(value.byteLength === remainingBytes ? value : value.slice(0, remainingBytes));
14002
+ totalBytes += remainingBytes;
14003
+ await reader.cancel().catch(() => void 0);
14004
+ break;
14005
+ }
14006
+ chunks.push(value);
14007
+ totalBytes += value.byteLength;
14008
+ }
14009
+ } finally {
14010
+ reader.releaseLock();
14011
+ }
14012
+ const body5 = new Uint8Array(totalBytes);
14013
+ let offset = 0;
14014
+ for (const chunk of chunks) {
14015
+ body5.set(chunk, offset);
14016
+ offset += chunk.byteLength;
14017
+ }
14018
+ return new TextDecoder().decode(body5);
14019
+ }
14020
+ async function fetchText(url) {
14021
+ if (process.env.APEX_PROSPECT_AUDIT_FIXTURE) {
14022
+ const fixture = JSON.parse(process.env.APEX_PROSPECT_AUDIT_FIXTURE);
14023
+ const hit = fixture[url] ?? fixture[new URL(url).pathname] ?? fixture["*"];
14024
+ if (hit?.throws) throw new Error(hit.throws);
14025
+ return { status: hit?.status ?? 200, text: hit?.body ?? "", finalUrl: url };
14026
+ }
14027
+ const res = await fetch(url, { headers: { "user-agent": DESKTOP_UA, accept: "text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8" }, redirect: "follow", signal: AbortSignal.timeout(12e3) });
14028
+ return { status: res.status, text: await readResponseText(res), finalUrl: res.url };
14029
+ }
14030
+ function abs(base, path21) {
14031
+ return new URL(path21, base).toString();
14032
+ }
14033
+ function looksLikeShopifyCart(text) {
14034
+ try {
14035
+ const payload = JSON.parse(text);
14036
+ return payload !== null && typeof payload === "object" && (typeof payload.token === "string" || payload.token === null) && Array.isArray(payload.items);
14037
+ } catch {
14038
+ return false;
14039
+ }
14040
+ }
14041
+ async function collectSignals(url) {
14042
+ let primary;
14043
+ try {
14044
+ primary = await fetchText(url);
14045
+ } catch {
14046
+ return { reachable: false, protected: false, source: "" };
14047
+ }
14048
+ const chunks = [`__apex_primary_status_${primary.status}__`, primary.text];
14049
+ const protectedHit = primary.status === 403 || primary.status === 429 || matchDetectionMarkers(primary.text, "protected").length > 0;
14050
+ if (!protectedHit) {
14051
+ for (const path21 of ["/cart.js"]) {
14052
+ try {
14053
+ const secondary = await fetchText(abs(primary.finalUrl || url, path21));
14054
+ if (secondary.status >= 200 && secondary.status < 300 && looksLikeShopifyCart(secondary.text)) {
14055
+ chunks.push("__apex_secondary_shopify_cart_js__", secondary.text.slice(0, 2e3));
14056
+ }
14057
+ } catch {
14058
+ }
14059
+ }
14060
+ }
14061
+ return { reachable: primary.status < 500, protected: protectedHit, source: chunks.join("\n") };
14062
+ }
14063
+ function unique2(values) {
14064
+ return [...new Set(values)];
14065
+ }
14066
+ async function runProspectAudit(url) {
14067
+ let parsed;
14068
+ try {
14069
+ parsed = new URL(url);
14070
+ } catch {
14071
+ parsed = new URL(`https://${url}`);
14072
+ }
14073
+ if (!["http:", "https:"].includes(parsed.protocol)) throw new Error("URL must use http or https");
14074
+ const { reachable, protected: isProtected, source } = await collectSignals(parsed.toString());
14075
+ if (isProtected) {
14076
+ return { url: parsed.toString(), reachable, protected: true, platform: { verdict: "unknown", confidence: "low", markers: [] }, cmp: { markers: [] }, analytics: [], spa: { markers: [] }, apexSnippet: false, recipe: { placement: "protected: verify in a real browser", runtimeHints: [] }, openQuestions: ["protected: verify in a real browser", "Who owns checkout and revenue-event access?"] };
14077
+ }
14078
+ const platforms = matchDetectionMarkers(source, "platform");
14079
+ const cmps = matchDetectionMarkers(source, "cmp");
14080
+ const analyticsMarkers = matchDetectionMarkers(source, "analytics");
14081
+ const spaMarkers = matchDetectionMarkers(source, "spa");
14082
+ const apexSnippet = matchDetectionMarkers(source, "apex").length > 0;
14083
+ const platform2 = platforms[0];
14084
+ const cmp = cmps[0];
14085
+ const runtimeHints = unique2(spaMarkers.map((marker) => marker.recipe?.runtimeHint).filter(Boolean));
14086
+ return {
14087
+ url: parsed.toString(),
14088
+ reachable,
14089
+ protected: false,
14090
+ platform: { verdict: platform2?.verdict ?? "unknown", confidence: platform2?.confidence ?? "low", markers: platforms.map((m) => m.id) },
14091
+ cmp: { ...cmp?.recipe?.consentProvider ? { vendor: cmp.verdict } : {}, markers: cmps.map((m) => m.id) },
14092
+ analytics: unique2(analyticsMarkers.map((m) => m.verdict)),
14093
+ spa: { ...spaMarkers[0] ? { framework: spaMarkers[0].verdict } : {}, markers: spaMarkers.map((m) => m.id) },
14094
+ apexSnippet,
14095
+ recipe: {
14096
+ placement: platform2?.recipe?.placement ?? "Install the Apex snippet in the global <head> before other experiment code, or use the edge worker for a headless/custom stack.",
14097
+ ...cmp?.recipe?.consentProvider && cmp.recipe.consentGroup ? { consentPreset: { provider: cmp.recipe.consentProvider, suggestedGroup: cmp.recipe.consentGroup } } : {},
14098
+ runtimeHints
14099
+ },
14100
+ openQuestions: ["Who owns checkout and can add/verify order attribution?", "Which revenue event is authoritative and available to Apex?", ...cmp ? [] : ["Which CMP consent category should gate analytics and storage?"]]
14101
+ };
14102
+ }
14103
+ var prospectCommand = new Command("prospect").description("Pre-sales storefront diagnostics").addCommand(new Command("audit").argument("<url>", "Prospect storefront URL").description("Audit a public storefront URL and recommend an Apex install recipe.").action(async (url, _options, command) => {
14104
+ try {
14105
+ const data = await runProspectAudit(url);
14106
+ if (data.protected) process.stderr.write("WARN protected: verify in a real browser\n");
14107
+ output(command, data, () => {
14108
+ console.log(source_default.bold(`Apex prospect audit: ${data.url}`));
14109
+ console.log(`Readiness: ${data.protected ? "WARN protected: verify in a real browser" : data.reachable ? "reachable" : "unreachable"}`);
14110
+ console.log(`Platform: ${data.platform.verdict} (${data.platform.confidence})`);
14111
+ console.log(`CMP: ${data.cmp.vendor ?? "unknown"}`);
14112
+ console.log(`Analytics: ${data.analytics.join(", ") || "none detected"}`);
14113
+ console.log(`SPA: ${data.spa.framework ?? "none detected"}`);
14114
+ console.log(`Apex snippet: ${data.apexSnippet ? "present" : "not detected"}`);
14115
+ console.log(`Install recipe: ${data.recipe.placement}`);
14116
+ if (data.recipe.consentPreset) console.log(`Consent preset: ${data.recipe.consentPreset.provider} / ${data.recipe.consentPreset.suggestedGroup}`);
14117
+ for (const hint of data.recipe.runtimeHints) console.log(`Runtime hint: ${hint}`);
14118
+ console.log("Open questions:");
14119
+ for (const question of data.openQuestions) console.log(`- ${question}`);
14120
+ });
14121
+ } catch (error) {
14122
+ fail(command, error instanceof Error ? error.message : String(error), EXIT_CODES.general);
14123
+ }
14124
+ }));
14125
+
14126
+ // src/commands/webhooks.ts
14127
+ init_source();
14128
+ var SIGNATURE_DOC = "https://app.drip-apex.com/docs/api/webhooks#signatures";
14129
+ function requireShopSecretKey(command) {
14130
+ const creds2 = requireCredentials();
14131
+ if (creds2.apiKey.startsWith("apx_uk_")) {
14132
+ fail(
14133
+ command,
14134
+ "apex webhooks requires a secret shop API key (apx_...). User API keys (apx_uk_...) are not accepted by shop-scoped v1 webhook routes.",
14135
+ EXIT_CODES.auth,
14136
+ { required: "secret_shop_api_key", rejectedPrefix: "apx_uk_" }
14137
+ );
14138
+ }
14139
+ }
14140
+ function handleResult(command, result, fallback2) {
14141
+ if (!result.ok || !result.data) {
14142
+ fail(command, result.error ?? fallback2, classifyStatus(result.status), result.errorDetails);
14143
+ }
14144
+ return result.data;
14145
+ }
14146
+ async function loadWebhookIndex(command) {
14147
+ requireShopSecretKey(command);
14148
+ return handleResult(command, await listWebhooks(requireCredentials()), "Failed to load webhooks");
14149
+ }
14150
+ function printWebhooks(items) {
14151
+ if (items.length === 0) {
14152
+ console.log(source_default.dim("No webhooks configured."));
14153
+ return;
14154
+ }
14155
+ printTable(
14156
+ ["ID", "ACTIVE", "FAILURES", "DISABLED", "SECRET", "URL", "EVENTS"],
14157
+ items.map((item) => [
14158
+ item.id,
14159
+ item.active ? "yes" : "no",
14160
+ String(item.consecutiveFailures ?? 0),
14161
+ item.disabledAt ?? "-",
14162
+ item.hasSecret ? "true" : "false",
14163
+ item.url,
14164
+ item.events.join(", ")
14165
+ ]),
14166
+ [28, 8, 8, 24, 8, 42, 48]
14167
+ );
14168
+ }
14169
+ function printDeliveries(deliveries2, webhook) {
14170
+ if (webhook) {
14171
+ console.log(`Webhook ${webhook.id}: ${webhook.consecutiveFailures ?? 0} consecutive failure(s)${webhook.disabledAt ? `, disabled at ${webhook.disabledAt}. Re-create the webhook to rotate its secret.` : ""}`);
14172
+ }
14173
+ if (deliveries2.length === 0) {
14174
+ console.log(source_default.dim("No deliveries found."));
14175
+ return;
14176
+ }
14177
+ printTable(
14178
+ ["ID", "EVENT", "STATUS", "ATTEMPTS", "HTTP", "ERROR", "CREATED"],
14179
+ deliveries2.map((delivery) => [
14180
+ delivery.id,
14181
+ delivery.event,
14182
+ delivery.status,
14183
+ String(delivery.attempts),
14184
+ delivery.response_status == null ? "-" : String(delivery.response_status),
14185
+ delivery.last_error ?? "-",
14186
+ delivery.created_at
14187
+ ]),
14188
+ [28, 28, 12, 8, 6, 36, 24]
14189
+ );
14190
+ }
14191
+ function selectedEvents(opts) {
14192
+ return [...new Set((opts.event ?? []).map((event) => event.trim()).filter(Boolean))];
14193
+ }
14194
+ var list14 = new Command("list").alias("ls").description("List webhooks. JSON includes availableEvents; secrets are never returned after create.").action(async function action62() {
14195
+ const data = await loadWebhookIndex(this);
14196
+ output(this, data, () => printWebhooks(data.data));
14197
+ });
14198
+ var create7 = new Command("create").description(`Create a webhook. Prints the signing secret once. Signature docs: ${SIGNATURE_DOC}`).requiredOption("--url <url>", "HTTPS callback URL").option("--event <event>", "Webhook event to subscribe to; repeat for multiple events", (value, previous = []) => [...previous, value], []).action(async function action63(opts) {
14199
+ const events = selectedEvents(opts);
14200
+ if (events.length === 0) fail(this, "At least one --event is required", EXIT_CODES.validation);
14201
+ const index = await loadWebhookIndex(this);
14202
+ const allowed = new Set(index.availableEvents);
14203
+ const unknown = events.filter((event) => !allowed.has(event));
14204
+ if (unknown.length > 0) {
14205
+ fail(this, `Unknown webhook event(s): ${unknown.join(", ")}`, EXIT_CODES.validation, { availableEvents: index.availableEvents });
14206
+ }
14207
+ const result = handleResult(this, await createWebhook(requireCredentials(), { url: opts.url, events }), "Failed to create webhook");
14208
+ output(this, { webhook: result, secret: result.secret, signatureDoc: SIGNATURE_DOC, warning: "Store this signing secret now. Apex will not show it again." }, () => {
14209
+ console.log(source_default.green(`Created webhook ${result.id}`));
14210
+ console.log(source_default.yellow("Store this signing secret now. Apex will not show it again."));
14211
+ console.log(result.secret);
14212
+ console.log(`Signature verification: ${SIGNATURE_DOC}`);
14213
+ });
14214
+ });
14215
+ var remove4 = new Command("delete").alias("rm").description("Delete a webhook").argument("<id>", "Webhook id").action(async function action64(id) {
14216
+ requireShopSecretKey(this);
14217
+ const result = handleResult(this, await deleteWebhook(requireCredentials(), id), "Failed to delete webhook");
14218
+ output(this, result, () => console.log(source_default.green(`Deleted webhook ${id}`)));
14219
+ });
14220
+ var deliveries = new Command("deliveries").description("List webhook deliveries including failure and disabled state").argument("<id>", "Webhook id").option("--limit <n>", "Maximum deliveries to show", "20").action(async function action65(id, opts) {
14221
+ const limit = Number.parseInt(opts.limit, 10);
14222
+ if (!Number.isInteger(limit) || limit < 1) fail(this, "--limit must be a positive integer", EXIT_CODES.validation);
14223
+ requireShopSecretKey(this);
14224
+ const result = handleResult(this, await getWebhookDeliveries(requireCredentials(), id, limit), "Failed to load deliveries");
14225
+ output(this, result, () => printDeliveries(result.data, result.webhook));
14226
+ });
14227
+ var update4 = new Command("update").description("Update a webhook URL, events, or active state").argument("<id>", "Webhook id").option("--url <url>", "HTTPS callback URL").option("--event <event>", "Replacement event; repeat for multiple events", (value, previous = []) => [...previous, value], []).option("--active", "Enable the webhook").option("--inactive", "Disable the webhook").action(async function action66(id, opts) {
14228
+ if (opts.active && opts.inactive) fail(this, "Use only one of --active or --inactive", EXIT_CODES.validation);
14229
+ const events = selectedEvents(opts);
14230
+ const body5 = {};
14231
+ if (opts.url) body5.url = opts.url;
14232
+ if (events.length > 0) {
14233
+ const index = await loadWebhookIndex(this);
14234
+ const allowed = new Set(index.availableEvents);
14235
+ const unknown = events.filter((event) => !allowed.has(event));
14236
+ if (unknown.length > 0) fail(this, `Unknown webhook event(s): ${unknown.join(", ")}`, EXIT_CODES.validation, { availableEvents: index.availableEvents });
14237
+ body5.events = events;
14238
+ } else {
14239
+ requireShopSecretKey(this);
14240
+ }
14241
+ if (opts.active) body5.active = true;
14242
+ if (opts.inactive) body5.active = false;
14243
+ if (Object.keys(body5).length === 0) fail(this, "Pass at least one of --url, --event, --active, or --inactive", EXIT_CODES.validation);
14244
+ const result = handleResult(this, await updateWebhook(requireCredentials(), id, body5), "Failed to update webhook");
14245
+ output(this, { webhook: result }, () => printWebhooks([result]));
14246
+ });
14247
+ var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function action67(id) {
14248
+ requireShopSecretKey(this);
14249
+ const result = handleResult(this, await testWebhook(requireCredentials(), id), "Failed to send test webhook");
14250
+ output(this, result, () => console.log(source_default.green(`Sent test delivery ${result.delivery.id}: ${result.delivery.status}`)));
14251
+ });
14252
+ var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(create7).addCommand(update4).addCommand(remove4).addCommand(deliveries).addCommand(test3);
12700
14253
 
12701
14254
  // src/commands/config-resources.ts
12702
- var import_node_fs21 = __toESM(require("node:fs"));
12703
- var import_node_path19 = __toESM(require("node:path"));
14255
+ var import_node_fs22 = __toESM(require("node:fs"));
14256
+ var import_node_path20 = __toESM(require("node:path"));
12704
14257
  function show(resource) {
12705
- console.log(JSON.stringify(resource, null, 2));
14258
+ process.stdout.write(`${JSON.stringify(resource, null, 2)}
14259
+ `);
12706
14260
  }
12707
14261
  var ASSET_MIME_TYPES = {
12708
14262
  ".png": "image/png",
@@ -12732,7 +14286,7 @@ async function confirmAssetDelete(id) {
12732
14286
  return answer.trim().toLowerCase() === "y";
12733
14287
  }
12734
14288
  function readFamily(family, label) {
12735
- const list12 = new Command("list").alias("ls").description(`List ${label}`).option("-n, --limit <n>", "Max results", "50").option("--cursor <id>", "Pagination cursor").action(async function(o) {
14289
+ const list15 = new Command("list").alias("ls").description(`List ${label}`).option("-n, --limit <n>", "Max results", "50").option("--cursor <id>", "Pagination cursor").action(async function(o) {
12736
14290
  const limit = Number(o.limit);
12737
14291
  if (!Number.isInteger(limit) || limit <= 0)
12738
14292
  fail(
@@ -12765,7 +14319,7 @@ function readFamily(family, label) {
12765
14319
  )
12766
14320
  );
12767
14321
  });
12768
- const get7 = new Command("get").description(`Get one ${label.slice(0, -1)}`).argument("<id>").action(async function(id) {
14322
+ const get9 = new Command("get").description(`Get one ${label.slice(0, -1)}`).argument("<id>").action(async function(id) {
12769
14323
  const r = await getConfigResource(requireCredentials(), family, id);
12770
14324
  if (!r.ok || !r.data)
12771
14325
  fail(
@@ -12780,18 +14334,18 @@ function readFamily(family, label) {
12780
14334
  () => show(r.data)
12781
14335
  );
12782
14336
  });
12783
- return { list: list12, get: get7 };
14337
+ return { list: list15, get: get9 };
12784
14338
  }
12785
14339
  var assetReads = readFamily("assets", "assets");
12786
14340
  var upload = new Command("upload").description("Upload an asset").argument("<file>").action(async function(file) {
12787
- const absolute = import_node_path19.default.resolve(process.cwd(), file);
12788
- if (!import_node_fs21.default.existsSync(absolute))
14341
+ const absolute = import_node_path20.default.resolve(process.cwd(), file);
14342
+ if (!import_node_fs22.default.existsSync(absolute))
12789
14343
  fail(this, `File not found: ${file}`, 3);
12790
- const fileStat = import_node_fs21.default.statSync(absolute);
14344
+ const fileStat = import_node_fs22.default.statSync(absolute);
12791
14345
  if (!fileStat.isFile()) fail(this, `File not found: ${file}`, 3);
12792
14346
  if (fileStat.size > MAX_ASSET_SIZE_BYTES)
12793
14347
  fail(this, `${file} is larger than 10 MB`, 3);
12794
- const mimeType = ASSET_MIME_TYPES[import_node_path19.default.extname(absolute).toLowerCase()];
14348
+ const mimeType = ASSET_MIME_TYPES[import_node_path20.default.extname(absolute).toLowerCase()];
12795
14349
  if (!mimeType)
12796
14350
  fail(
12797
14351
  this,
@@ -12800,8 +14354,8 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
12800
14354
  );
12801
14355
  const r = await uploadAssets(requireCredentials(), [
12802
14356
  {
12803
- name: import_node_path19.default.basename(absolute),
12804
- bytes: import_node_fs21.default.readFileSync(absolute),
14357
+ name: import_node_path20.default.basename(absolute),
14358
+ bytes: import_node_fs22.default.readFileSync(absolute),
12805
14359
  type: mimeType
12806
14360
  }
12807
14361
  ]);
@@ -12814,7 +14368,7 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
12814
14368
  );
12815
14369
  output(this, r.data, () => r.data.assets.forEach(show));
12816
14370
  });
12817
- var remove3 = new Command("delete").alias("rm").description("Delete an asset").argument("<id>").option("-f, --force", "Skip confirmation").action(async function(id, opts) {
14371
+ var remove5 = new Command("delete").alias("rm").description("Delete an asset").argument("<id>").option("-f, --force", "Skip confirmation").action(async function(id, opts) {
12818
14372
  if (!opts.force) {
12819
14373
  const confirmed = await confirmAssetDelete(id);
12820
14374
  if (!confirmed) {
@@ -12835,7 +14389,7 @@ var remove3 = new Command("delete").alias("rm").description("Delete an asset").a
12835
14389
  );
12836
14390
  output(this, r.data, () => console.log(`Deleted ${id}`));
12837
14391
  });
12838
- var assetsCommand = new Command("assets").description("Manage assets").addCommand(assetReads.list).addCommand(assetReads.get).addCommand(upload).addCommand(remove3);
14392
+ var assetsCommand = new Command("assets").description("Manage assets").addCommand(assetReads.list).addCommand(assetReads.get).addCommand(upload).addCommand(remove5);
12839
14393
  var landingReads = readFamily("landing-pages", "landing pages");
12840
14394
  var landingPagesCommand = new Command("landing-pages").description("Read landing pages").addCommand(landingReads.list).addCommand(landingReads.get);
12841
14395
  var snippet = new Command("snippet").description("Generate an external purchase pixel snippet").option("--provider <name>").option("--handover-param <name>").option("--global-name <name>").action(async function(opts) {
@@ -12863,12 +14417,14 @@ function addJsonOption(command) {
12863
14417
  }
12864
14418
  var program2 = new Command().name("apex").description(
12865
14419
  source_default.bold("Apex CLI") + " \u2014 A/B testing experiments from the command line"
12866
- ).version(CLI_VERSION).showSuggestionAfterError(false).showHelpAfterError(false);
14420
+ ).version(CLI_VERSION).showSuggestionAfterError(true).showHelpAfterError(true);
12867
14421
  program2.addCommand(loginCommand);
12868
14422
  program2.addCommand(logoutCommand);
12869
14423
  program2.addCommand(whoamiCommand);
12870
14424
  program2.addCommand(accountCommand);
12871
14425
  program2.addCommand(keysCommand);
14426
+ program2.addCommand(prospectCommand);
14427
+ program2.addCommand(webhooksCommand);
12872
14428
  program2.addCommand(experimentsCommand);
12873
14429
  program2.addCommand(pushCommand);
12874
14430
  program2.addCommand(previewCommand);
@@ -12877,7 +14433,11 @@ program2.addCommand(harnessCommand);
12877
14433
  program2.addCommand(operatorCommand);
12878
14434
  program2.addCommand(trackingCommand);
12879
14435
  program2.addCommand(goalsCommand);
14436
+ program2.addCommand(funnelsCommand);
12880
14437
  program2.addCommand(environmentsCommand);
14438
+ program2.addCommand(consentCommand);
14439
+ program2.addCommand(domainsCommand);
14440
+ program2.addCommand(handoverCommand);
12881
14441
  program2.addCommand(analyticsCommand);
12882
14442
  program2.addCommand(flagsCommand);
12883
14443
  program2.addCommand(backlogCommand);