@letterstory/cli 0.176.62 → 0.176.64

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
@@ -85,7 +85,7 @@ import {
85
85
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
86
86
 
87
87
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
88
- export const VERSION = "0.176.62";
88
+ export const VERSION = "0.176.64";
89
89
 
90
90
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
91
91
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -1902,6 +1902,12 @@ Domains (the domains you hold, and free ones that match what people search):
1902
1902
  Free .com/.org domains on what people search in your
1903
1903
  space (brainstormed, or the phrases given), ranked
1904
1904
  domains buy <domain> --dry-run | --yes The price and whose account pays; --yes buys it
1905
+ domains plan <domain> [--phrase "<search>"] [--searches <n>]
1906
+ The most strategic site for the domain, every version
1907
+ scored, and whether it can launch; spends nothing
1908
+ domains launch <domain> [--phrase …] [--searches …] [--words "<plan words>"] --yes
1909
+ Build and Launch Site: buys the domain if needed, builds
1910
+ the plan and puts it live
1905
1911
 
1906
1912
  Vary (A/B tests of a page's words and images on your own site):
1907
1913
  vary [list] [--status running] [--site <id>] Your variationals and how many visitors each has seen
@@ -1962,7 +1968,7 @@ Replicate (the Replicator: a website rebuilt as a native Next.js repo, page for
1962
1968
  replicate [list] Your replications: stage, how closely each matches
1963
1969
  replicate show <id> [--failing] Pages with their routes and pixel match; collections; events
1964
1970
  replicate attach <id> <repo-name> [--repo-owner <o>] The repo to commit it into (yours; Letter never creates it)
1965
- replicate advance <id> [--retry] Move it on now; --retry once blocked_on is done
1971
+ replicate advance <id> [--retry] [--again] Move it on now; --retry once blocked_on is done; --again replicates from the start
1966
1972
  replicate archive <id> Stop it; the project stays in storage and in the repo
1967
1973
 
1968
1974
  Evals (how the pipeline is doing on your posts, and the regression cases captured from them):
@@ -7,6 +7,11 @@
7
7
  // letterstory domains scan "nano banana pro" ["ai headshots" …] [--tld com,org,ai] [--expand none|searches|space]
8
8
  // letterstory domains buy nanobananaprofree.com --dry-run the price and whose account pays; buys nothing
9
9
  // letterstory domains buy nanobananaprofree.com --yes buys it (spends money; domains:write)
10
+ // letterstory domains plan bestaidatalabs.com [--phrase "best ai data labs"] [--searches 880]
11
+ // the most strategic site for it; spends nothing
12
+ // letterstory domains launch bestaidatalabs.com [--phrase …] [--searches …] --yes
13
+ // Build and Launch Site: buys it if needed, builds the
14
+ // plan and puts it live (spends money; domains:write)
10
15
 
11
16
  import { CliError } from "../client.mjs";
12
17
  import { flagCsv, requirePositional, printResult, argsFromFlags } from "./shared.mjs";
@@ -32,6 +37,19 @@ export const ARGS = {
32
37
  domain: { value: (flags, positionals) => stripScheme(requirePositional(positionals, 0, "domain")) },
33
38
  dry_run: { flag: "dry-run", type: "bool" },
34
39
  },
40
+ // `domains plan <domain>` — the scan row's search and its monthly searches, when you have them.
41
+ plan_domain_site: {
42
+ domain: { value: (flags, positionals) => stripScheme(requirePositional(positionals, 0, "domain")) },
43
+ phrase: { flag: "phrase" },
44
+ monthly_searches: { flag: "searches", type: "num" },
45
+ },
46
+ launch_domain_site: {
47
+ domain: { value: (flags, positionals) => stripScheme(requirePositional(positionals, 0, "domain")) },
48
+ phrase: { flag: "phrase" },
49
+ monthly_searches: { flag: "searches", type: "num" },
50
+ // The words of a plan already read (`domains plan … --json`'s plan.words): that plan is built as it was.
51
+ words: { flag: "words" },
52
+ },
35
53
  };
36
54
 
37
55
  function stripScheme(value) {
@@ -61,8 +79,18 @@ export async function cmdDomains(ctx) {
61
79
  return 1;
62
80
  }
63
81
  return domainsBuy(rest);
82
+ case "plan":
83
+ return domainsPlan(rest);
84
+ case "launch":
85
+ if (rest.flags.yes !== true) {
86
+ rest.io.error(
87
+ "Launching a site buys its domain (real money) and builds it. Run `domains plan` to see the site and the price, then launch with --yes."
88
+ );
89
+ return 1;
90
+ }
91
+ return domainsLaunch(rest);
64
92
  default:
65
- throw new CliError(`Unknown domains subcommand: ${sub}. Try: list, scan, buy`);
93
+ throw new CliError(`Unknown domains subcommand: ${sub}. Try: list, scan, buy, plan, launch`);
66
94
  }
67
95
  }
68
96
 
@@ -117,6 +145,59 @@ async function domainsBuy(ctx) {
117
145
  return 0;
118
146
  }
119
147
 
148
+ /** A plan as the terminal reads it: the version and why, every score, the pages, and what the domain costs. */
149
+ function planLines(plan) {
150
+ return [
151
+ `${plan.domain}: ${plan.label} for "${plan.phrase}"${plan.monthly_searches != null ? ` (${plan.monthly_searches.toLocaleString("en-US")} searches/mo)` : ""}`,
152
+ ` ${plan.promise}`,
153
+ ` For: ${plan.audience}`,
154
+ ` Why it wins: ${plan.angle}`,
155
+ ` ${plan.company_role}`,
156
+ " Pages:",
157
+ ...plan.pages.map((p) => ` ${p.path.padEnd(16)} ${p.title}: ${p.purpose}`),
158
+ " Scores:",
159
+ ...plan.scores.map((s) =>
160
+ ` ${String(s.score).padStart(3)} ${s.label.padEnd(18)} ${s.reasons.join("; ")}`.trimEnd()
161
+ ),
162
+ ];
163
+ }
164
+
165
+ async function domainsPlan(ctx) {
166
+ const { client, flags, io } = ctx;
167
+ const result = await client.callTool(
168
+ "plan_domain_site",
169
+ argsFromFlags(ARGS.plan_domain_site, flags, ctx.positionals)
170
+ );
171
+ printResult(io, flags, result, (r) => {
172
+ const s = r.domain_state;
173
+ const state = !s.launchable
174
+ ? `Can't launch: ${s.reason}`
175
+ : s.outcome === "held"
176
+ ? "The domain is yours: nothing to buy. Launch with: letterstory domains launch " +
177
+ r.plan.domain +
178
+ " --yes"
179
+ : `The domain is free: $${s.price_usd} for the first year, billed in tokens. Launch with: letterstory domains launch ${r.plan.domain} --yes`;
180
+ return [...planLines(r.plan), "", state].join("\n");
181
+ });
182
+ return 0;
183
+ }
184
+
185
+ async function domainsLaunch(ctx) {
186
+ const { client, flags, io } = ctx;
187
+ const result = await client.callTool(
188
+ "launch_domain_site",
189
+ argsFromFlags(ARGS.launch_domain_site, flags, ctx.positionals)
190
+ );
191
+ printResult(io, flags, result, (r) =>
192
+ [
193
+ ...(r.plan ? planLines(r.plan) : []),
194
+ "",
195
+ `Launching ${r.app.domain ?? r.app.name} (${r.app.stage}${r.app.stage_note ? `: ${r.app.stage_note}` : ""}). Follow it with: letterstory deploy show ${r.app.id}`,
196
+ ].join("\n")
197
+ );
198
+ return 0;
199
+ }
200
+
120
201
  const STATUS_MARK = { available: "free", taken: "·", unknown: "?", unchecked: " " };
121
202
 
122
203
  async function domainsScan(ctx) {
@@ -9,7 +9,7 @@
9
9
  // letterstory replicate list
10
10
  // letterstory replicate show <id> [--failing]
11
11
  // letterstory replicate attach <id> <repo-name> [--repo-owner <account>]
12
- // letterstory replicate advance <id> [--retry]
12
+ // letterstory replicate advance <id> [--retry] [--again]
13
13
  // letterstory replicate archive <id>
14
14
 
15
15
  import { CliError } from "../client.mjs";
@@ -35,7 +35,11 @@ export const ARGS = {
35
35
  repo_name: { positional: 1, label: "repo-name", required: true },
36
36
  repo_owner: "repo-owner",
37
37
  },
38
- advance_replication: { replication_id: replicationId, retry: { flag: "retry", type: "bool" } },
38
+ advance_replication: {
39
+ replication_id: replicationId,
40
+ retry: { flag: "retry", type: "bool" },
41
+ again: { flag: "again", type: "bool" },
42
+ },
39
43
  archive_replication: { replication_id: replicationId },
40
44
  };
41
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.62",
3
+ "version": "0.176.64",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",