@letterstory/cli 0.96.0 → 0.98.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 CHANGED
@@ -353,7 +353,7 @@ long-running agent can watch the fleet on a schedule without a browser.
353
353
  # every client against itself on share of voice, mention rate and page access, with what we
354
354
  # did for it. Needs fleet:read; `run` and `ask` spend Fable tokens and need fleet:query.
355
355
  letterstory admin patterns --period 30d
356
- letterstory admin patterns kernels --by version --outcome impressions14 # what each kernel/version change did
356
+ letterstory admin patterns chain --dimension arenaVersion --outcome cited14 # what each kernel/arena/research change did
357
357
  letterstory admin patterns reports # Fable's weekly reports and runs, newest first
358
358
  letterstory admin patterns report <id> # one report's markdown
359
359
  letterstory admin patterns run # a report now (takes minutes)
package/lib/cli.mjs CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  } from "./commands.mjs";
59
59
 
60
60
  // Keep in sync with cli/package.json.
61
- export const VERSION = "0.96.0";
61
+ export const VERSION = "0.98.0";
62
62
 
63
63
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
64
64
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
@@ -660,10 +660,12 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
660
660
  Client patterns: every client's share of voice,
661
661
  mention rate and page access against itself, with
662
662
  what we did for it. Needs fleet:read.
663
- admin patterns kernels [--by version|family] [--outcome cited14|read14|named|impressions14|clicks14|cited28|impressions28]
664
- What each kernel and version change did: the client's
665
- change minus clients that held still, pooled by
666
- transition. Needs fleet:read.
663
+ admin patterns chain [--dimension kernel|kernelVersion|arena|arenaVersion|researchPrompt] [--outcome cited14|read14|named|impressions14|clicks14|cited28|impressions28]
664
+ What each change to the writing chain did (kernel,
665
+ version, research arena, arena version, research
666
+ prompt): the client's change minus the comparison,
667
+ what changed with it, pooled by transition.
668
+ "kernels" = --dimension kernelVersion. Needs fleet:read.
667
669
  admin patterns reports [--kind reports|weekly|run|ask] | report <id>
668
670
  Fable's weekly reports, runs and Ask Fable answers.
669
671
  admin patterns run | ask "<question>" Run a Fable report now, or ask Fable a question
@@ -4,7 +4,7 @@
4
4
  // routes, because they exist precisely to act BEFORE an org exists.
5
5
  //
6
6
  // admin ask "which kernels are hooked up to the most clients?" [--sql] [--json]
7
- // admin patterns [--period 14d|30d] [--segment "…"] | kernels [--by version|family] [--outcome cited14] | reports [--kind reports|weekly|run|ask] | report <id> | run | ask "<question>" (fleet:read / fleet:query)
7
+ // admin patterns [--period 14d|30d] [--segment "…"] | chain [--dimension kernel|kernelVersion|arena|arenaVersion|researchPrompt] [--outcome cited14] | reports [--kind reports|weekly|run|ask] | report <id> | run | ask "<question>" (fleet:read / fleet:query)
8
8
  // admin experiments list|show|new|update|note|started|spend|conclude|abandon … (tuning:experiment)
9
9
  // admin tuning-context [--q "<question>"] (tuning:experiment)
10
10
  // admin tuning-import <brief-results.json> [--experiment <id>] (tuning:experiment)
@@ -273,25 +273,25 @@ async function cmdAdminPatterns(ctx) {
273
273
  }
274
274
  return;
275
275
  }
276
+ case "chain":
276
277
  case "kernels": {
277
278
  const params = new URLSearchParams();
278
- if (flagStr(ctx.flags.by)) params.set("by", flagStr(ctx.flags.by));
279
+ const dimension = flagStr(ctx.flags.dimension) ?? (sub === "kernels" ? "kernelVersion" : undefined);
280
+ if (dimension) params.set("dimension", dimension);
279
281
  if (flagStr(ctx.flags.outcome)) params.set("outcome", flagStr(ctx.flags.outcome));
280
- const result = await staffRequest(ctx, "GET", `${PATTERNS}/kernels${params.size ? `?${params}` : ""}`);
282
+ const result = await staffRequest(ctx, "GET", `${PATTERNS}/chain${params.size ? `?${params}` : ""}`);
281
283
  if (json) return printResult(ctx.io, ctx.flags, result);
282
284
  const fmt = (v) => (v == null ? "—" : `${v >= 0 ? "+" : "−"}${Math.abs(v).toFixed(3)}`);
283
- ctx.io.log(
284
- `Kernel changes on ${result.outcome}, by ${result.by} (effect = change minus clients that held still):`
285
- );
285
+ ctx.io.log(`Changes to ${result.dimension} on ${result.outcome} (effect = change minus the comparison):`);
286
286
  for (const t of result.transitions)
287
287
  ctx.io.log(
288
- ` ${`${t.from} → ${t.to}`.padEnd(30)} ${fmt(t.effect)} (${fmt(t.lo)} to ${fmt(t.hi)}) ${t.verdict} · ${t.swaps} swaps, ${t.clients} clients`
288
+ ` ${`${t.from} → ${t.to}`.padEnd(34)} ${fmt(t.effect)} (${fmt(t.lo)} to ${fmt(t.hi)}) ${t.verdict} · ${t.swaps} changes${t.confounded ? `, ${t.confounded} with another` : ""}, ${t.clients} clients`
289
289
  );
290
290
  if (!result.transitions.length) ctx.io.log(" none yet");
291
- ctx.io.log(`\n${result.by === "version" ? "Versions" : "Kernels"} (descriptive):`);
292
- for (const k of result.kernels)
291
+ ctx.io.log(`\nEvery value (descriptive):`);
292
+ for (const k of result.values)
293
293
  ctx.io.log(
294
- ` ${k.label.padEnd(16)} ${String(k.posts).padStart(6)} posts ${String(k.clients).padStart(4)} clients ${k.outcome ? k.outcome.mean.toFixed(3) : "—"}`
294
+ ` ${k.label.padEnd(26)} ${String(k.posts).padStart(6)} posts ${String(k.clients).padStart(4)} clients ${k.outcome ? k.outcome.mean.toFixed(3) : "—"}`
295
295
  );
296
296
  return;
297
297
  }
@@ -334,9 +334,7 @@ async function cmdAdminPatterns(ctx) {
334
334
  return;
335
335
  }
336
336
  default:
337
- throw new CliError(
338
- 'Usage: admin patterns [show] | kernels | reports | report <id> | run | ask "<question>"'
339
- );
337
+ throw new CliError('Usage: admin patterns [show] | chain | reports | report <id> | run | ask "<question>"');
340
338
  }
341
339
  }
342
340
 
@@ -241,6 +241,8 @@ export const ARGS = {
241
241
  },
242
242
  delete_dispatch: {
243
243
  dispatch_id: { positional: 0, label: "dispatch-id", required: true },
244
+ pending: { flag: "pending", enum: ["move", "send"] },
245
+ move_to: { flag: "move-to" },
244
246
  },
245
247
  list_dispatch_links: {},
246
248
  create_dispatch_link: {
@@ -393,14 +395,28 @@ export const ARGS = {
393
395
  },
394
396
  };
395
397
 
398
+ // `message` may be a function of the tool's result, for a line that reports what actually happened.
396
399
  async function run(ctx, tool, message) {
397
400
  refuseUnknownFlags(ctx, ARGS[tool]);
398
401
  const result = await ctx.client.callTool(tool, argsFromFlags(ARGS[tool], ctx.flags, ctx.positionals));
399
- if (message) ok(ctx, message);
402
+ const text = typeof message === "function" ? message(result) : message;
403
+ if (text) ok(ctx, text);
400
404
  printResult(ctx.io, ctx.flags, result);
401
405
  return 0;
402
406
  }
403
407
 
408
+ /** What a desk delete did, in one line: sent and moved counts as the tool reported them. */
409
+ function deleteDispatchLine(result) {
410
+ const sent = Number(result?.sent ?? 0);
411
+ const moved = Number(result?.moved ?? 0);
412
+ const n = (count) => `${count} queued email${count === 1 ? "" : "s"}`;
413
+ if (result?.pending === "send") {
414
+ const base = `Dispatch deleted; ${n(sent)} approved to send.`;
415
+ return moved > 0 ? `${base} ${n(moved)} couldn't be approved and moved to ${result.moved_to}.` : base;
416
+ }
417
+ return `Dispatch deleted; ${n(moved)} moved${moved > 0 ? ` to ${result.moved_to}` : ""}.`;
418
+ }
419
+
404
420
  const SEQUENCES_USAGE = `Usage: sequences <list|get|create|update|pause|resume|enroll|stop|review|links|cakes|heyreach>`;
405
421
  const DISPATCHES_USAGE = `Usage: dispatches <list|create|rename|delete>`;
406
422
 
@@ -422,12 +438,16 @@ export async function cmdDispatches(ctx) {
422
438
  case "delete": {
423
439
  if (!ctx.flags.yes) {
424
440
  const id = flagStr(rest.positionals[0]) ?? "<dispatch-id>";
441
+ const moveTo = flagStr(ctx.flags["move-to"]);
442
+ // The re-run command carries every option given, so copying it does what this line says.
425
443
  ctx.io.error(
426
- `Its queued emails move to the default desk. Re-run with --yes to confirm: ${ctx.bin} dispatches delete ${id} --yes`
444
+ ctx.flags.pending === "send"
445
+ ? `Its queued emails will be SENT now. Re-run with --yes to confirm: ${ctx.bin} dispatches delete ${id} --pending send --yes`
446
+ : `Its queued emails move to ${moveTo ?? "the default desk"}. Re-run with --yes to confirm: ${ctx.bin} dispatches delete ${id}${moveTo ? ` --move-to ${moveTo}` : ""} --yes`
427
447
  );
428
448
  return 1;
429
449
  }
430
- return run(rest, "delete_dispatch", "Dispatch deleted; its queued emails moved to the default desk.");
450
+ return run(rest, "delete_dispatch", deleteDispatchLine);
431
451
  }
432
452
  default:
433
453
  throw new CliError(`Unknown dispatches subcommand: ${sub}. ${DISPATCHES_USAGE}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.96.0",
3
+ "version": "0.98.0",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",