@drip-apex/apex-cli 0.10.0 → 0.12.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/README.md +22 -0
- package/dist/cli.js +101 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,16 @@ apex schema --json
|
|
|
86
86
|
|
|
87
87
|
Use `apex` as the canonical command name. `drip` is a compatibility alias.
|
|
88
88
|
|
|
89
|
+
`apex experiments list` returns code-free summaries by default, including
|
|
90
|
+
`targeting_urls` and `variation_count`, without targeting objects, variations, or goals.
|
|
91
|
+
Use `--full --json` for full details, including code. `--full` keeps the default table unchanged.
|
|
92
|
+
Use `--fields id,test_key,name,status --json` to select top-level keys (JSON always keeps `id`),
|
|
93
|
+
or `--fields status,name` to print exactly those table columns in order.
|
|
94
|
+
`--fields` and `--full` can combine; invalid fields return the allowed keys.
|
|
95
|
+
Existing CR-011 filters still narrow the set; these flags only select row content.
|
|
96
|
+
The direct API retains its full default view; the CLI requests `view=summary`.
|
|
97
|
+
Summary timestamps include `created_at` and `archived_at`; experiments have no `updated_at` field.
|
|
98
|
+
|
|
89
99
|
`apex experiments list` hides archived experiments by default. Pass
|
|
90
100
|
`--include-archived` to include them; archived rows display their archive date
|
|
91
101
|
when the API provides `archived_at`.
|
|
@@ -332,3 +342,15 @@ optimistic concurrency token.
|
|
|
332
342
|
QA status creates a run only when none exists. Reading an existing run never writes
|
|
333
343
|
to it: `updatedAt` is its last stored mutation time, while `computedAt` is the ISO
|
|
334
344
|
timestamp when the current checklist and summary were computed.
|
|
345
|
+
|
|
346
|
+
### Decision latency
|
|
347
|
+
|
|
348
|
+
`apex tracking latency <experiment-id-or-key> [--days <1..30>] [--qa-session <id>] [--json]`
|
|
349
|
+
compares forced-preview and natural decision latency before launch. The default
|
|
350
|
+
window is seven days. The table shows sample counts, decision p50/p95/p99,
|
|
351
|
+
config-fetch p50/p95 (milliseconds), and inline/cache/network/none counts.
|
|
352
|
+
JSON output includes the API body in the standard envelope and assignment-ready
|
|
353
|
+
percentiles. Fewer than five measured samples have null percentiles. Natural
|
|
354
|
+
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.
|
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.
|
|
7115
|
+
version: "0.12.0",
|
|
7116
7116
|
license: "MIT",
|
|
7117
7117
|
description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
|
|
7118
7118
|
private: false,
|
|
@@ -7762,6 +7762,8 @@ function listExperiments(creds, opts) {
|
|
|
7762
7762
|
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
7763
7763
|
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
7764
7764
|
if (opts?.includeArchived) params.set("archived", "1");
|
|
7765
|
+
if (opts?.view) params.set("view", opts.view);
|
|
7766
|
+
if (opts?.fields !== void 0) params.set("fields", opts.fields);
|
|
7765
7767
|
const qs = params.toString();
|
|
7766
7768
|
return request(creds, "GET", `/api/v1/experiments${qs ? `?${qs}` : ""}`);
|
|
7767
7769
|
}
|
|
@@ -7983,6 +7985,11 @@ function trackingEventProperties(event) {
|
|
|
7983
7985
|
if (event.goal_id != null) properties.goalId = event.goal_id;
|
|
7984
7986
|
return properties;
|
|
7985
7987
|
}
|
|
7988
|
+
function getDecisionLatency(creds, experimentId, opts) {
|
|
7989
|
+
const params = new URLSearchParams({ experiment_id: experimentId, days: String(opts.days) });
|
|
7990
|
+
if (opts.qaSessionId) params.set("qa_session_id", opts.qaSessionId);
|
|
7991
|
+
return request(creds, "GET", `/api/v1/tracking/latency?${params}`);
|
|
7992
|
+
}
|
|
7986
7993
|
function verifyTracking(creds, opts) {
|
|
7987
7994
|
const params = new URLSearchParams();
|
|
7988
7995
|
if (opts?.minutes) params.set("minutes", String(opts.minutes));
|
|
@@ -9805,16 +9812,23 @@ async function resolveExperiment(idOrSlug) {
|
|
|
9805
9812
|
res.errorDetails
|
|
9806
9813
|
);
|
|
9807
9814
|
}
|
|
9808
|
-
var list = new Command("list").alias("ls").description(
|
|
9815
|
+
var list = new Command("list").alias("ls").description(
|
|
9816
|
+
"List experiments in summary view by default; use --full for details or --fields for selected columns (archived experiments are hidden by default)"
|
|
9817
|
+
).option(
|
|
9809
9818
|
"-s, --status <status>",
|
|
9810
9819
|
"Filter by status (draft|awaiting_client_approval|changes_requested|client_approved|qa|ready_to_launch|running|paused|completed)"
|
|
9811
|
-
).option("-n, --limit <n>", "Max results", "20").option("--include-archived", "Include archived experiments").
|
|
9820
|
+
).option("-n, --limit <n>", "Max results", "20").option("--include-archived", "Include archived experiments").option("--full", "Return full experiment details instead of the default summary").option(
|
|
9821
|
+
"--fields <list>",
|
|
9822
|
+
"Comma-separated fields to return and table columns in order (JSON always keeps id)"
|
|
9823
|
+
).action(async function action3(opts) {
|
|
9812
9824
|
const creds = requireCredentials();
|
|
9813
9825
|
const spinner = await createSpinner("Fetching experiments...");
|
|
9814
9826
|
const res = await listExperiments(creds, {
|
|
9815
9827
|
status: opts.status,
|
|
9816
9828
|
limit: parseInt(opts.limit, 10),
|
|
9817
|
-
includeArchived: opts.includeArchived
|
|
9829
|
+
includeArchived: opts.includeArchived,
|
|
9830
|
+
view: opts.full ? "full" : "summary",
|
|
9831
|
+
fields: opts.fields
|
|
9818
9832
|
});
|
|
9819
9833
|
if (!res.ok) {
|
|
9820
9834
|
spinner?.fail("Failed to fetch experiments");
|
|
@@ -9827,18 +9841,32 @@ var list = new Command("list").alias("ls").description("List experiments (archiv
|
|
|
9827
9841
|
console.log(source_default.dim("No experiments found."));
|
|
9828
9842
|
return;
|
|
9829
9843
|
}
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9844
|
+
if (opts.fields !== void 0) {
|
|
9845
|
+
const fields3 = [
|
|
9846
|
+
...new Set(opts.fields.split(",").map((field) => field.trim()))
|
|
9847
|
+
];
|
|
9848
|
+
printTable(
|
|
9849
|
+
fields3.map((field) => field.toUpperCase()),
|
|
9850
|
+
experiments.map(
|
|
9851
|
+
(experiment) => fields3.map((field) => {
|
|
9852
|
+
const value = experiment[field];
|
|
9853
|
+
return value == null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
9854
|
+
})
|
|
9855
|
+
)
|
|
9856
|
+
);
|
|
9857
|
+
} else
|
|
9858
|
+
printTable(
|
|
9859
|
+
["NAME", "SLUG", "STATUS", "ARCHIVED", "TRAFFIC", "CREATED"],
|
|
9860
|
+
experiments.map((e) => [
|
|
9861
|
+
e.name.slice(0, 40),
|
|
9862
|
+
e.slug ?? source_default.dim("-"),
|
|
9863
|
+
statusBadge(e.status),
|
|
9864
|
+
e.archived_at ? formatDate(e.archived_at) : source_default.dim("-"),
|
|
9865
|
+
`${(e.traffic_allocation * 100).toFixed(0)}%`,
|
|
9866
|
+
formatDate(e.created_at)
|
|
9867
|
+
]),
|
|
9868
|
+
[40, 25, 12, 15, 8, 15]
|
|
9869
|
+
);
|
|
9842
9870
|
if (res.data.pagination.has_more) {
|
|
9843
9871
|
console.log(source_default.dim(`
|
|
9844
9872
|
(more results available)`));
|
|
@@ -13073,6 +13101,63 @@ trackingCommand.command("verify").description("Verify live tracking events for t
|
|
|
13073
13101
|
}
|
|
13074
13102
|
});
|
|
13075
13103
|
});
|
|
13104
|
+
trackingCommand.command("latency").description("Measure forced-preview versus natural decision latency before launch").argument("<experiment-id-or-key>", "Experiment ID or test key").option("--days <n>", "Lookback window in days (1\u201330)", "7").option("--qa-session <id>", "Include a QA session bucket").action(async function(experimentId, opts) {
|
|
13105
|
+
const days = Number(opts.days);
|
|
13106
|
+
if (!Number.isInteger(days) || days < 1 || days > 30) {
|
|
13107
|
+
fail(this, "days must be an integer from 1 to 30", classifyStatus(400));
|
|
13108
|
+
}
|
|
13109
|
+
const result = await getDecisionLatency(requireCredentials(), experimentId, {
|
|
13110
|
+
days,
|
|
13111
|
+
qaSessionId: opts.qaSession
|
|
13112
|
+
});
|
|
13113
|
+
if (!result.ok || !result.data) {
|
|
13114
|
+
fail(
|
|
13115
|
+
this,
|
|
13116
|
+
result.error ?? "Failed to read decision latency",
|
|
13117
|
+
classifyStatus(result.status),
|
|
13118
|
+
result.errorDetails
|
|
13119
|
+
);
|
|
13120
|
+
}
|
|
13121
|
+
const summary = result.data;
|
|
13122
|
+
output(this, summary, () => {
|
|
13123
|
+
const buckets = [
|
|
13124
|
+
["forced", summary.forced],
|
|
13125
|
+
["natural", summary.natural],
|
|
13126
|
+
...summary.qa_session ? [["qa_session", summary.qa_session]] : []
|
|
13127
|
+
];
|
|
13128
|
+
const ms = (value) => value === null ? "-" : String(Math.round(value));
|
|
13129
|
+
printTable(
|
|
13130
|
+
[
|
|
13131
|
+
"Bucket",
|
|
13132
|
+
"Samples",
|
|
13133
|
+
"Decision p50",
|
|
13134
|
+
"p95",
|
|
13135
|
+
"p99",
|
|
13136
|
+
"Config p50",
|
|
13137
|
+
"p95",
|
|
13138
|
+
"inline/cache/network/none"
|
|
13139
|
+
],
|
|
13140
|
+
buckets.map(([name, bucket]) => [
|
|
13141
|
+
name,
|
|
13142
|
+
String(bucket.samples),
|
|
13143
|
+
ms(bucket.decision_latency_ms.p50),
|
|
13144
|
+
ms(bucket.decision_latency_ms.p95),
|
|
13145
|
+
ms(bucket.decision_latency_ms.p99),
|
|
13146
|
+
ms(bucket.config_fetch_ms.p50),
|
|
13147
|
+
ms(bucket.config_fetch_ms.p95),
|
|
13148
|
+
[
|
|
13149
|
+
bucket.config_sources.inline,
|
|
13150
|
+
bucket.config_sources.cache,
|
|
13151
|
+
bucket.config_sources.network,
|
|
13152
|
+
bucket.config_sources.none
|
|
13153
|
+
].join("/")
|
|
13154
|
+
])
|
|
13155
|
+
);
|
|
13156
|
+
console.log(
|
|
13157
|
+
"Times in ms. Fewer than 5 measured samples: -. Preload/anti-flicker position: under evaluation."
|
|
13158
|
+
);
|
|
13159
|
+
});
|
|
13160
|
+
});
|
|
13076
13161
|
|
|
13077
13162
|
// src/commands/goals.ts
|
|
13078
13163
|
init_source();
|