@letterstory/cli 0.176.47 → 0.176.49

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
@@ -80,11 +80,12 @@ import {
80
80
  cmdBench,
81
81
  cmdReplicate,
82
82
  cmdDeploy,
83
+ cmdRefs,
83
84
  } from "./commands.mjs";
84
85
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
85
86
 
86
87
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
87
- export const VERSION = "0.176.47";
88
+ export const VERSION = "0.176.49";
88
89
 
89
90
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
90
91
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -1723,6 +1724,7 @@ Build (a change to your site, or launching what isn't code; the Build box's own
1723
1724
  done by Build itself (a phantom site buys its own domain), and the
1724
1725
  build reads Done with no PR to merge. --again repeats an open build's ask.
1725
1726
  --new-site: a website of its own, built and hosted (deploy create)
1727
+ --look-like a.com,b.com (with --new-site): sites to model its look after
1726
1728
  build talk <words…> [--conversation <id>|--build <id>] [--site <host>] [--harness letterpress-1|claude-code|codex]
1727
1729
  [--repo owner/name] [--action build|build_pr|build_merge]
1728
1730
  One turn of a conversation with Build (questions, follow-ups on a build)
@@ -1811,7 +1813,7 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letter GitHub App op
1811
1813
  repo install-letterprove --repo <owner/name> [--gitlab] [--branch <base>] [--action build|build_pr|build_merge|slack|linear] [--wait]
1812
1814
  One PR adding Letterprove's script and identify calls
1813
1815
  (pick the repo where users sign in)
1814
- repo install-visitors [--site <id>] [--repo <owner/name> [--gitlab]] [--branch <base>] [--action build|build_pr|build_merge|slack|linear] [--privacy-only] [--wait]
1816
+ repo install-visitors [--site <id> | --host <domain>] [--repo <owner/name> [--gitlab]] [--branch <base>] [--action build|build_pr|build_merge|slack|linear] [--privacy-only] [--wait]
1815
1817
  One PR adding the Visitors script tag and server snippet to the repo that
1816
1818
  builds the site (--repo: another repo)
1817
1819
  repo sites [--detect [--force]] Which website each repo builds, and the evidence
@@ -1891,9 +1893,10 @@ Vary (A/B tests of a page's words and images on your own site):
1891
1893
 
1892
1894
  Deploy (websites and apps Letter builds from your words and hosts on their own domains):
1893
1895
  deploy [list] [--kind app|phantom|bench] Everything deployed: apps, phantoms (and benches, for staff)
1894
- deploy create "<what it is and who it is for>" [--name <n>] [--domain <d>] [--wait]
1896
+ deploy create "<what it is and who it is for>" [--name <n>] [--domain <d>] [--ref <visual-ref-id>] [--wait]
1895
1897
  Named, given a domain (bought under $30 unless you name one),
1896
- built by Letterpress-1 with Visitors in it, and put live
1898
+ built by Letterpress-1 with Visitors in it, and put live;
1899
+ --ref designs it after a Visual Ref (refs create)
1897
1900
  deploy show <id> Where it stands, its domain, the records to set, recent events
1898
1901
  deploy update <id> [--name <n>] [--domain <d>] The domain only before it is built
1899
1902
  deploy pause <id> | deploy resume <id> | deploy archive <id>
@@ -1902,6 +1905,17 @@ Deploy (websites and apps Letter builds from your words and hosts on their own d
1902
1905
  deploy connect <id> <owner/name> Move its source to your GitHub repo (empty); it's
1903
1906
  pushed once the Letter GitHub App can reach it
1904
1907
 
1908
+ Refs (Visual Refs: other companies' brands to design something new after, apart from your own brand):
1909
+ refs [list] [--archived] Your Visual Refs and their brands
1910
+ refs create "<name>" --brand <host[=what to take]>… [--notes <n>]
1911
+ Each brand is learned from its site (a few minutes)
1912
+ refs show <id> Its brands, whether each is learned, their type
1913
+ refs update <id> [--name <n>] [--notes <n>] [--brand <host[=note]>]… [--remove <host>]…
1914
+ refs archive <id> | refs restore <id>
1915
+ refs brief <id> "<what is being made, for whom>" [--wait]
1916
+ Every brand's direction for it, as one file
1917
+ refs brief-show <brief-id> The brief, once ready
1918
+
1905
1919
  Bench (Benches: open-source benchmarks on their own domains, kept running; @letterstory.com only):
1906
1920
  bench [list] Your benches: stage, what each waits on, domain
1907
1921
  bench create (--prompt "<what it is and how it works>"|--prompt-file <path>) [--name <n>] [--domain <d>]
@@ -2036,6 +2050,7 @@ const CLIENT_COMMANDS = {
2036
2050
  bench: cmdBench,
2037
2051
  replicate: cmdReplicate,
2038
2052
  deploy: cmdDeploy,
2053
+ refs: cmdRefs,
2039
2054
  // Platform-scoped (staff key) — the only commands that act BEFORE an org exists.
2040
2055
  admin: cmdAdmin,
2041
2056
  };
@@ -16,7 +16,16 @@
16
16
 
17
17
  import { readFileSync } from "node:fs";
18
18
  import { CliError } from "../client.mjs";
19
- import { argsFromFlags, flagList, flagStr, followJob, ok, printResult, refuseUnknownFlags } from "./shared.mjs";
19
+ import {
20
+ argsFromFlags,
21
+ flagCsv,
22
+ flagList,
23
+ flagStr,
24
+ followJob,
25
+ ok,
26
+ printResult,
27
+ refuseUnknownFlags,
28
+ } from "./shared.mjs";
20
29
  import { attachmentUrls, uploadAttachments } from "./repo.mjs";
21
30
 
22
31
  const ARGS = {
@@ -54,6 +63,8 @@ const ARGS = {
54
63
  recipe: "recipe",
55
64
  // --new-site: a website or app of its own, built and hosted on its own domain (deploy create's path).
56
65
  new_site: { flag: "new-site", type: "bool" },
66
+ // --new-site --look-like linear.app,supabase.com: other companies' sites to model its look after.
67
+ look_like: { value: (flags) => (flagCsv(flags["look-like"]).length ? flagCsv(flags["look-like"]) : undefined) },
57
68
  // --plan-only: answer what it would do and start nothing (--json for the plan to send back).
58
69
  plan_only: { flag: "plan-only", type: "bool" },
59
70
  // --plan-only always plans what it's given, as /plan does: it was asked for by name.
@@ -3,7 +3,7 @@
3
3
  // Thin wrappers over the deployment tools (create_deployment, list_deployments, get_deployment,
4
4
  // update_deployment, advance_deployment), the same lib functions the app calls.
5
5
  //
6
- // letterstory deploy create "Build me a website for HVAC contractors to collect leads" [--name <n>] [--domain <d>] [--wait]
6
+ // letterstory deploy create "Build me a website for HVAC contractors to collect leads" [--name <n>] [--domain <d>] [--ref <visual-ref-id>] [--wait]
7
7
  // letterstory deploy list [--kind app|phantom|bench]
8
8
  // letterstory deploy show <id>
9
9
  // letterstory deploy update <id> [--name <n>] [--domain <d>]
@@ -29,6 +29,7 @@ export const ARGS = {
29
29
  prompt: { value: (flags, positionals) => flagStr(flags.prompt) ?? (positionals.join(" ").trim() || undefined) },
30
30
  name: "name",
31
31
  domain: "domain",
32
+ visual_ref_id: { flag: "ref" },
32
33
  wait: { flag: "wait", type: "bool" },
33
34
  },
34
35
  list_deployments: { kind: { flag: "kind", enum: ["app", "phantom", "bench"] } },
@@ -0,0 +1,131 @@
1
+ // `refs` — Visual Refs from a terminal (docs/architecture/visual-refs.md): sets of other companies'
2
+ // brands to design something new after, kept apart from your own brand. Thin wrappers over the
3
+ // Visual Ref tools (create_visual_ref, list_visual_refs, get_visual_ref, update_visual_ref,
4
+ // brief_visual_ref, get_visual_ref_brief), the same lib functions the app calls.
5
+ //
6
+ // letterstory refs list [--archived]
7
+ // letterstory refs create "Linear × Supabase" --brand linear.app --brand "supabase.com=the dark palette" [--notes <n>]
8
+ // letterstory refs show <id>
9
+ // letterstory refs update <id> [--name <n>] [--notes <n>] [--brand <host[=note]>]… [--remove <host>]…
10
+ // letterstory refs archive <id> | refs restore <id>
11
+ // letterstory refs brief <id> "<what is being made, for whom>" [--wait]
12
+ // letterstory refs brief-show <brief-id>
13
+ // letterstory deploy create "<the site>" --ref <id> a new site designed after the ref
14
+
15
+ import { CliError } from "../client.mjs";
16
+ import { argsFromFlags, flagList, flagStr, printResult, refuseUnknownFlags } from "./shared.mjs";
17
+
18
+ const refId = { positional: 0, label: "ref-id", required: true };
19
+
20
+ /** `--brand host` or `--brand "host=what to take from it"`, repeatable. */
21
+ function brandsOf(flags) {
22
+ const brands = flagList(flags.brand).map((raw) => {
23
+ const at = raw.indexOf("=");
24
+ const host = (at < 0 ? raw : raw.slice(0, at)).trim();
25
+ const note = at < 0 ? "" : raw.slice(at + 1).trim();
26
+ return note ? { host, note } : { host };
27
+ });
28
+ return brands.length ? brands : undefined;
29
+ }
30
+
31
+ export const ARGS = {
32
+ create_visual_ref: {
33
+ name: { value: (flags, positionals) => flagStr(flags.name) ?? (positionals.join(" ").trim() || undefined) },
34
+ notes: "notes",
35
+ brands: { value: (flags) => brandsOf(flags) },
36
+ },
37
+ list_visual_refs: { archived: { flag: "archived", type: "bool" } },
38
+ get_visual_ref: { ref_id: refId },
39
+ update_visual_ref: {
40
+ ref_id: refId,
41
+ name: "name",
42
+ notes: "notes",
43
+ add_brands: { value: (flags) => brandsOf(flags) },
44
+ remove_hosts: { value: (flags) => (flagList(flags.remove).length ? flagList(flags.remove) : undefined) },
45
+ archived: { internal: "refs archive / refs restore" },
46
+ },
47
+ brief_visual_ref: {
48
+ ref_id: refId,
49
+ task: {
50
+ value: (flags, positionals) => flagStr(flags.task) ?? (positionals.slice(1).join(" ").trim() || undefined),
51
+ },
52
+ wait: { flag: "wait", type: "bool" },
53
+ },
54
+ get_visual_ref_brief: { brief_id: { positional: 0, label: "brief-id", required: true } },
55
+ };
56
+
57
+ function call(ctx, tool, extra = {}) {
58
+ return ctx.client.callTool(tool, { ...argsFromFlags(ARGS[tool], ctx.flags, ctx.positionals), ...extra });
59
+ }
60
+
61
+ export async function cmdRefs(ctx) {
62
+ const sub = ctx.positionals[0];
63
+ const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
64
+ switch (sub) {
65
+ case undefined:
66
+ case "list":
67
+ case "ls": {
68
+ refuseUnknownFlags(rest, ARGS.list_visual_refs, []);
69
+ const result = await call(rest, "list_visual_refs");
70
+ printResult(rest.io, rest.flags, result, (r) => {
71
+ if (!r.refs?.length)
72
+ return 'No Visual Refs yet. Make one: refs create "Linear × Supabase" --brand linear.app --brand supabase.com';
73
+ return r.refs
74
+ .map((v) => `${v.id} ${v.name.slice(0, 28).padEnd(28)} ${v.brands.map((b) => b.host).join(", ")}`)
75
+ .join("\n");
76
+ });
77
+ return 0;
78
+ }
79
+ case "create": {
80
+ refuseUnknownFlags(rest, ARGS.create_visual_ref, ["brand", "name"]);
81
+ const args = argsFromFlags(ARGS.create_visual_ref, rest.flags, rest.positionals);
82
+ if (!args.name || !args.brands)
83
+ throw new CliError('Name it and give its brands: refs create "Linear × Supabase" --brand linear.app');
84
+ return refShow(rest, await rest.client.callTool("create_visual_ref", args));
85
+ }
86
+ case "show":
87
+ return refShow(rest, await call(rest, "get_visual_ref"));
88
+ case "update":
89
+ refuseUnknownFlags(rest, ARGS.update_visual_ref, ["brand", "remove"]);
90
+ return refShow(rest, await call(rest, "update_visual_ref"));
91
+ case "archive":
92
+ case "restore":
93
+ return refShow(rest, await call(rest, "update_visual_ref", { archived: sub === "archive" }));
94
+ case "brief": {
95
+ refuseUnknownFlags(rest, ARGS.brief_visual_ref, ["task"]);
96
+ return briefShow(rest, await call(rest, "brief_visual_ref"));
97
+ }
98
+ case "brief-show":
99
+ return briefShow(rest, await call(rest, "get_visual_ref_brief"));
100
+ default:
101
+ throw new CliError("Usage: refs [list|create|show|update|archive|restore|brief|brief-show]");
102
+ }
103
+ }
104
+
105
+ function refShow(ctx, result) {
106
+ printResult(ctx.io, ctx.flags, result, (r) => {
107
+ const v = r.ref;
108
+ const lines = [`${v.name}${v.archivedAt ? " (archived)" : ""} ${v.id}`, ...(v.notes ? [` ${v.notes}`] : [])];
109
+ for (const b of v.brands) {
110
+ const state = b.brand ? b.brand.state : "";
111
+ lines.push(` ${b.host.padEnd(28)} ${state.padEnd(12)} ${b.note ?? ""}`.trimEnd());
112
+ const type = b.brand?.profile?.type?.map((t) => t.family).filter(Boolean);
113
+ if (type?.length) lines.push(` type: ${[...new Set(type)].join(", ")}`);
114
+ }
115
+ lines.push("", `Brief a task on it: refs brief ${v.id} "<what is being made, for whom>"`);
116
+ return lines.join("\n");
117
+ });
118
+ return 0;
119
+ }
120
+
121
+ function briefShow(ctx, result) {
122
+ printResult(ctx.io, ctx.flags, result, (r) => {
123
+ const b = r.brief;
124
+ if (b.status === "pending") {
125
+ const parts = b.parts.map((p) => ` ${p.host.padEnd(28)} ${p.status}`);
126
+ return [`Brief ${b.id}: pending`, ...parts, "", `Check again: refs brief-show ${b.id}`].join("\n");
127
+ }
128
+ return b.markdown ?? `Brief ${b.id}: ${b.status}`;
129
+ });
130
+ return 0;
131
+ }
@@ -175,6 +175,7 @@ const ARGS = {
175
175
  visitors_open_install_pr: {
176
176
  provider,
177
177
  site_id: "site",
178
+ host: "host",
178
179
  repo_owner: { value: (flags) => splitRepo(flags).owner },
179
180
  repo_name: { value: (flags) => splitRepo(flags).name },
180
181
  base_branch: "branch",
@@ -566,7 +567,7 @@ async function repoInstallLetterprove(ctx) {
566
567
  return followRun(ctx, result.run);
567
568
  }
568
569
 
569
- /** `repo install-visitors [--site id] [--repo owner/name] [--branch b] [--privacy-only] [--wait]`: the Visitors script tag and server snippet → one PR. */
570
+ /** `repo install-visitors [--site id | --host getstylie.com] [--repo owner/name] [--branch b] [--privacy-only] [--wait]`: the Visitors script tag and server snippet → one PR. */
570
571
  async function repoInstallVisitors(ctx) {
571
572
  const { client, flags, io } = ctx;
572
573
  refuseUnknownFlags(ctx, {}, [
package/lib/commands.mjs CHANGED
@@ -57,3 +57,4 @@ export * from "./commands/domains.mjs";
57
57
  export * from "./commands/bench.mjs";
58
58
  export * from "./commands/replicate.mjs";
59
59
  export * from "./commands/deploy.mjs";
60
+ export * from "./commands/refs.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.47",
3
+ "version": "0.176.49",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",