@drip-apex/apex-cli 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1159,8 +1159,8 @@ var require_command = __commonJS({
1159
1159
  "node_modules/commander/lib/command.js"(exports2) {
1160
1160
  var EventEmitter = require("node:events").EventEmitter;
1161
1161
  var childProcess2 = require("node:child_process");
1162
- var 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.0",
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
  }
@@ -7911,6 +8379,16 @@ function getExperimentResults(creds, id, opts) {
7911
8379
  `/api/v1/experiments/${id}/results${qs ? `?${qs}` : ""}`
7912
8380
  );
7913
8381
  }
8382
+ function getExperimentResultsExplain(creds, id, opts) {
8383
+ const params = new URLSearchParams();
8384
+ for (const key of ["currency", "goal", "order"])
8385
+ if (opts[key]) params.set(key, opts[key]);
8386
+ return request(
8387
+ creds,
8388
+ "GET",
8389
+ `/api/v1/experiments/${id}/results/explain?${params}`
8390
+ );
8391
+ }
7914
8392
  function createExperimentScreenshots(creds, id, body5) {
7915
8393
  return request(creds, "POST", `/api/v1/experiments/${id}/screenshots`, body5);
7916
8394
  }
@@ -7937,7 +8415,10 @@ function applyExperimentFilesystem(creds, id, body5) {
7937
8415
  creds,
7938
8416
  "POST",
7939
8417
  `/api/v1/experiments/${id}/filesystem/apply`,
7940
- body5
8418
+ {
8419
+ files: body5.files,
8420
+ ...body5.confirmTargetingReplace === true ? { confirmTargetingReplace: true } : {}
8421
+ }
7941
8422
  );
7942
8423
  }
7943
8424
  function getExperimentQaRun(creds, id) {
@@ -8104,6 +8585,15 @@ function verifyTracking(creds, opts) {
8104
8585
  `/api/v1/tracking/verify${qs ? `?${qs}` : ""}`
8105
8586
  );
8106
8587
  }
8588
+ function exportEventsPage(creds, opts) {
8589
+ const params = new URLSearchParams({ from: opts.from, to: opts.to });
8590
+ if (opts.experiment) params.set("experiment", opts.experiment);
8591
+ if (opts.eventTypes?.length) params.set("event_type", opts.eventTypes.join(","));
8592
+ if (opts.qa) params.set("qa", opts.qa);
8593
+ if (opts.limit) params.set("limit", String(opts.limit));
8594
+ if (opts.cursor) params.set("cursor", opts.cursor);
8595
+ return request(creds, "GET", `/api/v1/events/export?${params}`);
8596
+ }
8107
8597
  function discoverTrackingSchema(creds, minutes) {
8108
8598
  const query = minutes ? `?minutes=${encodeURIComponent(String(minutes))}` : "";
8109
8599
  return request(creds, "GET", `/api/v1/tracking/discovery${query}`);
@@ -8114,7 +8604,7 @@ function testTargeting(creds, url, opts) {
8114
8604
  return request(creds, "GET", `/api/v1/targeting/test?${params.toString()}`);
8115
8605
  }
8116
8606
  function createSos(creds, body5) {
8117
- const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto.createHash)("sha256").update(
8607
+ const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto2.createHash)("sha256").update(
8118
8608
  [
8119
8609
  body5.type,
8120
8610
  body5.experiment_id ?? "",
@@ -8265,8 +8755,8 @@ function requestTargetingResource(creds, method, kind5, id, body5, limit, cursor
8265
8755
  if (limit) params.set("limit", String(limit));
8266
8756
  if (cursor) params.set("cursor", cursor);
8267
8757
  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);
8758
+ const path25 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
8759
+ return request(creds, method, path25, body5);
8270
8760
  }
8271
8761
  function managedPath(kind5, id, action74) {
8272
8762
  return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action74 ? `/${action74}` : ""}`;
@@ -8502,6 +8992,24 @@ function getWebhookDeliveries(creds, id, limit) {
8502
8992
  function testWebhook(creds, id) {
8503
8993
  return request(creds, "POST", `/api/v1/webhooks/${encodeURIComponent(id)}/test`);
8504
8994
  }
8995
+ function getOrderJourney(creds, opts) {
8996
+ const params = new URLSearchParams({
8997
+ platform: opts.platform,
8998
+ environment: opts.environment,
8999
+ from: opts.from,
9000
+ to: opts.to
9001
+ });
9002
+ if (opts.orderId !== void 0) params.set("platform_order_id", opts.orderId);
9003
+ if (opts.orderNumber !== void 0) params.set("order_number", opts.orderNumber);
9004
+ if (opts.includeQa) params.set("include_qa", "true");
9005
+ return request(
9006
+ creds,
9007
+ "GET",
9008
+ `/api/v1/analytics/order-journey?${params}`,
9009
+ void 0,
9010
+ 45e3
9011
+ );
9012
+ }
8505
9013
 
8506
9014
  // src/context.ts
8507
9015
  var import_node_readline = __toESM(require("node:readline"));
@@ -8587,9 +9095,9 @@ var STATUS_COLORS = {
8587
9095
  paused: source_default.yellow,
8588
9096
  completed: source_default.blue
8589
9097
  };
8590
- function statusBadge(status2) {
8591
- const colorize = STATUS_COLORS[status2] ?? source_default.white;
8592
- return colorize(status2.toUpperCase());
9098
+ function statusBadge(status3) {
9099
+ const colorize = STATUS_COLORS[status3] ?? source_default.white;
9100
+ return colorize(status3.toUpperCase());
8593
9101
  }
8594
9102
  function printTable(headers, rows, widths) {
8595
9103
  const colWidths = widths ?? headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
@@ -8667,8 +9175,8 @@ function parseFormBody(body5) {
8667
9175
  return new URLSearchParams(body5);
8668
9176
  }
8669
9177
  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";
9178
+ const state = import_node_crypto3.default.randomBytes(24).toString("base64url");
9179
+ const clientName = import_node_os6.default.hostname() || "Local machine";
8672
9180
  return new Promise((resolve2, reject) => {
8673
9181
  let settled = false;
8674
9182
  let timeout = null;
@@ -8999,9 +9507,9 @@ var useCommand = new Command("use").description("Set the current Apex organizati
8999
9507
 
9000
9508
  // src/commands/experiments.ts
9001
9509
  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"));
9510
+ var import_node_crypto5 = __toESM(require("node:crypto"));
9511
+ var import_node_fs8 = __toESM(require("node:fs"));
9512
+ var import_node_path6 = __toESM(require("node:path"));
9005
9513
 
9006
9514
  // src/lib/qa-vocabulary.ts
9007
9515
  var checklistStatuses = [
@@ -9031,6 +9539,30 @@ var evidenceKinds = [
9031
9539
  "readiness"
9032
9540
  ];
9033
9541
 
9542
+ // src/lib/experiment-handover.ts
9543
+ function summarizeHandoverQa(value) {
9544
+ const record3 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
9545
+ const qa3 = record3(value);
9546
+ const run3 = record3(qa3.latestRun);
9547
+ const summary = record3(run3.summary);
9548
+ const count2 = (value2) => typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : null;
9549
+ let requiredDone = count2(summary.completedRequired);
9550
+ let requiredTotal = count2(summary.required);
9551
+ if (requiredDone !== null && requiredTotal !== null && requiredDone > requiredTotal) {
9552
+ requiredDone = null;
9553
+ requiredTotal = null;
9554
+ }
9555
+ const blockers = Array.isArray(summary.blockers) && summary.blockers.every((item) => typeof item === "string") ? summary.blockers : null;
9556
+ const approvedAt = typeof qa3.qaApprovedAt === "string" ? Date.parse(qa3.qaApprovedAt) : Number.NaN;
9557
+ return {
9558
+ run_status: typeof run3.status === "string" ? run3.status : null,
9559
+ required_done: requiredDone,
9560
+ required_total: requiredTotal,
9561
+ blockers,
9562
+ approval_present: Number.isFinite(approvedAt)
9563
+ };
9564
+ }
9565
+
9034
9566
  // src/lib/dashboard-url.ts
9035
9567
  function buildDashboardUrl(baseUrl, shopId, experimentId) {
9036
9568
  if (!shopId) return null;
@@ -9148,8 +9680,8 @@ function pageEntries(raw) {
9148
9680
  }
9149
9681
  return [...byId.values()];
9150
9682
  }
9151
- function urlRuleClause(count) {
9152
- return count === 1 ? "1 URL rule" : `${count} URL rules`;
9683
+ function urlRuleClause(count2) {
9684
+ return count2 === 1 ? "1 URL rule" : `${count2} URL rules`;
9153
9685
  }
9154
9686
  function effectivePageTargetingLabel(targeting) {
9155
9687
  const raw = isRecord(targeting) ? targeting : {};
@@ -9194,9 +9726,9 @@ function deriveStoreUrl(patterns) {
9194
9726
  }
9195
9727
 
9196
9728
  // 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"));
9729
+ var import_node_crypto4 = __toESM(require("node:crypto"));
9730
+ var import_node_fs7 = __toESM(require("node:fs"));
9731
+ var import_node_path5 = __toESM(require("node:path"));
9200
9732
  var INLINE_DATA_URI_MAX_CHARS = 4096;
9201
9733
  var DATA_IMAGE_URI_PATTERN = /data:image\/[a-z0-9.+-]+(?:(?:;[^;,\s"'`]*)*;base64,[A-Za-z0-9+/=\s]+|(?:;[^,\s"'`]*)?,[^\s"'`]+)/gi;
9202
9734
  var QUOTED_LOCAL_IMAGE_PATTERN = /(["'])([^"'\n]+?\.(?:png|jpe?g|gif|webp|svg|avif))\1/gi;
@@ -9219,7 +9751,7 @@ var EXTENSION_BY_MIME = {
9219
9751
  "image/avif": ".avif"
9220
9752
  };
9221
9753
  function contentHash(bytes) {
9222
- return import_node_crypto3.default.createHash("sha256").update(bytes).digest("hex");
9754
+ return import_node_crypto4.default.createHash("sha256").update(bytes).digest("hex");
9223
9755
  }
9224
9756
  function deliveryUrl(baseUrl, asset) {
9225
9757
  const returnedUrl = asset.deliveryUrl ?? asset.delivery_url ?? asset.url;
@@ -9307,23 +9839,23 @@ function createAssetReferenceRewriter(options) {
9307
9839
  if (reference.startsWith("/") || reference.startsWith("data:") || reference.includes("://")) {
9308
9840
  return null;
9309
9841
  }
9310
- const absolutePath = import_node_path4.default.resolve(baseDir, reference);
9842
+ const absolutePath = import_node_path5.default.resolve(baseDir, reference);
9311
9843
  let realBaseDir;
9312
9844
  let realPath;
9313
9845
  try {
9314
- realBaseDir = import_node_fs6.default.realpathSync(baseDir);
9315
- realPath = import_node_fs6.default.realpathSync(absolutePath);
9846
+ realBaseDir = import_node_fs7.default.realpathSync(baseDir);
9847
+ realPath = import_node_fs7.default.realpathSync(absolutePath);
9316
9848
  } catch {
9317
9849
  return null;
9318
9850
  }
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();
9851
+ const relativeRealPath = import_node_path5.default.relative(realBaseDir, realPath);
9852
+ if (relativeRealPath.startsWith("..") || import_node_path5.default.isAbsolute(relativeRealPath)) return null;
9853
+ if (!import_node_fs7.default.statSync(realPath).isFile()) return null;
9854
+ const bytes = import_node_fs7.default.readFileSync(realPath);
9855
+ const extension = import_node_path5.default.extname(realPath).toLowerCase();
9324
9856
  return uploadOnce(
9325
9857
  {
9326
- name: import_node_path4.default.basename(absolutePath),
9858
+ name: import_node_path5.default.basename(absolutePath),
9327
9859
  bytes,
9328
9860
  type: MIME_BY_EXTENSION[extension]
9329
9861
  },
@@ -9500,6 +10032,16 @@ function printExperimentResults(results2, currency) {
9500
10032
  [32, 8, 6, 8, 10]
9501
10033
  );
9502
10034
  }
10035
+ const metrics = Array.isArray(results2.metrics) ? results2.metrics : [];
10036
+ if (metrics.length > 0) {
10037
+ console.log();
10038
+ console.log(source_default.bold("Goal counting"));
10039
+ metrics.forEach((metric) => {
10040
+ const basis = metric.counting === "once" ? "unique visitors" : metric.counting === "every" ? "every event" : "unknown basis";
10041
+ console.log(` - ${String(metric.name ?? metric.goalId ?? "Goal")}: ${basis}`);
10042
+ });
10043
+ }
10044
+ console.log(" Definitions: https://docs.drip-apex.com/docs/experiments/metric-definitions");
9503
10045
  const warnings = Array.isArray(results2.warnings) ? results2.warnings : [];
9504
10046
  if (warnings.length > 0) {
9505
10047
  console.log();
@@ -9528,39 +10070,59 @@ var FILESYSTEM_FILE_RE = new RegExp(
9528
10070
  `^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
9529
10071
  );
9530
10072
  function sha256Text(value) {
9531
- return import_node_crypto4.default.createHash("sha256").update(value).digest("hex");
10073
+ return import_node_crypto5.default.createHash("sha256").update(value).digest("hex");
9532
10074
  }
9533
10075
  function folderPath(folder) {
9534
- return import_node_path5.default.resolve(process.cwd(), folder);
10076
+ return import_node_path6.default.resolve(process.cwd(), folder);
9535
10077
  }
9536
10078
  function filesystemApexDir(folder) {
9537
- return import_node_path5.default.join(folder, ".apex");
10079
+ return import_node_path6.default.join(folder, ".apex");
9538
10080
  }
9539
10081
  function filesystemSessionPath(folder) {
9540
- return import_node_path5.default.join(filesystemApexDir(folder), "experiment-filesystem.json");
10082
+ return import_node_path6.default.join(filesystemApexDir(folder), "experiment-filesystem.json");
9541
10083
  }
9542
10084
  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"),
10085
+ import_node_fs8.default.mkdirSync(filesystemApexDir(folder), { recursive: true });
10086
+ import_node_fs8.default.writeFileSync(
10087
+ import_node_path6.default.join(filesystemApexDir(folder), ".gitignore"),
9546
10088
  "experiment-filesystem.json\n",
9547
10089
  "utf8"
9548
10090
  );
9549
- import_node_fs7.default.writeFileSync(filesystemSessionPath(folder), `${JSON.stringify(session, null, 2)}
10091
+ import_node_fs8.default.writeFileSync(filesystemSessionPath(folder), `${JSON.stringify(session, null, 2)}
9550
10092
  `, "utf8");
9551
10093
  }
9552
10094
  function readFilesystemSession(folder) {
9553
10095
  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."
10096
+ if (!import_node_fs8.default.existsSync(filePath)) {
10097
+ const workspaceMarker = import_node_path6.default.join(filesystemApexDir(folder), "session.json");
10098
+ if (import_node_fs8.default.existsSync(workspaceMarker)) {
10099
+ throw new CliError(
10100
+ "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.",
10101
+ EXIT_CODES.general,
10102
+ {
10103
+ code: "validation_failed",
10104
+ next_steps: [
10105
+ { command: "apex workspace status", why: "Use the command for this folder format." },
10106
+ {
10107
+ command: "apex experiments pull",
10108
+ why: "Create the required folder in a different directory."
10109
+ }
10110
+ ]
10111
+ }
9559
10112
  );
9560
10113
  }
9561
- throw new Error(`Missing ${filePath}. Run apex experiments pull first.`);
10114
+ throw new CliError(
10115
+ `Missing ${filePath}. Run apex experiments pull first.`,
10116
+ EXIT_CODES.general,
10117
+ {
10118
+ code: "local_file_error",
10119
+ next_steps: [
10120
+ { command: "apex experiments pull", why: "Create the missing local folder metadata." }
10121
+ ]
10122
+ }
10123
+ );
9562
10124
  }
9563
- return JSON.parse(import_node_fs7.default.readFileSync(filePath, "utf8"));
10125
+ return JSON.parse(import_node_fs8.default.readFileSync(filePath, "utf8"));
9564
10126
  }
9565
10127
  function validateFilesystemPath(relativePath) {
9566
10128
  const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
@@ -9576,17 +10138,17 @@ function writeExperimentFilesystemFiles(folder, files) {
9576
10138
  }));
9577
10139
  for (const file of validatedFiles) {
9578
10140
  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");
10141
+ const absPath = import_node_path6.default.join(folder, relativePath);
10142
+ import_node_fs8.default.mkdirSync(import_node_path6.default.dirname(absPath), { recursive: true });
10143
+ import_node_fs8.default.writeFileSync(absPath, file.content, "utf8");
9582
10144
  }
9583
10145
  }
9584
10146
  function listExperimentFilesystemFiles(folder, session) {
9585
10147
  const files = [];
9586
10148
  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");
10149
+ const absPath = import_node_path6.default.join(folder, fileName);
10150
+ if (!import_node_fs8.default.existsSync(absPath)) continue;
10151
+ const content = import_node_fs8.default.readFileSync(absPath, "utf8");
9590
10152
  const sha256 = sha256Text(content);
9591
10153
  if (SERVER_OWNED_EXPERIMENT_FILES.has(fileName)) {
9592
10154
  if (session.files[fileName]?.sha256 !== sha256) {
@@ -9600,21 +10162,21 @@ function listExperimentFilesystemFiles(folder, session) {
9600
10162
  }
9601
10163
  for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment) {
9602
10164
  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");
10165
+ const absPath = import_node_path6.default.join(folder, relativePath);
10166
+ if (!import_node_fs8.default.existsSync(absPath)) continue;
10167
+ const content = import_node_fs8.default.readFileSync(absPath, "utf8");
9606
10168
  files.push({ path: relativePath, content, sha256: sha256Text(content) });
9607
10169
  }
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 })) {
10170
+ const variationsRoot = import_node_path6.default.join(folder, "variations");
10171
+ if (import_node_fs8.default.existsSync(variationsRoot)) {
10172
+ for (const variationDir of import_node_fs8.default.readdirSync(variationsRoot, { withFileTypes: true })) {
9611
10173
  if (!variationDir.isDirectory()) continue;
9612
- const base = import_node_path5.default.join(variationsRoot, variationDir.name);
10174
+ const base = import_node_path6.default.join(variationsRoot, variationDir.name);
9613
10175
  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");
10176
+ const absPath = import_node_path6.default.join(base, fileName);
10177
+ if (!import_node_fs8.default.existsSync(absPath)) continue;
10178
+ const relativePath = validateFilesystemPath(import_node_path6.default.relative(folder, absPath));
10179
+ const content = import_node_fs8.default.readFileSync(absPath, "utf8");
9618
10180
  files.push({ path: relativePath, content, sha256: sha256Text(content) });
9619
10181
  }
9620
10182
  }
@@ -9627,7 +10189,7 @@ async function rewriteExperimentFilesystemAssets(input) {
9627
10189
  baseUrl: input.baseUrl,
9628
10190
  enabled: input.enabled,
9629
10191
  upload: async (file) => {
9630
- const hash = import_node_crypto4.default.createHash("sha256").update(file.bytes).digest("hex");
10192
+ const hash = import_node_crypto5.default.createHash("sha256").update(file.bytes).digest("hex");
9631
10193
  const cached = input.assets[hash];
9632
10194
  if (cached) return cached;
9633
10195
  if (input.upload === null) return null;
@@ -9656,7 +10218,7 @@ async function rewriteExperimentFilesystemAssets(input) {
9656
10218
  }
9657
10219
  const content = await rewriter.rewrite(
9658
10220
  file.content,
9659
- import_node_path5.default.dirname(import_node_path5.default.join(input.folder, file.path))
10221
+ import_node_path6.default.dirname(import_node_path6.default.join(input.folder, file.path))
9660
10222
  );
9661
10223
  return { ...file, content, sha256: sha256Text(content) };
9662
10224
  })
@@ -9667,9 +10229,17 @@ function printFilesystemPlan(plan) {
9667
10229
  console.log(source_default.red("\nErrors"));
9668
10230
  plan.errors.forEach((error) => console.log(` - ${error}`));
9669
10231
  }
9670
- if (plan.warnings.length > 0) {
10232
+ const compatibilityWarnings = (plan.previewCompatibility ?? []).map(
10233
+ (finding) => `${finding.path}:${finding.line}:${finding.column} ${finding.rule}: ${finding.message}. Try: ${finding.alternative}`
10234
+ );
10235
+ if (compatibilityWarnings.length > 0) {
10236
+ console.log(source_default.yellow("\nPreview compatibility"));
10237
+ compatibilityWarnings.forEach((warning) => console.log(` - ${warning}`));
10238
+ }
10239
+ const otherWarnings = plan.warnings.filter((warning) => !compatibilityWarnings.includes(warning));
10240
+ if (otherWarnings.length > 0) {
9671
10241
  console.log(source_default.yellow("\nWarnings"));
9672
- plan.warnings.forEach((warning) => console.log(` - ${warning}`));
10242
+ otherWarnings.forEach((warning) => console.log(` - ${warning}`));
9673
10243
  }
9674
10244
  if ((plan.blockingIssues?.length ?? 0) > 0) {
9675
10245
  console.log(source_default.red("\nBlocking issues"));
@@ -9744,6 +10314,10 @@ function printQaPreviewUrls(experimentId, result) {
9744
10314
  );
9745
10315
  }
9746
10316
  }
10317
+ function qaVersionMarker(value) {
10318
+ const version = value;
10319
+ return version.stale ? " [stale: checked against an earlier version]" : version.unversioned ? " [unversioned]" : "";
10320
+ }
9747
10321
  function printQaRun(run3) {
9748
10322
  console.log();
9749
10323
  console.log(source_default.bold(`QA run ${run3.id}`));
@@ -9760,7 +10334,7 @@ function printQaRun(run3) {
9760
10334
  for (const item of run3.checklist) {
9761
10335
  const required = item.required ? source_default.yellow(" required") : "";
9762
10336
  const notes = item.notes ? source_default.dim(` \u2014 ${item.notes}`) : "";
9763
- console.log(` ${statusBadge(item.status)} ${item.id}${required}`);
10337
+ console.log(` ${statusBadge(item.status)} ${item.id}${required}${qaVersionMarker(item)}`);
9764
10338
  console.log(` ${item.title}${notes}`);
9765
10339
  }
9766
10340
  if (run3.urls.length > 0) {
@@ -9771,10 +10345,10 @@ function printQaRun(run3) {
9771
10345
  }
9772
10346
  if ((run3.evidenceRecords ?? []).length > 0) {
9773
10347
  console.log(source_default.bold("\nEvidence records"));
9774
- for (const record2 of run3.evidenceRecords ?? []) {
9775
- const device = record2.device ? ` \xB7 ${record2.device}` : "";
10348
+ for (const record3 of run3.evidenceRecords ?? []) {
10349
+ const device = record3.device ? ` \xB7 ${record3.device}` : "";
9776
10350
  console.log(
9777
- ` - ${record2.kind} \xB7 ${record2.label} \xB7 ${record2.status}${device} \xB7 ${record2.capturedAt}`
10351
+ ` - ${record3.kind} \xB7 ${record3.label} \xB7 ${record3.status}${device} \xB7 ${record3.capturedAt}${qaVersionMarker(record3)}`
9778
10352
  );
9779
10353
  }
9780
10354
  }
@@ -9795,8 +10369,8 @@ async function confirmApply(message) {
9795
10369
  function parseEvidenceInput(value) {
9796
10370
  if (!value) return void 0;
9797
10371
  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);
10372
+ 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;
10373
+ const parsed = JSON.parse(text2);
9800
10374
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9801
10375
  throw new Error("QA evidence must be a JSON object");
9802
10376
  }
@@ -9840,7 +10414,7 @@ async function resolveExperimentForFilesystemOrId(value) {
9840
10414
  return {
9841
10415
  experimentId: session.experimentId,
9842
10416
  folder: candidate,
9843
- label: import_node_path5.default.basename(candidate)
10417
+ label: import_node_path6.default.basename(candidate)
9844
10418
  };
9845
10419
  }
9846
10420
  const exp = await resolveExperiment(value);
@@ -10026,7 +10600,53 @@ var detachIdea = new Command("detach-idea").description("Detach an experiment fr
10026
10600
  );
10027
10601
  output(this, res.data, () => printSuccess("Idea detached."));
10028
10602
  });
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) {
10603
+ function printResultsExplain(body5, samples) {
10604
+ const data = body5.data;
10605
+ console.log("Diagnostic results explanation");
10606
+ for (const arm of data.arms) {
10607
+ console.log(`
10608
+ ${arm.name} (${arm.variation_id})`);
10609
+ for (const name of ["visitors", "conversions", "orders", "revenue"]) {
10610
+ const metric = arm[name];
10611
+ if (!metric) continue;
10612
+ console.log(` ${name}`);
10613
+ for (const [label, count2] of Object.entries(metric.counts ?? {}))
10614
+ console.log(` ${label.padEnd(28)} ${count2}`);
10615
+ console.log(
10616
+ ` Counted: ${metric.classified_counted ?? "unavailable"} Reported: ${metric.reported ?? "unavailable"} Residual: ${metric.residual ?? "unavailable"}`
10617
+ );
10618
+ if (metric.explanation) console.log(` ${metric.explanation}`);
10619
+ if (samples && metric.samples) {
10620
+ for (const [label, entries] of Object.entries(
10621
+ metric.samples
10622
+ )) {
10623
+ for (const entry of entries)
10624
+ console.log(
10625
+ ` ${label}: ${entry.order_key || entry.event_id} ${entry.amount ?? ""} ${entry.currency ?? ""} ${entry.timestamp ?? ""} arm_only=${entry.arm_only ?? false}`
10626
+ );
10627
+ }
10628
+ }
10629
+ }
10630
+ }
10631
+ if (data.order) console.log(`
10632
+ Order trail
10633
+ ${JSON.stringify(data.order, null, 2)}`);
10634
+ const others = data.other_variations ?? [];
10635
+ if (others.length > 0) {
10636
+ console.log("\nOther variations (not part of this test)");
10637
+ for (const other of others) console.log(` ${other.variation_id}: ${other.row_count} rows`);
10638
+ }
10639
+ const limitations = data.limitations ?? [];
10640
+ if (limitations.length > 0) {
10641
+ console.log("\nLimitations");
10642
+ for (const limitation of limitations) console.log(` - ${limitation}`);
10643
+ }
10644
+ console.log(`
10645
+ Definitions: ${data.definitions_url}`);
10646
+ }
10647
+ 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) {
10648
+ if (!opts.explain && (opts.goal || opts.order || opts.samples))
10649
+ fail(this, "--goal, --order and --samples require --explain", EXIT_CODES.validation);
10030
10650
  const creds = requireCredentials();
10031
10651
  const spinner = await createSpinner("Fetching experiment results...");
10032
10652
  try {
@@ -10035,7 +10655,7 @@ var results = new Command("results").description("Read experiment results and ti
10035
10655
  spinner?.fail(`Experiment "${idOrSlug}" not found`);
10036
10656
  fail(this, `Experiment "${idOrSlug}" not found.`, EXIT_CODES.validation);
10037
10657
  }
10038
- const res = await getExperimentResults(creds, exp.id, { currency: opts.currency });
10658
+ const res = opts.explain ? await getExperimentResultsExplain(creds, exp.id, opts) : await getExperimentResults(creds, exp.id, { currency: opts.currency });
10039
10659
  if (!res.ok || !res.data) {
10040
10660
  spinner?.fail("Failed to fetch experiment results");
10041
10661
  fail(
@@ -10049,7 +10669,7 @@ var results = new Command("results").description("Read experiment results and ti
10049
10669
  output(
10050
10670
  this,
10051
10671
  res.data,
10052
- () => printExperimentResults(res.data, opts.currency)
10672
+ () => opts.explain ? printResultsExplain(res.data, Boolean(opts.samples)) : printExperimentResults(res.data, opts.currency)
10053
10673
  );
10054
10674
  } catch (error) {
10055
10675
  spinner?.fail("Failed to fetch experiment results");
@@ -10132,7 +10752,7 @@ function parseHttpDestination(value) {
10132
10752
  function readCreateJson(command, reference) {
10133
10753
  const file = reference.startsWith("@") ? reference.slice(1) : reference;
10134
10754
  try {
10135
- const parsed = JSON.parse(import_node_fs7.default.readFileSync(import_node_path5.default.resolve(process.cwd(), file), "utf8"));
10755
+ const parsed = JSON.parse(import_node_fs8.default.readFileSync(import_node_path6.default.resolve(process.cwd(), file), "utf8"));
10136
10756
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
10137
10757
  throw new Error("expected an object");
10138
10758
  return parsed;
@@ -10168,8 +10788,8 @@ function normalizeNewGoalKey(key) {
10168
10788
  return normalized === "page" ? "page_id" : normalized;
10169
10789
  }
10170
10790
  function validateNewGoal(command, value, body5) {
10171
- const invalid = (reason) => fail(command, `Invalid --goal "new:${value}"; ${reason}`, EXIT_CODES.validation);
10172
- const text = (field) => {
10791
+ const invalid = (reason2) => fail(command, `Invalid --goal "new:${value}"; ${reason2}`, EXIT_CODES.validation);
10792
+ const text2 = (field) => {
10173
10793
  const raw = body5[field];
10174
10794
  return typeof raw === "string" && raw.length > 0 ? raw : void 0;
10175
10795
  };
@@ -10179,12 +10799,12 @@ function validateNewGoal(command, value, body5) {
10179
10799
  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
10800
  );
10181
10801
  }
10182
- const type = text("type");
10183
- const name = text("name");
10802
+ const type = text2("type");
10803
+ const name = text2("name");
10184
10804
  if (!type || !name) invalid("type and name are required");
10185
10805
  if (!GOAL_TYPES.includes(type)) invalid(`type must be one of ${GOAL_TYPES.join(", ")}`);
10186
10806
  if (name.length > 255) invalid("name must be 255 characters or fewer");
10187
- const measurement = text("measurement") ?? "conversion";
10807
+ const measurement = text2("measurement") ?? "conversion";
10188
10808
  if (!GOAL_MEASUREMENTS.includes(measurement))
10189
10809
  invalid(`measurement must be one of ${GOAL_MEASUREMENTS.join(", ")}`);
10190
10810
  if (body5.count_once !== void 0) {
@@ -10192,21 +10812,21 @@ function validateNewGoal(command, value, body5) {
10192
10812
  if (flag !== "true" && flag !== "false") invalid("count-once must be true or false");
10193
10813
  body5.count_once = flag === "true";
10194
10814
  }
10195
- if (type === "click" && !text("selector") && !text("text_match"))
10815
+ if (type === "click" && !text2("selector") && !text2("text_match"))
10196
10816
  invalid("click goals need selector=<css> or text-match=<text>");
10197
- if (type === "pageview" && !text("page_id") && !text("url_pattern"))
10817
+ if (type === "pageview" && !text2("page_id") && !text2("url_pattern"))
10198
10818
  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");
10819
+ if (type === "custom" && !text2("event_name")) invalid("custom goals need event-name=<name>");
10820
+ const eventName = text2("event_name");
10201
10821
  if (eventName && !GOAL_EVENT_NAME.test(eventName))
10202
10822
  invalid("event-name must be 1-120 letters, numbers, dots, colons, underscores, or hyphens");
10203
- if ((type === "custom" || type === "revenue") && text("page_id"))
10823
+ if ((type === "custom" || type === "revenue") && text2("page_id"))
10204
10824
  invalid(`page is not supported for ${type} goals`);
10205
- const pageId = text("page_id");
10825
+ const pageId = text2("page_id");
10206
10826
  if (pageId && !GOAL_UUID.test(pageId))
10207
10827
  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");
10828
+ const valueProperty = text2("value_property");
10829
+ const valueUnit = text2("value_unit");
10210
10830
  if (measurement !== "value" && (valueProperty || valueUnit))
10211
10831
  invalid("value-property and value-unit need measurement=value");
10212
10832
  if (type === "revenue" && valueProperty)
@@ -10730,7 +11350,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
10730
11350
  {
10731
11351
  folder,
10732
11352
  experimentId: exp.id,
10733
- files: res.data.files.map((file) => import_node_path5.default.join(folder, file.path))
11353
+ files: res.data.files.map((file) => import_node_path6.default.join(folder, file.path))
10734
11354
  },
10735
11355
  () => {
10736
11356
  spinner.succeed(`Pulled ${exp.name}`);
@@ -10742,6 +11362,252 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
10742
11362
  throw error;
10743
11363
  }
10744
11364
  });
11365
+ var experimentStatus = new Command("status").description(
11366
+ "Read experiment context, local changes, QA, and handover next steps without writing"
11367
+ ).argument("[folder-or-id]", "Pulled folder, experiment ID, slug, or test key", ".").action(async function(target) {
11368
+ const creds = requireCredentials();
11369
+ const candidate = folderPath(target);
11370
+ const isFolder = import_node_fs8.default.existsSync(candidate) && import_node_fs8.default.statSync(candidate).isDirectory();
11371
+ const hasApexDir = isFolder && import_node_fs8.default.existsSync(import_node_path6.default.join(candidate, ".apex"));
11372
+ if (isFolder && !hasApexDir && target === ".") {
11373
+ fail(this, "This folder is not a pulled experiment.", EXIT_CODES.validation, {
11374
+ code: "validation_failed",
11375
+ next_steps: [
11376
+ {
11377
+ command: "apex experiments status <experiment-id-or-test-key>",
11378
+ why: "Read the saved experiment without a local folder."
11379
+ },
11380
+ {
11381
+ command: "apex experiments pull <experiment-id-or-test-key> --out <folder>",
11382
+ why: "Pull the experiment to compare local files before handover."
11383
+ }
11384
+ ]
11385
+ });
11386
+ }
11387
+ const session = hasApexDir ? readFilesystemSession(candidate) : null;
11388
+ if (session && new URL(session.baseUrl).origin !== new URL(creds.baseUrl).origin) {
11389
+ fail(
11390
+ this,
11391
+ "This folder was pulled from a different base URL. Select that base URL before inspecting it.",
11392
+ EXIT_CODES.validation
11393
+ );
11394
+ }
11395
+ const response = session ? await getExperiment(creds, session.experimentId) : null;
11396
+ if (response && (!response.ok || !response.data)) {
11397
+ throw new CliError(
11398
+ response.error ?? "Failed to read experiment",
11399
+ classifyStatus(response.status),
11400
+ response.errorDetails,
11401
+ apiFailureOf(response)
11402
+ );
11403
+ }
11404
+ const experiment = response?.data ?? await resolveExperiment(target);
11405
+ if (!experiment) fail(this, "Experiment not found.");
11406
+ const snapshot = await getExperimentFilesystem(creds, experiment.id);
11407
+ if (!snapshot.ok || !snapshot.data) {
11408
+ throw new CliError(
11409
+ snapshot.error ?? "Failed to read experiment filesystem",
11410
+ classifyStatus(snapshot.status),
11411
+ snapshot.errorDetails,
11412
+ apiFailureOf(snapshot)
11413
+ );
11414
+ }
11415
+ let versions = null;
11416
+ if (session) {
11417
+ const localFiles = listExperimentFilesystemFiles(candidate, session);
11418
+ const localHashes = new Map(localFiles.map((file) => [file.path, file.sha256]));
11419
+ for (const name of SERVER_OWNED_EXPERIMENT_FILES) {
11420
+ const file = import_node_path6.default.join(candidate, name);
11421
+ if (import_node_fs8.default.existsSync(file)) localHashes.set(name, sha256Text(import_node_fs8.default.readFileSync(file, "utf8")));
11422
+ }
11423
+ const paths = [.../* @__PURE__ */ new Set([...Object.keys(session.files), ...localHashes.keys()])].sort();
11424
+ const files = paths.map((file) => {
11425
+ const local = localHashes.get(file) ?? null;
11426
+ const pulled = session.files[file]?.sha256 ?? null;
11427
+ return {
11428
+ path: file,
11429
+ state: !local ? "deleted" : !pulled ? "added" : local === pulled ? "unchanged" : "modified",
11430
+ local_sha256: local,
11431
+ pulled_sha256: pulled
11432
+ };
11433
+ });
11434
+ const plannedFiles = await rewriteExperimentFilesystemAssets({
11435
+ files: localFiles,
11436
+ folder: candidate,
11437
+ baseUrl: creds.baseUrl,
11438
+ enabled: true,
11439
+ upload: null,
11440
+ assets: session.assets ?? {},
11441
+ credentials: creds
11442
+ });
11443
+ const plan = await planExperimentFilesystem(creds, experiment.id, { files: plannedFiles });
11444
+ if (!plan.ok || !plan.data) {
11445
+ throw new CliError(
11446
+ plan.error ?? "Failed to read saved state",
11447
+ classifyStatus(plan.status),
11448
+ plan.errorDetails,
11449
+ apiFailureOf(plan)
11450
+ );
11451
+ }
11452
+ versions = {
11453
+ files,
11454
+ saved: {
11455
+ would_change: plan.data.changes.length > 0,
11456
+ invalidates_qa: plan.data.invalidatesQa,
11457
+ blocking_issue_count: plan.data.blockingIssues?.filter((issue) => issue.severity === "blocking").length ?? 0,
11458
+ valid: plan.data.ok,
11459
+ errors: plan.data.errors,
11460
+ warnings: plan.data.warnings
11461
+ }
11462
+ };
11463
+ }
11464
+ const qaFile = snapshot.data.files.find((file) => file.path === "qa.json");
11465
+ let qaSnapshot = null;
11466
+ let qaUnreadable = false;
11467
+ if (qaFile) {
11468
+ try {
11469
+ qaSnapshot = JSON.parse(qaFile.content);
11470
+ } catch {
11471
+ qaUnreadable = true;
11472
+ }
11473
+ }
11474
+ const qa3 = {
11475
+ ...summarizeHandoverQa(qaSnapshot),
11476
+ ready_version_id: experiment.ready_version_id ?? null
11477
+ };
11478
+ const shopId = experiment.shop_id ?? creds.resolvedContext?.shopId ?? null;
11479
+ let org = {
11480
+ id: creds.resolvedContext?.orgId ?? null,
11481
+ name: null
11482
+ };
11483
+ let shop = { id: shopId, name: null };
11484
+ const notes = [];
11485
+ if (qaUnreadable) notes.push("The saved qa.json could not be parsed; QA counts are unknown.");
11486
+ if (!session)
11487
+ notes.push(
11488
+ "Versions are unavailable without a pulled folder. Pull the experiment to compare local files."
11489
+ );
11490
+ if (creds.credentialKind === "user") {
11491
+ const context = await getAccountContext(creds);
11492
+ const owner = context.data?.data?.orgs?.find(
11493
+ (item) => item.shops?.some((item2) => item2.id === shopId)
11494
+ );
11495
+ if (owner) {
11496
+ org = { id: owner.id, name: owner.name };
11497
+ shop = { id: shopId, name: owner.shops.find((item) => item.id === shopId)?.name ?? null };
11498
+ } else notes.push("Organization and shop names are unavailable from account context.");
11499
+ } else
11500
+ notes.push(
11501
+ "Shop credentials do not expose account context; organization and shop names are unknown."
11502
+ );
11503
+ const nextSteps = [];
11504
+ const ref = shellQuote(session ? target : experiment.id);
11505
+ if (!session)
11506
+ nextSteps.push({
11507
+ command: `apex experiments pull ${ref} --out <folder>`,
11508
+ why: "Pull files to verify the local version."
11509
+ });
11510
+ if (versions && (versions.files.some((file) => file.state !== "unchanged") || versions.saved.would_change || !versions.saved.valid || versions.saved.blocking_issue_count > 0)) {
11511
+ nextSteps.push(
11512
+ {
11513
+ command: `apex experiments plan ${ref} --json`,
11514
+ why: "Review local changes, saved differences, and blocking issues."
11515
+ },
11516
+ {
11517
+ command: `apex experiments apply ${ref} --json`,
11518
+ why: "Save the reviewed changes after resolving blockers and any QA invalidation acknowledgment."
11519
+ }
11520
+ );
11521
+ }
11522
+ if (qa3.required_done === null || qa3.required_total === null || qa3.required_done < qa3.required_total || qa3.blockers === null || qa3.blockers.length > 0) {
11523
+ nextSteps.push(
11524
+ {
11525
+ command: `apex experiments qa status ${ref} --json`,
11526
+ why: "Inspect missing required checks and evidence before recording QA results. This separate command can create a QA run."
11527
+ },
11528
+ {
11529
+ command: `apex experiments qa check ${ref} --item <item-id> --status pass`,
11530
+ why: "Record each missing required check; attach supporting evidence with apex experiments qa evidence."
11531
+ }
11532
+ );
11533
+ } else if (!qa3.approval_present) {
11534
+ nextSteps.push({
11535
+ command: `apex experiments qa approve ${ref}`,
11536
+ why: "Required QA checks are complete; approve after verifying the saved version."
11537
+ });
11538
+ }
11539
+ const locallyChanged = versions !== null && (versions.files.some((file) => file.state !== "unchanged") || versions.saved.would_change || !versions.saved.valid || versions.saved.blocking_issue_count > 0);
11540
+ const qaComplete = qa3.required_done !== null && qa3.required_total !== null && qa3.required_done === qa3.required_total && qa3.blockers !== null && qa3.blockers.length === 0;
11541
+ const readyVersionKnown = typeof qa3.ready_version_id === "string" && qa3.ready_version_id.length > 0;
11542
+ const approvalCurrent = qa3.approval_present && readyVersionKnown && !locallyChanged;
11543
+ const lifecycleReady = experiment.status === "ready_to_launch";
11544
+ const readyForHandover = lifecycleReady && approvalCurrent && qaComplete;
11545
+ if (approvalCurrent && qaComplete && !lifecycleReady)
11546
+ notes.push(`The experiment status is ${experiment.status}; handover needs ready_to_launch.`);
11547
+ if (qa3.approval_present && !readyVersionKnown && qaComplete && !locallyChanged)
11548
+ nextSteps.push({
11549
+ command: `apex experiments qa approve ${ref}`,
11550
+ why: "The approval is not bound to a saved version; approve again to bind it before handover."
11551
+ });
11552
+ if (readyForHandover)
11553
+ nextSteps.push({
11554
+ command: "launch in the Apex dashboard",
11555
+ why: "QA is approved for the saved version. Starting a test is a human decision."
11556
+ });
11557
+ if (!versions)
11558
+ notes.push(
11559
+ "Without a pulled folder, readiness reflects the saved version only; local edits elsewhere are not visible."
11560
+ );
11561
+ const data = {
11562
+ context: {
11563
+ org,
11564
+ shop,
11565
+ credential_kind: creds.credentialKind ?? "unknown",
11566
+ base_url: creds.baseUrl
11567
+ },
11568
+ experiment: {
11569
+ id: experiment.id,
11570
+ test_key: experiment.test_key ?? null,
11571
+ name: experiment.name,
11572
+ status: experiment.status,
11573
+ dashboard_url: buildDashboardUrl(creds.baseUrl, shopId, experiment.id)
11574
+ },
11575
+ versions,
11576
+ qa: { ...qa3, approval_current: approvalCurrent },
11577
+ next_steps: nextSteps,
11578
+ ready_for_handover: readyForHandover,
11579
+ notes
11580
+ };
11581
+ output(this, data, () => {
11582
+ console.log(
11583
+ `Context: ${org.name ?? org.id ?? "unknown organization"} / ${shop.name ?? shop.id ?? "unknown shop"}
11584
+ Credential: ${data.context.credential_kind}
11585
+ Base URL: ${creds.baseUrl}`
11586
+ );
11587
+ console.log(
11588
+ `Experiment: ${experiment.test_key ?? experiment.id} / ${experiment.name} (${experiment.status})
11589
+ Dashboard: ${data.experiment.dashboard_url ?? "unknown"}`
11590
+ );
11591
+ console.log("Versions:");
11592
+ if (versions) {
11593
+ for (const file of versions.files) console.log(` ${file.state}: ${file.path}`);
11594
+ console.log(
11595
+ ` Saved: would change ${versions.saved.would_change}; invalidates QA ${versions.saved.invalidates_qa}; blocking issues ${versions.saved.blocking_issue_count}`
11596
+ );
11597
+ for (const message of [...versions.saved.errors, ...versions.saved.warnings])
11598
+ console.log(` ${message}`);
11599
+ } else console.log(" unknown (no pulled folder)");
11600
+ console.log(
11601
+ `QA: ${qa3.run_status ?? "unknown"}; required ${qa3.required_done ?? "unknown"}/${qa3.required_total ?? "unknown"}; approval ${qa3.approval_present}; current for this version: ${approvalCurrent}`
11602
+ );
11603
+ for (const blocker of qa3.blockers ?? []) console.log(` Blocker: ${blocker}`);
11604
+ console.log(`Ready for handover: ${readyForHandover ? "yes" : "no"}
11605
+ Next steps:`);
11606
+ for (const step of nextSteps) console.log(` ${step.command}
11607
+ ${step.why}`);
11608
+ for (const note of notes) console.log(` ${note}`);
11609
+ });
11610
+ });
10745
11611
  var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function action14(folderArg) {
10746
11612
  const creds = requireCredentials();
10747
11613
  try {
@@ -10788,7 +11654,10 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
10788
11654
  fail(this, error instanceof Error ? error.message : String(error));
10789
11655
  }
10790
11656
  });
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) {
11657
+ var applyFilesystem = new Command("apply").option(
11658
+ "--confirm-targeting-replace",
11659
+ "Confirm that attaching a Page may replace URL targeting authored through the API"
11660
+ ).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
11661
  const creds = requireCredentials();
10793
11662
  try {
10794
11663
  const folder = folderPath(folderArg);
@@ -10822,13 +11691,19 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
10822
11691
  if (!shouldUseJson(this)) printFilesystemPlan(planRes.data);
10823
11692
  if (opts.assetUpload === false && planRes.data.blockingIssues?.some((issue) => /inline|hosted asset/i.test(issue.message))) {
10824
11693
  process.stderr.write(
10825
- `Retry with hosted asset upload: apex experiments apply ${folderArg}
11694
+ `Retry with hosted asset upload: apex experiments apply ${shellQuote(folderArg)}
10826
11695
  `
10827
11696
  );
10828
11697
  }
10829
11698
  fail(this, blockingIssueSummary(planRes.data.blockingIssues), EXIT_CODES.validation, {
10830
11699
  errors: planRes.data.errors,
10831
11700
  plan: planRes.data,
11701
+ next_steps: opts.assetUpload === false && planRes.data.blockingIssues?.some((issue) => /inline|hosted asset/i.test(issue.message)) ? [
11702
+ {
11703
+ command: `apex experiments apply ${shellQuote(folderArg)}`,
11704
+ why: "Retry with hosted asset upload."
11705
+ }
11706
+ ] : [],
10832
11707
  requiredAcknowledgment: planRes.data.invalidatesQa ? "accept-qa-invalidation" : null
10833
11708
  });
10834
11709
  }
@@ -10873,7 +11748,31 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
10873
11748
  }
10874
11749
  }
10875
11750
  const spinner = process.stderr.isTTY ? await createSpinner("Applying experiment filesystem...") : null;
10876
- const res = await applyExperimentFilesystem(creds, session.experimentId, { files });
11751
+ let res = await applyExperimentFilesystem(creds, session.experimentId, {
11752
+ files,
11753
+ ...opts.confirmTargetingReplace ? { confirmTargetingReplace: true } : {}
11754
+ });
11755
+ if (!res.ok && res.status === 409 && res.errorDetails?.code === "confirm_targeting_replace_required" && !opts.confirmTargetingReplace) {
11756
+ spinner?.stop();
11757
+ const ok = await confirmApply(
11758
+ "Attaching this Page replaces URL targeting authored through the API. Continue?"
11759
+ );
11760
+ if (ok === null) {
11761
+ confirmationRequired(
11762
+ this,
11763
+ "Attaching this Page replaces URL targeting authored through the API and requires confirmation.",
11764
+ `apex experiments apply ${folderArg} --confirm-targeting-replace`
11765
+ );
11766
+ }
11767
+ if (!ok) {
11768
+ output(this, { aborted: true }, () => console.log("Aborted."));
11769
+ return;
11770
+ }
11771
+ res = await applyExperimentFilesystem(creds, session.experimentId, {
11772
+ files,
11773
+ confirmTargetingReplace: true
11774
+ });
11775
+ }
10877
11776
  if (!res.ok || !res.data?.applied) {
10878
11777
  spinner?.fail("Failed to apply experiment filesystem");
10879
11778
  fail(this, res.error ?? "Apply failed", classifyStatus(res.status), {
@@ -11154,8 +12053,8 @@ var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled
11154
12053
  });
11155
12054
  function parseJsonValue(value) {
11156
12055
  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);
12056
+ 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;
12057
+ return JSON.parse(text2);
11159
12058
  }
11160
12059
  function collectFile(value, previous = []) {
11161
12060
  return [...previous, value];
@@ -11207,7 +12106,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
11207
12106
  classifyStatus(current.status),
11208
12107
  current.errorDetails
11209
12108
  );
11210
- for (const record2 of current.data.run.evidenceRecords) previousIds.add(record2.id);
12109
+ for (const record3 of current.data.run.evidenceRecords) previousIds.add(record3.id);
11211
12110
  }
11212
12111
  let response;
11213
12112
  if (opts.job) {
@@ -11226,12 +12125,12 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
11226
12125
  const variationId = resolveEvidenceVariation(experiment, opts.variation);
11227
12126
  const form = new FormData();
11228
12127
  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();
12128
+ const absolutePath = import_node_path6.default.resolve(process.cwd(), filePath);
12129
+ if (!import_node_fs8.default.existsSync(absolutePath)) throw new Error(`Evidence file not found: ${filePath}`);
12130
+ const bytes = import_node_fs8.default.readFileSync(absolutePath);
12131
+ const extension = import_node_path6.default.extname(absolutePath).toLowerCase();
11233
12132
  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 }));
12133
+ form.append("files", new File([bytes], import_node_path6.default.basename(absolutePath), { type: mimeType }));
11235
12134
  }
11236
12135
  if (opts.label) form.append("label", opts.label);
11237
12136
  const device = normalizeEvidenceDevice(opts.device);
@@ -11252,7 +12151,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
11252
12151
  const run3 = response.data.run;
11253
12152
  const data = opts.summary ? qaSummary(
11254
12153
  run3,
11255
- run3.evidenceRecords.filter((record2) => !previousIds.has(record2.id))
12154
+ run3.evidenceRecords.filter((record3) => !previousIds.has(record3.id))
11256
12155
  ) : response.data;
11257
12156
  output(this, data, () => {
11258
12157
  if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
@@ -11278,8 +12177,8 @@ function shellQuote(value) {
11278
12177
  if (value.length > 0 && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
11279
12178
  return `'${value.replaceAll("'", "'\\''")}'`;
11280
12179
  }
11281
- function isTransientResumeStatus(status2) {
11282
- return status2 === 429 || status2 !== void 0 && status2 >= 500;
12180
+ function isTransientResumeStatus(status3) {
12181
+ return status3 === 429 || status3 !== void 0 && status3 >= 500;
11283
12182
  }
11284
12183
  function screenshotTimeoutMessage(job, experimentRef, out, seconds, flags = {}) {
11285
12184
  const progress = job.progress ? ` Last progress: ${job.progress.captured}/${job.progress.total} captured.` : "";
@@ -11563,8 +12462,8 @@ var screenshots = new Command("screenshots").description(
11563
12462
  }
11564
12463
  if (job.status === "failed")
11565
12464
  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 });
12465
+ const outputDir = import_node_path6.default.resolve(process.cwd(), opts.out);
12466
+ import_node_fs8.default.mkdirSync(outputDir, { recursive: true });
11568
12467
  const rows = [];
11569
12468
  const errors = [];
11570
12469
  const usedFilenames = /* @__PURE__ */ new Set();
@@ -11622,8 +12521,8 @@ var screenshots = new Command("screenshots").description(
11622
12521
  try {
11623
12522
  const response = await fetch(url);
11624
12523
  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),
12524
+ await import_node_fs8.default.promises.writeFile(
12525
+ import_node_path6.default.join(outputDir, filename),
11627
12526
  Buffer.from(await response.arrayBuffer())
11628
12527
  );
11629
12528
  workerResults.push({ index, row: [variation.name, device, filename] });
@@ -11650,7 +12549,7 @@ var screenshots = new Command("screenshots").description(
11650
12549
  printTable(["VARIATION", "DEVICE", "FILE"], rows, [24, 20, 48]);
11651
12550
  });
11652
12551
  });
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(
12552
+ 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
12553
  "after",
11655
12554
  `
11656
12555
  Examples:
@@ -11663,9 +12562,9 @@ Examples:
11663
12562
 
11664
12563
  // src/commands/push.ts
11665
12564
  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"));
12565
+ var import_node_fs9 = __toESM(require("node:fs"));
12566
+ var import_node_os7 = __toESM(require("node:os"));
12567
+ var import_node_path7 = __toESM(require("node:path"));
11669
12568
  var import_node_url2 = require("node:url");
11670
12569
  function toApiBody(exp) {
11671
12570
  return {
@@ -11706,7 +12605,7 @@ async function rewriteExperimentAssets(exp, rewrite, baseDir) {
11706
12605
  };
11707
12606
  }
11708
12607
  async function importConfigModule(absPath) {
11709
- const ext = import_node_path6.default.extname(absPath).toLowerCase();
12608
+ const ext = import_node_path7.default.extname(absPath).toLowerCase();
11710
12609
  if (ext === ".js" || ext === ".mjs" || ext === ".cjs") {
11711
12610
  return await import((0, import_node_url2.pathToFileURL)(absPath).href);
11712
12611
  }
@@ -11720,8 +12619,8 @@ async function importConfigModule(absPath) {
11720
12619
  ` + (err instanceof Error ? err.message : String(err))
11721
12620
  );
11722
12621
  }
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");
12622
+ const tmpDir = import_node_fs9.default.mkdtempSync(import_node_path7.default.join(import_node_os7.default.tmpdir(), "drip-config-"));
12623
+ const outFile = import_node_path7.default.join(tmpDir, "config.mjs");
11725
12624
  try {
11726
12625
  await esbuild.build({
11727
12626
  entryPoints: [absPath],
@@ -11737,7 +12636,7 @@ async function importConfigModule(absPath) {
11737
12636
  return await import(href);
11738
12637
  } finally {
11739
12638
  try {
11740
- import_node_fs8.default.rmSync(tmpDir, { recursive: true, force: true });
12639
+ import_node_fs9.default.rmSync(tmpDir, { recursive: true, force: true });
11741
12640
  } catch {
11742
12641
  }
11743
12642
  }
@@ -11758,7 +12657,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
11758
12657
  );
11759
12658
  }
11760
12659
  const spinner = process.stderr.isTTY ? await createSpinner(`Loading config from ${source_default.cyan(configPath)}...`) : null;
11761
- const absoluteConfigPath = import_node_path6.default.resolve(configPath);
12660
+ const absoluteConfigPath = import_node_path7.default.resolve(configPath);
11762
12661
  let experiments;
11763
12662
  try {
11764
12663
  const mod = await importConfigModule(absoluteConfigPath);
@@ -11825,7 +12724,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
11825
12724
  return;
11826
12725
  }
11827
12726
  const assetRewriter = createAssetReferenceRewriter({
11828
- baseDir: import_node_path6.default.dirname(absoluteConfigPath),
12727
+ baseDir: import_node_path7.default.dirname(absoluteConfigPath),
11829
12728
  baseUrl: creds.baseUrl,
11830
12729
  enabled: opts.assetUpload !== false,
11831
12730
  upload: async (file) => {
@@ -11855,7 +12754,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
11855
12754
  let body5;
11856
12755
  try {
11857
12756
  body5 = toApiBody(
11858
- await rewriteExperimentAssets(exp, assetRewriter, import_node_path6.default.dirname(absoluteConfigPath))
12757
+ await rewriteExperimentAssets(exp, assetRewriter, import_node_path7.default.dirname(absoluteConfigPath))
11859
12758
  );
11860
12759
  } catch (error) {
11861
12760
  const message = error instanceof Error ? error.message : String(error);
@@ -12058,25 +12957,25 @@ var previewCommand = new Command("preview").description(
12058
12957
 
12059
12958
  // src/commands/harness.ts
12060
12959
  init_source();
12061
- var import_node_fs9 = __toESM(require("node:fs"));
12062
- var import_node_path7 = __toESM(require("node:path"));
12960
+ var import_node_fs10 = __toESM(require("node:fs"));
12961
+ var import_node_path8 = __toESM(require("node:path"));
12063
12962
  var import_node_child_process6 = require("node:child_process");
12064
12963
  function slugify(value, fallback2 = "variation") {
12065
12964
  const slug = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
12066
12965
  return slug || fallback2;
12067
12966
  }
12068
12967
  function readJson(filePath) {
12069
- if (!import_node_fs9.default.existsSync(filePath)) return null;
12070
- return JSON.parse(import_node_fs9.default.readFileSync(filePath, "utf8"));
12968
+ if (!import_node_fs10.default.existsSync(filePath)) return null;
12969
+ return JSON.parse(import_node_fs10.default.readFileSync(filePath, "utf8"));
12071
12970
  }
12072
12971
  function resolveFolder(folder) {
12073
- return import_node_path7.default.resolve(process.cwd(), folder);
12972
+ return import_node_path8.default.resolve(process.cwd(), folder);
12074
12973
  }
12075
12974
  function experimentConfigPath(folder) {
12076
- return import_node_path7.default.join(folder, "experiment.config.json");
12975
+ return import_node_path8.default.join(folder, "experiment.config.json");
12077
12976
  }
12078
12977
  function apexConfigPath(folder) {
12079
- return import_node_path7.default.join(folder, "apex.harness.json");
12978
+ return import_node_path8.default.join(folder, "apex.harness.json");
12080
12979
  }
12081
12980
  function loadHarnessConfig(folder) {
12082
12981
  const experiment = readJson(experimentConfigPath(folder)) ?? {};
@@ -12085,8 +12984,8 @@ function loadHarnessConfig(folder) {
12085
12984
  const targetOrigin = experiment.targetOrigin?.replace(/\/+$/, "") ?? "";
12086
12985
  const defaultPath = experiment.defaultPath ?? "/";
12087
12986
  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);
12987
+ const id = experiment.id ?? import_node_path8.default.basename(import_node_path8.default.dirname(folder));
12988
+ const variation = import_node_path8.default.basename(folder);
12090
12989
  return {
12091
12990
  experiment,
12092
12991
  apex: {
@@ -12121,37 +13020,37 @@ function runAbHarness(args, cwd) {
12121
13020
  function artifactPaths(folder, experiment) {
12122
13021
  const variant = experiment.variant;
12123
13022
  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 };
13023
+ const jsPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".js"));
13024
+ const cssPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".css"));
13025
+ return { jsPath, cssPath: import_node_fs10.default.existsSync(cssPath) ? cssPath : null };
12127
13026
  }
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) : [];
13027
+ const buildDir = import_node_path8.default.join(folder, "build");
13028
+ const files = import_node_fs10.default.existsSync(buildDir) ? import_node_fs10.default.readdirSync(buildDir) : [];
12130
13029
  const js = files.find(
12131
13030
  (file) => file.endsWith(".js") && !file.endsWith(".bundle.js") && !file.endsWith(".min.js")
12132
13031
  );
12133
13032
  const css = files.find((file) => file.endsWith(".css") && !file.endsWith(".min.css"));
12134
13033
  if (!js) throw new Error("Could not find harness JS artifact in build/");
12135
13034
  return {
12136
- jsPath: import_node_path7.default.join(buildDir, js),
12137
- cssPath: css ? import_node_path7.default.join(buildDir, css) : null
13035
+ jsPath: import_node_path8.default.join(buildDir, js),
13036
+ cssPath: css ? import_node_path8.default.join(buildDir, css) : null
12138
13037
  };
12139
13038
  }
12140
13039
  async function uploadVersion(params) {
12141
13040
  const creds = requireCredentials();
12142
13041
  const { jsPath, cssPath } = artifactPaths(params.folder, params.experiment);
12143
- if (!import_node_fs9.default.existsSync(jsPath)) throw new Error(`Missing JS artifact: ${jsPath}`);
13042
+ if (!import_node_fs10.default.existsSync(jsPath)) throw new Error(`Missing JS artifact: ${jsPath}`);
12144
13043
  const form = new FormData();
12145
13044
  form.append(
12146
13045
  "js",
12147
- new Blob([import_node_fs9.default.readFileSync(jsPath)], { type: "application/javascript" }),
12148
- import_node_path7.default.basename(jsPath)
13046
+ new Blob([import_node_fs10.default.readFileSync(jsPath)], { type: "application/javascript" }),
13047
+ import_node_path8.default.basename(jsPath)
12149
13048
  );
12150
- if (cssPath && import_node_fs9.default.existsSync(cssPath) && import_node_fs9.default.statSync(cssPath).size > 0) {
13049
+ if (cssPath && import_node_fs10.default.existsSync(cssPath) && import_node_fs10.default.statSync(cssPath).size > 0) {
12151
13050
  form.append(
12152
13051
  "css",
12153
- new Blob([import_node_fs9.default.readFileSync(cssPath)], { type: "text/css" }),
12154
- import_node_path7.default.basename(cssPath)
13052
+ new Blob([import_node_fs10.default.readFileSync(cssPath)], { type: "text/css" }),
13053
+ import_node_path8.default.basename(cssPath)
12155
13054
  );
12156
13055
  }
12157
13056
  form.append(
@@ -12160,8 +13059,8 @@ async function uploadVersion(params) {
12160
13059
  ...params.apex,
12161
13060
  resetJs: cleanupResetJs(params.experiment),
12162
13061
  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
13062
+ jsPath: import_node_path8.default.relative(params.folder, jsPath),
13063
+ cssPath: cssPath ? import_node_path8.default.relative(params.folder, cssPath) : null
12165
13064
  })
12166
13065
  );
12167
13066
  const res = await uploadHarnessVersion(creds, params.session.sessionId, form);
@@ -12202,11 +13101,11 @@ var harnessCommand = new Command("harness").description(
12202
13101
  harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action(function action24(folderArg, opts) {
12203
13102
  const folder = resolveFolder(folderArg);
12204
13103
  const targetPath = apexConfigPath(folder);
12205
- if (import_node_fs9.default.existsSync(targetPath) && !opts.force) {
13104
+ if (import_node_fs10.default.existsSync(targetPath) && !opts.force) {
12206
13105
  fail(this, `${targetPath} already exists. Use --force to overwrite.`, EXIT_CODES.conflict);
12207
13106
  }
12208
13107
  const { apex } = loadHarnessConfig(folder);
12209
- import_node_fs9.default.writeFileSync(targetPath, `${JSON.stringify(apex, null, 2)}
13108
+ import_node_fs10.default.writeFileSync(targetPath, `${JSON.stringify(apex, null, 2)}
12210
13109
  `, "utf8");
12211
13110
  output(
12212
13111
  this,
@@ -12272,7 +13171,7 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
12272
13171
  }
12273
13172
  };
12274
13173
  let debounce = null;
12275
- const watcher = import_node_fs9.default.watch(folder, { recursive: true }, (_event, filename) => {
13174
+ const watcher = import_node_fs10.default.watch(folder, { recursive: true }, (_event, filename) => {
12276
13175
  const name = String(filename ?? "");
12277
13176
  if (!name || name.startsWith("build/") || name.includes("/build/")) return;
12278
13177
  if (debounce) clearTimeout(debounce);
@@ -12314,9 +13213,9 @@ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experime
12314
13213
 
12315
13214
  // src/commands/operator.ts
12316
13215
  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"));
13216
+ var import_node_crypto6 = __toESM(require("node:crypto"));
13217
+ var import_node_fs11 = __toESM(require("node:fs"));
13218
+ var import_node_path9 = __toESM(require("node:path"));
12320
13219
  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
13220
  function workspaceIssueFile(field, evidence) {
12322
13221
  if (field === "custom_js" || field === "reset_js") return "src/index.js";
@@ -12360,7 +13259,7 @@ function formatWorkspaceBlockingIssues(value) {
12360
13259
  }).join("\n");
12361
13260
  }
12362
13261
  function sha256Text2(value) {
12363
- return import_node_crypto5.default.createHash("sha256").update(value).digest("hex");
13262
+ return import_node_crypto6.default.createHash("sha256").update(value).digest("hex");
12364
13263
  }
12365
13264
  function byteLength(value) {
12366
13265
  return Buffer.byteLength(value, "utf8");
@@ -12370,40 +13269,60 @@ function slugify2(value, fallback2 = "operator") {
12370
13269
  return slug || fallback2;
12371
13270
  }
12372
13271
  function folderPath2(folder) {
12373
- return import_node_path8.default.resolve(process.cwd(), folder);
13272
+ return import_node_path9.default.resolve(process.cwd(), folder);
12374
13273
  }
12375
13274
  function apexDir(folder) {
12376
- return import_node_path8.default.join(folder, ".apex");
13275
+ return import_node_path9.default.join(folder, ".apex");
12377
13276
  }
12378
13277
  function sessionPath(folder) {
12379
- return import_node_path8.default.join(apexDir(folder), "session.json");
13278
+ return import_node_path9.default.join(apexDir(folder), "session.json");
12380
13279
  }
12381
13280
  function readJsonFile(filePath) {
12382
- if (!import_node_fs10.default.existsSync(filePath)) return null;
12383
- return JSON.parse(import_node_fs10.default.readFileSync(filePath, "utf8"));
13281
+ if (!import_node_fs11.default.existsSync(filePath)) return null;
13282
+ return JSON.parse(import_node_fs11.default.readFileSync(filePath, "utf8"));
12384
13283
  }
12385
13284
  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)}
13285
+ import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(filePath), { recursive: true });
13286
+ import_node_fs11.default.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
12388
13287
  `, "utf8");
12389
13288
  }
12390
13289
  function readSession(folder) {
12391
13290
  const session = readJsonFile(sessionPath(folder));
12392
13291
  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."
13292
+ const filesystemMarker = import_node_path9.default.join(apexDir(folder), "experiment-filesystem.json");
13293
+ if (import_node_fs11.default.existsSync(filesystemMarker)) {
13294
+ throw new CliError(
13295
+ "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.",
13296
+ EXIT_CODES.general,
13297
+ {
13298
+ code: "validation_failed",
13299
+ next_steps: [
13300
+ { command: "apex experiments plan", why: "Use the command for this folder format." },
13301
+ {
13302
+ command: "apex workspace checkout",
13303
+ why: "Create the required folder in a different directory."
13304
+ }
13305
+ ]
13306
+ }
12397
13307
  );
12398
13308
  }
12399
- throw new Error(`Missing ${sessionPath(folder)}. Run apex workspace checkout first.`);
13309
+ throw new CliError(
13310
+ `Missing ${sessionPath(folder)}. Run apex workspace checkout first.`,
13311
+ EXIT_CODES.general,
13312
+ {
13313
+ code: "local_file_error",
13314
+ next_steps: [
13315
+ { command: "apex workspace checkout", why: "Create the missing local folder metadata." }
13316
+ ]
13317
+ }
13318
+ );
12400
13319
  }
12401
13320
  return session;
12402
13321
  }
12403
13322
  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"),
13323
+ import_node_fs11.default.mkdirSync(apexDir(folder), { recursive: true });
13324
+ import_node_fs11.default.writeFileSync(
13325
+ import_node_path9.default.join(apexDir(folder), ".gitignore"),
12407
13326
  "session.json\nharness-session.json\n",
12408
13327
  "utf8"
12409
13328
  );
@@ -12427,15 +13346,15 @@ function listLocalWorkspaceFiles(folder) {
12427
13346
  "brand.md"
12428
13347
  ];
12429
13348
  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);
13349
+ if (!import_node_fs11.default.existsSync(dir)) return;
13350
+ for (const entry of import_node_fs11.default.readdirSync(dir, { withFileTypes: true })) {
13351
+ const absPath = import_node_path9.default.join(dir, entry.name);
12433
13352
  if (entry.isDirectory()) {
12434
13353
  visit2(absPath);
12435
13354
  continue;
12436
13355
  }
12437
- const relativePath = validateCanonicalPath(import_node_path8.default.relative(folder, absPath));
12438
- const content = import_node_fs10.default.readFileSync(absPath, "utf8");
13356
+ const relativePath = validateCanonicalPath(import_node_path9.default.relative(folder, absPath));
13357
+ const content = import_node_fs11.default.readFileSync(absPath, "utf8");
12439
13358
  files.push({
12440
13359
  path: relativePath,
12441
13360
  absPath,
@@ -12445,11 +13364,11 @@ function listLocalWorkspaceFiles(folder) {
12445
13364
  });
12446
13365
  }
12447
13366
  }
12448
- for (const root of roots) visit2(import_node_path8.default.join(folder, root));
13367
+ for (const root of roots) visit2(import_node_path9.default.join(folder, root));
12449
13368
  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");
13369
+ const absPath = import_node_path9.default.join(folder, file);
13370
+ if (!import_node_fs11.default.existsSync(absPath)) continue;
13371
+ const content = import_node_fs11.default.readFileSync(absPath, "utf8");
12453
13372
  files.push({
12454
13373
  path: file,
12455
13374
  absPath,
@@ -12473,7 +13392,7 @@ function validateJsonFiles(files) {
12473
13392
  }
12474
13393
  }
12475
13394
  function readExperimentConfig(folder) {
12476
- return readJsonFile(import_node_path8.default.join(folder, "experiment.config.json")) ?? {};
13395
+ return readJsonFile(import_node_path9.default.join(folder, "experiment.config.json")) ?? {};
12477
13396
  }
12478
13397
  function resolveVariation(exp, variationArg) {
12479
13398
  const variations = exp.variations ?? [];
@@ -12490,9 +13409,9 @@ function resolveVariation(exp, variationArg) {
12490
13409
  function writeWorkspaceFiles(folder, workspace) {
12491
13410
  for (const file of workspace.files) {
12492
13411
  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");
13412
+ const absPath = import_node_path9.default.join(folder, relativePath);
13413
+ import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(absPath), { recursive: true });
13414
+ import_node_fs11.default.writeFileSync(absPath, file.content, "utf8");
12496
13415
  }
12497
13416
  }
12498
13417
  function sessionFromWorkspace(input) {
@@ -12561,10 +13480,10 @@ async function uploadCurrentArtifactVersion(folder, sessionId) {
12561
13480
  form.append(
12562
13481
  "js",
12563
13482
  new Blob([js.content], { type: "application/javascript" }),
12564
- import_node_path8.default.basename(js.path)
13483
+ import_node_path9.default.basename(js.path)
12565
13484
  );
12566
13485
  if (css && css.content.trim()) {
12567
- form.append("css", new Blob([css.content], { type: "text/css" }), import_node_path8.default.basename(css.path));
13486
+ form.append("css", new Blob([css.content], { type: "text/css" }), import_node_path9.default.basename(css.path));
12568
13487
  }
12569
13488
  form.append("manifest", JSON.stringify(manifest));
12570
13489
  const res = await uploadHarnessVersion(creds, sessionId, form);
@@ -12683,7 +13602,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
12683
13602
  classifyStatus(workspace.status),
12684
13603
  workspace.errorDetails
12685
13604
  );
12686
- import_node_fs10.default.mkdirSync(folder, { recursive: true });
13605
+ import_node_fs11.default.mkdirSync(folder, { recursive: true });
12687
13606
  writeWorkspaceFiles(folder, workspace.data);
12688
13607
  writeSession(
12689
13608
  folder,
@@ -12740,6 +13659,7 @@ operatorCommand.command("status").argument("[folder]", "Operator workspace folde
12740
13659
  }
12741
13660
  );
12742
13661
  } catch (error) {
13662
+ if (error instanceof CliError) throw error;
12743
13663
  fail(this, error instanceof Error ? error.message : String(error));
12744
13664
  }
12745
13665
  });
@@ -12749,8 +13669,8 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
12749
13669
  const session = readSession(folder);
12750
13670
  const harnessSession = await createExactHarnessSession(folder, session);
12751
13671
  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"), {
13672
+ import_node_fs11.default.mkdirSync(apexDir(folder), { recursive: true });
13673
+ writeJsonFile(import_node_path9.default.join(apexDir(folder), "harness-session.json"), {
12754
13674
  sessionId: harnessSession.sessionId,
12755
13675
  experimentId: harnessSession.experimentId,
12756
13676
  variationId: harnessSession.variationId,
@@ -12805,7 +13725,7 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
12805
13725
  }
12806
13726
  };
12807
13727
  let timer = null;
12808
- const watcher = import_node_fs10.default.watch(folder, { recursive: true }, (_event, filename) => {
13728
+ const watcher = import_node_fs11.default.watch(folder, { recursive: true }, (_event, filename) => {
12809
13729
  const name = String(filename ?? "");
12810
13730
  if (!name || name.startsWith(".apex/") || name.includes("/.apex/")) return;
12811
13731
  if (timer) clearTimeout(timer);
@@ -12840,13 +13760,13 @@ operatorCommand.command("publish").argument("[folder]", "Operator workspace fold
12840
13760
 
12841
13761
  // src/commands/roadmap.ts
12842
13762
  init_source();
12843
- var import_node_fs11 = __toESM(require("node:fs"));
12844
- var import_node_path9 = __toESM(require("node:path"));
13763
+ var import_node_fs12 = __toESM(require("node:fs"));
13764
+ var import_node_path10 = __toESM(require("node:path"));
12845
13765
  var LEGACY_OUTPUT_REMOVAL_VERSION = "0.7.0";
12846
13766
  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))}...`;
13767
+ const text2 = typeof value === "string" ? value : value == null ? "" : String(value);
13768
+ if (text2.length <= max) return text2;
13769
+ return `${text2.slice(0, Math.max(0, max - 3))}...`;
12850
13770
  }
12851
13771
  function score(value) {
12852
13772
  if (typeof value !== "number" || !Number.isFinite(value)) return "-";
@@ -12951,9 +13871,9 @@ var handoff = new Command("handoff").description(
12951
13871
  `;
12952
13872
  const outputData = opts.instructions ? { roadmapItemId: id, instructions: String(payload.instructions ?? "") } : payload;
12953
13873
  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");
13874
+ const target = import_node_path10.default.resolve(process.cwd(), opts.out ?? opts.output);
13875
+ import_node_fs12.default.mkdirSync(import_node_path10.default.dirname(target), { recursive: true });
13876
+ import_node_fs12.default.writeFileSync(target, rendered, "utf8");
12957
13877
  output(this, { path: target, roadmapItemId: id }, () => printSuccess(`Wrote ${target}`));
12958
13878
  return;
12959
13879
  }
@@ -12967,8 +13887,8 @@ roadmapCommand.addCommand(handoff);
12967
13887
 
12968
13888
  // src/commands/tracking.ts
12969
13889
  init_source();
12970
- var import_node_fs12 = __toESM(require("node:fs"));
12971
- var import_node_path10 = __toESM(require("node:path"));
13890
+ var import_node_fs13 = __toESM(require("node:fs"));
13891
+ var import_node_path11 = __toESM(require("node:path"));
12972
13892
  function normalizeUrl(input) {
12973
13893
  const value = input.trim();
12974
13894
  if (!value) throw new Error("URL cannot be empty");
@@ -13014,17 +13934,17 @@ function commonCookieDomain(hostnames) {
13014
13934
  return suffix && suffixes.every((candidate) => candidate === suffix) ? `.${suffix}` : null;
13015
13935
  }
13016
13936
  function defaultManifestPath() {
13017
- return import_node_path10.default.resolve(process.cwd(), ".apex", "tracking-manifest.json");
13937
+ return import_node_path11.default.resolve(process.cwd(), ".apex", "tracking-manifest.json");
13018
13938
  }
13019
13939
  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"));
13940
+ if (!import_node_fs13.default.existsSync(filePath)) throw new Error(`Manifest not found: ${filePath}`);
13941
+ const parsed = JSON.parse(import_node_fs13.default.readFileSync(filePath, "utf8"));
13022
13942
  if (parsed.schemaVersion !== 1) throw new Error("Unsupported tracking manifest schema");
13023
13943
  return parsed;
13024
13944
  }
13025
13945
  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)}
13946
+ import_node_fs13.default.mkdirSync(import_node_path11.default.dirname(filePath), { recursive: true });
13947
+ import_node_fs13.default.writeFileSync(filePath, `${JSON.stringify(manifest, null, 2)}
13028
13948
  `, "utf8");
13029
13949
  }
13030
13950
  function buildSnippet(shopId, endpoint) {
@@ -13091,7 +14011,7 @@ function buildManifest(urlArgs, opts) {
13091
14011
  };
13092
14012
  }
13093
14013
  function manifestPath(input) {
13094
- return import_node_path10.default.resolve(process.cwd(), input ?? defaultManifestPath());
14014
+ return import_node_path11.default.resolve(process.cwd(), input ?? defaultManifestPath());
13095
14015
  }
13096
14016
  function printManifestPlan(manifest) {
13097
14017
  console.log(source_default.bold("Apex tracking ingestion plan"));
@@ -13181,12 +14101,15 @@ trackingCommand.command("verify").description("Verify live tracking events for t
13181
14101
  ["Last event", totals.last_event_at ?? "-"]
13182
14102
  ]
13183
14103
  );
14104
+ if (res.data.truncated) {
14105
+ console.log(`Showing the newest ${totals.events} events; totals cover this sample only`);
14106
+ }
13184
14107
  const eventTypes = Object.entries(totals.event_types);
13185
14108
  if (eventTypes.length > 0) {
13186
14109
  console.log();
13187
14110
  printTable(
13188
14111
  ["EVENT TYPE", "COUNT"],
13189
- eventTypes.map(([eventType, count]) => [eventType, String(count)])
14112
+ eventTypes.map(([eventType, count2]) => [eventType, String(count2)])
13190
14113
  );
13191
14114
  }
13192
14115
  if (res.data.recent_events.length > 0) {
@@ -13261,10 +14184,180 @@ trackingCommand.command("latency").description("Measure forced-preview versus na
13261
14184
  });
13262
14185
  });
13263
14186
 
14187
+ // src/commands/events.ts
14188
+ var import_node_fs14 = __toESM(require("node:fs"));
14189
+ var eventsCommand = new Command("events").description("Export raw tracking events");
14190
+ 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) => {
14191
+ if (!Number.isFinite(Date.parse(opts.from)) || !Number.isFinite(Date.parse(opts.to))) {
14192
+ fail(command, "--from and --to must be ISO 8601 timestamps", EXIT_CODES.validation);
14193
+ }
14194
+ if (!["exclude", "include", "only"].includes(opts.qa)) {
14195
+ fail(command, "--qa must be exclude, include, or only", EXIT_CODES.validation);
14196
+ }
14197
+ const limit = Number(opts.limit);
14198
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) {
14199
+ fail(command, "--limit must be an integer from 1 to 1000", EXIT_CODES.validation);
14200
+ }
14201
+ const summaryOnStdout = Boolean(opts.jsonSummary) || isJsonMode();
14202
+ if (isJsonMode() && !opts.out) {
14203
+ fail(
14204
+ command,
14205
+ "In JSON mode (--json or piped stdout) pass --out <file> for the NDJSON rows; stdout carries one summary document.",
14206
+ EXIT_CODES.validation,
14207
+ {
14208
+ next_steps: [
14209
+ {
14210
+ command: "apex events export --from <iso> --to <iso> --out events.ndjson",
14211
+ why: "Write rows to a file."
14212
+ }
14213
+ ]
14214
+ }
14215
+ );
14216
+ }
14217
+ if (opts.jsonSummary && !opts.out) {
14218
+ fail(command, "--json-summary requires --out for the event rows", EXIT_CODES.validation);
14219
+ }
14220
+ const output2 = opts.out ? import_node_fs14.default.createWriteStream(opts.out, { flags: "wx" }) : null;
14221
+ let outputError = null;
14222
+ output2?.on("error", (error) => {
14223
+ outputError = error;
14224
+ });
14225
+ if (output2) {
14226
+ try {
14227
+ await new Promise((resolve2, reject) => {
14228
+ output2.once("open", () => resolve2());
14229
+ output2.once("error", reject);
14230
+ });
14231
+ } catch (error) {
14232
+ fail(
14233
+ command,
14234
+ `Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
14235
+ EXIT_CODES.general,
14236
+ { code: "local_file_error" }
14237
+ );
14238
+ }
14239
+ }
14240
+ const writeLine = async (line) => {
14241
+ const stream = output2 ?? process.stdout;
14242
+ const accepted = output2 ? output2.write(line) : writeRawCommandOutput(line);
14243
+ if (accepted) return;
14244
+ await new Promise((resolve2, reject) => {
14245
+ const onDrain = () => {
14246
+ stream.off("error", onError);
14247
+ resolve2();
14248
+ };
14249
+ const onError = (error) => {
14250
+ stream.off("drain", onDrain);
14251
+ reject(error);
14252
+ };
14253
+ stream.once("drain", onDrain);
14254
+ stream.once("error", onError);
14255
+ });
14256
+ };
14257
+ let cursor;
14258
+ let written = 0;
14259
+ let pages = 0;
14260
+ let totals;
14261
+ do {
14262
+ const response = await exportEventsPage(requireCredentials(), {
14263
+ from: opts.from,
14264
+ to: opts.to,
14265
+ experiment: opts.experiment,
14266
+ eventTypes: opts.eventType?.split(",").map((value) => value.trim()).filter(Boolean),
14267
+ qa: opts.qa,
14268
+ limit,
14269
+ cursor
14270
+ });
14271
+ if (!response.ok || !response.data) {
14272
+ output2?.destroy();
14273
+ fail(
14274
+ command,
14275
+ response.error ?? "Event export page failed",
14276
+ classifyStatus(response.status),
14277
+ response.errorDetails
14278
+ );
14279
+ }
14280
+ if (outputError)
14281
+ fail(
14282
+ command,
14283
+ `Cannot write ${opts.out}: ${outputError.message}`,
14284
+ EXIT_CODES.general,
14285
+ {
14286
+ code: "local_file_error"
14287
+ }
14288
+ );
14289
+ const page = response.data.data;
14290
+ totals ??= page.totals;
14291
+ for (const event of page.events) {
14292
+ try {
14293
+ await writeLine(`${JSON.stringify(event)}
14294
+ `);
14295
+ } catch (error) {
14296
+ fail(
14297
+ command,
14298
+ `Cannot write ${opts.out ?? "stdout"}: ${error instanceof Error ? error.message : String(error)}`,
14299
+ EXIT_CODES.general,
14300
+ { code: "local_file_error" }
14301
+ );
14302
+ }
14303
+ written += 1;
14304
+ }
14305
+ pages += 1;
14306
+ cursor = page.next_cursor ?? void 0;
14307
+ } while (cursor);
14308
+ if (output2) {
14309
+ try {
14310
+ await new Promise((resolve2, reject) => {
14311
+ if (outputError) return reject(outputError);
14312
+ output2.once("error", reject);
14313
+ output2.end(resolve2);
14314
+ });
14315
+ } catch (error) {
14316
+ fail(
14317
+ command,
14318
+ `Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
14319
+ EXIT_CODES.general,
14320
+ { code: "local_file_error" }
14321
+ );
14322
+ }
14323
+ if (outputError)
14324
+ fail(
14325
+ command,
14326
+ `Cannot write ${opts.out}: ${outputError.message}`,
14327
+ EXIT_CODES.general,
14328
+ {
14329
+ code: "local_file_error"
14330
+ }
14331
+ );
14332
+ }
14333
+ const matches = totals ? written === totals.events : false;
14334
+ const summary = {
14335
+ events_written: written,
14336
+ pages,
14337
+ totals: totals ?? null,
14338
+ count_matches: matches
14339
+ };
14340
+ if (!matches)
14341
+ fail(
14342
+ command,
14343
+ "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.",
14344
+ EXIT_CODES.general,
14345
+ {
14346
+ reason: "export_count_mismatch",
14347
+ ...summary
14348
+ }
14349
+ );
14350
+ if (summaryOnStdout) writeEnvelope(summary, "apex events export");
14351
+ process.stderr.write(
14352
+ `Exported ${written} events across ${pages} page(s); total ${totals?.events ?? "unknown"}; count matches: ${matches}.
14353
+ `
14354
+ );
14355
+ });
14356
+
13264
14357
  // src/commands/goals.ts
13265
14358
  init_source();
13266
- var import_node_fs13 = __toESM(require("node:fs"));
13267
- var import_node_path11 = __toESM(require("node:path"));
14359
+ var import_node_fs15 = __toESM(require("node:fs"));
14360
+ var import_node_path12 = __toESM(require("node:path"));
13268
14361
 
13269
14362
  // ../sdk/src/safe-regex.ts
13270
14363
  var MAX_PATTERN_LENGTH = 1024;
@@ -13440,9 +14533,9 @@ function printSuggestions(data) {
13440
14533
  function parseCapture(command, value) {
13441
14534
  if (!value) return void 0;
13442
14535
  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;
14536
+ 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
14537
  try {
13445
- return JSON.parse(text);
14538
+ return JSON.parse(text2);
13446
14539
  } catch (error) {
13447
14540
  const detail5 = error instanceof Error ? error.message : String(error);
13448
14541
  fail(command, `Invalid --capture JSON: ${detail5}`, EXIT_CODES.validation);
@@ -13586,10 +14679,10 @@ var test = new Command("test").description("Dry run a goal URL pattern against t
13586
14679
  const days = Math.max(1, Number.parseInt(opts.days, 10) || 14);
13587
14680
  const dependence = urlPatternDependence(pattern);
13588
14681
  if (dependence !== "none") {
13589
- const reason = DEPENDENCE_REASON[dependence];
13590
- output(this, { verdict: "indeterminate", pattern, dependence, reason }, () => {
14682
+ const reason2 = DEPENDENCE_REASON[dependence];
14683
+ output(this, { verdict: "indeterminate", pattern, dependence, reason: reason2 }, () => {
13591
14684
  console.log(source_default.yellow("INDETERMINATE"));
13592
- console.log(reason);
14685
+ console.log(reason2);
13593
14686
  });
13594
14687
  return;
13595
14688
  }
@@ -13693,12 +14786,12 @@ Examples:
13693
14786
 
13694
14787
  // src/commands/funnels.ts
13695
14788
  init_source();
13696
- var import_node_fs14 = __toESM(require("node:fs"));
13697
- var import_node_path12 = __toESM(require("node:path"));
14789
+ var import_node_fs16 = __toESM(require("node:fs"));
14790
+ var import_node_path13 = __toESM(require("node:path"));
13698
14791
  function readSteps(command, file) {
13699
14792
  let parsed;
13700
14793
  try {
13701
- parsed = JSON.parse(import_node_fs14.default.readFileSync(import_node_path12.default.resolve(process.cwd(), file), "utf8"));
14794
+ parsed = JSON.parse(import_node_fs16.default.readFileSync(import_node_path13.default.resolve(process.cwd(), file), "utf8"));
13702
14795
  } catch (error) {
13703
14796
  fail(
13704
14797
  command,
@@ -14012,8 +15105,8 @@ var off = new Command("off").description("Disable consent gating").action(async
14012
15105
  var consentCommand = new Command("consent").description("Manage consent gating").addCommand(get2).addCommand(set).addCommand(off);
14013
15106
 
14014
15107
  // src/commands/project-code.ts
14015
- var import_node_fs15 = require("node:fs");
14016
- var import_node_path13 = require("node:path");
15108
+ var import_node_fs17 = require("node:fs");
15109
+ var import_node_path14 = require("node:path");
14017
15110
 
14018
15111
  // src/commands/runtime-settings-shop.ts
14019
15112
  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 +15151,7 @@ var fields = ["enabled", "css", "helperJs", "js"];
14058
15151
  function readPatch(command, file) {
14059
15152
  let input;
14060
15153
  try {
14061
- input = JSON.parse((0, import_node_fs15.readFileSync)(file, "utf8"));
15154
+ input = JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
14062
15155
  } catch (error) {
14063
15156
  fail(command, `Cannot read project code JSON: ${String(error)}`, EXIT_CODES.validation);
14064
15157
  }
@@ -14118,15 +15211,15 @@ var projectCodeCommand = new Command("project-code").description(
14118
15211
  );
14119
15212
  projectCodeCommand.addCommand(
14120
15213
  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(
15214
+ const { credentials: _credentials, ...identity2 } = await identifyRuntimeSettingsShop(
14122
15215
  command,
14123
15216
  options.shop
14124
15217
  );
14125
- const current = await readCurrent(command, { ...identity, credentials: _credentials });
15218
+ const current = await readCurrent(command, { ...identity2, credentials: _credentials });
14126
15219
  output(
14127
15220
  command,
14128
- { ...identity, ...current },
14129
- () => process.stdout.write(`${JSON.stringify({ ...identity, ...current }, null, 2)}
15221
+ { ...identity2, ...current },
15222
+ () => process.stdout.write(`${JSON.stringify({ ...identity2, ...current }, null, 2)}
14130
15223
  `)
14131
15224
  );
14132
15225
  })
@@ -14144,11 +15237,11 @@ for (const mode of ["diff", "set"]) {
14144
15237
  const patch = readPatch(actionCommand, options.file);
14145
15238
  const shop = await identifyRuntimeSettingsShop(actionCommand, options.shop);
14146
15239
  const current = await readCurrent(actionCommand, shop);
14147
- const { credentials: _credentials, ...identity } = shop;
15240
+ const { credentials: _credentials, ...identity2 } = shop;
14148
15241
  const diff = effectiveDiff(current.projectCode, patch);
14149
15242
  if (mode === "diff" || diff.changedFields.length === 0) {
14150
15243
  const result2 = {
14151
- ...identity,
15244
+ ...identity2,
14152
15245
  status: diff.changedFields.length ? "changed" : "unchanged",
14153
15246
  version: current.version,
14154
15247
  diff
@@ -14161,11 +15254,11 @@ for (const mode of ["diff", "set"]) {
14161
15254
  );
14162
15255
  return;
14163
15256
  }
14164
- const backupPath = (0, import_node_path13.resolve)(options.backup);
15257
+ const backupPath = (0, import_node_path14.resolve)(options.backup);
14165
15258
  try {
14166
- (0, import_node_fs15.writeFileSync)(
15259
+ (0, import_node_fs17.writeFileSync)(
14167
15260
  backupPath,
14168
- `${JSON.stringify({ ...identity, ...current, capturedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
15261
+ `${JSON.stringify({ ...identity2, ...current, capturedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
14169
15262
  `,
14170
15263
  { flag: "wx", mode: 384 }
14171
15264
  );
@@ -14195,14 +15288,14 @@ for (const mode of ["diff", "set"]) {
14195
15288
  `Project code saved; publication ${data.precompile.status}: ${data.precompile.summary}`
14196
15289
  );
14197
15290
  }
14198
- if (data.shop_id !== identity.shop_id)
15291
+ if (data.shop_id !== identity2.shop_id)
14199
15292
  warnings.push(
14200
15293
  "The write response has a different shop identity; inspect the saved state before another write."
14201
15294
  );
14202
15295
  for (const warning of warnings) process.stderr.write(`WARNING: ${warning}
14203
15296
  `);
14204
15297
  const result = {
14205
- ...identity,
15298
+ ...identity2,
14206
15299
  status: "saved",
14207
15300
  diff,
14208
15301
  backupPath,
@@ -14222,12 +15315,12 @@ for (const mode of ["diff", "set"]) {
14222
15315
  }
14223
15316
 
14224
15317
  // src/commands/triggers.ts
14225
- var import_node_fs16 = __toESM(require("node:fs"));
14226
- var import_node_path14 = __toESM(require("node:path"));
15318
+ var import_node_fs18 = __toESM(require("node:fs"));
15319
+ var import_node_path15 = __toESM(require("node:path"));
14227
15320
 
14228
15321
  // src/trigger-proposal.ts
14229
- function runtimePath(path24) {
14230
- return path24.replaceAll("[]", ".0");
15322
+ function runtimePath(path25) {
15323
+ return path25.replaceAll("[]", ".0");
14231
15324
  }
14232
15325
  function fieldsFrom(value, prefix = "", descriptorList = false) {
14233
15326
  if (!value || typeof value !== "object") return [];
@@ -14240,18 +15333,18 @@ function fieldsFrom(value, prefix = "", descriptorList = false) {
14240
15333
  )
14241
15334
  );
14242
15335
  }
14243
- const record2 = value;
14244
- const explicitPath = typeof record2.path === "string" ? record2.path : void 0;
15336
+ const record3 = value;
15337
+ const explicitPath = typeof record3.path === "string" ? record3.path : void 0;
14245
15338
  if (descriptorList && explicitPath) {
14246
- const rawValues = record2.values ?? record2.samples ?? record2.examples ?? record2.example;
15339
+ const rawValues = record3.values ?? record3.samples ?? record3.examples ?? record3.example;
14247
15340
  const values = Array.isArray(rawValues) ? rawValues : rawValues === void 0 ? [] : [rawValues];
14248
- const magnitude = [record2.mean, record2.average, record2.max, record2.magnitude].find(
15341
+ const magnitude = [record3.mean, record3.average, record3.max, record3.magnitude].find(
14249
15342
  (candidate) => typeof candidate === "number"
14250
15343
  );
14251
- const numericType = Array.isArray(record2.types) && record2.types.length > 0 && record2.types.every((type) => type === "num" || type === "numstr" || type === "numstr_comma");
15344
+ const numericType = Array.isArray(record3.types) && record3.types.length > 0 && record3.types.every((type) => type === "num" || type === "numstr" || type === "numstr_comma");
14252
15345
  return [{ path: runtimePath(explicitPath), values, magnitude, numericType }];
14253
15346
  }
14254
- return Object.entries(record2).flatMap(([key, child]) => {
15347
+ return Object.entries(record3).flatMap(([key, child]) => {
14255
15348
  if (["eventName", "event_name", "name", "count", "frequency", "evidence"].includes(key))
14256
15349
  return [];
14257
15350
  const next = prefix ? `${prefix}.${key}` : key;
@@ -14284,8 +15377,8 @@ function isDescriptorArray(value) {
14284
15377
  ]);
14285
15378
  return Array.isArray(value) && value.length > 0 && value.every((item) => {
14286
15379
  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));
15380
+ const record3 = item;
15381
+ return typeof record3.path === "string" && record3.path.length > 0 && Object.keys(record3).every((key) => descriptorKeys.has(key));
14289
15382
  });
14290
15383
  }
14291
15384
  function candidatesFrom(report) {
@@ -14305,9 +15398,9 @@ function candidatesFrom(report) {
14305
15398
  }
14306
15399
  return [root];
14307
15400
  }
14308
- function score2(path24, patterns) {
15401
+ function score2(path25, patterns) {
14309
15402
  return patterns.reduce(
14310
- (total, pattern, index) => total + (pattern.test(path24) ? patterns.length - index : 0),
15403
+ (total, pattern, index) => total + (pattern.test(path25) ? patterns.length - index : 0),
14311
15404
  0
14312
15405
  );
14313
15406
  }
@@ -14441,10 +15534,10 @@ var whenOps = new Set(runtime_settings_trigger_constants_default.whenOps);
14441
15534
  function strictObject(value, location, keys) {
14442
15535
  if (!value || typeof value !== "object" || Array.isArray(value))
14443
15536
  throw new Error(`${location} must be an object`);
14444
- const record2 = value;
14445
- for (const key of Object.keys(record2))
15537
+ const record3 = value;
15538
+ for (const key of Object.keys(record3))
14446
15539
  if (!keys.has(key)) throw new Error(`${location}.${key} is not allowed`);
14447
- return record2;
15540
+ return record3;
14448
15541
  }
14449
15542
  function optionalType(value, type, location) {
14450
15543
  if (value !== void 0 && typeof value !== type)
@@ -14471,8 +15564,8 @@ function strictSource(value, location, allowSignal, required = false) {
14471
15564
  function defaultMatchesValueType(value, valueType) {
14472
15565
  if (valueType === "number") {
14473
15566
  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;
15567
+ const text2 = String(value).trim();
15568
+ const normalized = /^-?\d{1,3}(\.\d{3})+,\d+$/.test(text2) ? text2.replace(/\./g, "").replace(",", ".") : /^-?\d+,\d+$/.test(text2) ? text2.replace(",", ".") : text2;
14476
15569
  return Number.isFinite(Number(normalized));
14477
15570
  }
14478
15571
  if (valueType === "boolean") {
@@ -14580,19 +15673,19 @@ function strictTriggers(value) {
14580
15673
  return value.map((item, index) => {
14581
15674
  if (!item || typeof item !== "object" || Array.isArray(item))
14582
15675
  throw new Error(`trigger[${index}] must be an object`);
14583
- const record2 = item;
14584
- for (const key of Object.keys(record2))
15676
+ const record3 = item;
15677
+ for (const key of Object.keys(record3))
14585
15678
  if (!allowed.has(key)) throw new Error(`trigger[${index}].${key} is not allowed`);
14586
- if (typeof record2.eventName !== "string" || !record2.eventName.trim())
15679
+ if (typeof record3.eventName !== "string" || !record3.eventName.trim())
14587
15680
  throw new Error(`trigger[${index}].eventName must be a non-empty string`);
14588
15681
  for (const key of ["enabled", "reEvaluate", "trackPageview"])
14589
- optionalType(record2[key], "boolean", `trigger[${index}].${key}`);
15682
+ optionalType(record3[key], "boolean", `trigger[${index}].${key}`);
14590
15683
  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) {
15684
+ optionalType(record3[key], "string", `trigger[${index}].${key}`);
15685
+ strictWhen(record3.when, `trigger[${index}].when`);
15686
+ if (record3.trackRevenue !== void 0) {
14594
15687
  const revenue = strictObject(
14595
- record2.trackRevenue,
15688
+ record3.trackRevenue,
14596
15689
  `trigger[${index}].trackRevenue`,
14597
15690
  /* @__PURE__ */ new Set([
14598
15691
  "enabled",
@@ -14615,21 +15708,21 @@ function strictTriggers(value) {
14615
15708
  );
14616
15709
  }
14617
15710
  }
14618
- if (record2.mappings !== void 0) {
14619
- if (!Array.isArray(record2.mappings))
15711
+ if (record3.mappings !== void 0) {
15712
+ if (!Array.isArray(record3.mappings))
14620
15713
  throw new Error(`trigger[${index}].mappings must be an array`);
14621
- record2.mappings.forEach(
15714
+ record3.mappings.forEach(
14622
15715
  (mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].mappings[${mappingIndex}]`, false)
14623
15716
  );
14624
15717
  }
14625
- if (record2.properties !== void 0) {
14626
- if (!Array.isArray(record2.properties))
15718
+ if (record3.properties !== void 0) {
15719
+ if (!Array.isArray(record3.properties))
14627
15720
  throw new Error(`trigger[${index}].properties must be an array`);
14628
- record2.properties.forEach(
15721
+ record3.properties.forEach(
14629
15722
  (mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].properties[${mappingIndex}]`, true)
14630
15723
  );
14631
15724
  }
14632
- return record2;
15725
+ return record3;
14633
15726
  });
14634
15727
  }
14635
15728
  function writeWarnings(result) {
@@ -14649,9 +15742,22 @@ async function identify(command, requestedShop) {
14649
15742
  resolvedContext: { ...credentials.resolvedContext, shopId: void 0 }
14650
15743
  };
14651
15744
  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);
15745
+ if (!context.ok) {
15746
+ fail(
15747
+ command,
15748
+ `${context.status === 401 || context.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${context.error ?? "account context unavailable"}`,
15749
+ classifyStatus(context.status),
15750
+ context.errorDetails,
15751
+ apiFailureOf(context)
15752
+ );
15753
+ }
15754
+ const match2 = context.data?.data?.orgs?.flatMap((org) => org.shops).find((shop) => shop.domain.toLowerCase() === requestedShop.toLowerCase());
15755
+ if (!match2) {
15756
+ fail(
15757
+ command,
15758
+ "TRIGGERS_CONFIG_ABSENT: --shop domain is not available to this user",
15759
+ EXIT_CODES.validation
15760
+ );
14655
15761
  }
14656
15762
  credentials = {
14657
15763
  ...credentials,
@@ -14660,46 +15766,49 @@ async function identify(command, requestedShop) {
14660
15766
  }
14661
15767
  const res = await getInstallDomains(credentials);
14662
15768
  if (!res.ok || !res.data) {
14663
- const exitCode = res.status === 401 || res.status === 403 ? 4 : 2;
14664
15769
  fail(
14665
15770
  command,
14666
- `${exitCode === 4 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "shop identity unavailable"}`,
14667
- exitCode
15771
+ `${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "shop identity unavailable"}`,
15772
+ classifyStatus(res.status)
14668
15773
  );
14669
15774
  }
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 };
15775
+ const identity2 = { shop_id: res.data.shop_id, domain: res.data.domain };
15776
+ if (requestedShop !== identity2.shop_id && requestedShop.toLowerCase() !== identity2.domain.toLowerCase())
15777
+ fail(
15778
+ command,
15779
+ "TRIGGERS_CONFIG_ABSENT: --shop does not match the authenticated shop",
15780
+ EXIT_CODES.validation
15781
+ );
15782
+ return { ...identity2, credentials };
14674
15783
  }
14675
15784
  async function send(command, requestedShop, body5) {
14676
- const identity = await identify(command, requestedShop);
14677
- const res = await updateRuntimeSettings(identity.credentials, body5);
15785
+ const identity2 = await identify(command, requestedShop);
15786
+ const res = await updateRuntimeSettings(identity2.credentials, body5);
14678
15787
  if (!res.ok || !res.data)
14679
15788
  fail(
14680
15789
  command,
14681
15790
  `${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,
15791
+ classifyStatus(res.status),
14683
15792
  res.errorDetails
14684
15793
  );
14685
- const { credentials: _credentials, ...publicIdentity } = identity;
15794
+ const { credentials: _credentials, ...publicIdentity } = identity2;
14686
15795
  return { data: res.data, identity: publicIdentity };
14687
15796
  }
14688
15797
  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);
15798
+ const identity2 = await identify(this, opts.shop);
15799
+ const res = await getRuntimeSettings(identity2.credentials);
14691
15800
  if (!res.ok || !res.data)
14692
15801
  fail(
14693
15802
  this,
14694
15803
  `${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),
15804
+ classifyStatus(res.status),
14696
15805
  res.errorDetails
14697
15806
  );
14698
15807
  const triggers = res.data.runtime_settings.dataLayerTriggers ?? [];
14699
- const { credentials: _credentials, ...publicIdentity } = identity;
15808
+ const { credentials: _credentials, ...publicIdentity } = identity2;
14700
15809
  output(this, { verdict: "TRIGGERS_GET", ...publicIdentity, triggers }, () => {
14701
15810
  process.stdout.write(
14702
- `TRIGGERS_GET shop_id=${identity.shop_id} domain=${identity.domain} count=${triggers.length}
15811
+ `TRIGGERS_GET shop_id=${identity2.shop_id} domain=${identity2.domain} count=${triggers.length}
14703
15812
  `
14704
15813
  );
14705
15814
  printTable(
@@ -14716,24 +15825,24 @@ var get3 = new Command("get").description("Get data layer triggers").requiredOpt
14716
15825
  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
15826
  let triggers;
14718
15827
  try {
14719
- triggers = strictTriggers(JSON.parse(import_node_fs16.default.readFileSync(import_node_path14.default.resolve(opts.file), "utf8")));
15828
+ triggers = strictTriggers(JSON.parse(import_node_fs18.default.readFileSync(import_node_path15.default.resolve(opts.file), "utf8")));
14720
15829
  } catch (error) {
14721
15830
  fail(
14722
15831
  this,
14723
15832
  `TRIGGERS_CONFIG_ABSENT: ${error instanceof Error ? error.message : String(error)}`,
14724
- 2
15833
+ EXIT_CODES.validation
14725
15834
  );
14726
15835
  }
14727
- const { data, identity } = await send(this, opts.shop, { dataLayerTriggers: triggers });
15836
+ const { data, identity: identity2 } = await send(this, opts.shop, { dataLayerTriggers: triggers });
14728
15837
  const result = {
14729
15838
  verdict: "TRIGGERS_SET",
14730
- ...identity,
15839
+ ...identity2,
14731
15840
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
14732
15841
  ...data
14733
15842
  };
14734
15843
  output(this, result, () => {
14735
15844
  process.stdout.write(
14736
- `TRIGGERS_SET shop_id=${identity.shop_id} domain=${identity.domain} updated_at=${result.updated_at} count=${triggers.length}
15845
+ `TRIGGERS_SET shop_id=${identity2.shop_id} domain=${identity2.domain} updated_at=${result.updated_at} count=${triggers.length}
14737
15846
  `
14738
15847
  );
14739
15848
  writeWarnings(result);
@@ -14743,17 +15852,21 @@ var propose = new Command("propose").requiredOption("--from-file <u2-report.json
14743
15852
  let proposals;
14744
15853
  let report;
14745
15854
  try {
14746
- report = JSON.parse(import_node_fs16.default.readFileSync(import_node_path14.default.resolve(opts.fromFile), "utf8"));
15855
+ report = JSON.parse(import_node_fs18.default.readFileSync(import_node_path15.default.resolve(opts.fromFile), "utf8"));
14747
15856
  proposals = proposeTriggers(report);
14748
15857
  } catch (error) {
14749
- fail(this, error instanceof Error ? error.message : String(error), 2);
15858
+ fail(this, error instanceof Error ? error.message : String(error), EXIT_CODES.validation);
14750
15859
  }
14751
15860
  const reportBody = report.data && typeof report.data === "object" && !Array.isArray(report.data) ? report.data : report;
14752
15861
  const reportShop = reportBody.shop ?? reportBody.shop_context ?? {};
14753
15862
  const shop_id = String(reportShop.shop_id ?? reportShop.id ?? reportBody.shop_id ?? "");
14754
15863
  const domain = String(reportShop.domain ?? reportBody.domain ?? "");
14755
15864
  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);
15865
+ fail(
15866
+ this,
15867
+ "TRIGGERS_CONFIG_ABSENT: --shop must match the report shop_id or domain",
15868
+ EXIT_CODES.validation
15869
+ );
14757
15870
  }
14758
15871
  if (!proposals.length)
14759
15872
  fail(this, "TRIGGERS_NO_DATA: report has no usable purchase candidates", 3);
@@ -14782,29 +15895,34 @@ var recipe = new Command("apply-recipe").argument("<recipeId>").requiredOption("
14782
15895
  const params = Object.fromEntries(
14783
15896
  opts.param.map((pair) => {
14784
15897
  const i = pair.indexOf("=");
14785
- if (i < 1) fail(this, `TRIGGERS_CONFIG_ABSENT: invalid --param ${pair}; expected k=v`, 2);
15898
+ if (i < 1)
15899
+ fail(
15900
+ this,
15901
+ `TRIGGERS_CONFIG_ABSENT: invalid --param ${pair}; expected k=v`,
15902
+ EXIT_CODES.validation
15903
+ );
14786
15904
  return [pair.slice(0, i), parseParamValue(pair.slice(i + 1))];
14787
15905
  })
14788
15906
  );
14789
- const identity = await identify(this, opts.shop);
14790
- const res = await applyRuntimeSettingsRecipe(identity.credentials, { recipeId, params });
15907
+ const identity2 = await identify(this, opts.shop);
15908
+ const res = await applyRuntimeSettingsRecipe(identity2.credentials, { recipeId, params });
14791
15909
  if (!res.ok || !res.data)
14792
15910
  fail(
14793
15911
  this,
14794
15912
  `${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Recipe failed"}`,
14795
- res.status === 401 || res.status === 403 ? 4 : 2,
15913
+ classifyStatus(res.status),
14796
15914
  res.errorDetails
14797
15915
  );
14798
15916
  const result = {
14799
15917
  verdict: "TRIGGERS_APPLIED",
14800
15918
  ...res.data,
14801
- shop_id: identity.shop_id,
14802
- domain: identity.domain,
15919
+ shop_id: identity2.shop_id,
15920
+ domain: identity2.domain,
14803
15921
  applied_at: (/* @__PURE__ */ new Date()).toISOString()
14804
15922
  };
14805
15923
  output(this, result, () => {
14806
15924
  process.stdout.write(
14807
- `TRIGGERS_APPLIED shop_id=${identity.shop_id} domain=${identity.domain} applied_at=${result.applied_at} recipe=${recipeId}
15925
+ `TRIGGERS_APPLIED shop_id=${identity2.shop_id} domain=${identity2.domain} applied_at=${result.applied_at} recipe=${recipeId}
14808
15926
  `
14809
15927
  );
14810
15928
  writeWarnings(result);
@@ -14813,35 +15931,39 @@ var recipe = new Command("apply-recipe").argument("<recipeId>").requiredOption("
14813
15931
  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
15932
  const hours = Number(opts.hours);
14815
15933
  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);
15934
+ fail(
15935
+ this,
15936
+ "TRIGGERS_CONFIG_ABSENT: --hours must be greater than 0 and at most 48",
15937
+ EXIT_CODES.validation
15938
+ );
14817
15939
  const expiresAt = new Date(Date.now() + hours * 36e5).toISOString();
14818
15940
  const setupCapture = {
14819
15941
  enabled: true,
14820
15942
  expiresAt,
14821
15943
  ...opts.urlPattern.length > 0 ? { urlPatterns: opts.urlPattern } : {}
14822
15944
  };
14823
- const { data, identity } = await send(this, opts.shop, { setupCapture });
14824
- const result = { verdict: "CAPTURE_ENABLED", ...identity, expiresAt, ...data };
15945
+ const { data, identity: identity2 } = await send(this, opts.shop, { setupCapture });
15946
+ const result = { verdict: "CAPTURE_ENABLED", ...identity2, expiresAt, ...data };
14825
15947
  output(
14826
15948
  this,
14827
15949
  result,
14828
15950
  () => process.stdout.write(
14829
- `CAPTURE_ENABLED shop_id=${identity.shop_id} domain=${identity.domain} expires_at=${expiresAt}
15951
+ `CAPTURE_ENABLED shop_id=${identity2.shop_id} domain=${identity2.domain} expires_at=${expiresAt}
14830
15952
  `
14831
15953
  )
14832
15954
  );
14833
15955
  });
14834
15956
  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, {
15957
+ const { data, identity: identity2 } = await send(this, opts.shop, {
14836
15958
  setupCapture: { enabled: false, expiresAt: null }
14837
15959
  });
14838
15960
  const disabled_at = (/* @__PURE__ */ new Date()).toISOString();
14839
- const result = { verdict: "CAPTURE_DISABLED", ...identity, disabled_at, ...data };
15961
+ const result = { verdict: "CAPTURE_DISABLED", ...identity2, disabled_at, ...data };
14840
15962
  output(
14841
15963
  this,
14842
15964
  result,
14843
15965
  () => process.stdout.write(
14844
- `CAPTURE_DISABLED shop_id=${identity.shop_id} domain=${identity.domain} disabled_at=${disabled_at}
15966
+ `CAPTURE_DISABLED shop_id=${identity2.shop_id} domain=${identity2.domain} disabled_at=${disabled_at}
14845
15967
  `
14846
15968
  )
14847
15969
  );
@@ -15061,8 +16183,8 @@ var qa2 = new Command("qa").description("Check static link/form handover paramet
15061
16183
  html = await response.text();
15062
16184
  } catch (error) {
15063
16185
  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);
16186
+ const reason2 = error instanceof Error ? error.message : String(error);
16187
+ fail(this, `Could not fetch ${opts.from}: ${reason2}`, EXIT_CODES.general);
15066
16188
  }
15067
16189
  const param = config.externalTracking.handoverQueryParam;
15068
16190
  const baseUrl = response.url || opts.from;
@@ -15109,6 +16231,138 @@ var qa2 = new Command("qa").description("Check static link/form handover paramet
15109
16231
  });
15110
16232
  var handoverCommand = new Command("handover").description("Manage cross-domain identity handover").addCommand(get4).addCommand(set4).addCommand(off2).addCommand(qa2);
15111
16233
 
16234
+ // src/commands/order-journey.ts
16235
+ function instant(value) {
16236
+ const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:\d{2})$/.exec(
16237
+ value
16238
+ );
16239
+ if (!m) return null;
16240
+ const year = Number(m[1]), month = Number(m[2]), day = Number(m[3]);
16241
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
16242
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
16243
+ 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))
16244
+ return null;
16245
+ const second = Date.parse(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6] ?? "00"}${m[8]}`);
16246
+ return Number.isFinite(second) ? { second, fraction: (m[7] ?? "").replace(/0+$/, "") } : null;
16247
+ }
16248
+ function compare(a, b) {
16249
+ if (a.second !== b.second) return a.second < b.second ? -1 : 1;
16250
+ const precision = Math.max(a.fraction.length, b.fraction.length);
16251
+ const left = a.fraction.padEnd(precision, "0"), right = b.fraction.padEnd(precision, "0");
16252
+ return left === right ? 0 : left < right ? -1 : 1;
16253
+ }
16254
+ var record = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
16255
+ var text = (v) => typeof v === "string" && v.length > 0;
16256
+ 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(
16257
+ v
16258
+ ));
16259
+ var member = (v, values) => typeof v === "string" && values.includes(v);
16260
+ var status = (v) => member(v, ["pass", "fail", "inconclusive"]);
16261
+ var count = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
16262
+ function validReport(v) {
16263
+ 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(
16264
+ (s) => record(s) && (s.freshness === void 0 || member(s.freshness, ["current", "stale", "unknown"])) && member(s.stage, [
16265
+ "consent",
16266
+ "assignment",
16267
+ "event",
16268
+ "order_identity",
16269
+ "authoritative_fact",
16270
+ "attribution",
16271
+ "result"
16272
+ ]) && status(s.status) && Array.isArray(s.reasonCodes) && s.reasonCodes.every(reason)
16273
+ ))
16274
+ return false;
16275
+ if (v.receiptOutcomes !== void 0 && (!Array.isArray(v.receiptOutcomes) || v.receiptOutcomes.length > 6856 || !v.receiptOutcomes.every(
16276
+ (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(
16277
+ (x) => x === null || text(x)
16278
+ ) && member(r.freshness, ["current", "stale", "unknown"]) && Array.isArray(r.reasonCodes) && r.reasonCodes.every(reason)
16279
+ )))
16280
+ return false;
16281
+ const coverage = v.assignmentCoverage;
16282
+ if (!record(coverage) || typeof coverage.complete !== "boolean" || typeof coverage.overflowCountIsLowerBound !== "boolean" || !(coverage.overflowCount === null || count(coverage.overflowCount)))
16283
+ return false;
16284
+ return Array.isArray(v.assignments) && v.assignments.length <= 50 && v.assignments.every(
16285
+ (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(
16286
+ (g) => record(g) && text(g.goalId) && member(g.status, [
16287
+ "eligible_for_attributed_goal_read",
16288
+ "excluded_from_attributed_goal_read",
16289
+ "unknown"
16290
+ ]) && reason(g.reasonCode)
16291
+ )
16292
+ );
16293
+ }
16294
+ 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) {
16295
+ const key = opts.orderId ?? opts.orderNumber;
16296
+ const from = instant(opts.from), to = instant(opts.to);
16297
+ 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) {
16298
+ fail(
16299
+ this,
16300
+ "Invalid order journey query: supply exactly one order identifier and a bounded ISO interval",
16301
+ EXIT_CODES.validation
16302
+ );
16303
+ }
16304
+ const result = await getOrderJourney(requireCredentials(), {
16305
+ ...opts,
16306
+ orderId: opts.orderId?.trim(),
16307
+ orderNumber: opts.orderNumber?.trim()
16308
+ });
16309
+ if (!result.ok || !result.data)
16310
+ fail(this, result.error ?? "Order journey unavailable", classifyStatus(result.status));
16311
+ const report = result.data.data;
16312
+ if (!validReport(report)) fail(this, "Malformed order journey response", EXIT_CODES.general);
16313
+ output(this, { data: report }, () => {
16314
+ const coverage = report.assignmentCoverage;
16315
+ printTable(
16316
+ ["Assignment coverage", "Overflow"],
16317
+ [
16318
+ [
16319
+ coverage.complete ? "complete" : "incomplete",
16320
+ coverage.overflowCount === null ? "unknown" : `${coverage.overflowCount} (${coverage.overflowCountIsLowerBound ? "lower bound" : "exact"})`
16321
+ ]
16322
+ ]
16323
+ );
16324
+ printTable(
16325
+ ["Journey", "Freshness", "Eligibility basis", "Final contribution"],
16326
+ [
16327
+ [
16328
+ report.evaluation.status,
16329
+ report.evaluation.freshness,
16330
+ "attributed_goal_counts",
16331
+ report.finalStatisticalContribution
16332
+ ]
16333
+ ]
16334
+ );
16335
+ printTable(
16336
+ ["Stage", "Status", "Freshness", "Reasons"],
16337
+ report.evaluation.stages.map((s) => [
16338
+ s.stage,
16339
+ s.status,
16340
+ s.freshness ?? "unknown",
16341
+ s.reasonCodes.join(", ")
16342
+ ])
16343
+ );
16344
+ if (report.receiptOutcomes?.length)
16345
+ printTable(
16346
+ ["Receipt", "Stage", "Experiment / variation / goal", "Freshness"],
16347
+ report.receiptOutcomes.map((r) => [
16348
+ r.receiptId,
16349
+ r.stage,
16350
+ [r.scope.experimentId, r.scope.variationId, r.scope.goalId].filter(Boolean).join(" / ") || "global",
16351
+ r.freshness
16352
+ ])
16353
+ );
16354
+ printTable(
16355
+ ["Experiment", "Variation", "Attribution", "Goal eligibility"],
16356
+ report.assignments.map((a) => [
16357
+ a.experimentId,
16358
+ a.variationId,
16359
+ a.storedAttribution === null ? "unknown" : a.storedAttribution ? "present" : "not observed",
16360
+ a.goals.length ? a.goals.map((g) => `${g.goalId}: ${g.status} (${g.reasonCode})`).join("; ") : `unknown (${a.reasonCode})`
16361
+ ])
16362
+ );
16363
+ });
16364
+ });
16365
+
15112
16366
  // src/lib/detect-markers.ts
15113
16367
  var DETECTION_MARKERS = [
15114
16368
  {
@@ -15335,11 +16589,11 @@ async function fetchPrimaryHtml(host) {
15335
16589
  headers: { "user-agent": "apex-cli-doctor/1.0" },
15336
16590
  signal: AbortSignal.timeout(PAGE_FETCH_TIMEOUT_MS)
15337
16591
  });
15338
- const text = await res.text();
15339
- if (CHALLENGE_STATUSES.includes(res.status) || /cf-chl|challenge-platform|captcha/i.test(text))
16592
+ const text2 = await res.text();
16593
+ if (CHALLENGE_STATUSES.includes(res.status) || /cf-chl|challenge-platform|captcha/i.test(text2))
15340
16594
  return { protected: true };
15341
16595
  if (!res.ok) return { error: `http ${res.status}` };
15342
- return { html: text };
16596
+ return { html: text2 };
15343
16597
  } catch (error) {
15344
16598
  return { error: error instanceof Error ? error.message : String(error) };
15345
16599
  }
@@ -15361,9 +16615,9 @@ function environmentByHost(envs) {
15361
16615
  }
15362
16616
  return map;
15363
16617
  }
15364
- function devPollutionCheck(eventHosts, envs) {
16618
+ function devPollutionCheck(eventHosts, envs, unparsedRows) {
15365
16619
  const dev = eventHosts.filter((host) => isDevHost(host));
15366
- if (!dev.length)
16620
+ if (!dev.length && !unparsedRows.length)
15367
16621
  return {
15368
16622
  checkId: "dev_pollution",
15369
16623
  status: "PASS",
@@ -15378,6 +16632,13 @@ function devPollutionCheck(eventHosts, envs) {
15378
16632
  return `${host} matches development host markers and is bound to the production environment`;
15379
16633
  return `${host} matches development host markers, sent production-labeled traffic, and is not bound to any environment`;
15380
16634
  });
16635
+ if (unparsedRows.length) {
16636
+ const pageviews = unparsedRows.reduce((sum, row) => sum + row.pageviews, 0);
16637
+ const firstPath = unparsedRows[0].path || "(empty)";
16638
+ evidence.push(
16639
+ `${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`
16640
+ );
16641
+ }
15381
16642
  const mappable = dev.filter(
15382
16643
  (host) => (bindings.get(host) ?? PRODUCTION_ENVIRONMENT_KEY) === PRODUCTION_ENVIRONMENT_KEY
15383
16644
  );
@@ -15393,7 +16654,7 @@ var PURCHASE_CADENCE_WINDOW_MINUTES = PURCHASE_WINDOW_MINUTES * 4;
15393
16654
  var MIN_CAPTURE_WINDOW_MINUTES = 60;
15394
16655
  var DUPLICATE_RATE_THRESHOLD = 0.02;
15395
16656
  var MAGNITUDE_TOLERANCE_RATE = 0.1;
15396
- function record(value) {
16657
+ function record2(value) {
15397
16658
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
15398
16659
  }
15399
16660
  function purchasePlane(config) {
@@ -15401,8 +16662,8 @@ function purchasePlane(config) {
15401
16662
  const names = [];
15402
16663
  const triggers = config.runtime?.dataLayerTriggers ?? config.dataLayerTriggers;
15403
16664
  if (Array.isArray(triggers) && triggers.some((trigger) => {
15404
- const row = record(trigger);
15405
- const trackRevenue = record(row?.trackRevenue);
16665
+ const row = record2(trigger);
16666
+ const trackRevenue = record2(row?.trackRevenue);
15406
16667
  return row?.enabled !== false && trackRevenue !== null && trackRevenue.enabled !== false;
15407
16668
  }))
15408
16669
  names.push("dataLayer trigger");
@@ -15444,7 +16705,7 @@ function lineItemSum(value) {
15444
16705
  if (!Array.isArray(value)) return null;
15445
16706
  let sum = 0;
15446
16707
  for (const item of value) {
15447
- const row = record(item);
16708
+ const row = record2(item);
15448
16709
  const price = numeric(row?.price);
15449
16710
  if (price === null) return null;
15450
16711
  sum += price * (numeric(row?.quantity) ?? 1);
@@ -15838,7 +17099,10 @@ async function runDoctor(options = {}) {
15838
17099
  const rows = inventory.rows;
15839
17100
  const inventoryCaveat = inventory.truncated ? [INVENTORY_TRUNCATED_CAVEAT] : [];
15840
17101
  const hosts = configuredHosts(domainsRes.data, envsRes.data);
15841
- const eventHosts = [...new Set(rows.map((row) => row.host.toLowerCase()))];
17102
+ const unparsedRows = rows.filter((row) => row.host.trim() === "");
17103
+ const eventHosts = [
17104
+ ...new Set(rows.filter((row) => row.host.trim() !== "").map((row) => row.host.toLowerCase()))
17105
+ ];
15842
17106
  const checks = [];
15843
17107
  const unbound = eventHosts.filter((host) => !hosts.has(host));
15844
17108
  checks.push(
@@ -15859,7 +17123,9 @@ async function runDoctor(options = {}) {
15859
17123
  ]
15860
17124
  }
15861
17125
  );
15862
- checks.push(withCaveat(devPollutionCheck(eventHosts, envsRes.data), inventoryCaveat));
17126
+ checks.push(
17127
+ withCaveat(devPollutionCheck(eventHosts, envsRes.data, unparsedRows), inventoryCaveat)
17128
+ );
15863
17129
  const primary = hostOnly(domainsRes.data.domain) ?? eventHosts[0];
15864
17130
  const shopId = resolveShopId(options) ?? domainsRes.data.shop_id;
15865
17131
  const workerBase = resolveWorkerBase(options);
@@ -16019,11 +17285,11 @@ function withCaveat(check, caveat) {
16019
17285
  }
16020
17286
  var DAY_MS = 864e5;
16021
17287
  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`);
17288
+ const text2 = value?.trim();
17289
+ if (!text2 || !/^\d{4}-\d{2}-\d{2}$/.test(text2)) return null;
17290
+ const parsed = Date.parse(`${text2}T00:00:00.000Z`);
16025
17291
  if (Number.isNaN(parsed)) return null;
16026
- return toDay(parsed) === text ? parsed : null;
17292
+ return toDay(parsed) === text2 ? parsed : null;
16027
17293
  }
16028
17294
  function toDay(value) {
16029
17295
  return new Date(value).toISOString().slice(0, 10);
@@ -16263,7 +17529,7 @@ function urlsCommand() {
16263
17529
  );
16264
17530
  });
16265
17531
  }
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(
17532
+ 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
17533
  "after",
16268
17534
  `
16269
17535
  Examples:
@@ -16275,7 +17541,7 @@ Examples:
16275
17541
  );
16276
17542
 
16277
17543
  // src/commands/install.ts
16278
- var import_promises3 = require("node:dns/promises");
17544
+ var import_promises4 = require("node:dns/promises");
16279
17545
  var import_node_http2 = require("node:http");
16280
17546
  var import_node_https = require("node:https");
16281
17547
  var import_node_net = require("node:net");
@@ -16284,6 +17550,23 @@ var DESKTOP_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/53
16284
17550
  var PRODUCTION_WORKER_BASE_URL2 = "https://events.drip-apex.com";
16285
17551
  var UrlPolicyError = class extends Error {
16286
17552
  };
17553
+ var IdentityProxyError = class extends Error {
17554
+ constructor(originalUrl, proxyHost) {
17555
+ super(
17556
+ `${originalUrl} redirects to ${proxyHost}, an identity-aware proxy sign-in; the install check cannot reach a storefront behind it`
17557
+ );
17558
+ this.originalUrl = originalUrl;
17559
+ this.proxyHost = proxyHost;
17560
+ }
17561
+ originalUrl;
17562
+ proxyHost;
17563
+ };
17564
+ var IDENTITY_PROXY_HOSTS = ["accounts.google.com", "login.microsoftonline.com"];
17565
+ var IDENTITY_PROXY_SUFFIXES = [".cloudflareaccess.com"];
17566
+ function identityProxyHost(host) {
17567
+ const normalized = normalizeHostname(host);
17568
+ return IDENTITY_PROXY_HOSTS.includes(normalized) || IDENTITY_PROXY_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
17569
+ }
16287
17570
  function normalizeHostname(host) {
16288
17571
  return host.toLowerCase().replace(/\.$/, "");
16289
17572
  }
@@ -16316,7 +17599,7 @@ var testResolver;
16316
17599
  var testFetch;
16317
17600
  async function resolveHost(hostname) {
16318
17601
  const mocked = testResolver?.(hostname) ?? null;
16319
- const addresses = mocked ?? await (0, import_promises3.lookup)(hostname, { all: true });
17602
+ const addresses = mocked ?? await (0, import_promises4.lookup)(hostname, { all: true });
16320
17603
  return addresses.map((entry) => entry.address);
16321
17604
  }
16322
17605
  async function validateUrl(raw, allowedHosts) {
@@ -16427,7 +17710,7 @@ function overLimitMessage(hostname, maxBytes) {
16427
17710
  return `Response from ${hostname} exceeded the ${maxBytes}-byte cap`;
16428
17711
  }
16429
17712
  function responseFromIncoming(res, body5, decoded) {
16430
- const status2 = res.statusCode ?? 0;
17713
+ const status3 = res.statusCode ?? 0;
16431
17714
  const headers = new Headers();
16432
17715
  for (const [key, value] of Object.entries(res.headers)) {
16433
17716
  if (Array.isArray(value)) for (const entry of value) headers.append(key, entry);
@@ -16437,9 +17720,9 @@ function responseFromIncoming(res, body5, decoded) {
16437
17720
  headers.delete("content-encoding");
16438
17721
  headers.delete("content-length");
16439
17722
  }
16440
- const nullBody = status2 === 101 || status2 === 204 || status2 === 205 || status2 === 304;
17723
+ const nullBody = status3 === 101 || status3 === 204 || status3 === 205 || status3 === 304;
16441
17724
  return new Response(nullBody ? null : body5, {
16442
- status: status2,
17725
+ status: status3,
16443
17726
  statusText: res.statusMessage ?? "",
16444
17727
  headers
16445
17728
  });
@@ -16574,7 +17857,10 @@ async function fetchSafe(raw, allowedHosts, limits) {
16574
17857
  if (![301, 302, 303, 307, 308].includes(result.response.status)) return result;
16575
17858
  const location = result.response.headers.get("location");
16576
17859
  if (!location) return result;
16577
- target = await validateUrl(new URL(location, target.url).toString(), allowedHosts);
17860
+ const next = new URL(location, target.url);
17861
+ if (identityProxyHost(next.hostname))
17862
+ throw new IdentityProxyError(raw, normalizeHostname(next.hostname));
17863
+ target = await validateUrl(next.toString(), allowedHosts);
16578
17864
  }
16579
17865
  throw new UrlPolicyError("Redirect limit exceeded (5)");
16580
17866
  }
@@ -16584,7 +17870,7 @@ function detectChallenge(html) {
16584
17870
  );
16585
17871
  }
16586
17872
  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) {
17873
+ function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTruncated) {
16588
17874
  const challenge = detectChallenge(html);
16589
17875
  const shopIdMatches = html.includes(shopId);
16590
17876
  const snippetPresent = workerSnippetPresent || LEGACY_SNIPPET_MARKER.test(html) && shopIdMatches;
@@ -16592,29 +17878,29 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
16592
17878
  return {
16593
17879
  url,
16594
17880
  status: "WARN",
16595
- httpStatus: status2,
17881
+ httpStatus: status3,
16596
17882
  snippetPresent,
16597
17883
  shopIdMatches,
16598
17884
  botChallenge: true,
16599
17885
  bodyTruncated,
16600
17886
  message: "Bot challenge detected; verify in a real browser."
16601
17887
  };
16602
- if (status2 < 200 || status2 >= 300)
17888
+ if (status3 < 200 || status3 >= 300)
16603
17889
  return {
16604
17890
  url,
16605
17891
  status: "FAIL",
16606
- httpStatus: status2,
17892
+ httpStatus: status3,
16607
17893
  snippetPresent,
16608
17894
  shopIdMatches,
16609
17895
  botChallenge: false,
16610
17896
  bodyTruncated,
16611
- message: `Storefront page returned HTTP ${status2}; the install cannot be verified from this response.`
17897
+ message: `Storefront page returned HTTP ${status3}; the install cannot be verified from this response.`
16612
17898
  };
16613
17899
  if (!snippetPresent)
16614
17900
  return {
16615
17901
  url,
16616
17902
  status: "FAIL",
16617
- httpStatus: status2,
17903
+ httpStatus: status3,
16618
17904
  snippetPresent,
16619
17905
  shopIdMatches,
16620
17906
  botChallenge: false,
@@ -16625,7 +17911,7 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
16625
17911
  return {
16626
17912
  url,
16627
17913
  status: "FAIL",
16628
- httpStatus: status2,
17914
+ httpStatus: status3,
16629
17915
  snippetPresent,
16630
17916
  shopIdMatches,
16631
17917
  botChallenge: false,
@@ -16635,7 +17921,7 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
16635
17921
  return {
16636
17922
  url,
16637
17923
  status: "PASS",
16638
- httpStatus: status2,
17924
+ httpStatus: status3,
16639
17925
  snippetPresent,
16640
17926
  shopIdMatches,
16641
17927
  botChallenge: false,
@@ -16832,6 +18118,18 @@ var installCommand = new Command("install").description("Check Apex storefront i
16832
18118
  pageReport(url, shopId, response.status, html, derivedWorkerBaseUrl !== null, truncated)
16833
18119
  );
16834
18120
  } catch (error) {
18121
+ if (error instanceof IdentityProxyError) {
18122
+ pages.push({
18123
+ url,
18124
+ status: "WARN",
18125
+ snippetPresent: false,
18126
+ shopIdMatches: false,
18127
+ botChallenge: false,
18128
+ blockedBy: "identity_proxy",
18129
+ message: `blocked_by_identity_proxy: ${error.message}. Verify from a signed-in browser.`
18130
+ });
18131
+ continue;
18132
+ }
16835
18133
  if (error instanceof UrlPolicyError) fail(this, error.message, EXIT_CODES.validation);
16836
18134
  if (error instanceof BodyDecodeError) {
16837
18135
  pages.push({
@@ -16939,18 +18237,18 @@ var installCommand = new Command("install").description("Check Apex storefront i
16939
18237
 
16940
18238
  // src/commands/flags.ts
16941
18239
  init_source();
16942
- var import_node_fs17 = __toESM(require("node:fs"));
16943
- var import_node_path15 = __toESM(require("node:path"));
18240
+ var import_node_fs19 = __toESM(require("node:fs"));
18241
+ var import_node_path16 = __toESM(require("node:path"));
16944
18242
 
16945
18243
  // src/edge-status.ts
16946
18244
  function getEdgeDeliveryStatus(deliveryType, expectedWorstCaseSeconds = 360) {
16947
18245
  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
18246
  }
16949
18247
  function parsePublicationMetadata(value) {
16950
- const record2 = value && typeof value === "object" ? value : {};
18248
+ const record3 = value && typeof value === "object" ? value : {};
16951
18249
  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
18250
+ publication_revision: Number.isSafeInteger(record3.publication_revision) && record3.publication_revision >= 0 ? record3.publication_revision : null,
18251
+ generated_at: typeof record3.generated_at === "string" && !Number.isNaN(Date.parse(record3.generated_at)) ? record3.generated_at : null
16954
18252
  };
16955
18253
  }
16956
18254
  function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSeconds, flagUpdatedAtValue, confirmedAt = (/* @__PURE__ */ new Date()).toISOString()) {
@@ -16967,13 +18265,13 @@ function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSecond
16967
18265
  if (edge.publication_revision < current.publication_revision) return { ...base, verdict: "propagating", relationship: "edge_behind" };
16968
18266
  return { ...base, verdict: "live", relationship: edge.publication_revision > current.publication_revision ? "edge_ahead" : "equal" };
16969
18267
  }
16970
- function formatEdgePropagationStatus(status2) {
18268
+ function formatEdgePropagationStatus(status3) {
16971
18269
  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}`;
18270
+ 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}`;
18271
+ if (status3.verdict === "unknown") return `Unknown \u2014 edge payload predates publication metadata. ${caveat}`;
18272
+ 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}`;
18273
+ const anomaly = status3.relationship === "edge_ahead" ? ` (edge is ahead of current revision ${status3.current.publication_revision}; anomalous)` : "";
18274
+ 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
18275
  }
16978
18276
 
16979
18277
  // src/commands/flags.ts
@@ -16982,9 +18280,9 @@ function body(command, value, opts) {
16982
18280
  let parsed = {};
16983
18281
  if (value) {
16984
18282
  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;
18283
+ 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
18284
  try {
16987
- parsed = JSON.parse(text);
18285
+ parsed = JSON.parse(text2);
16988
18286
  } catch (e) {
16989
18287
  fail(
16990
18288
  command,
@@ -17032,7 +18330,7 @@ var get5 = new Command("get").description("Get a flag").argument("<id>").action(
17032
18330
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
17033
18331
  output(this, r.data, () => detail(r.data));
17034
18332
  });
17035
- var status = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
18333
+ var status2 = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
17036
18334
  const r = await getFlagStatus(requireCredentials(), id);
17037
18335
  if (!r.ok || !r.data)
17038
18336
  fail(this, r.error ?? "Failed to get flag status", classifyStatus(r.status), r.errorDetails);
@@ -17103,7 +18401,7 @@ function lifecycle(action74) {
17103
18401
  output(this, r.data, () => detail(r.data));
17104
18402
  });
17105
18403
  }
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(
18404
+ 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
18405
  "after",
17108
18406
  `
17109
18407
  Examples:
@@ -17115,8 +18413,8 @@ Examples:
17115
18413
  );
17116
18414
 
17117
18415
  // src/commands/backlog.ts
17118
- var import_node_fs18 = __toESM(require("node:fs"));
17119
- var import_node_path16 = __toESM(require("node:path"));
18416
+ var import_node_fs20 = __toESM(require("node:fs"));
18417
+ var import_node_path17 = __toESM(require("node:path"));
17120
18418
  init_source();
17121
18419
  var kind2 = "backlog";
17122
18420
  var singular = "backlog item";
@@ -17124,8 +18422,8 @@ function parse(command, value) {
17124
18422
  if (!value) return {};
17125
18423
  try {
17126
18424
  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);
18425
+ 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;
18426
+ const body5 = JSON.parse(text2);
17129
18427
  if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
17130
18428
  throw new Error("body must be an object");
17131
18429
  return body5;
@@ -17401,8 +18699,8 @@ var fields2 = new Command("fields").description("Manage the shop's own idea fiel
17401
18699
  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
18700
 
17403
18701
  // src/commands/signals.ts
17404
- var import_node_fs19 = __toESM(require("node:fs"));
17405
- var import_node_path17 = __toESM(require("node:path"));
18702
+ var import_node_fs21 = __toESM(require("node:fs"));
18703
+ var import_node_path18 = __toESM(require("node:path"));
17406
18704
  init_source();
17407
18705
  var kind3 = "signals";
17408
18706
  var singular2 = "signal";
@@ -17410,8 +18708,8 @@ function parse2(command, value) {
17410
18708
  if (!value) return {};
17411
18709
  try {
17412
18710
  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);
18711
+ 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;
18712
+ const body5 = JSON.parse(text2);
17415
18713
  if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
17416
18714
  throw new Error("body must be an object");
17417
18715
  return body5;
@@ -17492,16 +18790,16 @@ var signalsCommand = new Command("signals").description("Manage signals").addCom
17492
18790
 
17493
18791
  // src/commands/personalizations.ts
17494
18792
  init_source();
17495
- var import_node_fs20 = __toESM(require("node:fs"));
17496
- var import_node_path18 = __toESM(require("node:path"));
18793
+ var import_node_fs22 = __toESM(require("node:fs"));
18794
+ var import_node_path19 = __toESM(require("node:path"));
17497
18795
  var kind4 = "personalizations";
17498
18796
  function body2(command, value, opts) {
17499
18797
  let parsed = {};
17500
18798
  if (value) {
17501
18799
  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;
18800
+ 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
18801
  try {
17504
- parsed = JSON.parse(text);
18802
+ parsed = JSON.parse(text2);
17505
18803
  } catch (e) {
17506
18804
  fail(
17507
18805
  command,
@@ -17581,14 +18879,14 @@ var personalizationsCommand = new Command("personalizations").description("Manag
17581
18879
 
17582
18880
  // src/commands/targeting-resources.ts
17583
18881
  init_source();
17584
- var import_node_fs21 = __toESM(require("node:fs"));
17585
- var import_node_path19 = __toESM(require("node:path"));
18882
+ var import_node_fs23 = __toESM(require("node:fs"));
18883
+ var import_node_path20 = __toESM(require("node:path"));
17586
18884
  function body3(command, value) {
17587
18885
  if (!value) return {};
17588
18886
  const source = value.startsWith("@") ? value.slice(1) : value;
17589
18887
  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);
18888
+ 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;
18889
+ return JSON.parse(text2);
17592
18890
  } catch (error) {
17593
18891
  fail(
17594
18892
  command,
@@ -17830,8 +19128,8 @@ var targetingCommand = new Command("targeting").description("Test experiment tar
17830
19128
 
17831
19129
  // src/commands/sos.ts
17832
19130
  init_source();
17833
- var import_node_fs22 = __toESM(require("node:fs"));
17834
- var import_node_path20 = __toESM(require("node:path"));
19131
+ var import_node_fs24 = __toESM(require("node:fs"));
19132
+ var import_node_path21 = __toESM(require("node:path"));
17835
19133
  var SOS_TYPES = /* @__PURE__ */ new Set([
17836
19134
  "full_handover",
17837
19135
  "design",
@@ -17844,7 +19142,7 @@ var SOS_TYPES = /* @__PURE__ */ new Set([
17844
19142
  function readText(value) {
17845
19143
  if (!value) return void 0;
17846
19144
  if (!value.startsWith("@")) return value;
17847
- return import_node_fs22.default.readFileSync(import_node_path20.default.resolve(process.cwd(), value.slice(1)), "utf8");
19145
+ return import_node_fs24.default.readFileSync(import_node_path21.default.resolve(process.cwd(), value.slice(1)), "utf8");
17848
19146
  }
17849
19147
  function printTicket(ticket, reused) {
17850
19148
  console.log();
@@ -17982,14 +19280,14 @@ var markRead = new Command("mark-read").option("--channel <key>", "Mark one chan
17982
19280
  var inboxCommand = new Command("inbox").description("Read and send inbox messages").addCommand(list11).addCommand(send2).addCommand(markRead);
17983
19281
 
17984
19282
  // src/commands/shops.ts
17985
- var import_node_fs23 = __toESM(require("node:fs"));
17986
- var import_node_path21 = __toESM(require("node:path"));
19283
+ var import_node_fs25 = __toESM(require("node:fs"));
19284
+ var import_node_path22 = __toESM(require("node:path"));
17987
19285
  function body4(command, raw) {
17988
19286
  if (!raw) return {};
17989
19287
  try {
17990
19288
  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);
19289
+ const text2 = raw.startsWith("@") || import_node_fs25.default.existsSync(source) ? import_node_fs25.default.readFileSync(import_node_path22.default.resolve(source), "utf8") : raw;
19290
+ const value = JSON.parse(text2);
17993
19291
  if (!value || typeof value !== "object" || Array.isArray(value))
17994
19292
  throw new Error("body must be an object");
17995
19293
  return value;
@@ -18370,7 +19668,7 @@ function parseContainer(source, id, dataLayerName = "dataLayer") {
18370
19668
  vtpPaths,
18371
19669
  ecommerceFrom: [...ecommerce][0] ?? null,
18372
19670
  serverContainerUrl: [...serverUrls][0] ?? null,
18373
- piiPaths: vtpPaths.filter((path24) => /email|phone|address|name/i.test(path24))
19671
+ piiPaths: vtpPaths.filter((path25) => /email|phone|address|name/i.test(path25))
18374
19672
  }
18375
19673
  };
18376
19674
  }
@@ -18439,11 +19737,11 @@ function isContainerField(field) {
18439
19737
  const types = baseFieldTypes(field);
18440
19738
  return types.has("obj") || types.has("arr");
18441
19739
  }
18442
- function hasSyntheticPlaceholder(path24) {
18443
- return path24.split(".").map((segment) => segment.replace(/\[\]$/, "")).some((segment) => /^<[^>]+>$/.test(segment));
19740
+ function hasSyntheticPlaceholder(path25) {
19741
+ return path25.split(".").map((segment) => segment.replace(/\[\]$/, "")).some((segment) => /^<[^>]+>$/.test(segment));
18444
19742
  }
18445
- function ambiguousDottedPrefix(event, path24) {
18446
- const segments = path24.split(".");
19743
+ function ambiguousDottedPrefix(event, path25) {
19744
+ const segments = path25.split(".");
18447
19745
  for (let index = 1; index < segments.length; index += 1) {
18448
19746
  const prefix = segments.slice(0, index).join(".");
18449
19747
  const parent = event.fields.find((field) => field.path === prefix);
@@ -18452,42 +19750,42 @@ function ambiguousDottedPrefix(event, path24) {
18452
19750
  return void 0;
18453
19751
  }
18454
19752
  function dimensionEligibility(event, field) {
18455
- const path24 = field.path;
18456
- if (hasSyntheticPlaceholder(path24)) {
19753
+ const path25 = field.path;
19754
+ if (hasSyntheticPlaceholder(path25)) {
18457
19755
  return {
18458
19756
  eligible: false,
18459
- message: `Path ${JSON.stringify(path24)} is a synthetic placeholder and cannot create a dimension. Choose an observed scalar path.`
19757
+ message: `Path ${JSON.stringify(path25)} is a synthetic placeholder and cannot create a dimension. Choose an observed scalar path.`
18460
19758
  };
18461
19759
  }
18462
19760
  if (isContainerField(field)) {
18463
19761
  return {
18464
19762
  eligible: false,
18465
- message: `Path ${JSON.stringify(path24)} is container-typed. Choose an observed scalar path.`
19763
+ message: `Path ${JSON.stringify(path25)} is container-typed. Choose an observed scalar path.`
18466
19764
  };
18467
19765
  }
18468
- if (path24.includes("[]")) {
19766
+ if (path25.includes("[]")) {
18469
19767
  return {
18470
19768
  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.`
19769
+ message: `Path ${JSON.stringify(path25)} includes an array-element segment and cannot create a reliable dimension. Choose a scalar path outside an array.`
18472
19770
  };
18473
19771
  }
18474
19772
  if (!isStableField(field)) {
18475
19773
  return {
18476
19774
  eligible: false,
18477
- message: `Path ${JSON.stringify(path24)} does not have a stable observed type. Choose a path without a type collision.`
19775
+ message: `Path ${JSON.stringify(path25)} does not have a stable observed type. Choose a path without a type collision.`
18478
19776
  };
18479
19777
  }
18480
19778
  if (!event.event) {
18481
19779
  return {
18482
19780
  eligible: false,
18483
- message: `Path ${JSON.stringify(path24)} has no observed event name. Choose a path from a named event.`
19781
+ message: `Path ${JSON.stringify(path25)} has no observed event name. Choose a path from a named event.`
18484
19782
  };
18485
19783
  }
18486
- const ambiguousPrefix = ambiguousDottedPrefix(event, path24);
19784
+ const ambiguousPrefix = ambiguousDottedPrefix(event, path25);
18487
19785
  if (ambiguousPrefix) {
18488
19786
  return {
18489
19787
  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.`
19788
+ 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
19789
  };
18492
19790
  }
18493
19791
  return { eligible: true };
@@ -18496,24 +19794,24 @@ function isNumericField(field) {
18496
19794
  const numericTypes = /* @__PURE__ */ new Set(["num", "numstr", "numstr_comma"]);
18497
19795
  return field.types.length > 0 && field.types.every((type) => numericTypes.has(type.split(":")[0]));
18498
19796
  }
18499
- function defaultDimensionLabel(path24) {
18500
- const words = path24.split(/[._-]+/).filter(Boolean).join(" ");
18501
- return words ? `${words[0].toUpperCase()}${words.slice(1)}` : path24;
19797
+ function defaultDimensionLabel(path25) {
19798
+ const words = path25.split(/[._-]+/).filter(Boolean).join(" ");
19799
+ return words ? `${words[0].toUpperCase()}${words.slice(1)}` : path25;
18502
19800
  }
18503
19801
  function quoteShellArgument(value) {
18504
19802
  return "'" + value.replaceAll("'", "'\\''") + "'";
18505
19803
  }
18506
- function createDimensionInvocation(path24, eventName) {
18507
- return `apex discover --create-dimension ${quoteShellArgument(path24)} --event ${quoteShellArgument(eventName)}`;
19804
+ function createDimensionInvocation(path25, eventName) {
19805
+ return `apex discover --create-dimension ${quoteShellArgument(path25)} --event ${quoteShellArgument(eventName)}`;
18508
19806
  }
18509
- function resolveDimensionObservation(command, report, path24, eventName) {
19807
+ function resolveDimensionObservation(command, report, path25, eventName) {
18510
19808
  const matches = report.data_layer.flatMap(
18511
- (event) => event.fields.filter((field) => field.path === path24).map((field) => ({ event, field }))
19809
+ (event) => event.fields.filter((field) => field.path === path25).map((field) => ({ event, field }))
18512
19810
  );
18513
19811
  if (!matches.length) {
18514
19812
  fail(
18515
19813
  command,
18516
- `Path ${JSON.stringify(path24)} was not found in the current discovery report. Run apex discover and copy an observed path exactly.`,
19814
+ `Path ${JSON.stringify(path25)} was not found in the current discovery report. Run apex discover and copy an observed path exactly.`,
18517
19815
  EXIT_CODES.validation
18518
19816
  );
18519
19817
  }
@@ -18523,7 +19821,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
18523
19821
  if (!eventMatches.length) {
18524
19822
  fail(
18525
19823
  command,
18526
- `Path ${JSON.stringify(path24)} was not observed on event ${JSON.stringify(eventName)}. Run apex discover and copy the event name exactly.`,
19824
+ `Path ${JSON.stringify(path25)} was not observed on event ${JSON.stringify(eventName)}. Run apex discover and copy the event name exactly.`,
18527
19825
  EXIT_CODES.validation
18528
19826
  );
18529
19827
  }
@@ -18531,7 +19829,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
18531
19829
  if (layers.length > 1) {
18532
19830
  fail(
18533
19831
  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.`,
19832
+ `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
19833
  EXIT_CODES.validation
18536
19834
  );
18537
19835
  }
@@ -18543,7 +19841,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
18543
19841
  if (namedEvents.length > 1) {
18544
19842
  fail(
18545
19843
  command,
18546
- `Path ${JSON.stringify(path24)} was observed on multiple events: ${namedEvents.join(", ")}. Retry with --event <name>.`,
19844
+ `Path ${JSON.stringify(path25)} was observed on multiple events: ${namedEvents.join(", ")}. Retry with --event <name>.`,
18547
19845
  EXIT_CODES.validation
18548
19846
  );
18549
19847
  }
@@ -18552,7 +19850,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
18552
19850
  if (layers.length > 1) {
18553
19851
  fail(
18554
19852
  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.`,
19853
+ `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
19854
  EXIT_CODES.validation
18557
19855
  );
18558
19856
  }
@@ -18587,9 +19885,9 @@ function renderDiscovery(report) {
18587
19885
  `
18588
19886
  ${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
19887
  );
18590
- for (const path24 of event.paths)
19888
+ for (const path25 of event.paths)
18591
19889
  lines.push(
18592
- ` URL ${path24.path}: ${path24.pageviews} pageviews, ${path24.count} pushes, ${path24.pspReturn_count} PSP returns`
19890
+ ` URL ${path25.path}: ${path25.pageviews} pageviews, ${path25.count} pushes, ${path25.pspReturn_count} PSP returns`
18593
19891
  );
18594
19892
  for (const field of event.fields) {
18595
19893
  lines.push(
@@ -18767,11 +20065,11 @@ var discoverCommand = new Command("discover").description("Read back the trackin
18767
20065
  );
18768
20066
  }
18769
20067
  if (!created.ok || !created.data) {
18770
- const reason = created.error ?? "The signals API rejected the request";
20068
+ const reason2 = created.error ?? "The signals API rejected the request";
18771
20069
  if (created.status === 409) {
18772
20070
  fail(
18773
20071
  this,
18774
- `Dimension was not created: ${reason} Choose another --key or archive an existing dimension in Audience > Attributes, then retry.`,
20072
+ `Dimension was not created: ${reason2} Choose another --key or archive an existing dimension in Audience > Attributes, then retry.`,
18775
20073
  classifyStatus(created.status),
18776
20074
  created.errorDetails
18777
20075
  );
@@ -18779,14 +20077,14 @@ var discoverCommand = new Command("discover").description("Read back the trackin
18779
20077
  if (created.status === 400) {
18780
20078
  fail(
18781
20079
  this,
18782
- `Dimension was not created: ${reason} Check --create-dimension, --event, --key, and --label, then retry.`,
20080
+ `Dimension was not created: ${reason2} Check --create-dimension, --event, --key, and --label, then retry.`,
18783
20081
  classifyStatus(created.status),
18784
20082
  created.errorDetails
18785
20083
  );
18786
20084
  }
18787
20085
  fail(
18788
20086
  this,
18789
- `Dimension was not created: ${reason}`,
20087
+ `Dimension was not created: ${reason2}`,
18790
20088
  classifyStatus(created.status),
18791
20089
  created.errorDetails
18792
20090
  );
@@ -18802,16 +20100,16 @@ var discoverCommand = new Command("discover").description("Read back the trackin
18802
20100
  });
18803
20101
 
18804
20102
  // src/commands/skills.ts
18805
- var import_node_fs24 = __toESM(require("node:fs"));
18806
- var import_node_path22 = __toESM(require("node:path"));
20103
+ var import_node_fs26 = __toESM(require("node:fs"));
20104
+ var import_node_path23 = __toESM(require("node:path"));
18807
20105
  function packageRoot() {
18808
20106
  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")
20107
+ import_node_path23.default.resolve(__dirname, ".."),
20108
+ import_node_path23.default.resolve(import_node_path23.default.dirname(process.argv[1] ?? process.cwd()), ".."),
20109
+ import_node_path23.default.resolve(process.cwd(), "packages/cli")
18812
20110
  ];
18813
20111
  for (const candidate of candidates) {
18814
- if (import_node_fs24.default.existsSync(import_node_path22.default.join(candidate, "AGENTS.md"))) return candidate;
20112
+ if (import_node_fs26.default.existsSync(import_node_path23.default.join(candidate, "AGENTS.md"))) return candidate;
18815
20113
  }
18816
20114
  throw new Error(
18817
20115
  `Apex CLI package files not found. Looked for AGENTS.md in: ${candidates.join(", ")}`
@@ -18820,19 +20118,19 @@ function packageRoot() {
18820
20118
  function skillPaths() {
18821
20119
  const root = packageRoot();
18822
20120
  return {
18823
- agents: import_node_path22.default.join(root, "AGENTS.md"),
18824
- skill: import_node_path22.default.join(root, "skills", "apex-cli", "SKILL.md")
20121
+ agents: import_node_path23.default.join(root, "AGENTS.md"),
20122
+ skill: import_node_path23.default.join(root, "skills", "apex-cli", "SKILL.md")
18825
20123
  };
18826
20124
  }
18827
20125
  function installSkill(paths, targetRoot) {
18828
- const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs24.default.existsSync(filePath));
20126
+ const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs26.default.existsSync(filePath));
18829
20127
  if (missing.length > 0) {
18830
20128
  throw new Error(`Apex CLI package files not found. Missing: ${missing.join(", ")}`);
18831
20129
  }
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"));
20130
+ const target = import_node_path23.default.resolve(targetRoot, "apex-cli");
20131
+ import_node_fs26.default.mkdirSync(target, { recursive: true });
20132
+ import_node_fs26.default.copyFileSync(paths.skill, import_node_path23.default.join(target, "SKILL.md"));
20133
+ import_node_fs26.default.copyFileSync(paths.agents, import_node_path23.default.join(target, "AGENTS.md"));
18836
20134
  return target;
18837
20135
  }
18838
20136
  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 +20560,12 @@ async function fetchText(url) {
19262
20560
  });
19263
20561
  return { status: res.status, text: await readResponseText(res), finalUrl: res.url };
19264
20562
  }
19265
- function abs(base, path24) {
19266
- return new URL(path24, base).toString();
20563
+ function abs(base, path25) {
20564
+ return new URL(path25, base).toString();
19267
20565
  }
19268
- function looksLikeShopifyCart(text) {
20566
+ function looksLikeShopifyCart(text2) {
19269
20567
  try {
19270
- const payload = JSON.parse(text);
20568
+ const payload = JSON.parse(text2);
19271
20569
  return payload !== null && typeof payload === "object" && (typeof payload.token === "string" || payload.token === null) && Array.isArray(payload.items);
19272
20570
  } catch {
19273
20571
  return false;
@@ -19283,9 +20581,9 @@ async function collectSignals(url) {
19283
20581
  const chunks = [`__apex_primary_status_${primary.status}__`, primary.text];
19284
20582
  const protectedHit = primary.status === 403 || primary.status === 429 || matchDetectionMarkers(primary.text, "protected").length > 0;
19285
20583
  if (!protectedHit) {
19286
- for (const path24 of ["/cart.js"]) {
20584
+ for (const path25 of ["/cart.js"]) {
19287
20585
  try {
19288
- const secondary = await fetchText(abs(primary.finalUrl || url, path24));
20586
+ const secondary = await fetchText(abs(primary.finalUrl || url, path25));
19289
20587
  if (secondary.status >= 200 && secondary.status < 300 && looksLikeShopifyCart(secondary.text)) {
19290
20588
  chunks.push("__apex_secondary_shopify_cart_js__", secondary.text.slice(0, 2e3));
19291
20589
  }
@@ -19580,20 +20878,20 @@ var webhooksCommand = new Command("webhooks").description("Manage shop webhooks"
19580
20878
 
19581
20879
  // src/commands/simulate.ts
19582
20880
  init_source();
19583
- var import_node_fs25 = __toESM(require("node:fs"));
19584
- var import_node_path23 = __toESM(require("node:path"));
20881
+ var import_node_fs27 = __toESM(require("node:fs"));
20882
+ var import_node_path24 = __toESM(require("node:path"));
19585
20883
  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");
20884
+ const text2 = file === "-" ? import_node_fs27.default.readFileSync(0, "utf8") : import_node_fs27.default.readFileSync(import_node_path24.default.resolve(file), "utf8");
19587
20885
  try {
19588
- return JSON.parse(text);
20886
+ return JSON.parse(text2);
19589
20887
  } catch (error) {
19590
20888
  throw new Error(
19591
20889
  `Invalid JSON in ${file}: ${error instanceof Error ? error.message : String(error)}`
19592
20890
  );
19593
20891
  }
19594
20892
  }
19595
- function plural(count, singular3) {
19596
- return `${count} ${singular3}${count === 1 ? "" : "s"}`;
20893
+ function plural(count2, singular3) {
20894
+ return `${count2} ${singular3}${count2 === 1 ? "" : "s"}`;
19597
20895
  }
19598
20896
  function isGtagEventTuple(value) {
19599
20897
  return Array.isArray(value) && value[0] === "event" && typeof value[1] === "string";
@@ -19705,8 +21003,8 @@ var simulateCommand = new Command("simulate").description("Simulate data-layer t
19705
21003
  });
19706
21004
 
19707
21005
  // src/commands/config-resources.ts
19708
- var import_node_fs26 = __toESM(require("node:fs"));
19709
- var import_node_path24 = __toESM(require("node:path"));
21006
+ var import_node_fs28 = __toESM(require("node:fs"));
21007
+ var import_node_path25 = __toESM(require("node:path"));
19710
21008
  function show(resource) {
19711
21009
  process.stdout.write(`${JSON.stringify(resource, null, 2)}
19712
21010
  `);
@@ -19772,12 +21070,12 @@ function readFamily(family, label) {
19772
21070
  }
19773
21071
  var assetReads = readFamily("assets", "assets");
19774
21072
  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);
21073
+ const absolute = import_node_path25.default.resolve(process.cwd(), file);
21074
+ if (!import_node_fs28.default.existsSync(absolute)) fail(this, `File not found: ${file}`, 3);
21075
+ const fileStat = import_node_fs28.default.statSync(absolute);
19778
21076
  if (!fileStat.isFile()) fail(this, `File not found: ${file}`, 3);
19779
21077
  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()];
21078
+ const mimeType = ASSET_MIME_TYPES[import_node_path25.default.extname(absolute).toLowerCase()];
19781
21079
  if (!mimeType)
19782
21080
  fail(
19783
21081
  this,
@@ -19786,8 +21084,8 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
19786
21084
  );
19787
21085
  const r = await uploadAssets(requireCredentials(), [
19788
21086
  {
19789
- name: import_node_path24.default.basename(absolute),
19790
- bytes: import_node_fs26.default.readFileSync(absolute),
21087
+ name: import_node_path25.default.basename(absolute),
21088
+ bytes: import_node_fs28.default.readFileSync(absolute),
19791
21089
  type: mimeType
19792
21090
  }
19793
21091
  ]);
@@ -19872,6 +21170,7 @@ program2.addCommand(roadmapCommand);
19872
21170
  program2.addCommand(harnessCommand);
19873
21171
  program2.addCommand(operatorCommand);
19874
21172
  program2.addCommand(trackingCommand);
21173
+ program2.addCommand(eventsCommand);
19875
21174
  program2.addCommand(goalsCommand);
19876
21175
  program2.addCommand(funnelsCommand);
19877
21176
  program2.addCommand(environmentsCommand);
@@ -19913,10 +21212,18 @@ try {
19913
21212
  } catch {
19914
21213
  }
19915
21214
  program2.hook("preAction", (_thisCommand, actionCommand) => {
19916
- configureInvocationContext();
21215
+ try {
21216
+ configureInvocationContext();
21217
+ } catch (error) {
21218
+ beginInvocation(actionCommand);
21219
+ throw error;
21220
+ }
19917
21221
  configureRuntime(actionCommand);
21222
+ beginInvocation(actionCommand, resolveContext());
19918
21223
  });
19919
21224
  process.on("beforeExit", () => {
19920
21225
  flushFallbackEnvelope();
19921
21226
  });
19922
- program2.parseAsync(process.argv).then(() => maybeCheckForUpdate()).catch((err) => handleCliError(err, program2));
21227
+ maybeCheckForUpdate().then(() => program2.parseAsync(process.argv)).then(async () => {
21228
+ await finishInvocation({ exitCode: process.exitCode ?? 0 });
21229
+ }).catch((err) => handleCliError(err, program2));