@drip-apex/apex-cli 0.12.1 → 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,
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.1",
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;
@@ -7679,7 +7735,7 @@ function createHttpClient(creds, options) {
7679
7735
  }
7680
7736
  inspectVersion(response.headers);
7681
7737
  const json = await response.json().catch(() => null);
7682
- 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") } };
7683
7739
  return { ok: true, status: response.status, data: json };
7684
7740
  }
7685
7741
  return { request: (method, path24, body5, timeoutMs) => request2(method, path24, body5, timeoutMs), requestForm: (method, path24, body5) => request2(method, path24, body5, void 0, true) };
@@ -7792,10 +7848,14 @@ function sharedClient(creds) {
7792
7848
  return client;
7793
7849
  }
7794
7850
  async function request(creds, method, path24, body5, requestTimeoutMs) {
7795
- 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;
7796
7854
  }
7797
7855
  async function requestForm(creds, method, path24, body5) {
7798
- return sharedClient(creds).requestForm(method, path24, body5);
7856
+ const response = await sharedClient(creds).requestForm(method, path24, body5);
7857
+ recordApiResponse(response);
7858
+ return response;
7799
7859
  }
7800
7860
  function listExperiments(creds, opts) {
7801
7861
  const params = new URLSearchParams();
@@ -9841,7 +9901,8 @@ async function resolveExperiment(idOrSlug) {
9841
9901
  throw new CliError(
9842
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`,
9843
9903
  classifyStatus(res2.status),
9844
- res2.errorDetails
9904
+ res2.errorDetails,
9905
+ apiFailureOf(res2)
9845
9906
  );
9846
9907
  }
9847
9908
  const slug = idOrSlug.startsWith("slug:") ? idOrSlug.slice(5) : idOrSlug;
@@ -9850,7 +9911,8 @@ async function resolveExperiment(idOrSlug) {
9850
9911
  throw new CliError(
9851
9912
  res.error ?? `Experiment "${idOrSlug}" not found`,
9852
9913
  classifyStatus(res.status),
9853
- res.errorDetails
9914
+ res.errorDetails,
9915
+ apiFailureOf(res)
9854
9916
  );
9855
9917
  }
9856
9918
  var list = new Command("list").alias("ls").description(
@@ -9877,7 +9939,7 @@ var list = new Command("list").alias("ls").description(
9877
9939
  }
9878
9940
  spinner?.stop();
9879
9941
  const experiments = res.data.data;
9880
- output(this, { experiments, pagination: res.data.pagination }, () => {
9942
+ output(this, res.data, () => {
9881
9943
  if (experiments.length === 0) {
9882
9944
  console.log(source_default.dim("No experiments found."));
9883
9945
  return;
@@ -9929,11 +9991,7 @@ var get = new Command("get").description(
9929
9991
  exp.shop_id ?? creds.resolvedContext?.shopId,
9930
9992
  exp.id
9931
9993
  );
9932
- output(
9933
- this,
9934
- { experiment: exp, dashboard_url: dashboardUrl },
9935
- () => printExperiment(exp, dashboardUrl)
9936
- );
9994
+ output(this, { ...exp, dashboard_url: dashboardUrl }, () => printExperiment(exp, dashboardUrl));
9937
9995
  });
9938
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) {
9939
9997
  const experiment = await resolveExperiment(experimentRef);
@@ -9990,7 +10048,7 @@ var results = new Command("results").description("Read experiment results and ti
9990
10048
  spinner?.stop();
9991
10049
  output(
9992
10050
  this,
9993
- { experiment: exp, results: res.data },
10051
+ res.data,
9994
10052
  () => printExperimentResults(res.data, opts.currency)
9995
10053
  );
9996
10054
  } catch (error) {
@@ -10576,7 +10634,7 @@ var pause = new Command("pause").description("Pause a running experiment").argum
10576
10634
  res.errorDetails
10577
10635
  );
10578
10636
  }
10579
- output(this, { experiment: { id: exp.id, name: exp.name, status: "paused" } }, () => {
10637
+ output(this, res.data, () => {
10580
10638
  spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("paused")}`);
10581
10639
  });
10582
10640
  });
@@ -10598,7 +10656,7 @@ var complete = new Command("complete").description("Complete/stop an experiment"
10598
10656
  res.errorDetails
10599
10657
  );
10600
10658
  }
10601
- output(this, { experiment: { id: exp.id, name: exp.name, status: "completed" } }, () => {
10659
+ output(this, res.data, () => {
10602
10660
  spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("completed")}`);
10603
10661
  });
10604
10662
  });
@@ -10626,7 +10684,7 @@ var del = new Command("delete").alias("rm").description("Delete an experiment").
10626
10684
  });
10627
10685
  rl.close();
10628
10686
  if (answer.toLowerCase() !== "y") {
10629
- console.log("Aborted.");
10687
+ output(this, { aborted: true }, () => console.log("Aborted."));
10630
10688
  return;
10631
10689
  }
10632
10690
  }
@@ -10641,11 +10699,7 @@ var del = new Command("delete").alias("rm").description("Delete an experiment").
10641
10699
  res.errorDetails
10642
10700
  );
10643
10701
  }
10644
- output(
10645
- this,
10646
- { experiment: { id: exp.id, name: exp.name }, deleted: true },
10647
- () => spinner.succeed(`Experiment "${exp.name}" deleted.`)
10648
- );
10702
+ output(this, res.data, () => spinner.succeed(`Experiment "${exp.name}" deleted.`));
10649
10703
  });
10650
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) {
10651
10705
  const creds = requireCredentials();
@@ -10814,7 +10868,7 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
10814
10868
  );
10815
10869
  }
10816
10870
  if (!ok) {
10817
- console.log("Aborted.");
10871
+ output(this, { aborted: true }, () => console.log("Aborted."));
10818
10872
  return;
10819
10873
  }
10820
10874
  }
@@ -10920,7 +10974,7 @@ var qaStatus = new Command("status").description(
10920
10974
  resolved.folder,
10921
10975
  resolved.experimentId
10922
10976
  );
10923
- output(this, { run: res.data.run }, () => printQaRun(res.data.run));
10977
+ output(this, res.data, () => printQaRun(res.data.run));
10924
10978
  } catch (error) {
10925
10979
  spinner.fail("Failed to fetch QA run");
10926
10980
  throw error;
@@ -10962,7 +11016,7 @@ var qaCheck = new Command("check").description("Update one QA checklist item wit
10962
11016
  resolved.folder,
10963
11017
  resolved.experimentId
10964
11018
  );
10965
- 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;
10966
11020
  output(this, data, () => {
10967
11021
  spinner.succeed("Updated QA checklist");
10968
11022
  if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
@@ -11011,7 +11065,7 @@ var qaNotes = new Command("notes").description("Update QA run notes or acceptanc
11011
11065
  const data = opts.summary ? qaSummary(run3, {
11012
11066
  notes: run3.notes,
11013
11067
  acceptanceCriteriaSummary: run3.acceptanceCriteriaSummary
11014
- }) : { run: run3 };
11068
+ }) : res.data;
11015
11069
  output(this, data, () => {
11016
11070
  spinner.succeed("Updated QA notes");
11017
11071
  if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
@@ -11092,11 +11146,7 @@ var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled
11092
11146
  resolved.folder,
11093
11147
  resolved.experimentId
11094
11148
  );
11095
- output(
11096
- this,
11097
- { experimentId: resolved.experimentId, qaStatus: "open" },
11098
- () => spinner.succeed(`QA reopened for ${resolved.label}`)
11099
- );
11149
+ output(this, res.data, () => spinner.succeed(`QA reopened for ${resolved.label}`));
11100
11150
  } catch (error) {
11101
11151
  spinner.fail("Failed to reopen QA");
11102
11152
  throw error;
@@ -11203,7 +11253,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
11203
11253
  const data = opts.summary ? qaSummary(
11204
11254
  run3,
11205
11255
  run3.evidenceRecords.filter((record2) => !previousIds.has(record2.id))
11206
- ) : { run: run3 };
11256
+ ) : response.data;
11207
11257
  output(this, data, () => {
11208
11258
  if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
11209
11259
  `);
@@ -12057,7 +12107,10 @@ function cleanupResetJs(experiment) {
12057
12107
  function runAbHarness(args, cwd) {
12058
12108
  const bin = process.env.AB_HARNESS_BIN || "ab-harness";
12059
12109
  return new Promise((resolve2, reject) => {
12060
- 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
+ });
12061
12114
  child.on("error", reject);
12062
12115
  child.on("exit", (code) => {
12063
12116
  if (code === 0) resolve2();
@@ -13066,29 +13119,36 @@ function printManifestPlan(manifest) {
13066
13119
  var trackingCommand = new Command("tracking").description(
13067
13120
  "Create Apex-first tracking ingestion manifests and QA handoffs"
13068
13121
  );
13069
- 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) {
13070
13123
  const manifest = buildManifest(urls, opts);
13071
13124
  const outPath = manifestPath(opts.out);
13072
13125
  writeManifest(outPath, manifest);
13073
- printSuccess(`Wrote ${outPath}`);
13074
- if (manifest.recommendedCookieDomain) {
13075
- console.log(`Recommended cookie domain: ${source_default.cyan(manifest.recommendedCookieDomain)}`);
13076
- }
13077
- if (manifest.snippet.html) {
13078
- console.log(`Head snippet: ${source_default.cyan(manifest.snippet.html)}`);
13079
- } else {
13126
+ if (!manifest.snippet.html)
13080
13127
  printWarning("No shop ID provided, so the manifest has no install snippet.");
13081
- }
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
+ });
13082
13137
  });
13083
- trackingCommand.command("plan").description("Print the implementation plan from a tracking manifest").argument("[manifest]", "Tracking manifest path", defaultManifestPath()).action((input) => {
13084
- 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));
13085
13142
  });
13086
- trackingCommand.command("qa").description("Print the QA checklist for a tracking manifest").argument("[manifest]", "Tracking manifest path", defaultManifestPath()).action((input) => {
13087
- const manifest = readManifest(manifestPath(input));
13088
- console.log(source_default.bold("Apex tracking QA"));
13089
- console.log();
13090
- manifest.qa.checks.forEach((check, index) => {
13091
- 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
+ });
13092
13152
  });
13093
13153
  });
13094
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) {
@@ -13107,7 +13167,7 @@ trackingCommand.command("verify").description("Verify live tracking events for t
13107
13167
  res.errorDetails
13108
13168
  );
13109
13169
  }
13110
- output(this, { verification: res.data }, () => {
13170
+ output(this, res.data, () => {
13111
13171
  const totals = res.data.totals;
13112
13172
  console.log(source_default.bold("Apex tracking verification"));
13113
13173
  console.log();
@@ -13412,7 +13472,7 @@ var list2 = new Command("list").alias("ls").description("List goals").option("-n
13412
13472
  if (!res.ok || !res.data) {
13413
13473
  fail(this, res.error ?? "Failed to list goals", classifyStatus(res.status), res.errorDetails);
13414
13474
  }
13415
- output(this, { goals: res.data.data, pagination: res.data.pagination }, () => {
13475
+ output(this, res.data, () => {
13416
13476
  if (res.data.data.length === 0) {
13417
13477
  console.log(source_default.dim("No goals found."));
13418
13478
  return;
@@ -13454,7 +13514,7 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
13454
13514
  res.errorDetails
13455
13515
  );
13456
13516
  }
13457
- output(this, { goal: res.data }, () => printGoal(res.data));
13517
+ output(this, res.data, () => printGoal(res.data));
13458
13518
  });
13459
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(
13460
13520
  "--value-property <property>",
@@ -13486,7 +13546,7 @@ var update2 = new Command("update").description("Update a goal").argument("<id>"
13486
13546
  res.errorDetails
13487
13547
  );
13488
13548
  }
13489
- output(this, { goal: res.data }, () => printGoal(res.data));
13549
+ output(this, res.data, () => printGoal(res.data));
13490
13550
  });
13491
13551
  var test = new Command("test").description("Dry run a goal URL pattern against traffic").option(
13492
13552
  "--pattern <regex>",
@@ -13583,9 +13643,8 @@ var suggest = new Command("suggest").description("Suggest goals and funnels from
13583
13643
  classifyStatus(res.status),
13584
13644
  res.errorDetails
13585
13645
  );
13586
- output(this, res.data, () => printSuggestions(res.data));
13587
- if (!opts.create) return;
13588
- for (const goal of res.data.goals) {
13646
+ if (!shouldUseJson(this)) printSuggestions(res.data);
13647
+ for (const goal of opts.create ? res.data.goals : []) {
13589
13648
  console.error(`Would create ${goal.type} goal: ${goal.name}`);
13590
13649
  if (!opts.yes) {
13591
13650
  if (!process.stdin.isTTY || !process.stdout.isTTY)
@@ -13620,6 +13679,7 @@ var suggest = new Command("suggest").description("Suggest goals and funnels from
13620
13679
  created.errorDetails
13621
13680
  );
13622
13681
  }
13682
+ output(this, res.data, () => void 0);
13623
13683
  });
13624
13684
  var goalsCommand = new Command("goals").description("Manage conversion and revenue goals").addCommand(list2).addCommand(create2).addCommand(update2).addCommand(test).addCommand(suggest).addHelpText(
13625
13685
  "after",
@@ -13674,7 +13734,7 @@ var list3 = new Command("list").alias("ls").description("List funnels").action(a
13674
13734
  );
13675
13735
  output(
13676
13736
  this,
13677
- { funnels: res.data.data },
13737
+ res.data,
13678
13738
  () => printTable(
13679
13739
  ["NAME", "ID", "STEPS"],
13680
13740
  res.data.data.map((f) => [f.name.slice(0, 36), f.id, String(f.steps.length)]),
@@ -13694,7 +13754,7 @@ var create3 = new Command("create").description("Create a funnel").requiredOptio
13694
13754
  classifyStatus(res.status),
13695
13755
  res.errorDetails
13696
13756
  );
13697
- output(this, { funnel: res.data }, () => printFunnel(res.data));
13757
+ output(this, res.data, () => printFunnel(res.data));
13698
13758
  });
13699
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) {
13700
13760
  const res = await updateFunnel(requireCredentials(), id, {
@@ -13707,7 +13767,7 @@ var update3 = new Command("update").description("Replace a funnel's steps").argu
13707
13767
  classifyStatus(res.status),
13708
13768
  res.errorDetails
13709
13769
  );
13710
- output(this, { funnel: res.data }, () => printFunnel(res.data));
13770
+ output(this, res.data, () => printFunnel(res.data));
13711
13771
  });
13712
13772
  var del2 = new Command("delete").description("Delete a funnel").argument("<id>", "Funnel ID").action(async function action42(id) {
13713
13773
  const res = await deleteFunnel(requireCredentials(), id);
@@ -13718,7 +13778,7 @@ var del2 = new Command("delete").description("Delete a funnel").argument("<id>",
13718
13778
  classifyStatus(res.status),
13719
13779
  res.errorDetails
13720
13780
  );
13721
- output(this, { deleted: true, id }, () => console.log(`Deleted funnel ${id}`));
13781
+ output(this, res.data ?? null, () => console.log(`Deleted funnel ${id}`));
13722
13782
  });
13723
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) {
13724
13784
  if (!/^[1-9]\d*$/.test(opts.days))
@@ -13731,7 +13791,7 @@ var run = new Command("run").description("Run funnel analysis").argument("<id>",
13731
13791
  fail(this, res.error ?? "Failed to run funnel", classifyStatus(res.status), res.errorDetails);
13732
13792
  output(
13733
13793
  this,
13734
- { run: res.data },
13794
+ res.data,
13735
13795
  () => printTable(
13736
13796
  ["STEP", "LABEL", "VISITORS", "DROP-OFF"],
13737
13797
  res.data.steps.map((s) => [
@@ -13806,8 +13866,8 @@ async function load2(command) {
13806
13866
  return result.data;
13807
13867
  }
13808
13868
  var list4 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function action45() {
13809
- const { environments } = await load2(this);
13810
- output(this, { environments }, () => printEnvironments(environments.items));
13869
+ const body5 = await load2(this);
13870
+ output(this, body5, () => printEnvironments(body5.environments.items));
13811
13871
  });
13812
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) {
13813
13873
  const normalizedDomain = normalizeEnvironmentDomain(domain);
@@ -13850,11 +13910,7 @@ var setDomain = new Command("set-domain").description("Map a domain to a runtime
13850
13910
  );
13851
13911
  }
13852
13912
  warnOnPrecompileError(this, result.data.precompile);
13853
- output(
13854
- this,
13855
- { environments: result.data.environments, precompile: result.data.precompile },
13856
- () => printEnvironments(result.data.environments.items)
13857
- );
13913
+ output(this, result.data, () => printEnvironments(result.data.environments.items));
13858
13914
  });
13859
13915
  var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function action47(domain) {
13860
13916
  const normalizedDomain = normalizeEnvironmentDomain(domain);
@@ -13885,11 +13941,7 @@ var removeDomain = new Command("remove-domain").description("Remove a domain map
13885
13941
  );
13886
13942
  }
13887
13943
  warnOnPrecompileError(this, result.data.precompile);
13888
- output(
13889
- this,
13890
- { environments: result.data.environments, precompile: result.data.precompile },
13891
- () => printEnvironments(result.data.environments.items)
13892
- );
13944
+ output(this, result.data, () => printEnvironments(result.data.environments.items));
13893
13945
  });
13894
13946
  var environmentsCommand = new Command("environments").description("Manage runtime environments and domain mappings").addCommand(list4).addCommand(setDomain).addCommand(removeDomain);
13895
13947
 
@@ -15766,7 +15818,9 @@ async function runDoctor(options = {}) {
15766
15818
  null,
15767
15819
  res.error ?? `Failed to load ${label}`,
15768
15820
  classifyStatus(res.status),
15769
- res.errorDetails
15821
+ res.errorDetails,
15822
+ // Parallel reads: name this response's envelope explicitly.
15823
+ apiFailureOf(res)
15770
15824
  );
15771
15825
  }
15772
15826
  for (const [label, res] of [
@@ -16127,7 +16181,7 @@ function analyticsSubcommand(name, description, defaultDays) {
16127
16181
  classifyStatus(result.status),
16128
16182
  result.errorDetails
16129
16183
  );
16130
- output(this, { data: result.data.data }, () => printAnalytics(result.data.data));
16184
+ output(this, result.data, () => printAnalytics(result.data.data));
16131
16185
  });
16132
16186
  }
16133
16187
  function urlsCommand() {
@@ -16964,7 +17018,7 @@ var list6 = new Command("list").alias("ls").description("List flags").option("-n
16964
17018
  fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
16965
17019
  output(
16966
17020
  this,
16967
- { flags: r.data.data, pagination: r.data.pagination },
17021
+ r.data,
16968
17022
  () => printTable(
16969
17023
  ["KEY/NAME", "STATE", "UPDATED"],
16970
17024
  r.data.data.map((x) => [x.key || x.name, x.status, x.updated_at ?? "-"]),
@@ -16976,7 +17030,7 @@ var get5 = new Command("get").description("Get a flag").argument("<id>").action(
16976
17030
  const r = await getManagedDelivery(requireCredentials(), kind, id);
16977
17031
  if (!r.ok || !r.data)
16978
17032
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
16979
- output(this, { flag: r.data }, () => detail(r.data));
17033
+ output(this, r.data, () => detail(r.data));
16980
17034
  });
16981
17035
  var status = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
16982
17036
  const r = await getFlagStatus(requireCredentials(), id);
@@ -17037,7 +17091,7 @@ function write(name) {
17037
17091
  const r = name === "create" ? await createManagedDelivery(requireCredentials(), kind, b) : await updateManagedDelivery(requireCredentials(), kind, id, b);
17038
17092
  if (!r.ok || !r.data)
17039
17093
  fail(this, r.error ?? `Failed to ${name}`, classifyStatus(r.status), r.errorDetails);
17040
- output(this, { flag: r.data }, () => detail(r.data));
17094
+ output(this, r.data, () => detail(r.data));
17041
17095
  });
17042
17096
  return c;
17043
17097
  }
@@ -17046,7 +17100,7 @@ function lifecycle(action74) {
17046
17100
  const r = await transitionManagedDelivery(requireCredentials(), kind, id, action74);
17047
17101
  if (!r.ok || !r.data)
17048
17102
  fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
17049
- output(this, { flag: r.data }, () => detail(r.data));
17103
+ output(this, r.data, () => detail(r.data));
17050
17104
  });
17051
17105
  }
17052
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(
@@ -17163,7 +17217,7 @@ var list7 = new Command("list").alias("ls").description("List backlog items iden
17163
17217
  fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
17164
17218
  output(
17165
17219
  this,
17166
- { backlog: r.data.data, pagination: r.data.pagination },
17220
+ r.data,
17167
17221
  () => printTable(
17168
17222
  ["ID", "NAME", "STATE"],
17169
17223
  r.data.data.map((x) => [
@@ -17180,7 +17234,7 @@ var get6 = new Command("get").description("Get a backlog item identified as apex
17180
17234
  if (!r.ok || !r.data)
17181
17235
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
17182
17236
  const labels = await fieldLabels(this, r.data);
17183
- output(this, { backlog_item: r.data }, () => detail2(r.data, labels));
17237
+ output(this, r.data, () => detail2(r.data, labels));
17184
17238
  });
17185
17239
  function write2(action74) {
17186
17240
  const c = new Command(action74).description(
@@ -17207,7 +17261,7 @@ function write2(action74) {
17207
17261
  if (!r.ok || !r.data)
17208
17262
  fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
17209
17263
  const labels = await fieldLabels(this, r.data);
17210
- output(this, { backlog_item: r.data }, () => detail2(r.data, labels));
17264
+ output(this, r.data, () => detail2(r.data, labels));
17211
17265
  });
17212
17266
  return c;
17213
17267
  }
@@ -17220,13 +17274,13 @@ var archive = new Command("archive").description("Archive a backlog item with a
17220
17274
  const r = await archiveBacklogInsight(requireCredentials(), id, opts.reason.trim());
17221
17275
  if (!r.ok || !r.data)
17222
17276
  fail(this, r.error ?? "Failed to archive", classifyStatus(r.status), r.errorDetails);
17223
- output(this, { archived: true, id }, () => console.log(`Archived ${id}`));
17277
+ output(this, r.data, () => console.log(`Archived ${id}`));
17224
17278
  });
17225
17279
  var restore = new Command("restore").description("Restore an archived backlog item").argument("<id>", "backlog item ID").action(async function(id) {
17226
17280
  const r = await restoreBacklogInsight(requireCredentials(), id);
17227
17281
  if (!r.ok || !r.data)
17228
17282
  fail(this, r.error ?? "Failed to restore", classifyStatus(r.status), r.errorDetails);
17229
- output(this, { restored: true, id }, () => console.log(`Restored ${id}`));
17283
+ output(this, r.data, () => console.log(`Restored ${id}`));
17230
17284
  });
17231
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) {
17232
17286
  if (!opts.force) {
@@ -17243,14 +17297,14 @@ var remove2 = new Command("delete").description("Delete a backlog item").argumen
17243
17297
  );
17244
17298
  rl.close();
17245
17299
  if (answer.toLowerCase() !== "y") {
17246
- console.log("Aborted.");
17300
+ output(this, { aborted: true }, () => console.log("Aborted."));
17247
17301
  return;
17248
17302
  }
17249
17303
  }
17250
17304
  const r = await deleteInsight(requireCredentials(), kind2, id);
17251
17305
  if (!r.ok || !r.data)
17252
17306
  fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
17253
- output(this, { deleted: true, id }, () => console.log(`Deleted ${id}`));
17307
+ output(this, r.data, () => console.log(`Deleted ${id}`));
17254
17308
  });
17255
17309
  function fieldDetail(field) {
17256
17310
  console.log();
@@ -17277,7 +17331,7 @@ var fieldsList = new Command("list").alias("ls").description("List the shop's id
17277
17331
  fail(this, r.error ?? "Failed to list fields", classifyStatus(r.status), r.errorDetails);
17278
17332
  output(
17279
17333
  this,
17280
- { fields: r.data.data },
17334
+ r.data,
17281
17335
  () => printTable(
17282
17336
  ["KEY", "LABEL", "TYPE", "REQUIRED", "CLIENT"],
17283
17337
  r.data.data.map((field) => [
@@ -17312,7 +17366,7 @@ var fieldsCreate = new Command("create").description("Add an idea field").requir
17312
17366
  });
17313
17367
  if (!r.ok || !r.data)
17314
17368
  fail(this, r.error ?? "Failed to create field", classifyStatus(r.status), r.errorDetails);
17315
- output(this, { field: r.data }, () => fieldDetail(r.data));
17369
+ output(this, r.data, () => fieldDetail(r.data));
17316
17370
  });
17317
17371
  var fieldsUpdate = new Command("update").description("Update an idea field").argument("<id>", "field ID").option("--label <text>", "What the field is called").option(
17318
17372
  "--option <value>",
@@ -17335,13 +17389,13 @@ var fieldsUpdate = new Command("update").description("Update an idea field").arg
17335
17389
  const r = await updateBacklogField(requireCredentials(), id, body5);
17336
17390
  if (!r.ok || !r.data)
17337
17391
  fail(this, r.error ?? "Failed to update field", classifyStatus(r.status), r.errorDetails);
17338
- output(this, { field: r.data }, () => fieldDetail(r.data));
17392
+ output(this, r.data, () => fieldDetail(r.data));
17339
17393
  });
17340
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) {
17341
17395
  const r = await archiveBacklogField(requireCredentials(), id);
17342
17396
  if (!r.ok || !r.data)
17343
17397
  fail(this, r.error ?? "Failed to archive field", classifyStatus(r.status), r.errorDetails);
17344
- output(this, { field: r.data }, () => fieldDetail(r.data));
17398
+ output(this, r.data, () => fieldDetail(r.data));
17345
17399
  });
17346
17400
  var fields2 = new Command("fields").description("Manage the shop's own idea fields").addCommand(fieldsList).addCommand(fieldsCreate).addCommand(fieldsUpdate).addCommand(fieldsArchive);
17347
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);
@@ -17388,7 +17442,7 @@ var list8 = new Command("list").alias("ls").description("List signals").option("
17388
17442
  fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
17389
17443
  output(
17390
17444
  this,
17391
- { signals: r.data.data, pagination: r.data.pagination },
17445
+ r.data,
17392
17446
  () => printTable(
17393
17447
  ["ID", "NAME", "STATE"],
17394
17448
  r.data.data.map((x) => [
@@ -17404,7 +17458,7 @@ var get7 = new Command("get").description("Get a signal").argument("<id>", "sign
17404
17458
  const r = await getInsight(requireCredentials(), kind3, id);
17405
17459
  if (!r.ok || !r.data)
17406
17460
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
17407
- output(this, { signal: r.data }, () => detail3(r.data));
17461
+ output(this, r.data, () => detail3(r.data));
17408
17462
  });
17409
17463
  function write3(action74) {
17410
17464
  const c = new Command(action74).description(
@@ -17424,7 +17478,7 @@ function write3(action74) {
17424
17478
  const r = action74 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
17425
17479
  if (!r.ok || !r.data)
17426
17480
  fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
17427
- output(this, { signal: r.data }, () => detail3(r.data));
17481
+ output(this, r.data, () => detail3(r.data));
17428
17482
  });
17429
17483
  return c;
17430
17484
  }
@@ -17432,7 +17486,7 @@ var remove3 = new Command("delete").alias("archive").description("Delete or arch
17432
17486
  const r = await deleteInsight(requireCredentials(), kind3, id);
17433
17487
  if (!r.ok || !r.data)
17434
17488
  fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
17435
- output(this, { deleted: true, id }, () => console.log(`Deleted ${id}`));
17489
+ output(this, r.data, () => console.log(`Deleted ${id}`));
17436
17490
  });
17437
17491
  var signalsCommand = new Command("signals").description("Manage signals").addCommand(list8).addCommand(get7).addCommand(write3("create")).addCommand(write3("update")).addCommand(remove3);
17438
17492
 
@@ -17481,7 +17535,7 @@ var list9 = new Command("list").alias("ls").description("List personalizations")
17481
17535
  fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
17482
17536
  output(
17483
17537
  this,
17484
- { personalizations: r.data.data, pagination: r.data.pagination },
17538
+ r.data,
17485
17539
  () => printTable(
17486
17540
  ["KEY/NAME", "STATE", "UPDATED"],
17487
17541
  r.data.data.map((x) => [x.key || x.name, x.status, x.updated_at ?? "-"]),
@@ -17493,7 +17547,7 @@ var get8 = new Command("get").description("Get a personalization").argument("<id
17493
17547
  const r = await getManagedDelivery(requireCredentials(), kind4, id);
17494
17548
  if (!r.ok || !r.data)
17495
17549
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
17496
- output(this, { personalization: r.data }, () => detail4(r.data));
17550
+ output(this, r.data, () => detail4(r.data));
17497
17551
  });
17498
17552
  function write4(name) {
17499
17553
  const c = new Command(name).description(
@@ -17511,7 +17565,7 @@ function write4(name) {
17511
17565
  const r = name === "create" ? await createManagedDelivery(requireCredentials(), kind4, b) : await updateManagedDelivery(requireCredentials(), kind4, id, b);
17512
17566
  if (!r.ok || !r.data)
17513
17567
  fail(this, r.error ?? `Failed to ${name}`, classifyStatus(r.status), r.errorDetails);
17514
- output(this, { personalization: r.data }, () => detail4(r.data));
17568
+ output(this, r.data, () => detail4(r.data));
17515
17569
  });
17516
17570
  return c;
17517
17571
  }
@@ -17520,7 +17574,7 @@ function lifecycle2(action74) {
17520
17574
  const r = await transitionManagedDelivery(requireCredentials(), kind4, id, action74);
17521
17575
  if (!r.ok || !r.data)
17522
17576
  fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
17523
- output(this, { personalization: r.data }, () => detail4(r.data));
17577
+ output(this, r.data, () => detail4(r.data));
17524
17578
  });
17525
17579
  }
17526
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"));
@@ -17570,7 +17624,7 @@ function targetingResourceCommand(kind5) {
17570
17624
  const page = response.data;
17571
17625
  output(
17572
17626
  this,
17573
- { [kind5.replace("-", "_")]: page.data, pagination: page.pagination },
17627
+ page,
17574
17628
  () => printTable(
17575
17629
  ["NAME", "ID", "UPDATED"],
17576
17630
  page.data.map((item) => [
@@ -17596,7 +17650,7 @@ function targetingResourceCommand(kind5) {
17596
17650
  );
17597
17651
  output(
17598
17652
  this,
17599
- { exclusion_group: response.data },
17653
+ response.data,
17600
17654
  () => console.log(source_default.bold(response.data.name ?? id))
17601
17655
  );
17602
17656
  })
@@ -17627,13 +17681,7 @@ function targetingResourceCommand(kind5) {
17627
17681
  classifyStatus(response.status),
17628
17682
  response.errorDetails
17629
17683
  );
17630
- output(
17631
- this,
17632
- {
17633
- [kind5 === "exclusion-groups" ? "exclusion_group" : singular3]: response.data
17634
- },
17635
- () => console.log(source_default.green(`${action74}d ${singular3}`))
17636
- );
17684
+ output(this, response.data, () => console.log(source_default.green(`${action74}d ${singular3}`)));
17637
17685
  });
17638
17686
  root.addCommand(command);
17639
17687
  }
@@ -17658,7 +17706,7 @@ function targetingResourceCommand(kind5) {
17658
17706
  });
17659
17707
  rl.close();
17660
17708
  if (answer.toLowerCase() !== "y") {
17661
- console.log("Aborted.");
17709
+ output(this, { aborted: true }, () => console.log("Aborted."));
17662
17710
  return;
17663
17711
  }
17664
17712
  }
@@ -17670,7 +17718,7 @@ function targetingResourceCommand(kind5) {
17670
17718
  classifyStatus(response.status),
17671
17719
  response.errorDetails
17672
17720
  );
17673
- 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}`)));
17674
17722
  })
17675
17723
  );
17676
17724
  if (kind5 === "exclusion-groups")
@@ -17690,11 +17738,7 @@ function targetingResourceCommand(kind5) {
17690
17738
  classifyStatus(response.status),
17691
17739
  response.errorDetails
17692
17740
  );
17693
- output(
17694
- this,
17695
- { exclusion_group: response.data },
17696
- () => console.log(source_default.green("Replaced members"))
17697
- );
17741
+ output(this, response.data, () => console.log(source_default.green("Replaced members")));
17698
17742
  })
17699
17743
  );
17700
17744
  return root;
@@ -17710,7 +17754,9 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
17710
17754
  const creds = requireCredentials();
17711
17755
  const res = await getBrandContext(creds);
17712
17756
  if (res.status === 404) {
17713
- output(this, { brandContext: null, message: NO_PROFILE_MESSAGE }, () => {
17757
+ if (shouldUseJson(this)) process.stderr.write(`${NO_PROFILE_MESSAGE}
17758
+ `);
17759
+ output(this, { data: null }, () => {
17714
17760
  console.log(source_default.dim(NO_PROFILE_MESSAGE));
17715
17761
  });
17716
17762
  return;
@@ -17724,7 +17770,7 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
17724
17770
  );
17725
17771
  }
17726
17772
  const brandContext = res.data.data;
17727
- output(this, { brandContext }, () => {
17773
+ output(this, res.data, () => {
17728
17774
  if (!brandContext.hasProfile) {
17729
17775
  console.log(source_default.dim(NO_PROFILE_MESSAGE));
17730
17776
  return;
@@ -17750,7 +17796,7 @@ var test2 = new Command("test").description("Evaluate experiment URL targeting f
17750
17796
  res.errorDetails
17751
17797
  );
17752
17798
  }
17753
- output(this, { targeting: res.data }, () => {
17799
+ output(this, res.data, () => {
17754
17800
  console.log(source_default.bold("Apex targeting test"));
17755
17801
  console.log();
17756
17802
  console.log(`URL: ${source_default.cyan(res.data.url)}`);
@@ -17824,7 +17870,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
17824
17870
  "--type <type>",
17825
17871
  "full_handover|design|development|qa|config|bug|help (internal types require a DRIP account)",
17826
17872
  "qa"
17827
- ).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) {
17828
17874
  if (!SOS_TYPES.has(opts.type)) {
17829
17875
  fail(
17830
17876
  this,
@@ -17832,12 +17878,16 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
17832
17878
  3
17833
17879
  );
17834
17880
  }
17881
+ if (opts.experiment && opts.backlogItem) {
17882
+ fail(this, "Provide only one of --experiment or --backlog-item", 3);
17883
+ }
17835
17884
  const creds = requireCredentials();
17836
17885
  const description = readText(opts.description) ?? descriptionArg ?? `Apex CLI ${opts.type} SOS request${opts.loom ? ` with Loom evidence: ${opts.loom}` : "."}`;
17837
17886
  const res = await createSos(creds, {
17838
17887
  type: opts.type,
17839
17888
  description,
17840
17889
  experiment_id: opts.experiment ?? null,
17890
+ backlog_item_id: opts.backlogItem ?? null,
17841
17891
  sub_class: opts.subClass ?? null,
17842
17892
  loom_url: opts.loom ?? null
17843
17893
  });
@@ -17862,7 +17912,7 @@ var mine = new Command("mine").description("List SOS tickets created by this API
17862
17912
  res.errorDetails
17863
17913
  );
17864
17914
  }
17865
- output(this, { tickets: res.data.tickets }, () => {
17915
+ output(this, res.data, () => {
17866
17916
  if (res.data.tickets.length === 0) {
17867
17917
  console.log(source_default.dim("No SOS tickets found for this API key."));
17868
17918
  return;
@@ -17963,7 +18013,7 @@ var list12 = new Command("list").alias("ls").description("List organization shop
17963
18013
  fail(this, r.error ?? "Failed to list shops", classifyStatus(r.status), r.errorDetails);
17964
18014
  output(
17965
18015
  this,
17966
- { shops: r.data.data },
18016
+ r.data,
17967
18017
  () => printTable(
17968
18018
  ["ID", "NAME", "DOMAIN", "TIER"],
17969
18019
  r.data.data.map((s) => [s.id, s.name ?? "-", s.domain ?? "-", s.tier ?? "-"]),
@@ -18741,7 +18791,7 @@ var discoverCommand = new Command("discover").description("Read back the trackin
18741
18791
  created.errorDetails
18742
18792
  );
18743
18793
  }
18744
- output(this, { signal: created.data }, () => {
18794
+ output(this, created.data, () => {
18745
18795
  process.stdout.write(`Created dimension ${created.data.id}.
18746
18796
  `);
18747
18797
  process.stdout.write("Next: open Audience > Attributes in the Apex dashboard.\n");
@@ -18893,7 +18943,10 @@ async function confirmArchive(command, shopId, force) {
18893
18943
  return true;
18894
18944
  }
18895
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) {
18896
- 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
+ }
18897
18950
  const res = await archiveAdminShop(requireUserCredentials(), shopId);
18898
18951
  if (!res.ok || !res.data)
18899
18952
  fail(
@@ -19137,7 +19190,9 @@ var create6 = new Command("create").requiredOption("--name <name>").requiredOpti
19137
19190
  classifyApiError(res.status, res.errorDetails),
19138
19191
  res.errorDetails
19139
19192
  );
19140
- 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, () => {
19141
19196
  console.error("Warning: this secret will not be shown again.");
19142
19197
  console.log(res.data.data.key);
19143
19198
  });
@@ -19434,21 +19489,18 @@ var create7 = new Command("create").description(`Create a webhook. Prints the si
19434
19489
  await createWebhook(requireCredentials(), { url: opts.url, events }),
19435
19490
  "Failed to create webhook"
19436
19491
  );
19437
- output(
19438
- this,
19439
- {
19440
- webhook: result,
19441
- secret: result.secret,
19442
- signatureDoc: SIGNATURE_DOC,
19443
- warning: "Store this signing secret now. Apex will not show it again."
19444
- },
19445
- () => {
19446
- console.log(source_default.green(`Created webhook ${result.id}`));
19447
- console.log(source_default.yellow("Store this signing secret now. Apex will not show it again."));
19448
- console.log(result.secret);
19449
- console.log(`Signature verification: ${SIGNATURE_DOC}`);
19450
- }
19451
- );
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
+ });
19452
19504
  });
19453
19505
  var remove4 = new Command("delete").alias("rm").description("Delete a webhook").argument("<id>", "Webhook id").action(async function action70(id) {
19454
19506
  requireShopSecretKey(this);
@@ -19507,7 +19559,7 @@ var update5 = new Command("update").description("Update a webhook URL, events, o
19507
19559
  await updateWebhook(requireCredentials(), id, body5),
19508
19560
  "Failed to update webhook"
19509
19561
  );
19510
- output(this, { webhook: result }, () => printWebhooks([result]));
19562
+ output(this, result, () => printWebhooks([result]));
19511
19563
  });
19512
19564
  var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function action73(id) {
19513
19565
  requireShopSecretKey(this);
@@ -19702,10 +19754,7 @@ function readFamily(family, label) {
19702
19754
  fail(this, r.error ?? `Failed to list ${label}`, classifyStatus(r.status), r.errorDetails);
19703
19755
  output(
19704
19756
  this,
19705
- {
19706
- [family.replace("-", "_")]: r.data.data,
19707
- pagination: r.data.pagination
19708
- },
19757
+ r.data,
19709
19758
  () => printTable(
19710
19759
  ["ID", "NAME", "STATUS"],
19711
19760
  r.data.data.map((x) => [x.id, x.name ?? "-", x.status ?? "-"]),
@@ -19717,11 +19766,7 @@ function readFamily(family, label) {
19717
19766
  const r = await getConfigResource(requireCredentials(), family, id);
19718
19767
  if (!r.ok || !r.data)
19719
19768
  fail(this, r.error ?? "Failed to get resource", classifyStatus(r.status), r.errorDetails);
19720
- output(
19721
- this,
19722
- { [family === "assets" ? "asset" : "landing_page"]: r.data },
19723
- () => show(r.data)
19724
- );
19769
+ output(this, r.data, () => show(r.data));
19725
19770
  });
19726
19771
  return { list: list15, get: get10 };
19727
19772
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drip-apex/apex-cli",
3
- "version": "0.12.1",
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