@letterstory/cli 0.176.48 → 0.176.50
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 +20 -4
- package/lib/commands/build.mjs +12 -1
- package/lib/commands/deploy.mjs +2 -1
- package/lib/commands/plan.mjs +4 -10
- package/lib/commands/refs.mjs +131 -0
- package/lib/commands.mjs +1 -0
- package/package.json +1 -1
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.
|
|
88
|
+
export const VERSION = "0.176.50";
|
|
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;
|
|
@@ -1676,7 +1677,8 @@ Insights:
|
|
|
1676
1677
|
Plan tab's box), what's broken (--lens fix, most pressing
|
|
1677
1678
|
first), what to build (--lens build), or your own words. Nothing asked:
|
|
1678
1679
|
each list's head. The same answer as the console and Slack.
|
|
1679
|
-
plan auto [activate|disable] [--site host] [--veto-hours <1-168>|off] [--pace-hours <1-168>]
|
|
1680
|
+
plan auto [activate|disable] [--site host] [--veto-hours <1-168>|off] [--pace-hours <1-168>]
|
|
1681
|
+
[--delivery build|build_pr|build_merge]
|
|
1680
1682
|
[--aggression low|medium|high|xhigh] [--goal geo|seo|agent_conviction|inbound_conversion|any]
|
|
1681
1683
|
[--slack-channel <C…>|none]
|
|
1682
1684
|
Auto-Plan on a site: Letterpress picks its strategies and builds
|
|
@@ -1723,6 +1725,7 @@ Build (a change to your site, or launching what isn't code; the Build box's own
|
|
|
1723
1725
|
done by Build itself (a phantom site buys its own domain), and the
|
|
1724
1726
|
build reads Done with no PR to merge. --again repeats an open build's ask.
|
|
1725
1727
|
--new-site: a website of its own, built and hosted (deploy create)
|
|
1728
|
+
--look-like a.com,b.com (with --new-site): sites to model its look after
|
|
1726
1729
|
build talk <words…> [--conversation <id>|--build <id>] [--site <host>] [--harness letterpress-1|claude-code|codex]
|
|
1727
1730
|
[--repo owner/name] [--action build|build_pr|build_merge]
|
|
1728
1731
|
One turn of a conversation with Build (questions, follow-ups on a build)
|
|
@@ -1891,9 +1894,10 @@ Vary (A/B tests of a page's words and images on your own site):
|
|
|
1891
1894
|
|
|
1892
1895
|
Deploy (websites and apps Letter builds from your words and hosts on their own domains):
|
|
1893
1896
|
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]
|
|
1897
|
+
deploy create "<what it is and who it is for>" [--name <n>] [--domain <d>] [--ref <visual-ref-id>] [--wait]
|
|
1895
1898
|
Named, given a domain (bought under $30 unless you name one),
|
|
1896
|
-
built by Letterpress-1 with Visitors in it, and put live
|
|
1899
|
+
built by Letterpress-1 with Visitors in it, and put live;
|
|
1900
|
+
--ref designs it after a Visual Ref (refs create)
|
|
1897
1901
|
deploy show <id> Where it stands, its domain, the records to set, recent events
|
|
1898
1902
|
deploy update <id> [--name <n>] [--domain <d>] The domain only before it is built
|
|
1899
1903
|
deploy pause <id> | deploy resume <id> | deploy archive <id>
|
|
@@ -1902,6 +1906,17 @@ Deploy (websites and apps Letter builds from your words and hosts on their own d
|
|
|
1902
1906
|
deploy connect <id> <owner/name> Move its source to your GitHub repo (empty); it's
|
|
1903
1907
|
pushed once the Letter GitHub App can reach it
|
|
1904
1908
|
|
|
1909
|
+
Refs (Visual Refs: other companies' brands to design something new after, apart from your own brand):
|
|
1910
|
+
refs [list] [--archived] Your Visual Refs and their brands
|
|
1911
|
+
refs create "<name>" --brand <host[=what to take]>… [--notes <n>]
|
|
1912
|
+
Each brand is learned from its site (a few minutes)
|
|
1913
|
+
refs show <id> Its brands, whether each is learned, their type
|
|
1914
|
+
refs update <id> [--name <n>] [--notes <n>] [--brand <host[=note]>]… [--remove <host>]…
|
|
1915
|
+
refs archive <id> | refs restore <id>
|
|
1916
|
+
refs brief <id> "<what is being made, for whom>" [--wait]
|
|
1917
|
+
Every brand's direction for it, as one file
|
|
1918
|
+
refs brief-show <brief-id> The brief, once ready
|
|
1919
|
+
|
|
1905
1920
|
Bench (Benches: open-source benchmarks on their own domains, kept running; @letterstory.com only):
|
|
1906
1921
|
bench [list] Your benches: stage, what each waits on, domain
|
|
1907
1922
|
bench create (--prompt "<what it is and how it works>"|--prompt-file <path>) [--name <n>] [--domain <d>]
|
|
@@ -2036,6 +2051,7 @@ const CLIENT_COMMANDS = {
|
|
|
2036
2051
|
bench: cmdBench,
|
|
2037
2052
|
replicate: cmdReplicate,
|
|
2038
2053
|
deploy: cmdDeploy,
|
|
2054
|
+
refs: cmdRefs,
|
|
2039
2055
|
// Platform-scoped (staff key) — the only commands that act BEFORE an org exists.
|
|
2040
2056
|
admin: cmdAdmin,
|
|
2041
2057
|
};
|
package/lib/commands/build.mjs
CHANGED
|
@@ -16,7 +16,16 @@
|
|
|
16
16
|
|
|
17
17
|
import { readFileSync } from "node:fs";
|
|
18
18
|
import { CliError } from "../client.mjs";
|
|
19
|
-
import {
|
|
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.
|
package/lib/commands/deploy.mjs
CHANGED
|
@@ -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"] } },
|
package/lib/commands/plan.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `plan` — Auto-Plan on one site (Plan → Auto-Plan; Plan is scoped to one website, --site, else the
|
|
2
2
|
// organization's own domain). The same calls as the Plan's modal and its sidebar's Pending rows.
|
|
3
3
|
// plan auto → get_auto_plan
|
|
4
|
-
// plan auto [activate|disable] [--veto-hours <1-168>|off] [--pace-hours <1-168>] [--
|
|
4
|
+
// plan auto [activate|disable] [--veto-hours <1-168>|off] [--pace-hours <1-168>] [--delivery build|build_pr|build_merge]
|
|
5
5
|
// [--aggression low|medium|high|xhigh] [--goal geo|seo|agent_conviction|inbound_conversion|any]
|
|
6
6
|
// [--slack-channel <C…>|none] → set_auto_plan
|
|
7
7
|
// plan pending → list_auto_plan_proposals (status pending)
|
|
@@ -28,13 +28,6 @@ const jsonFlag = (name) => ({
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
});
|
|
31
|
-
const onOff = (name) => (flags) => {
|
|
32
|
-
const v = flagStr(flags[name]);
|
|
33
|
-
if (v === undefined) return undefined;
|
|
34
|
-
if (["on", "true", "1"].includes(v)) return true;
|
|
35
|
-
if (["off", "false", "0"].includes(v)) return false;
|
|
36
|
-
throw new CliError(`--${name} takes on or off.`);
|
|
37
|
-
};
|
|
38
31
|
|
|
39
32
|
export const ARGS = {
|
|
40
33
|
list_plan_work_purposes: { resource: jsonFlag("resource"), strategy_id: "strategy" },
|
|
@@ -71,7 +64,8 @@ export const ARGS = {
|
|
|
71
64
|
},
|
|
72
65
|
// --pace-hours <1-168> how often it picks the next action (1 every hour, 24 every day, 168 every week).
|
|
73
66
|
pace_hours: { flag: "pace-hours", type: "num" },
|
|
74
|
-
|
|
67
|
+
// How far each pick goes: a preview, a pull request, or one merged once ready.
|
|
68
|
+
delivery: { flag: "delivery", enum: ["build", "build_pr", "build_merge"] },
|
|
75
69
|
aggression: { flag: "aggression", enum: ["low", "medium", "high", "xhigh"] },
|
|
76
70
|
// --goal geo picks strategies for that goal only; --goal any (the default) reads every strategy.
|
|
77
71
|
goal: {
|
|
@@ -117,7 +111,7 @@ export const ARGS = {
|
|
|
117
111
|
},
|
|
118
112
|
};
|
|
119
113
|
|
|
120
|
-
const SETTING_FLAGS = ["veto-hours", "pace-hours", "
|
|
114
|
+
const SETTING_FLAGS = ["veto-hours", "pace-hours", "delivery", "aggression", "goal", "slack-channel"];
|
|
121
115
|
|
|
122
116
|
export async function cmdPlan(ctx) {
|
|
123
117
|
const { client, flags, io } = ctx;
|
|
@@ -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