@koda-sl/baker-cli 0.354.0-dev.e6a354971 → 0.354.0-dev.faae9de67

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 CHANGED
@@ -98,12 +98,7 @@ All commands return a JSON envelope:
98
98
  ### Unknown options are refused
99
99
 
100
100
  An option a command does not declare stops the run with `UNKNOWN_ARGUMENT`, naming the
101
- real one it was most likely reaching for, with the first sentence of that option's own
102
- description. A word the command spells differently is matched by meaning (`--from` and
103
- `--since` → `--start-date`, `--to` and `--until` → `--end-date`, `--path` and `--url` →
104
- `--landing`, each only where the command has it); otherwise only a near spelling is
105
- offered, so a short option is never pointed at an unrelated one (`--from` at `--full`).
106
- Previously such an option was accepted and ignored, so a call that
101
+ closest real one. Previously such an option was accepted and ignored, so a call that
107
102
  looked like it had filtered or narrowed a result was in fact answering the unfiltered
108
103
  question — and nothing in the output said so. Both spellings of an option work
109
104
  (`--run-id` and `--runId`), as they always did. The error's `fix` names the exact call
@@ -1338,7 +1333,7 @@ Shared flags: `--days <n>` (default 30) or `--start-date` / `--end-date` (`YYYY-
1338
1333
 
1339
1334
  `--measure` decides whose activity is measured and defaults to `campaigns` (only visitors a Baker landing brought). `--measure all` measures the client's entire website and is a decision somebody has to ask for. `host` is the client's own landing domain when one shares a domain with the site being measured, so the script and its events stay first-party; `firstParty` in the response says which, and removing that domain from Baker means re-running this command.
1340
1335
 
1341
- **Paging the list reports.** `people`, `submissions` and `delivery` are lists of individual things rather than breakdowns — `delivery` and `submissions` are the ones the CLI exposes, `people` backs the People section of the dashboard's landing Analytics screen over the same wire — and only they take `--page <n>` (1-based) and `--page-size <n>` (up to 200, default 50). The response carries `pageInfo` with `hasMore` — when it is true there **are** more rows, so a total must never be reported from one page. Pass neither flag and a list comes back at the size it always did. A value that is not a whole positive number (`--page /pricing`) is refused before any request, naming `--landing` as the flag that narrows a report to one page of the site.
1336
+ **Paging the list reports.** `people`, `submissions` and `delivery` are lists of individual things rather than breakdowns — `delivery` and `submissions` are the ones the CLI exposes, `people` backs the People section of the dashboard's landing Analytics screen over the same wire — and only they take `--page <n>` (1-based) and `--page-size <n>` (up to 200, default 50). The response carries `pageInfo` with `hasMore` — when it is true there **are** more rows, so a total must never be reported from one page. Pass neither flag and a list comes back at the size it always did.
1342
1337
 
1343
1338
  **A conversion is a company-level named event, not a property of a Form.** `baker analytics conversions` is the only place "what counts" is decided, and an empty `definitions` list means every conversion number in every other report is zero — not because nobody converted, but because nothing is named as an outcome and Baker never guesses which event is the point of a page. `--candidates` lists every event these pages actually produced — each Form step and trigger, every `data-baker-*` event, every outbound destination — with volume and whether it is already counted; that list is where an event key comes from, so never invent one. `--event <key> --name "Booked a call"` starts counting it **immediately and retroactively**: the whole stored history is rescored, so naming the right event today also fixes last month. Nothing here is staged and publishing is not involved. Two events given the same `--name` become one row and one number, which is how a call booked on three different Forms reads as one outcome. `--count-mode every_time` is for the outcomes people genuinely repeat (a guide downloaded twice is two downloads); the default counts once per visit.
1344
1339
 
@@ -1426,8 +1421,8 @@ baker experiment plan --landing oferta --variant oferta--1 --goal submit:book-a-
1426
1421
  baker experiment start --landing oferta --variant oferta--1 --because "60% of visitors never scroll past the hero" --change "put the price in the headline"
1427
1422
  baker experiment update --id temp_exp_1 --goal submit:book-a-call # a staged test can still change what it measures
1428
1423
  baker experiment update --id a3f91c2b --because "…" # a live one can only change its wording
1429
- baker experiment status # the verdict on every test
1430
- baker experiment status --id a3f91c2b --full # one test, with the confidence and posteriors behind it
1424
+ baker experiment status # the verdict on every test, first; dates as calendar dates
1425
+ baker experiment status --id a3f91c2b --full # one test, with everything the dashboard reads and the posteriors
1431
1426
  baker experiment history --landing oferta # every finished test on a page: claim, numbers, decision, learning
1432
1427
  baker experiment pause --id a3f91c2b # everyone sees the original for now
1433
1428
  baker experiment resume --id a3f91c2b # the split picks up with the same sides
package/dist/cli.js CHANGED
@@ -33401,19 +33401,6 @@ function pageOf(args5) {
33401
33401
  ...size ? { pageSize: Number(size) } : {}
33402
33402
  };
33403
33403
  }
33404
- function isWholePositive(raw) {
33405
- return /^[1-9]\d*$/.test(String(raw));
33406
- }
33407
- function paginationError(args5) {
33408
- if (args5.page !== void 0 && !isWholePositive(args5.page)) {
33409
- return `--page takes a page number of a list report (1, 2, \u2026), not "${String(args5.page)}". To narrow a report to one page of the site, pass --landing <internalId> (from \`baker analytics landings\`), or read one path with \`baker analytics page --path /pricing\`.`;
33410
- }
33411
- const size = args5["page-size"];
33412
- if (size !== void 0 && !isWholePositive(size)) {
33413
- return `--page-size takes a number of rows (1 to 200), not "${String(size)}".`;
33414
- }
33415
- return null;
33416
- }
33417
33404
  function requestBody(args5, options) {
33418
33405
  const candidates = {
33419
33406
  preset: options.preset,
@@ -33478,11 +33465,6 @@ async function runPreset(args5, options) {
33478
33465
  });
33479
33466
  process.exit(1);
33480
33467
  }
33481
- const pagination = paginationError(args5);
33482
- if (pagination) {
33483
- writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message: pagination } });
33484
- process.exit(1);
33485
- }
33486
33468
  try {
33487
33469
  const response = await apiPost("/api/analytics/query", requestBody(args5, options));
33488
33470
  const requestedPlatform = args5.platform === void 0 ? "" : String(args5.platform);
@@ -47133,6 +47115,80 @@ async function declarationsOf(root, files) {
47133
47115
  return sources.flatMap(eventDeclarationsIn);
47134
47116
  }
47135
47117
 
47118
+ // src/commands/experiment/readout.ts
47119
+ function isoMoment(ms) {
47120
+ return ms === null ? null : new Date(ms).toISOString();
47121
+ }
47122
+ function isoDay2(ms) {
47123
+ return ms === null ? null : new Date(ms).toISOString().slice(0, 10);
47124
+ }
47125
+ function compactArm(arm) {
47126
+ return { visitors: arm.visitors, conversions: arm.conversions, rate: Math.round(arm.rate * 1e4) / 1e4 };
47127
+ }
47128
+ function compactMetric(metric) {
47129
+ return {
47130
+ name: metric.name,
47131
+ reading: metric.reading,
47132
+ control: metric.control.conversions,
47133
+ variant: metric.variant.conversions
47134
+ };
47135
+ }
47136
+ function fullArm(arm) {
47137
+ return {
47138
+ ...arm,
47139
+ ...arm.snapshotCapturedAt === void 0 ? {} : { snapshotCapturedAt: isoMoment(arm.snapshotCapturedAt) }
47140
+ };
47141
+ }
47142
+ function statusReadout(row, { full, now }) {
47143
+ const decisionWhen = {
47144
+ earliestDecisionAt: isoDay2(row.earliestDecisionAt),
47145
+ ...row.earliestDecisionAt === null ? {} : { earliestDecisionIn: describeWait(row.earliestDecisionAt, now) }
47146
+ };
47147
+ const moments = {
47148
+ startedAt: isoMoment(row.startedAt),
47149
+ pausedAt: isoMoment(row.pausedAt),
47150
+ finishedAt: isoMoment(row.finishedAt)
47151
+ };
47152
+ if (full) {
47153
+ return {
47154
+ ...row,
47155
+ ...moments,
47156
+ ...decisionWhen,
47157
+ control: fullArm(row.control),
47158
+ variant: fullArm(row.variant)
47159
+ };
47160
+ }
47161
+ return {
47162
+ experimentId: row.experimentId,
47163
+ landingSlug: row.landingSlug,
47164
+ variantSlug: row.variantSlug,
47165
+ status: row.status,
47166
+ verdict: row.verdict,
47167
+ summary: row.summary,
47168
+ ...row.winner === null ? {} : { winner: row.winner },
47169
+ ...row.invalidReason === null ? {} : { invalidReason: row.invalidReason },
47170
+ stillNeeded: row.stillNeeded,
47171
+ ...decisionWhen,
47172
+ willNotConclude: row.willNotConclude,
47173
+ control: compactArm(row.control),
47174
+ variant: compactArm(row.variant),
47175
+ secondary: row.secondary.map(compactMetric),
47176
+ hypothesisStatement: row.hypothesisStatement,
47177
+ minDetectableRelativeLift: row.minDetectableRelativeLift,
47178
+ goal: row.goal,
47179
+ ...row.variantGoal === null ? {} : { variantGoal: row.variantGoal },
47180
+ goalLabel: row.goalLabel,
47181
+ ...moments,
47182
+ ...row.status === "finished" ? {
47183
+ survivor: row.survivor,
47184
+ hypothesisOutcome: row.hypothesisOutcome,
47185
+ fold: row.fold,
47186
+ learning: row.learning,
47187
+ endReason: row.endReason
47188
+ } : {}
47189
+ };
47190
+ }
47191
+
47136
47192
  // src/commands/experiment/update.ts
47137
47193
  function buildUpdateRequest(args5, rawArgs) {
47138
47194
  const expect = args5.expect === void 0 ? void 0 : String(args5.expect);
@@ -47374,18 +47430,26 @@ registerSchema({
47374
47430
  args: {
47375
47431
  id: { type: "string", description: "One test, by its id", required: false },
47376
47432
  landing: LANDING_FILTER_ARG,
47377
- full: { type: "boolean", description: "Include the posteriors behind the verdict", required: false }
47433
+ full: {
47434
+ type: "boolean",
47435
+ description: "Every field the dashboard reads too \u2014 the hypothesis in parts, what each version owns, the preview pictures and the posteriors behind the verdict",
47436
+ required: false
47437
+ }
47378
47438
  }
47379
47439
  });
47380
47440
  var statusCommand6 = defineCommand124({
47381
47441
  meta: {
47382
47442
  name: "status",
47383
- description: "The verdict on each test. Read `verdict` and nothing else to decide: keep_running | winner | no_difference | cannot_win | invalid. `cannot_win` means even the best case is under the lift the test was looking for \u2014 end it and keep the original. `stillNeeded` is the data (visitors, conversions) the test still needs; a test is gated on data, never on days. `summary` is one sentence you can show the client, and `secondary` lists every other conversion the company counts on the same two arms \u2014 context, never a second decision. The numbers under `--full` are for printing, never for deciding \u2014 a test that says keep_running has not finished, however good its numbers look."
47443
+ description: "The verdict on each test. Read `verdict` and nothing else to decide: keep_running | winner | no_difference | cannot_win | invalid. `cannot_win` means even the best case is under the lift the test was looking for \u2014 end it and keep the original. `stillNeeded` is the data (visitors, conversions) the test still needs; a test is gated on data, never on days. `earliestDecisionAt` is the day this page's traffic would get it there (YYYY-MM-DD) and `earliestDecisionIn` how far off that is \u2014 say those, never work a date out yourself. `summary` is one sentence you can show the client, and `secondary` lists every other conversion the company counts on the same two arms \u2014 context, never a second decision. The numbers under `--full` are for printing, never for deciding \u2014 a test that says keep_running has not finished, however good its numbers look."
47384
47444
  },
47385
47445
  args: {
47386
47446
  id: { type: "string", description: "One test, by its id", required: false },
47387
47447
  landing: LANDING_FILTER_ARG,
47388
- full: { type: "boolean", description: "Include the posteriors behind the verdict", required: false }
47448
+ full: {
47449
+ type: "boolean",
47450
+ description: "Every field the dashboard reads too \u2014 the hypothesis in parts, what each version owns, the preview pictures and the posteriors behind the verdict",
47451
+ required: false
47452
+ }
47389
47453
  },
47390
47454
  run: async ({ args: args5 }) => {
47391
47455
  try {
@@ -47394,10 +47458,13 @@ var statusCommand6 = defineCommand124({
47394
47458
  ...args5.landing ? { landingSlug: String(args5.landing) } : {},
47395
47459
  ...args5.full === true ? { full: true } : {}
47396
47460
  });
47461
+ const now = Date.now();
47397
47462
  writeJsonEnvelope({
47398
47463
  ok: true,
47399
- data: response.data,
47400
- hints: buildStatusHints(response.data.experiments)
47464
+ data: {
47465
+ experiments: response.data.experiments.map((row) => statusReadout(row, { full: args5.full === true, now }))
47466
+ },
47467
+ hints: buildStatusHints(response.data.experiments, now)
47401
47468
  });
47402
47469
  } catch (error) {
47403
47470
  reportApiError(error);
@@ -68628,36 +68695,19 @@ function findUnknownFlags(root, argv) {
68628
68695
  }
68629
68696
  return unknown;
68630
68697
  }
68631
- var SYNONYMS = {
68632
- from: ["start-date", "start", "since"],
68633
- since: ["start-date", "start", "from"],
68634
- start: ["start-date", "since", "from"],
68635
- to: ["end-date", "end", "until"],
68636
- until: ["end-date", "end", "to"],
68637
- end: ["end-date", "until", "to"],
68638
- path: ["landing"],
68639
- url: ["landing"]
68640
- };
68641
68698
  function closestFlag(unknown, command) {
68642
68699
  const accepted = acceptedNames(command);
68643
68700
  if (!accepted) {
68644
68701
  return null;
68645
68702
  }
68646
68703
  const name = unknown.replace(/^--/, "").split("=")[0] ?? "";
68647
- const synonym = SYNONYMS[name]?.find((candidate) => accepted.has(candidate));
68648
- if (synonym) {
68649
- return `--${synonym}`;
68650
- }
68651
68704
  const scored = [...accepted].filter((candidate) => !ALWAYS_ACCEPTED.has(candidate)).map((candidate) => ({
68652
68705
  candidate,
68653
68706
  prefix: sharedPrefix(name, candidate),
68654
68707
  distance: editDistance(name, candidate)
68655
- })).filter(({ prefix, distance }) => prefix >= 4 || distance <= typoBudget(name)).sort((a, b) => b.prefix - a.prefix || a.distance - b.distance);
68708
+ })).filter(({ prefix, distance }) => prefix >= 4 || distance <= 3).sort((a, b) => b.prefix - a.prefix || a.distance - b.distance);
68656
68709
  return scored[0] ? `--${scored[0].candidate}` : null;
68657
68710
  }
68658
- function typoBudget(name) {
68659
- return Math.max(1, Math.floor(name.length / 3));
68660
- }
68661
68711
  function sharedPrefix(a, b) {
68662
68712
  let i = 0;
68663
68713
  while (i < a.length && i < b.length && a[i] === b[i]) {
@@ -68681,24 +68731,6 @@ function commandFor(root, argv) {
68681
68731
  const { command, resolved } = resolveCommand2(root, argv);
68682
68732
  return resolved ? command : null;
68683
68733
  }
68684
- function suggestFlag(unknown, command) {
68685
- const flag = closestFlag(unknown, command);
68686
- if (!flag) {
68687
- return null;
68688
- }
68689
- const args5 = argsOf(command) ?? {};
68690
- const name = flag.slice(2);
68691
- const def = args5[name] ?? args5[camel(name)];
68692
- const described = def && typeof def === "object" ? def.description : void 0;
68693
- const description = typeof described === "string" ? described.split(/\.\s/)[0]?.trim().replace(/\.$/, "") : "";
68694
- return description ? { flag, description } : { flag };
68695
- }
68696
- function suggestionText(suggestion) {
68697
- if (!suggestion) {
68698
- return "";
68699
- }
68700
- return suggestion.description ? `Did you mean ${suggestion.flag}? It is described as: ${suggestion.description}. ` : `Did you mean ${suggestion.flag}? `;
68701
- }
68702
68734
  var OUTPUT_EXPLANATION = "This command has no --output option: `--output json` is accepted by every command that prints the JSON envelope, and md or files only where `baker schema` lists `output`. Drop the option and read the default output. ";
68703
68735
  function unknownFlagEnvelope(unknown, commandPath, suggestion, hasSchema) {
68704
68736
  const list = unknown.join(", ");
@@ -68709,7 +68741,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion, hasSchema) {
68709
68741
  message: `${list} ${unknown.length > 1 ? "are not options" : "is not an option"} of \`baker ${commandPath}\`.`,
68710
68742
  fix: {
68711
68743
  action: hasSchema ? "check_schema" : "check_help",
68712
- explanation: (unknown.some((flag) => flag === "--output" || flag.startsWith("--output=")) ? OUTPUT_EXPLANATION : "") + suggestionText(suggestion) + `Run \`${hasSchema ? `baker schema ${commandPath}` : `baker ${commandPath} --help`}\` to see every option this command takes. Do not re-run the same call: until this version an unrecognised option was accepted and ignored, so a command that looked like it had filtered or narrowed the result was in fact answering the unfiltered question.`
68744
+ explanation: (unknown.some((flag) => flag === "--output" || flag.startsWith("--output=")) ? OUTPUT_EXPLANATION : "") + (suggestion ? `Did you mean ${suggestion}? ` : "") + `Run \`${hasSchema ? `baker schema ${commandPath}` : `baker ${commandPath} --help`}\` to see every option this command takes. Do not re-run the same call: until this version an unrecognised option was accepted and ignored, so a command that looked like it had filtered or narrowed the result was in fact answering the unfiltered question.`
68713
68745
  },
68714
68746
  retryable: false
68715
68747
  }
@@ -68739,7 +68771,7 @@ function refuseUnknownFlags(root, argv) {
68739
68771
  }
68740
68772
  const command = commandFor(root, argv);
68741
68773
  const first = unknown[0];
68742
- const suggestion = command && first ? suggestFlag(first, command) : null;
68774
+ const suggestion = command && first ? closestFlag(first, command) : null;
68743
68775
  const path48 = commandPathOf(root, argv);
68744
68776
  process.stdout.write(
68745
68777
  `${JSON.stringify(unknownFlagEnvelope(unknown, path48, suggestion, findSchema(path48.split(" ")) !== void 0), null, 2)}