@letterstory/cli 0.148.1 → 0.148.2
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 +17 -1
- package/lib/commands/build.mjs +99 -0
- package/lib/commands/repo.mjs +2 -2
- package/lib/commands.mjs +1 -0
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
cmdAdmin,
|
|
54
54
|
cmdFeedback,
|
|
55
55
|
cmdRepo,
|
|
56
|
+
cmdBuild,
|
|
56
57
|
cmdSite,
|
|
57
58
|
cmdEvals,
|
|
58
59
|
cmdOfferings,
|
|
@@ -65,12 +66,15 @@ import {
|
|
|
65
66
|
|
|
66
67
|
// Matches cli/package.json; publish-cli.yml rewrites both to the published version.
|
|
67
68
|
// Matches cli/package.json; publish-cli.yml rewrites both to the published version.
|
|
68
|
-
export const VERSION = "0.148.
|
|
69
|
+
export const VERSION = "0.148.2";
|
|
69
70
|
|
|
70
71
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
71
72
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
72
73
|
// `negate` spelling) in a command's ARGS map must be listed; boolean-flags.test.ts enforces it.
|
|
73
74
|
export const BOOLEAN_FLAGS = new Set([
|
|
75
|
+
// build: repeat an open build's ask; post its progress in the org's Slack channel.
|
|
76
|
+
"again",
|
|
77
|
+
"notify-slack",
|
|
74
78
|
"remap",
|
|
75
79
|
// login: one-link device sign-in instead of the loopback browser flow.
|
|
76
80
|
"device",
|
|
@@ -1378,6 +1382,17 @@ Insights:
|
|
|
1378
1382
|
Page access: who's reading your phantom sites
|
|
1379
1383
|
insights activity [--phantom <uuid>] [--limit <n>] What hit your phantom sites in the last 7 days, newest first
|
|
1380
1384
|
|
|
1385
|
+
Build (a change to your site, or launching what isn't code; the Build box's own door):
|
|
1386
|
+
build <words…> [--prompt <text>] [--site <host>] [--subject <host/path>]... [--action build|build_pr|build_merge|slack|linear]
|
|
1387
|
+
[--attach <path>]... [--attach-url <name>=<https url>]... [--attach-ref <path>]... [--attach-url-ref <name>=<https url>]...
|
|
1388
|
+
[--harness claude-code|letterpress-1] [--notify-slack] [--again]
|
|
1389
|
+
Build from your words: "add a pricing page" opens a preview or PR;
|
|
1390
|
+
"launch a phantom for us", "set up mailers" or "onboard my blog" is
|
|
1391
|
+
done by Build itself (a phantom site buys its own domain), and the
|
|
1392
|
+
build reads Done with no PR to merge. --again repeats an open build's ask
|
|
1393
|
+
build talk <words…> [--conversation <id>|--build <id>] [--site <host>] [--harness claude-code|letterpress-1]
|
|
1394
|
+
One turn of a conversation with Build (questions, follow-ups on a build)
|
|
1395
|
+
|
|
1381
1396
|
The Site (one model of your whole marketing site, keyed by host and path):
|
|
1382
1397
|
site list Mapped hosts, and hosts you already have elsewhere
|
|
1383
1398
|
site map <host> [--sitemap <url>] [--max-pages <n>] [--no-audit] [--relation owned|watched]
|
|
@@ -1587,6 +1602,7 @@ const CLIENT_COMMANDS = {
|
|
|
1587
1602
|
mailers: cmdMailers,
|
|
1588
1603
|
feedback: cmdFeedback,
|
|
1589
1604
|
repo: cmdRepo,
|
|
1605
|
+
build: cmdBuild,
|
|
1590
1606
|
site: cmdSite,
|
|
1591
1607
|
evals: cmdEvals,
|
|
1592
1608
|
visits: cmdVisits,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// `build` — Build from words, the way the Build box and the letterpress terminal do it
|
|
2
|
+
// (src/lib/growth-builds/start-build.ts). What the words name is done: a change to the site
|
|
3
|
+
// becomes a preview or a pull request, and launching phantom sites or mailers, or onboarding the
|
|
4
|
+
// main blog, is carried out by Build itself with nothing to merge.
|
|
5
|
+
// build <words…> → start_build (--prompt for a long ask; --again to repeat one)
|
|
6
|
+
// build talk <words…> → send_build_message (--conversation <id> | --build <id> to continue)
|
|
7
|
+
|
|
8
|
+
import { CliError } from "../client.mjs";
|
|
9
|
+
import { argsFromFlags, flagList, flagStr, ok, printResult, refuseUnknownFlags } from "./shared.mjs";
|
|
10
|
+
import { attachmentUrls, uploadAttachments } from "./repo.mjs";
|
|
11
|
+
|
|
12
|
+
const ARGS = {
|
|
13
|
+
start_build: {
|
|
14
|
+
// The words after `build`, or --prompt for a longer ask.
|
|
15
|
+
prompt: { value: (flags, positionals) => flagStr(flags.prompt) ?? (positionals.join(" ").trim() || undefined) },
|
|
16
|
+
domain: "site",
|
|
17
|
+
// --subject host/path (repeatable): the pages it is about.
|
|
18
|
+
subjects: { flag: "subject", type: "list" },
|
|
19
|
+
// --attach <path> (uploaded first) and --attach-url <name>=<https url>, both repeatable;
|
|
20
|
+
// -ref ones are context the agent reads, never put in the repo.
|
|
21
|
+
files: { value: (_flags, _positionals, extra) => (extra.files?.length ? extra.files : undefined) },
|
|
22
|
+
delivery: { flag: "action", enum: ["build", "build_pr", "build_merge", "slack", "linear"] },
|
|
23
|
+
notify_slack: { flag: "notify-slack", type: "bool" },
|
|
24
|
+
harness: { flag: "harness", enum: ["claude-code", "letterpress-1"] },
|
|
25
|
+
again: { flag: "again", type: "bool" },
|
|
26
|
+
},
|
|
27
|
+
send_build_message: {
|
|
28
|
+
message: {
|
|
29
|
+
value: (flags, positionals) => flagStr(flags.message) ?? (positionals.join(" ").trim() || undefined),
|
|
30
|
+
},
|
|
31
|
+
conversation_id: "conversation",
|
|
32
|
+
build_id: "build",
|
|
33
|
+
domain: "site",
|
|
34
|
+
subjects: { flag: "subject", type: "list" },
|
|
35
|
+
files: { value: (_flags, _positionals, extra) => (extra.files?.length ? extra.files : undefined) },
|
|
36
|
+
harness: { flag: "harness", enum: ["claude-code", "letterpress-1"] },
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const FILE_FLAGS = ["attach", "attach-url", "attach-ref", "attach-url-ref"];
|
|
41
|
+
|
|
42
|
+
/** The files named by --attach / --attach-url (and their -ref forms), uploaded first. */
|
|
43
|
+
async function attachedFiles(ctx) {
|
|
44
|
+
const { flags } = ctx;
|
|
45
|
+
const place = flagList(flags.attach);
|
|
46
|
+
const uploaded = await uploadAttachments(ctx, [...place, ...flagList(flags["attach-ref"])]);
|
|
47
|
+
return [
|
|
48
|
+
...uploaded.map((f, i) => (i < place.length ? f : { ...f, reference: true })),
|
|
49
|
+
...attachmentUrls(flagList(flags["attach-url"])),
|
|
50
|
+
...attachmentUrls(flagList(flags["attach-url-ref"])).map((f) => ({ ...f, reference: true })),
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function cmdBuild(ctx) {
|
|
55
|
+
if (ctx.positionals[0] === "talk") return buildTalk({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
56
|
+
return buildStart(ctx);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function buildStart(ctx) {
|
|
60
|
+
const { client, flags, io } = ctx;
|
|
61
|
+
refuseUnknownFlags(ctx, {}, [
|
|
62
|
+
"prompt",
|
|
63
|
+
"site",
|
|
64
|
+
"subject",
|
|
65
|
+
"action",
|
|
66
|
+
"notify-slack",
|
|
67
|
+
"harness",
|
|
68
|
+
"again",
|
|
69
|
+
...FILE_FLAGS,
|
|
70
|
+
]);
|
|
71
|
+
const files = await attachedFiles(ctx);
|
|
72
|
+
const args = argsFromFlags(ARGS.start_build, flags, ctx.positionals, { files });
|
|
73
|
+
if (!args.prompt) throw new CliError('Say what to build: letterstory build "launch a phantom for us"');
|
|
74
|
+
const result = await client.callTool("start_build", args);
|
|
75
|
+
printResult(io, flags, result, (r) => {
|
|
76
|
+
if (r.status !== "started") return r.message ?? r.status;
|
|
77
|
+
const steps = (r.build?.steps ?? []).map((s) => ` ${s.status} ${s.title}${s.note ? `: ${s.note}` : ""}`);
|
|
78
|
+
return [`Started: ${r.build.title} (${r.build.id})`, ...steps].join("\n");
|
|
79
|
+
});
|
|
80
|
+
if (result.status !== "started" && !flags.json) ok(ctx, "Nothing was started.");
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function buildTalk(ctx) {
|
|
85
|
+
const { client, flags, io } = ctx;
|
|
86
|
+
refuseUnknownFlags(ctx, {}, ["message", "conversation", "build", "site", "subject", "harness", ...FILE_FLAGS]);
|
|
87
|
+
const files = await attachedFiles(ctx);
|
|
88
|
+
const args = argsFromFlags(ARGS.send_build_message, flags, ctx.positionals, { files });
|
|
89
|
+
if (!args.message) throw new CliError('Say something: letterstory build talk "make the hero darker"');
|
|
90
|
+
const result = await client.callTool("send_build_message", args);
|
|
91
|
+
printResult(io, flags, result, (r) =>
|
|
92
|
+
[r.reply, ...(r.questions ?? []).map((q) => `? ${q.question}`), r.build ? `Build: ${r.build.id}` : null]
|
|
93
|
+
.filter(Boolean)
|
|
94
|
+
.join("\n")
|
|
95
|
+
);
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { ARGS };
|
package/lib/commands/repo.mjs
CHANGED
|
@@ -347,7 +347,7 @@ export async function cmdRepo(ctx) {
|
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
/** `--attach-url name=https://…` → `{ name, url }`. */
|
|
350
|
-
function attachmentUrls(specs) {
|
|
350
|
+
export function attachmentUrls(specs) {
|
|
351
351
|
return specs.map((spec) => {
|
|
352
352
|
const at = spec.indexOf("=");
|
|
353
353
|
if (at < 1) throw new CliError(`--attach-url must be name=https://… (got ${spec})`);
|
|
@@ -356,7 +356,7 @@ function attachmentUrls(specs) {
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
/** Reserve an upload per --attach file, PUT the bytes, and return `{ name, upload_path }` for each. */
|
|
359
|
-
async function uploadAttachments(ctx, paths) {
|
|
359
|
+
export async function uploadAttachments(ctx, paths) {
|
|
360
360
|
if (!paths.length) return [];
|
|
361
361
|
const names = paths.map((p) =>
|
|
362
362
|
basename(p)
|
package/lib/commands.mjs
CHANGED
|
@@ -44,6 +44,7 @@ export * from "./commands/targets.mjs";
|
|
|
44
44
|
export * from "./commands/admin.mjs";
|
|
45
45
|
export * from "./commands/feedback.mjs";
|
|
46
46
|
export * from "./commands/repo.mjs";
|
|
47
|
+
export * from "./commands/build.mjs";
|
|
47
48
|
export * from "./commands/site.mjs";
|
|
48
49
|
export * from "./commands/evals.mjs";
|
|
49
50
|
export * from "./commands/visits.mjs";
|