@letterstory/cli 0.176.48 → 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.48";
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)
@@ -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
+ }
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.48",
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",