@drip-apex/apex-cli 0.8.0 → 0.9.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.
Files changed (3) hide show
  1. package/README.md +15 -0
  2. package/dist/cli.js +35 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -313,3 +313,18 @@ Create/get return `dashboard_url`; apply returns `dashboardUrl`. These links
313
313
  switch the signed-in dashboard user's active shop before opening the experiment.
314
314
 
315
315
  Filesystem apply caches uploaded asset URLs by byte SHA-256 in `.apex/experiment-filesystem.json`, so unchanged images reuse their URL across applies without rewriting authored files.
316
+
317
+ `--instructions` cannot be combined with `--out` (or its legacy alias `--output`); file exports always contain the full JSON contract.
318
+
319
+ Roadmap handoff JSON identifies itself as `apex.roadmap_handoff.v1`; backlog get/list
320
+ items identify themselves as `apex.backlog_item.v1`, both with `schema_version: 1`.
321
+ The handoff embeds the same closed item projection that backlog responses serve
322
+ alongside their existing raw fields. Published schemas and fixtures live in
323
+ [`docs/contracts/experiment-os`](../../docs/contracts/experiment-os/README.md).
324
+ Breaking changes require a new v2 contract; the backlog `version` is a separate
325
+ optimistic concurrency token.
326
+
327
+ | Key | Source |
328
+ | --- | --- |
329
+ | `approval` | Roadmap decision status (`proposed` without a decision), item stage, and decision's nullable `decided_at`. Top-level `status` remains the item lifecycle status. |
330
+ | `goals` | `apex_goal_id` is the linked experiment goal or null. |
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.8.0",
7115
+ version: "0.9.0",
7116
7116
  license: "MIT",
7117
7117
  description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
7118
7118
  private: false,
@@ -8969,6 +8969,16 @@ function buildQaPreviewUrl2(storeUrl, experimentId, variationId) {
8969
8969
  return url;
8970
8970
  }
8971
8971
 
8972
+ // src/lib/device-label.ts
8973
+ function deviceLabel(targeting) {
8974
+ if (!targeting || typeof targeting !== "object") return void 0;
8975
+ const device = targeting.deviceType;
8976
+ if (device === "desktop") return "Desktop only";
8977
+ if (device === "mobile") return "Mobile only";
8978
+ if (device === "tablet") return "Tablet only";
8979
+ return void 0;
8980
+ }
8981
+
8972
8982
  // src/lib/test-key.ts
8973
8983
  var TEST_KEY_RE = /^([A-Z0-9]{2,8})-(\d+)$/;
8974
8984
  function isTestKeyReference(value) {
@@ -9279,8 +9289,13 @@ function printExperiment(exp, dashboardUrl = null) {
9279
9289
  console.log(` Status: ${statusBadge(exp.status)}`);
9280
9290
  console.log(` Classification: ${exp.counting_type}`);
9281
9291
  console.log(` Traffic: ${(exp.traffic_allocation * 100).toFixed(0)}%`);
9292
+ console.log(` QA approved: ${exp.qa_approved_at ?? "none"}`);
9293
+ console.log(` Scheduled: ${exp.scheduled_start_at ?? "none"}`);
9294
+ console.log(` Started: ${exp.started_at ?? "none"}`);
9282
9295
  if (exp.targeting !== void 0) {
9283
9296
  console.log(` Targeting: ${effectivePageTargetingLabel(exp.targeting)}`);
9297
+ const device = deviceLabel(exp.targeting);
9298
+ if (device) console.log(` Device: ${device}`);
9284
9299
  }
9285
9300
  console.log(` Created: ${formatDate(exp.created_at)}`);
9286
9301
  if (exp.linked_idea) {
@@ -9560,6 +9575,9 @@ function printFilesystemPlan(plan) {
9560
9575
  console.log(source_default.dim(" No changes."));
9561
9576
  } else {
9562
9577
  plan.changes.forEach((change) => {
9578
+ if (change.entity === "experiment.targeting" && Array.isArray(change.detail?.changedKeys)) {
9579
+ console.log(` Changed keys: ${change.detail.changedKeys.join(", ")}`);
9580
+ }
9563
9581
  const pathLabel = change.path ? source_default.dim(` ${change.path}`) : "";
9564
9582
  const qa3 = change.invalidatesQa ? source_default.yellow(" invalidates QA") : "";
9565
9583
  console.log(
@@ -9826,7 +9844,9 @@ var list = new Command("list").alias("ls").description("List experiments (archiv
9826
9844
  }
9827
9845
  });
9828
9846
  });
9829
- var get = new Command("get").description("Get experiment details by ID or slug").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).action(async function action4(idOrSlug) {
9847
+ var get = new Command("get").description(
9848
+ "Get experiment details by ID or slug. JSON readback always carries started_at, scheduled_start_at and qa_approved_at as ISO-8601 UTC timestamps or null (all null on a fresh draft)."
9849
+ ).argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).action(async function action4(idOrSlug) {
9830
9850
  const spinner = await createSpinner("Fetching experiment...");
9831
9851
  const exp = await resolveExperiment(idOrSlug);
9832
9852
  spinner?.stop();
@@ -10140,7 +10160,9 @@ ${rows.map((row, index) => ` ${index + 1}. ${row.name} (${row.id})`).join("\n")
10140
10160
  `
10141
10161
  );
10142
10162
  }
10143
- var create = new Command("create").description("Create a new experiment").addOption(
10163
+ var create = new Command("create").description(
10164
+ "Create a complete experiment with page targeting, audiences, goals, and an optional QA preview URL. JSON readback always carries started_at, scheduled_start_at and qa_approved_at as ISO-8601 UTC timestamps or null (all null on a fresh draft)."
10165
+ ).addOption(
10144
10166
  new Option("--counting-type <type>", "Experiment classification").choices(countingTypes).default("standard")
10145
10167
  ).option("--name <name>", "Experiment name").option("--slug <slug>", "URL-safe slug for GitOps").option("--description <desc>", "Description").option("--type <type>", "Experiment type: mutation or redirect", "mutation").option(
10146
10168
  "--source-url <pattern>",
@@ -10963,6 +10985,7 @@ var qaApprove = new Command("approve").description("Approve QA and move the expe
10963
10985
  {
10964
10986
  experimentId: resolved.experimentId,
10965
10987
  qaStatus: "done",
10988
+ experiment: res.data,
10966
10989
  experiment_status_before: before.status,
10967
10990
  experiment_status_after: res.data.status,
10968
10991
  transition
@@ -12767,7 +12790,11 @@ More results: --cursor ${res.data.pagination.next_cursor}`));
12767
12790
  }
12768
12791
  );
12769
12792
  });
12770
- var handoff = new Command("handoff").description("Get the full build brief for an approved roadmap item").argument("<id>", "Roadmap/backlog item ID").option("--out <path>", "Write handoff JSON to a file").addOption(new Option("-o, --output <path>", "Legacy alias for --out").hideHelp()).option("--instructions", "Print only the build instructions").action(async function action32(id, opts) {
12793
+ var handoff = new Command("handoff").description(
12794
+ "Get apex.roadmap_handoff.v1 JSON: contract, schema_version, item, experiment, instructions, goals, mockups, acceptance_criteria, harness, scoring, source, metadata"
12795
+ ).argument("<id>", "Roadmap/backlog item ID").option("--out <path>", "Write handoff JSON to a file").addOption(new Option("-o, --output <path>", "Legacy alias for --out").hideHelp()).addOption(
12796
+ new Option("--instructions", "Print only the build instructions").conflicts(["out", "output"])
12797
+ ).action(async function action32(id, opts) {
12771
12798
  if (opts.output !== void 0) {
12772
12799
  process.stderr.write(
12773
12800
  `Warning: --output is deprecated for apex roadmap handoff; use --out instead. --output will be removed in v${LEGACY_OUTPUT_REMOVAL_VERSION}.
@@ -12788,7 +12815,8 @@ var handoff = new Command("handoff").description("Get the full build brief for a
12788
12815
  classifyStatus(res.status),
12789
12816
  res.errorDetails
12790
12817
  );
12791
- const payload = res.data ?? {};
12818
+ const payload = res.data;
12819
+ if (!payload) fail(this, "Missing roadmap handoff response");
12792
12820
  const rendered = opts.instructions ? String(payload.instructions ?? "") : `${JSON.stringify(payload, null, 2)}
12793
12821
  `;
12794
12822
  const outputData = opts.instructions ? { roadmapItemId: id, instructions: String(payload.instructions ?? "") } : payload;
@@ -16988,7 +17016,7 @@ async function fieldLabels(command, item) {
16988
17016
  (fields3.ok && fields3.data ? fields3.data.data : []).map((field) => [field.key, field.label])
16989
17017
  );
16990
17018
  }
16991
- var list7 = new Command("list").alias("ls").description("List backlog").option("-n, --limit <n>", "Max results", "50").option("--cursor <cursor>", "Continue from cursor").option("--include-archived", "Include archived backlog items").action(async function(o) {
17019
+ var list7 = new Command("list").alias("ls").description("List backlog items identified as apex.backlog_item.v1").option("-n, --limit <n>", "Max results", "50").option("--cursor <cursor>", "Continue from cursor").option("--include-archived", "Include archived backlog items").action(async function(o) {
16992
17020
  const r = await listInsights(
16993
17021
  requireCredentials(),
16994
17022
  kind2,
@@ -17012,7 +17040,7 @@ var list7 = new Command("list").alias("ls").description("List backlog").option("
17012
17040
  )
17013
17041
  );
17014
17042
  });
17015
- var get6 = new Command("get").description("Get a backlog item").argument("<id>", "backlog item ID").action(async function(id) {
17043
+ var get6 = new Command("get").description("Get a backlog item identified as apex.backlog_item.v1").argument("<id>", "backlog item ID").action(async function(id) {
17016
17044
  const r = await getInsight(requireCredentials(), kind2, id);
17017
17045
  if (!r.ok || !r.data)
17018
17046
  fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drip-apex/apex-cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "license": "MIT",
5
5
  "description": "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
6
6
  "private": false,