@drip-apex/apex-cli 0.12.0 → 0.13.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/AGENTS.md CHANGED
@@ -18,7 +18,10 @@ Use `apex` as the canonical command name. `drip` is a compatibility alias.
18
18
  - Launch is dashboard-only. The API and CLI do not start traffic.
19
19
  - Prefer `--json`; piped stdout defaults to JSON automatically.
20
20
  - JSON output is a stable contract: top level is `{ ok, version, command, data | error }`.
21
- - Diagnostics, prompts, progress, and update notices go to stderr.
21
+ - With `--json`, stdout is exactly one JSON document. Diagnostics, prompts, progress,
22
+ and update notices go to stderr.
23
+ - For single-request commands `data` is the API response body as returned (lists at
24
+ `.data.data[]`). API failures carry the API error body in `error.api`.
22
25
  - Do not parse human tables. Use `apex schema --json` first if you need flags.
23
26
  - Keep command names exact. Do not infer or guess names from the CLI's
24
27
  "did you mean" hints — those are stderr diagnostics for humans; machine
package/README.md CHANGED
@@ -100,6 +100,42 @@ Summary timestamps include `created_at` and `archived_at`; experiments have no `
100
100
  `--include-archived` to include them; archived rows display their archive date
101
101
  when the API provides `archived_at`.
102
102
 
103
+ ## JSON output contract
104
+
105
+ With `--json`, or whenever stdout is piped, stdout carries exactly one JSON
106
+ document and nothing else. Spinners, progress, prompts, warnings, human text and
107
+ child-process output go to stderr, so `apex ... --json | jq` always parses.
108
+
109
+ ```json
110
+ { "ok": true, "version": "0.13.0", "command": "apex flags get", "data": {}, "context": {} }
111
+ ```
112
+
113
+ - `data` is the API response body exactly as the API returned it for every command
114
+ backed by one API request. Lists keep the API's `{ "data": [...], "pagination": {...} }`
115
+ shape, so rows are at `.data.data[]`; single resources are at `.data` directly.
116
+ - `apex experiments get` adds one CLI-derived key, `dashboard_url`, beside the
117
+ experiment fields.
118
+ - Commands that combine several requests or local files (`experiments create`,
119
+ `pull`, `plan`, `apply`, `qa approve`, `triggers`, `project-code`, `harness`,
120
+ `workspace`, `doctor`, `whoami`, `push`, `analytics urls`, `flags status`,
121
+ `goals test`) print a documented CLI-composed `data` object.
122
+ - Failures print `{ "ok": false, "version", "command", "error", "context" }`.
123
+ `error.code` is the CLI exit code, `error.details` carries retry hints and
124
+ server details, and `error.api` is `{ "status", "body" }` with the API error
125
+ envelope verbatim whenever an API response caused the failure.
126
+ - `deprecated` appears on the envelope only when a deprecated flag was used.
127
+
128
+ **Breaking in 0.13.0.** Earlier releases re-nested or renamed API bodies. Update
129
+ scripts: `.data.experiments[]` becomes `.data.data[]`; `experiments get`
130
+ `.data.experiment.id` becomes `.data.id`; `.data.flag`, `.data.goal`,
131
+ `.data.funnel`, `.data.signal`, `.data.backlog_item`, `.data.personalization`,
132
+ `.data.exclusion_group`, `.data.asset`, `.data.landing_page`, `.data.webhook`,
133
+ `.data.targeting` and `.data.verification` wrappers are gone (read the fields at
134
+ `.data`); `experiments results` reads at `.data` instead of `.data.results`,
135
+ `funnels run` at `.data` instead of `.data.run`, and `brand` at `.data.data`
136
+ instead of `.data.brandContext`; lifecycle and delete commands return the API's
137
+ body instead of a CLI summary.
138
+
103
139
  ## Command index
104
140
 
105
141
  Use `apex schema --json` to emit the complete machine-readable command surface,
@@ -352,5 +388,29 @@ config-fetch p50/p95 (milliseconds), and inline/cache/network/none counts.
352
388
  JSON output includes the API body in the standard envelope and assignment-ready
353
389
  percentiles. Fewer than five measured samples have null percentiles. Natural
354
390
  loads are shop-wide non-QA loads without forced IDs; a QA-session bucket can
355
- overlap forced loads. Analytics failures return errors. Preload and anti-flicker
356
- changes remain under evaluation pending the first report.
391
+ overlap forced loads. Analytics failures return errors. The API position is
392
+ `runtime_inline_no_change`.
393
+
394
+ The runtime remains unchanged. Natural runtime decides within tens of milliseconds
395
+ of SDK start when config ships inline with the snippet (measured p50 12 ms /
396
+ p95 54 ms on a large storefront). QA previews, screenshot-mode previews and
397
+ cookie-forced sessions take the QA config lane and fetch fresh config over the
398
+ network (measured p50 1.3 s / p95 7 s); they are not representative of launch
399
+ behaviour. URL-forced sessions without a `qa` parameter keep the inline or cached
400
+ config, so these measurements do not describe them. No config preload or
401
+ anti-flicker hold is planned.
402
+
403
+ ### Connection troubleshooting
404
+
405
+ Connection errors retain the underlying failure. JSON output exposes
406
+ `error.details.reason` and, when available, `error.details.code`; the numeric
407
+ `error.code` remains the CLI exit code.
408
+
409
+ | Reason | Meaning and usual fix |
410
+ | --- | --- |
411
+ | `dns` | The host could not be resolved. Check `APEX_BASE_URL` (or `DRIP_BASE_URL`), DNS, and whether your sandbox permits network access. |
412
+ | `refused` | The host rejected the connection. Check the base URL, port, and whether the server is running. |
413
+ | `reset` | The connection was interrupted. Check network stability and proxy settings before retrying. |
414
+ | `tls` | Certificate or TLS validation failed. Check expired certificates and your corporate TLS proxy's trusted CA configuration. |
415
+ | `timeout` | The request was aborted or exceeded its deadline. Check server availability and network access. |
416
+ | `unknown` | Another network failure occurred. Use the included cause message to diagnose network or sandbox restrictions. |
package/dist/cli.js CHANGED
@@ -7112,7 +7112,7 @@ var import_node_os3 = __toESM(require("node:os"));
7112
7112
  // package.json
7113
7113
  var package_default = {
7114
7114
  name: "@drip-apex/apex-cli",
7115
- version: "0.12.0",
7115
+ version: "0.13.0",
7116
7116
  license: "MIT",
7117
7117
  description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
7118
7118
  private: false,
@@ -7172,16 +7172,42 @@ var EXIT_CODES = {
7172
7172
  confirmationRequired: 4,
7173
7173
  conflict: 5
7174
7174
  };
7175
+ var MAX_RECORDED_API_FAILURES = 32;
7176
+ var recentApiFailures = [];
7177
+ function recordApiResponse(response) {
7178
+ if (response.ok) return;
7179
+ recentApiFailures.push({
7180
+ status: response.status,
7181
+ body: response.data ?? null,
7182
+ message: response.error,
7183
+ details: response.errorDetails
7184
+ });
7185
+ if (recentApiFailures.length > MAX_RECORDED_API_FAILURES) recentApiFailures.shift();
7186
+ }
7187
+ function matchApiFailure(message, details) {
7188
+ const byDetails = details !== void 0 ? recentApiFailures.find((entry) => entry.details === details) : void 0;
7189
+ if (byDetails) return { status: byDetails.status, body: byDetails.body };
7190
+ const byMessage = recentApiFailures.filter(
7191
+ (entry) => Boolean(entry.message) && message.includes(entry.message)
7192
+ );
7193
+ if (byMessage.length !== 1) return void 0;
7194
+ return { status: byMessage[0].status, body: byMessage[0].body };
7195
+ }
7175
7196
  var CliError = class extends Error {
7176
7197
  exitCode;
7177
7198
  details;
7178
- constructor(message, exitCode = EXIT_CODES.general, details) {
7199
+ api;
7200
+ constructor(message, exitCode = EXIT_CODES.general, details, api) {
7179
7201
  super(message);
7180
7202
  this.name = "CliError";
7181
7203
  this.exitCode = exitCode;
7182
7204
  this.details = details;
7205
+ this.api = api ?? matchApiFailure(message, details);
7183
7206
  }
7184
7207
  };
7208
+ function apiFailureOf(response) {
7209
+ return response.ok ? void 0 : { status: response.status, body: response.data ?? null };
7210
+ }
7185
7211
  var initialEnvironmentApiKey = process.env.APEX_API_KEY || process.env.DRIP_API_KEY;
7186
7212
  var envelopeContext = {
7187
7213
  org_id: null,
@@ -7196,6 +7222,7 @@ var commandName = "apex";
7196
7222
  var wroteEnvelope = false;
7197
7223
  var stdoutLines = [];
7198
7224
  var originalConsoleLog = null;
7225
+ var rawStdoutWrite = null;
7199
7226
  var deprecationMarker = null;
7200
7227
  var interceptedCommands = /* @__PURE__ */ new WeakMap();
7201
7228
  function interceptCliError(err, command) {
@@ -7222,18 +7249,52 @@ function formatConsoleArgs(args) {
7222
7249
  return JSON.stringify(arg);
7223
7250
  }).join(" ");
7224
7251
  }
7252
+ function installJsonStdoutGuard() {
7253
+ if (rawStdoutWrite) return;
7254
+ const stdout = process.stdout;
7255
+ const original = stdout.write.bind(stdout);
7256
+ rawStdoutWrite = (text) => original(text);
7257
+ stdout.write = ((chunk, ...args) => process.stderr.write(
7258
+ chunk,
7259
+ ...args
7260
+ ));
7261
+ }
7262
+ function isJsonMode() {
7263
+ return jsonMode;
7264
+ }
7225
7265
  function configureRuntime(command) {
7226
7266
  commandName = commandPath(command) || commandName;
7227
7267
  jsonMode = shouldUseJson(command);
7228
- if (!jsonMode || originalConsoleLog) return;
7268
+ if (!jsonMode) return;
7269
+ installJsonStdoutGuard();
7270
+ if (originalConsoleLog) return;
7229
7271
  originalConsoleLog = console.log;
7230
7272
  console.log = (...args) => {
7231
7273
  stdoutLines.push(formatConsoleArgs(args));
7232
7274
  };
7233
7275
  }
7276
+ function writeJsonDocument(envelope) {
7277
+ const text = `${JSON.stringify(envelope, null, 2)}
7278
+ `;
7279
+ if (wroteEnvelope) {
7280
+ process.stderr.write(text);
7281
+ return;
7282
+ }
7283
+ wroteEnvelope = true;
7284
+ const pendingHumanLines = stdoutLines;
7285
+ stdoutLines = [];
7286
+ restoreConsoleLog();
7287
+ if (pendingHumanLines.length > 0) process.stderr.write(`${pendingHumanLines.join("\n")}
7288
+ `);
7289
+ if (rawStdoutWrite) rawStdoutWrite(text);
7290
+ else process.stdout.write(text);
7291
+ }
7234
7292
  function flushFallbackEnvelope() {
7235
- if (!jsonMode || wroteEnvelope || stdoutLines.length === 0) return;
7236
- writeEnvelope({ output: stdoutLines.join("\n") });
7293
+ if (!jsonMode || wroteEnvelope) return;
7294
+ if (process.exitCode !== void 0 && process.exitCode !== 0) return;
7295
+ const output2 = stdoutLines.join("\n");
7296
+ stdoutLines = [];
7297
+ writeEnvelope(output2 ? { output: output2 } : null);
7237
7298
  }
7238
7299
  function restoreConsoleLog() {
7239
7300
  if (!originalConsoleLog) return;
@@ -7252,10 +7313,7 @@ function writeEnvelope(data, command = commandName) {
7252
7313
  context: envelopeContext,
7253
7314
  ...deprecationMarker ? { deprecated: deprecationMarker } : {}
7254
7315
  };
7255
- wroteEnvelope = true;
7256
- restoreConsoleLog();
7257
- process.stdout.write(`${JSON.stringify(envelope, null, 2)}
7258
- `);
7316
+ writeJsonDocument(envelope);
7259
7317
  }
7260
7318
  function writeErrorEnvelope(error, command = commandName) {
7261
7319
  const envelope = {
@@ -7265,14 +7323,12 @@ function writeErrorEnvelope(error, command = commandName) {
7265
7323
  error: {
7266
7324
  message: error.message,
7267
7325
  code: error.exitCode,
7268
- ...error.details ? { details: error.details } : {}
7326
+ ...error.details ? { details: error.details } : {},
7327
+ ...error.api ? { api: error.api } : {}
7269
7328
  },
7270
7329
  context: envelopeContext
7271
7330
  };
7272
- wroteEnvelope = true;
7273
- restoreConsoleLog();
7274
- process.stdout.write(`${JSON.stringify(envelope, null, 2)}
7275
- `);
7331
+ writeJsonDocument(envelope);
7276
7332
  }
7277
7333
  function output(command, data, human) {
7278
7334
  if (shouldUseJson(command)) {
@@ -7291,8 +7347,8 @@ function commandPath(command) {
7291
7347
  }
7292
7348
  return names.join(" ");
7293
7349
  }
7294
- function fail(command, message, exitCode = EXIT_CODES.general, details) {
7295
- throw new CliError(message, exitCode, details);
7350
+ function fail(command, message, exitCode = EXIT_CODES.general, details, api) {
7351
+ throw new CliError(message, exitCode, details, api);
7296
7352
  }
7297
7353
  function classifyStatus(status2) {
7298
7354
  if (status2 === 401 || status2 === 403) return EXIT_CODES.auth;
@@ -7666,11 +7722,20 @@ function createHttpClient(creds, options) {
7666
7722
  try {
7667
7723
  response = await fetch(`${creds.baseUrl}${path24}`, { method, headers, body: form ? body5 : body5 ? JSON.stringify(body5) : void 0, signal: AbortSignal.timeout(timeoutMs) });
7668
7724
  } catch (error) {
7669
- return options.onNetworkError({ baseUrl: creds.baseUrl, timeoutMs, cause: error instanceof Error ? error.message : String(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
+ });
7670
7735
  }
7671
7736
  inspectVersion(response.headers);
7672
7737
  const json = await response.json().catch(() => null);
7673
- if (!response.ok) return { ok: false, status: response.status, data: form ? void 0 : 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") } };
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") } };
7674
7739
  return { ok: true, status: response.status, data: json };
7675
7740
  }
7676
7741
  return { request: (method, path24, body5, timeoutMs) => request2(method, path24, body5, timeoutMs), requestForm: (method, path24, body5) => request2(method, path24, body5, void 0, true) };
@@ -7739,10 +7804,42 @@ function sharedClient(creds) {
7739
7804
  transport: testTransport,
7740
7805
  testTransportVersion: () => testTransport ? process.env[["APEX", "CLI", "MOCK", "API", "VERSION"].join("_")] : void 0,
7741
7806
  onNetworkError: (info) => {
7807
+ let url;
7808
+ try {
7809
+ url = new URL(info.baseUrl);
7810
+ } catch {
7811
+ }
7812
+ const host = url?.hostname ?? info.baseUrl;
7813
+ const code = info.code;
7814
+ let reason = "unknown";
7815
+ let message = info.causeMessage ?? info.cause;
7816
+ if (info.name === "TimeoutError" || info.name === "AbortError" || code === "UND_ERR_CONNECT_TIMEOUT") {
7817
+ reason = "timeout";
7818
+ message = `server did not respond within ${info.timeoutMs / 1e3}s`;
7819
+ } else if (code === "ENOTFOUND" || code === "EAI_AGAIN") {
7820
+ reason = "dns";
7821
+ message = `could not resolve host ${host}`;
7822
+ } else if (code === "ECONNREFUSED") {
7823
+ reason = "refused";
7824
+ message = `connection refused by ${host}:${url?.port || (url?.protocol === "https:" ? "443" : "80")}`;
7825
+ } else if (code === "ECONNRESET" || code === "EPIPE") {
7826
+ reason = "reset";
7827
+ message = `connection reset by ${host}`;
7828
+ } else if (code?.startsWith("CERT_") || code?.startsWith("ERR_TLS_") || [
7829
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
7830
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
7831
+ "SELF_SIGNED_CERT_IN_CHAIN",
7832
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
7833
+ "UNABLE_TO_GET_ISSUER_CERT",
7834
+ "HOSTNAME_MISMATCH"
7835
+ ].includes(code ?? "")) {
7836
+ reason = "tls";
7837
+ message = `TLS error (${code})`;
7838
+ }
7742
7839
  throw new CliError(
7743
- `Could not reach ${info.baseUrl}: check APEX_BASE_URL (or DRIP_BASE_URL) / network; server did not respond within ${info.timeoutMs / 1e3}s.`,
7840
+ `Could not reach ${info.baseUrl}: check APEX_BASE_URL (or DRIP_BASE_URL) / network; ${message}.`,
7744
7841
  EXIT_CODES.general,
7745
- { ...info }
7842
+ { reason, code, name: info.name, baseUrl: info.baseUrl, timeoutMs: info.timeoutMs }
7746
7843
  );
7747
7844
  }
7748
7845
  });
@@ -7751,10 +7848,14 @@ function sharedClient(creds) {
7751
7848
  return client;
7752
7849
  }
7753
7850
  async function request(creds, method, path24, body5, requestTimeoutMs) {
7754
- return sharedClient(creds).request(method, path24, body5, requestTimeoutMs);
7851
+ const response = await sharedClient(creds).request(method, path24, body5, requestTimeoutMs);
7852
+ recordApiResponse(response);
7853
+ return response;
7755
7854
  }
7756
7855
  async function requestForm(creds, method, path24, body5) {
7757
- return sharedClient(creds).requestForm(method, path24, body5);
7856
+ const response = await sharedClient(creds).requestForm(method, path24, body5);
7857
+ recordApiResponse(response);
7858
+ return response;
7758
7859
  }
7759
7860
  function listExperiments(creds, opts) {
7760
7861
  const params = new URLSearchParams();
@@ -9800,7 +9901,8 @@ async function resolveExperiment(idOrSlug) {
9800
9901
  throw new CliError(
9801
9902
  res2.status === 404 && isTestKeyReference(idOrSlug) ? `Experiment "${idOrSlug}" not found in selected shop ${creds.resolvedContext?.shopId ?? "(API key shop)"}. Test keys resolve within the selected shop; use --shop <shop-id> to change it.` : res2.error ?? `Experiment "${idOrSlug}" not found`,
9802
9903
  classifyStatus(res2.status),
9803
- res2.errorDetails
9904
+ res2.errorDetails,
9905
+ apiFailureOf(res2)
9804
9906
  );
9805
9907
  }
9806
9908
  const slug = idOrSlug.startsWith("slug:") ? idOrSlug.slice(5) : idOrSlug;
@@ -9809,7 +9911,8 @@ async function resolveExperiment(idOrSlug) {
9809
9911
  throw new CliError(
9810
9912
  res.error ?? `Experiment "${idOrSlug}" not found`,
9811
9913
  classifyStatus(res.status),
9812
- res.errorDetails
9914
+ res.errorDetails,
9915
+ apiFailureOf(res)
9813
9916
  );
9814
9917
  }
9815
9918
  var list = new Command("list").alias("ls").description(
@@ -9836,7 +9939,7 @@ var list = new Command("list").alias("ls").description(
9836
9939
  }
9837
9940
  spinner?.stop();
9838
9941
  const experiments = res.data.data;
9839
- output(this, { experiments, pagination: res.data.pagination }, () => {
9942
+ output(this, res.data, () => {
9840
9943
  if (experiments.length === 0) {
9841
9944
  console.log(source_default.dim("No experiments found."));
9842
9945
  return;
@@ -9888,11 +9991,7 @@ var get = new Command("get").description(
9888
9991
  exp.shop_id ?? creds.resolvedContext?.shopId,
9889
9992
  exp.id
9890
9993
  );
9891
- output(
9892
- this,
9893
- { experiment: exp, dashboard_url: dashboardUrl },
9894
- () => printExperiment(exp, dashboardUrl)
9895
- );
9994
+ output(this, { ...exp, dashboard_url: dashboardUrl }, () => printExperiment(exp, dashboardUrl));
9896
9995
  });
9897
9996
  var attachIdea = new Command("attach-idea").description("Attach an experiment to an idea").argument("<experiment>", EXPERIMENT_REFERENCE_DESCRIPTION).requiredOption("--idea <key>", "Idea UUID or Apex backlog key").option("--follow-up", "Attach as a follow-up test").action(async function action5(experimentRef, opts) {
9898
9997
  const experiment = await resolveExperiment(experimentRef);
@@ -9949,7 +10048,7 @@ var results = new Command("results").description("Read experiment results and ti
9949
10048
  spinner?.stop();
9950
10049
  output(
9951
10050
  this,
9952
- { experiment: exp, results: res.data },
10051
+ res.data,
9953
10052
  () => printExperimentResults(res.data, opts.currency)
9954
10053
  );
9955
10054
  } catch (error) {
@@ -10535,7 +10634,7 @@ var pause = new Command("pause").description("Pause a running experiment").argum
10535
10634
  res.errorDetails
10536
10635
  );
10537
10636
  }
10538
- output(this, { experiment: { id: exp.id, name: exp.name, status: "paused" } }, () => {
10637
+ output(this, res.data, () => {
10539
10638
  spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("paused")}`);
10540
10639
  });
10541
10640
  });
@@ -10557,7 +10656,7 @@ var complete = new Command("complete").description("Complete/stop an experiment"
10557
10656
  res.errorDetails
10558
10657
  );
10559
10658
  }
10560
- output(this, { experiment: { id: exp.id, name: exp.name, status: "completed" } }, () => {
10659
+ output(this, res.data, () => {
10561
10660
  spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("completed")}`);
10562
10661
  });
10563
10662
  });
@@ -10585,7 +10684,7 @@ var del = new Command("delete").alias("rm").description("Delete an experiment").
10585
10684
  });
10586
10685
  rl.close();
10587
10686
  if (answer.toLowerCase() !== "y") {
10588
- console.log("Aborted.");
10687
+ output(this, { aborted: true }, () => console.log("Aborted."));
10589
10688
  return;
10590
10689
  }
10591
10690
  }
@@ -10600,11 +10699,7 @@ var del = new Command("delete").alias("rm").description("Delete an experiment").
10600
10699
  res.errorDetails
10601
10700
  );
10602
10701
  }
10603
- output(
10604
- this,
10605
- { experiment: { id: exp.id, name: exp.name }, deleted: true },
10606
- () => spinner.succeed(`Experiment "${exp.name}" deleted.`)
10607
- );
10702
+ output(this, res.data, () => spinner.succeed(`Experiment "${exp.name}" deleted.`));
10608
10703
  });
10609
10704
  var pullFilesystem = new Command("pull").description("Pull an experiment as an editable filesystem").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--out <path>", "Target folder").action(async function action13(idOrSlug, opts) {
10610
10705
  const creds = requireCredentials();
@@ -10773,7 +10868,7 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
10773
10868
  );
10774
10869
  }
10775
10870
  if (!ok) {
10776
- console.log("Aborted.");
10871
+ output(this, { aborted: true }, () => console.log("Aborted."));
10777
10872
  return;
10778
10873
  }
10779
10874
  }
@@ -10879,7 +10974,7 @@ var qaStatus = new Command("status").description(
10879
10974
  resolved.folder,
10880
10975
  resolved.experimentId
10881
10976
  );
10882
- output(this, { run: res.data.run }, () => printQaRun(res.data.run));
10977
+ output(this, res.data, () => printQaRun(res.data.run));
10883
10978
  } catch (error) {
10884
10979
  spinner.fail("Failed to fetch QA run");
10885
10980
  throw error;
@@ -10921,7 +11016,7 @@ var qaCheck = new Command("check").description("Update one QA checklist item wit
10921
11016
  resolved.folder,
10922
11017
  resolved.experimentId
10923
11018
  );
10924
- const data = opts.summary ? qaSummary(run3, run3.checklist.find((item) => item.id === opts.item) ?? null) : { run: run3 };
11019
+ const data = opts.summary ? qaSummary(run3, run3.checklist.find((item) => item.id === opts.item) ?? null) : res.data;
10925
11020
  output(this, data, () => {
10926
11021
  spinner.succeed("Updated QA checklist");
10927
11022
  if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
@@ -10970,7 +11065,7 @@ var qaNotes = new Command("notes").description("Update QA run notes or acceptanc
10970
11065
  const data = opts.summary ? qaSummary(run3, {
10971
11066
  notes: run3.notes,
10972
11067
  acceptanceCriteriaSummary: run3.acceptanceCriteriaSummary
10973
- }) : { run: run3 };
11068
+ }) : res.data;
10974
11069
  output(this, data, () => {
10975
11070
  spinner.succeed("Updated QA notes");
10976
11071
  if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
@@ -11051,11 +11146,7 @@ var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled
11051
11146
  resolved.folder,
11052
11147
  resolved.experimentId
11053
11148
  );
11054
- output(
11055
- this,
11056
- { experimentId: resolved.experimentId, qaStatus: "open" },
11057
- () => spinner.succeed(`QA reopened for ${resolved.label}`)
11058
- );
11149
+ output(this, res.data, () => spinner.succeed(`QA reopened for ${resolved.label}`));
11059
11150
  } catch (error) {
11060
11151
  spinner.fail("Failed to reopen QA");
11061
11152
  throw error;
@@ -11162,7 +11253,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
11162
11253
  const data = opts.summary ? qaSummary(
11163
11254
  run3,
11164
11255
  run3.evidenceRecords.filter((record2) => !previousIds.has(record2.id))
11165
- ) : { run: run3 };
11256
+ ) : response.data;
11166
11257
  output(this, data, () => {
11167
11258
  if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
11168
11259
  `);
@@ -12016,7 +12107,10 @@ function cleanupResetJs(experiment) {
12016
12107
  function runAbHarness(args, cwd) {
12017
12108
  const bin = process.env.AB_HARNESS_BIN || "ab-harness";
12018
12109
  return new Promise((resolve2, reject) => {
12019
- const child = (0, import_node_child_process6.spawn)(bin, args, { cwd, stdio: "inherit" });
12110
+ const child = (0, import_node_child_process6.spawn)(bin, args, {
12111
+ cwd,
12112
+ stdio: isJsonMode() ? ["inherit", 2, "inherit"] : "inherit"
12113
+ });
12020
12114
  child.on("error", reject);
12021
12115
  child.on("exit", (code) => {
12022
12116
  if (code === 0) resolve2();
@@ -13025,29 +13119,36 @@ function printManifestPlan(manifest) {
13025
13119
  var trackingCommand = new Command("tracking").description(
13026
13120
  "Create Apex-first tracking ingestion manifests and QA handoffs"
13027
13121
  );
13028
- trackingCommand.command("inspect").description("Create a local tracking manifest from funnel URLs").argument("<urls...>", "Landing, storefront, checkout, or confirmation URLs").option("--shop-id <id>", "Apex shop ID for snippet generation").option("--endpoint <url>", "Apex Worker endpoint", "https://events.drip-apex.com").option("--out <path>", "Manifest output path", defaultManifestPath()).action((urls, opts) => {
13122
+ trackingCommand.command("inspect").description("Create a local tracking manifest from funnel URLs").argument("<urls...>", "Landing, storefront, checkout, or confirmation URLs").option("--shop-id <id>", "Apex shop ID for snippet generation").option("--endpoint <url>", "Apex Worker endpoint", "https://events.drip-apex.com").option("--out <path>", "Manifest output path", defaultManifestPath()).action(function(urls, opts) {
13029
13123
  const manifest = buildManifest(urls, opts);
13030
13124
  const outPath = manifestPath(opts.out);
13031
13125
  writeManifest(outPath, manifest);
13032
- printSuccess(`Wrote ${outPath}`);
13033
- if (manifest.recommendedCookieDomain) {
13034
- console.log(`Recommended cookie domain: ${source_default.cyan(manifest.recommendedCookieDomain)}`);
13035
- }
13036
- if (manifest.snippet.html) {
13037
- console.log(`Head snippet: ${source_default.cyan(manifest.snippet.html)}`);
13038
- } else {
13126
+ if (!manifest.snippet.html)
13039
13127
  printWarning("No shop ID provided, so the manifest has no install snippet.");
13040
- }
13128
+ output(this, { path: outPath, manifest }, () => {
13129
+ printSuccess(`Wrote ${outPath}`);
13130
+ if (manifest.recommendedCookieDomain) {
13131
+ console.log(`Recommended cookie domain: ${source_default.cyan(manifest.recommendedCookieDomain)}`);
13132
+ }
13133
+ if (manifest.snippet.html) {
13134
+ console.log(`Head snippet: ${source_default.cyan(manifest.snippet.html)}`);
13135
+ }
13136
+ });
13041
13137
  });
13042
- trackingCommand.command("plan").description("Print the implementation plan from a tracking manifest").argument("[manifest]", "Tracking manifest path", defaultManifestPath()).action((input) => {
13043
- printManifestPlan(readManifest(manifestPath(input)));
13138
+ trackingCommand.command("plan").description("Print the implementation plan from a tracking manifest").argument("[manifest]", "Tracking manifest path", defaultManifestPath()).action(function(input) {
13139
+ const filePath = manifestPath(input);
13140
+ const manifest = readManifest(filePath);
13141
+ output(this, { path: filePath, manifest }, () => printManifestPlan(manifest));
13044
13142
  });
13045
- trackingCommand.command("qa").description("Print the QA checklist for a tracking manifest").argument("[manifest]", "Tracking manifest path", defaultManifestPath()).action((input) => {
13046
- const manifest = readManifest(manifestPath(input));
13047
- console.log(source_default.bold("Apex tracking QA"));
13048
- console.log();
13049
- manifest.qa.checks.forEach((check, index) => {
13050
- console.log(`${index + 1}. ${check}`);
13143
+ trackingCommand.command("qa").description("Print the QA checklist for a tracking manifest").argument("[manifest]", "Tracking manifest path", defaultManifestPath()).action(function(input) {
13144
+ const filePath = manifestPath(input);
13145
+ const manifest = readManifest(filePath);
13146
+ output(this, { path: filePath, checks: manifest.qa.checks }, () => {
13147
+ console.log(source_default.bold("Apex tracking QA"));
13148
+ console.log();
13149
+ manifest.qa.checks.forEach((check, index) => {
13150
+ console.log(`${index + 1}. ${check}`);
13151
+ });
13051
13152
  });
13052
13153
  });
13053
13154
  trackingCommand.command("verify").description("Verify live tracking events for the current shop").option("--minutes <n>", "Lookback window in minutes", "120").option("--limit <n>", "Recent events to include", "20").option("--experiment <id>", "Filter to one experiment ID").option("--event-type <type>", "Filter to one event type; repeat with comma-separated values").action(async function action33(opts) {
@@ -13066,7 +13167,7 @@ trackingCommand.command("verify").description("Verify live tracking events for t
13066
13167
  res.errorDetails
13067
13168
  );
13068
13169
  }
13069
- output(this, { verification: res.data }, () => {
13170
+ output(this, res.data, () => {
13070
13171
  const totals = res.data.totals;
13071
13172
  console.log(source_default.bold("Apex tracking verification"));
13072
13173
  console.log();
@@ -13153,8 +13254,9 @@ trackingCommand.command("latency").description("Measure forced-preview versus na
13153
13254
  ].join("/")
13154
13255
  ])
13155
13256
  );
13257
+ console.log("Times in ms. Fewer than 5 measured samples: -.");
13156
13258
  console.log(
13157
- "Times in ms. Fewer than 5 measured samples: -. Preload/anti-flicker position: under evaluation."
13259
+ "Position: runtime unchanged (inline config decides within tens of ms; forced previews fetch config over the network and are not representative)"
13158
13260
  );
13159
13261
  });
13160
13262
  });
@@ -13370,7 +13472,7 @@ var list2 = new Command("list").alias("ls").description("List goals").option("-n
13370
13472
  if (!res.ok || !res.data) {
13371
13473
  fail(this, res.error ?? "Failed to list goals", classifyStatus(res.status), res.errorDetails);
13372
13474
  }
13373
- output(this, { goals: res.data.data, pagination: res.data.pagination }, () => {
13475
+ output(this, res.data, () => {
13374
13476
  if (res.data.data.length === 0) {
13375
13477
  console.log(source_default.dim("No goals found."));
13376
13478
  return;
@@ -13412,7 +13514,7 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
13412
13514
  res.errorDetails
13413
13515
  );
13414
13516
  }
13415
- output(this, { goal: res.data }, () => printGoal(res.data));
13517
+ output(this, res.data, () => printGoal(res.data));
13416
13518
  });
13417
13519
  var update2 = new Command("update").description("Update a goal").argument("<id>", "Goal ID").option("--name <name>", "Goal name").option("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value").option(
13418
13520
  "--value-property <property>",
@@ -13444,7 +13546,7 @@ var update2 = new Command("update").description("Update a goal").argument("<id>"
13444
13546
  res.errorDetails
13445
13547
  );
13446
13548
  }
13447
- output(this, { goal: res.data }, () => printGoal(res.data));
13549
+ output(this, res.data, () => printGoal(res.data));
13448
13550
  });
13449
13551
  var test = new Command("test").description("Dry run a goal URL pattern against traffic").option(
13450
13552
  "--pattern <regex>",
@@ -13541,9 +13643,8 @@ var suggest = new Command("suggest").description("Suggest goals and funnels from
13541
13643
  classifyStatus(res.status),
13542
13644
  res.errorDetails
13543
13645
  );
13544
- output(this, res.data, () => printSuggestions(res.data));
13545
- if (!opts.create) return;
13546
- for (const goal of res.data.goals) {
13646
+ if (!shouldUseJson(this)) printSuggestions(res.data);
13647
+ for (const goal of opts.create ? res.data.goals : []) {
13547
13648
  console.error(`Would create ${goal.type} goal: ${goal.name}`);
13548
13649
  if (!opts.yes) {
13549
13650
  if (!process.stdin.isTTY || !process.stdout.isTTY)
@@ -13578,6 +13679,7 @@ var suggest = new Command("suggest").description("Suggest goals and funnels from
13578
13679
  created.errorDetails
13579
13680
  );
13580
13681
  }
13682
+ output(this, res.data, () => void 0);
13581
13683
  });
13582
13684
  var goalsCommand = new Command("goals").description("Manage conversion and revenue goals").addCommand(list2).addCommand(create2).addCommand(update2).addCommand(test).addCommand(suggest).addHelpText(
13583
13685
  "after",
@@ -13632,7 +13734,7 @@ var list3 = new Command("list").alias("ls").description("List funnels").action(a
13632
13734
  );
13633
13735
  output(
13634
13736
  this,
13635
- { funnels: res.data.data },
13737
+ res.data,
13636
13738
  () => printTable(
13637
13739
  ["NAME", "ID", "STEPS"],
13638
13740
  res.data.data.map((f) => [f.name.slice(0, 36), f.id, String(f.steps.length)]),
@@ -13652,7 +13754,7 @@ var create3 = new Command("create").description("Create a funnel").requiredOptio
13652
13754
  classifyStatus(res.status),
13653
13755
  res.errorDetails
13654
13756
  );
13655
- output(this, { funnel: res.data }, () => printFunnel(res.data));
13757
+ output(this, res.data, () => printFunnel(res.data));
13656
13758
  });
13657
13759
  var update3 = new Command("update").description("Replace a funnel's steps").argument("<id>", "Funnel ID").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(async function action41(id, opts) {
13658
13760
  const res = await updateFunnel(requireCredentials(), id, {
@@ -13665,7 +13767,7 @@ var update3 = new Command("update").description("Replace a funnel's steps").argu
13665
13767
  classifyStatus(res.status),
13666
13768
  res.errorDetails
13667
13769
  );
13668
- output(this, { funnel: res.data }, () => printFunnel(res.data));
13770
+ output(this, res.data, () => printFunnel(res.data));
13669
13771
  });
13670
13772
  var del2 = new Command("delete").description("Delete a funnel").argument("<id>", "Funnel ID").action(async function action42(id) {
13671
13773
  const res = await deleteFunnel(requireCredentials(), id);
@@ -13676,7 +13778,7 @@ var del2 = new Command("delete").description("Delete a funnel").argument("<id>",
13676
13778
  classifyStatus(res.status),
13677
13779
  res.errorDetails
13678
13780
  );
13679
- output(this, { deleted: true, id }, () => console.log(`Deleted funnel ${id}`));
13781
+ output(this, res.data ?? null, () => console.log(`Deleted funnel ${id}`));
13680
13782
  });
13681
13783
  var run = new Command("run").description("Run funnel analysis").argument("<id>", "Funnel ID").option("--days <n>", "Analysis window", "30").action(async function action43(id, opts) {
13682
13784
  if (!/^[1-9]\d*$/.test(opts.days))
@@ -13689,7 +13791,7 @@ var run = new Command("run").description("Run funnel analysis").argument("<id>",
13689
13791
  fail(this, res.error ?? "Failed to run funnel", classifyStatus(res.status), res.errorDetails);
13690
13792
  output(
13691
13793
  this,
13692
- { run: res.data },
13794
+ res.data,
13693
13795
  () => printTable(
13694
13796
  ["STEP", "LABEL", "VISITORS", "DROP-OFF"],
13695
13797
  res.data.steps.map((s) => [
@@ -13764,8 +13866,8 @@ async function load2(command) {
13764
13866
  return result.data;
13765
13867
  }
13766
13868
  var list4 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function action45() {
13767
- const { environments } = await load2(this);
13768
- output(this, { environments }, () => printEnvironments(environments.items));
13869
+ const body5 = await load2(this);
13870
+ output(this, body5, () => printEnvironments(body5.environments.items));
13769
13871
  });
13770
13872
  var setDomain = new Command("set-domain").description("Map a domain to a runtime environment").argument("<domain>", "Domain to map").requiredOption("--environment <env>", "Environment key").action(async function action46(domain, opts) {
13771
13873
  const normalizedDomain = normalizeEnvironmentDomain(domain);
@@ -13808,11 +13910,7 @@ var setDomain = new Command("set-domain").description("Map a domain to a runtime
13808
13910
  );
13809
13911
  }
13810
13912
  warnOnPrecompileError(this, result.data.precompile);
13811
- output(
13812
- this,
13813
- { environments: result.data.environments, precompile: result.data.precompile },
13814
- () => printEnvironments(result.data.environments.items)
13815
- );
13913
+ output(this, result.data, () => printEnvironments(result.data.environments.items));
13816
13914
  });
13817
13915
  var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function action47(domain) {
13818
13916
  const normalizedDomain = normalizeEnvironmentDomain(domain);
@@ -13843,11 +13941,7 @@ var removeDomain = new Command("remove-domain").description("Remove a domain map
13843
13941
  );
13844
13942
  }
13845
13943
  warnOnPrecompileError(this, result.data.precompile);
13846
- output(
13847
- this,
13848
- { environments: result.data.environments, precompile: result.data.precompile },
13849
- () => printEnvironments(result.data.environments.items)
13850
- );
13944
+ output(this, result.data, () => printEnvironments(result.data.environments.items));
13851
13945
  });
13852
13946
  var environmentsCommand = new Command("environments").description("Manage runtime environments and domain mappings").addCommand(list4).addCommand(setDomain).addCommand(removeDomain);
13853
13947
 
@@ -15724,7 +15818,9 @@ async function runDoctor(options = {}) {
15724
15818
  null,
15725
15819
  res.error ?? `Failed to load ${label}`,
15726
15820
  classifyStatus(res.status),
15727
- res.errorDetails
15821
+ res.errorDetails,
15822
+ // Parallel reads: name this response's envelope explicitly.
15823
+ apiFailureOf(res)
15728
15824
  );
15729
15825
  }
15730
15826
  for (const [label, res] of [
@@ -16085,7 +16181,7 @@ function analyticsSubcommand(name, description, defaultDays) {
16085
16181
  classifyStatus(result.status),
16086
16182
  result.errorDetails
16087
16183
  );
16088
- output(this, { data: result.data.data }, () => printAnalytics(result.data.data));
16184
+ output(this, result.data, () => printAnalytics(result.data.data));
16089
16185
  });
16090
16186
  }
16091
16187
  function urlsCommand() {
@@ -16922,7 +17018,7 @@ var list6 = new Command("list").alias("ls").description("List flags").option("-n
16922
17018
  fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
16923
17019
  output(
16924
17020
  this,
16925
- { flags: r.data.data, pagination: r.data.pagination },
17021
+ r.data,
16926
17022
  () => printTable(
16927
17023
  ["KEY/NAME", "STATE", "UPDATED"],
16928
17024
  r.data.data.map((x) => [x.key || x.name, x.status, x.updated_at ?? "-"]),
@@ -16934,7 +17030,7 @@ var get5 = new Command("get").description("Get a flag").argument("<id>").action(
16934
17030
  const r = await getManagedDelivery(requireCredentials(), kind, id);
16935
17031
  if (!r.ok || !r.data)
16936
17032
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
16937
- output(this, { flag: r.data }, () => detail(r.data));
17033
+ output(this, r.data, () => detail(r.data));
16938
17034
  });
16939
17035
  var status = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
16940
17036
  const r = await getFlagStatus(requireCredentials(), id);
@@ -16995,7 +17091,7 @@ function write(name) {
16995
17091
  const r = name === "create" ? await createManagedDelivery(requireCredentials(), kind, b) : await updateManagedDelivery(requireCredentials(), kind, id, b);
16996
17092
  if (!r.ok || !r.data)
16997
17093
  fail(this, r.error ?? `Failed to ${name}`, classifyStatus(r.status), r.errorDetails);
16998
- output(this, { flag: r.data }, () => detail(r.data));
17094
+ output(this, r.data, () => detail(r.data));
16999
17095
  });
17000
17096
  return c;
17001
17097
  }
@@ -17004,7 +17100,7 @@ function lifecycle(action74) {
17004
17100
  const r = await transitionManagedDelivery(requireCredentials(), kind, id, action74);
17005
17101
  if (!r.ok || !r.data)
17006
17102
  fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
17007
- output(this, { flag: r.data }, () => detail(r.data));
17103
+ output(this, r.data, () => detail(r.data));
17008
17104
  });
17009
17105
  }
17010
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(
@@ -17121,7 +17217,7 @@ var list7 = new Command("list").alias("ls").description("List backlog items iden
17121
17217
  fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
17122
17218
  output(
17123
17219
  this,
17124
- { backlog: r.data.data, pagination: r.data.pagination },
17220
+ r.data,
17125
17221
  () => printTable(
17126
17222
  ["ID", "NAME", "STATE"],
17127
17223
  r.data.data.map((x) => [
@@ -17138,7 +17234,7 @@ var get6 = new Command("get").description("Get a backlog item identified as apex
17138
17234
  if (!r.ok || !r.data)
17139
17235
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
17140
17236
  const labels = await fieldLabels(this, r.data);
17141
- output(this, { backlog_item: r.data }, () => detail2(r.data, labels));
17237
+ output(this, r.data, () => detail2(r.data, labels));
17142
17238
  });
17143
17239
  function write2(action74) {
17144
17240
  const c = new Command(action74).description(
@@ -17165,7 +17261,7 @@ function write2(action74) {
17165
17261
  if (!r.ok || !r.data)
17166
17262
  fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
17167
17263
  const labels = await fieldLabels(this, r.data);
17168
- output(this, { backlog_item: r.data }, () => detail2(r.data, labels));
17264
+ output(this, r.data, () => detail2(r.data, labels));
17169
17265
  });
17170
17266
  return c;
17171
17267
  }
@@ -17178,13 +17274,13 @@ var archive = new Command("archive").description("Archive a backlog item with a
17178
17274
  const r = await archiveBacklogInsight(requireCredentials(), id, opts.reason.trim());
17179
17275
  if (!r.ok || !r.data)
17180
17276
  fail(this, r.error ?? "Failed to archive", classifyStatus(r.status), r.errorDetails);
17181
- output(this, { archived: true, id }, () => console.log(`Archived ${id}`));
17277
+ output(this, r.data, () => console.log(`Archived ${id}`));
17182
17278
  });
17183
17279
  var restore = new Command("restore").description("Restore an archived backlog item").argument("<id>", "backlog item ID").action(async function(id) {
17184
17280
  const r = await restoreBacklogInsight(requireCredentials(), id);
17185
17281
  if (!r.ok || !r.data)
17186
17282
  fail(this, r.error ?? "Failed to restore", classifyStatus(r.status), r.errorDetails);
17187
- output(this, { restored: true, id }, () => console.log(`Restored ${id}`));
17283
+ output(this, r.data, () => console.log(`Restored ${id}`));
17188
17284
  });
17189
17285
  var remove2 = new Command("delete").description("Delete a backlog item").argument("<id>", "backlog item ID").option("-f, --force", "Skip confirmation").action(async function(id, opts) {
17190
17286
  if (!opts.force) {
@@ -17201,14 +17297,14 @@ var remove2 = new Command("delete").description("Delete a backlog item").argumen
17201
17297
  );
17202
17298
  rl.close();
17203
17299
  if (answer.toLowerCase() !== "y") {
17204
- console.log("Aborted.");
17300
+ output(this, { aborted: true }, () => console.log("Aborted."));
17205
17301
  return;
17206
17302
  }
17207
17303
  }
17208
17304
  const r = await deleteInsight(requireCredentials(), kind2, id);
17209
17305
  if (!r.ok || !r.data)
17210
17306
  fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
17211
- output(this, { deleted: true, id }, () => console.log(`Deleted ${id}`));
17307
+ output(this, r.data, () => console.log(`Deleted ${id}`));
17212
17308
  });
17213
17309
  function fieldDetail(field) {
17214
17310
  console.log();
@@ -17235,7 +17331,7 @@ var fieldsList = new Command("list").alias("ls").description("List the shop's id
17235
17331
  fail(this, r.error ?? "Failed to list fields", classifyStatus(r.status), r.errorDetails);
17236
17332
  output(
17237
17333
  this,
17238
- { fields: r.data.data },
17334
+ r.data,
17239
17335
  () => printTable(
17240
17336
  ["KEY", "LABEL", "TYPE", "REQUIRED", "CLIENT"],
17241
17337
  r.data.data.map((field) => [
@@ -17270,7 +17366,7 @@ var fieldsCreate = new Command("create").description("Add an idea field").requir
17270
17366
  });
17271
17367
  if (!r.ok || !r.data)
17272
17368
  fail(this, r.error ?? "Failed to create field", classifyStatus(r.status), r.errorDetails);
17273
- output(this, { field: r.data }, () => fieldDetail(r.data));
17369
+ output(this, r.data, () => fieldDetail(r.data));
17274
17370
  });
17275
17371
  var fieldsUpdate = new Command("update").description("Update an idea field").argument("<id>", "field ID").option("--label <text>", "What the field is called").option(
17276
17372
  "--option <value>",
@@ -17293,13 +17389,13 @@ var fieldsUpdate = new Command("update").description("Update an idea field").arg
17293
17389
  const r = await updateBacklogField(requireCredentials(), id, body5);
17294
17390
  if (!r.ok || !r.data)
17295
17391
  fail(this, r.error ?? "Failed to update field", classifyStatus(r.status), r.errorDetails);
17296
- output(this, { field: r.data }, () => fieldDetail(r.data));
17392
+ output(this, r.data, () => fieldDetail(r.data));
17297
17393
  });
17298
17394
  var fieldsArchive = new Command("archive").description("Archive an idea field; values already on ideas are kept").argument("<id>", "field ID").action(async function(id) {
17299
17395
  const r = await archiveBacklogField(requireCredentials(), id);
17300
17396
  if (!r.ok || !r.data)
17301
17397
  fail(this, r.error ?? "Failed to archive field", classifyStatus(r.status), r.errorDetails);
17302
- output(this, { field: r.data }, () => fieldDetail(r.data));
17398
+ output(this, r.data, () => fieldDetail(r.data));
17303
17399
  });
17304
17400
  var fields2 = new Command("fields").description("Manage the shop's own idea fields").addCommand(fieldsList).addCommand(fieldsCreate).addCommand(fieldsUpdate).addCommand(fieldsArchive);
17305
17401
  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);
@@ -17346,7 +17442,7 @@ var list8 = new Command("list").alias("ls").description("List signals").option("
17346
17442
  fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
17347
17443
  output(
17348
17444
  this,
17349
- { signals: r.data.data, pagination: r.data.pagination },
17445
+ r.data,
17350
17446
  () => printTable(
17351
17447
  ["ID", "NAME", "STATE"],
17352
17448
  r.data.data.map((x) => [
@@ -17362,7 +17458,7 @@ var get7 = new Command("get").description("Get a signal").argument("<id>", "sign
17362
17458
  const r = await getInsight(requireCredentials(), kind3, id);
17363
17459
  if (!r.ok || !r.data)
17364
17460
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
17365
- output(this, { signal: r.data }, () => detail3(r.data));
17461
+ output(this, r.data, () => detail3(r.data));
17366
17462
  });
17367
17463
  function write3(action74) {
17368
17464
  const c = new Command(action74).description(
@@ -17382,7 +17478,7 @@ function write3(action74) {
17382
17478
  const r = action74 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
17383
17479
  if (!r.ok || !r.data)
17384
17480
  fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
17385
- output(this, { signal: r.data }, () => detail3(r.data));
17481
+ output(this, r.data, () => detail3(r.data));
17386
17482
  });
17387
17483
  return c;
17388
17484
  }
@@ -17390,7 +17486,7 @@ var remove3 = new Command("delete").alias("archive").description("Delete or arch
17390
17486
  const r = await deleteInsight(requireCredentials(), kind3, id);
17391
17487
  if (!r.ok || !r.data)
17392
17488
  fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
17393
- output(this, { deleted: true, id }, () => console.log(`Deleted ${id}`));
17489
+ output(this, r.data, () => console.log(`Deleted ${id}`));
17394
17490
  });
17395
17491
  var signalsCommand = new Command("signals").description("Manage signals").addCommand(list8).addCommand(get7).addCommand(write3("create")).addCommand(write3("update")).addCommand(remove3);
17396
17492
 
@@ -17439,7 +17535,7 @@ var list9 = new Command("list").alias("ls").description("List personalizations")
17439
17535
  fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
17440
17536
  output(
17441
17537
  this,
17442
- { personalizations: r.data.data, pagination: r.data.pagination },
17538
+ r.data,
17443
17539
  () => printTable(
17444
17540
  ["KEY/NAME", "STATE", "UPDATED"],
17445
17541
  r.data.data.map((x) => [x.key || x.name, x.status, x.updated_at ?? "-"]),
@@ -17451,7 +17547,7 @@ var get8 = new Command("get").description("Get a personalization").argument("<id
17451
17547
  const r = await getManagedDelivery(requireCredentials(), kind4, id);
17452
17548
  if (!r.ok || !r.data)
17453
17549
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
17454
- output(this, { personalization: r.data }, () => detail4(r.data));
17550
+ output(this, r.data, () => detail4(r.data));
17455
17551
  });
17456
17552
  function write4(name) {
17457
17553
  const c = new Command(name).description(
@@ -17469,7 +17565,7 @@ function write4(name) {
17469
17565
  const r = name === "create" ? await createManagedDelivery(requireCredentials(), kind4, b) : await updateManagedDelivery(requireCredentials(), kind4, id, b);
17470
17566
  if (!r.ok || !r.data)
17471
17567
  fail(this, r.error ?? `Failed to ${name}`, classifyStatus(r.status), r.errorDetails);
17472
- output(this, { personalization: r.data }, () => detail4(r.data));
17568
+ output(this, r.data, () => detail4(r.data));
17473
17569
  });
17474
17570
  return c;
17475
17571
  }
@@ -17478,7 +17574,7 @@ function lifecycle2(action74) {
17478
17574
  const r = await transitionManagedDelivery(requireCredentials(), kind4, id, action74);
17479
17575
  if (!r.ok || !r.data)
17480
17576
  fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
17481
- output(this, { personalization: r.data }, () => detail4(r.data));
17577
+ output(this, r.data, () => detail4(r.data));
17482
17578
  });
17483
17579
  }
17484
17580
  var personalizationsCommand = new Command("personalizations").description("Manage personalizations").addCommand(list9).addCommand(get8).addCommand(write4("create")).addCommand(write4("update")).addCommand(lifecycle2("activate")).addCommand(lifecycle2("pause")).addCommand(lifecycle2("archive"));
@@ -17528,7 +17624,7 @@ function targetingResourceCommand(kind5) {
17528
17624
  const page = response.data;
17529
17625
  output(
17530
17626
  this,
17531
- { [kind5.replace("-", "_")]: page.data, pagination: page.pagination },
17627
+ page,
17532
17628
  () => printTable(
17533
17629
  ["NAME", "ID", "UPDATED"],
17534
17630
  page.data.map((item) => [
@@ -17554,7 +17650,7 @@ function targetingResourceCommand(kind5) {
17554
17650
  );
17555
17651
  output(
17556
17652
  this,
17557
- { exclusion_group: response.data },
17653
+ response.data,
17558
17654
  () => console.log(source_default.bold(response.data.name ?? id))
17559
17655
  );
17560
17656
  })
@@ -17585,13 +17681,7 @@ function targetingResourceCommand(kind5) {
17585
17681
  classifyStatus(response.status),
17586
17682
  response.errorDetails
17587
17683
  );
17588
- output(
17589
- this,
17590
- {
17591
- [kind5 === "exclusion-groups" ? "exclusion_group" : singular3]: response.data
17592
- },
17593
- () => console.log(source_default.green(`${action74}d ${singular3}`))
17594
- );
17684
+ output(this, response.data, () => console.log(source_default.green(`${action74}d ${singular3}`)));
17595
17685
  });
17596
17686
  root.addCommand(command);
17597
17687
  }
@@ -17616,7 +17706,7 @@ function targetingResourceCommand(kind5) {
17616
17706
  });
17617
17707
  rl.close();
17618
17708
  if (answer.toLowerCase() !== "y") {
17619
- console.log("Aborted.");
17709
+ output(this, { aborted: true }, () => console.log("Aborted."));
17620
17710
  return;
17621
17711
  }
17622
17712
  }
@@ -17628,7 +17718,7 @@ function targetingResourceCommand(kind5) {
17628
17718
  classifyStatus(response.status),
17629
17719
  response.errorDetails
17630
17720
  );
17631
- output(this, { deleted: true, id }, () => console.log(source_default.green(`Deleted ${singular3}`)));
17721
+ output(this, response.data ?? null, () => console.log(source_default.green(`Deleted ${singular3}`)));
17632
17722
  })
17633
17723
  );
17634
17724
  if (kind5 === "exclusion-groups")
@@ -17648,11 +17738,7 @@ function targetingResourceCommand(kind5) {
17648
17738
  classifyStatus(response.status),
17649
17739
  response.errorDetails
17650
17740
  );
17651
- output(
17652
- this,
17653
- { exclusion_group: response.data },
17654
- () => console.log(source_default.green("Replaced members"))
17655
- );
17741
+ output(this, response.data, () => console.log(source_default.green("Replaced members")));
17656
17742
  })
17657
17743
  );
17658
17744
  return root;
@@ -17668,7 +17754,9 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
17668
17754
  const creds = requireCredentials();
17669
17755
  const res = await getBrandContext(creds);
17670
17756
  if (res.status === 404) {
17671
- output(this, { brandContext: null, message: NO_PROFILE_MESSAGE }, () => {
17757
+ if (shouldUseJson(this)) process.stderr.write(`${NO_PROFILE_MESSAGE}
17758
+ `);
17759
+ output(this, { data: null }, () => {
17672
17760
  console.log(source_default.dim(NO_PROFILE_MESSAGE));
17673
17761
  });
17674
17762
  return;
@@ -17682,7 +17770,7 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
17682
17770
  );
17683
17771
  }
17684
17772
  const brandContext = res.data.data;
17685
- output(this, { brandContext }, () => {
17773
+ output(this, res.data, () => {
17686
17774
  if (!brandContext.hasProfile) {
17687
17775
  console.log(source_default.dim(NO_PROFILE_MESSAGE));
17688
17776
  return;
@@ -17708,7 +17796,7 @@ var test2 = new Command("test").description("Evaluate experiment URL targeting f
17708
17796
  res.errorDetails
17709
17797
  );
17710
17798
  }
17711
- output(this, { targeting: res.data }, () => {
17799
+ output(this, res.data, () => {
17712
17800
  console.log(source_default.bold("Apex targeting test"));
17713
17801
  console.log();
17714
17802
  console.log(`URL: ${source_default.cyan(res.data.url)}`);
@@ -17782,7 +17870,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
17782
17870
  "--type <type>",
17783
17871
  "full_handover|design|development|qa|config|bug|help (internal types require a DRIP account)",
17784
17872
  "qa"
17785
- ).option("--loom <url>", "Loom URL with reproduction or QA context").option("--experiment <id>", "Experiment ID").option("--sub-class <text>", "SOS sub-classification").option("--description <text-or-@file>", "Description text or @path").argument("[description]", "Description text").action(async function action64(descriptionArg, opts) {
17873
+ ).option("--loom <url>", "Loom URL with reproduction or QA context").option("--experiment <id>", "Experiment ID").option("--backlog-item <id>", "Backlog item ID (internal SOS only)").option("--sub-class <text>", "SOS sub-classification").option("--description <text-or-@file>", "Description text or @path").argument("[description]", "Description text").action(async function action64(descriptionArg, opts) {
17786
17874
  if (!SOS_TYPES.has(opts.type)) {
17787
17875
  fail(
17788
17876
  this,
@@ -17790,12 +17878,16 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
17790
17878
  3
17791
17879
  );
17792
17880
  }
17881
+ if (opts.experiment && opts.backlogItem) {
17882
+ fail(this, "Provide only one of --experiment or --backlog-item", 3);
17883
+ }
17793
17884
  const creds = requireCredentials();
17794
17885
  const description = readText(opts.description) ?? descriptionArg ?? `Apex CLI ${opts.type} SOS request${opts.loom ? ` with Loom evidence: ${opts.loom}` : "."}`;
17795
17886
  const res = await createSos(creds, {
17796
17887
  type: opts.type,
17797
17888
  description,
17798
17889
  experiment_id: opts.experiment ?? null,
17890
+ backlog_item_id: opts.backlogItem ?? null,
17799
17891
  sub_class: opts.subClass ?? null,
17800
17892
  loom_url: opts.loom ?? null
17801
17893
  });
@@ -17820,7 +17912,7 @@ var mine = new Command("mine").description("List SOS tickets created by this API
17820
17912
  res.errorDetails
17821
17913
  );
17822
17914
  }
17823
- output(this, { tickets: res.data.tickets }, () => {
17915
+ output(this, res.data, () => {
17824
17916
  if (res.data.tickets.length === 0) {
17825
17917
  console.log(source_default.dim("No SOS tickets found for this API key."));
17826
17918
  return;
@@ -17921,7 +18013,7 @@ var list12 = new Command("list").alias("ls").description("List organization shop
17921
18013
  fail(this, r.error ?? "Failed to list shops", classifyStatus(r.status), r.errorDetails);
17922
18014
  output(
17923
18015
  this,
17924
- { shops: r.data.data },
18016
+ r.data,
17925
18017
  () => printTable(
17926
18018
  ["ID", "NAME", "DOMAIN", "TIER"],
17927
18019
  r.data.data.map((s) => [s.id, s.name ?? "-", s.domain ?? "-", s.tier ?? "-"]),
@@ -18699,7 +18791,7 @@ var discoverCommand = new Command("discover").description("Read back the trackin
18699
18791
  created.errorDetails
18700
18792
  );
18701
18793
  }
18702
- output(this, { signal: created.data }, () => {
18794
+ output(this, created.data, () => {
18703
18795
  process.stdout.write(`Created dimension ${created.data.id}.
18704
18796
  `);
18705
18797
  process.stdout.write("Next: open Audience > Attributes in the Apex dashboard.\n");
@@ -18851,7 +18943,10 @@ async function confirmArchive(command, shopId, force) {
18851
18943
  return true;
18852
18944
  }
18853
18945
  var archive2 = new Command("archive").description("Archive a shop and disable Apex delivery").argument("<shopId>", "Shop ID").option("-f, --force", "Skip confirmation").action(async function(shopId, opts) {
18854
- if (!await confirmArchive(this, shopId, opts.force)) return;
18946
+ if (!await confirmArchive(this, shopId, opts.force)) {
18947
+ output(this, { aborted: true }, () => void 0);
18948
+ return;
18949
+ }
18855
18950
  const res = await archiveAdminShop(requireUserCredentials(), shopId);
18856
18951
  if (!res.ok || !res.data)
18857
18952
  fail(
@@ -19095,7 +19190,9 @@ var create6 = new Command("create").requiredOption("--name <name>").requiredOpti
19095
19190
  classifyApiError(res.status, res.errorDetails),
19096
19191
  res.errorDetails
19097
19192
  );
19098
- output(this, res.data.data, () => {
19193
+ if (shouldUseJson(this))
19194
+ process.stderr.write("Warning: this secret will not be shown again.\n");
19195
+ output(this, res.data, () => {
19099
19196
  console.error("Warning: this secret will not be shown again.");
19100
19197
  console.log(res.data.data.key);
19101
19198
  });
@@ -19392,21 +19489,18 @@ var create7 = new Command("create").description(`Create a webhook. Prints the si
19392
19489
  await createWebhook(requireCredentials(), { url: opts.url, events }),
19393
19490
  "Failed to create webhook"
19394
19491
  );
19395
- output(
19396
- this,
19397
- {
19398
- webhook: result,
19399
- secret: result.secret,
19400
- signatureDoc: SIGNATURE_DOC,
19401
- warning: "Store this signing secret now. Apex will not show it again."
19402
- },
19403
- () => {
19404
- console.log(source_default.green(`Created webhook ${result.id}`));
19405
- console.log(source_default.yellow("Store this signing secret now. Apex will not show it again."));
19406
- console.log(result.secret);
19407
- console.log(`Signature verification: ${SIGNATURE_DOC}`);
19408
- }
19409
- );
19492
+ if (shouldUseJson(this))
19493
+ process.stderr.write(
19494
+ `Store this signing secret now. Apex will not show it again.
19495
+ Signature verification: ${SIGNATURE_DOC}
19496
+ `
19497
+ );
19498
+ output(this, result, () => {
19499
+ console.log(source_default.green(`Created webhook ${result.id}`));
19500
+ console.log(source_default.yellow("Store this signing secret now. Apex will not show it again."));
19501
+ console.log(result.secret);
19502
+ console.log(`Signature verification: ${SIGNATURE_DOC}`);
19503
+ });
19410
19504
  });
19411
19505
  var remove4 = new Command("delete").alias("rm").description("Delete a webhook").argument("<id>", "Webhook id").action(async function action70(id) {
19412
19506
  requireShopSecretKey(this);
@@ -19465,7 +19559,7 @@ var update5 = new Command("update").description("Update a webhook URL, events, o
19465
19559
  await updateWebhook(requireCredentials(), id, body5),
19466
19560
  "Failed to update webhook"
19467
19561
  );
19468
- output(this, { webhook: result }, () => printWebhooks([result]));
19562
+ output(this, result, () => printWebhooks([result]));
19469
19563
  });
19470
19564
  var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function action73(id) {
19471
19565
  requireShopSecretKey(this);
@@ -19660,10 +19754,7 @@ function readFamily(family, label) {
19660
19754
  fail(this, r.error ?? `Failed to list ${label}`, classifyStatus(r.status), r.errorDetails);
19661
19755
  output(
19662
19756
  this,
19663
- {
19664
- [family.replace("-", "_")]: r.data.data,
19665
- pagination: r.data.pagination
19666
- },
19757
+ r.data,
19667
19758
  () => printTable(
19668
19759
  ["ID", "NAME", "STATUS"],
19669
19760
  r.data.data.map((x) => [x.id, x.name ?? "-", x.status ?? "-"]),
@@ -19675,11 +19766,7 @@ function readFamily(family, label) {
19675
19766
  const r = await getConfigResource(requireCredentials(), family, id);
19676
19767
  if (!r.ok || !r.data)
19677
19768
  fail(this, r.error ?? "Failed to get resource", classifyStatus(r.status), r.errorDetails);
19678
- output(
19679
- this,
19680
- { [family === "assets" ? "asset" : "landing_page"]: r.data },
19681
- () => show(r.data)
19682
- );
19769
+ output(this, r.data, () => show(r.data));
19683
19770
  });
19684
19771
  return { list: list15, get: get10 };
19685
19772
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drip-apex/apex-cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "license": "MIT",
5
5
  "description": "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
6
6
  "private": false,
@@ -11,7 +11,9 @@ Start by asking the installed CLI for its live contract:
11
11
  apex schema --json
12
12
  ```
13
13
 
14
- Prefer JSON output. When stdout is piped, Apex emits JSON by default.
14
+ Prefer JSON output. When stdout is piped, Apex emits JSON by default. Stdout then
15
+ carries exactly one JSON document; `data` is the API response body as returned
16
+ (lists at `.data.data[]`), and API failures carry the API error body in `error.api`.
15
17
 
16
18
  ## Zero-Experiment Shop Bootstrap
17
19