@letterstory/cli 0.176.97 → 0.176.99
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 +9 -2
- package/lib/commands/admin.mjs +26 -1
- package/lib/commands/replicate.mjs +50 -3
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -87,7 +87,7 @@ import {
|
|
|
87
87
|
import { fetchParkedSections, withoutParked } from "./parked.mjs";
|
|
88
88
|
|
|
89
89
|
// Matches cli/package.json; publish-cli.yml rewrites both to the published version.
|
|
90
|
-
export const VERSION = "0.176.
|
|
90
|
+
export const VERSION = "0.176.99";
|
|
91
91
|
|
|
92
92
|
/** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
|
|
93
93
|
export const EXIT_PAYMENT_REQUIRED = 3;
|
|
@@ -119,6 +119,8 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
119
119
|
"no-disclosure",
|
|
120
120
|
// replicate show: list only the pages below the bar.
|
|
121
121
|
"failing",
|
|
122
|
+
// replicate forms: read the site's forms key (admins) instead of its submissions.
|
|
123
|
+
"show-key",
|
|
122
124
|
// sim create / resample / sample-key: keep the customer's sample key, or delete it now.
|
|
123
125
|
"keep-key",
|
|
124
126
|
"delete-key",
|
|
@@ -131,7 +133,9 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
131
133
|
"start",
|
|
132
134
|
// site research-builds: run now even when the stored pass is under an hour old.
|
|
133
135
|
"fresh",
|
|
134
|
-
// admin letterpress
|
|
136
|
+
// admin letterpress tokens: today so far against yesterday and a week ago.
|
|
137
|
+
"so-far",
|
|
138
|
+
// admin letterpress telemetry | prs | tokens: every org, test and demo orgs included.
|
|
135
139
|
"all-orgs",
|
|
136
140
|
// admin letterpress prs | failures: since the first PR or build (all_time).
|
|
137
141
|
"all-time",
|
|
@@ -2067,6 +2071,9 @@ Replicate (the Replicator: a website rebuilt as a native Next.js repo, page for
|
|
|
2067
2071
|
--again replicates from the start; --preview deploys its staging link again;
|
|
2068
2072
|
--extend-preview keeps the preview up longer (Letter's team only)
|
|
2069
2073
|
replicate archive <id> Stop it and take its preview down; the project stays in storage and in the repo
|
|
2074
|
+
replicate forms [<id>] [--form <f>] [--limit <n>] [--before <iso>]
|
|
2075
|
+
What people sent through the replicated sites' forms, newest first;
|
|
2076
|
+
--show-key (admins): LETTER_FORMS_URL and LETTER_FORMS_KEY to host it yourself
|
|
2070
2077
|
|
|
2071
2078
|
Evals (how the pipeline is doing on your posts, and the regression cases captured from them):
|
|
2072
2079
|
evals scoreboard [--collection <id>] [--days N] Every measure by stage: posts, failing, rate
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -5261,9 +5261,10 @@ const ENGINE = "/api/superadmin/engine-settings";
|
|
|
5261
5261
|
const LETTERPRESS = "/api/superadmin/letterpress";
|
|
5262
5262
|
const CODING_TELEMETRY = "/api/superadmin/coding-telemetry";
|
|
5263
5263
|
const PULL_REQUESTS = "/api/superadmin/pull-requests";
|
|
5264
|
+
const PLATFORM_USAGE = "/api/superadmin/usage/platform";
|
|
5264
5265
|
const BUILD_FAILURES = "/api/superadmin/build-failures";
|
|
5265
5266
|
const ENGINE_USAGE =
|
|
5266
|
-
"Usage: admin letterpress status | gaps | telemetry [--days N] [--harness h] [--all-orgs] | prs [--days N | --from YYYY-MM-DD [--to YYYY-MM-DD] | --all-time] [--day YYYY-MM-DD] [--org id] [--state merged|open|closed] [--top N] [--tz zone] [--all-orgs] | failures [--days N | --from YYYY-MM-DD [--to YYYY-MM-DD] | --all-time] [--day YYYY-MM-DD] [--org id] [--failure ours|agent|setup|undelivered] [--cause c] [--stopped-at step] [--repo owner/name] [--slow] [--tz zone] [--all-orgs] | blocked [--days N] [--org id] [--all-orgs] | unblock <org-id> <host> <subject> | decisions <org-id> [--site host] [--surface plan|fix|build|site] | undo <org-id> <host> <decision-id> | runs [--status s] [--error-code c] [--first-review-cause agent|context|setup|unknown] [--pr merged|closed|open|none] [--harness h] [--days N] | beyond [--limit N] [--before ISO] | companies | company <org-id> [--site host] | facts <org-id> [--site host] | fits [--recipe id] [--org id] [--site host] [--fits yes|no|unknown|waiting] [--limit N] | replay <run-id> | mark <run-id> <item-id> <right|weak|wrong|lead|gap|recipe|clear> [--note …] | settings | set <weights.<ask>.<reading>|lenses.<lens>.bar.<reading>|lenses.<lens>.bar|lenses.<lens>.research.turns|lenses.<lens>.research.ms|asks.<lens>.ask|asks.<lens>.guidance|dismissals.block_days|preflight.off|explore.ask|explore.research.turns|explore.research.ms|explore.every_days> <value>";
|
|
5267
|
+
"Usage: admin letterpress status | gaps | telemetry [--days N] [--harness h] [--all-orgs] | tokens [--days N] [--so-far] [--all-orgs] | prs [--days N | --from YYYY-MM-DD [--to YYYY-MM-DD] | --all-time] [--day YYYY-MM-DD] [--org id] [--state merged|open|closed] [--top N] [--tz zone] [--all-orgs] | failures [--days N | --from YYYY-MM-DD [--to YYYY-MM-DD] | --all-time] [--day YYYY-MM-DD] [--org id] [--failure ours|agent|setup|undelivered] [--cause c] [--stopped-at step] [--repo owner/name] [--slow] [--tz zone] [--all-orgs] | blocked [--days N] [--org id] [--all-orgs] | unblock <org-id> <host> <subject> | decisions <org-id> [--site host] [--surface plan|fix|build|site] | undo <org-id> <host> <decision-id> | runs [--status s] [--error-code c] [--first-review-cause agent|context|setup|unknown] [--pr merged|closed|open|none] [--harness h] [--days N] | beyond [--limit N] [--before ISO] | companies | company <org-id> [--site host] | facts <org-id> [--site host] | fits [--recipe id] [--org id] [--site host] [--fits yes|no|unknown|waiting] [--limit N] | replay <run-id> | mark <run-id> <item-id> <right|weak|wrong|lead|gap|recipe|clear> [--note …] | settings | set <weights.<ask>.<reading>|lenses.<lens>.bar.<reading>|lenses.<lens>.bar|lenses.<lens>.research.turns|lenses.<lens>.research.ms|asks.<lens>.ask|asks.<lens>.guidance|dismissals.block_days|preflight.off|explore.ask|explore.research.turns|explore.research.ms|explore.every_days> <value>";
|
|
5267
5268
|
|
|
5268
5269
|
/** One number of the settings from a dotted path and a value: the PUT body save_engine_settings reads. */
|
|
5269
5270
|
function engineSettingsPatch(path, value) {
|
|
@@ -5506,6 +5507,30 @@ async function cmdAdminLetterpress(ctx) {
|
|
|
5506
5507
|
);
|
|
5507
5508
|
return 0;
|
|
5508
5509
|
}
|
|
5510
|
+
case "tokens": {
|
|
5511
|
+
// get_platform_usage: every customer's tokens day by day, Letterpress's on its own (Letterpress → Today).
|
|
5512
|
+
const q = new URLSearchParams({ days: String(flags.days ?? 30) });
|
|
5513
|
+
if (flagBool(flags["all-orgs"])) q.set("customers", "0");
|
|
5514
|
+
if (flagBool(flags["so-far"])) q.set("so_far", "1");
|
|
5515
|
+
const r = await staffRequest(ctx, "GET", `${PLATFORM_USAGE}?${q}`);
|
|
5516
|
+
const n = (v) => Math.round(v).toLocaleString("en-US");
|
|
5517
|
+
printResult(io, flags, r, (x) =>
|
|
5518
|
+
[
|
|
5519
|
+
`Tokens, ${x.since_day} to ${x.until_day} (${x.tz})${x.customer_only ? ", customers only" : ""}: ${n(x.totals.tokens)}, Letterpress ${n(x.totals.letterpress)}`,
|
|
5520
|
+
`by product: ${x.categories.map((c) => `${c.label} ${n(x.totals.by_category[c.key] ?? 0)}`).join(", ") || "—"}`,
|
|
5521
|
+
...x.series.map(
|
|
5522
|
+
(d) =>
|
|
5523
|
+
`${d.day} ${n(d.tokens).padStart(10)} tokens ${n(d.letterpress).padStart(8)} Letterpress`
|
|
5524
|
+
),
|
|
5525
|
+
...(x.so_far
|
|
5526
|
+
? [
|
|
5527
|
+
`today so far ${n(x.so_far.today)}; by this time yesterday ${n(x.so_far.yesterday)}, a week ago ${n(x.so_far.week_ago)}`,
|
|
5528
|
+
]
|
|
5529
|
+
: []),
|
|
5530
|
+
].join("\n")
|
|
5531
|
+
);
|
|
5532
|
+
return 0;
|
|
5533
|
+
}
|
|
5509
5534
|
case "prs": {
|
|
5510
5535
|
// get_pull_requests (the days) or, with --day / --org / --state, list_pull_requests (the PRs behind them).
|
|
5511
5536
|
const listing = flags.day || flags.org || flags.state;
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// Who may use it is one switch in the app (lib/replicator/access.ts): until it opens, the tools answer
|
|
4
4
|
// only a key an @letterstory.com person made. Free, within fair-use limits. Thin wrappers over the
|
|
5
5
|
// replicator tools (create_replication, list_replications, get_replication,
|
|
6
|
-
// attach_replication_repo, advance_replication, archive_replication), the
|
|
7
|
-
// app calls.
|
|
6
|
+
// attach_replication_repo, advance_replication, archive_replication, list_form_submissions), the
|
|
7
|
+
// same lib functions the app calls.
|
|
8
8
|
//
|
|
9
9
|
// letterstory replicate https://example.com [--max-pages 300] [--refine-budget 25] [--repo-name example-site] [--repo-owner <account>]
|
|
10
10
|
// letterstory replicate list
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// letterstory replicate attach <id> <repo-name> [--repo-owner <account>]
|
|
13
13
|
// letterstory replicate advance <id> [--retry] [--again] [--preview] [--extend-preview <days>]
|
|
14
14
|
// letterstory replicate archive <id>
|
|
15
|
+
// letterstory replicate forms [<id>] [--form <form>] [--limit <n>] [--before <iso>]
|
|
16
|
+
// letterstory replicate forms <id> --show-key (admins: LETTER_FORMS_URL and LETTER_FORMS_KEY to host it yourself)
|
|
15
17
|
|
|
16
18
|
import { CliError } from "../client.mjs";
|
|
17
19
|
import { argsFromFlags, printResult, refuseUnknownFlags } from "./shared.mjs";
|
|
@@ -44,6 +46,13 @@ export const ARGS = {
|
|
|
44
46
|
extend_preview_days: { flag: "extend-preview", type: "num" },
|
|
45
47
|
},
|
|
46
48
|
archive_replication: { replication_id: replicationId },
|
|
49
|
+
get_replication_forms_key: { replication_id: replicationId },
|
|
50
|
+
list_form_submissions: {
|
|
51
|
+
replication_id: { positional: 0, label: "replication-id" },
|
|
52
|
+
form: "form",
|
|
53
|
+
limit: { flag: "limit", type: "num" },
|
|
54
|
+
before: "before",
|
|
55
|
+
},
|
|
47
56
|
};
|
|
48
57
|
|
|
49
58
|
function call(ctx, tool) {
|
|
@@ -75,8 +84,15 @@ export async function cmdReplicate(ctx) {
|
|
|
75
84
|
return replicationShow(rest, await call(rest, "advance_replication"));
|
|
76
85
|
case "archive":
|
|
77
86
|
return replicationShow(rest, await call(rest, "archive_replication"));
|
|
87
|
+
case "forms":
|
|
88
|
+
if (rest.flags["show-key"] === true) {
|
|
89
|
+
refuseUnknownFlags(rest, ARGS.get_replication_forms_key, ["show-key"]);
|
|
90
|
+
return formsKey(rest, await call(rest, "get_replication_forms_key"));
|
|
91
|
+
}
|
|
92
|
+
refuseUnknownFlags(rest, ARGS.list_form_submissions, []);
|
|
93
|
+
return formSubmissions(rest, await call(rest, "list_form_submissions"));
|
|
78
94
|
default:
|
|
79
|
-
throw new CliError("Usage: replicate <https://site> | replicate [list|show|attach|advance|archive]");
|
|
95
|
+
throw new CliError("Usage: replicate <https://site> | replicate [list|show|attach|advance|archive|forms]");
|
|
80
96
|
}
|
|
81
97
|
}
|
|
82
98
|
|
|
@@ -201,6 +217,37 @@ function previewLines(x) {
|
|
|
201
217
|
return [];
|
|
202
218
|
}
|
|
203
219
|
|
|
220
|
+
// The two values the site's forms send to Letter with, to set where it is hosted (never in its repo).
|
|
221
|
+
function formsKey(ctx, result) {
|
|
222
|
+
printResult(ctx.io, ctx.flags, result, (r) =>
|
|
223
|
+
[
|
|
224
|
+
`LETTER_FORMS_URL=${r.url}`,
|
|
225
|
+
`LETTER_FORMS_KEY=${r.secret_key}`,
|
|
226
|
+
"",
|
|
227
|
+
"Set these where the site is hosted, the key as a secret. Never commit the key.",
|
|
228
|
+
].join("\n")
|
|
229
|
+
);
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// What people sent through the replicated sites' forms (Letter's default destination), newest first.
|
|
234
|
+
function formSubmissions(ctx, result) {
|
|
235
|
+
printResult(ctx.io, ctx.flags, result, (r) => {
|
|
236
|
+
if (!r.submissions?.length) return "No form submissions yet.";
|
|
237
|
+
const lines = [];
|
|
238
|
+
for (const s of r.submissions) {
|
|
239
|
+
lines.push(
|
|
240
|
+
`${s.received_at} ${s.host}${s.page ?? ""} ${s.form_name ?? s.form}${s.notified ? "" : ` (not emailed${s.notify_error ? `: ${s.notify_error}` : ""})`}`
|
|
241
|
+
);
|
|
242
|
+
for (const [name, value] of Object.entries(s.fields))
|
|
243
|
+
lines.push(` ${name}: ${Array.isArray(value) ? value.join(", ") : value}`);
|
|
244
|
+
}
|
|
245
|
+
if (r.next_before) lines.push("", `More: replicate forms --before ${r.next_before}`);
|
|
246
|
+
return lines.join("\n");
|
|
247
|
+
});
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
204
251
|
function replicationShow(ctx, result) {
|
|
205
252
|
printResult(ctx.io, ctx.flags, result, (r) =>
|
|
206
253
|
[...headline(r.replication), ...(r.notice ? [` Note ${r.notice}`] : []), ...limitsLines(r.limits)].join(
|