@letterstory/cli 0.176.100 → 0.176.102

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
@@ -144,6 +144,7 @@ letterstory domains # every domain y
144
144
  letterstory domains scan # brainstorms the searches in your space, free .com/.org (no charge)
145
145
  letterstory domains scan "nano banana pro" # or the phrases you give
146
146
  letterstory domains scan "nano banana pro" --tld com,org,ai,xyz --expand space
147
+ letterstory domains scan --domain olapdatabase.com # whether a domain you have in mind is free
147
148
  letterstory domains buy nanobananaprofree.com --dry-run # the price and whose account pays
148
149
  letterstory domains buy nanobananaprofree.com --yes # buys it (spends money; domains:write)
149
150
  ```
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.100";
90
+ export const VERSION = "0.176.102";
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;
@@ -96,6 +96,8 @@ export const EXIT_PAYMENT_REQUIRED = 3;
96
96
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
97
97
  // `negate` spelling) in a command's ARGS map must be listed; boolean-flags.test.ts enforces it.
98
98
  export const BOOLEAN_FLAGS = new Set([
99
+ // admin demo ideas: every live recipe, not only the Ideas that fit.
100
+ "library",
99
101
  // admin targets check: check every target again, checked or not.
100
102
  "recheck",
101
103
  // build list: also move the builds' open changes along while polling.
@@ -1981,6 +1983,7 @@ Domains (the domains you hold, and free ones that match what people search):
1981
1983
  [--no-modifiers] [--market US] [--limit <n>]
1982
1984
  Free .com/.org domains on what people search in your
1983
1985
  space (brainstormed, or the phrases given), ranked
1986
+ domains scan --domain <domain> [--domain …] Whether each domain is free (a bare name: .com and .org)
1984
1987
  domains buy <domain> --dry-run | --yes The price and whose account pays; --yes buys it
1985
1988
  domains plan <domain> [--phrase "<search>"] [--searches <n>] [--version <version>] [--no-write]
1986
1989
  The most strategic site for the domain, every version
@@ -22,7 +22,7 @@
22
22
  // admin letterpress companies | company <org-id> | facts <org-id> (the standing fit, per company)
23
23
  // admin letterpress beyond [--limit N] [--before ISO] (what members asked that went past the library)
24
24
  // admin letterpress settings|flip|set … (recipes:read / recipes:write)
25
- // admin demo guide|list|show|set|reset|check … (demo:read / demo:write)
25
+ // admin demo guide|list|show|set|reset|check|ideas … (demo:read / demo:write)
26
26
  // admin deployments [--health down|failing|blocked|setting_up|live|paused|archived] [--org <id>] [--q <text>] [--archived] [--test] [--no-host] (fleet:read)
27
27
  // admin models runs|run|star|unstar|scoreboard|requests|request|sampling … (models:read / models:write)
28
28
  // admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
@@ -6808,7 +6808,8 @@ async function cmdAdminBilling(ctx) {
6808
6808
 
6809
6809
  // `admin demo` — demo control (docs/architecture/demo-control.md): what each demo org shows,
6810
6810
  // set, rehearsed and reset. The staff routes behind /superadmin/demo, the same functions and
6811
- // schema as list_demo_orgs / get_demo_state / set_demo_state / reset_demo_state / run_demo_readiness_check.
6811
+ // schema as list_demo_orgs / get_demo_state / set_demo_state / reset_demo_state / run_demo_readiness_check /
6812
+ // run_demo_ideas / get_demo_ideas_run.
6812
6813
  const DEMO = "/api/superadmin/demo";
6813
6814
  const DEMO_ITEM_FLAGS = [
6814
6815
  "phantom",
@@ -6823,7 +6824,7 @@ const DEMO_ITEM_FLAGS = [
6823
6824
  "setup",
6824
6825
  ];
6825
6826
  const DEMO_USAGE =
6826
- "Usage: admin demo guide | list [--enrollable] | show <org-id> [--moves] | set <org-id> [--enroll] [--unenroll] [--file <state.json>] [--phantom parked|shown] [--main-blog …] [--visitors …] [--probes not_run|shown] [--cms hidden|shown] [--publish-target …] [--search-console …] [--github …] [--domain …] [--setup fresh|finished] [--domain-cap n] [--inject <key>]... [--hide <key>]... [--save-as <name>] [--baseline <owner/repo>[@sha] | --clear-baseline] [--copy-from <org-id> [--copy-scenarios a,b] [--overwrite]] [--auto-reset <scenario>|off] [--every <minutes>] [--release] | domains | new <domain> [--name <n>] [--brief <t>] [--full] [--host vercel|pages] [--no-wait] | teardown <org-id> [--keep-repo] | bootstrap <org-id>... --from <org-id> [--scenarios a,b] [--overwrite] | reset <org-id> <scenario> [--clean-slate] [--close-prs since_applied|all_open|none] [--hold <minutes>] [--restore-repo revert|force] [--rebuild] [--reread-company] [--forget-company] [--forget-site] | check <org-id>";
6827
+ "Usage: admin demo guide | list [--enrollable] | show <org-id> [--moves] | set <org-id> [--enroll] [--unenroll] [--file <state.json>] [--phantom parked|shown] [--main-blog …] [--visitors …] [--probes not_run|shown] [--cms hidden|shown] [--publish-target …] [--search-console …] [--github …] [--domain …] [--setup fresh|finished] [--domain-cap n] [--inject <key>]... [--hide <key>]... [--save-as <name>] [--baseline <owner/repo>[@sha] | --clear-baseline] [--copy-from <org-id> [--copy-scenarios a,b] [--overwrite]] [--auto-reset <scenario>|off] [--every <minutes>] [--release] | domains | new <domain> [--name <n>] [--brief <t>] [--full] [--host vercel|pages] [--no-wait] | teardown <org-id> [--keep-repo] | bootstrap <org-id>... --from <org-id> [--scenarios a,b] [--overwrite] | reset <org-id> <scenario> [--clean-slate] [--close-prs since_applied|all_open|none] [--hold <minutes>] [--restore-repo revert|force] [--rebuild] [--reread-company] [--forget-company] [--forget-site] | check <org-id> | ideas <org-id> [--library] [--preview] [--force] | ideas <org-id> --status [--id <run-id>]";
6827
6828
 
6828
6829
  const readinessLines = (r) =>
6829
6830
  r
@@ -6833,6 +6834,22 @@ const readinessLines = (r) =>
6833
6834
  ].join("\n")
6834
6835
  : "Not checked yet.";
6835
6836
 
6837
+ const ideasRunLines = (run) =>
6838
+ run
6839
+ ? [
6840
+ `${run.summary} (${run.state}${run.note && run.state !== "done" ? `: ${run.note}` : ""})`,
6841
+ `${run.scope === "library" ? "every live recipe" : "Ideas"} on ${run.host}, ${run.delivery === "build" ? "previews" : "pull requests"}${run.force ? ", forced" : ""}, by ${run.started_by_name ?? "staff"} at ${run.created_at} run ${run.id}`,
6842
+ ...run.items.map(
6843
+ (i) =>
6844
+ ` ${i.outcome.padEnd(9)} ${i.title}${i.reason && i.outcome !== "started" ? ` [${i.reason}]` : ""}${
6845
+ i.build
6846
+ ? ` build: ${i.build.archived ? "archived" : i.build.detail || i.build.kind}${i.build.pr_url ? ` ${i.build.pr_url}` : ""}`
6847
+ : ""
6848
+ }${i.message && i.outcome !== "started" ? `\n ${i.message}` : ""}`
6849
+ ),
6850
+ ].join("\n")
6851
+ : "No run yet.";
6852
+
6836
6853
  async function cmdAdminDemo(ctx) {
6837
6854
  const { flags, positionals, io } = ctx;
6838
6855
  const sub = positionals[0];
@@ -7071,6 +7088,26 @@ async function cmdAdminDemo(ctx) {
7071
7088
  printResult(io, flags, result, (r) => readinessLines(r.readiness));
7072
7089
  return result.readiness?.ok ? 0 : 1;
7073
7090
  }
7091
+ case "ideas": {
7092
+ // Every Idea on the demo org started for real (run_demo_ideas), or the latest run read (get_demo_ideas_run).
7093
+ const orgId = requirePositional(positionals, 1, "org-id");
7094
+ const path = `${DEMO}/${encodeURIComponent(orgId)}/ideas`;
7095
+ const id = flagStr(flags.id);
7096
+ const result =
7097
+ flagBool(flags.status) || id
7098
+ ? await staffRequest(ctx, "GET", id ? `${path}?id=${encodeURIComponent(id)}` : path)
7099
+ : await staffPost(
7100
+ ctx,
7101
+ path,
7102
+ compact({
7103
+ scope: flagBool(flags.library) ? "library" : undefined,
7104
+ delivery: flagBool(flags.preview) ? "build" : undefined,
7105
+ force: flagBool(flags.force) || undefined,
7106
+ })
7107
+ );
7108
+ printResult(io, flags, result, (r) => ideasRunLines(r.run));
7109
+ return result.run?.state === "failed" ? 1 : 0;
7110
+ }
7074
7111
  default:
7075
7112
  throw new CliError(DEMO_USAGE);
7076
7113
  }
@@ -5,6 +5,7 @@
5
5
  // letterstory domains [list]
6
6
  // letterstory domains scan brainstorms the searches in your space, checks .com and .org
7
7
  // letterstory domains scan "nano banana pro" ["ai headshots" …] [--tld com,org,ai] [--expand none|searches|space]
8
+ // letterstory domains scan --domain olapdatabase.com [--domain …] whether each is free (a bare name: .com and .org)
8
9
  // letterstory domains buy nanobananaprofree.com --dry-run the price and whose account pays; buys nothing
9
10
  // letterstory domains buy nanobananaprofree.com --yes buys it (spends money; domains:write)
10
11
  // letterstory domains plan bestaidatalabs.com [--phrase "best ai data labs"] [--searches 880] [--version comparison]
@@ -22,6 +23,13 @@ export const ARGS = {
22
23
  // `domains scan "nano banana pro" ["ai headshots" …]` — each positional is one phrase; none brainstorms your space.
23
24
  scan_domains: {
24
25
  phrases: { value: (flags, positionals) => (positionals.length ? positionals : undefined) },
26
+ // `--domain olapdatabase.com` (repeatable or comma-separated): checked as it is; alone, it is only that check.
27
+ domains: {
28
+ value: (flags) => {
29
+ const domains = flagCsv(flags.domain).map(stripScheme);
30
+ return domains.length ? domains : undefined;
31
+ },
32
+ },
25
33
  // `--tld com,org` or `--tld com --tld org`.
26
34
  tlds: {
27
35
  value: (flags) => {
@@ -205,6 +213,7 @@ async function domainsLaunch(ctx) {
205
213
  }
206
214
 
207
215
  const STATUS_MARK = { available: "free", taken: "·", unknown: "?", unchecked: " " };
216
+ const NAMED_MARK = { available: "free", taken: "taken", unknown: "?" };
208
217
 
209
218
  async function domainsScan(ctx) {
210
219
  const { client, flags, io } = ctx;
@@ -218,20 +227,34 @@ async function domainsScan(ctx) {
218
227
  ? "not sold here"
219
228
  : `$${p.purchase_usd}${p.purchasable ? "" : " (over cap)"}`;
220
229
  const lines = [];
221
- if (r.space) lines.push(`Searches brainstormed for your space: ${r.space}`);
222
- lines.push(`Free domains (${r.available} of ${r.checked} checked, searches in ${r.market}):`);
223
- for (const d of r.domains) {
224
- lines.push(
225
- ` ${String(d.score).padStart(5)} ${d.domain.padEnd(32)} ${d.match.padEnd(8)} ${searches(d.monthly_searches).padStart(8)}/mo ${d.phrase} ${price(d.price)}`
226
- );
230
+ const named = r.named ?? [];
231
+ if (named.length) {
232
+ const rows = new Map(r.domains.map((d) => [d.domain, d]));
233
+ lines.push("Domains you named:");
234
+ for (const n of named) {
235
+ lines.push(
236
+ ` ${NAMED_MARK[n.status].padEnd(6)} ${n.domain.padEnd(32)} ${price(rows.get(n.domain)?.price)}`.trimEnd()
237
+ );
238
+ }
227
239
  }
228
- if (!r.domains.length) lines.push(" None free. Try phrases of your own, --expand space, or more --tld.");
229
- lines.push("", `Exact match on each TLD (free / · taken):`);
230
- lines.push(` ${"".padEnd(30)} ${"searches".padStart(8)} ${r.tlds.map((t) => t.padEnd(5)).join("")}`);
231
- for (const row of r.coverage) {
232
- lines.push(
233
- ` ${row.phrase.slice(0, 30).padEnd(30)} ${searches(row.monthly_searches).padStart(8)} ${row.cells.map((c) => STATUS_MARK[c.status].padEnd(5)).join("")}`
234
- );
240
+ // Named alone, the check is the whole answer: there was no scan of searches to print.
241
+ if (!named.length || r.phrases.length) {
242
+ if (named.length) lines.push("");
243
+ if (r.space) lines.push(`Searches brainstormed for your space: ${r.space}`);
244
+ lines.push(`Free domains (${r.available} of ${r.checked} checked, searches in ${r.market}):`);
245
+ for (const d of r.domains) {
246
+ lines.push(
247
+ ` ${String(d.score).padStart(5)} ${d.domain.padEnd(32)} ${d.match.padEnd(8)} ${searches(d.monthly_searches).padStart(8)}/mo ${d.phrase} ${price(d.price)}`
248
+ );
249
+ }
250
+ if (!r.domains.length) lines.push(" None free. Try phrases of your own, --expand space, or more --tld.");
251
+ lines.push("", `Exact match on each TLD (free / · taken):`);
252
+ lines.push(` ${"".padEnd(30)} ${"searches".padStart(8)} ${r.tlds.map((t) => t.padEnd(5)).join("")}`);
253
+ for (const row of r.coverage) {
254
+ lines.push(
255
+ ` ${row.phrase.slice(0, 30).padEnd(30)} ${searches(row.monthly_searches).padStart(8)} ${row.cells.map((c) => STATUS_MARK[c.status].padEnd(5)).join("")}`
256
+ );
257
+ }
235
258
  }
236
259
  for (const note of r.notes) lines.push(`Note: ${note}`);
237
260
  return lines.join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.100",
3
+ "version": "0.176.102",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",