@letterstory/cli 0.176.93 → 0.176.95

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/lib/cli.mjs CHANGED
@@ -86,7 +86,7 @@ import {
86
86
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
87
87
 
88
88
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
89
- export const VERSION = "0.176.93";
89
+ export const VERSION = "0.176.95";
90
90
 
91
91
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
92
92
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -563,6 +563,7 @@ Collections (the content engine as a primitive — one door, 'collection run'):
563
563
  collection run-status <run-id> Status, steps, constraints, verification
564
564
  collection runs [--collection <id>] [--status <s>] [--limit <n>]
565
565
  collection approve <run-id> [--now] Answer the gate (--now publishes immediately)
566
+ collection start <run-id> Start a scheduled slot's writing now (it goes live at its slot)
566
567
  collection move <run-id> --date YYYY-MM-DD Move a slot not yet written, or an approved written post, to another day
567
568
  collection cancel <run-id>
568
569
  collection inventory <id> Where every piece is; each finished draft's publish-on-its-own time
@@ -2031,15 +2032,17 @@ Bench (Benches: open-source benchmarks on their own domains, kept running; @lett
2031
2032
  bench cite <id> The reference line a post cites
2032
2033
  bench replicate <id> REPLICATE.md: how an agent reproduces the results
2033
2034
 
2034
- Replicate (the Replicator: a website rebuilt as a native Next.js repo, page for page; @letterstory.com only):
2035
+ Replicate (the Replicator: a website rebuilt as a native Next.js repo, page for page; free, within fair-use limits):
2035
2036
  replicate <https://site> [--max-pages <n>] [--refine-budget <usd>] [--repo-name <r>] [--repo-owner <o>]
2036
2037
  Capture every page, rebuild it, compare it pixel by pixel
2037
- replicate [list] Your replications: stage, how closely each matches, preview link
2038
+ replicate [list] Your replications: stage, how closely each matches, preview link;
2039
+ the limits (in progress, pages this month, preview days)
2038
2040
  replicate show <id> [--failing] Pages with their routes and pixel match; collections; events
2039
2041
  replicate attach <id> <repo-name> [--repo-owner <o>] The repo to commit it into (yours; Letter never creates it)
2040
- replicate advance <id> [--retry] [--again] [--preview]
2041
- Move it on now; --retry once blocked_on is done; --again replicates from the start;
2042
- --preview deploys its staging link again
2042
+ replicate advance <id> [--retry] [--again] [--preview] [--extend-preview <days>]
2043
+ Move it on now; --retry once blocked_on is done (checks the tag or TXT record);
2044
+ --again replicates from the start; --preview deploys its staging link again;
2045
+ --extend-preview keeps the preview up longer (Letter's team only)
2043
2046
  replicate archive <id> Stop it and take its preview down; the project stays in storage and in the repo
2044
2047
 
2045
2048
  Evals (how the pipeline is doing on your posts, and the regression cases captured from them):
@@ -134,6 +134,9 @@ export const ARGS = {
134
134
  cancel_collection_run: {
135
135
  run_id: { positional: 0, label: "run-id", required: true },
136
136
  },
137
+ start_collection_run: {
138
+ run_id: { positional: 0, label: "run-id", required: true },
139
+ },
137
140
  reschedule_collection_run: {
138
141
  run_id: { positional: 0, label: "run-id", required: true },
139
142
  date: { flag: "date", required: true },
@@ -184,6 +187,8 @@ export async function cmdCollection(ctx) {
184
187
  return collectionApprove(rest);
185
188
  case "cancel":
186
189
  return collectionCancel(rest);
190
+ case "start":
191
+ return collectionStart(rest);
187
192
  case "move":
188
193
  case "reschedule":
189
194
  return collectionMove(rest);
@@ -197,7 +202,7 @@ export async function cmdCollection(ctx) {
197
202
  return collectionMentions(rest);
198
203
  default:
199
204
  throw new CliError(
200
- `Unknown collection subcommand: ${sub ?? "(none)"}. Try: list, overview, new, config, delete, run, add, queue, headline, run-status, runs, approve, move, cancel, inventory, links, mentions`
205
+ `Unknown collection subcommand: ${sub ?? "(none)"}. Try: list, overview, new, config, delete, run, add, queue, headline, run-status, runs, approve, start, move, cancel, inventory, links, mentions`
201
206
  );
202
207
  }
203
208
  }
@@ -395,6 +400,18 @@ async function collectionCancel(ctx) {
395
400
  return 0;
396
401
  }
397
402
 
403
+ // `collection start <run-id>` — a slot on the calendar starts writing now; it still goes live at its slot
404
+ // (start_collection_run, the way a letterbase's writing automation starts its slot).
405
+ async function collectionStart(ctx) {
406
+ const result = await ctx.client.callTool(
407
+ "start_collection_run",
408
+ argsFromFlags(ARGS.start_collection_run, ctx.flags, ctx.positionals)
409
+ );
410
+ ok(ctx, toolField(result, "started") ? "Started: it is being written." : "Not started yet: see its step.");
411
+ printResult(ctx.io, ctx.flags, result);
412
+ return 0;
413
+ }
414
+
398
415
  // `collection move <run-id> --date YYYY-MM-DD` — a slot not yet written, or a written post
399
416
  // already approved, onto another day (reschedule_collection_run).
400
417
  async function collectionMove(ctx) {
@@ -1,6 +1,7 @@
1
1
  // `replicate` — the Replicator from a terminal (docs/architecture/replicator.md): a website rebuilt
2
2
  // as a native Next.js repo, page for page, and committed to a repo on your organization's GitHub.
3
- // Staff only: the tools answer only a key an @letterstory.com person made. Thin wrappers over the
3
+ // Who may use it is one switch in the app (lib/replicator/access.ts): until it opens, the tools answer
4
+ // only a key an @letterstory.com person made. Free, within fair-use limits. Thin wrappers over the
4
5
  // replicator tools (create_replication, list_replications, get_replication,
5
6
  // attach_replication_repo, advance_replication, archive_replication), the same lib functions the
6
7
  // app calls.
@@ -9,7 +10,7 @@
9
10
  // letterstory replicate list
10
11
  // letterstory replicate show <id> [--failing]
11
12
  // letterstory replicate attach <id> <repo-name> [--repo-owner <account>]
12
- // letterstory replicate advance <id> [--retry] [--again] [--preview]
13
+ // letterstory replicate advance <id> [--retry] [--again] [--preview] [--extend-preview <days>]
13
14
  // letterstory replicate archive <id>
14
15
 
15
16
  import { CliError } from "../client.mjs";
@@ -40,6 +41,7 @@ export const ARGS = {
40
41
  retry: { flag: "retry", type: "bool" },
41
42
  again: { flag: "again", type: "bool" },
42
43
  preview: { flag: "preview", type: "bool" },
44
+ extend_preview_days: { flag: "extend-preview", type: "num" },
43
45
  },
44
46
  archive_replication: { replication_id: replicationId },
45
47
  };
@@ -81,7 +83,9 @@ export async function cmdReplicate(ctx) {
81
83
  async function replicationList(ctx) {
82
84
  const result = await call(ctx, "list_replications");
83
85
  printResult(ctx.io, ctx.flags, result, (r) => {
84
- if (!r.replications?.length) return "No replications yet. Start one: replicate https://example.com";
86
+ const limits = limitsLines(r.limits);
87
+ if (!r.replications?.length)
88
+ return ["No replications yet. Start one: replicate https://example.com", ...limits].join("\n");
85
89
  return r.replications
86
90
  .map(
87
91
  (x) =>
@@ -91,17 +95,40 @@ async function replicationList(ctx) {
91
95
  : ""
92
96
  }${x.staging_url ? ` ${x.staging_url}` : ""}`
93
97
  )
98
+ .concat(limits.length ? ["", ...limits] : [])
94
99
  .join("\n");
95
100
  });
96
101
  return 0;
97
102
  }
98
103
 
104
+ // The fair-use limits (replicating is free): in progress, pages this month, how long a preview stays up.
105
+ function limitsLines(l) {
106
+ if (!l || l.exempt) return [];
107
+ const p = l.pages_this_month;
108
+ return [
109
+ `Limits ${l.concurrent.used} of ${l.concurrent.max} in progress; ${p.used} of ${p.max} pages this month (resets ${p.resets_at.slice(0, 10)}); previews stay up ${l.preview_days} days`,
110
+ ];
111
+ }
112
+
113
+ // What it used, in quantities (free: never a charge).
114
+ function usageLine(u) {
115
+ if (!u) return [];
116
+ const parts = [
117
+ u.sandbox_minutes ? `${u.sandbox_minutes} sandbox min` : null,
118
+ u.pages_captured ? `${u.pages_captured} pages` : null,
119
+ u.preview_hours ? `${u.preview_hours} preview hours` : null,
120
+ u.media_bytes ? `${(u.media_bytes / 1e6).toFixed(1)} MB media` : null,
121
+ ].filter(Boolean);
122
+ return parts.length ? [` Used ${parts.join(", ")} (free)`] : [];
123
+ }
124
+
99
125
  function headline(x) {
100
126
  return [
101
127
  `${x.host} — ${x.status}, stage ${x.stage}`,
102
128
  ...(x.blocked_on ? [` Blocked: ${x.blocked_on}`, ` Then: replicate advance ${x.id} --retry`] : []),
103
129
  ...(x.stage_note && !x.blocked_on ? [` ${x.stage_note}`] : []),
104
130
  ...previewLines(x),
131
+ ...usageLine(x.usage),
105
132
  ...(x.summary
106
133
  ? [
107
134
  ` ${x.summary.replicated} of ${x.summary.pages} pages replicated; ${x.summary.passing} match the original at every width (mean ${pct(x.summary.meanPixel)})`,
@@ -155,9 +182,11 @@ function motionLines(m) {
155
182
 
156
183
  // The staging link: the replica live on a Letter domain, to share before (or without) a repo.
157
184
  function previewLines(x) {
185
+ if (x.staging_state === "queued" || x.staging_state === "expired")
186
+ return [` Preview ${x.staging_note ?? x.staging_state}`];
158
187
  if (x.staging_url)
159
188
  return [
160
- ` Preview ${x.staging_url}${x.staging_state === "deploying" || x.staging_state === "pending" ? " (redeploying)" : ""}`,
189
+ ` Preview ${x.staging_url}${x.staging_state === "deploying" || x.staging_state === "pending" ? " (redeploying)" : ""}${x.staging_expires_at ? ` (online until ${x.staging_expires_at.slice(0, 10)})` : ""}`,
161
190
  ...(x.staging_state === "failed" && x.staging_error
162
191
  ? [` Preview redeploy failed: ${x.staging_error}`]
163
192
  : []),
@@ -173,7 +202,11 @@ function previewLines(x) {
173
202
  }
174
203
 
175
204
  function replicationShow(ctx, result) {
176
- printResult(ctx.io, ctx.flags, result, (r) => headline(r.replication).join("\n"));
205
+ printResult(ctx.io, ctx.flags, result, (r) =>
206
+ [...headline(r.replication), ...(r.notice ? [` Note ${r.notice}`] : []), ...limitsLines(r.limits)].join(
207
+ "\n"
208
+ )
209
+ );
177
210
  return 0;
178
211
  }
179
212
 
@@ -181,12 +214,20 @@ function replicationDetail(ctx, result) {
181
214
  printResult(ctx.io, ctx.flags, result, (r) => {
182
215
  const lines = headline(r.replication);
183
216
  // Waiting for its domain to be verified: the tag for the homepage (then advance --retry checks it).
184
- if (r.domain_verification)
217
+ if (r.domain_verification) {
185
218
  lines.push(
186
219
  "",
187
220
  `Add this tag to the <head> of ${r.domain_verification.host}'s homepage:`,
188
221
  r.domain_verification.tag
189
222
  );
223
+ if (r.domain_verification.dns)
224
+ lines.push(
225
+ "",
226
+ "Or add this TXT record to its DNS:",
227
+ ` Name ${r.domain_verification.dns.name}`,
228
+ ` Value ${r.domain_verification.dns.value}`
229
+ );
230
+ }
190
231
  if (r.pages?.length) {
191
232
  lines.push("", "Pages:");
192
233
  for (const p of r.pages) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.93",
3
+ "version": "0.176.95",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",