@drip-apex/apex-cli 0.13.0 → 0.14.1

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 path24 = require("node:path");
1163
- var fs28 = require("node:fs");
1162
+ var path25 = require("node:path");
1163
+ var fs29 = 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 (fs28.existsSync(executableFile)) return;
2144
+ if (fs29.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 = path24.resolve(baseDir, baseName);
2163
- if (fs28.existsSync(localBin)) return localBin;
2164
- if (sourceExt.includes(path24.extname(baseName))) return void 0;
2162
+ const localBin = path25.resolve(baseDir, baseName);
2163
+ if (fs29.existsSync(localBin)) return localBin;
2164
+ if (sourceExt.includes(path25.extname(baseName))) return void 0;
2165
2165
  const foundExt = sourceExt.find(
2166
- (ext) => fs28.existsSync(`${localBin}${ext}`)
2166
+ (ext) => fs29.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 = fs28.realpathSync(this._scriptPath);
2178
+ resolvedScriptPath = fs29.realpathSync(this._scriptPath);
2179
2179
  } catch {
2180
2180
  resolvedScriptPath = this._scriptPath;
2181
2181
  }
2182
- executableDir = path24.resolve(
2183
- path24.dirname(resolvedScriptPath),
2182
+ executableDir = path25.resolve(
2183
+ path25.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 = path24.basename(
2190
+ const legacyName = path25.basename(
2191
2191
  this._scriptPath,
2192
- path24.extname(this._scriptPath)
2192
+ path25.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(path24.extname(executableFile));
2203
+ launchWithNode = sourceExt.includes(path25.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 = path24.basename(filename, path24.extname(filename));
3050
+ this._name = path25.basename(filename, path25.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(path25) {
3065
- if (path25 === void 0) return this._executableDir;
3066
- this._executableDir = path25;
3064
+ executableDir(path26) {
3065
+ if (path26 === void 0) return this._executableDir;
3066
+ this._executableDir = path26;
3067
3067
  return this;
3068
3068
  }
3069
3069
  /**
@@ -3080,9 +3080,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3080
3080
  helpWidth: context.helpWidth,
3081
3081
  outputHasColors: context.hasColors
3082
3082
  });
3083
- const text = helper.formatHelp(this, helper);
3084
- if (context.hasColors) return text;
3085
- return this._outputConfiguration.stripColor(text);
3083
+ const text2 = helper.formatHelp(this, helper);
3084
+ if (context.hasColors) return text2;
3085
+ return this._outputConfiguration.stripColor(text2);
3086
3086
  }
3087
3087
  /**
3088
3088
  * @typedef HelpContext
@@ -3236,7 +3236,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3236
3236
  * @param {(string | Function)} text - string to add, or a function returning a string
3237
3237
  * @return {Command} `this` command for chaining
3238
3238
  */
3239
- addHelpText(position, text) {
3239
+ addHelpText(position, text2) {
3240
3240
  const allowedValues = ["beforeAll", "before", "after", "afterAll"];
3241
3241
  if (!allowedValues.includes(position)) {
3242
3242
  throw new Error(`Unexpected value for position to addHelpText.
@@ -3245,10 +3245,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
3245
3245
  const helpEvent = `${position}Help`;
3246
3246
  this.on(helpEvent, (context) => {
3247
3247
  let helpStr;
3248
- if (typeof text === "function") {
3249
- helpStr = text({ error: context.error, command: context.command });
3248
+ if (typeof text2 === "function") {
3249
+ helpStr = text2({ error: context.error, command: context.command });
3250
3250
  } else {
3251
- helpStr = text;
3251
+ helpStr = text2;
3252
3252
  }
3253
3253
  if (helpStr) {
3254
3254
  context.write(`${helpStr}
@@ -3583,7 +3583,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
3583
3583
  return min;
3584
3584
  }
3585
3585
  if (import_node_process.default.platform === "win32") {
3586
- const osRelease = import_node_os.default.release().split(".");
3586
+ const osRelease = import_node_os3.default.release().split(".");
3587
3587
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
3588
3588
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
3589
3589
  }
@@ -3642,11 +3642,11 @@ function createSupportsColor(stream, options = {}) {
3642
3642
  });
3643
3643
  return translateLevel(level);
3644
3644
  }
3645
- var import_node_process, import_node_os, import_node_tty, env, flagForceColor, supportsColor, supports_color_default;
3645
+ var import_node_process, import_node_os3, import_node_tty, env, flagForceColor, supportsColor, supports_color_default;
3646
3646
  var init_supports_color = __esm({
3647
3647
  "node_modules/chalk/source/vendor/supports-color/index.js"() {
3648
3648
  import_node_process = __toESM(require("node:process"), 1);
3649
- import_node_os = __toESM(require("node:os"), 1);
3649
+ import_node_os3 = __toESM(require("node:os"), 1);
3650
3650
  import_node_tty = __toESM(require("node:tty"), 1);
3651
3651
  ({ env } = import_node_process.default);
3652
3652
  if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
@@ -4073,12 +4073,12 @@ var init_mjs = __esm({
4073
4073
  for (const sig of signals) {
4074
4074
  this.#sigListeners[sig] = () => {
4075
4075
  const listeners = this.#process.listeners(sig);
4076
- let { count } = this.#emitter;
4076
+ let { count: count2 } = this.#emitter;
4077
4077
  const p = process16;
4078
4078
  if (typeof p.__signal_exit_emitter__ === "object" && typeof p.__signal_exit_emitter__.count === "number") {
4079
- count += p.__signal_exit_emitter__.count;
4079
+ count2 += p.__signal_exit_emitter__.count;
4080
4080
  }
4081
- if (listeners.length === count) {
4081
+ if (listeners.length === count2) {
4082
4082
  this.unload();
4083
4083
  const ret = this.#emitter.emit("exit", null, sig);
4084
4084
  const s = sig === "SIGHUP" ? this.#hupSig : sig;
@@ -6405,9 +6405,9 @@ var init_ora = __esm({
6405
6405
  this.#linesToClear = this.#lineCount;
6406
6406
  return this;
6407
6407
  }
6408
- start(text) {
6409
- if (text) {
6410
- this.text = text;
6408
+ start(text2) {
6409
+ if (text2) {
6410
+ this.text = text2;
6411
6411
  }
6412
6412
  if (this.#isSilent) {
6413
6413
  return this;
@@ -6450,17 +6450,17 @@ var init_ora = __esm({
6450
6450
  }
6451
6451
  return this;
6452
6452
  }
6453
- succeed(text) {
6454
- return this.stopAndPersist({ symbol: log_symbols_default.success, text });
6453
+ succeed(text2) {
6454
+ return this.stopAndPersist({ symbol: log_symbols_default.success, text: text2 });
6455
6455
  }
6456
- fail(text) {
6457
- return this.stopAndPersist({ symbol: log_symbols_default.error, text });
6456
+ fail(text2) {
6457
+ return this.stopAndPersist({ symbol: log_symbols_default.error, text: text2 });
6458
6458
  }
6459
- warn(text) {
6460
- return this.stopAndPersist({ symbol: log_symbols_default.warning, text });
6459
+ warn(text2) {
6460
+ return this.stopAndPersist({ symbol: log_symbols_default.warning, text: text2 });
6461
6461
  }
6462
- info(text) {
6463
- return this.stopAndPersist({ symbol: log_symbols_default.info, text });
6462
+ info(text2) {
6463
+ return this.stopAndPersist({ symbol: log_symbols_default.info, text: text2 });
6464
6464
  }
6465
6465
  stopAndPersist(options = {}) {
6466
6466
  if (this.#isSilent) {
@@ -6469,9 +6469,9 @@ var init_ora = __esm({
6469
6469
  const prefixText = options.prefixText ?? this.#prefixText;
6470
6470
  const fullPrefixText = this.#getFullPrefixText(prefixText, " ");
6471
6471
  const symbolText = options.symbol ?? " ";
6472
- const text = options.text ?? this.text;
6472
+ const text2 = options.text ?? this.text;
6473
6473
  const separatorText = symbolText ? " " : "";
6474
- const fullText = typeof text === "string" ? separatorText + text : "";
6474
+ const fullText = typeof text2 === "string" ? separatorText + text2 : "";
6475
6475
  const suffixText = options.suffixText ?? this.#suffixText;
6476
6476
  const fullSuffixText = this.#getFullSuffixText(suffixText, " ");
6477
6477
  const textToWrite = fullPrefixText + symbolText + fullText + fullSuffixText + "\n";
@@ -6486,7 +6486,7 @@ var init_ora = __esm({
6486
6486
  // node_modules/is-docker/index.js
6487
6487
  function hasDockerEnv() {
6488
6488
  try {
6489
- import_node_fs3.default.statSync("/.dockerenv");
6489
+ import_node_fs4.default.statSync("/.dockerenv");
6490
6490
  return true;
6491
6491
  } catch {
6492
6492
  return false;
@@ -6494,7 +6494,7 @@ function hasDockerEnv() {
6494
6494
  }
6495
6495
  function hasDockerCGroup() {
6496
6496
  try {
6497
- return import_node_fs3.default.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
6497
+ return import_node_fs4.default.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
6498
6498
  } catch {
6499
6499
  return false;
6500
6500
  }
@@ -6505,10 +6505,10 @@ function isDocker() {
6505
6505
  }
6506
6506
  return isDockerCached;
6507
6507
  }
6508
- var import_node_fs3, isDockerCached;
6508
+ var import_node_fs4, isDockerCached;
6509
6509
  var init_is_docker = __esm({
6510
6510
  "node_modules/is-docker/index.js"() {
6511
- import_node_fs3 = __toESM(require("node:fs"), 1);
6511
+ import_node_fs4 = __toESM(require("node:fs"), 1);
6512
6512
  }
6513
6513
  });
6514
6514
 
@@ -6519,14 +6519,14 @@ function isInsideContainer() {
6519
6519
  }
6520
6520
  return cachedResult;
6521
6521
  }
6522
- var import_node_fs4, cachedResult, hasContainerEnv;
6522
+ var import_node_fs5, cachedResult, hasContainerEnv;
6523
6523
  var init_is_inside_container = __esm({
6524
6524
  "node_modules/is-inside-container/index.js"() {
6525
- import_node_fs4 = __toESM(require("node:fs"), 1);
6525
+ import_node_fs5 = __toESM(require("node:fs"), 1);
6526
6526
  init_is_docker();
6527
6527
  hasContainerEnv = () => {
6528
6528
  try {
6529
- import_node_fs4.default.statSync("/run/.containerenv");
6529
+ import_node_fs5.default.statSync("/run/.containerenv");
6530
6530
  return true;
6531
6531
  } catch {
6532
6532
  return false;
@@ -6536,25 +6536,25 @@ var init_is_inside_container = __esm({
6536
6536
  });
6537
6537
 
6538
6538
  // node_modules/is-wsl/index.js
6539
- var import_node_process8, import_node_os4, import_node_fs5, isWsl, is_wsl_default;
6539
+ var import_node_process8, import_node_os5, import_node_fs6, isWsl, is_wsl_default;
6540
6540
  var init_is_wsl = __esm({
6541
6541
  "node_modules/is-wsl/index.js"() {
6542
6542
  import_node_process8 = __toESM(require("node:process"), 1);
6543
- import_node_os4 = __toESM(require("node:os"), 1);
6544
- import_node_fs5 = __toESM(require("node:fs"), 1);
6543
+ import_node_os5 = __toESM(require("node:os"), 1);
6544
+ import_node_fs6 = __toESM(require("node:fs"), 1);
6545
6545
  init_is_inside_container();
6546
6546
  isWsl = () => {
6547
6547
  if (import_node_process8.default.platform !== "linux") {
6548
6548
  return false;
6549
6549
  }
6550
- if (import_node_os4.default.release().toLowerCase().includes("microsoft")) {
6550
+ if (import_node_os5.default.release().toLowerCase().includes("microsoft")) {
6551
6551
  if (isInsideContainer()) {
6552
6552
  return false;
6553
6553
  }
6554
6554
  return true;
6555
6555
  }
6556
6556
  try {
6557
- return import_node_fs5.default.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
6557
+ return import_node_fs6.default.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
6558
6558
  } catch {
6559
6559
  return false;
6560
6560
  }
@@ -6564,11 +6564,11 @@ var init_is_wsl = __esm({
6564
6564
  });
6565
6565
 
6566
6566
  // node_modules/wsl-utils/index.js
6567
- var import_node_process9, import_promises, wslDrivesMountPoint, powerShellPathFromWsl, powerShellPath;
6567
+ var import_node_process9, import_promises2, wslDrivesMountPoint, powerShellPathFromWsl, powerShellPath;
6568
6568
  var init_wsl_utils = __esm({
6569
6569
  "node_modules/wsl-utils/index.js"() {
6570
6570
  import_node_process9 = __toESM(require("node:process"), 1);
6571
- import_promises = __toESM(require("node:fs/promises"), 1);
6571
+ import_promises2 = __toESM(require("node:fs/promises"), 1);
6572
6572
  init_is_wsl();
6573
6573
  init_is_wsl();
6574
6574
  wslDrivesMountPoint = /* @__PURE__ */ (() => {
@@ -6581,14 +6581,14 @@ var init_wsl_utils = __esm({
6581
6581
  const configFilePath = "/etc/wsl.conf";
6582
6582
  let isConfigFileExists = false;
6583
6583
  try {
6584
- await import_promises.default.access(configFilePath, import_promises.constants.F_OK);
6584
+ await import_promises2.default.access(configFilePath, import_promises2.constants.F_OK);
6585
6585
  isConfigFileExists = true;
6586
6586
  } catch {
6587
6587
  }
6588
6588
  if (!isConfigFileExists) {
6589
6589
  return defaultMountPoint;
6590
6590
  }
6591
- const configContent = await import_promises.default.readFile(configFilePath, { encoding: "utf8" });
6591
+ const configContent = await import_promises2.default.readFile(configFilePath, { encoding: "utf8" });
6592
6592
  const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
6593
6593
  if (!configMountPoint) {
6594
6594
  return defaultMountPoint;
@@ -6823,24 +6823,24 @@ function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
6823
6823
  }
6824
6824
  return detectArchBinary(platformBinary);
6825
6825
  }
6826
- var import_node_process13, import_node_buffer, import_node_path3, import_node_url, import_node_util5, import_node_child_process5, import_promises2, import_meta, execFile5, __dirname2, localXdgOpenPath, platform, arch, pTryEach, baseOpen, open, openApp, apps, open_default;
6826
+ var import_node_process13, import_node_buffer, import_node_path4, import_node_url, import_node_util5, import_node_child_process5, import_promises3, import_meta, execFile5, __dirname2, localXdgOpenPath, platform, arch, pTryEach, baseOpen, open, openApp, apps, open_default;
6827
6827
  var init_open = __esm({
6828
6828
  "node_modules/open/index.js"() {
6829
6829
  import_node_process13 = __toESM(require("node:process"), 1);
6830
6830
  import_node_buffer = require("node:buffer");
6831
- import_node_path3 = __toESM(require("node:path"), 1);
6831
+ import_node_path4 = __toESM(require("node:path"), 1);
6832
6832
  import_node_url = require("node:url");
6833
6833
  import_node_util5 = require("node:util");
6834
6834
  import_node_child_process5 = __toESM(require("node:child_process"), 1);
6835
- import_promises2 = __toESM(require("node:fs/promises"), 1);
6835
+ import_promises3 = __toESM(require("node:fs/promises"), 1);
6836
6836
  init_wsl_utils();
6837
6837
  init_define_lazy_prop();
6838
6838
  init_default_browser();
6839
6839
  init_is_inside_container();
6840
6840
  import_meta = {};
6841
6841
  execFile5 = (0, import_node_util5.promisify)(import_node_child_process5.default.execFile);
6842
- __dirname2 = import_node_path3.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
6843
- localXdgOpenPath = import_node_path3.default.join(__dirname2, "xdg-open");
6842
+ __dirname2 = import_node_path4.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
6843
+ localXdgOpenPath = import_node_path4.default.join(__dirname2, "xdg-open");
6844
6844
  ({ platform, arch } = import_node_process13.default);
6845
6845
  pTryEach = async (array, mapper) => {
6846
6846
  let latestError;
@@ -6965,7 +6965,7 @@ var init_open = __esm({
6965
6965
  const isBundled = !__dirname2 || __dirname2 === "/";
6966
6966
  let exeLocalXdgOpen = false;
6967
6967
  try {
6968
- await import_promises2.default.access(localXdgOpenPath, import_promises2.constants.X_OK);
6968
+ await import_promises3.default.access(localXdgOpenPath, import_promises3.constants.X_OK);
6969
6969
  exeLocalXdgOpen = true;
6970
6970
  } catch {
6971
6971
  }
@@ -7085,34 +7085,247 @@ var {
7085
7085
  Help
7086
7086
  } = import_index.default;
7087
7087
 
7088
- // src/test-harness.ts
7089
- var readSetting;
7090
- function cliTestSetting(name) {
7091
- return readSetting?.(name);
7088
+ // ../api-client/src/client-identity.ts
7089
+ var import_node_crypto = require("node:crypto");
7090
+ var invocationId = (0, import_node_crypto.randomUUID)();
7091
+ var identity;
7092
+ function setClientIdentity(value) {
7093
+ identity = { ...value, invocationId };
7092
7094
  }
7093
- function cliTestHarnessActive() {
7094
- return readSetting !== void 0;
7095
+ function getClientIdentity() {
7096
+ return identity ? { ...identity } : void 0;
7097
+ }
7098
+ function clientIdentityHeaders() {
7099
+ if (!identity) return {};
7100
+ return {
7101
+ "User-Agent": `${identity.client}/${identity.version} (${process.platform}; node/${process.versions.node.split(".")[0]})`,
7102
+ "X-Apex-Invocation-Id": invocationId,
7103
+ ...identity.command ? { "X-Apex-Client-Command": identity.command } : {}
7104
+ };
7095
7105
  }
7096
7106
 
7097
- // src/cli.ts
7098
- init_source();
7107
+ // ../api-client/src/http.ts
7108
+ var APEX_API_VERSION_HEADER = "x-apex-api-version";
7109
+ var SUPPORTED_APEX_API_VERSION = "1";
7110
+ function extractErrorMessage(json, status3) {
7111
+ if (!json) return `HTTP ${status3}`;
7112
+ const error = json.error;
7113
+ if (typeof error === "string" && error.trim()) return error;
7114
+ if (error && typeof error === "object") {
7115
+ const message = error.message;
7116
+ if (typeof message === "string" && message.trim()) return message;
7117
+ }
7118
+ return typeof json.message === "string" && json.message.trim() ? json.message : `HTTP ${status3}`;
7119
+ }
7120
+ function extractErrorDetails(json) {
7121
+ if (!json) return void 0;
7122
+ const nested = json.error && typeof json.error === "object" && !Array.isArray(json.error) ? json.error : null;
7123
+ const code = typeof json.code === "string" ? json.code : typeof nested?.code === "string" ? nested.code : void 0;
7124
+ for (const value of [json.details, json.meta, nested?.details]) if (value && typeof value === "object" && !Array.isArray(value)) return { ...value, ...code ? { code } : {} };
7125
+ const details = Object.fromEntries(["limit", "current", "max", "actual", "field", "code"].filter((key) => key in json).map((key) => [key, json[key]]));
7126
+ if (code) details.code = code;
7127
+ return Object.keys(details).length ? details : void 0;
7128
+ }
7129
+ var warnedAboutVersionSkew = false;
7130
+ function createHttpClient(creds, options) {
7131
+ const warn3 = options.warn ?? ((message) => process.stderr.write(message));
7132
+ function inspectVersion(headers) {
7133
+ const value = headers.get(APEX_API_VERSION_HEADER);
7134
+ if (!value || warnedAboutVersionSkew) return;
7135
+ const version = Number(value);
7136
+ if (!Number.isFinite(version) || version === Number(SUPPORTED_APEX_API_VERSION)) return;
7137
+ warnedAboutVersionSkew = true;
7138
+ warn3(version > 1 ? "CLI is behind the server \u2014 update.\n" : "Server API is older than this CLI supports.\n");
7139
+ }
7140
+ async function request2(method, path25, body5, requestTimeoutMs, form = false) {
7141
+ const started2 = performance.now();
7142
+ const observe = (status3, requestId2, errorCode) => {
7143
+ try {
7144
+ options.onResponse?.({ method, path: path25, status: status3, requestId: requestId2, errorCode: typeof errorCode === "string" ? errorCode : void 0, durationMs: Math.max(0, Math.round(performance.now() - started2)) });
7145
+ } catch {
7146
+ }
7147
+ };
7148
+ const fallback2 = form ? 12e4 : 3e4;
7149
+ const configured = Number(process.env.APEX_TIMEOUT_MS ?? fallback2);
7150
+ const defaultMs = Number.isFinite(configured) && configured > 0 ? configured : fallback2;
7151
+ const timeoutMs = requestTimeoutMs === void 0 ? defaultMs : Math.max(1, Math.min(defaultMs, requestTimeoutMs));
7152
+ const headers = { ...clientIdentityHeaders(), Authorization: `Bearer ${creds.apiKey}` };
7153
+ if (!form) headers["Content-Type"] = "application/json";
7154
+ if (creds.credentialKind === "user" || creds.apiKey.startsWith("apx_uk_")) {
7155
+ if (creds.resolvedContext?.orgId) headers["X-Apex-Org"] = creds.resolvedContext.orgId;
7156
+ if (creds.resolvedContext?.shopId) headers["X-Apex-Shop"] = creds.resolvedContext.shopId;
7157
+ }
7158
+ const mock = options.transport?.(method, path25, body5, headers);
7159
+ if (mock !== void 0) {
7160
+ const version = options.testTransportVersion?.();
7161
+ if (version) inspectVersion(new Headers({ [APEX_API_VERSION_HEADER]: version }));
7162
+ observe(mock.status, mock.requestId, mock.errorDetails?.code);
7163
+ return mock;
7164
+ }
7165
+ let response;
7166
+ try {
7167
+ response = await fetch(`${creds.baseUrl}${path25}`, { method, headers, body: form ? body5 : body5 ? JSON.stringify(body5) : void 0, signal: AbortSignal.timeout(timeoutMs) });
7168
+ } catch (error) {
7169
+ const outer = error && typeof error === "object" ? error : void 0;
7170
+ const nested = outer?.cause && typeof outer.cause === "object" ? outer.cause : void 0;
7171
+ return options.onNetworkError({
7172
+ baseUrl: creds.baseUrl,
7173
+ timeoutMs,
7174
+ cause: error instanceof Error ? error.message : String(error),
7175
+ code: typeof nested?.code === "string" ? nested.code : typeof outer?.code === "string" ? outer.code : void 0,
7176
+ name: typeof outer?.name === "string" ? outer.name : void 0,
7177
+ causeMessage: typeof nested?.message === "string" ? nested.message : void 0
7178
+ });
7179
+ }
7180
+ inspectVersion(response.headers);
7181
+ const json = await response.json().catch(() => null);
7182
+ const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-vercel-id") ?? response.headers.get("cf-ray");
7183
+ observe(response.status, requestId, extractErrorDetails(json)?.code);
7184
+ if (!response.ok) return { ok: false, status: response.status, data: json, error: extractErrorMessage(json, response.status), errorDetails: extractErrorDetails(json), requestId, retryAfter: response.headers.get("retry-after"), rateLimit: { limit: response.headers.get("x-ratelimit-limit"), remaining: response.headers.get("x-ratelimit-remaining"), reset: response.headers.get("x-ratelimit-reset") } };
7185
+ return { ok: true, status: response.status, data: json };
7186
+ }
7187
+ return { request: (method, path25, body5, timeoutMs) => request2(method, path25, body5, timeoutMs), requestForm: (method, path25, body5) => request2(method, path25, body5, void 0, true) };
7188
+ }
7099
7189
 
7100
- // src/commands/login.ts
7101
- init_source();
7102
- var import_node_crypto2 = __toESM(require("node:crypto"));
7103
- var import_node_http = __toESM(require("node:http"));
7104
- var import_node_os5 = __toESM(require("node:os"));
7105
- var import_node_readline2 = __toESM(require("node:readline"));
7190
+ // ../api-client/src/credentials.ts
7191
+ var import_node_fs = __toESM(require("node:fs"), 1);
7192
+ var import_node_os = __toESM(require("node:os"), 1);
7193
+ var import_node_path = __toESM(require("node:path"), 1);
7194
+ function readStoredCredentials(file = import_node_path.default.join(import_node_os.default.homedir(), ".drip", "credentials.json")) {
7195
+ try {
7196
+ return JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
7197
+ } catch {
7198
+ return null;
7199
+ }
7200
+ }
7201
+ function resolveCredentials(env2 = process.env, stored = readStoredCredentials()) {
7202
+ const context = {
7203
+ orgId: env2.DRIP_ORG || stored?.context?.orgId,
7204
+ shopId: env2.DRIP_SHOP || stored?.context?.shopId
7205
+ };
7206
+ const environmentApiKey = env2.APEX_API_KEY || env2.DRIP_API_KEY;
7207
+ const environmentBaseUrl = env2.APEX_BASE_URL || env2.DRIP_BASE_URL;
7208
+ if (environmentApiKey) {
7209
+ const user = environmentApiKey.startsWith("apx_uk_");
7210
+ return {
7211
+ ...stored ?? { apiKey: "", baseUrl: "https://app.drip-apex.com" },
7212
+ apiKey: environmentApiKey,
7213
+ baseUrl: environmentBaseUrl ?? "https://app.drip-apex.com",
7214
+ credentialKind: user ? "user" : "shop",
7215
+ resolvedContext: context
7216
+ };
7217
+ }
7218
+ if (stored?.userApiKey) return {
7219
+ ...stored,
7220
+ apiKey: stored.userApiKey,
7221
+ baseUrl: stored.userBaseUrl ?? stored.baseUrl,
7222
+ credentialKind: "user",
7223
+ resolvedContext: context
7224
+ };
7225
+ if (stored?.apiKey) return { ...stored, credentialKind: "shop", resolvedContext: context };
7226
+ return null;
7227
+ }
7106
7228
 
7107
7229
  // src/config.ts
7108
- var import_node_fs2 = __toESM(require("node:fs"));
7230
+ var import_node_fs3 = __toESM(require("node:fs"));
7231
+ var import_node_path3 = __toESM(require("node:path"));
7232
+ var import_node_os4 = __toESM(require("node:os"));
7233
+
7234
+ // src/update.ts
7235
+ var import_node_fs2 = require("node:fs");
7236
+ var import_promises = require("node:fs/promises");
7237
+ var import_node_os2 = require("node:os");
7109
7238
  var import_node_path2 = __toESM(require("node:path"));
7110
- var import_node_os3 = __toESM(require("node:os"));
7239
+ var TTL = 24 * 60 * 60 * 1e3;
7240
+ var FAILURE_TTL = 60 * 60 * 1e3;
7241
+ function versionParts(version) {
7242
+ return /^\d+\.\d+\.\d+$/.test(version) ? version.split(".").map(Number) : void 0;
7243
+ }
7244
+ async function checkForUpdate(current) {
7245
+ if (process.env.APEX_NO_UPDATE_CHECK === "1") return;
7246
+ if (process.env[["APEX", "CLI", "MOCK", "API"].join("_")] === "1" || process.env.CI) return;
7247
+ const installed = versionParts(current);
7248
+ if (!installed) return;
7249
+ const controller = new AbortController();
7250
+ let timer;
7251
+ const cachePath = import_node_path2.default.join((0, import_node_os2.homedir)(), ".drip", "update-check.json");
7252
+ const lookup2 = async () => {
7253
+ let latest;
7254
+ const remember = async (value) => {
7255
+ try {
7256
+ await (0, import_promises.mkdir)(import_node_path2.default.dirname(cachePath), { recursive: true });
7257
+ await (0, import_promises.writeFile)(cachePath, JSON.stringify({ latest: value, checked_at: Date.now() }), {
7258
+ mode: 384
7259
+ });
7260
+ } catch {
7261
+ }
7262
+ };
7263
+ try {
7264
+ const cache = JSON.parse(await (0, import_promises.readFile)(cachePath, "utf8"));
7265
+ const age = Date.now() - cache.checked_at;
7266
+ if (cache.latest === null && typeof cache.checked_at === "number" && age >= 0 && age < FAILURE_TTL)
7267
+ return;
7268
+ if (typeof cache.checked_at === "number" && age >= 0 && age < TTL && typeof cache.latest === "string" && versionParts(cache.latest))
7269
+ latest = cache.latest;
7270
+ } catch {
7271
+ }
7272
+ if (!latest && !controller.signal.aborted) {
7273
+ let body5;
7274
+ try {
7275
+ const response = await fetch("https://registry.npmjs.org/@drip-apex%2Fapex-cli/latest", {
7276
+ headers: { Accept: "application/json" },
7277
+ signal: controller.signal
7278
+ });
7279
+ body5 = response.ok ? await response.json() : void 0;
7280
+ } catch {
7281
+ body5 = void 0;
7282
+ }
7283
+ if (typeof body5?.version !== "string" || !versionParts(body5.version)) {
7284
+ await remember(null);
7285
+ return;
7286
+ }
7287
+ latest = body5.version;
7288
+ await remember(latest);
7289
+ }
7290
+ if (!latest) return;
7291
+ const available = versionParts(latest);
7292
+ const difference = available.findIndex((part, index) => part !== installed[index]);
7293
+ if (difference < 0 || available[difference] < installed[difference]) return;
7294
+ return {
7295
+ current,
7296
+ latest,
7297
+ breaking: available[0] !== installed[0] || installed[0] === 0 && available[1] !== installed[1],
7298
+ notes_url: "https://docs.drip-apex.com/docs/cli/commands-output"
7299
+ };
7300
+ };
7301
+ try {
7302
+ return await Promise.race([
7303
+ lookup2(),
7304
+ new Promise((resolve2) => {
7305
+ timer = setTimeout(() => {
7306
+ controller.abort();
7307
+ try {
7308
+ (0, import_node_fs2.mkdirSync)(import_node_path2.default.dirname(cachePath), { recursive: true });
7309
+ (0, import_node_fs2.writeFileSync)(cachePath, JSON.stringify({ latest: null, checked_at: Date.now() }), {
7310
+ mode: 384
7311
+ });
7312
+ } catch {
7313
+ }
7314
+ resolve2(void 0);
7315
+ }, 900);
7316
+ })
7317
+ ]);
7318
+ } catch {
7319
+ return;
7320
+ } finally {
7321
+ if (timer) clearTimeout(timer);
7322
+ }
7323
+ }
7111
7324
 
7112
7325
  // package.json
7113
7326
  var package_default = {
7114
7327
  name: "@drip-apex/apex-cli",
7115
- version: "0.13.0",
7328
+ version: "0.14.1",
7116
7329
  license: "MIT",
7117
7330
  description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
7118
7331
  private: false,
@@ -7180,18 +7393,20 @@ function recordApiResponse(response) {
7180
7393
  status: response.status,
7181
7394
  body: response.data ?? null,
7182
7395
  message: response.error,
7183
- details: response.errorDetails
7396
+ details: response.errorDetails,
7397
+ requestId: response.requestId,
7398
+ retryAfter: response.retryAfter
7184
7399
  });
7185
7400
  if (recentApiFailures.length > MAX_RECORDED_API_FAILURES) recentApiFailures.shift();
7186
7401
  }
7187
7402
  function matchApiFailure(message, details) {
7188
7403
  const byDetails = details !== void 0 ? recentApiFailures.find((entry) => entry.details === details) : void 0;
7189
- if (byDetails) return { status: byDetails.status, body: byDetails.body };
7404
+ if (byDetails) return byDetails;
7190
7405
  const byMessage = recentApiFailures.filter(
7191
7406
  (entry) => Boolean(entry.message) && message.includes(entry.message)
7192
7407
  );
7193
7408
  if (byMessage.length !== 1) return void 0;
7194
- return { status: byMessage[0].status, body: byMessage[0].body };
7409
+ return byMessage[0];
7195
7410
  }
7196
7411
  var CliError = class extends Error {
7197
7412
  exitCode;
@@ -7206,7 +7421,12 @@ var CliError = class extends Error {
7206
7421
  }
7207
7422
  };
7208
7423
  function apiFailureOf(response) {
7209
- return response.ok ? void 0 : { status: response.status, body: response.data ?? null };
7424
+ return response.ok ? void 0 : {
7425
+ status: response.status,
7426
+ body: response.data ?? null,
7427
+ requestId: response.requestId,
7428
+ retryAfter: response.retryAfter
7429
+ };
7210
7430
  }
7211
7431
  var initialEnvironmentApiKey = process.env.APEX_API_KEY || process.env.DRIP_API_KEY;
7212
7432
  var envelopeContext = {
@@ -7253,7 +7473,7 @@ function installJsonStdoutGuard() {
7253
7473
  if (rawStdoutWrite) return;
7254
7474
  const stdout = process.stdout;
7255
7475
  const original = stdout.write.bind(stdout);
7256
- rawStdoutWrite = (text) => original(text);
7476
+ rawStdoutWrite = (text2) => original(text2);
7257
7477
  stdout.write = ((chunk, ...args) => process.stderr.write(
7258
7478
  chunk,
7259
7479
  ...args
@@ -7263,6 +7483,7 @@ function isJsonMode() {
7263
7483
  return jsonMode;
7264
7484
  }
7265
7485
  function configureRuntime(command) {
7486
+ setClientIdentity({ client: "apex-cli", version: CLI_VERSION, command: commandPath(command) });
7266
7487
  commandName = commandPath(command) || commandName;
7267
7488
  jsonMode = shouldUseJson(command);
7268
7489
  if (!jsonMode) return;
@@ -7274,10 +7495,10 @@ function configureRuntime(command) {
7274
7495
  };
7275
7496
  }
7276
7497
  function writeJsonDocument(envelope) {
7277
- const text = `${JSON.stringify(envelope, null, 2)}
7498
+ const text2 = `${JSON.stringify({ ...envelope, ...updateNotice ? { update: updateNotice } : {} }, null, 2)}
7278
7499
  `;
7279
7500
  if (wroteEnvelope) {
7280
- process.stderr.write(text);
7501
+ process.stderr.write(text2);
7281
7502
  return;
7282
7503
  }
7283
7504
  wroteEnvelope = true;
@@ -7286,8 +7507,8 @@ function writeJsonDocument(envelope) {
7286
7507
  restoreConsoleLog();
7287
7508
  if (pendingHumanLines.length > 0) process.stderr.write(`${pendingHumanLines.join("\n")}
7288
7509
  `);
7289
- if (rawStdoutWrite) rawStdoutWrite(text);
7290
- else process.stdout.write(text);
7510
+ if (rawStdoutWrite) rawStdoutWrite(text2);
7511
+ else process.stdout.write(text2);
7291
7512
  }
7292
7513
  function flushFallbackEnvelope() {
7293
7514
  if (!jsonMode || wroteEnvelope) return;
@@ -7315,6 +7536,11 @@ function writeEnvelope(data, command = commandName) {
7315
7536
  };
7316
7537
  writeJsonDocument(envelope);
7317
7538
  }
7539
+ function writeRawCommandOutput(text2) {
7540
+ wroteEnvelope = true;
7541
+ if (rawStdoutWrite) return rawStdoutWrite(text2);
7542
+ return process.stdout.write(text2);
7543
+ }
7318
7544
  function writeErrorEnvelope(error, command = commandName) {
7319
7545
  const envelope = {
7320
7546
  ok: false,
@@ -7323,8 +7549,11 @@ function writeErrorEnvelope(error, command = commandName) {
7323
7549
  error: {
7324
7550
  message: error.message,
7325
7551
  code: error.exitCode,
7552
+ reason: errorReason(error),
7553
+ next_steps: errorNextSteps(error),
7554
+ ...error.api?.requestId ? { request_id: error.api.requestId } : {},
7326
7555
  ...error.details ? { details: error.details } : {},
7327
- ...error.api ? { api: error.api } : {}
7556
+ ...error.api ? { api: { status: error.api.status, body: error.api.body } } : {}
7328
7557
  },
7329
7558
  context: envelopeContext
7330
7559
  };
@@ -7350,18 +7579,18 @@ function commandPath(command) {
7350
7579
  function fail(command, message, exitCode = EXIT_CODES.general, details, api) {
7351
7580
  throw new CliError(message, exitCode, details, api);
7352
7581
  }
7353
- function classifyStatus(status2) {
7354
- if (status2 === 401 || status2 === 403) return EXIT_CODES.auth;
7355
- if (status2 === 409) return EXIT_CODES.conflict;
7356
- if (status2 === 400 || status2 === 413 || status2 === 422) return EXIT_CODES.validation;
7582
+ function classifyStatus(status3) {
7583
+ if (status3 === 401 || status3 === 403) return EXIT_CODES.auth;
7584
+ if (status3 === 409) return EXIT_CODES.conflict;
7585
+ if (status3 === 400 || status3 === 413 || status3 === 422) return EXIT_CODES.validation;
7357
7586
  return EXIT_CODES.general;
7358
7587
  }
7359
- function classifyApiError(status2, details) {
7588
+ function classifyApiError(status3, details) {
7360
7589
  const code = details?.code;
7361
7590
  if (code === "shop_context_required" || code === "org_context_required")
7362
7591
  return EXIT_CODES.validation;
7363
7592
  if (code === "shop_access_denied" || code === "org_access_denied") return EXIT_CODES.auth;
7364
- return classifyStatus(status2);
7593
+ return classifyStatus(status3);
7365
7594
  }
7366
7595
  function confirmationRequired(command, message, retry) {
7367
7596
  return fail(command, message, EXIT_CODES.confirmationRequired, { retry });
@@ -7408,33 +7637,25 @@ async function createSpinner(message) {
7408
7637
  }
7409
7638
  return spinner.start();
7410
7639
  }
7640
+ var updateNotice;
7411
7641
  async function maybeCheckForUpdate() {
7412
- if (!process.stderr.isTTY || process.env.APEX_NO_UPDATE_CHECK === "1" || CLI_VERSION === "unknown")
7413
- return;
7414
- try {
7415
- const res = await fetch("https://registry.npmjs.org/@drip-apex%2Fapex-cli/latest", {
7416
- headers: { Accept: "application/json" },
7417
- signal: AbortSignal.timeout(900)
7418
- });
7419
- if (!res.ok) return;
7420
- const latest = await res.json();
7421
- if (latest.version && latest.version !== CLI_VERSION) {
7422
- process.stderr.write(`Apex CLI ${latest.version} is available; current ${CLI_VERSION}.
7642
+ updateNotice = await checkForUpdate(CLI_VERSION);
7643
+ if (updateNotice)
7644
+ process.stderr.write(`Apex CLI ${updateNotice.latest} is available; current ${CLI_VERSION}.
7423
7645
  `);
7424
- }
7425
- } catch {
7426
- }
7427
7646
  }
7428
7647
  async function handleCliError(err, command) {
7429
7648
  if (err && typeof err === "object" && "exitCode" in err && err.exitCode === 0) {
7430
7649
  flushFallbackEnvelope();
7650
+ await finishInvocation({ exitCode: 0, error: err });
7431
7651
  drainAndExit(0);
7432
7652
  return;
7433
7653
  }
7434
7654
  const commanderExitCode = err && typeof err === "object" && "exitCode" in err && typeof err.exitCode === "number" ? err.exitCode : null;
7435
7655
  const cliError = err instanceof CliError ? err : new CliError(
7436
7656
  err instanceof Error ? err.message : String(err),
7437
- commanderExitCode === EXIT_CODES.general ? EXIT_CODES.validation : commanderExitCode ?? EXIT_CODES.general
7657
+ commanderExitCode === EXIT_CODES.general ? EXIT_CODES.validation : commanderExitCode ?? EXIT_CODES.general,
7658
+ err && typeof err === "object" && "code" in err && ["ENOENT", "EACCES", "EPERM", "EISDIR", "ENOTDIR", "ENOSPC"].includes(String(err.code)) ? { code: "local_file_error" } : void 0
7438
7659
  );
7439
7660
  const interceptedCommand = err && typeof err === "object" ? interceptedCommands.get(err) : void 0;
7440
7661
  if (shouldUseJson(command)) {
@@ -7443,26 +7664,94 @@ async function handleCliError(err, command) {
7443
7664
  interceptedCommand ? commandPath(interceptedCommand) : commandName
7444
7665
  );
7445
7666
  } else {
7446
- process.stderr.write(`Error: ${cliError.message}
7667
+ process.stderr.write(
7668
+ `Error: ${cliError.message}${cliError.api?.requestId ? ` (request ${cliError.api.requestId})` : ""}
7669
+ `
7670
+ );
7671
+ for (const step of errorNextSteps(cliError))
7672
+ process.stderr.write(`Next: ${step.command} - ${step.why}
7447
7673
  `);
7448
7674
  if (cliError.details?.retry) {
7449
7675
  process.stderr.write(`Retry: ${String(cliError.details.retry)}
7450
7676
  `);
7451
7677
  }
7452
7678
  }
7679
+ await finishInvocation({ exitCode: cliError.exitCode, error: err });
7453
7680
  drainAndExit(cliError.exitCode);
7454
7681
  }
7455
-
7456
- // ../api-client/src/credentials.ts
7457
- var import_node_fs = __toESM(require("node:fs"), 1);
7458
- var import_node_os2 = __toESM(require("node:os"), 1);
7459
- var import_node_path = __toESM(require("node:path"), 1);
7460
- function readStoredCredentials(file = import_node_path.default.join(import_node_os2.default.homedir(), ".drip", "credentials.json")) {
7461
- try {
7462
- return JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
7463
- } catch {
7464
- return null;
7682
+ function errorReason(error) {
7683
+ const details = error.details;
7684
+ if (!error.api && typeof details?.baseUrl === "string" && typeof details.reason === "string")
7685
+ return `network_${details.reason}`;
7686
+ const body5 = error.api?.body;
7687
+ const code = body5?.code ?? body5?.error?.code ?? (error.api ? details?.code : void 0);
7688
+ if (typeof code === "string" && code) return code;
7689
+ if (["shop_context_required", "local_file_error", "validation_failed"].includes(
7690
+ String(details?.code)
7691
+ ))
7692
+ return String(details?.code);
7693
+ const status3 = error.api?.status;
7694
+ if (status3 === 401) return "auth_required";
7695
+ if (status3 === 403) return "auth_forbidden";
7696
+ if (status3 === 404) return "not_found";
7697
+ if (status3 === 429) return "rate_limited";
7698
+ if (status3 === 409 || error.exitCode === EXIT_CODES.conflict) return "conflict";
7699
+ if (error.exitCode === EXIT_CODES.auth) return "auth_required";
7700
+ if (error.exitCode === EXIT_CODES.confirmationRequired) return "confirmation_required";
7701
+ if (error.exitCode === EXIT_CODES.validation) return "validation_failed";
7702
+ return "internal_error";
7703
+ }
7704
+ function errorNextSteps(error) {
7705
+ const reason2 = errorReason(error);
7706
+ const steps = [];
7707
+ if (reason2 === "auth_required" || error.api?.status === 401) {
7708
+ steps.push({ command: "apex login", why: "Authenticate with a valid API key." });
7709
+ if (!error.api)
7710
+ steps.push({
7711
+ command: "set APEX_API_KEY",
7712
+ why: "Provide credentials for a headless invocation."
7713
+ });
7465
7714
  }
7715
+ if (reason2 === "auth_forbidden" || error.api?.status === 403)
7716
+ steps.push({ command: "apex whoami", why: "Check the API key scope and permissions." });
7717
+ if (reason2 === "shop_context_required")
7718
+ steps.push({
7719
+ command: typeof error.details?.retry === "string" ? error.details.retry : "apex use <org>/<shop>",
7720
+ why: "Select the shop for this command, or pass --shop <id>."
7721
+ });
7722
+ if (reason2.startsWith("network_")) {
7723
+ steps.push(
7724
+ { command: "check APEX_BASE_URL", why: "Verify the API host and network access." },
7725
+ { command: "retry", why: "Retry the command after connectivity is restored." }
7726
+ );
7727
+ if (reason2.includes("timeout"))
7728
+ steps.push({
7729
+ command: "set APEX_TIMEOUT_MS",
7730
+ why: "Increase the request timeout if needed."
7731
+ });
7732
+ }
7733
+ if (reason2 === "rate_limited" || error.api?.status === 429)
7734
+ steps.push({
7735
+ command: "wait",
7736
+ why: error.api?.retryAfter ? `Wait for Retry-After: ${error.api.retryAfter} before retrying.` : "Wait before retrying the request."
7737
+ });
7738
+ if (typeof error.details?.retry === "string" && reason2 !== "shop_context_required")
7739
+ steps.push({
7740
+ command: error.details.retry,
7741
+ why: "Review the requested action before retrying."
7742
+ });
7743
+ const hints = error.details?.next_steps;
7744
+ if (Array.isArray(hints))
7745
+ for (const hint of hints) {
7746
+ if (hint && typeof hint.command === "string" && typeof hint.why === "string")
7747
+ steps.push({ command: hint.command, why: hint.why });
7748
+ }
7749
+ if (reason2 === "validation_failed" && error.details?.plan)
7750
+ steps.push({
7751
+ command: "apex experiments plan",
7752
+ why: "Correct the reported blocking issues, then validate the folder again."
7753
+ });
7754
+ return steps;
7466
7755
  }
7467
7756
 
7468
7757
  // src/config.ts
@@ -7534,8 +7823,8 @@ function configureInvocationContext() {
7534
7823
  shop_id: context.shopId ?? null
7535
7824
  });
7536
7825
  }
7537
- var DRIP_DIR = import_node_path2.default.join(import_node_os3.default.homedir(), ".drip");
7538
- var CREDS_FILE = import_node_path2.default.join(DRIP_DIR, "credentials.json");
7826
+ var DRIP_DIR = import_node_path3.default.join(import_node_os4.default.homedir(), ".drip");
7827
+ var CREDS_FILE = import_node_path3.default.join(DRIP_DIR, "credentials.json");
7539
7828
  function getStoredCredentials() {
7540
7829
  return readStoredCredentials(CREDS_FILE);
7541
7830
  }
@@ -7561,14 +7850,14 @@ function getCredentials() {
7561
7850
  }
7562
7851
  function saveCredentials(creds) {
7563
7852
  const { credentialKind: _kind, resolvedContext: _resolved, ...persisted } = creds;
7564
- import_node_fs2.default.mkdirSync(DRIP_DIR, { recursive: true });
7565
- import_node_fs2.default.writeFileSync(CREDS_FILE, JSON.stringify(persisted, null, 2) + "\n", {
7853
+ import_node_fs3.default.mkdirSync(DRIP_DIR, { recursive: true });
7854
+ import_node_fs3.default.writeFileSync(CREDS_FILE, JSON.stringify(persisted, null, 2) + "\n", {
7566
7855
  mode: 384
7567
7856
  });
7568
7857
  }
7569
7858
  function clearCredentials() {
7570
7859
  try {
7571
- import_node_fs2.default.unlinkSync(CREDS_FILE);
7860
+ import_node_fs3.default.unlinkSync(CREDS_FILE);
7572
7861
  } catch {
7573
7862
  }
7574
7863
  }
@@ -7659,89 +7948,264 @@ var CONFIG_NAMES = [".drip.config.ts", ".drip.config.js", "drip.config.ts", "dri
7659
7948
  function findConfigPath() {
7660
7949
  const cwd = process.cwd();
7661
7950
  for (const name of CONFIG_NAMES) {
7662
- const p = import_node_path2.default.join(cwd, name);
7663
- if (import_node_fs2.default.existsSync(p)) return p;
7951
+ const p = import_node_path3.default.join(cwd, name);
7952
+ if (import_node_fs3.default.existsSync(p)) return p;
7664
7953
  }
7665
7954
  return null;
7666
7955
  }
7667
7956
 
7668
- // src/api.ts
7669
- var import_node_crypto = require("node:crypto");
7670
-
7671
- // ../api-client/src/http.ts
7672
- var APEX_API_VERSION_HEADER = "x-apex-api-version";
7673
- var SUPPORTED_APEX_API_VERSION = "1";
7674
- function extractErrorMessage(json, status2) {
7675
- if (!json) return `HTTP ${status2}`;
7676
- const error = json.error;
7677
- if (typeof error === "string" && error.trim()) return error;
7678
- if (error && typeof error === "object") {
7679
- const message = error.message;
7680
- if (typeof message === "string" && message.trim()) return message;
7681
- }
7682
- return typeof json.message === "string" && json.message.trim() ? json.message : `HTTP ${status2}`;
7957
+ // src/test-harness.ts
7958
+ var readSetting;
7959
+ function cliTestSetting(name) {
7960
+ return readSetting?.(name);
7683
7961
  }
7684
- function extractErrorDetails(json) {
7685
- if (!json) return void 0;
7686
- const nested = json.error && typeof json.error === "object" && !Array.isArray(json.error) ? json.error : null;
7687
- const code = typeof json.code === "string" ? json.code : typeof nested?.code === "string" ? nested.code : void 0;
7688
- for (const value of [json.details, json.meta, nested?.details]) if (value && typeof value === "object" && !Array.isArray(value)) return { ...value, ...code ? { code } : {} };
7689
- const details = Object.fromEntries(["limit", "current", "max", "actual", "field", "code"].filter((key) => key in json).map((key) => [key, json[key]]));
7690
- if (code) details.code = code;
7691
- return Object.keys(details).length ? details : void 0;
7962
+ function cliTestHarnessActive() {
7963
+ return readSetting !== void 0;
7692
7964
  }
7693
- var warnedAboutVersionSkew = false;
7694
- function createHttpClient(creds, options) {
7695
- const warn3 = options.warn ?? ((message) => process.stderr.write(message));
7696
- function inspectVersion(headers) {
7697
- const value = headers.get(APEX_API_VERSION_HEADER);
7698
- if (!value || warnedAboutVersionSkew) return;
7699
- const version = Number(value);
7700
- if (!Number.isFinite(version) || version === Number(SUPPORTED_APEX_API_VERSION)) return;
7701
- warnedAboutVersionSkew = true;
7702
- warn3(version > 1 ? "CLI is behind the server \u2014 update.\n" : "Server API is older than this CLI supports.\n");
7703
- }
7704
- async function request2(method, path24, body5, requestTimeoutMs, form = false) {
7705
- const mock = options.transport?.(method, path24, body5);
7706
- if (mock !== void 0) {
7707
- const version = options.testTransportVersion?.();
7708
- if (version) inspectVersion(new Headers({ [APEX_API_VERSION_HEADER]: version }));
7709
- return mock;
7710
- }
7711
- const fallback2 = form ? 12e4 : 3e4;
7712
- const configured = Number(process.env.APEX_TIMEOUT_MS ?? fallback2);
7713
- const defaultMs = Number.isFinite(configured) && configured > 0 ? configured : fallback2;
7714
- const timeoutMs = requestTimeoutMs === void 0 ? defaultMs : Math.max(1, Math.min(defaultMs, requestTimeoutMs));
7715
- const headers = { Authorization: `Bearer ${creds.apiKey}` };
7716
- if (!form) headers["Content-Type"] = "application/json";
7717
- if (creds.credentialKind === "user" || creds.apiKey.startsWith("apx_uk_")) {
7718
- if (creds.resolvedContext?.orgId) headers["X-Apex-Org"] = creds.resolvedContext.orgId;
7719
- if (creds.resolvedContext?.shopId) headers["X-Apex-Shop"] = creds.resolvedContext.shopId;
7720
- }
7721
- let response;
7722
- try {
7723
- response = await fetch(`${creds.baseUrl}${path24}`, { method, headers, body: form ? body5 : body5 ? JSON.stringify(body5) : void 0, signal: AbortSignal.timeout(timeoutMs) });
7724
- } catch (error) {
7725
- const outer = error && typeof error === "object" ? error : void 0;
7726
- const nested = outer?.cause && typeof outer.cause === "object" ? outer.cause : void 0;
7727
- return options.onNetworkError({
7728
- baseUrl: creds.baseUrl,
7729
- timeoutMs,
7730
- cause: error instanceof Error ? error.message : String(error),
7731
- code: typeof nested?.code === "string" ? nested.code : typeof outer?.code === "string" ? outer.code : void 0,
7732
- name: typeof outer?.name === "string" ? outer.name : void 0,
7733
- causeMessage: typeof nested?.message === "string" ? nested.message : void 0
7734
- });
7965
+
7966
+ // src/telemetry.ts
7967
+ var started = performance.now();
7968
+ var invocation;
7969
+ var firstError;
7970
+ var apiCalls = 0;
7971
+ var finished = false;
7972
+ function beginInvocation(command, context = {}) {
7973
+ const names = /* @__PURE__ */ new Set();
7974
+ for (let current = command; current; current = current.parent) {
7975
+ for (const option of current.options) {
7976
+ const name = option.attributeName();
7977
+ const source = current.getOptionValueSource(name);
7978
+ if (source && source !== "default") names.add(name);
7735
7979
  }
7736
- inspectVersion(response.headers);
7737
- const json = await response.json().catch(() => null);
7738
- if (!response.ok) return { ok: false, status: response.status, data: json, error: extractErrorMessage(json, response.status), errorDetails: extractErrorDetails(json), requestId: response.headers.get("x-request-id"), rateLimit: { limit: response.headers.get("x-ratelimit-limit"), remaining: response.headers.get("x-ratelimit-remaining"), reset: response.headers.get("x-ratelimit-reset") } };
7739
- return { ok: true, status: response.status, data: json };
7740
7980
  }
7741
- return { request: (method, path24, body5, timeoutMs) => request2(method, path24, body5, timeoutMs), requestForm: (method, path24, body5) => request2(method, path24, body5, void 0, true) };
7981
+ invocation = {
7982
+ command: commandPath(command).replace(/^apex\s+/, ""),
7983
+ option_names: [...names].sort(),
7984
+ json_mode: shouldUseJson(command),
7985
+ tty: process.stdout.isTTY === true,
7986
+ credential_kind: process.env.APEX_API_KEY || process.env.DRIP_API_KEY ? "env" : defaultCredentialKind(getCredentials()),
7987
+ org_id: context.orgId,
7988
+ shop_id: context.shopId
7989
+ };
7990
+ }
7991
+ var staticRouteSegments = /* @__PURE__ */ new Set([
7992
+ "account",
7993
+ "activate",
7994
+ "admin",
7995
+ "analytics",
7996
+ "api",
7997
+ "apply",
7998
+ "approve",
7999
+ "approved",
8000
+ "archive",
8001
+ "assets",
8002
+ "audit-logs",
8003
+ "backlog",
8004
+ "brand-context",
8005
+ "build",
8006
+ "bulk",
8007
+ "by-slug",
8008
+ "cli",
8009
+ "complete",
8010
+ "conflicts",
8011
+ "consent",
8012
+ "context",
8013
+ "count",
8014
+ "create",
8015
+ "current",
8016
+ "data-deletion",
8017
+ "data-export",
8018
+ "deliveries",
8019
+ "design",
8020
+ "discovery",
8021
+ "domains",
8022
+ "duplicate",
8023
+ "entry-exit",
8024
+ "environments",
8025
+ "evidence",
8026
+ "exclusion-groups",
8027
+ "experiments",
8028
+ "external-pixels",
8029
+ "external-tracking",
8030
+ "fields",
8031
+ "files",
8032
+ "filesystem",
8033
+ "flags",
8034
+ "funnels",
8035
+ "generate",
8036
+ "geo",
8037
+ "goals",
8038
+ "handoff",
8039
+ "harness",
8040
+ "idea",
8041
+ "ideas",
8042
+ "inbox",
8043
+ "install-domains",
8044
+ "invitations",
8045
+ "items",
8046
+ "keys",
8047
+ "landing-pages",
8048
+ "latency",
8049
+ "list",
8050
+ "manifest",
8051
+ "me",
8052
+ "members",
8053
+ "messages",
8054
+ "mine",
8055
+ "org",
8056
+ "organizations",
8057
+ "orgs",
8058
+ "pages",
8059
+ "pause",
8060
+ "personalizations",
8061
+ "plan",
8062
+ "preview-sessions",
8063
+ "profile",
8064
+ "publish",
8065
+ "qa",
8066
+ "quarantine",
8067
+ "read-state",
8068
+ "realtime",
8069
+ "recipes",
8070
+ "reject",
8071
+ "restore",
8072
+ "results",
8073
+ "revisions",
8074
+ "roadmap",
8075
+ "run",
8076
+ "runs",
8077
+ "runtime-settings",
8078
+ "screenshots",
8079
+ "search",
8080
+ "segments",
8081
+ "sessions",
8082
+ "settings",
8083
+ "shops",
8084
+ "signals",
8085
+ "simulate-trigger",
8086
+ "snippet",
8087
+ "sos",
8088
+ "start",
8089
+ "stats",
8090
+ "status",
8091
+ "suggest-goals",
8092
+ "summary",
8093
+ "targeting",
8094
+ "team",
8095
+ "telemetry",
8096
+ "test",
8097
+ "timeseries",
8098
+ "tracking",
8099
+ "traffic",
8100
+ "triggers",
8101
+ "url-inventory",
8102
+ "v1",
8103
+ "variations",
8104
+ "verify",
8105
+ "versions",
8106
+ "visitor-series",
8107
+ "webhooks",
8108
+ "workspace"
8109
+ ]);
8110
+ function normalizeRoute(path25) {
8111
+ if (!path25.startsWith("/api/")) return ":route";
8112
+ const parts = path25.split(/[?#]/, 1)[0].split("/");
8113
+ const files = parts.indexOf("files");
8114
+ if (files !== -1 && parts.length > files + 1) parts.splice(files + 1, parts.length, ":id");
8115
+ return parts.map((part) => !part || staticRouteSegments.has(part) ? part : ":id").join("/");
8116
+ }
8117
+ function recordApiResponse2(response) {
8118
+ apiCalls++;
8119
+ if (response.status >= 400 && !firstError)
8120
+ firstError = { ...response, path: normalizeRoute(response.path) };
8121
+ }
8122
+ function classifyError(exitCode, error, status3) {
8123
+ const detail5 = error && typeof error === "object" ? error : {};
8124
+ if (exitCode === 0) return "ok";
8125
+ if (detail5.code === "commander.unknownCommand") return "unknown_command";
8126
+ if (detail5.code?.startsWith("commander.")) return "validation";
8127
+ if (exitCode === 4) return "confirmation_required";
8128
+ if (status3 === 401 || status3 === 403 || exitCode === 2) return "auth";
8129
+ if (status3 === 404) return "not_found";
8130
+ if (status3 === 429) return "rate_limited";
8131
+ if (status3 === 409 || exitCode === 5) return "conflict";
8132
+ if (status3 && status3 >= 500) return "server";
8133
+ if (status3 === 400 || status3 === 422 || exitCode === 3) return "validation";
8134
+ if (Boolean(
8135
+ detail5.details?.reason && ["network", "dns", "timeout", "tls", "refused", "reset", "unknown"].includes(
8136
+ detail5.details.reason
8137
+ )
8138
+ ) || detail5.details?.code === "network_error" || detail5.name === "TimeoutError")
8139
+ return "network";
8140
+ return "internal";
8141
+ }
8142
+ async function finishInvocation({
8143
+ exitCode: rawExitCode,
8144
+ error
8145
+ }) {
8146
+ const exitCode = Number(rawExitCode);
8147
+ if (finished) return;
8148
+ finished = true;
8149
+ await (async () => {
8150
+ if (process.env.APEX_TELEMETRY === "0" || process.env.NODE_ENV === "test" || cliTestHarnessActive())
8151
+ return;
8152
+ const stored = getStoredCredentials();
8153
+ const creds = resolveCredentials(void 0, stored);
8154
+ if (!creds) return;
8155
+ if (invocation)
8156
+ creds.resolvedContext = {
8157
+ orgId: typeof invocation.org_id === "string" ? invocation.org_id : void 0,
8158
+ shopId: typeof invocation.shop_id === "string" ? invocation.shop_id : void 0
8159
+ };
8160
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
8161
+ const commanderExit = code.startsWith("commander.");
8162
+ const parse3 = commanderExit && exitCode !== 0;
8163
+ const token = commanderExit ? process.argv.slice(2).find((token2) => !token2.startsWith("-")) : void 0;
8164
+ const attempted = token && (/^(?:apx_|sk_|pk_|rk_|ghp_|gho_|xox|Bearer)/.test(token) || /^[A-Za-z0-9_-]{24,}$/.test(token) || /:\/\/|[/\\@=]/.test(token) || token.length > 40) ? "(redacted)" : token;
8165
+ if (attempted === "login" || attempted === "logout") return;
8166
+ const command = parse3 ? "(parse)" : invocation?.command ?? (commanderExit && exitCode === 0 ? attempted ?? (code === "commander.version" ? "(version)" : "(help)") : void 0);
8167
+ if (!command || command === "login" || command === "logout") return;
8168
+ if (!getClientIdentity()) setClientIdentity({ client: "apex-cli", version: CLI_VERSION });
8169
+ const payload = {
8170
+ ...invocation,
8171
+ command,
8172
+ ...parse3 ? { attempted } : {},
8173
+ invocation_id: getClientIdentity().invocationId,
8174
+ client_version: CLI_VERSION,
8175
+ node_version: process.versions.node,
8176
+ platform: process.platform,
8177
+ exit_code: exitCode,
8178
+ error_class: classifyError(exitCode, error, firstError?.status),
8179
+ api_calls: apiCalls,
8180
+ first_error_status: firstError?.status,
8181
+ first_error_code: firstError?.errorCode,
8182
+ first_error_request_id: firstError?.requestId,
8183
+ first_error_route: firstError?.path,
8184
+ duration_ms: Math.max(0, Math.round(performance.now() - started))
8185
+ };
8186
+ const client = createHttpClient(creds, {
8187
+ onNetworkError() {
8188
+ throw new Error("telemetry unavailable");
8189
+ },
8190
+ warn() {
8191
+ }
8192
+ });
8193
+ await client.request("POST", "/api/v1/telemetry/cli", payload, 800);
8194
+ })().catch(() => void 0);
7742
8195
  }
7743
8196
 
8197
+ // src/cli.ts
8198
+ init_source();
8199
+
8200
+ // src/commands/login.ts
8201
+ init_source();
8202
+ var import_node_crypto3 = __toESM(require("node:crypto"));
8203
+ var import_node_http = __toESM(require("node:http"));
8204
+ var import_node_os6 = __toESM(require("node:os"));
8205
+ var import_node_readline2 = __toESM(require("node:readline"));
8206
+
7744
8207
  // src/api.ts
8208
+ var import_node_crypto2 = require("node:crypto");
7745
8209
  function simulateTriggers(creds, body5) {
7746
8210
  return request(creds, "POST", "/api/v1/tracking/simulate-trigger", body5);
7747
8211
  }
@@ -7765,11 +8229,11 @@ function getUserKeyIdentity(creds) {
7765
8229
  function getAccountContext(creds) {
7766
8230
  return userRequest(creds, "GET", "/api/v1/me/context");
7767
8231
  }
7768
- function userRequest(creds, method, path24, body5) {
8232
+ function userRequest(creds, method, path25, body5) {
7769
8233
  return request(
7770
8234
  { ...creds, apiKey: creds.userApiKey ?? "", baseUrl: creds.userBaseUrl ?? creds.baseUrl },
7771
8235
  method,
7772
- path24,
8236
+ path25,
7773
8237
  body5
7774
8238
  );
7775
8239
  }
@@ -7801,6 +8265,7 @@ function sharedClient(creds) {
7801
8265
  let client = sharedClients.get(creds);
7802
8266
  if (!client) {
7803
8267
  client = createHttpClient(creds, {
8268
+ onResponse: recordApiResponse2,
7804
8269
  transport: testTransport,
7805
8270
  testTransportVersion: () => testTransport ? process.env[["APEX", "CLI", "MOCK", "API", "VERSION"].join("_")] : void 0,
7806
8271
  onNetworkError: (info) => {
@@ -7811,19 +8276,22 @@ function sharedClient(creds) {
7811
8276
  }
7812
8277
  const host = url?.hostname ?? info.baseUrl;
7813
8278
  const code = info.code;
7814
- let reason = "unknown";
8279
+ let reason2 = "unknown";
7815
8280
  let message = info.causeMessage ?? info.cause;
7816
- if (info.name === "TimeoutError" || info.name === "AbortError" || code === "UND_ERR_CONNECT_TIMEOUT") {
7817
- reason = "timeout";
8281
+ if (code === "UND_ERR_CONNECT_TIMEOUT") {
8282
+ reason2 = "connect_timeout";
8283
+ message = `could not open a connection to ${host} within ${info.timeoutMs / 1e3}s`;
8284
+ } else if (info.name === "TimeoutError" || info.name === "AbortError") {
8285
+ reason2 = "timeout";
7818
8286
  message = `server did not respond within ${info.timeoutMs / 1e3}s`;
7819
8287
  } else if (code === "ENOTFOUND" || code === "EAI_AGAIN") {
7820
- reason = "dns";
8288
+ reason2 = "dns";
7821
8289
  message = `could not resolve host ${host}`;
7822
8290
  } else if (code === "ECONNREFUSED") {
7823
- reason = "refused";
8291
+ reason2 = "refused";
7824
8292
  message = `connection refused by ${host}:${url?.port || (url?.protocol === "https:" ? "443" : "80")}`;
7825
8293
  } else if (code === "ECONNRESET" || code === "EPIPE") {
7826
- reason = "reset";
8294
+ reason2 = "reset";
7827
8295
  message = `connection reset by ${host}`;
7828
8296
  } else if (code?.startsWith("CERT_") || code?.startsWith("ERR_TLS_") || [
7829
8297
  "DEPTH_ZERO_SELF_SIGNED_CERT",
@@ -7833,13 +8301,13 @@ function sharedClient(creds) {
7833
8301
  "UNABLE_TO_GET_ISSUER_CERT",
7834
8302
  "HOSTNAME_MISMATCH"
7835
8303
  ].includes(code ?? "")) {
7836
- reason = "tls";
8304
+ reason2 = "tls";
7837
8305
  message = `TLS error (${code})`;
7838
8306
  }
7839
8307
  throw new CliError(
7840
8308
  `Could not reach ${info.baseUrl}: check APEX_BASE_URL (or DRIP_BASE_URL) / network; ${message}.`,
7841
8309
  EXIT_CODES.general,
7842
- { reason, code, name: info.name, baseUrl: info.baseUrl, timeoutMs: info.timeoutMs }
8310
+ { reason: reason2, code, name: info.name, baseUrl: info.baseUrl, timeoutMs: info.timeoutMs }
7843
8311
  );
7844
8312
  }
7845
8313
  });
@@ -7847,13 +8315,13 @@ function sharedClient(creds) {
7847
8315
  }
7848
8316
  return client;
7849
8317
  }
7850
- async function request(creds, method, path24, body5, requestTimeoutMs) {
7851
- const response = await sharedClient(creds).request(method, path24, body5, requestTimeoutMs);
8318
+ async function request(creds, method, path25, body5, requestTimeoutMs) {
8319
+ const response = await sharedClient(creds).request(method, path25, body5, requestTimeoutMs);
7852
8320
  recordApiResponse(response);
7853
8321
  return response;
7854
8322
  }
7855
- async function requestForm(creds, method, path24, body5) {
7856
- const response = await sharedClient(creds).requestForm(method, path24, body5);
8323
+ async function requestForm(creds, method, path25, body5) {
8324
+ const response = await sharedClient(creds).requestForm(method, path25, body5);
7857
8325
  recordApiResponse(response);
7858
8326
  return response;
7859
8327
  }
@@ -7868,11 +8336,19 @@ function listExperiments(creds, opts) {
7868
8336
  const qs = params.toString();
7869
8337
  return request(creds, "GET", `/api/v1/experiments${qs ? `?${qs}` : ""}`);
7870
8338
  }
7871
- function getExperiment(creds, id) {
7872
- return request(creds, "GET", `/api/v1/experiments/${id}`);
8339
+ function getExperiment(creds, id, includePreviewTarget = false) {
8340
+ return request(
8341
+ creds,
8342
+ "GET",
8343
+ `/api/v1/experiments/${id}${includePreviewTarget ? "?include=preview_target" : ""}`
8344
+ );
7873
8345
  }
7874
- function getExperimentBySlug(creds, slug) {
7875
- return request(creds, "GET", `/api/v1/experiments/by-slug/${slug}`);
8346
+ function getExperimentBySlug(creds, slug, includePreviewTarget = false) {
8347
+ return request(
8348
+ creds,
8349
+ "GET",
8350
+ `/api/v1/experiments/by-slug/${slug}${includePreviewTarget ? "?include=preview_target" : ""}`
8351
+ );
7876
8352
  }
7877
8353
  function upsertExperimentBySlug(creds, slug, body5) {
7878
8354
  return request(creds, "PUT", `/api/v1/experiments/by-slug/${slug}`, body5);
@@ -7911,6 +8387,16 @@ function getExperimentResults(creds, id, opts) {
7911
8387
  `/api/v1/experiments/${id}/results${qs ? `?${qs}` : ""}`
7912
8388
  );
7913
8389
  }
8390
+ function getExperimentResultsExplain(creds, id, opts) {
8391
+ const params = new URLSearchParams();
8392
+ for (const key of ["currency", "goal", "order"])
8393
+ if (opts[key]) params.set(key, opts[key]);
8394
+ return request(
8395
+ creds,
8396
+ "GET",
8397
+ `/api/v1/experiments/${id}/results/explain?${params}`
8398
+ );
8399
+ }
7914
8400
  function createExperimentScreenshots(creds, id, body5) {
7915
8401
  return request(creds, "POST", `/api/v1/experiments/${id}/screenshots`, body5);
7916
8402
  }
@@ -7937,7 +8423,10 @@ function applyExperimentFilesystem(creds, id, body5) {
7937
8423
  creds,
7938
8424
  "POST",
7939
8425
  `/api/v1/experiments/${id}/filesystem/apply`,
7940
- body5
8426
+ {
8427
+ files: body5.files,
8428
+ ...body5.confirmTargetingReplace === true ? { confirmTargetingReplace: true } : {}
8429
+ }
7941
8430
  );
7942
8431
  }
7943
8432
  function getExperimentQaRun(creds, id) {
@@ -8104,6 +8593,15 @@ function verifyTracking(creds, opts) {
8104
8593
  `/api/v1/tracking/verify${qs ? `?${qs}` : ""}`
8105
8594
  );
8106
8595
  }
8596
+ function exportEventsPage(creds, opts) {
8597
+ const params = new URLSearchParams({ from: opts.from, to: opts.to });
8598
+ if (opts.experiment) params.set("experiment", opts.experiment);
8599
+ if (opts.eventTypes?.length) params.set("event_type", opts.eventTypes.join(","));
8600
+ if (opts.qa) params.set("qa", opts.qa);
8601
+ if (opts.limit) params.set("limit", String(opts.limit));
8602
+ if (opts.cursor) params.set("cursor", opts.cursor);
8603
+ return request(creds, "GET", `/api/v1/events/export?${params}`);
8604
+ }
8107
8605
  function discoverTrackingSchema(creds, minutes) {
8108
8606
  const query = minutes ? `?minutes=${encodeURIComponent(String(minutes))}` : "";
8109
8607
  return request(creds, "GET", `/api/v1/tracking/discovery${query}`);
@@ -8114,7 +8612,7 @@ function testTargeting(creds, url, opts) {
8114
8612
  return request(creds, "GET", `/api/v1/targeting/test?${params.toString()}`);
8115
8613
  }
8116
8614
  function createSos(creds, body5) {
8117
- const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto.createHash)("sha256").update(
8615
+ const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto2.createHash)("sha256").update(
8118
8616
  [
8119
8617
  body5.type,
8120
8618
  body5.experiment_id ?? "",
@@ -8265,8 +8763,8 @@ function requestTargetingResource(creds, method, kind5, id, body5, limit, cursor
8265
8763
  if (limit) params.set("limit", String(limit));
8266
8764
  if (cursor) params.set("cursor", cursor);
8267
8765
  const query = params.toString();
8268
- const path24 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
8269
- return request(creds, method, path24, body5);
8766
+ const path25 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
8767
+ return request(creds, method, path25, body5);
8270
8768
  }
8271
8769
  function managedPath(kind5, id, action74) {
8272
8770
  return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action74 ? `/${action74}` : ""}`;
@@ -8502,6 +9000,24 @@ function getWebhookDeliveries(creds, id, limit) {
8502
9000
  function testWebhook(creds, id) {
8503
9001
  return request(creds, "POST", `/api/v1/webhooks/${encodeURIComponent(id)}/test`);
8504
9002
  }
9003
+ function getOrderJourney(creds, opts) {
9004
+ const params = new URLSearchParams({
9005
+ platform: opts.platform,
9006
+ environment: opts.environment,
9007
+ from: opts.from,
9008
+ to: opts.to
9009
+ });
9010
+ if (opts.orderId !== void 0) params.set("platform_order_id", opts.orderId);
9011
+ if (opts.orderNumber !== void 0) params.set("order_number", opts.orderNumber);
9012
+ if (opts.includeQa) params.set("include_qa", "true");
9013
+ return request(
9014
+ creds,
9015
+ "GET",
9016
+ `/api/v1/analytics/order-journey?${params}`,
9017
+ void 0,
9018
+ 45e3
9019
+ );
9020
+ }
8505
9021
 
8506
9022
  // src/context.ts
8507
9023
  var import_node_readline = __toESM(require("node:readline"));
@@ -8587,9 +9103,9 @@ var STATUS_COLORS = {
8587
9103
  paused: source_default.yellow,
8588
9104
  completed: source_default.blue
8589
9105
  };
8590
- function statusBadge(status2) {
8591
- const colorize = STATUS_COLORS[status2] ?? source_default.white;
8592
- return colorize(status2.toUpperCase());
9106
+ function statusBadge(status3) {
9107
+ const colorize = STATUS_COLORS[status3] ?? source_default.white;
9108
+ return colorize(status3.toUpperCase());
8593
9109
  }
8594
9110
  function printTable(headers, rows, widths) {
8595
9111
  const colWidths = widths ?? headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
@@ -8667,8 +9183,8 @@ function parseFormBody(body5) {
8667
9183
  return new URLSearchParams(body5);
8668
9184
  }
8669
9185
  async function waitForBrowserLogin(baseUrl, userMode = false) {
8670
- const state = import_node_crypto2.default.randomBytes(24).toString("base64url");
8671
- const clientName = import_node_os5.default.hostname() || "Local machine";
9186
+ const state = import_node_crypto3.default.randomBytes(24).toString("base64url");
9187
+ const clientName = import_node_os6.default.hostname() || "Local machine";
8672
9188
  return new Promise((resolve2, reject) => {
8673
9189
  let settled = false;
8674
9190
  let timeout = null;
@@ -8999,9 +9515,9 @@ var useCommand = new Command("use").description("Set the current Apex organizati
8999
9515
 
9000
9516
  // src/commands/experiments.ts
9001
9517
  init_source();
9002
- var import_node_crypto4 = __toESM(require("node:crypto"));
9003
- var import_node_fs7 = __toESM(require("node:fs"));
9004
- var import_node_path5 = __toESM(require("node:path"));
9518
+ var import_node_crypto5 = __toESM(require("node:crypto"));
9519
+ var import_node_fs8 = __toESM(require("node:fs"));
9520
+ var import_node_path6 = __toESM(require("node:path"));
9005
9521
 
9006
9522
  // src/lib/qa-vocabulary.ts
9007
9523
  var checklistStatuses = [
@@ -9031,6 +9547,30 @@ var evidenceKinds = [
9031
9547
  "readiness"
9032
9548
  ];
9033
9549
 
9550
+ // src/lib/experiment-handover.ts
9551
+ function summarizeHandoverQa(value) {
9552
+ const record3 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
9553
+ const qa3 = record3(value);
9554
+ const run3 = record3(qa3.latestRun);
9555
+ const summary = record3(run3.summary);
9556
+ const count2 = (value2) => typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : null;
9557
+ let requiredDone = count2(summary.completedRequired);
9558
+ let requiredTotal = count2(summary.required);
9559
+ if (requiredDone !== null && requiredTotal !== null && requiredDone > requiredTotal) {
9560
+ requiredDone = null;
9561
+ requiredTotal = null;
9562
+ }
9563
+ const blockers = Array.isArray(summary.blockers) && summary.blockers.every((item) => typeof item === "string") ? summary.blockers : null;
9564
+ const approvedAt = typeof qa3.qaApprovedAt === "string" ? Date.parse(qa3.qaApprovedAt) : Number.NaN;
9565
+ return {
9566
+ run_status: typeof run3.status === "string" ? run3.status : null,
9567
+ required_done: requiredDone,
9568
+ required_total: requiredTotal,
9569
+ blockers,
9570
+ approval_present: Number.isFinite(approvedAt)
9571
+ };
9572
+ }
9573
+
9034
9574
  // src/lib/dashboard-url.ts
9035
9575
  function buildDashboardUrl(baseUrl, shopId, experimentId) {
9036
9576
  if (!shopId) return null;
@@ -9148,8 +9688,8 @@ function pageEntries(raw) {
9148
9688
  }
9149
9689
  return [...byId.values()];
9150
9690
  }
9151
- function urlRuleClause(count) {
9152
- return count === 1 ? "1 URL rule" : `${count} URL rules`;
9691
+ function urlRuleClause(count2) {
9692
+ return count2 === 1 ? "1 URL rule" : `${count2} URL rules`;
9153
9693
  }
9154
9694
  function effectivePageTargetingLabel(targeting) {
9155
9695
  const raw = isRecord(targeting) ? targeting : {};
@@ -9194,9 +9734,9 @@ function deriveStoreUrl(patterns) {
9194
9734
  }
9195
9735
 
9196
9736
  // src/lib/asset-upload.ts
9197
- var import_node_crypto3 = __toESM(require("node:crypto"));
9198
- var import_node_fs6 = __toESM(require("node:fs"));
9199
- var import_node_path4 = __toESM(require("node:path"));
9737
+ var import_node_crypto4 = __toESM(require("node:crypto"));
9738
+ var import_node_fs7 = __toESM(require("node:fs"));
9739
+ var import_node_path5 = __toESM(require("node:path"));
9200
9740
  var INLINE_DATA_URI_MAX_CHARS = 4096;
9201
9741
  var DATA_IMAGE_URI_PATTERN = /data:image\/[a-z0-9.+-]+(?:(?:;[^;,\s"'`]*)*;base64,[A-Za-z0-9+/=\s]+|(?:;[^,\s"'`]*)?,[^\s"'`]+)/gi;
9202
9742
  var QUOTED_LOCAL_IMAGE_PATTERN = /(["'])([^"'\n]+?\.(?:png|jpe?g|gif|webp|svg|avif))\1/gi;
@@ -9219,7 +9759,7 @@ var EXTENSION_BY_MIME = {
9219
9759
  "image/avif": ".avif"
9220
9760
  };
9221
9761
  function contentHash(bytes) {
9222
- return import_node_crypto3.default.createHash("sha256").update(bytes).digest("hex");
9762
+ return import_node_crypto4.default.createHash("sha256").update(bytes).digest("hex");
9223
9763
  }
9224
9764
  function deliveryUrl(baseUrl, asset) {
9225
9765
  const returnedUrl = asset.deliveryUrl ?? asset.delivery_url ?? asset.url;
@@ -9307,23 +9847,23 @@ function createAssetReferenceRewriter(options) {
9307
9847
  if (reference.startsWith("/") || reference.startsWith("data:") || reference.includes("://")) {
9308
9848
  return null;
9309
9849
  }
9310
- const absolutePath = import_node_path4.default.resolve(baseDir, reference);
9850
+ const absolutePath = import_node_path5.default.resolve(baseDir, reference);
9311
9851
  let realBaseDir;
9312
9852
  let realPath;
9313
9853
  try {
9314
- realBaseDir = import_node_fs6.default.realpathSync(baseDir);
9315
- realPath = import_node_fs6.default.realpathSync(absolutePath);
9854
+ realBaseDir = import_node_fs7.default.realpathSync(baseDir);
9855
+ realPath = import_node_fs7.default.realpathSync(absolutePath);
9316
9856
  } catch {
9317
9857
  return null;
9318
9858
  }
9319
- const relativeRealPath = import_node_path4.default.relative(realBaseDir, realPath);
9320
- if (relativeRealPath.startsWith("..") || import_node_path4.default.isAbsolute(relativeRealPath)) return null;
9321
- if (!import_node_fs6.default.statSync(realPath).isFile()) return null;
9322
- const bytes = import_node_fs6.default.readFileSync(realPath);
9323
- const extension = import_node_path4.default.extname(realPath).toLowerCase();
9859
+ const relativeRealPath = import_node_path5.default.relative(realBaseDir, realPath);
9860
+ if (relativeRealPath.startsWith("..") || import_node_path5.default.isAbsolute(relativeRealPath)) return null;
9861
+ if (!import_node_fs7.default.statSync(realPath).isFile()) return null;
9862
+ const bytes = import_node_fs7.default.readFileSync(realPath);
9863
+ const extension = import_node_path5.default.extname(realPath).toLowerCase();
9324
9864
  return uploadOnce(
9325
9865
  {
9326
- name: import_node_path4.default.basename(absolutePath),
9866
+ name: import_node_path5.default.basename(absolutePath),
9327
9867
  bytes,
9328
9868
  type: MIME_BY_EXTENSION[extension]
9329
9869
  },
@@ -9500,6 +10040,16 @@ function printExperimentResults(results2, currency) {
9500
10040
  [32, 8, 6, 8, 10]
9501
10041
  );
9502
10042
  }
10043
+ const metrics = Array.isArray(results2.metrics) ? results2.metrics : [];
10044
+ if (metrics.length > 0) {
10045
+ console.log();
10046
+ console.log(source_default.bold("Goal counting"));
10047
+ metrics.forEach((metric) => {
10048
+ const basis = metric.counting === "once" ? "unique visitors" : metric.counting === "every" ? "every event" : "unknown basis";
10049
+ console.log(` - ${String(metric.name ?? metric.goalId ?? "Goal")}: ${basis}`);
10050
+ });
10051
+ }
10052
+ console.log(" Definitions: https://docs.drip-apex.com/docs/experiments/metric-definitions");
9503
10053
  const warnings = Array.isArray(results2.warnings) ? results2.warnings : [];
9504
10054
  if (warnings.length > 0) {
9505
10055
  console.log();
@@ -9528,39 +10078,59 @@ var FILESYSTEM_FILE_RE = new RegExp(
9528
10078
  `^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
9529
10079
  );
9530
10080
  function sha256Text(value) {
9531
- return import_node_crypto4.default.createHash("sha256").update(value).digest("hex");
10081
+ return import_node_crypto5.default.createHash("sha256").update(value).digest("hex");
9532
10082
  }
9533
10083
  function folderPath(folder) {
9534
- return import_node_path5.default.resolve(process.cwd(), folder);
10084
+ return import_node_path6.default.resolve(process.cwd(), folder);
9535
10085
  }
9536
10086
  function filesystemApexDir(folder) {
9537
- return import_node_path5.default.join(folder, ".apex");
10087
+ return import_node_path6.default.join(folder, ".apex");
9538
10088
  }
9539
10089
  function filesystemSessionPath(folder) {
9540
- return import_node_path5.default.join(filesystemApexDir(folder), "experiment-filesystem.json");
10090
+ return import_node_path6.default.join(filesystemApexDir(folder), "experiment-filesystem.json");
9541
10091
  }
9542
10092
  function writeFilesystemSession(folder, session) {
9543
- import_node_fs7.default.mkdirSync(filesystemApexDir(folder), { recursive: true });
9544
- import_node_fs7.default.writeFileSync(
9545
- import_node_path5.default.join(filesystemApexDir(folder), ".gitignore"),
10093
+ import_node_fs8.default.mkdirSync(filesystemApexDir(folder), { recursive: true });
10094
+ import_node_fs8.default.writeFileSync(
10095
+ import_node_path6.default.join(filesystemApexDir(folder), ".gitignore"),
9546
10096
  "experiment-filesystem.json\n",
9547
10097
  "utf8"
9548
10098
  );
9549
- import_node_fs7.default.writeFileSync(filesystemSessionPath(folder), `${JSON.stringify(session, null, 2)}
10099
+ import_node_fs8.default.writeFileSync(filesystemSessionPath(folder), `${JSON.stringify(session, null, 2)}
9550
10100
  `, "utf8");
9551
10101
  }
9552
10102
  function readFilesystemSession(folder) {
9553
10103
  const filePath = filesystemSessionPath(folder);
9554
- if (!import_node_fs7.default.existsSync(filePath)) {
9555
- const workspaceMarker = import_node_path5.default.join(filesystemApexDir(folder), "session.json");
9556
- if (import_node_fs7.default.existsSync(workspaceMarker)) {
9557
- throw new Error(
9558
- "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."
10104
+ if (!import_node_fs8.default.existsSync(filePath)) {
10105
+ const workspaceMarker = import_node_path6.default.join(filesystemApexDir(folder), "session.json");
10106
+ if (import_node_fs8.default.existsSync(workspaceMarker)) {
10107
+ throw new CliError(
10108
+ "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.",
10109
+ EXIT_CODES.general,
10110
+ {
10111
+ code: "validation_failed",
10112
+ next_steps: [
10113
+ { command: "apex workspace status", why: "Use the command for this folder format." },
10114
+ {
10115
+ command: "apex experiments pull",
10116
+ why: "Create the required folder in a different directory."
10117
+ }
10118
+ ]
10119
+ }
9559
10120
  );
9560
10121
  }
9561
- throw new Error(`Missing ${filePath}. Run apex experiments pull first.`);
10122
+ throw new CliError(
10123
+ `Missing ${filePath}. Run apex experiments pull first.`,
10124
+ EXIT_CODES.general,
10125
+ {
10126
+ code: "local_file_error",
10127
+ next_steps: [
10128
+ { command: "apex experiments pull", why: "Create the missing local folder metadata." }
10129
+ ]
10130
+ }
10131
+ );
9562
10132
  }
9563
- return JSON.parse(import_node_fs7.default.readFileSync(filePath, "utf8"));
10133
+ return JSON.parse(import_node_fs8.default.readFileSync(filePath, "utf8"));
9564
10134
  }
9565
10135
  function validateFilesystemPath(relativePath) {
9566
10136
  const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
@@ -9576,17 +10146,17 @@ function writeExperimentFilesystemFiles(folder, files) {
9576
10146
  }));
9577
10147
  for (const file of validatedFiles) {
9578
10148
  const relativePath = file.relativePath;
9579
- const absPath = import_node_path5.default.join(folder, relativePath);
9580
- import_node_fs7.default.mkdirSync(import_node_path5.default.dirname(absPath), { recursive: true });
9581
- import_node_fs7.default.writeFileSync(absPath, file.content, "utf8");
10149
+ const absPath = import_node_path6.default.join(folder, relativePath);
10150
+ import_node_fs8.default.mkdirSync(import_node_path6.default.dirname(absPath), { recursive: true });
10151
+ import_node_fs8.default.writeFileSync(absPath, file.content, "utf8");
9582
10152
  }
9583
10153
  }
9584
10154
  function listExperimentFilesystemFiles(folder, session) {
9585
10155
  const files = [];
9586
10156
  for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel) {
9587
- const absPath = import_node_path5.default.join(folder, fileName);
9588
- if (!import_node_fs7.default.existsSync(absPath)) continue;
9589
- const content = import_node_fs7.default.readFileSync(absPath, "utf8");
10157
+ const absPath = import_node_path6.default.join(folder, fileName);
10158
+ if (!import_node_fs8.default.existsSync(absPath)) continue;
10159
+ const content = import_node_fs8.default.readFileSync(absPath, "utf8");
9590
10160
  const sha256 = sha256Text(content);
9591
10161
  if (SERVER_OWNED_EXPERIMENT_FILES.has(fileName)) {
9592
10162
  if (session.files[fileName]?.sha256 !== sha256) {
@@ -9600,21 +10170,21 @@ function listExperimentFilesystemFiles(folder, session) {
9600
10170
  }
9601
10171
  for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment) {
9602
10172
  const relativePath = `experiment/${fileName}`;
9603
- const absPath = import_node_path5.default.join(folder, relativePath);
9604
- if (!import_node_fs7.default.existsSync(absPath)) continue;
9605
- const content = import_node_fs7.default.readFileSync(absPath, "utf8");
10173
+ const absPath = import_node_path6.default.join(folder, relativePath);
10174
+ if (!import_node_fs8.default.existsSync(absPath)) continue;
10175
+ const content = import_node_fs8.default.readFileSync(absPath, "utf8");
9606
10176
  files.push({ path: relativePath, content, sha256: sha256Text(content) });
9607
10177
  }
9608
- const variationsRoot = import_node_path5.default.join(folder, "variations");
9609
- if (import_node_fs7.default.existsSync(variationsRoot)) {
9610
- for (const variationDir of import_node_fs7.default.readdirSync(variationsRoot, { withFileTypes: true })) {
10178
+ const variationsRoot = import_node_path6.default.join(folder, "variations");
10179
+ if (import_node_fs8.default.existsSync(variationsRoot)) {
10180
+ for (const variationDir of import_node_fs8.default.readdirSync(variationsRoot, { withFileTypes: true })) {
9611
10181
  if (!variationDir.isDirectory()) continue;
9612
- const base = import_node_path5.default.join(variationsRoot, variationDir.name);
10182
+ const base = import_node_path6.default.join(variationsRoot, variationDir.name);
9613
10183
  for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation) {
9614
- const absPath = import_node_path5.default.join(base, fileName);
9615
- if (!import_node_fs7.default.existsSync(absPath)) continue;
9616
- const relativePath = validateFilesystemPath(import_node_path5.default.relative(folder, absPath));
9617
- const content = import_node_fs7.default.readFileSync(absPath, "utf8");
10184
+ const absPath = import_node_path6.default.join(base, fileName);
10185
+ if (!import_node_fs8.default.existsSync(absPath)) continue;
10186
+ const relativePath = validateFilesystemPath(import_node_path6.default.relative(folder, absPath));
10187
+ const content = import_node_fs8.default.readFileSync(absPath, "utf8");
9618
10188
  files.push({ path: relativePath, content, sha256: sha256Text(content) });
9619
10189
  }
9620
10190
  }
@@ -9627,7 +10197,7 @@ async function rewriteExperimentFilesystemAssets(input) {
9627
10197
  baseUrl: input.baseUrl,
9628
10198
  enabled: input.enabled,
9629
10199
  upload: async (file) => {
9630
- const hash = import_node_crypto4.default.createHash("sha256").update(file.bytes).digest("hex");
10200
+ const hash = import_node_crypto5.default.createHash("sha256").update(file.bytes).digest("hex");
9631
10201
  const cached = input.assets[hash];
9632
10202
  if (cached) return cached;
9633
10203
  if (input.upload === null) return null;
@@ -9656,7 +10226,7 @@ async function rewriteExperimentFilesystemAssets(input) {
9656
10226
  }
9657
10227
  const content = await rewriter.rewrite(
9658
10228
  file.content,
9659
- import_node_path5.default.dirname(import_node_path5.default.join(input.folder, file.path))
10229
+ import_node_path6.default.dirname(import_node_path6.default.join(input.folder, file.path))
9660
10230
  );
9661
10231
  return { ...file, content, sha256: sha256Text(content) };
9662
10232
  })
@@ -9667,9 +10237,17 @@ function printFilesystemPlan(plan) {
9667
10237
  console.log(source_default.red("\nErrors"));
9668
10238
  plan.errors.forEach((error) => console.log(` - ${error}`));
9669
10239
  }
9670
- if (plan.warnings.length > 0) {
10240
+ const compatibilityWarnings = (plan.previewCompatibility ?? []).map(
10241
+ (finding) => `${finding.path}:${finding.line}:${finding.column} ${finding.rule}: ${finding.message}. Try: ${finding.alternative}`
10242
+ );
10243
+ if (compatibilityWarnings.length > 0) {
10244
+ console.log(source_default.yellow("\nPreview compatibility"));
10245
+ compatibilityWarnings.forEach((warning) => console.log(` - ${warning}`));
10246
+ }
10247
+ const otherWarnings = plan.warnings.filter((warning) => !compatibilityWarnings.includes(warning));
10248
+ if (otherWarnings.length > 0) {
9671
10249
  console.log(source_default.yellow("\nWarnings"));
9672
- plan.warnings.forEach((warning) => console.log(` - ${warning}`));
10250
+ otherWarnings.forEach((warning) => console.log(` - ${warning}`));
9673
10251
  }
9674
10252
  if ((plan.blockingIssues?.length ?? 0) > 0) {
9675
10253
  console.log(source_default.red("\nBlocking issues"));
@@ -9744,6 +10322,10 @@ function printQaPreviewUrls(experimentId, result) {
9744
10322
  );
9745
10323
  }
9746
10324
  }
10325
+ function qaVersionMarker(value) {
10326
+ const version = value;
10327
+ return version.stale ? " [stale: checked against an earlier version]" : version.unversioned ? " [unversioned]" : "";
10328
+ }
9747
10329
  function printQaRun(run3) {
9748
10330
  console.log();
9749
10331
  console.log(source_default.bold(`QA run ${run3.id}`));
@@ -9760,7 +10342,7 @@ function printQaRun(run3) {
9760
10342
  for (const item of run3.checklist) {
9761
10343
  const required = item.required ? source_default.yellow(" required") : "";
9762
10344
  const notes = item.notes ? source_default.dim(` \u2014 ${item.notes}`) : "";
9763
- console.log(` ${statusBadge(item.status)} ${item.id}${required}`);
10345
+ console.log(` ${statusBadge(item.status)} ${item.id}${required}${qaVersionMarker(item)}`);
9764
10346
  console.log(` ${item.title}${notes}`);
9765
10347
  }
9766
10348
  if (run3.urls.length > 0) {
@@ -9771,10 +10353,10 @@ function printQaRun(run3) {
9771
10353
  }
9772
10354
  if ((run3.evidenceRecords ?? []).length > 0) {
9773
10355
  console.log(source_default.bold("\nEvidence records"));
9774
- for (const record2 of run3.evidenceRecords ?? []) {
9775
- const device = record2.device ? ` \xB7 ${record2.device}` : "";
10356
+ for (const record3 of run3.evidenceRecords ?? []) {
10357
+ const device = record3.device ? ` \xB7 ${record3.device}` : "";
9776
10358
  console.log(
9777
- ` - ${record2.kind} \xB7 ${record2.label} \xB7 ${record2.status}${device} \xB7 ${record2.capturedAt}`
10359
+ ` - ${record3.kind} \xB7 ${record3.label} \xB7 ${record3.status}${device} \xB7 ${record3.capturedAt}${qaVersionMarker(record3)}`
9778
10360
  );
9779
10361
  }
9780
10362
  }
@@ -9795,8 +10377,8 @@ async function confirmApply(message) {
9795
10377
  function parseEvidenceInput(value) {
9796
10378
  if (!value) return void 0;
9797
10379
  const source = value.startsWith("@") ? value.slice(1) : value;
9798
- const text = value.startsWith("@") || import_node_fs7.default.existsSync(source) ? import_node_fs7.default.readFileSync(import_node_path5.default.resolve(process.cwd(), source), "utf8") : value;
9799
- const parsed = JSON.parse(text);
10380
+ const text2 = value.startsWith("@") || import_node_fs8.default.existsSync(source) ? import_node_fs8.default.readFileSync(import_node_path6.default.resolve(process.cwd(), source), "utf8") : value;
10381
+ const parsed = JSON.parse(text2);
9800
10382
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9801
10383
  throw new Error("QA evidence must be a JSON object");
9802
10384
  }
@@ -9840,7 +10422,7 @@ async function resolveExperimentForFilesystemOrId(value) {
9840
10422
  return {
9841
10423
  experimentId: session.experimentId,
9842
10424
  folder: candidate,
9843
- label: import_node_path5.default.basename(candidate)
10425
+ label: import_node_path6.default.basename(candidate)
9844
10426
  };
9845
10427
  }
9846
10428
  const exp = await resolveExperiment(value);
@@ -10026,7 +10608,53 @@ var detachIdea = new Command("detach-idea").description("Detach an experiment fr
10026
10608
  );
10027
10609
  output(this, res.data, () => printSuccess("Idea detached."));
10028
10610
  });
10029
- var results = new Command("results").description("Read experiment results and timeseries by ID or slug").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--currency <code>", "Currency code for revenue formatting").action(async function action7(idOrSlug, opts) {
10611
+ function printResultsExplain(body5, samples) {
10612
+ const data = body5.data;
10613
+ console.log("Diagnostic results explanation");
10614
+ for (const arm of data.arms) {
10615
+ console.log(`
10616
+ ${arm.name} (${arm.variation_id})`);
10617
+ for (const name of ["visitors", "conversions", "orders", "revenue"]) {
10618
+ const metric = arm[name];
10619
+ if (!metric) continue;
10620
+ console.log(` ${name}`);
10621
+ for (const [label, count2] of Object.entries(metric.counts ?? {}))
10622
+ console.log(` ${label.padEnd(28)} ${count2}`);
10623
+ console.log(
10624
+ ` Counted: ${metric.classified_counted ?? "unavailable"} Reported: ${metric.reported ?? "unavailable"} Residual: ${metric.residual ?? "unavailable"}`
10625
+ );
10626
+ if (metric.explanation) console.log(` ${metric.explanation}`);
10627
+ if (samples && metric.samples) {
10628
+ for (const [label, entries] of Object.entries(
10629
+ metric.samples
10630
+ )) {
10631
+ for (const entry of entries)
10632
+ console.log(
10633
+ ` ${label}: ${entry.order_key || entry.event_id} ${entry.amount ?? ""} ${entry.currency ?? ""} ${entry.timestamp ?? ""} arm_only=${entry.arm_only ?? false}`
10634
+ );
10635
+ }
10636
+ }
10637
+ }
10638
+ }
10639
+ if (data.order) console.log(`
10640
+ Order trail
10641
+ ${JSON.stringify(data.order, null, 2)}`);
10642
+ const others = data.other_variations ?? [];
10643
+ if (others.length > 0) {
10644
+ console.log("\nOther variations (not part of this test)");
10645
+ for (const other of others) console.log(` ${other.variation_id}: ${other.row_count} rows`);
10646
+ }
10647
+ const limitations = data.limitations ?? [];
10648
+ if (limitations.length > 0) {
10649
+ console.log("\nLimitations");
10650
+ for (const limitation of limitations) console.log(` - ${limitation}`);
10651
+ }
10652
+ console.log(`
10653
+ Definitions: ${data.definitions_url}`);
10654
+ }
10655
+ var results = new Command("results").description("Read experiment results and timeseries by ID or slug").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--currency <code>", "Currency code for revenue formatting").option("--explain", "Explain raw-event exclusions and residuals").option("--goal <id>", "Goal ID or event key for --explain").option("--order <key>", "Order label trail for --explain").option("--samples", "Show sample identifiers with --explain").action(async function action7(idOrSlug, opts) {
10656
+ if (!opts.explain && (opts.goal || opts.order || opts.samples))
10657
+ fail(this, "--goal, --order and --samples require --explain", EXIT_CODES.validation);
10030
10658
  const creds = requireCredentials();
10031
10659
  const spinner = await createSpinner("Fetching experiment results...");
10032
10660
  try {
@@ -10035,7 +10663,7 @@ var results = new Command("results").description("Read experiment results and ti
10035
10663
  spinner?.fail(`Experiment "${idOrSlug}" not found`);
10036
10664
  fail(this, `Experiment "${idOrSlug}" not found.`, EXIT_CODES.validation);
10037
10665
  }
10038
- const res = await getExperimentResults(creds, exp.id, { currency: opts.currency });
10666
+ const res = opts.explain ? await getExperimentResultsExplain(creds, exp.id, opts) : await getExperimentResults(creds, exp.id, { currency: opts.currency });
10039
10667
  if (!res.ok || !res.data) {
10040
10668
  spinner?.fail("Failed to fetch experiment results");
10041
10669
  fail(
@@ -10049,7 +10677,7 @@ var results = new Command("results").description("Read experiment results and ti
10049
10677
  output(
10050
10678
  this,
10051
10679
  res.data,
10052
- () => printExperimentResults(res.data, opts.currency)
10680
+ () => opts.explain ? printResultsExplain(res.data, Boolean(opts.samples)) : printExperimentResults(res.data, opts.currency)
10053
10681
  );
10054
10682
  } catch (error) {
10055
10683
  spinner?.fail("Failed to fetch experiment results");
@@ -10132,7 +10760,7 @@ function parseHttpDestination(value) {
10132
10760
  function readCreateJson(command, reference) {
10133
10761
  const file = reference.startsWith("@") ? reference.slice(1) : reference;
10134
10762
  try {
10135
- const parsed = JSON.parse(import_node_fs7.default.readFileSync(import_node_path5.default.resolve(process.cwd(), file), "utf8"));
10763
+ const parsed = JSON.parse(import_node_fs8.default.readFileSync(import_node_path6.default.resolve(process.cwd(), file), "utf8"));
10136
10764
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
10137
10765
  throw new Error("expected an object");
10138
10766
  return parsed;
@@ -10168,8 +10796,8 @@ function normalizeNewGoalKey(key) {
10168
10796
  return normalized === "page" ? "page_id" : normalized;
10169
10797
  }
10170
10798
  function validateNewGoal(command, value, body5) {
10171
- const invalid = (reason) => fail(command, `Invalid --goal "new:${value}"; ${reason}`, EXIT_CODES.validation);
10172
- const text = (field) => {
10799
+ const invalid = (reason2) => fail(command, `Invalid --goal "new:${value}"; ${reason2}`, EXIT_CODES.validation);
10800
+ const text2 = (field) => {
10173
10801
  const raw = body5[field];
10174
10802
  return typeof raw === "string" && raw.length > 0 ? raw : void 0;
10175
10803
  };
@@ -10179,12 +10807,12 @@ function validateNewGoal(command, value, body5) {
10179
10807
  key === "capture" || key === "filter" ? `${key} is not expressible inline; create the goal with \`apex goals create\` and pass its id` : `unknown field "${key}"; inline fields are ${INLINE_GOAL_FIELDS.join(", ")}`
10180
10808
  );
10181
10809
  }
10182
- const type = text("type");
10183
- const name = text("name");
10810
+ const type = text2("type");
10811
+ const name = text2("name");
10184
10812
  if (!type || !name) invalid("type and name are required");
10185
10813
  if (!GOAL_TYPES.includes(type)) invalid(`type must be one of ${GOAL_TYPES.join(", ")}`);
10186
10814
  if (name.length > 255) invalid("name must be 255 characters or fewer");
10187
- const measurement = text("measurement") ?? "conversion";
10815
+ const measurement = text2("measurement") ?? "conversion";
10188
10816
  if (!GOAL_MEASUREMENTS.includes(measurement))
10189
10817
  invalid(`measurement must be one of ${GOAL_MEASUREMENTS.join(", ")}`);
10190
10818
  if (body5.count_once !== void 0) {
@@ -10192,21 +10820,21 @@ function validateNewGoal(command, value, body5) {
10192
10820
  if (flag !== "true" && flag !== "false") invalid("count-once must be true or false");
10193
10821
  body5.count_once = flag === "true";
10194
10822
  }
10195
- if (type === "click" && !text("selector") && !text("text_match"))
10823
+ if (type === "click" && !text2("selector") && !text2("text_match"))
10196
10824
  invalid("click goals need selector=<css> or text-match=<text>");
10197
- if (type === "pageview" && !text("page_id") && !text("url_pattern"))
10825
+ if (type === "pageview" && !text2("page_id") && !text2("url_pattern"))
10198
10826
  invalid("pageview goals need page=<page-id> or url-pattern=<pattern>");
10199
- if (type === "custom" && !text("event_name")) invalid("custom goals need event-name=<name>");
10200
- const eventName = text("event_name");
10827
+ if (type === "custom" && !text2("event_name")) invalid("custom goals need event-name=<name>");
10828
+ const eventName = text2("event_name");
10201
10829
  if (eventName && !GOAL_EVENT_NAME.test(eventName))
10202
10830
  invalid("event-name must be 1-120 letters, numbers, dots, colons, underscores, or hyphens");
10203
- if ((type === "custom" || type === "revenue") && text("page_id"))
10831
+ if ((type === "custom" || type === "revenue") && text2("page_id"))
10204
10832
  invalid(`page is not supported for ${type} goals`);
10205
- const pageId = text("page_id");
10833
+ const pageId = text2("page_id");
10206
10834
  if (pageId && !GOAL_UUID.test(pageId))
10207
10835
  invalid("page must be a page UUID (list them with `apex pages list`)");
10208
- const valueProperty = text("value_property");
10209
- const valueUnit = text("value_unit");
10836
+ const valueProperty = text2("value_property");
10837
+ const valueUnit = text2("value_unit");
10210
10838
  if (measurement !== "value" && (valueProperty || valueUnit))
10211
10839
  invalid("value-property and value-unit need measurement=value");
10212
10840
  if (type === "revenue" && valueProperty)
@@ -10730,7 +11358,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
10730
11358
  {
10731
11359
  folder,
10732
11360
  experimentId: exp.id,
10733
- files: res.data.files.map((file) => import_node_path5.default.join(folder, file.path))
11361
+ files: res.data.files.map((file) => import_node_path6.default.join(folder, file.path))
10734
11362
  },
10735
11363
  () => {
10736
11364
  spinner.succeed(`Pulled ${exp.name}`);
@@ -10742,6 +11370,252 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
10742
11370
  throw error;
10743
11371
  }
10744
11372
  });
11373
+ var experimentStatus = new Command("status").description(
11374
+ "Read experiment context, local changes, QA, and handover next steps without writing"
11375
+ ).argument("[folder-or-id]", "Pulled folder, experiment ID, slug, or test key", ".").action(async function(target) {
11376
+ const creds = requireCredentials();
11377
+ const candidate = folderPath(target);
11378
+ const isFolder = import_node_fs8.default.existsSync(candidate) && import_node_fs8.default.statSync(candidate).isDirectory();
11379
+ const hasApexDir = isFolder && import_node_fs8.default.existsSync(import_node_path6.default.join(candidate, ".apex"));
11380
+ if (isFolder && !hasApexDir && target === ".") {
11381
+ fail(this, "This folder is not a pulled experiment.", EXIT_CODES.validation, {
11382
+ code: "validation_failed",
11383
+ next_steps: [
11384
+ {
11385
+ command: "apex experiments status <experiment-id-or-test-key>",
11386
+ why: "Read the saved experiment without a local folder."
11387
+ },
11388
+ {
11389
+ command: "apex experiments pull <experiment-id-or-test-key> --out <folder>",
11390
+ why: "Pull the experiment to compare local files before handover."
11391
+ }
11392
+ ]
11393
+ });
11394
+ }
11395
+ const session = hasApexDir ? readFilesystemSession(candidate) : null;
11396
+ if (session && new URL(session.baseUrl).origin !== new URL(creds.baseUrl).origin) {
11397
+ fail(
11398
+ this,
11399
+ "This folder was pulled from a different base URL. Select that base URL before inspecting it.",
11400
+ EXIT_CODES.validation
11401
+ );
11402
+ }
11403
+ const response = session ? await getExperiment(creds, session.experimentId) : null;
11404
+ if (response && (!response.ok || !response.data)) {
11405
+ throw new CliError(
11406
+ response.error ?? "Failed to read experiment",
11407
+ classifyStatus(response.status),
11408
+ response.errorDetails,
11409
+ apiFailureOf(response)
11410
+ );
11411
+ }
11412
+ const experiment = response?.data ?? await resolveExperiment(target);
11413
+ if (!experiment) fail(this, "Experiment not found.");
11414
+ const snapshot = await getExperimentFilesystem(creds, experiment.id);
11415
+ if (!snapshot.ok || !snapshot.data) {
11416
+ throw new CliError(
11417
+ snapshot.error ?? "Failed to read experiment filesystem",
11418
+ classifyStatus(snapshot.status),
11419
+ snapshot.errorDetails,
11420
+ apiFailureOf(snapshot)
11421
+ );
11422
+ }
11423
+ let versions = null;
11424
+ if (session) {
11425
+ const localFiles = listExperimentFilesystemFiles(candidate, session);
11426
+ const localHashes = new Map(localFiles.map((file) => [file.path, file.sha256]));
11427
+ for (const name of SERVER_OWNED_EXPERIMENT_FILES) {
11428
+ const file = import_node_path6.default.join(candidate, name);
11429
+ if (import_node_fs8.default.existsSync(file)) localHashes.set(name, sha256Text(import_node_fs8.default.readFileSync(file, "utf8")));
11430
+ }
11431
+ const paths = [.../* @__PURE__ */ new Set([...Object.keys(session.files), ...localHashes.keys()])].sort();
11432
+ const files = paths.map((file) => {
11433
+ const local = localHashes.get(file) ?? null;
11434
+ const pulled = session.files[file]?.sha256 ?? null;
11435
+ return {
11436
+ path: file,
11437
+ state: !local ? "deleted" : !pulled ? "added" : local === pulled ? "unchanged" : "modified",
11438
+ local_sha256: local,
11439
+ pulled_sha256: pulled
11440
+ };
11441
+ });
11442
+ const plannedFiles = await rewriteExperimentFilesystemAssets({
11443
+ files: localFiles,
11444
+ folder: candidate,
11445
+ baseUrl: creds.baseUrl,
11446
+ enabled: true,
11447
+ upload: null,
11448
+ assets: session.assets ?? {},
11449
+ credentials: creds
11450
+ });
11451
+ const plan = await planExperimentFilesystem(creds, experiment.id, { files: plannedFiles });
11452
+ if (!plan.ok || !plan.data) {
11453
+ throw new CliError(
11454
+ plan.error ?? "Failed to read saved state",
11455
+ classifyStatus(plan.status),
11456
+ plan.errorDetails,
11457
+ apiFailureOf(plan)
11458
+ );
11459
+ }
11460
+ versions = {
11461
+ files,
11462
+ saved: {
11463
+ would_change: plan.data.changes.length > 0,
11464
+ invalidates_qa: plan.data.invalidatesQa,
11465
+ blocking_issue_count: plan.data.blockingIssues?.filter((issue) => issue.severity === "blocking").length ?? 0,
11466
+ valid: plan.data.ok,
11467
+ errors: plan.data.errors,
11468
+ warnings: plan.data.warnings
11469
+ }
11470
+ };
11471
+ }
11472
+ const qaFile = snapshot.data.files.find((file) => file.path === "qa.json");
11473
+ let qaSnapshot = null;
11474
+ let qaUnreadable = false;
11475
+ if (qaFile) {
11476
+ try {
11477
+ qaSnapshot = JSON.parse(qaFile.content);
11478
+ } catch {
11479
+ qaUnreadable = true;
11480
+ }
11481
+ }
11482
+ const qa3 = {
11483
+ ...summarizeHandoverQa(qaSnapshot),
11484
+ ready_version_id: experiment.ready_version_id ?? null
11485
+ };
11486
+ const shopId = experiment.shop_id ?? creds.resolvedContext?.shopId ?? null;
11487
+ let org = {
11488
+ id: creds.resolvedContext?.orgId ?? null,
11489
+ name: null
11490
+ };
11491
+ let shop = { id: shopId, name: null };
11492
+ const notes = [];
11493
+ if (qaUnreadable) notes.push("The saved qa.json could not be parsed; QA counts are unknown.");
11494
+ if (!session)
11495
+ notes.push(
11496
+ "Versions are unavailable without a pulled folder. Pull the experiment to compare local files."
11497
+ );
11498
+ if (creds.credentialKind === "user") {
11499
+ const context = await getAccountContext(creds);
11500
+ const owner = context.data?.data?.orgs?.find(
11501
+ (item) => item.shops?.some((item2) => item2.id === shopId)
11502
+ );
11503
+ if (owner) {
11504
+ org = { id: owner.id, name: owner.name };
11505
+ shop = { id: shopId, name: owner.shops.find((item) => item.id === shopId)?.name ?? null };
11506
+ } else notes.push("Organization and shop names are unavailable from account context.");
11507
+ } else
11508
+ notes.push(
11509
+ "Shop credentials do not expose account context; organization and shop names are unknown."
11510
+ );
11511
+ const nextSteps = [];
11512
+ const ref = shellQuote(session ? target : experiment.id);
11513
+ if (!session)
11514
+ nextSteps.push({
11515
+ command: `apex experiments pull ${ref} --out <folder>`,
11516
+ why: "Pull files to verify the local version."
11517
+ });
11518
+ if (versions && (versions.files.some((file) => file.state !== "unchanged") || versions.saved.would_change || !versions.saved.valid || versions.saved.blocking_issue_count > 0)) {
11519
+ nextSteps.push(
11520
+ {
11521
+ command: `apex experiments plan ${ref} --json`,
11522
+ why: "Review local changes, saved differences, and blocking issues."
11523
+ },
11524
+ {
11525
+ command: `apex experiments apply ${ref} --json`,
11526
+ why: "Save the reviewed changes after resolving blockers and any QA invalidation acknowledgment."
11527
+ }
11528
+ );
11529
+ }
11530
+ if (qa3.required_done === null || qa3.required_total === null || qa3.required_done < qa3.required_total || qa3.blockers === null || qa3.blockers.length > 0) {
11531
+ nextSteps.push(
11532
+ {
11533
+ command: `apex experiments qa status ${ref} --json`,
11534
+ why: "Inspect missing required checks and evidence before recording QA results. This separate command can create a QA run."
11535
+ },
11536
+ {
11537
+ command: `apex experiments qa check ${ref} --item <item-id> --status pass`,
11538
+ why: "Record each missing required check; attach supporting evidence with apex experiments qa evidence."
11539
+ }
11540
+ );
11541
+ } else if (!qa3.approval_present) {
11542
+ nextSteps.push({
11543
+ command: `apex experiments qa approve ${ref}`,
11544
+ why: "Required QA checks are complete; approve after verifying the saved version."
11545
+ });
11546
+ }
11547
+ const locallyChanged = versions !== null && (versions.files.some((file) => file.state !== "unchanged") || versions.saved.would_change || !versions.saved.valid || versions.saved.blocking_issue_count > 0);
11548
+ const qaComplete = qa3.required_done !== null && qa3.required_total !== null && qa3.required_done === qa3.required_total && qa3.blockers !== null && qa3.blockers.length === 0;
11549
+ const readyVersionKnown = typeof qa3.ready_version_id === "string" && qa3.ready_version_id.length > 0;
11550
+ const approvalCurrent = qa3.approval_present && readyVersionKnown && !locallyChanged;
11551
+ const lifecycleReady = experiment.status === "ready_to_launch";
11552
+ const readyForHandover = lifecycleReady && approvalCurrent && qaComplete;
11553
+ if (approvalCurrent && qaComplete && !lifecycleReady)
11554
+ notes.push(`The experiment status is ${experiment.status}; handover needs ready_to_launch.`);
11555
+ if (qa3.approval_present && !readyVersionKnown && qaComplete && !locallyChanged)
11556
+ nextSteps.push({
11557
+ command: `apex experiments qa approve ${ref}`,
11558
+ why: "The approval is not bound to a saved version; approve again to bind it before handover."
11559
+ });
11560
+ if (readyForHandover)
11561
+ nextSteps.push({
11562
+ command: "launch in the Apex dashboard",
11563
+ why: "QA is approved for the saved version. Starting a test is a human decision."
11564
+ });
11565
+ if (!versions)
11566
+ notes.push(
11567
+ "Without a pulled folder, readiness reflects the saved version only; local edits elsewhere are not visible."
11568
+ );
11569
+ const data = {
11570
+ context: {
11571
+ org,
11572
+ shop,
11573
+ credential_kind: creds.credentialKind ?? "unknown",
11574
+ base_url: creds.baseUrl
11575
+ },
11576
+ experiment: {
11577
+ id: experiment.id,
11578
+ test_key: experiment.test_key ?? null,
11579
+ name: experiment.name,
11580
+ status: experiment.status,
11581
+ dashboard_url: buildDashboardUrl(creds.baseUrl, shopId, experiment.id)
11582
+ },
11583
+ versions,
11584
+ qa: { ...qa3, approval_current: approvalCurrent },
11585
+ next_steps: nextSteps,
11586
+ ready_for_handover: readyForHandover,
11587
+ notes
11588
+ };
11589
+ output(this, data, () => {
11590
+ console.log(
11591
+ `Context: ${org.name ?? org.id ?? "unknown organization"} / ${shop.name ?? shop.id ?? "unknown shop"}
11592
+ Credential: ${data.context.credential_kind}
11593
+ Base URL: ${creds.baseUrl}`
11594
+ );
11595
+ console.log(
11596
+ `Experiment: ${experiment.test_key ?? experiment.id} / ${experiment.name} (${experiment.status})
11597
+ Dashboard: ${data.experiment.dashboard_url ?? "unknown"}`
11598
+ );
11599
+ console.log("Versions:");
11600
+ if (versions) {
11601
+ for (const file of versions.files) console.log(` ${file.state}: ${file.path}`);
11602
+ console.log(
11603
+ ` Saved: would change ${versions.saved.would_change}; invalidates QA ${versions.saved.invalidates_qa}; blocking issues ${versions.saved.blocking_issue_count}`
11604
+ );
11605
+ for (const message of [...versions.saved.errors, ...versions.saved.warnings])
11606
+ console.log(` ${message}`);
11607
+ } else console.log(" unknown (no pulled folder)");
11608
+ console.log(
11609
+ `QA: ${qa3.run_status ?? "unknown"}; required ${qa3.required_done ?? "unknown"}/${qa3.required_total ?? "unknown"}; approval ${qa3.approval_present}; current for this version: ${approvalCurrent}`
11610
+ );
11611
+ for (const blocker of qa3.blockers ?? []) console.log(` Blocker: ${blocker}`);
11612
+ console.log(`Ready for handover: ${readyForHandover ? "yes" : "no"}
11613
+ Next steps:`);
11614
+ for (const step of nextSteps) console.log(` ${step.command}
11615
+ ${step.why}`);
11616
+ for (const note of notes) console.log(` ${note}`);
11617
+ });
11618
+ });
10745
11619
  var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function action14(folderArg) {
10746
11620
  const creds = requireCredentials();
10747
11621
  try {
@@ -10788,7 +11662,10 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
10788
11662
  fail(this, error instanceof Error ? error.message : String(error));
10789
11663
  }
10790
11664
  });
10791
- var applyFilesystem = new Command("apply").option("--sync", "Refresh local authored files with the stored server bytes").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option("--accept-qa-invalidation", "Acknowledge that applying changes invalidates QA approval").addOption(new Option("-f, --force", "Deprecated alias for --accept-qa-invalidation").hideHelp()).option("--no-asset-upload", "Keep inline and local image references unchanged").action(async function action15(folderArg, opts) {
11665
+ var applyFilesystem = new Command("apply").option(
11666
+ "--confirm-targeting-replace",
11667
+ "Confirm that attaching a Page may replace URL targeting authored through the API"
11668
+ ).option("--sync", "Refresh local authored files with the stored server bytes").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option("--accept-qa-invalidation", "Acknowledge that applying changes invalidates QA approval").addOption(new Option("-f, --force", "Deprecated alias for --accept-qa-invalidation").hideHelp()).option("--no-asset-upload", "Keep inline and local image references unchanged").action(async function action15(folderArg, opts) {
10792
11669
  const creds = requireCredentials();
10793
11670
  try {
10794
11671
  const folder = folderPath(folderArg);
@@ -10822,13 +11699,19 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
10822
11699
  if (!shouldUseJson(this)) printFilesystemPlan(planRes.data);
10823
11700
  if (opts.assetUpload === false && planRes.data.blockingIssues?.some((issue) => /inline|hosted asset/i.test(issue.message))) {
10824
11701
  process.stderr.write(
10825
- `Retry with hosted asset upload: apex experiments apply ${folderArg}
11702
+ `Retry with hosted asset upload: apex experiments apply ${shellQuote(folderArg)}
10826
11703
  `
10827
11704
  );
10828
11705
  }
10829
11706
  fail(this, blockingIssueSummary(planRes.data.blockingIssues), EXIT_CODES.validation, {
10830
11707
  errors: planRes.data.errors,
10831
11708
  plan: planRes.data,
11709
+ next_steps: opts.assetUpload === false && planRes.data.blockingIssues?.some((issue) => /inline|hosted asset/i.test(issue.message)) ? [
11710
+ {
11711
+ command: `apex experiments apply ${shellQuote(folderArg)}`,
11712
+ why: "Retry with hosted asset upload."
11713
+ }
11714
+ ] : [],
10832
11715
  requiredAcknowledgment: planRes.data.invalidatesQa ? "accept-qa-invalidation" : null
10833
11716
  });
10834
11717
  }
@@ -10873,7 +11756,31 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
10873
11756
  }
10874
11757
  }
10875
11758
  const spinner = process.stderr.isTTY ? await createSpinner("Applying experiment filesystem...") : null;
10876
- const res = await applyExperimentFilesystem(creds, session.experimentId, { files });
11759
+ let res = await applyExperimentFilesystem(creds, session.experimentId, {
11760
+ files,
11761
+ ...opts.confirmTargetingReplace ? { confirmTargetingReplace: true } : {}
11762
+ });
11763
+ if (!res.ok && res.status === 409 && res.errorDetails?.code === "confirm_targeting_replace_required" && !opts.confirmTargetingReplace) {
11764
+ spinner?.stop();
11765
+ const ok = await confirmApply(
11766
+ "Attaching this Page replaces URL targeting authored through the API. Continue?"
11767
+ );
11768
+ if (ok === null) {
11769
+ confirmationRequired(
11770
+ this,
11771
+ "Attaching this Page replaces URL targeting authored through the API and requires confirmation.",
11772
+ `apex experiments apply ${folderArg} --confirm-targeting-replace`
11773
+ );
11774
+ }
11775
+ if (!ok) {
11776
+ output(this, { aborted: true }, () => console.log("Aborted."));
11777
+ return;
11778
+ }
11779
+ res = await applyExperimentFilesystem(creds, session.experimentId, {
11780
+ files,
11781
+ confirmTargetingReplace: true
11782
+ });
11783
+ }
10877
11784
  if (!res.ok || !res.data?.applied) {
10878
11785
  spinner?.fail("Failed to apply experiment filesystem");
10879
11786
  fail(this, res.error ?? "Apply failed", classifyStatus(res.status), {
@@ -11154,8 +12061,8 @@ var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled
11154
12061
  });
11155
12062
  function parseJsonValue(value) {
11156
12063
  const source = value.startsWith("@") ? value.slice(1) : value;
11157
- const text = value.startsWith("@") || import_node_fs7.default.existsSync(source) ? import_node_fs7.default.readFileSync(import_node_path5.default.resolve(process.cwd(), source), "utf8") : value;
11158
- return JSON.parse(text);
12064
+ const text2 = value.startsWith("@") || import_node_fs8.default.existsSync(source) ? import_node_fs8.default.readFileSync(import_node_path6.default.resolve(process.cwd(), source), "utf8") : value;
12065
+ return JSON.parse(text2);
11159
12066
  }
11160
12067
  function collectFile(value, previous = []) {
11161
12068
  return [...previous, value];
@@ -11207,7 +12114,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
11207
12114
  classifyStatus(current.status),
11208
12115
  current.errorDetails
11209
12116
  );
11210
- for (const record2 of current.data.run.evidenceRecords) previousIds.add(record2.id);
12117
+ for (const record3 of current.data.run.evidenceRecords) previousIds.add(record3.id);
11211
12118
  }
11212
12119
  let response;
11213
12120
  if (opts.job) {
@@ -11226,12 +12133,12 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
11226
12133
  const variationId = resolveEvidenceVariation(experiment, opts.variation);
11227
12134
  const form = new FormData();
11228
12135
  for (const filePath of opts.file) {
11229
- const absolutePath = import_node_path5.default.resolve(process.cwd(), filePath);
11230
- if (!import_node_fs7.default.existsSync(absolutePath)) throw new Error(`Evidence file not found: ${filePath}`);
11231
- const bytes = import_node_fs7.default.readFileSync(absolutePath);
11232
- const extension = import_node_path5.default.extname(absolutePath).toLowerCase();
12136
+ const absolutePath = import_node_path6.default.resolve(process.cwd(), filePath);
12137
+ if (!import_node_fs8.default.existsSync(absolutePath)) throw new Error(`Evidence file not found: ${filePath}`);
12138
+ const bytes = import_node_fs8.default.readFileSync(absolutePath);
12139
+ const extension = import_node_path6.default.extname(absolutePath).toLowerCase();
11233
12140
  const mimeType = extension === ".png" ? "image/png" : extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".webp" ? "image/webp" : "application/octet-stream";
11234
- form.append("files", new File([bytes], import_node_path5.default.basename(absolutePath), { type: mimeType }));
12141
+ form.append("files", new File([bytes], import_node_path6.default.basename(absolutePath), { type: mimeType }));
11235
12142
  }
11236
12143
  if (opts.label) form.append("label", opts.label);
11237
12144
  const device = normalizeEvidenceDevice(opts.device);
@@ -11252,7 +12159,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
11252
12159
  const run3 = response.data.run;
11253
12160
  const data = opts.summary ? qaSummary(
11254
12161
  run3,
11255
- run3.evidenceRecords.filter((record2) => !previousIds.has(record2.id))
12162
+ run3.evidenceRecords.filter((record3) => !previousIds.has(record3.id))
11256
12163
  ) : response.data;
11257
12164
  output(this, data, () => {
11258
12165
  if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
@@ -11278,8 +12185,8 @@ function shellQuote(value) {
11278
12185
  if (value.length > 0 && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
11279
12186
  return `'${value.replaceAll("'", "'\\''")}'`;
11280
12187
  }
11281
- function isTransientResumeStatus(status2) {
11282
- return status2 === 429 || status2 !== void 0 && status2 >= 500;
12188
+ function isTransientResumeStatus(status3) {
12189
+ return status3 === 429 || status3 !== void 0 && status3 >= 500;
11283
12190
  }
11284
12191
  function screenshotTimeoutMessage(job, experimentRef, out, seconds, flags = {}) {
11285
12192
  const progress = job.progress ? ` Last progress: ${job.progress.captured}/${job.progress.total} captured.` : "";
@@ -11563,8 +12470,8 @@ var screenshots = new Command("screenshots").description(
11563
12470
  }
11564
12471
  if (job.status === "failed")
11565
12472
  fail(this, `Screenshot job failed: ${job.error ?? "unknown error"}`);
11566
- const outputDir = import_node_path5.default.resolve(process.cwd(), opts.out);
11567
- import_node_fs7.default.mkdirSync(outputDir, { recursive: true });
12473
+ const outputDir = import_node_path6.default.resolve(process.cwd(), opts.out);
12474
+ import_node_fs8.default.mkdirSync(outputDir, { recursive: true });
11568
12475
  const rows = [];
11569
12476
  const errors = [];
11570
12477
  const usedFilenames = /* @__PURE__ */ new Set();
@@ -11622,8 +12529,8 @@ var screenshots = new Command("screenshots").description(
11622
12529
  try {
11623
12530
  const response = await fetch(url);
11624
12531
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
11625
- await import_node_fs7.default.promises.writeFile(
11626
- import_node_path5.default.join(outputDir, filename),
12532
+ await import_node_fs8.default.promises.writeFile(
12533
+ import_node_path6.default.join(outputDir, filename),
11627
12534
  Buffer.from(await response.arrayBuffer())
11628
12535
  );
11629
12536
  workerResults.push({ index, row: [variation.name, device, filename] });
@@ -11650,7 +12557,7 @@ var screenshots = new Command("screenshots").description(
11650
12557
  printTable(["VARIATION", "DEVICE", "FILE"], rows, [24, 20, 48]);
11651
12558
  });
11652
12559
  });
11653
- var experimentsCommand = new Command("experiments").alias("exp").description("Manage experiments").addCommand(list).addCommand(get).addCommand(results).addCommand(update).addCommand(create).addCommand(attachIdea).addCommand(detachIdea).addCommand(pause).addCommand(complete).addCommand(del).addCommand(pullFilesystem).addCommand(planFilesystem).addCommand(applyFilesystem).addCommand(screenshots).addCommand(qa).addHelpText(
12560
+ var experimentsCommand = new Command("experiments").alias("exp").description("Manage experiments").addCommand(list).addCommand(get).addCommand(results).addCommand(update).addCommand(create).addCommand(attachIdea).addCommand(detachIdea).addCommand(pause).addCommand(complete).addCommand(del).addCommand(pullFilesystem).addCommand(experimentStatus).addCommand(planFilesystem).addCommand(applyFilesystem).addCommand(screenshots).addCommand(qa).addHelpText(
11654
12561
  "after",
11655
12562
  `
11656
12563
  Examples:
@@ -11663,9 +12570,9 @@ Examples:
11663
12570
 
11664
12571
  // src/commands/push.ts
11665
12572
  init_source();
11666
- var import_node_fs8 = __toESM(require("node:fs"));
11667
- var import_node_os6 = __toESM(require("node:os"));
11668
- var import_node_path6 = __toESM(require("node:path"));
12573
+ var import_node_fs9 = __toESM(require("node:fs"));
12574
+ var import_node_os7 = __toESM(require("node:os"));
12575
+ var import_node_path7 = __toESM(require("node:path"));
11669
12576
  var import_node_url2 = require("node:url");
11670
12577
  function toApiBody(exp) {
11671
12578
  return {
@@ -11706,7 +12613,7 @@ async function rewriteExperimentAssets(exp, rewrite, baseDir) {
11706
12613
  };
11707
12614
  }
11708
12615
  async function importConfigModule(absPath) {
11709
- const ext = import_node_path6.default.extname(absPath).toLowerCase();
12616
+ const ext = import_node_path7.default.extname(absPath).toLowerCase();
11710
12617
  if (ext === ".js" || ext === ".mjs" || ext === ".cjs") {
11711
12618
  return await import((0, import_node_url2.pathToFileURL)(absPath).href);
11712
12619
  }
@@ -11720,8 +12627,8 @@ async function importConfigModule(absPath) {
11720
12627
  ` + (err instanceof Error ? err.message : String(err))
11721
12628
  );
11722
12629
  }
11723
- const tmpDir = import_node_fs8.default.mkdtempSync(import_node_path6.default.join(import_node_os6.default.tmpdir(), "drip-config-"));
11724
- const outFile = import_node_path6.default.join(tmpDir, "config.mjs");
12630
+ const tmpDir = import_node_fs9.default.mkdtempSync(import_node_path7.default.join(import_node_os7.default.tmpdir(), "drip-config-"));
12631
+ const outFile = import_node_path7.default.join(tmpDir, "config.mjs");
11725
12632
  try {
11726
12633
  await esbuild.build({
11727
12634
  entryPoints: [absPath],
@@ -11737,7 +12644,7 @@ async function importConfigModule(absPath) {
11737
12644
  return await import(href);
11738
12645
  } finally {
11739
12646
  try {
11740
- import_node_fs8.default.rmSync(tmpDir, { recursive: true, force: true });
12647
+ import_node_fs9.default.rmSync(tmpDir, { recursive: true, force: true });
11741
12648
  } catch {
11742
12649
  }
11743
12650
  }
@@ -11758,7 +12665,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
11758
12665
  );
11759
12666
  }
11760
12667
  const spinner = process.stderr.isTTY ? await createSpinner(`Loading config from ${source_default.cyan(configPath)}...`) : null;
11761
- const absoluteConfigPath = import_node_path6.default.resolve(configPath);
12668
+ const absoluteConfigPath = import_node_path7.default.resolve(configPath);
11762
12669
  let experiments;
11763
12670
  try {
11764
12671
  const mod = await importConfigModule(absoluteConfigPath);
@@ -11825,7 +12732,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
11825
12732
  return;
11826
12733
  }
11827
12734
  const assetRewriter = createAssetReferenceRewriter({
11828
- baseDir: import_node_path6.default.dirname(absoluteConfigPath),
12735
+ baseDir: import_node_path7.default.dirname(absoluteConfigPath),
11829
12736
  baseUrl: creds.baseUrl,
11830
12737
  enabled: opts.assetUpload !== false,
11831
12738
  upload: async (file) => {
@@ -11855,7 +12762,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
11855
12762
  let body5;
11856
12763
  try {
11857
12764
  body5 = toApiBody(
11858
- await rewriteExperimentAssets(exp, assetRewriter, import_node_path6.default.dirname(absoluteConfigPath))
12765
+ await rewriteExperimentAssets(exp, assetRewriter, import_node_path7.default.dirname(absoluteConfigPath))
11859
12766
  );
11860
12767
  } catch (error) {
11861
12768
  const message = error instanceof Error ? error.message : String(error);
@@ -11943,6 +12850,14 @@ var previewCommand = new Command("preview").description(
11943
12850
  const data = created.data;
11944
12851
  output(previewCommand, data, () => {
11945
12852
  console.log(source_default.cyan(data.preview_url));
12853
+ if (data.preview_target) {
12854
+ console.log(`Chosen page: ${data.preview_target.url} (${data.preview_target.source})`);
12855
+ if (!data.preview_target.matches_targeting) {
12856
+ console.log(
12857
+ `The test will not show on this page: ${data.preview_target.reason ?? "The page does not match URL targeting."}`
12858
+ );
12859
+ }
12860
+ }
11946
12861
  console.log(
11947
12862
  data.qa_run.created ? `QA run ${data.qa_run.id} created` : `QA run ${data.qa_run.id} refreshed (${data.qa_run.automated_evidence_count} automated evidence records)`
11948
12863
  );
@@ -11968,7 +12883,7 @@ var previewCommand = new Command("preview").description(
11968
12883
  const spinner = await createSpinner("Fetching experiment...");
11969
12884
  let exp;
11970
12885
  if (/^[0-9a-f-]{36}$/i.test(idOrSlug)) {
11971
- const res = await getExperiment(creds, idOrSlug);
12886
+ const res = await getExperiment(creds, idOrSlug, !opts?.storeUrl);
11972
12887
  if (!res.ok) {
11973
12888
  spinner.fail(`Experiment "${idOrSlug}" not found`);
11974
12889
  fail(
@@ -11981,7 +12896,7 @@ var previewCommand = new Command("preview").description(
11981
12896
  exp = res.data;
11982
12897
  } else {
11983
12898
  const slug = idOrSlug.startsWith("slug:") ? idOrSlug.slice(5) : idOrSlug;
11984
- const res = await getExperimentBySlug(creds, slug);
12899
+ const res = await getExperimentBySlug(creds, slug, !opts?.storeUrl);
11985
12900
  if (!res.ok) {
11986
12901
  spinner.fail(`Experiment "${slug}" not found`);
11987
12902
  fail(
@@ -12016,7 +12931,7 @@ var previewCommand = new Command("preview").description(
12016
12931
  variation = exp.variations.find((v) => !v.is_control) ?? exp.variations[0];
12017
12932
  }
12018
12933
  spinner.stop();
12019
- const storeUrl = opts?.storeUrl ?? exp.store_url;
12934
+ const storeUrl = opts?.storeUrl ?? exp.preview_target?.url ?? exp.store_url;
12020
12935
  if (!storeUrl) {
12021
12936
  fail(
12022
12937
  previewCommand,
@@ -12034,11 +12949,24 @@ var previewCommand = new Command("preview").description(
12034
12949
  }
12035
12950
  output(
12036
12951
  previewCommand,
12037
- { experimentId: exp.id, variationId: variation.id, previewUrl: previewUrl.toString() },
12952
+ {
12953
+ experimentId: exp.id,
12954
+ variationId: variation.id,
12955
+ previewUrl: previewUrl.toString(),
12956
+ ...!opts?.storeUrl && exp.preview_target ? { previewTarget: exp.preview_target } : {}
12957
+ },
12038
12958
  () => {
12039
12959
  console.log();
12040
12960
  console.log(source_default.bold(`Preview: ${exp.name}`));
12041
12961
  console.log(`Variation: ${variation.name}${variation.is_control ? " (control)" : ""}`);
12962
+ if (!opts?.storeUrl && exp.preview_target) {
12963
+ console.log(`Chosen page: ${exp.preview_target.url} (${exp.preview_target.source})`);
12964
+ if (!exp.preview_target.matches_targeting) {
12965
+ console.log(
12966
+ `The test will not show on this page: ${exp.preview_target.reason ?? "The page does not match URL targeting."}`
12967
+ );
12968
+ }
12969
+ }
12042
12970
  console.log();
12043
12971
  console.log(source_default.cyan(previewUrl.toString()));
12044
12972
  console.log();
@@ -12058,25 +12986,25 @@ var previewCommand = new Command("preview").description(
12058
12986
 
12059
12987
  // src/commands/harness.ts
12060
12988
  init_source();
12061
- var import_node_fs9 = __toESM(require("node:fs"));
12062
- var import_node_path7 = __toESM(require("node:path"));
12989
+ var import_node_fs10 = __toESM(require("node:fs"));
12990
+ var import_node_path8 = __toESM(require("node:path"));
12063
12991
  var import_node_child_process6 = require("node:child_process");
12064
12992
  function slugify(value, fallback2 = "variation") {
12065
12993
  const slug = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
12066
12994
  return slug || fallback2;
12067
12995
  }
12068
12996
  function readJson(filePath) {
12069
- if (!import_node_fs9.default.existsSync(filePath)) return null;
12070
- return JSON.parse(import_node_fs9.default.readFileSync(filePath, "utf8"));
12997
+ if (!import_node_fs10.default.existsSync(filePath)) return null;
12998
+ return JSON.parse(import_node_fs10.default.readFileSync(filePath, "utf8"));
12071
12999
  }
12072
13000
  function resolveFolder(folder) {
12073
- return import_node_path7.default.resolve(process.cwd(), folder);
13001
+ return import_node_path8.default.resolve(process.cwd(), folder);
12074
13002
  }
12075
13003
  function experimentConfigPath(folder) {
12076
- return import_node_path7.default.join(folder, "experiment.config.json");
13004
+ return import_node_path8.default.join(folder, "experiment.config.json");
12077
13005
  }
12078
13006
  function apexConfigPath(folder) {
12079
- return import_node_path7.default.join(folder, "apex.harness.json");
13007
+ return import_node_path8.default.join(folder, "apex.harness.json");
12080
13008
  }
12081
13009
  function loadHarnessConfig(folder) {
12082
13010
  const experiment = readJson(experimentConfigPath(folder)) ?? {};
@@ -12085,8 +13013,8 @@ function loadHarnessConfig(folder) {
12085
13013
  const targetOrigin = experiment.targetOrigin?.replace(/\/+$/, "") ?? "";
12086
13014
  const defaultPath = experiment.defaultPath ?? "/";
12087
13015
  const targetUrl = targetOrigin ? new URL(defaultPath, targetOrigin).toString() : "";
12088
- const id = experiment.id ?? import_node_path7.default.basename(import_node_path7.default.dirname(folder));
12089
- const variation = import_node_path7.default.basename(folder);
13016
+ const id = experiment.id ?? import_node_path8.default.basename(import_node_path8.default.dirname(folder));
13017
+ const variation = import_node_path8.default.basename(folder);
12090
13018
  return {
12091
13019
  experiment,
12092
13020
  apex: {
@@ -12121,37 +13049,37 @@ function runAbHarness(args, cwd) {
12121
13049
  function artifactPaths(folder, experiment) {
12122
13050
  const variant = experiment.variant;
12123
13051
  if (variant?.endsWith(".bundle.js")) {
12124
- const jsPath = import_node_path7.default.join(folder, variant.replace(/\.bundle\.js$/, ".js"));
12125
- const cssPath = import_node_path7.default.join(folder, variant.replace(/\.bundle\.js$/, ".css"));
12126
- return { jsPath, cssPath: import_node_fs9.default.existsSync(cssPath) ? cssPath : null };
13052
+ const jsPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".js"));
13053
+ const cssPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".css"));
13054
+ return { jsPath, cssPath: import_node_fs10.default.existsSync(cssPath) ? cssPath : null };
12127
13055
  }
12128
- const buildDir = import_node_path7.default.join(folder, "build");
12129
- const files = import_node_fs9.default.existsSync(buildDir) ? import_node_fs9.default.readdirSync(buildDir) : [];
13056
+ const buildDir = import_node_path8.default.join(folder, "build");
13057
+ const files = import_node_fs10.default.existsSync(buildDir) ? import_node_fs10.default.readdirSync(buildDir) : [];
12130
13058
  const js = files.find(
12131
13059
  (file) => file.endsWith(".js") && !file.endsWith(".bundle.js") && !file.endsWith(".min.js")
12132
13060
  );
12133
13061
  const css = files.find((file) => file.endsWith(".css") && !file.endsWith(".min.css"));
12134
13062
  if (!js) throw new Error("Could not find harness JS artifact in build/");
12135
13063
  return {
12136
- jsPath: import_node_path7.default.join(buildDir, js),
12137
- cssPath: css ? import_node_path7.default.join(buildDir, css) : null
13064
+ jsPath: import_node_path8.default.join(buildDir, js),
13065
+ cssPath: css ? import_node_path8.default.join(buildDir, css) : null
12138
13066
  };
12139
13067
  }
12140
13068
  async function uploadVersion(params) {
12141
13069
  const creds = requireCredentials();
12142
13070
  const { jsPath, cssPath } = artifactPaths(params.folder, params.experiment);
12143
- if (!import_node_fs9.default.existsSync(jsPath)) throw new Error(`Missing JS artifact: ${jsPath}`);
13071
+ if (!import_node_fs10.default.existsSync(jsPath)) throw new Error(`Missing JS artifact: ${jsPath}`);
12144
13072
  const form = new FormData();
12145
13073
  form.append(
12146
13074
  "js",
12147
- new Blob([import_node_fs9.default.readFileSync(jsPath)], { type: "application/javascript" }),
12148
- import_node_path7.default.basename(jsPath)
13075
+ new Blob([import_node_fs10.default.readFileSync(jsPath)], { type: "application/javascript" }),
13076
+ import_node_path8.default.basename(jsPath)
12149
13077
  );
12150
- if (cssPath && import_node_fs9.default.existsSync(cssPath) && import_node_fs9.default.statSync(cssPath).size > 0) {
13078
+ if (cssPath && import_node_fs10.default.existsSync(cssPath) && import_node_fs10.default.statSync(cssPath).size > 0) {
12151
13079
  form.append(
12152
13080
  "css",
12153
- new Blob([import_node_fs9.default.readFileSync(cssPath)], { type: "text/css" }),
12154
- import_node_path7.default.basename(cssPath)
13081
+ new Blob([import_node_fs10.default.readFileSync(cssPath)], { type: "text/css" }),
13082
+ import_node_path8.default.basename(cssPath)
12155
13083
  );
12156
13084
  }
12157
13085
  form.append(
@@ -12160,8 +13088,8 @@ async function uploadVersion(params) {
12160
13088
  ...params.apex,
12161
13089
  resetJs: cleanupResetJs(params.experiment),
12162
13090
  builtAt: (/* @__PURE__ */ new Date()).toISOString(),
12163
- jsPath: import_node_path7.default.relative(params.folder, jsPath),
12164
- cssPath: cssPath ? import_node_path7.default.relative(params.folder, cssPath) : null
13091
+ jsPath: import_node_path8.default.relative(params.folder, jsPath),
13092
+ cssPath: cssPath ? import_node_path8.default.relative(params.folder, cssPath) : null
12165
13093
  })
12166
13094
  );
12167
13095
  const res = await uploadHarnessVersion(creds, params.session.sessionId, form);
@@ -12202,11 +13130,11 @@ var harnessCommand = new Command("harness").description(
12202
13130
  harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action(function action24(folderArg, opts) {
12203
13131
  const folder = resolveFolder(folderArg);
12204
13132
  const targetPath = apexConfigPath(folder);
12205
- if (import_node_fs9.default.existsSync(targetPath) && !opts.force) {
13133
+ if (import_node_fs10.default.existsSync(targetPath) && !opts.force) {
12206
13134
  fail(this, `${targetPath} already exists. Use --force to overwrite.`, EXIT_CODES.conflict);
12207
13135
  }
12208
13136
  const { apex } = loadHarnessConfig(folder);
12209
- import_node_fs9.default.writeFileSync(targetPath, `${JSON.stringify(apex, null, 2)}
13137
+ import_node_fs10.default.writeFileSync(targetPath, `${JSON.stringify(apex, null, 2)}
12210
13138
  `, "utf8");
12211
13139
  output(
12212
13140
  this,
@@ -12272,7 +13200,7 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
12272
13200
  }
12273
13201
  };
12274
13202
  let debounce = null;
12275
- const watcher = import_node_fs9.default.watch(folder, { recursive: true }, (_event, filename) => {
13203
+ const watcher = import_node_fs10.default.watch(folder, { recursive: true }, (_event, filename) => {
12276
13204
  const name = String(filename ?? "");
12277
13205
  if (!name || name.startsWith("build/") || name.includes("/build/")) return;
12278
13206
  if (debounce) clearTimeout(debounce);
@@ -12314,9 +13242,9 @@ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experime
12314
13242
 
12315
13243
  // src/commands/operator.ts
12316
13244
  init_source();
12317
- var import_node_crypto5 = __toESM(require("node:crypto"));
12318
- var import_node_fs10 = __toESM(require("node:fs"));
12319
- var import_node_path8 = __toESM(require("node:path"));
13245
+ var import_node_crypto6 = __toESM(require("node:crypto"));
13246
+ var import_node_fs11 = __toESM(require("node:fs"));
13247
+ var import_node_path9 = __toESM(require("node:path"));
12320
13248
  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)$/;
12321
13249
  function workspaceIssueFile(field, evidence) {
12322
13250
  if (field === "custom_js" || field === "reset_js") return "src/index.js";
@@ -12360,7 +13288,7 @@ function formatWorkspaceBlockingIssues(value) {
12360
13288
  }).join("\n");
12361
13289
  }
12362
13290
  function sha256Text2(value) {
12363
- return import_node_crypto5.default.createHash("sha256").update(value).digest("hex");
13291
+ return import_node_crypto6.default.createHash("sha256").update(value).digest("hex");
12364
13292
  }
12365
13293
  function byteLength(value) {
12366
13294
  return Buffer.byteLength(value, "utf8");
@@ -12370,40 +13298,60 @@ function slugify2(value, fallback2 = "operator") {
12370
13298
  return slug || fallback2;
12371
13299
  }
12372
13300
  function folderPath2(folder) {
12373
- return import_node_path8.default.resolve(process.cwd(), folder);
13301
+ return import_node_path9.default.resolve(process.cwd(), folder);
12374
13302
  }
12375
13303
  function apexDir(folder) {
12376
- return import_node_path8.default.join(folder, ".apex");
13304
+ return import_node_path9.default.join(folder, ".apex");
12377
13305
  }
12378
13306
  function sessionPath(folder) {
12379
- return import_node_path8.default.join(apexDir(folder), "session.json");
13307
+ return import_node_path9.default.join(apexDir(folder), "session.json");
12380
13308
  }
12381
13309
  function readJsonFile(filePath) {
12382
- if (!import_node_fs10.default.existsSync(filePath)) return null;
12383
- return JSON.parse(import_node_fs10.default.readFileSync(filePath, "utf8"));
13310
+ if (!import_node_fs11.default.existsSync(filePath)) return null;
13311
+ return JSON.parse(import_node_fs11.default.readFileSync(filePath, "utf8"));
12384
13312
  }
12385
13313
  function writeJsonFile(filePath, value) {
12386
- import_node_fs10.default.mkdirSync(import_node_path8.default.dirname(filePath), { recursive: true });
12387
- import_node_fs10.default.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
13314
+ import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(filePath), { recursive: true });
13315
+ import_node_fs11.default.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
12388
13316
  `, "utf8");
12389
13317
  }
12390
13318
  function readSession(folder) {
12391
13319
  const session = readJsonFile(sessionPath(folder));
12392
13320
  if (!session) {
12393
- const filesystemMarker = import_node_path8.default.join(apexDir(folder), "experiment-filesystem.json");
12394
- if (import_node_fs10.default.existsSync(filesystemMarker)) {
12395
- throw new Error(
12396
- "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."
13321
+ const filesystemMarker = import_node_path9.default.join(apexDir(folder), "experiment-filesystem.json");
13322
+ if (import_node_fs11.default.existsSync(filesystemMarker)) {
13323
+ throw new CliError(
13324
+ "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.",
13325
+ EXIT_CODES.general,
13326
+ {
13327
+ code: "validation_failed",
13328
+ next_steps: [
13329
+ { command: "apex experiments plan", why: "Use the command for this folder format." },
13330
+ {
13331
+ command: "apex workspace checkout",
13332
+ why: "Create the required folder in a different directory."
13333
+ }
13334
+ ]
13335
+ }
12397
13336
  );
12398
13337
  }
12399
- throw new Error(`Missing ${sessionPath(folder)}. Run apex workspace checkout first.`);
13338
+ throw new CliError(
13339
+ `Missing ${sessionPath(folder)}. Run apex workspace checkout first.`,
13340
+ EXIT_CODES.general,
13341
+ {
13342
+ code: "local_file_error",
13343
+ next_steps: [
13344
+ { command: "apex workspace checkout", why: "Create the missing local folder metadata." }
13345
+ ]
13346
+ }
13347
+ );
12400
13348
  }
12401
13349
  return session;
12402
13350
  }
12403
13351
  function writeSession(folder, session) {
12404
- import_node_fs10.default.mkdirSync(apexDir(folder), { recursive: true });
12405
- import_node_fs10.default.writeFileSync(
12406
- import_node_path8.default.join(apexDir(folder), ".gitignore"),
13352
+ import_node_fs11.default.mkdirSync(apexDir(folder), { recursive: true });
13353
+ import_node_fs11.default.writeFileSync(
13354
+ import_node_path9.default.join(apexDir(folder), ".gitignore"),
12407
13355
  "session.json\nharness-session.json\n",
12408
13356
  "utf8"
12409
13357
  );
@@ -12427,15 +13375,15 @@ function listLocalWorkspaceFiles(folder) {
12427
13375
  "brand.md"
12428
13376
  ];
12429
13377
  function visit2(dir) {
12430
- if (!import_node_fs10.default.existsSync(dir)) return;
12431
- for (const entry of import_node_fs10.default.readdirSync(dir, { withFileTypes: true })) {
12432
- const absPath = import_node_path8.default.join(dir, entry.name);
13378
+ if (!import_node_fs11.default.existsSync(dir)) return;
13379
+ for (const entry of import_node_fs11.default.readdirSync(dir, { withFileTypes: true })) {
13380
+ const absPath = import_node_path9.default.join(dir, entry.name);
12433
13381
  if (entry.isDirectory()) {
12434
13382
  visit2(absPath);
12435
13383
  continue;
12436
13384
  }
12437
- const relativePath = validateCanonicalPath(import_node_path8.default.relative(folder, absPath));
12438
- const content = import_node_fs10.default.readFileSync(absPath, "utf8");
13385
+ const relativePath = validateCanonicalPath(import_node_path9.default.relative(folder, absPath));
13386
+ const content = import_node_fs11.default.readFileSync(absPath, "utf8");
12439
13387
  files.push({
12440
13388
  path: relativePath,
12441
13389
  absPath,
@@ -12445,11 +13393,11 @@ function listLocalWorkspaceFiles(folder) {
12445
13393
  });
12446
13394
  }
12447
13395
  }
12448
- for (const root of roots) visit2(import_node_path8.default.join(folder, root));
13396
+ for (const root of roots) visit2(import_node_path9.default.join(folder, root));
12449
13397
  for (const file of topLevel) {
12450
- const absPath = import_node_path8.default.join(folder, file);
12451
- if (!import_node_fs10.default.existsSync(absPath)) continue;
12452
- const content = import_node_fs10.default.readFileSync(absPath, "utf8");
13398
+ const absPath = import_node_path9.default.join(folder, file);
13399
+ if (!import_node_fs11.default.existsSync(absPath)) continue;
13400
+ const content = import_node_fs11.default.readFileSync(absPath, "utf8");
12453
13401
  files.push({
12454
13402
  path: file,
12455
13403
  absPath,
@@ -12473,7 +13421,7 @@ function validateJsonFiles(files) {
12473
13421
  }
12474
13422
  }
12475
13423
  function readExperimentConfig(folder) {
12476
- return readJsonFile(import_node_path8.default.join(folder, "experiment.config.json")) ?? {};
13424
+ return readJsonFile(import_node_path9.default.join(folder, "experiment.config.json")) ?? {};
12477
13425
  }
12478
13426
  function resolveVariation(exp, variationArg) {
12479
13427
  const variations = exp.variations ?? [];
@@ -12490,9 +13438,9 @@ function resolveVariation(exp, variationArg) {
12490
13438
  function writeWorkspaceFiles(folder, workspace) {
12491
13439
  for (const file of workspace.files) {
12492
13440
  const relativePath = validateCanonicalPath(file.path);
12493
- const absPath = import_node_path8.default.join(folder, relativePath);
12494
- import_node_fs10.default.mkdirSync(import_node_path8.default.dirname(absPath), { recursive: true });
12495
- import_node_fs10.default.writeFileSync(absPath, file.content, "utf8");
13441
+ const absPath = import_node_path9.default.join(folder, relativePath);
13442
+ import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(absPath), { recursive: true });
13443
+ import_node_fs11.default.writeFileSync(absPath, file.content, "utf8");
12496
13444
  }
12497
13445
  }
12498
13446
  function sessionFromWorkspace(input) {
@@ -12561,10 +13509,10 @@ async function uploadCurrentArtifactVersion(folder, sessionId) {
12561
13509
  form.append(
12562
13510
  "js",
12563
13511
  new Blob([js.content], { type: "application/javascript" }),
12564
- import_node_path8.default.basename(js.path)
13512
+ import_node_path9.default.basename(js.path)
12565
13513
  );
12566
13514
  if (css && css.content.trim()) {
12567
- form.append("css", new Blob([css.content], { type: "text/css" }), import_node_path8.default.basename(css.path));
13515
+ form.append("css", new Blob([css.content], { type: "text/css" }), import_node_path9.default.basename(css.path));
12568
13516
  }
12569
13517
  form.append("manifest", JSON.stringify(manifest));
12570
13518
  const res = await uploadHarnessVersion(creds, sessionId, form);
@@ -12683,7 +13631,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
12683
13631
  classifyStatus(workspace.status),
12684
13632
  workspace.errorDetails
12685
13633
  );
12686
- import_node_fs10.default.mkdirSync(folder, { recursive: true });
13634
+ import_node_fs11.default.mkdirSync(folder, { recursive: true });
12687
13635
  writeWorkspaceFiles(folder, workspace.data);
12688
13636
  writeSession(
12689
13637
  folder,
@@ -12740,6 +13688,7 @@ operatorCommand.command("status").argument("[folder]", "Operator workspace folde
12740
13688
  }
12741
13689
  );
12742
13690
  } catch (error) {
13691
+ if (error instanceof CliError) throw error;
12743
13692
  fail(this, error instanceof Error ? error.message : String(error));
12744
13693
  }
12745
13694
  });
@@ -12749,8 +13698,8 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
12749
13698
  const session = readSession(folder);
12750
13699
  const harnessSession = await createExactHarnessSession(folder, session);
12751
13700
  const version = await uploadCurrentArtifactVersion(folder, harnessSession.sessionId);
12752
- import_node_fs10.default.mkdirSync(apexDir(folder), { recursive: true });
12753
- writeJsonFile(import_node_path8.default.join(apexDir(folder), "harness-session.json"), {
13701
+ import_node_fs11.default.mkdirSync(apexDir(folder), { recursive: true });
13702
+ writeJsonFile(import_node_path9.default.join(apexDir(folder), "harness-session.json"), {
12754
13703
  sessionId: harnessSession.sessionId,
12755
13704
  experimentId: harnessSession.experimentId,
12756
13705
  variationId: harnessSession.variationId,
@@ -12805,7 +13754,7 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
12805
13754
  }
12806
13755
  };
12807
13756
  let timer = null;
12808
- const watcher = import_node_fs10.default.watch(folder, { recursive: true }, (_event, filename) => {
13757
+ const watcher = import_node_fs11.default.watch(folder, { recursive: true }, (_event, filename) => {
12809
13758
  const name = String(filename ?? "");
12810
13759
  if (!name || name.startsWith(".apex/") || name.includes("/.apex/")) return;
12811
13760
  if (timer) clearTimeout(timer);
@@ -12840,13 +13789,13 @@ operatorCommand.command("publish").argument("[folder]", "Operator workspace fold
12840
13789
 
12841
13790
  // src/commands/roadmap.ts
12842
13791
  init_source();
12843
- var import_node_fs11 = __toESM(require("node:fs"));
12844
- var import_node_path9 = __toESM(require("node:path"));
13792
+ var import_node_fs12 = __toESM(require("node:fs"));
13793
+ var import_node_path10 = __toESM(require("node:path"));
12845
13794
  var LEGACY_OUTPUT_REMOVAL_VERSION = "0.7.0";
12846
13795
  function truncate(value, max) {
12847
- const text = typeof value === "string" ? value : value == null ? "" : String(value);
12848
- if (text.length <= max) return text;
12849
- return `${text.slice(0, Math.max(0, max - 3))}...`;
13796
+ const text2 = typeof value === "string" ? value : value == null ? "" : String(value);
13797
+ if (text2.length <= max) return text2;
13798
+ return `${text2.slice(0, Math.max(0, max - 3))}...`;
12850
13799
  }
12851
13800
  function score(value) {
12852
13801
  if (typeof value !== "number" || !Number.isFinite(value)) return "-";
@@ -12951,9 +13900,9 @@ var handoff = new Command("handoff").description(
12951
13900
  `;
12952
13901
  const outputData = opts.instructions ? { roadmapItemId: id, instructions: String(payload.instructions ?? "") } : payload;
12953
13902
  if (opts.out || opts.output) {
12954
- const target = import_node_path9.default.resolve(process.cwd(), opts.out ?? opts.output);
12955
- import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(target), { recursive: true });
12956
- import_node_fs11.default.writeFileSync(target, rendered, "utf8");
13903
+ const target = import_node_path10.default.resolve(process.cwd(), opts.out ?? opts.output);
13904
+ import_node_fs12.default.mkdirSync(import_node_path10.default.dirname(target), { recursive: true });
13905
+ import_node_fs12.default.writeFileSync(target, rendered, "utf8");
12957
13906
  output(this, { path: target, roadmapItemId: id }, () => printSuccess(`Wrote ${target}`));
12958
13907
  return;
12959
13908
  }
@@ -12967,8 +13916,8 @@ roadmapCommand.addCommand(handoff);
12967
13916
 
12968
13917
  // src/commands/tracking.ts
12969
13918
  init_source();
12970
- var import_node_fs12 = __toESM(require("node:fs"));
12971
- var import_node_path10 = __toESM(require("node:path"));
13919
+ var import_node_fs13 = __toESM(require("node:fs"));
13920
+ var import_node_path11 = __toESM(require("node:path"));
12972
13921
  function normalizeUrl(input) {
12973
13922
  const value = input.trim();
12974
13923
  if (!value) throw new Error("URL cannot be empty");
@@ -13014,17 +13963,17 @@ function commonCookieDomain(hostnames) {
13014
13963
  return suffix && suffixes.every((candidate) => candidate === suffix) ? `.${suffix}` : null;
13015
13964
  }
13016
13965
  function defaultManifestPath() {
13017
- return import_node_path10.default.resolve(process.cwd(), ".apex", "tracking-manifest.json");
13966
+ return import_node_path11.default.resolve(process.cwd(), ".apex", "tracking-manifest.json");
13018
13967
  }
13019
13968
  function readManifest(filePath) {
13020
- if (!import_node_fs12.default.existsSync(filePath)) throw new Error(`Manifest not found: ${filePath}`);
13021
- const parsed = JSON.parse(import_node_fs12.default.readFileSync(filePath, "utf8"));
13969
+ if (!import_node_fs13.default.existsSync(filePath)) throw new Error(`Manifest not found: ${filePath}`);
13970
+ const parsed = JSON.parse(import_node_fs13.default.readFileSync(filePath, "utf8"));
13022
13971
  if (parsed.schemaVersion !== 1) throw new Error("Unsupported tracking manifest schema");
13023
13972
  return parsed;
13024
13973
  }
13025
13974
  function writeManifest(filePath, manifest) {
13026
- import_node_fs12.default.mkdirSync(import_node_path10.default.dirname(filePath), { recursive: true });
13027
- import_node_fs12.default.writeFileSync(filePath, `${JSON.stringify(manifest, null, 2)}
13975
+ import_node_fs13.default.mkdirSync(import_node_path11.default.dirname(filePath), { recursive: true });
13976
+ import_node_fs13.default.writeFileSync(filePath, `${JSON.stringify(manifest, null, 2)}
13028
13977
  `, "utf8");
13029
13978
  }
13030
13979
  function buildSnippet(shopId, endpoint) {
@@ -13091,7 +14040,7 @@ function buildManifest(urlArgs, opts) {
13091
14040
  };
13092
14041
  }
13093
14042
  function manifestPath(input) {
13094
- return import_node_path10.default.resolve(process.cwd(), input ?? defaultManifestPath());
14043
+ return import_node_path11.default.resolve(process.cwd(), input ?? defaultManifestPath());
13095
14044
  }
13096
14045
  function printManifestPlan(manifest) {
13097
14046
  console.log(source_default.bold("Apex tracking ingestion plan"));
@@ -13181,12 +14130,15 @@ trackingCommand.command("verify").description("Verify live tracking events for t
13181
14130
  ["Last event", totals.last_event_at ?? "-"]
13182
14131
  ]
13183
14132
  );
14133
+ if (res.data.truncated) {
14134
+ console.log(`Showing the newest ${totals.events} events; totals cover this sample only`);
14135
+ }
13184
14136
  const eventTypes = Object.entries(totals.event_types);
13185
14137
  if (eventTypes.length > 0) {
13186
14138
  console.log();
13187
14139
  printTable(
13188
14140
  ["EVENT TYPE", "COUNT"],
13189
- eventTypes.map(([eventType, count]) => [eventType, String(count)])
14141
+ eventTypes.map(([eventType, count2]) => [eventType, String(count2)])
13190
14142
  );
13191
14143
  }
13192
14144
  if (res.data.recent_events.length > 0) {
@@ -13261,10 +14213,180 @@ trackingCommand.command("latency").description("Measure forced-preview versus na
13261
14213
  });
13262
14214
  });
13263
14215
 
14216
+ // src/commands/events.ts
14217
+ var import_node_fs14 = __toESM(require("node:fs"));
14218
+ var eventsCommand = new Command("events").description("Export raw tracking events");
14219
+ eventsCommand.command("export").description("Export every raw event in an absolute time window").requiredOption("--from <iso>", "Inclusive ISO 8601 window start").requiredOption("--to <iso>", "Exclusive ISO 8601 window end").option("--experiment <id>", "Filter by experiment ID").option("--event-type <types>", "Comma-separated event types").option("--qa <scope>", "QA scope: exclude, include, or only", "include").option("--limit <count>", "Page size from 1 to 1000", "500").option("--out <path>", "Write event rows to a file instead of stdout").option("--json-summary", "Print a JSON summary on stdout; requires --out for the NDJSON rows").action(async (opts, command) => {
14220
+ if (!Number.isFinite(Date.parse(opts.from)) || !Number.isFinite(Date.parse(opts.to))) {
14221
+ fail(command, "--from and --to must be ISO 8601 timestamps", EXIT_CODES.validation);
14222
+ }
14223
+ if (!["exclude", "include", "only"].includes(opts.qa)) {
14224
+ fail(command, "--qa must be exclude, include, or only", EXIT_CODES.validation);
14225
+ }
14226
+ const limit = Number(opts.limit);
14227
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) {
14228
+ fail(command, "--limit must be an integer from 1 to 1000", EXIT_CODES.validation);
14229
+ }
14230
+ const summaryOnStdout = Boolean(opts.jsonSummary) || isJsonMode();
14231
+ if (isJsonMode() && !opts.out) {
14232
+ fail(
14233
+ command,
14234
+ "In JSON mode (--json or piped stdout) pass --out <file> for the NDJSON rows; stdout carries one summary document.",
14235
+ EXIT_CODES.validation,
14236
+ {
14237
+ next_steps: [
14238
+ {
14239
+ command: "apex events export --from <iso> --to <iso> --out events.ndjson",
14240
+ why: "Write rows to a file."
14241
+ }
14242
+ ]
14243
+ }
14244
+ );
14245
+ }
14246
+ if (opts.jsonSummary && !opts.out) {
14247
+ fail(command, "--json-summary requires --out for the event rows", EXIT_CODES.validation);
14248
+ }
14249
+ const output2 = opts.out ? import_node_fs14.default.createWriteStream(opts.out, { flags: "wx" }) : null;
14250
+ let outputError = null;
14251
+ output2?.on("error", (error) => {
14252
+ outputError = error;
14253
+ });
14254
+ if (output2) {
14255
+ try {
14256
+ await new Promise((resolve2, reject) => {
14257
+ output2.once("open", () => resolve2());
14258
+ output2.once("error", reject);
14259
+ });
14260
+ } catch (error) {
14261
+ fail(
14262
+ command,
14263
+ `Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
14264
+ EXIT_CODES.general,
14265
+ { code: "local_file_error" }
14266
+ );
14267
+ }
14268
+ }
14269
+ const writeLine = async (line) => {
14270
+ const stream = output2 ?? process.stdout;
14271
+ const accepted = output2 ? output2.write(line) : writeRawCommandOutput(line);
14272
+ if (accepted) return;
14273
+ await new Promise((resolve2, reject) => {
14274
+ const onDrain = () => {
14275
+ stream.off("error", onError);
14276
+ resolve2();
14277
+ };
14278
+ const onError = (error) => {
14279
+ stream.off("drain", onDrain);
14280
+ reject(error);
14281
+ };
14282
+ stream.once("drain", onDrain);
14283
+ stream.once("error", onError);
14284
+ });
14285
+ };
14286
+ let cursor;
14287
+ let written = 0;
14288
+ let pages = 0;
14289
+ let totals;
14290
+ do {
14291
+ const response = await exportEventsPage(requireCredentials(), {
14292
+ from: opts.from,
14293
+ to: opts.to,
14294
+ experiment: opts.experiment,
14295
+ eventTypes: opts.eventType?.split(",").map((value) => value.trim()).filter(Boolean),
14296
+ qa: opts.qa,
14297
+ limit,
14298
+ cursor
14299
+ });
14300
+ if (!response.ok || !response.data) {
14301
+ output2?.destroy();
14302
+ fail(
14303
+ command,
14304
+ response.error ?? "Event export page failed",
14305
+ classifyStatus(response.status),
14306
+ response.errorDetails
14307
+ );
14308
+ }
14309
+ if (outputError)
14310
+ fail(
14311
+ command,
14312
+ `Cannot write ${opts.out}: ${outputError.message}`,
14313
+ EXIT_CODES.general,
14314
+ {
14315
+ code: "local_file_error"
14316
+ }
14317
+ );
14318
+ const page = response.data.data;
14319
+ totals ??= page.totals;
14320
+ for (const event of page.events) {
14321
+ try {
14322
+ await writeLine(`${JSON.stringify(event)}
14323
+ `);
14324
+ } catch (error) {
14325
+ fail(
14326
+ command,
14327
+ `Cannot write ${opts.out ?? "stdout"}: ${error instanceof Error ? error.message : String(error)}`,
14328
+ EXIT_CODES.general,
14329
+ { code: "local_file_error" }
14330
+ );
14331
+ }
14332
+ written += 1;
14333
+ }
14334
+ pages += 1;
14335
+ cursor = page.next_cursor ?? void 0;
14336
+ } while (cursor);
14337
+ if (output2) {
14338
+ try {
14339
+ await new Promise((resolve2, reject) => {
14340
+ if (outputError) return reject(outputError);
14341
+ output2.once("error", reject);
14342
+ output2.end(resolve2);
14343
+ });
14344
+ } catch (error) {
14345
+ fail(
14346
+ command,
14347
+ `Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
14348
+ EXIT_CODES.general,
14349
+ { code: "local_file_error" }
14350
+ );
14351
+ }
14352
+ if (outputError)
14353
+ fail(
14354
+ command,
14355
+ `Cannot write ${opts.out}: ${outputError.message}`,
14356
+ EXIT_CODES.general,
14357
+ {
14358
+ code: "local_file_error"
14359
+ }
14360
+ );
14361
+ }
14362
+ const matches = totals ? written === totals.events : false;
14363
+ const summary = {
14364
+ events_written: written,
14365
+ pages,
14366
+ totals: totals ?? null,
14367
+ count_matches: matches
14368
+ };
14369
+ if (!matches)
14370
+ fail(
14371
+ command,
14372
+ "Exported event count does not match the server total. Totals are counted when the first page is read, so events that arrive late for a window ending in the last few minutes, or rows sharing one timestamp and event ID, can cause a difference. Re-run for a window that ended at least 15 minutes ago.",
14373
+ EXIT_CODES.general,
14374
+ {
14375
+ reason: "export_count_mismatch",
14376
+ ...summary
14377
+ }
14378
+ );
14379
+ if (summaryOnStdout) writeEnvelope(summary, "apex events export");
14380
+ process.stderr.write(
14381
+ `Exported ${written} events across ${pages} page(s); total ${totals?.events ?? "unknown"}; count matches: ${matches}.
14382
+ `
14383
+ );
14384
+ });
14385
+
13264
14386
  // src/commands/goals.ts
13265
14387
  init_source();
13266
- var import_node_fs13 = __toESM(require("node:fs"));
13267
- var import_node_path11 = __toESM(require("node:path"));
14388
+ var import_node_fs15 = __toESM(require("node:fs"));
14389
+ var import_node_path12 = __toESM(require("node:path"));
13268
14390
 
13269
14391
  // ../sdk/src/safe-regex.ts
13270
14392
  var MAX_PATTERN_LENGTH = 1024;
@@ -13440,9 +14562,9 @@ function printSuggestions(data) {
13440
14562
  function parseCapture(command, value) {
13441
14563
  if (!value) return void 0;
13442
14564
  const source = value.startsWith("@") ? value.slice(1) : value;
13443
- 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;
14565
+ const text2 = value.startsWith("@") || import_node_fs15.default.existsSync(source) ? import_node_fs15.default.readFileSync(import_node_path12.default.resolve(process.cwd(), source), "utf8") : value;
13444
14566
  try {
13445
- return JSON.parse(text);
14567
+ return JSON.parse(text2);
13446
14568
  } catch (error) {
13447
14569
  const detail5 = error instanceof Error ? error.message : String(error);
13448
14570
  fail(command, `Invalid --capture JSON: ${detail5}`, EXIT_CODES.validation);
@@ -13586,10 +14708,10 @@ var test = new Command("test").description("Dry run a goal URL pattern against t
13586
14708
  const days = Math.max(1, Number.parseInt(opts.days, 10) || 14);
13587
14709
  const dependence = urlPatternDependence(pattern);
13588
14710
  if (dependence !== "none") {
13589
- const reason = DEPENDENCE_REASON[dependence];
13590
- output(this, { verdict: "indeterminate", pattern, dependence, reason }, () => {
14711
+ const reason2 = DEPENDENCE_REASON[dependence];
14712
+ output(this, { verdict: "indeterminate", pattern, dependence, reason: reason2 }, () => {
13591
14713
  console.log(source_default.yellow("INDETERMINATE"));
13592
- console.log(reason);
14714
+ console.log(reason2);
13593
14715
  });
13594
14716
  return;
13595
14717
  }
@@ -13693,12 +14815,12 @@ Examples:
13693
14815
 
13694
14816
  // src/commands/funnels.ts
13695
14817
  init_source();
13696
- var import_node_fs14 = __toESM(require("node:fs"));
13697
- var import_node_path12 = __toESM(require("node:path"));
14818
+ var import_node_fs16 = __toESM(require("node:fs"));
14819
+ var import_node_path13 = __toESM(require("node:path"));
13698
14820
  function readSteps(command, file) {
13699
14821
  let parsed;
13700
14822
  try {
13701
- parsed = JSON.parse(import_node_fs14.default.readFileSync(import_node_path12.default.resolve(process.cwd(), file), "utf8"));
14823
+ parsed = JSON.parse(import_node_fs16.default.readFileSync(import_node_path13.default.resolve(process.cwd(), file), "utf8"));
13702
14824
  } catch (error) {
13703
14825
  fail(
13704
14826
  command,
@@ -14012,8 +15134,8 @@ var off = new Command("off").description("Disable consent gating").action(async
14012
15134
  var consentCommand = new Command("consent").description("Manage consent gating").addCommand(get2).addCommand(set).addCommand(off);
14013
15135
 
14014
15136
  // src/commands/project-code.ts
14015
- var import_node_fs15 = require("node:fs");
14016
- var import_node_path13 = require("node:path");
15137
+ var import_node_fs17 = require("node:fs");
15138
+ var import_node_path14 = require("node:path");
14017
15139
 
14018
15140
  // src/commands/runtime-settings-shop.ts
14019
15141
  var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -14058,7 +15180,7 @@ var fields = ["enabled", "css", "helperJs", "js"];
14058
15180
  function readPatch(command, file) {
14059
15181
  let input;
14060
15182
  try {
14061
- input = JSON.parse((0, import_node_fs15.readFileSync)(file, "utf8"));
15183
+ input = JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
14062
15184
  } catch (error) {
14063
15185
  fail(command, `Cannot read project code JSON: ${String(error)}`, EXIT_CODES.validation);
14064
15186
  }
@@ -14118,15 +15240,15 @@ var projectCodeCommand = new Command("project-code").description(
14118
15240
  );
14119
15241
  projectCodeCommand.addCommand(
14120
15242
  new Command("get").description("Read complete project code and its opaque version").requiredOption("--shop <domain-or-id>", "Shop domain or UUID").action(async (options, command) => {
14121
- const { credentials: _credentials, ...identity } = await identifyRuntimeSettingsShop(
15243
+ const { credentials: _credentials, ...identity2 } = await identifyRuntimeSettingsShop(
14122
15244
  command,
14123
15245
  options.shop
14124
15246
  );
14125
- const current = await readCurrent(command, { ...identity, credentials: _credentials });
15247
+ const current = await readCurrent(command, { ...identity2, credentials: _credentials });
14126
15248
  output(
14127
15249
  command,
14128
- { ...identity, ...current },
14129
- () => process.stdout.write(`${JSON.stringify({ ...identity, ...current }, null, 2)}
15250
+ { ...identity2, ...current },
15251
+ () => process.stdout.write(`${JSON.stringify({ ...identity2, ...current }, null, 2)}
14130
15252
  `)
14131
15253
  );
14132
15254
  })
@@ -14144,11 +15266,11 @@ for (const mode of ["diff", "set"]) {
14144
15266
  const patch = readPatch(actionCommand, options.file);
14145
15267
  const shop = await identifyRuntimeSettingsShop(actionCommand, options.shop);
14146
15268
  const current = await readCurrent(actionCommand, shop);
14147
- const { credentials: _credentials, ...identity } = shop;
15269
+ const { credentials: _credentials, ...identity2 } = shop;
14148
15270
  const diff = effectiveDiff(current.projectCode, patch);
14149
15271
  if (mode === "diff" || diff.changedFields.length === 0) {
14150
15272
  const result2 = {
14151
- ...identity,
15273
+ ...identity2,
14152
15274
  status: diff.changedFields.length ? "changed" : "unchanged",
14153
15275
  version: current.version,
14154
15276
  diff
@@ -14161,11 +15283,11 @@ for (const mode of ["diff", "set"]) {
14161
15283
  );
14162
15284
  return;
14163
15285
  }
14164
- const backupPath = (0, import_node_path13.resolve)(options.backup);
15286
+ const backupPath = (0, import_node_path14.resolve)(options.backup);
14165
15287
  try {
14166
- (0, import_node_fs15.writeFileSync)(
15288
+ (0, import_node_fs17.writeFileSync)(
14167
15289
  backupPath,
14168
- `${JSON.stringify({ ...identity, ...current, capturedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
15290
+ `${JSON.stringify({ ...identity2, ...current, capturedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
14169
15291
  `,
14170
15292
  { flag: "wx", mode: 384 }
14171
15293
  );
@@ -14195,14 +15317,14 @@ for (const mode of ["diff", "set"]) {
14195
15317
  `Project code saved; publication ${data.precompile.status}: ${data.precompile.summary}`
14196
15318
  );
14197
15319
  }
14198
- if (data.shop_id !== identity.shop_id)
15320
+ if (data.shop_id !== identity2.shop_id)
14199
15321
  warnings.push(
14200
15322
  "The write response has a different shop identity; inspect the saved state before another write."
14201
15323
  );
14202
15324
  for (const warning of warnings) process.stderr.write(`WARNING: ${warning}
14203
15325
  `);
14204
15326
  const result = {
14205
- ...identity,
15327
+ ...identity2,
14206
15328
  status: "saved",
14207
15329
  diff,
14208
15330
  backupPath,
@@ -14222,12 +15344,12 @@ for (const mode of ["diff", "set"]) {
14222
15344
  }
14223
15345
 
14224
15346
  // src/commands/triggers.ts
14225
- var import_node_fs16 = __toESM(require("node:fs"));
14226
- var import_node_path14 = __toESM(require("node:path"));
15347
+ var import_node_fs18 = __toESM(require("node:fs"));
15348
+ var import_node_path15 = __toESM(require("node:path"));
14227
15349
 
14228
15350
  // src/trigger-proposal.ts
14229
- function runtimePath(path24) {
14230
- return path24.replaceAll("[]", ".0");
15351
+ function runtimePath(path25) {
15352
+ return path25.replaceAll("[]", ".0");
14231
15353
  }
14232
15354
  function fieldsFrom(value, prefix = "", descriptorList = false) {
14233
15355
  if (!value || typeof value !== "object") return [];
@@ -14240,18 +15362,18 @@ function fieldsFrom(value, prefix = "", descriptorList = false) {
14240
15362
  )
14241
15363
  );
14242
15364
  }
14243
- const record2 = value;
14244
- const explicitPath = typeof record2.path === "string" ? record2.path : void 0;
15365
+ const record3 = value;
15366
+ const explicitPath = typeof record3.path === "string" ? record3.path : void 0;
14245
15367
  if (descriptorList && explicitPath) {
14246
- const rawValues = record2.values ?? record2.samples ?? record2.examples ?? record2.example;
15368
+ const rawValues = record3.values ?? record3.samples ?? record3.examples ?? record3.example;
14247
15369
  const values = Array.isArray(rawValues) ? rawValues : rawValues === void 0 ? [] : [rawValues];
14248
- const magnitude = [record2.mean, record2.average, record2.max, record2.magnitude].find(
15370
+ const magnitude = [record3.mean, record3.average, record3.max, record3.magnitude].find(
14249
15371
  (candidate) => typeof candidate === "number"
14250
15372
  );
14251
- const numericType = Array.isArray(record2.types) && record2.types.length > 0 && record2.types.every((type) => type === "num" || type === "numstr" || type === "numstr_comma");
15373
+ const numericType = Array.isArray(record3.types) && record3.types.length > 0 && record3.types.every((type) => type === "num" || type === "numstr" || type === "numstr_comma");
14252
15374
  return [{ path: runtimePath(explicitPath), values, magnitude, numericType }];
14253
15375
  }
14254
- return Object.entries(record2).flatMap(([key, child]) => {
15376
+ return Object.entries(record3).flatMap(([key, child]) => {
14255
15377
  if (["eventName", "event_name", "name", "count", "frequency", "evidence"].includes(key))
14256
15378
  return [];
14257
15379
  const next = prefix ? `${prefix}.${key}` : key;
@@ -14284,8 +15406,8 @@ function isDescriptorArray(value) {
14284
15406
  ]);
14285
15407
  return Array.isArray(value) && value.length > 0 && value.every((item) => {
14286
15408
  if (!item || typeof item !== "object" || Array.isArray(item)) return false;
14287
- const record2 = item;
14288
- return typeof record2.path === "string" && record2.path.length > 0 && Object.keys(record2).every((key) => descriptorKeys.has(key));
15409
+ const record3 = item;
15410
+ return typeof record3.path === "string" && record3.path.length > 0 && Object.keys(record3).every((key) => descriptorKeys.has(key));
14289
15411
  });
14290
15412
  }
14291
15413
  function candidatesFrom(report) {
@@ -14305,9 +15427,9 @@ function candidatesFrom(report) {
14305
15427
  }
14306
15428
  return [root];
14307
15429
  }
14308
- function score2(path24, patterns) {
15430
+ function score2(path25, patterns) {
14309
15431
  return patterns.reduce(
14310
- (total, pattern, index) => total + (pattern.test(path24) ? patterns.length - index : 0),
15432
+ (total, pattern, index) => total + (pattern.test(path25) ? patterns.length - index : 0),
14311
15433
  0
14312
15434
  );
14313
15435
  }
@@ -14441,10 +15563,10 @@ var whenOps = new Set(runtime_settings_trigger_constants_default.whenOps);
14441
15563
  function strictObject(value, location, keys) {
14442
15564
  if (!value || typeof value !== "object" || Array.isArray(value))
14443
15565
  throw new Error(`${location} must be an object`);
14444
- const record2 = value;
14445
- for (const key of Object.keys(record2))
15566
+ const record3 = value;
15567
+ for (const key of Object.keys(record3))
14446
15568
  if (!keys.has(key)) throw new Error(`${location}.${key} is not allowed`);
14447
- return record2;
15569
+ return record3;
14448
15570
  }
14449
15571
  function optionalType(value, type, location) {
14450
15572
  if (value !== void 0 && typeof value !== type)
@@ -14471,8 +15593,8 @@ function strictSource(value, location, allowSignal, required = false) {
14471
15593
  function defaultMatchesValueType(value, valueType) {
14472
15594
  if (valueType === "number") {
14473
15595
  if (typeof value !== "string") return Number.isFinite(Number(value));
14474
- const text = String(value).trim();
14475
- const normalized = /^-?\d{1,3}(\.\d{3})+,\d+$/.test(text) ? text.replace(/\./g, "").replace(",", ".") : /^-?\d+,\d+$/.test(text) ? text.replace(",", ".") : text;
15596
+ const text2 = String(value).trim();
15597
+ const normalized = /^-?\d{1,3}(\.\d{3})+,\d+$/.test(text2) ? text2.replace(/\./g, "").replace(",", ".") : /^-?\d+,\d+$/.test(text2) ? text2.replace(",", ".") : text2;
14476
15598
  return Number.isFinite(Number(normalized));
14477
15599
  }
14478
15600
  if (valueType === "boolean") {
@@ -14580,19 +15702,19 @@ function strictTriggers(value) {
14580
15702
  return value.map((item, index) => {
14581
15703
  if (!item || typeof item !== "object" || Array.isArray(item))
14582
15704
  throw new Error(`trigger[${index}] must be an object`);
14583
- const record2 = item;
14584
- for (const key of Object.keys(record2))
15705
+ const record3 = item;
15706
+ for (const key of Object.keys(record3))
14585
15707
  if (!allowed.has(key)) throw new Error(`trigger[${index}].${key} is not allowed`);
14586
- if (typeof record2.eventName !== "string" || !record2.eventName.trim())
15708
+ if (typeof record3.eventName !== "string" || !record3.eventName.trim())
14587
15709
  throw new Error(`trigger[${index}].eventName must be a non-empty string`);
14588
15710
  for (const key of ["enabled", "reEvaluate", "trackPageview"])
14589
- optionalType(record2[key], "boolean", `trigger[${index}].${key}`);
15711
+ optionalType(record3[key], "boolean", `trigger[${index}].${key}`);
14590
15712
  for (const key of ["dataLayerName", "trackEventName"])
14591
- optionalType(record2[key], "string", `trigger[${index}].${key}`);
14592
- strictWhen(record2.when, `trigger[${index}].when`);
14593
- if (record2.trackRevenue !== void 0) {
15713
+ optionalType(record3[key], "string", `trigger[${index}].${key}`);
15714
+ strictWhen(record3.when, `trigger[${index}].when`);
15715
+ if (record3.trackRevenue !== void 0) {
14594
15716
  const revenue = strictObject(
14595
- record2.trackRevenue,
15717
+ record3.trackRevenue,
14596
15718
  `trigger[${index}].trackRevenue`,
14597
15719
  /* @__PURE__ */ new Set([
14598
15720
  "enabled",
@@ -14615,21 +15737,21 @@ function strictTriggers(value) {
14615
15737
  );
14616
15738
  }
14617
15739
  }
14618
- if (record2.mappings !== void 0) {
14619
- if (!Array.isArray(record2.mappings))
15740
+ if (record3.mappings !== void 0) {
15741
+ if (!Array.isArray(record3.mappings))
14620
15742
  throw new Error(`trigger[${index}].mappings must be an array`);
14621
- record2.mappings.forEach(
15743
+ record3.mappings.forEach(
14622
15744
  (mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].mappings[${mappingIndex}]`, false)
14623
15745
  );
14624
15746
  }
14625
- if (record2.properties !== void 0) {
14626
- if (!Array.isArray(record2.properties))
15747
+ if (record3.properties !== void 0) {
15748
+ if (!Array.isArray(record3.properties))
14627
15749
  throw new Error(`trigger[${index}].properties must be an array`);
14628
- record2.properties.forEach(
15750
+ record3.properties.forEach(
14629
15751
  (mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].properties[${mappingIndex}]`, true)
14630
15752
  );
14631
15753
  }
14632
- return record2;
15754
+ return record3;
14633
15755
  });
14634
15756
  }
14635
15757
  function writeWarnings(result) {
@@ -14649,9 +15771,22 @@ async function identify(command, requestedShop) {
14649
15771
  resolvedContext: { ...credentials.resolvedContext, shopId: void 0 }
14650
15772
  };
14651
15773
  const context = await getAccountContext(discoveryCredentials);
14652
- const match2 = context.data?.data.orgs.flatMap((org) => org.shops).find((shop) => shop.domain.toLowerCase() === requestedShop.toLowerCase());
14653
- if (!context.ok || !match2) {
14654
- fail(command, "TRIGGERS_CONFIG_ABSENT: --shop domain is not available to this user", 2);
15774
+ if (!context.ok) {
15775
+ fail(
15776
+ command,
15777
+ `${context.status === 401 || context.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${context.error ?? "account context unavailable"}`,
15778
+ classifyStatus(context.status),
15779
+ context.errorDetails,
15780
+ apiFailureOf(context)
15781
+ );
15782
+ }
15783
+ const match2 = context.data?.data?.orgs?.flatMap((org) => org.shops).find((shop) => shop.domain.toLowerCase() === requestedShop.toLowerCase());
15784
+ if (!match2) {
15785
+ fail(
15786
+ command,
15787
+ "TRIGGERS_CONFIG_ABSENT: --shop domain is not available to this user",
15788
+ EXIT_CODES.validation
15789
+ );
14655
15790
  }
14656
15791
  credentials = {
14657
15792
  ...credentials,
@@ -14660,46 +15795,49 @@ async function identify(command, requestedShop) {
14660
15795
  }
14661
15796
  const res = await getInstallDomains(credentials);
14662
15797
  if (!res.ok || !res.data) {
14663
- const exitCode = res.status === 401 || res.status === 403 ? 4 : 2;
14664
15798
  fail(
14665
15799
  command,
14666
- `${exitCode === 4 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "shop identity unavailable"}`,
14667
- exitCode
15800
+ `${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "shop identity unavailable"}`,
15801
+ classifyStatus(res.status)
14668
15802
  );
14669
15803
  }
14670
- const identity = { shop_id: res.data.shop_id, domain: res.data.domain };
14671
- if (requestedShop !== identity.shop_id && requestedShop.toLowerCase() !== identity.domain.toLowerCase())
14672
- fail(command, "TRIGGERS_CONFIG_ABSENT: --shop does not match the authenticated shop", 2);
14673
- return { ...identity, credentials };
15804
+ const identity2 = { shop_id: res.data.shop_id, domain: res.data.domain };
15805
+ if (requestedShop !== identity2.shop_id && requestedShop.toLowerCase() !== identity2.domain.toLowerCase())
15806
+ fail(
15807
+ command,
15808
+ "TRIGGERS_CONFIG_ABSENT: --shop does not match the authenticated shop",
15809
+ EXIT_CODES.validation
15810
+ );
15811
+ return { ...identity2, credentials };
14674
15812
  }
14675
15813
  async function send(command, requestedShop, body5) {
14676
- const identity = await identify(command, requestedShop);
14677
- const res = await updateRuntimeSettings(identity.credentials, body5);
15814
+ const identity2 = await identify(command, requestedShop);
15815
+ const res = await updateRuntimeSettings(identity2.credentials, body5);
14678
15816
  if (!res.ok || !res.data)
14679
15817
  fail(
14680
15818
  command,
14681
15819
  `${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Runtime settings update failed"}`,
14682
- res.status === 401 || res.status === 403 ? 4 : 2,
15820
+ classifyStatus(res.status),
14683
15821
  res.errorDetails
14684
15822
  );
14685
- const { credentials: _credentials, ...publicIdentity } = identity;
15823
+ const { credentials: _credentials, ...publicIdentity } = identity2;
14686
15824
  return { data: res.data, identity: publicIdentity };
14687
15825
  }
14688
15826
  var get3 = new Command("get").description("Get data layer triggers").requiredOption("--shop <domain-or-id>", "Shop domain or Apex shop ID").action(async function(opts) {
14689
- const identity = await identify(this, opts.shop);
14690
- const res = await getRuntimeSettings(identity.credentials);
15827
+ const identity2 = await identify(this, opts.shop);
15828
+ const res = await getRuntimeSettings(identity2.credentials);
14691
15829
  if (!res.ok || !res.data)
14692
15830
  fail(
14693
15831
  this,
14694
15832
  `${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Runtime settings read failed"}`,
14695
- res.status === 401 || res.status === 403 ? 4 : classifyStatus(res.status),
15833
+ classifyStatus(res.status),
14696
15834
  res.errorDetails
14697
15835
  );
14698
15836
  const triggers = res.data.runtime_settings.dataLayerTriggers ?? [];
14699
- const { credentials: _credentials, ...publicIdentity } = identity;
15837
+ const { credentials: _credentials, ...publicIdentity } = identity2;
14700
15838
  output(this, { verdict: "TRIGGERS_GET", ...publicIdentity, triggers }, () => {
14701
15839
  process.stdout.write(
14702
- `TRIGGERS_GET shop_id=${identity.shop_id} domain=${identity.domain} count=${triggers.length}
15840
+ `TRIGGERS_GET shop_id=${identity2.shop_id} domain=${identity2.domain} count=${triggers.length}
14703
15841
  `
14704
15842
  );
14705
15843
  printTable(
@@ -14716,24 +15854,24 @@ var get3 = new Command("get").description("Get data layer triggers").requiredOpt
14716
15854
  var set2 = new Command("set").requiredOption("--file <json>", "JSON trigger array").requiredOption("--shop <domain-or-id>", "Shop domain or Apex shop ID").action(async function(opts) {
14717
15855
  let triggers;
14718
15856
  try {
14719
- triggers = strictTriggers(JSON.parse(import_node_fs16.default.readFileSync(import_node_path14.default.resolve(opts.file), "utf8")));
15857
+ triggers = strictTriggers(JSON.parse(import_node_fs18.default.readFileSync(import_node_path15.default.resolve(opts.file), "utf8")));
14720
15858
  } catch (error) {
14721
15859
  fail(
14722
15860
  this,
14723
15861
  `TRIGGERS_CONFIG_ABSENT: ${error instanceof Error ? error.message : String(error)}`,
14724
- 2
15862
+ EXIT_CODES.validation
14725
15863
  );
14726
15864
  }
14727
- const { data, identity } = await send(this, opts.shop, { dataLayerTriggers: triggers });
15865
+ const { data, identity: identity2 } = await send(this, opts.shop, { dataLayerTriggers: triggers });
14728
15866
  const result = {
14729
15867
  verdict: "TRIGGERS_SET",
14730
- ...identity,
15868
+ ...identity2,
14731
15869
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
14732
15870
  ...data
14733
15871
  };
14734
15872
  output(this, result, () => {
14735
15873
  process.stdout.write(
14736
- `TRIGGERS_SET shop_id=${identity.shop_id} domain=${identity.domain} updated_at=${result.updated_at} count=${triggers.length}
15874
+ `TRIGGERS_SET shop_id=${identity2.shop_id} domain=${identity2.domain} updated_at=${result.updated_at} count=${triggers.length}
14737
15875
  `
14738
15876
  );
14739
15877
  writeWarnings(result);
@@ -14743,17 +15881,21 @@ var propose = new Command("propose").requiredOption("--from-file <u2-report.json
14743
15881
  let proposals;
14744
15882
  let report;
14745
15883
  try {
14746
- report = JSON.parse(import_node_fs16.default.readFileSync(import_node_path14.default.resolve(opts.fromFile), "utf8"));
15884
+ report = JSON.parse(import_node_fs18.default.readFileSync(import_node_path15.default.resolve(opts.fromFile), "utf8"));
14747
15885
  proposals = proposeTriggers(report);
14748
15886
  } catch (error) {
14749
- fail(this, error instanceof Error ? error.message : String(error), 2);
15887
+ fail(this, error instanceof Error ? error.message : String(error), EXIT_CODES.validation);
14750
15888
  }
14751
15889
  const reportBody = report.data && typeof report.data === "object" && !Array.isArray(report.data) ? report.data : report;
14752
15890
  const reportShop = reportBody.shop ?? reportBody.shop_context ?? {};
14753
15891
  const shop_id = String(reportShop.shop_id ?? reportShop.id ?? reportBody.shop_id ?? "");
14754
15892
  const domain = String(reportShop.domain ?? reportBody.domain ?? "");
14755
15893
  if (!shop_id || !domain || opts.shop !== shop_id && opts.shop.toLowerCase() !== domain.toLowerCase()) {
14756
- fail(this, "TRIGGERS_CONFIG_ABSENT: --shop must match the report shop_id or domain", 2);
15894
+ fail(
15895
+ this,
15896
+ "TRIGGERS_CONFIG_ABSENT: --shop must match the report shop_id or domain",
15897
+ EXIT_CODES.validation
15898
+ );
14757
15899
  }
14758
15900
  if (!proposals.length)
14759
15901
  fail(this, "TRIGGERS_NO_DATA: report has no usable purchase candidates", 3);
@@ -14782,29 +15924,34 @@ var recipe = new Command("apply-recipe").argument("<recipeId>").requiredOption("
14782
15924
  const params = Object.fromEntries(
14783
15925
  opts.param.map((pair) => {
14784
15926
  const i = pair.indexOf("=");
14785
- if (i < 1) fail(this, `TRIGGERS_CONFIG_ABSENT: invalid --param ${pair}; expected k=v`, 2);
15927
+ if (i < 1)
15928
+ fail(
15929
+ this,
15930
+ `TRIGGERS_CONFIG_ABSENT: invalid --param ${pair}; expected k=v`,
15931
+ EXIT_CODES.validation
15932
+ );
14786
15933
  return [pair.slice(0, i), parseParamValue(pair.slice(i + 1))];
14787
15934
  })
14788
15935
  );
14789
- const identity = await identify(this, opts.shop);
14790
- const res = await applyRuntimeSettingsRecipe(identity.credentials, { recipeId, params });
15936
+ const identity2 = await identify(this, opts.shop);
15937
+ const res = await applyRuntimeSettingsRecipe(identity2.credentials, { recipeId, params });
14791
15938
  if (!res.ok || !res.data)
14792
15939
  fail(
14793
15940
  this,
14794
15941
  `${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Recipe failed"}`,
14795
- res.status === 401 || res.status === 403 ? 4 : 2,
15942
+ classifyStatus(res.status),
14796
15943
  res.errorDetails
14797
15944
  );
14798
15945
  const result = {
14799
15946
  verdict: "TRIGGERS_APPLIED",
14800
15947
  ...res.data,
14801
- shop_id: identity.shop_id,
14802
- domain: identity.domain,
15948
+ shop_id: identity2.shop_id,
15949
+ domain: identity2.domain,
14803
15950
  applied_at: (/* @__PURE__ */ new Date()).toISOString()
14804
15951
  };
14805
15952
  output(this, result, () => {
14806
15953
  process.stdout.write(
14807
- `TRIGGERS_APPLIED shop_id=${identity.shop_id} domain=${identity.domain} applied_at=${result.applied_at} recipe=${recipeId}
15954
+ `TRIGGERS_APPLIED shop_id=${identity2.shop_id} domain=${identity2.domain} applied_at=${result.applied_at} recipe=${recipeId}
14808
15955
  `
14809
15956
  );
14810
15957
  writeWarnings(result);
@@ -14813,35 +15960,39 @@ var recipe = new Command("apply-recipe").argument("<recipeId>").requiredOption("
14813
15960
  var enable = new Command("enable-capture").requiredOption("--hours <n>").requiredOption("--shop <domain-or-id>", "Shop domain or Apex shop ID").option("--url-pattern <p>", "URL pattern", collect, []).action(async function(opts) {
14814
15961
  const hours = Number(opts.hours);
14815
15962
  if (!Number.isFinite(hours) || hours <= 0 || hours > 48)
14816
- fail(this, "TRIGGERS_CONFIG_ABSENT: --hours must be greater than 0 and at most 48", 2);
15963
+ fail(
15964
+ this,
15965
+ "TRIGGERS_CONFIG_ABSENT: --hours must be greater than 0 and at most 48",
15966
+ EXIT_CODES.validation
15967
+ );
14817
15968
  const expiresAt = new Date(Date.now() + hours * 36e5).toISOString();
14818
15969
  const setupCapture = {
14819
15970
  enabled: true,
14820
15971
  expiresAt,
14821
15972
  ...opts.urlPattern.length > 0 ? { urlPatterns: opts.urlPattern } : {}
14822
15973
  };
14823
- const { data, identity } = await send(this, opts.shop, { setupCapture });
14824
- const result = { verdict: "CAPTURE_ENABLED", ...identity, expiresAt, ...data };
15974
+ const { data, identity: identity2 } = await send(this, opts.shop, { setupCapture });
15975
+ const result = { verdict: "CAPTURE_ENABLED", ...identity2, expiresAt, ...data };
14825
15976
  output(
14826
15977
  this,
14827
15978
  result,
14828
15979
  () => process.stdout.write(
14829
- `CAPTURE_ENABLED shop_id=${identity.shop_id} domain=${identity.domain} expires_at=${expiresAt}
15980
+ `CAPTURE_ENABLED shop_id=${identity2.shop_id} domain=${identity2.domain} expires_at=${expiresAt}
14830
15981
  `
14831
15982
  )
14832
15983
  );
14833
15984
  });
14834
15985
  var disable = new Command("disable-capture").requiredOption("--shop <domain-or-id>", "Shop domain or Apex shop ID").action(async function(opts) {
14835
- const { data, identity } = await send(this, opts.shop, {
15986
+ const { data, identity: identity2 } = await send(this, opts.shop, {
14836
15987
  setupCapture: { enabled: false, expiresAt: null }
14837
15988
  });
14838
15989
  const disabled_at = (/* @__PURE__ */ new Date()).toISOString();
14839
- const result = { verdict: "CAPTURE_DISABLED", ...identity, disabled_at, ...data };
15990
+ const result = { verdict: "CAPTURE_DISABLED", ...identity2, disabled_at, ...data };
14840
15991
  output(
14841
15992
  this,
14842
15993
  result,
14843
15994
  () => process.stdout.write(
14844
- `CAPTURE_DISABLED shop_id=${identity.shop_id} domain=${identity.domain} disabled_at=${disabled_at}
15995
+ `CAPTURE_DISABLED shop_id=${identity2.shop_id} domain=${identity2.domain} disabled_at=${disabled_at}
14845
15996
  `
14846
15997
  )
14847
15998
  );
@@ -15061,8 +16212,8 @@ var qa2 = new Command("qa").description("Check static link/form handover paramet
15061
16212
  html = await response.text();
15062
16213
  } catch (error) {
15063
16214
  if (error instanceof CliError) throw error;
15064
- const reason = error instanceof Error ? error.message : String(error);
15065
- fail(this, `Could not fetch ${opts.from}: ${reason}`, EXIT_CODES.general);
16215
+ const reason2 = error instanceof Error ? error.message : String(error);
16216
+ fail(this, `Could not fetch ${opts.from}: ${reason2}`, EXIT_CODES.general);
15066
16217
  }
15067
16218
  const param = config.externalTracking.handoverQueryParam;
15068
16219
  const baseUrl = response.url || opts.from;
@@ -15109,6 +16260,138 @@ var qa2 = new Command("qa").description("Check static link/form handover paramet
15109
16260
  });
15110
16261
  var handoverCommand = new Command("handover").description("Manage cross-domain identity handover").addCommand(get4).addCommand(set4).addCommand(off2).addCommand(qa2);
15111
16262
 
16263
+ // src/commands/order-journey.ts
16264
+ function instant(value) {
16265
+ const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:\d{2})$/.exec(
16266
+ value
16267
+ );
16268
+ if (!m) return null;
16269
+ const year = Number(m[1]), month = Number(m[2]), day = Number(m[3]);
16270
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
16271
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
16272
+ if (month < 1 || month > 12 || day < 1 || day > days[month - 1] || Number(m[4]) > 23 || Number(m[5]) > 59 || Number(m[6] ?? 0) > 59 || m[8] !== "Z" && (Number(m[8].slice(1, 3)) > 23 || Number(m[8].slice(4)) > 59))
16273
+ return null;
16274
+ const second = Date.parse(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6] ?? "00"}${m[8]}`);
16275
+ return Number.isFinite(second) ? { second, fraction: (m[7] ?? "").replace(/0+$/, "") } : null;
16276
+ }
16277
+ function compare(a, b) {
16278
+ if (a.second !== b.second) return a.second < b.second ? -1 : 1;
16279
+ const precision = Math.max(a.fraction.length, b.fraction.length);
16280
+ const left = a.fraction.padEnd(precision, "0"), right = b.fraction.padEnd(precision, "0");
16281
+ return left === right ? 0 : left < right ? -1 : 1;
16282
+ }
16283
+ var record = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
16284
+ var text = (v) => typeof v === "string" && v.length > 0;
16285
+ var reason = (v) => typeof v === "string" && (/^[a-z][a-z0-9_]{0,79}$/.test(v) || /^(publishedConfig|consentPolicy|goalMapping|sdkArtifact|sdkPolicy|identityPolicy|goalDefinition|resultWindow|inputSnapshot)_(expected_unknown|observed_unknown|mismatch|binding_mismatch)$/.test(
16286
+ v
16287
+ ));
16288
+ var member = (v, values) => typeof v === "string" && values.includes(v);
16289
+ var status = (v) => member(v, ["pass", "fail", "inconclusive"]);
16290
+ var count = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
16291
+ function validReport(v) {
16292
+ if (!record(v) || v.schemaVersion !== 1 || v.finalStatisticalContribution !== "not_evaluated" || !record(v.evaluation) || !status(v.evaluation.status) || !member(v.evaluation.freshness, ["current", "stale", "unknown"]) || !Array.isArray(v.evaluation.stages) || !v.evaluation.stages.every(
16293
+ (s) => record(s) && (s.freshness === void 0 || member(s.freshness, ["current", "stale", "unknown"])) && member(s.stage, [
16294
+ "consent",
16295
+ "assignment",
16296
+ "event",
16297
+ "order_identity",
16298
+ "authoritative_fact",
16299
+ "attribution",
16300
+ "result"
16301
+ ]) && status(s.status) && Array.isArray(s.reasonCodes) && s.reasonCodes.every(reason)
16302
+ ))
16303
+ return false;
16304
+ if (v.receiptOutcomes !== void 0 && (!Array.isArray(v.receiptOutcomes) || v.receiptOutcomes.length > 6856 || !v.receiptOutcomes.every(
16305
+ (r) => record(r) && text(r.receiptId) && text(r.stage) && text(r.producerContract) && record(r.scope) && [r.scope.experimentId, r.scope.variationId, r.scope.goalId].every(
16306
+ (x) => x === null || text(x)
16307
+ ) && member(r.freshness, ["current", "stale", "unknown"]) && Array.isArray(r.reasonCodes) && r.reasonCodes.every(reason)
16308
+ )))
16309
+ return false;
16310
+ const coverage = v.assignmentCoverage;
16311
+ if (!record(coverage) || typeof coverage.complete !== "boolean" || typeof coverage.overflowCountIsLowerBound !== "boolean" || !(coverage.overflowCount === null || count(coverage.overflowCount)))
16312
+ return false;
16313
+ return Array.isArray(v.assignments) && v.assignments.length <= 50 && v.assignments.every(
16314
+ (a) => record(a) && text(a.experimentId) && text(a.variationId) && (a.storedAttribution === null || typeof a.storedAttribution === "boolean") && reason(a.reasonCode) && Array.isArray(a.goals) && a.goals.length <= 50 && a.goals.every(
16315
+ (g) => record(g) && text(g.goalId) && member(g.status, [
16316
+ "eligible_for_attributed_goal_read",
16317
+ "excluded_from_attributed_goal_read",
16318
+ "unknown"
16319
+ ]) && reason(g.reasonCode)
16320
+ )
16321
+ );
16322
+ }
16323
+ var orderJourneyCommand = new Command("order-journey").description("Inspect retained order evidence and attributed-goal eligibility (read-only)").requiredOption("--platform <platform>", "shopify, shopware, woocommerce, centra or custom").option("--order-id <id>", "Platform order ID, mutually exclusive with --order-number").option("--order-number <number>", "Order number, mutually exclusive with --order-id").requiredOption("--environment <key>", "Exact environment").requiredOption("--from <iso>", "Inclusive ISO start timestamp").requiredOption("--to <iso>", "Inclusive ISO end timestamp, at most 31 days after start").option("--include-qa", "Include QA in the canonical eligibility read").action(async function(opts) {
16324
+ const key = opts.orderId ?? opts.orderNumber;
16325
+ const from = instant(opts.from), to = instant(opts.to);
16326
+ if (Number(opts.orderId !== void 0) + Number(opts.orderNumber !== void 0) !== 1 || !key?.trim() || key.trim().length > 255 || /[\x00-\x1f\x7f]/.test(key) || !["shopify", "shopware", "woocommerce", "centra", "custom"].includes(opts.platform) || !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(opts.environment) || !from || !to || compare(from, to) > 0 || compare(to, { ...from, second: from.second + 31 * 864e5 }) > 0 || compare(to, instant(new Date(Date.now() + 3e5).toISOString())) > 0) {
16327
+ fail(
16328
+ this,
16329
+ "Invalid order journey query: supply exactly one order identifier and a bounded ISO interval",
16330
+ EXIT_CODES.validation
16331
+ );
16332
+ }
16333
+ const result = await getOrderJourney(requireCredentials(), {
16334
+ ...opts,
16335
+ orderId: opts.orderId?.trim(),
16336
+ orderNumber: opts.orderNumber?.trim()
16337
+ });
16338
+ if (!result.ok || !result.data)
16339
+ fail(this, result.error ?? "Order journey unavailable", classifyStatus(result.status));
16340
+ const report = result.data.data;
16341
+ if (!validReport(report)) fail(this, "Malformed order journey response", EXIT_CODES.general);
16342
+ output(this, { data: report }, () => {
16343
+ const coverage = report.assignmentCoverage;
16344
+ printTable(
16345
+ ["Assignment coverage", "Overflow"],
16346
+ [
16347
+ [
16348
+ coverage.complete ? "complete" : "incomplete",
16349
+ coverage.overflowCount === null ? "unknown" : `${coverage.overflowCount} (${coverage.overflowCountIsLowerBound ? "lower bound" : "exact"})`
16350
+ ]
16351
+ ]
16352
+ );
16353
+ printTable(
16354
+ ["Journey", "Freshness", "Eligibility basis", "Final contribution"],
16355
+ [
16356
+ [
16357
+ report.evaluation.status,
16358
+ report.evaluation.freshness,
16359
+ "attributed_goal_counts",
16360
+ report.finalStatisticalContribution
16361
+ ]
16362
+ ]
16363
+ );
16364
+ printTable(
16365
+ ["Stage", "Status", "Freshness", "Reasons"],
16366
+ report.evaluation.stages.map((s) => [
16367
+ s.stage,
16368
+ s.status,
16369
+ s.freshness ?? "unknown",
16370
+ s.reasonCodes.join(", ")
16371
+ ])
16372
+ );
16373
+ if (report.receiptOutcomes?.length)
16374
+ printTable(
16375
+ ["Receipt", "Stage", "Experiment / variation / goal", "Freshness"],
16376
+ report.receiptOutcomes.map((r) => [
16377
+ r.receiptId,
16378
+ r.stage,
16379
+ [r.scope.experimentId, r.scope.variationId, r.scope.goalId].filter(Boolean).join(" / ") || "global",
16380
+ r.freshness
16381
+ ])
16382
+ );
16383
+ printTable(
16384
+ ["Experiment", "Variation", "Attribution", "Goal eligibility"],
16385
+ report.assignments.map((a) => [
16386
+ a.experimentId,
16387
+ a.variationId,
16388
+ a.storedAttribution === null ? "unknown" : a.storedAttribution ? "present" : "not observed",
16389
+ a.goals.length ? a.goals.map((g) => `${g.goalId}: ${g.status} (${g.reasonCode})`).join("; ") : `unknown (${a.reasonCode})`
16390
+ ])
16391
+ );
16392
+ });
16393
+ });
16394
+
15112
16395
  // src/lib/detect-markers.ts
15113
16396
  var DETECTION_MARKERS = [
15114
16397
  {
@@ -15335,11 +16618,11 @@ async function fetchPrimaryHtml(host) {
15335
16618
  headers: { "user-agent": "apex-cli-doctor/1.0" },
15336
16619
  signal: AbortSignal.timeout(PAGE_FETCH_TIMEOUT_MS)
15337
16620
  });
15338
- const text = await res.text();
15339
- if (CHALLENGE_STATUSES.includes(res.status) || /cf-chl|challenge-platform|captcha/i.test(text))
16621
+ const text2 = await res.text();
16622
+ if (CHALLENGE_STATUSES.includes(res.status) || /cf-chl|challenge-platform|captcha/i.test(text2))
15340
16623
  return { protected: true };
15341
16624
  if (!res.ok) return { error: `http ${res.status}` };
15342
- return { html: text };
16625
+ return { html: text2 };
15343
16626
  } catch (error) {
15344
16627
  return { error: error instanceof Error ? error.message : String(error) };
15345
16628
  }
@@ -15361,9 +16644,9 @@ function environmentByHost(envs) {
15361
16644
  }
15362
16645
  return map;
15363
16646
  }
15364
- function devPollutionCheck(eventHosts, envs) {
16647
+ function devPollutionCheck(eventHosts, envs, unparsedRows) {
15365
16648
  const dev = eventHosts.filter((host) => isDevHost(host));
15366
- if (!dev.length)
16649
+ if (!dev.length && !unparsedRows.length)
15367
16650
  return {
15368
16651
  checkId: "dev_pollution",
15369
16652
  status: "PASS",
@@ -15378,6 +16661,13 @@ function devPollutionCheck(eventHosts, envs) {
15378
16661
  return `${host} matches development host markers and is bound to the production environment`;
15379
16662
  return `${host} matches development host markers, sent production-labeled traffic, and is not bound to any environment`;
15380
16663
  });
16664
+ if (unparsedRows.length) {
16665
+ const pageviews = unparsedRows.reduce((sum, row) => sum + row.pageviews, 0);
16666
+ const firstPath = unparsedRows[0].path || "(empty)";
16667
+ evidence.push(
16668
+ `${pageviews} production-labeled pageview(s) carry a host the analytics read could not parse (first path: ${firstPath}); development or local-file traffic cannot be ruled out`
16669
+ );
16670
+ }
15381
16671
  const mappable = dev.filter(
15382
16672
  (host) => (bindings.get(host) ?? PRODUCTION_ENVIRONMENT_KEY) === PRODUCTION_ENVIRONMENT_KEY
15383
16673
  );
@@ -15393,7 +16683,7 @@ var PURCHASE_CADENCE_WINDOW_MINUTES = PURCHASE_WINDOW_MINUTES * 4;
15393
16683
  var MIN_CAPTURE_WINDOW_MINUTES = 60;
15394
16684
  var DUPLICATE_RATE_THRESHOLD = 0.02;
15395
16685
  var MAGNITUDE_TOLERANCE_RATE = 0.1;
15396
- function record(value) {
16686
+ function record2(value) {
15397
16687
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
15398
16688
  }
15399
16689
  function purchasePlane(config) {
@@ -15401,8 +16691,8 @@ function purchasePlane(config) {
15401
16691
  const names = [];
15402
16692
  const triggers = config.runtime?.dataLayerTriggers ?? config.dataLayerTriggers;
15403
16693
  if (Array.isArray(triggers) && triggers.some((trigger) => {
15404
- const row = record(trigger);
15405
- const trackRevenue = record(row?.trackRevenue);
16694
+ const row = record2(trigger);
16695
+ const trackRevenue = record2(row?.trackRevenue);
15406
16696
  return row?.enabled !== false && trackRevenue !== null && trackRevenue.enabled !== false;
15407
16697
  }))
15408
16698
  names.push("dataLayer trigger");
@@ -15444,7 +16734,7 @@ function lineItemSum(value) {
15444
16734
  if (!Array.isArray(value)) return null;
15445
16735
  let sum = 0;
15446
16736
  for (const item of value) {
15447
- const row = record(item);
16737
+ const row = record2(item);
15448
16738
  const price = numeric(row?.price);
15449
16739
  if (price === null) return null;
15450
16740
  sum += price * (numeric(row?.quantity) ?? 1);
@@ -15838,7 +17128,10 @@ async function runDoctor(options = {}) {
15838
17128
  const rows = inventory.rows;
15839
17129
  const inventoryCaveat = inventory.truncated ? [INVENTORY_TRUNCATED_CAVEAT] : [];
15840
17130
  const hosts = configuredHosts(domainsRes.data, envsRes.data);
15841
- const eventHosts = [...new Set(rows.map((row) => row.host.toLowerCase()))];
17131
+ const unparsedRows = rows.filter((row) => row.host.trim() === "");
17132
+ const eventHosts = [
17133
+ ...new Set(rows.filter((row) => row.host.trim() !== "").map((row) => row.host.toLowerCase()))
17134
+ ];
15842
17135
  const checks = [];
15843
17136
  const unbound = eventHosts.filter((host) => !hosts.has(host));
15844
17137
  checks.push(
@@ -15859,7 +17152,9 @@ async function runDoctor(options = {}) {
15859
17152
  ]
15860
17153
  }
15861
17154
  );
15862
- checks.push(withCaveat(devPollutionCheck(eventHosts, envsRes.data), inventoryCaveat));
17155
+ checks.push(
17156
+ withCaveat(devPollutionCheck(eventHosts, envsRes.data, unparsedRows), inventoryCaveat)
17157
+ );
15863
17158
  const primary = hostOnly(domainsRes.data.domain) ?? eventHosts[0];
15864
17159
  const shopId = resolveShopId(options) ?? domainsRes.data.shop_id;
15865
17160
  const workerBase = resolveWorkerBase(options);
@@ -16019,11 +17314,11 @@ function withCaveat(check, caveat) {
16019
17314
  }
16020
17315
  var DAY_MS = 864e5;
16021
17316
  function parseDay(value) {
16022
- const text = value?.trim();
16023
- if (!text || !/^\d{4}-\d{2}-\d{2}$/.test(text)) return null;
16024
- const parsed = Date.parse(`${text}T00:00:00.000Z`);
17317
+ const text2 = value?.trim();
17318
+ if (!text2 || !/^\d{4}-\d{2}-\d{2}$/.test(text2)) return null;
17319
+ const parsed = Date.parse(`${text2}T00:00:00.000Z`);
16025
17320
  if (Number.isNaN(parsed)) return null;
16026
- return toDay(parsed) === text ? parsed : null;
17321
+ return toDay(parsed) === text2 ? parsed : null;
16027
17322
  }
16028
17323
  function toDay(value) {
16029
17324
  return new Date(value).toISOString().slice(0, 10);
@@ -16263,7 +17558,7 @@ function urlsCommand() {
16263
17558
  );
16264
17559
  });
16265
17560
  }
16266
- 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("realtime", "Show realtime analytics")).addCommand(analyticsSubcommand("entry-exit", "Show entry and exit page analytics", 30)).addCommand(consentRateCommand).addCommand(urlsCommand()).addHelpText(
17561
+ var analyticsCommand = new Command("analytics").description("Read shop analytics").addCommand(orderJourneyCommand).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("realtime", "Show realtime analytics")).addCommand(analyticsSubcommand("entry-exit", "Show entry and exit page analytics", 30)).addCommand(consentRateCommand).addCommand(urlsCommand()).addHelpText(
16267
17562
  "after",
16268
17563
  `
16269
17564
  Examples:
@@ -16275,7 +17570,7 @@ Examples:
16275
17570
  );
16276
17571
 
16277
17572
  // src/commands/install.ts
16278
- var import_promises3 = require("node:dns/promises");
17573
+ var import_promises4 = require("node:dns/promises");
16279
17574
  var import_node_http2 = require("node:http");
16280
17575
  var import_node_https = require("node:https");
16281
17576
  var import_node_net = require("node:net");
@@ -16284,6 +17579,23 @@ var DESKTOP_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/53
16284
17579
  var PRODUCTION_WORKER_BASE_URL2 = "https://events.drip-apex.com";
16285
17580
  var UrlPolicyError = class extends Error {
16286
17581
  };
17582
+ var IdentityProxyError = class extends Error {
17583
+ constructor(originalUrl, proxyHost) {
17584
+ super(
17585
+ `${originalUrl} redirects to ${proxyHost}, an identity-aware proxy sign-in; the install check cannot reach a storefront behind it`
17586
+ );
17587
+ this.originalUrl = originalUrl;
17588
+ this.proxyHost = proxyHost;
17589
+ }
17590
+ originalUrl;
17591
+ proxyHost;
17592
+ };
17593
+ var IDENTITY_PROXY_HOSTS = ["accounts.google.com", "login.microsoftonline.com"];
17594
+ var IDENTITY_PROXY_SUFFIXES = [".cloudflareaccess.com"];
17595
+ function identityProxyHost(host) {
17596
+ const normalized = normalizeHostname(host);
17597
+ return IDENTITY_PROXY_HOSTS.includes(normalized) || IDENTITY_PROXY_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
17598
+ }
16287
17599
  function normalizeHostname(host) {
16288
17600
  return host.toLowerCase().replace(/\.$/, "");
16289
17601
  }
@@ -16316,7 +17628,7 @@ var testResolver;
16316
17628
  var testFetch;
16317
17629
  async function resolveHost(hostname) {
16318
17630
  const mocked = testResolver?.(hostname) ?? null;
16319
- const addresses = mocked ?? await (0, import_promises3.lookup)(hostname, { all: true });
17631
+ const addresses = mocked ?? await (0, import_promises4.lookup)(hostname, { all: true });
16320
17632
  return addresses.map((entry) => entry.address);
16321
17633
  }
16322
17634
  async function validateUrl(raw, allowedHosts) {
@@ -16427,7 +17739,7 @@ function overLimitMessage(hostname, maxBytes) {
16427
17739
  return `Response from ${hostname} exceeded the ${maxBytes}-byte cap`;
16428
17740
  }
16429
17741
  function responseFromIncoming(res, body5, decoded) {
16430
- const status2 = res.statusCode ?? 0;
17742
+ const status3 = res.statusCode ?? 0;
16431
17743
  const headers = new Headers();
16432
17744
  for (const [key, value] of Object.entries(res.headers)) {
16433
17745
  if (Array.isArray(value)) for (const entry of value) headers.append(key, entry);
@@ -16437,9 +17749,9 @@ function responseFromIncoming(res, body5, decoded) {
16437
17749
  headers.delete("content-encoding");
16438
17750
  headers.delete("content-length");
16439
17751
  }
16440
- const nullBody = status2 === 101 || status2 === 204 || status2 === 205 || status2 === 304;
17752
+ const nullBody = status3 === 101 || status3 === 204 || status3 === 205 || status3 === 304;
16441
17753
  return new Response(nullBody ? null : body5, {
16442
- status: status2,
17754
+ status: status3,
16443
17755
  statusText: res.statusMessage ?? "",
16444
17756
  headers
16445
17757
  });
@@ -16574,7 +17886,10 @@ async function fetchSafe(raw, allowedHosts, limits) {
16574
17886
  if (![301, 302, 303, 307, 308].includes(result.response.status)) return result;
16575
17887
  const location = result.response.headers.get("location");
16576
17888
  if (!location) return result;
16577
- target = await validateUrl(new URL(location, target.url).toString(), allowedHosts);
17889
+ const next = new URL(location, target.url);
17890
+ if (identityProxyHost(next.hostname))
17891
+ throw new IdentityProxyError(raw, normalizeHostname(next.hostname));
17892
+ target = await validateUrl(next.toString(), allowedHosts);
16578
17893
  }
16579
17894
  throw new UrlPolicyError("Redirect limit exceeded (5)");
16580
17895
  }
@@ -16584,7 +17899,7 @@ function detectChallenge(html) {
16584
17899
  );
16585
17900
  }
16586
17901
  var LEGACY_SNIPPET_MARKER = /sdk\.drip-apex\.com|events\.drip-apex\.com|\/drip\.js\b|\bDrip\.init\s*\(|\bwindow\.drip\b/i;
16587
- function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTruncated) {
17902
+ function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTruncated) {
16588
17903
  const challenge = detectChallenge(html);
16589
17904
  const shopIdMatches = html.includes(shopId);
16590
17905
  const snippetPresent = workerSnippetPresent || LEGACY_SNIPPET_MARKER.test(html) && shopIdMatches;
@@ -16592,29 +17907,29 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
16592
17907
  return {
16593
17908
  url,
16594
17909
  status: "WARN",
16595
- httpStatus: status2,
17910
+ httpStatus: status3,
16596
17911
  snippetPresent,
16597
17912
  shopIdMatches,
16598
17913
  botChallenge: true,
16599
17914
  bodyTruncated,
16600
17915
  message: "Bot challenge detected; verify in a real browser."
16601
17916
  };
16602
- if (status2 < 200 || status2 >= 300)
17917
+ if (status3 < 200 || status3 >= 300)
16603
17918
  return {
16604
17919
  url,
16605
17920
  status: "FAIL",
16606
- httpStatus: status2,
17921
+ httpStatus: status3,
16607
17922
  snippetPresent,
16608
17923
  shopIdMatches,
16609
17924
  botChallenge: false,
16610
17925
  bodyTruncated,
16611
- message: `Storefront page returned HTTP ${status2}; the install cannot be verified from this response.`
17926
+ message: `Storefront page returned HTTP ${status3}; the install cannot be verified from this response.`
16612
17927
  };
16613
17928
  if (!snippetPresent)
16614
17929
  return {
16615
17930
  url,
16616
17931
  status: "FAIL",
16617
- httpStatus: status2,
17932
+ httpStatus: status3,
16618
17933
  snippetPresent,
16619
17934
  shopIdMatches,
16620
17935
  botChallenge: false,
@@ -16625,7 +17940,7 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
16625
17940
  return {
16626
17941
  url,
16627
17942
  status: "FAIL",
16628
- httpStatus: status2,
17943
+ httpStatus: status3,
16629
17944
  snippetPresent,
16630
17945
  shopIdMatches,
16631
17946
  botChallenge: false,
@@ -16635,7 +17950,7 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
16635
17950
  return {
16636
17951
  url,
16637
17952
  status: "PASS",
16638
- httpStatus: status2,
17953
+ httpStatus: status3,
16639
17954
  snippetPresent,
16640
17955
  shopIdMatches,
16641
17956
  botChallenge: false,
@@ -16832,6 +18147,18 @@ var installCommand = new Command("install").description("Check Apex storefront i
16832
18147
  pageReport(url, shopId, response.status, html, derivedWorkerBaseUrl !== null, truncated)
16833
18148
  );
16834
18149
  } catch (error) {
18150
+ if (error instanceof IdentityProxyError) {
18151
+ pages.push({
18152
+ url,
18153
+ status: "WARN",
18154
+ snippetPresent: false,
18155
+ shopIdMatches: false,
18156
+ botChallenge: false,
18157
+ blockedBy: "identity_proxy",
18158
+ message: `blocked_by_identity_proxy: ${error.message}. Verify from a signed-in browser.`
18159
+ });
18160
+ continue;
18161
+ }
16835
18162
  if (error instanceof UrlPolicyError) fail(this, error.message, EXIT_CODES.validation);
16836
18163
  if (error instanceof BodyDecodeError) {
16837
18164
  pages.push({
@@ -16939,18 +18266,18 @@ var installCommand = new Command("install").description("Check Apex storefront i
16939
18266
 
16940
18267
  // src/commands/flags.ts
16941
18268
  init_source();
16942
- var import_node_fs17 = __toESM(require("node:fs"));
16943
- var import_node_path15 = __toESM(require("node:path"));
18269
+ var import_node_fs19 = __toESM(require("node:fs"));
18270
+ var import_node_path16 = __toESM(require("node:path"));
16944
18271
 
16945
18272
  // src/edge-status.ts
16946
18273
  function getEdgeDeliveryStatus(deliveryType, expectedWorstCaseSeconds = 360) {
16947
18274
  return deliveryType === "api" ? { edge_delivery: "not_applicable", message: `API-delivered flag \u2014 edge delivery is not applicable. A previously cached visual payload may remain at some PoPs for up to ~${expectedWorstCaseSeconds}s; a single-vantage status cannot prove global propagation.` } : { edge_delivery: "applicable", message: null };
16948
18275
  }
16949
18276
  function parsePublicationMetadata(value) {
16950
- const record2 = value && typeof value === "object" ? value : {};
18277
+ const record3 = value && typeof value === "object" ? value : {};
16951
18278
  return {
16952
- publication_revision: Number.isSafeInteger(record2.publication_revision) && record2.publication_revision >= 0 ? record2.publication_revision : null,
16953
- generated_at: typeof record2.generated_at === "string" && !Number.isNaN(Date.parse(record2.generated_at)) ? record2.generated_at : null
18279
+ publication_revision: Number.isSafeInteger(record3.publication_revision) && record3.publication_revision >= 0 ? record3.publication_revision : null,
18280
+ generated_at: typeof record3.generated_at === "string" && !Number.isNaN(Date.parse(record3.generated_at)) ? record3.generated_at : null
16954
18281
  };
16955
18282
  }
16956
18283
  function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSeconds, flagUpdatedAtValue, confirmedAt = (/* @__PURE__ */ new Date()).toISOString()) {
@@ -16967,13 +18294,13 @@ function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSecond
16967
18294
  if (edge.publication_revision < current.publication_revision) return { ...base, verdict: "propagating", relationship: "edge_behind" };
16968
18295
  return { ...base, verdict: "live", relationship: edge.publication_revision > current.publication_revision ? "edge_ahead" : "equal" };
16969
18296
  }
16970
- function formatEdgePropagationStatus(status2) {
18297
+ function formatEdgePropagationStatus(status3) {
16971
18298
  const caveat = "This checks one CDN vantage point; other PoPs may lag by up to max-age.";
16972
- if (status2.verdict === "pending_publication") return `Pending publication \u2014 flag updated ${status2.flag_updated_at}, after the latest publication generated ${status2.current.generated_at ?? "never"}; the latest change is not live. After publication, allow ~${status2.expected_worst_case_seconds}s for propagation. ${caveat}`;
16973
- if (status2.verdict === "unknown") return `Unknown \u2014 edge payload predates publication metadata. ${caveat}`;
16974
- if (status2.verdict === "propagating") return `Propagating \u2014 edge revision ${status2.edge.publication_revision} (generated ${status2.edge.generated_at}) vs current revision ${status2.current.publication_revision} (generated ${status2.current.generated_at}); expected within ~${status2.expected_worst_case_seconds}s after generation. ${caveat}`;
16975
- const anomaly = status2.relationship === "edge_ahead" ? ` (edge is ahead of current revision ${status2.current.publication_revision}; anomalous)` : "";
16976
- return `Live at edge \u2014 revision ${status2.edge.publication_revision} (generated ${status2.edge.generated_at}) confirmed at edge at ${status2.confirmed_at}${anomaly}. ${caveat}`;
18299
+ if (status3.verdict === "pending_publication") return `Pending publication \u2014 flag updated ${status3.flag_updated_at}, after the latest publication generated ${status3.current.generated_at ?? "never"}; the latest change is not live. After publication, allow ~${status3.expected_worst_case_seconds}s for propagation. ${caveat}`;
18300
+ if (status3.verdict === "unknown") return `Unknown \u2014 edge payload predates publication metadata. ${caveat}`;
18301
+ if (status3.verdict === "propagating") return `Propagating \u2014 edge revision ${status3.edge.publication_revision} (generated ${status3.edge.generated_at}) vs current revision ${status3.current.publication_revision} (generated ${status3.current.generated_at}); expected within ~${status3.expected_worst_case_seconds}s after generation. ${caveat}`;
18302
+ const anomaly = status3.relationship === "edge_ahead" ? ` (edge is ahead of current revision ${status3.current.publication_revision}; anomalous)` : "";
18303
+ return `Live at edge \u2014 revision ${status3.edge.publication_revision} (generated ${status3.edge.generated_at}) confirmed at edge at ${status3.confirmed_at}${anomaly}. ${caveat}`;
16977
18304
  }
16978
18305
 
16979
18306
  // src/commands/flags.ts
@@ -16982,9 +18309,9 @@ function body(command, value, opts) {
16982
18309
  let parsed = {};
16983
18310
  if (value) {
16984
18311
  const source = value.startsWith("@") ? value.slice(1) : value;
16985
- 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;
18312
+ const text2 = value.startsWith("@") || import_node_fs19.default.existsSync(source) ? import_node_fs19.default.readFileSync(import_node_path16.default.resolve(process.cwd(), source), "utf8") : value;
16986
18313
  try {
16987
- parsed = JSON.parse(text);
18314
+ parsed = JSON.parse(text2);
16988
18315
  } catch (e) {
16989
18316
  fail(
16990
18317
  command,
@@ -17032,7 +18359,7 @@ var get5 = new Command("get").description("Get a flag").argument("<id>").action(
17032
18359
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
17033
18360
  output(this, r.data, () => detail(r.data));
17034
18361
  });
17035
- var status = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
18362
+ var status2 = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
17036
18363
  const r = await getFlagStatus(requireCredentials(), id);
17037
18364
  if (!r.ok || !r.data)
17038
18365
  fail(this, r.error ?? "Failed to get flag status", classifyStatus(r.status), r.errorDetails);
@@ -17103,7 +18430,7 @@ function lifecycle(action74) {
17103
18430
  output(this, r.data, () => detail(r.data));
17104
18431
  });
17105
18432
  }
17106
- var flagsCommand = new Command("flags").description("Manage flags").addCommand(list6).addCommand(get5).addCommand(status).addCommand(write("create")).addCommand(write("update")).addCommand(lifecycle("activate")).addCommand(lifecycle("pause")).addCommand(lifecycle("archive")).addHelpText(
18433
+ var flagsCommand = new Command("flags").description("Manage flags").addCommand(list6).addCommand(get5).addCommand(status2).addCommand(write("create")).addCommand(write("update")).addCommand(lifecycle("activate")).addCommand(lifecycle("pause")).addCommand(lifecycle("archive")).addHelpText(
17107
18434
  "after",
17108
18435
  `
17109
18436
  Examples:
@@ -17115,8 +18442,8 @@ Examples:
17115
18442
  );
17116
18443
 
17117
18444
  // src/commands/backlog.ts
17118
- var import_node_fs18 = __toESM(require("node:fs"));
17119
- var import_node_path16 = __toESM(require("node:path"));
18445
+ var import_node_fs20 = __toESM(require("node:fs"));
18446
+ var import_node_path17 = __toESM(require("node:path"));
17120
18447
  init_source();
17121
18448
  var kind2 = "backlog";
17122
18449
  var singular = "backlog item";
@@ -17124,8 +18451,8 @@ function parse(command, value) {
17124
18451
  if (!value) return {};
17125
18452
  try {
17126
18453
  const source = value.startsWith("@") ? value.slice(1) : value;
17127
- 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;
17128
- const body5 = JSON.parse(text);
18454
+ const text2 = value.startsWith("@") || import_node_fs20.default.existsSync(source) ? import_node_fs20.default.readFileSync(import_node_path17.default.resolve(process.cwd(), source), "utf8") : value;
18455
+ const body5 = JSON.parse(text2);
17129
18456
  if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
17130
18457
  throw new Error("body must be an object");
17131
18458
  return body5;
@@ -17401,8 +18728,8 @@ var fields2 = new Command("fields").description("Manage the shop's own idea fiel
17401
18728
  var backlogCommand = new Command("backlog").description("Manage backlog").addCommand(list7).addCommand(get6).addCommand(write2("create")).addCommand(write2("update")).addCommand(archive).addCommand(restore).addCommand(remove2).addCommand(fields2);
17402
18729
 
17403
18730
  // src/commands/signals.ts
17404
- var import_node_fs19 = __toESM(require("node:fs"));
17405
- var import_node_path17 = __toESM(require("node:path"));
18731
+ var import_node_fs21 = __toESM(require("node:fs"));
18732
+ var import_node_path18 = __toESM(require("node:path"));
17406
18733
  init_source();
17407
18734
  var kind3 = "signals";
17408
18735
  var singular2 = "signal";
@@ -17410,8 +18737,8 @@ function parse2(command, value) {
17410
18737
  if (!value) return {};
17411
18738
  try {
17412
18739
  const source = value.startsWith("@") ? value.slice(1) : value;
17413
- const text = value.startsWith("@") || import_node_fs19.default.existsSync(source) ? import_node_fs19.default.readFileSync(import_node_path17.default.resolve(process.cwd(), source), "utf8") : value;
17414
- const body5 = JSON.parse(text);
18740
+ const text2 = value.startsWith("@") || import_node_fs21.default.existsSync(source) ? import_node_fs21.default.readFileSync(import_node_path18.default.resolve(process.cwd(), source), "utf8") : value;
18741
+ const body5 = JSON.parse(text2);
17415
18742
  if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
17416
18743
  throw new Error("body must be an object");
17417
18744
  return body5;
@@ -17492,16 +18819,16 @@ var signalsCommand = new Command("signals").description("Manage signals").addCom
17492
18819
 
17493
18820
  // src/commands/personalizations.ts
17494
18821
  init_source();
17495
- var import_node_fs20 = __toESM(require("node:fs"));
17496
- var import_node_path18 = __toESM(require("node:path"));
18822
+ var import_node_fs22 = __toESM(require("node:fs"));
18823
+ var import_node_path19 = __toESM(require("node:path"));
17497
18824
  var kind4 = "personalizations";
17498
18825
  function body2(command, value, opts) {
17499
18826
  let parsed = {};
17500
18827
  if (value) {
17501
18828
  const source = value.startsWith("@") ? value.slice(1) : value;
17502
- const text = value.startsWith("@") || import_node_fs20.default.existsSync(source) ? import_node_fs20.default.readFileSync(import_node_path18.default.resolve(process.cwd(), source), "utf8") : value;
18829
+ const text2 = value.startsWith("@") || import_node_fs22.default.existsSync(source) ? import_node_fs22.default.readFileSync(import_node_path19.default.resolve(process.cwd(), source), "utf8") : value;
17503
18830
  try {
17504
- parsed = JSON.parse(text);
18831
+ parsed = JSON.parse(text2);
17505
18832
  } catch (e) {
17506
18833
  fail(
17507
18834
  command,
@@ -17581,14 +18908,14 @@ var personalizationsCommand = new Command("personalizations").description("Manag
17581
18908
 
17582
18909
  // src/commands/targeting-resources.ts
17583
18910
  init_source();
17584
- var import_node_fs21 = __toESM(require("node:fs"));
17585
- var import_node_path19 = __toESM(require("node:path"));
18911
+ var import_node_fs23 = __toESM(require("node:fs"));
18912
+ var import_node_path20 = __toESM(require("node:path"));
17586
18913
  function body3(command, value) {
17587
18914
  if (!value) return {};
17588
18915
  const source = value.startsWith("@") ? value.slice(1) : value;
17589
18916
  try {
17590
- const text = value.startsWith("@") || import_node_fs21.default.existsSync(source) ? import_node_fs21.default.readFileSync(import_node_path19.default.resolve(process.cwd(), source), "utf8") : value;
17591
- return JSON.parse(text);
18917
+ const text2 = value.startsWith("@") || import_node_fs23.default.existsSync(source) ? import_node_fs23.default.readFileSync(import_node_path20.default.resolve(process.cwd(), source), "utf8") : value;
18918
+ return JSON.parse(text2);
17592
18919
  } catch (error) {
17593
18920
  fail(
17594
18921
  command,
@@ -17830,8 +19157,8 @@ var targetingCommand = new Command("targeting").description("Test experiment tar
17830
19157
 
17831
19158
  // src/commands/sos.ts
17832
19159
  init_source();
17833
- var import_node_fs22 = __toESM(require("node:fs"));
17834
- var import_node_path20 = __toESM(require("node:path"));
19160
+ var import_node_fs24 = __toESM(require("node:fs"));
19161
+ var import_node_path21 = __toESM(require("node:path"));
17835
19162
  var SOS_TYPES = /* @__PURE__ */ new Set([
17836
19163
  "full_handover",
17837
19164
  "design",
@@ -17844,7 +19171,7 @@ var SOS_TYPES = /* @__PURE__ */ new Set([
17844
19171
  function readText(value) {
17845
19172
  if (!value) return void 0;
17846
19173
  if (!value.startsWith("@")) return value;
17847
- return import_node_fs22.default.readFileSync(import_node_path20.default.resolve(process.cwd(), value.slice(1)), "utf8");
19174
+ return import_node_fs24.default.readFileSync(import_node_path21.default.resolve(process.cwd(), value.slice(1)), "utf8");
17848
19175
  }
17849
19176
  function printTicket(ticket, reused) {
17850
19177
  console.log();
@@ -17982,14 +19309,14 @@ var markRead = new Command("mark-read").option("--channel <key>", "Mark one chan
17982
19309
  var inboxCommand = new Command("inbox").description("Read and send inbox messages").addCommand(list11).addCommand(send2).addCommand(markRead);
17983
19310
 
17984
19311
  // src/commands/shops.ts
17985
- var import_node_fs23 = __toESM(require("node:fs"));
17986
- var import_node_path21 = __toESM(require("node:path"));
19312
+ var import_node_fs25 = __toESM(require("node:fs"));
19313
+ var import_node_path22 = __toESM(require("node:path"));
17987
19314
  function body4(command, raw) {
17988
19315
  if (!raw) return {};
17989
19316
  try {
17990
19317
  const source = raw.startsWith("@") ? raw.slice(1) : raw;
17991
- const text = raw.startsWith("@") || import_node_fs23.default.existsSync(source) ? import_node_fs23.default.readFileSync(import_node_path21.default.resolve(source), "utf8") : raw;
17992
- const value = JSON.parse(text);
19318
+ const text2 = raw.startsWith("@") || import_node_fs25.default.existsSync(source) ? import_node_fs25.default.readFileSync(import_node_path22.default.resolve(source), "utf8") : raw;
19319
+ const value = JSON.parse(text2);
17993
19320
  if (!value || typeof value !== "object" || Array.isArray(value))
17994
19321
  throw new Error("body must be an object");
17995
19322
  return value;
@@ -18370,7 +19697,7 @@ function parseContainer(source, id, dataLayerName = "dataLayer") {
18370
19697
  vtpPaths,
18371
19698
  ecommerceFrom: [...ecommerce][0] ?? null,
18372
19699
  serverContainerUrl: [...serverUrls][0] ?? null,
18373
- piiPaths: vtpPaths.filter((path24) => /email|phone|address|name/i.test(path24))
19700
+ piiPaths: vtpPaths.filter((path25) => /email|phone|address|name/i.test(path25))
18374
19701
  }
18375
19702
  };
18376
19703
  }
@@ -18439,11 +19766,11 @@ function isContainerField(field) {
18439
19766
  const types = baseFieldTypes(field);
18440
19767
  return types.has("obj") || types.has("arr");
18441
19768
  }
18442
- function hasSyntheticPlaceholder(path24) {
18443
- return path24.split(".").map((segment) => segment.replace(/\[\]$/, "")).some((segment) => /^<[^>]+>$/.test(segment));
19769
+ function hasSyntheticPlaceholder(path25) {
19770
+ return path25.split(".").map((segment) => segment.replace(/\[\]$/, "")).some((segment) => /^<[^>]+>$/.test(segment));
18444
19771
  }
18445
- function ambiguousDottedPrefix(event, path24) {
18446
- const segments = path24.split(".");
19772
+ function ambiguousDottedPrefix(event, path25) {
19773
+ const segments = path25.split(".");
18447
19774
  for (let index = 1; index < segments.length; index += 1) {
18448
19775
  const prefix = segments.slice(0, index).join(".");
18449
19776
  const parent = event.fields.find((field) => field.path === prefix);
@@ -18452,42 +19779,42 @@ function ambiguousDottedPrefix(event, path24) {
18452
19779
  return void 0;
18453
19780
  }
18454
19781
  function dimensionEligibility(event, field) {
18455
- const path24 = field.path;
18456
- if (hasSyntheticPlaceholder(path24)) {
19782
+ const path25 = field.path;
19783
+ if (hasSyntheticPlaceholder(path25)) {
18457
19784
  return {
18458
19785
  eligible: false,
18459
- message: `Path ${JSON.stringify(path24)} is a synthetic placeholder and cannot create a dimension. Choose an observed scalar path.`
19786
+ message: `Path ${JSON.stringify(path25)} is a synthetic placeholder and cannot create a dimension. Choose an observed scalar path.`
18460
19787
  };
18461
19788
  }
18462
19789
  if (isContainerField(field)) {
18463
19790
  return {
18464
19791
  eligible: false,
18465
- message: `Path ${JSON.stringify(path24)} is container-typed. Choose an observed scalar path.`
19792
+ message: `Path ${JSON.stringify(path25)} is container-typed. Choose an observed scalar path.`
18466
19793
  };
18467
19794
  }
18468
- if (path24.includes("[]")) {
19795
+ if (path25.includes("[]")) {
18469
19796
  return {
18470
19797
  eligible: false,
18471
- message: `Path ${JSON.stringify(path24)} includes an array-element segment and cannot create a reliable dimension. Choose a scalar path outside an array.`
19798
+ message: `Path ${JSON.stringify(path25)} includes an array-element segment and cannot create a reliable dimension. Choose a scalar path outside an array.`
18472
19799
  };
18473
19800
  }
18474
19801
  if (!isStableField(field)) {
18475
19802
  return {
18476
19803
  eligible: false,
18477
- message: `Path ${JSON.stringify(path24)} does not have a stable observed type. Choose a path without a type collision.`
19804
+ message: `Path ${JSON.stringify(path25)} does not have a stable observed type. Choose a path without a type collision.`
18478
19805
  };
18479
19806
  }
18480
19807
  if (!event.event) {
18481
19808
  return {
18482
19809
  eligible: false,
18483
- message: `Path ${JSON.stringify(path24)} has no observed event name. Choose a path from a named event.`
19810
+ message: `Path ${JSON.stringify(path25)} has no observed event name. Choose a path from a named event.`
18484
19811
  };
18485
19812
  }
18486
- const ambiguousPrefix = ambiguousDottedPrefix(event, path24);
19813
+ const ambiguousPrefix = ambiguousDottedPrefix(event, path25);
18487
19814
  if (ambiguousPrefix) {
18488
19815
  return {
18489
19816
  eligible: false,
18490
- message: `Path ${JSON.stringify(path24)} could be a literal key containing dots because the discovery report has no observed container at ${JSON.stringify(ambiguousPrefix)}. Apex cannot safely create a capture for it.`
19817
+ message: `Path ${JSON.stringify(path25)} could be a literal key containing dots because the discovery report has no observed container at ${JSON.stringify(ambiguousPrefix)}. Apex cannot safely create a capture for it.`
18491
19818
  };
18492
19819
  }
18493
19820
  return { eligible: true };
@@ -18496,24 +19823,24 @@ function isNumericField(field) {
18496
19823
  const numericTypes = /* @__PURE__ */ new Set(["num", "numstr", "numstr_comma"]);
18497
19824
  return field.types.length > 0 && field.types.every((type) => numericTypes.has(type.split(":")[0]));
18498
19825
  }
18499
- function defaultDimensionLabel(path24) {
18500
- const words = path24.split(/[._-]+/).filter(Boolean).join(" ");
18501
- return words ? `${words[0].toUpperCase()}${words.slice(1)}` : path24;
19826
+ function defaultDimensionLabel(path25) {
19827
+ const words = path25.split(/[._-]+/).filter(Boolean).join(" ");
19828
+ return words ? `${words[0].toUpperCase()}${words.slice(1)}` : path25;
18502
19829
  }
18503
19830
  function quoteShellArgument(value) {
18504
19831
  return "'" + value.replaceAll("'", "'\\''") + "'";
18505
19832
  }
18506
- function createDimensionInvocation(path24, eventName) {
18507
- return `apex discover --create-dimension ${quoteShellArgument(path24)} --event ${quoteShellArgument(eventName)}`;
19833
+ function createDimensionInvocation(path25, eventName) {
19834
+ return `apex discover --create-dimension ${quoteShellArgument(path25)} --event ${quoteShellArgument(eventName)}`;
18508
19835
  }
18509
- function resolveDimensionObservation(command, report, path24, eventName) {
19836
+ function resolveDimensionObservation(command, report, path25, eventName) {
18510
19837
  const matches = report.data_layer.flatMap(
18511
- (event) => event.fields.filter((field) => field.path === path24).map((field) => ({ event, field }))
19838
+ (event) => event.fields.filter((field) => field.path === path25).map((field) => ({ event, field }))
18512
19839
  );
18513
19840
  if (!matches.length) {
18514
19841
  fail(
18515
19842
  command,
18516
- `Path ${JSON.stringify(path24)} was not found in the current discovery report. Run apex discover and copy an observed path exactly.`,
19843
+ `Path ${JSON.stringify(path25)} was not found in the current discovery report. Run apex discover and copy an observed path exactly.`,
18517
19844
  EXIT_CODES.validation
18518
19845
  );
18519
19846
  }
@@ -18523,7 +19850,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
18523
19850
  if (!eventMatches.length) {
18524
19851
  fail(
18525
19852
  command,
18526
- `Path ${JSON.stringify(path24)} was not observed on event ${JSON.stringify(eventName)}. Run apex discover and copy the event name exactly.`,
19853
+ `Path ${JSON.stringify(path25)} was not observed on event ${JSON.stringify(eventName)}. Run apex discover and copy the event name exactly.`,
18527
19854
  EXIT_CODES.validation
18528
19855
  );
18529
19856
  }
@@ -18531,7 +19858,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
18531
19858
  if (layers.length > 1) {
18532
19859
  fail(
18533
19860
  command,
18534
- `Path ${JSON.stringify(path24)} on event ${JSON.stringify(eventName)} was observed on multiple data layers: ${layers.join(", ")}. Dimensions bind to one layer; this ambiguity cannot be resolved automatically \u2014 capture the value under one layer, or define the dimension in the dashboard with the data layer name set explicitly.`,
19861
+ `Path ${JSON.stringify(path25)} on event ${JSON.stringify(eventName)} was observed on multiple data layers: ${layers.join(", ")}. Dimensions bind to one layer; this ambiguity cannot be resolved automatically \u2014 capture the value under one layer, or define the dimension in the dashboard with the data layer name set explicitly.`,
18535
19862
  EXIT_CODES.validation
18536
19863
  );
18537
19864
  }
@@ -18543,7 +19870,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
18543
19870
  if (namedEvents.length > 1) {
18544
19871
  fail(
18545
19872
  command,
18546
- `Path ${JSON.stringify(path24)} was observed on multiple events: ${namedEvents.join(", ")}. Retry with --event <name>.`,
19873
+ `Path ${JSON.stringify(path25)} was observed on multiple events: ${namedEvents.join(", ")}. Retry with --event <name>.`,
18547
19874
  EXIT_CODES.validation
18548
19875
  );
18549
19876
  }
@@ -18552,7 +19879,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
18552
19879
  if (layers.length > 1) {
18553
19880
  fail(
18554
19881
  command,
18555
- `Path ${JSON.stringify(path24)} on event ${JSON.stringify(namedEvents[0] ?? "")} was observed on multiple data layers: ${layers.join(", ")}. Dimensions bind to one layer; this ambiguity cannot be resolved automatically \u2014 capture the value under one layer, or define the dimension in the dashboard with the data layer name set explicitly.`,
19882
+ `Path ${JSON.stringify(path25)} on event ${JSON.stringify(namedEvents[0] ?? "")} was observed on multiple data layers: ${layers.join(", ")}. Dimensions bind to one layer; this ambiguity cannot be resolved automatically \u2014 capture the value under one layer, or define the dimension in the dashboard with the data layer name set explicitly.`,
18556
19883
  EXIT_CODES.validation
18557
19884
  );
18558
19885
  }
@@ -18587,9 +19914,9 @@ function renderDiscovery(report) {
18587
19914
  `
18588
19915
  ${event.event || "(unnamed event)"} [${event.dlName}] \u2014 ${event.count} observation(s)${event.matched ? " \u2014 matched" : ""}; t_ms p50=${event.t_ms_p50 ?? "n/a"}, p95=${event.t_ms_p95 ?? "n/a"}, pre-init=${event.pre_init_count}`
18589
19916
  );
18590
- for (const path24 of event.paths)
19917
+ for (const path25 of event.paths)
18591
19918
  lines.push(
18592
- ` URL ${path24.path}: ${path24.pageviews} pageviews, ${path24.count} pushes, ${path24.pspReturn_count} PSP returns`
19919
+ ` URL ${path25.path}: ${path25.pageviews} pageviews, ${path25.count} pushes, ${path25.pspReturn_count} PSP returns`
18593
19920
  );
18594
19921
  for (const field of event.fields) {
18595
19922
  lines.push(
@@ -18767,11 +20094,11 @@ var discoverCommand = new Command("discover").description("Read back the trackin
18767
20094
  );
18768
20095
  }
18769
20096
  if (!created.ok || !created.data) {
18770
- const reason = created.error ?? "The signals API rejected the request";
20097
+ const reason2 = created.error ?? "The signals API rejected the request";
18771
20098
  if (created.status === 409) {
18772
20099
  fail(
18773
20100
  this,
18774
- `Dimension was not created: ${reason} Choose another --key or archive an existing dimension in Audience > Attributes, then retry.`,
20101
+ `Dimension was not created: ${reason2} Choose another --key or archive an existing dimension in Audience > Attributes, then retry.`,
18775
20102
  classifyStatus(created.status),
18776
20103
  created.errorDetails
18777
20104
  );
@@ -18779,14 +20106,14 @@ var discoverCommand = new Command("discover").description("Read back the trackin
18779
20106
  if (created.status === 400) {
18780
20107
  fail(
18781
20108
  this,
18782
- `Dimension was not created: ${reason} Check --create-dimension, --event, --key, and --label, then retry.`,
20109
+ `Dimension was not created: ${reason2} Check --create-dimension, --event, --key, and --label, then retry.`,
18783
20110
  classifyStatus(created.status),
18784
20111
  created.errorDetails
18785
20112
  );
18786
20113
  }
18787
20114
  fail(
18788
20115
  this,
18789
- `Dimension was not created: ${reason}`,
20116
+ `Dimension was not created: ${reason2}`,
18790
20117
  classifyStatus(created.status),
18791
20118
  created.errorDetails
18792
20119
  );
@@ -18802,16 +20129,16 @@ var discoverCommand = new Command("discover").description("Read back the trackin
18802
20129
  });
18803
20130
 
18804
20131
  // src/commands/skills.ts
18805
- var import_node_fs24 = __toESM(require("node:fs"));
18806
- var import_node_path22 = __toESM(require("node:path"));
20132
+ var import_node_fs26 = __toESM(require("node:fs"));
20133
+ var import_node_path23 = __toESM(require("node:path"));
18807
20134
  function packageRoot() {
18808
20135
  const candidates = [
18809
- import_node_path22.default.resolve(__dirname, ".."),
18810
- import_node_path22.default.resolve(import_node_path22.default.dirname(process.argv[1] ?? process.cwd()), ".."),
18811
- import_node_path22.default.resolve(process.cwd(), "packages/cli")
20136
+ import_node_path23.default.resolve(__dirname, ".."),
20137
+ import_node_path23.default.resolve(import_node_path23.default.dirname(process.argv[1] ?? process.cwd()), ".."),
20138
+ import_node_path23.default.resolve(process.cwd(), "packages/cli")
18812
20139
  ];
18813
20140
  for (const candidate of candidates) {
18814
- if (import_node_fs24.default.existsSync(import_node_path22.default.join(candidate, "AGENTS.md"))) return candidate;
20141
+ if (import_node_fs26.default.existsSync(import_node_path23.default.join(candidate, "AGENTS.md"))) return candidate;
18815
20142
  }
18816
20143
  throw new Error(
18817
20144
  `Apex CLI package files not found. Looked for AGENTS.md in: ${candidates.join(", ")}`
@@ -18820,19 +20147,19 @@ function packageRoot() {
18820
20147
  function skillPaths() {
18821
20148
  const root = packageRoot();
18822
20149
  return {
18823
- agents: import_node_path22.default.join(root, "AGENTS.md"),
18824
- skill: import_node_path22.default.join(root, "skills", "apex-cli", "SKILL.md")
20150
+ agents: import_node_path23.default.join(root, "AGENTS.md"),
20151
+ skill: import_node_path23.default.join(root, "skills", "apex-cli", "SKILL.md")
18825
20152
  };
18826
20153
  }
18827
20154
  function installSkill(paths, targetRoot) {
18828
- const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs24.default.existsSync(filePath));
20155
+ const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs26.default.existsSync(filePath));
18829
20156
  if (missing.length > 0) {
18830
20157
  throw new Error(`Apex CLI package files not found. Missing: ${missing.join(", ")}`);
18831
20158
  }
18832
- const target = import_node_path22.default.resolve(targetRoot, "apex-cli");
18833
- import_node_fs24.default.mkdirSync(target, { recursive: true });
18834
- import_node_fs24.default.copyFileSync(paths.skill, import_node_path22.default.join(target, "SKILL.md"));
18835
- import_node_fs24.default.copyFileSync(paths.agents, import_node_path22.default.join(target, "AGENTS.md"));
20159
+ const target = import_node_path23.default.resolve(targetRoot, "apex-cli");
20160
+ import_node_fs26.default.mkdirSync(target, { recursive: true });
20161
+ import_node_fs26.default.copyFileSync(paths.skill, import_node_path23.default.join(target, "SKILL.md"));
20162
+ import_node_fs26.default.copyFileSync(paths.agents, import_node_path23.default.join(target, "AGENTS.md"));
18836
20163
  return target;
18837
20164
  }
18838
20165
  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 action67(opts) {
@@ -19262,12 +20589,12 @@ async function fetchText(url) {
19262
20589
  });
19263
20590
  return { status: res.status, text: await readResponseText(res), finalUrl: res.url };
19264
20591
  }
19265
- function abs(base, path24) {
19266
- return new URL(path24, base).toString();
20592
+ function abs(base, path25) {
20593
+ return new URL(path25, base).toString();
19267
20594
  }
19268
- function looksLikeShopifyCart(text) {
20595
+ function looksLikeShopifyCart(text2) {
19269
20596
  try {
19270
- const payload = JSON.parse(text);
20597
+ const payload = JSON.parse(text2);
19271
20598
  return payload !== null && typeof payload === "object" && (typeof payload.token === "string" || payload.token === null) && Array.isArray(payload.items);
19272
20599
  } catch {
19273
20600
  return false;
@@ -19283,9 +20610,9 @@ async function collectSignals(url) {
19283
20610
  const chunks = [`__apex_primary_status_${primary.status}__`, primary.text];
19284
20611
  const protectedHit = primary.status === 403 || primary.status === 429 || matchDetectionMarkers(primary.text, "protected").length > 0;
19285
20612
  if (!protectedHit) {
19286
- for (const path24 of ["/cart.js"]) {
20613
+ for (const path25 of ["/cart.js"]) {
19287
20614
  try {
19288
- const secondary = await fetchText(abs(primary.finalUrl || url, path24));
20615
+ const secondary = await fetchText(abs(primary.finalUrl || url, path25));
19289
20616
  if (secondary.status >= 200 && secondary.status < 300 && looksLikeShopifyCart(secondary.text)) {
19290
20617
  chunks.push("__apex_secondary_shopify_cart_js__", secondary.text.slice(0, 2e3));
19291
20618
  }
@@ -19580,20 +20907,20 @@ var webhooksCommand = new Command("webhooks").description("Manage shop webhooks"
19580
20907
 
19581
20908
  // src/commands/simulate.ts
19582
20909
  init_source();
19583
- var import_node_fs25 = __toESM(require("node:fs"));
19584
- var import_node_path23 = __toESM(require("node:path"));
20910
+ var import_node_fs27 = __toESM(require("node:fs"));
20911
+ var import_node_path24 = __toESM(require("node:path"));
19585
20912
  function readJson2(file) {
19586
- const text = file === "-" ? import_node_fs25.default.readFileSync(0, "utf8") : import_node_fs25.default.readFileSync(import_node_path23.default.resolve(file), "utf8");
20913
+ const text2 = file === "-" ? import_node_fs27.default.readFileSync(0, "utf8") : import_node_fs27.default.readFileSync(import_node_path24.default.resolve(file), "utf8");
19587
20914
  try {
19588
- return JSON.parse(text);
20915
+ return JSON.parse(text2);
19589
20916
  } catch (error) {
19590
20917
  throw new Error(
19591
20918
  `Invalid JSON in ${file}: ${error instanceof Error ? error.message : String(error)}`
19592
20919
  );
19593
20920
  }
19594
20921
  }
19595
- function plural(count, singular3) {
19596
- return `${count} ${singular3}${count === 1 ? "" : "s"}`;
20922
+ function plural(count2, singular3) {
20923
+ return `${count2} ${singular3}${count2 === 1 ? "" : "s"}`;
19597
20924
  }
19598
20925
  function isGtagEventTuple(value) {
19599
20926
  return Array.isArray(value) && value[0] === "event" && typeof value[1] === "string";
@@ -19705,8 +21032,8 @@ var simulateCommand = new Command("simulate").description("Simulate data-layer t
19705
21032
  });
19706
21033
 
19707
21034
  // src/commands/config-resources.ts
19708
- var import_node_fs26 = __toESM(require("node:fs"));
19709
- var import_node_path24 = __toESM(require("node:path"));
21035
+ var import_node_fs28 = __toESM(require("node:fs"));
21036
+ var import_node_path25 = __toESM(require("node:path"));
19710
21037
  function show(resource) {
19711
21038
  process.stdout.write(`${JSON.stringify(resource, null, 2)}
19712
21039
  `);
@@ -19772,12 +21099,12 @@ function readFamily(family, label) {
19772
21099
  }
19773
21100
  var assetReads = readFamily("assets", "assets");
19774
21101
  var upload = new Command("upload").description("Upload an asset").argument("<file>").action(async function(file) {
19775
- const absolute = import_node_path24.default.resolve(process.cwd(), file);
19776
- if (!import_node_fs26.default.existsSync(absolute)) fail(this, `File not found: ${file}`, 3);
19777
- const fileStat = import_node_fs26.default.statSync(absolute);
21102
+ const absolute = import_node_path25.default.resolve(process.cwd(), file);
21103
+ if (!import_node_fs28.default.existsSync(absolute)) fail(this, `File not found: ${file}`, 3);
21104
+ const fileStat = import_node_fs28.default.statSync(absolute);
19778
21105
  if (!fileStat.isFile()) fail(this, `File not found: ${file}`, 3);
19779
21106
  if (fileStat.size > MAX_ASSET_SIZE_BYTES) fail(this, `${file} is larger than 10 MB`, 3);
19780
- const mimeType = ASSET_MIME_TYPES[import_node_path24.default.extname(absolute).toLowerCase()];
21107
+ const mimeType = ASSET_MIME_TYPES[import_node_path25.default.extname(absolute).toLowerCase()];
19781
21108
  if (!mimeType)
19782
21109
  fail(
19783
21110
  this,
@@ -19786,8 +21113,8 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
19786
21113
  );
19787
21114
  const r = await uploadAssets(requireCredentials(), [
19788
21115
  {
19789
- name: import_node_path24.default.basename(absolute),
19790
- bytes: import_node_fs26.default.readFileSync(absolute),
21116
+ name: import_node_path25.default.basename(absolute),
21117
+ bytes: import_node_fs28.default.readFileSync(absolute),
19791
21118
  type: mimeType
19792
21119
  }
19793
21120
  ]);
@@ -19872,6 +21199,7 @@ program2.addCommand(roadmapCommand);
19872
21199
  program2.addCommand(harnessCommand);
19873
21200
  program2.addCommand(operatorCommand);
19874
21201
  program2.addCommand(trackingCommand);
21202
+ program2.addCommand(eventsCommand);
19875
21203
  program2.addCommand(goalsCommand);
19876
21204
  program2.addCommand(funnelsCommand);
19877
21205
  program2.addCommand(environmentsCommand);
@@ -19913,10 +21241,18 @@ try {
19913
21241
  } catch {
19914
21242
  }
19915
21243
  program2.hook("preAction", (_thisCommand, actionCommand) => {
19916
- configureInvocationContext();
21244
+ try {
21245
+ configureInvocationContext();
21246
+ } catch (error) {
21247
+ beginInvocation(actionCommand);
21248
+ throw error;
21249
+ }
19917
21250
  configureRuntime(actionCommand);
21251
+ beginInvocation(actionCommand, resolveContext());
19918
21252
  });
19919
21253
  process.on("beforeExit", () => {
19920
21254
  flushFallbackEnvelope();
19921
21255
  });
19922
- program2.parseAsync(process.argv).then(() => maybeCheckForUpdate()).catch((err) => handleCliError(err, program2));
21256
+ maybeCheckForUpdate().then(() => program2.parseAsync(process.argv)).then(async () => {
21257
+ await finishInvocation({ exitCode: process.exitCode ?? 0 });
21258
+ }).catch((err) => handleCliError(err, program2));