@letterstory/cli 0.176.98 → 0.176.100
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 +7 -1
- package/lib/commands/replicate.mjs +50 -3
- package/lib/commands/vary.mjs +16 -2
- 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.100";
|
|
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",
|
|
@@ -1990,6 +1992,7 @@ Domains (the domains you hold, and free ones that match what people search):
|
|
|
1990
1992
|
Vary (A/B tests of a page's words and images on your own site):
|
|
1991
1993
|
vary [list] [--status running] [--site <id>] Your variationals and how many visitors each has seen
|
|
1992
1994
|
vary create --page <url> [--name <n>] [--goals-file <path>]
|
|
1995
|
+
vary launch [--brief <words>] [--page <url>] An A/B test Letter writes from the page and starts
|
|
1993
1996
|
vary get <id> Variants, their changes, and the results against the original
|
|
1994
1997
|
vary page <id> The page's copy as changes need it, and whether each variant still applies
|
|
1995
1998
|
vary save-variant <id> --deltas-file <path> [--key b] [--name <n>]
|
|
@@ -2069,6 +2072,9 @@ Replicate (the Replicator: a website rebuilt as a native Next.js repo, page for
|
|
|
2069
2072
|
--again replicates from the start; --preview deploys its staging link again;
|
|
2070
2073
|
--extend-preview keeps the preview up longer (Letter's team only)
|
|
2071
2074
|
replicate archive <id> Stop it and take its preview down; the project stays in storage and in the repo
|
|
2075
|
+
replicate forms [<id>] [--form <f>] [--limit <n>] [--before <iso>]
|
|
2076
|
+
What people sent through the replicated sites' forms, newest first;
|
|
2077
|
+
--show-key (admins): LETTER_FORMS_URL and LETTER_FORMS_KEY to host it yourself
|
|
2072
2078
|
|
|
2073
2079
|
Evals (how the pipeline is doing on your posts, and the regression cases captured from them):
|
|
2074
2080
|
evals scoreboard [--collection <id>] [--days N] Every measure by stage: posts, failing, rate
|
|
@@ -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(
|
package/lib/commands/vary.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// `vary` — Variationals from a terminal: A/B tests of a page's words and images on your own site
|
|
2
2
|
// (docs/architecture/variationals.md). Thin wrappers over the variationals tools (list_variationals,
|
|
3
|
-
// get_variational, create_variational, update_variational, save_variational_variant,
|
|
3
|
+
// get_variational, create_variational, launch_variational_test, update_variational, save_variational_variant,
|
|
4
4
|
// remove_variational_variant, delete_variational, ship_variational_winner), the same lib functions the app calls.
|
|
5
5
|
//
|
|
6
6
|
// letterstory vary list [--status running]
|
|
7
7
|
// letterstory vary create --page https://acme.com/pricing [--name "Pricing headline"] [--goals-file goals.json]
|
|
8
|
+
// letterstory vary launch [--brief "Test the hero button"] [--page https://acme.com/] [--name <n>]
|
|
9
|
+
// made, given a challenger Letter writes from the page, and started (Ideas' A/B test row)
|
|
8
10
|
// letterstory vary get <id>
|
|
9
11
|
// letterstory vary page <id> the page's blocks, as a change's tag/before/occurrence
|
|
10
12
|
// letterstory vary save-variant <id> --deltas-file b.json [--key b] [--name "Shorter headline"]
|
|
@@ -74,6 +76,12 @@ export const ARGS = {
|
|
|
74
76
|
name: "name",
|
|
75
77
|
goals: jsonFile("goals-file", "goals"),
|
|
76
78
|
},
|
|
79
|
+
launch_variational_test: {
|
|
80
|
+
brief: "brief",
|
|
81
|
+
page_url: "page",
|
|
82
|
+
site_id: "site",
|
|
83
|
+
name: "name",
|
|
84
|
+
},
|
|
77
85
|
ship_variational_winner: {
|
|
78
86
|
variational_id: variationalId,
|
|
79
87
|
variant: "variant",
|
|
@@ -135,6 +143,12 @@ export async function cmdVary(ctx) {
|
|
|
135
143
|
case "create":
|
|
136
144
|
refuseUnknownFlags(rest, ARGS.create_variational, ["goals-file"]);
|
|
137
145
|
return varyShow(rest, await call(rest, "create_variational"));
|
|
146
|
+
case "launch": {
|
|
147
|
+
refuseUnknownFlags(rest, ARGS.launch_variational_test, []);
|
|
148
|
+
const result = await call(rest, "launch_variational_test");
|
|
149
|
+
printResult(rest.io, rest.flags, result, (r) => `${r.message}\n${r.variational.edit_url}`);
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
138
152
|
case "update":
|
|
139
153
|
case "weights":
|
|
140
154
|
case "goals":
|
|
@@ -170,7 +184,7 @@ export async function cmdVary(ctx) {
|
|
|
170
184
|
}
|
|
171
185
|
default:
|
|
172
186
|
throw new CliError(
|
|
173
|
-
"Usage: vary [list|get|page|create|update|weights|goals|start|pause|end|save-variant|remove-variant|ship|delete]"
|
|
187
|
+
"Usage: vary [list|get|page|create|launch|update|weights|goals|start|pause|end|save-variant|remove-variant|ship|delete]"
|
|
174
188
|
);
|
|
175
189
|
}
|
|
176
190
|
}
|