@letterstory/cli 0.177.9 → 0.177.11
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 +22 -1
- package/lib/commands/admin.mjs +68 -1
- package/lib/commands/memory.mjs +88 -0
- package/lib/commands.mjs +1 -0
- package/lib/documents.mjs +2 -0
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
cmdAsk,
|
|
69
69
|
cmdIdeas,
|
|
70
70
|
cmdPlan,
|
|
71
|
+
cmdMemory,
|
|
71
72
|
cmdSite,
|
|
72
73
|
cmdEvals,
|
|
73
74
|
cmdOfferings,
|
|
@@ -87,7 +88,7 @@ import {
|
|
|
87
88
|
import { fetchParkedSections, withoutParked } from "./parked.mjs";
|
|
88
89
|
|
|
89
90
|
// Matches cli/package.json; publish-cli.yml rewrites both to the published version.
|
|
90
|
-
export const VERSION = "0.177.
|
|
91
|
+
export const VERSION = "0.177.11";
|
|
91
92
|
|
|
92
93
|
/** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
|
|
93
94
|
export const EXIT_PAYMENT_REQUIRED = 3;
|
|
@@ -100,6 +101,11 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
100
101
|
"connect-only",
|
|
101
102
|
// admin demo ideas: every live recipe, not only the Ideas that fit.
|
|
102
103
|
"library",
|
|
104
|
+
// admin demo sweep: prepare each recipe's org for a person to start, pull requests, make orgs, skip the refit.
|
|
105
|
+
"prepare",
|
|
106
|
+
"pull-requests",
|
|
107
|
+
"make-orgs",
|
|
108
|
+
"no-refit",
|
|
103
109
|
// admin targets check: check every target again, checked or not.
|
|
104
110
|
"recheck",
|
|
105
111
|
// build list: also move the builds' open changes along while polling.
|
|
@@ -430,6 +436,8 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
430
436
|
"dry",
|
|
431
437
|
// admin billing emails --bounced
|
|
432
438
|
"bounced",
|
|
439
|
+
// `memory ingest --include-website` reads the org's own site too: value-less.
|
|
440
|
+
"include-website",
|
|
433
441
|
// models add: train once the links are added. models confirm: keep the found links not named, or don't train yet.
|
|
434
442
|
"train",
|
|
435
443
|
"keep-rest",
|
|
@@ -1204,6 +1212,14 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
1204
1212
|
admin demo check <org-id> Is it ready to show? Competitors, targets, answers, the
|
|
1205
1213
|
Plan's rows, the issues open, no PR open, GitHub, the
|
|
1206
1214
|
phantom's site. Exits 1 when not ready.
|
|
1215
|
+
admin demo ideas <org-id> [--library] [--preview] [--force] | --status [--id <run>]
|
|
1216
|
+
Start every Idea on a demo org for real (pull requests, or
|
|
1217
|
+
previews), or read how the latest run went.
|
|
1218
|
+
admin demo sweep [--prepare] [--pull-requests] [--recipes a,b] [--orgs a,b] [--exclude a,b] [--buy a,b] [--make-orgs] [--no-refit] | --status [--id <sweep>]
|
|
1219
|
+
Every live recipe end to end: each gets a test org whose Ideas
|
|
1220
|
+
offers it, its Start pressed (or, with --prepare, left for you
|
|
1221
|
+
to press in the app), and its outcome judged, with who owns a
|
|
1222
|
+
failure. Exits 1 when the sweep failed.
|
|
1207
1223
|
admin brand-detection <org-id> [--term <t>]... [--exact <t>]... [--exclude <phrase>]... [--note "…"] | --clear [--note "…"]
|
|
1208
1224
|
How a client's brand is detected in AI answers, with
|
|
1209
1225
|
every earlier setting; with flags, set the exact set
|
|
@@ -1793,6 +1809,10 @@ Insights:
|
|
|
1793
1809
|
plan later remove <queued-id> Take a queued row back off
|
|
1794
1810
|
plan strategies [--site host] [--archived] The goals typed on Ideas, each a strategy
|
|
1795
1811
|
plan strategy add "<words…>" [--site host] Keep a goal in your words as a strategy
|
|
1812
|
+
plan strategy archive <strategy-id> [--restore] Put one away (its card leaves the Plan), or bring it back
|
|
1813
|
+
memory ingest [--text|--text-file <path|->] [--link <url>]… [--include-website] [--mcp-url <url>] Push context to Memory: a reviewable proposal
|
|
1814
|
+
memory apply <ingestion-id> --changes <id>,<id> Save the proposed changes you accept
|
|
1815
|
+
memory add --section <id> --body <text> [--title] [--origin] File one entry in a custom memory section
|
|
1796
1816
|
plan strategy archive <strategy-id> [--restore] Put one away (its goal leaves Ideas), or bring it back
|
|
1797
1817
|
insights summary [--period …] [--part <block>]... Measure Overview: AI named/cited rates, share of voice, search, targets
|
|
1798
1818
|
insights reads [--period …] [--site all|own|phantoms|<key>]... [--path <path>] [--engine <operator>]
|
|
@@ -2190,6 +2210,7 @@ const CLIENT_COMMANDS = {
|
|
|
2190
2210
|
ask: cmdAsk,
|
|
2191
2211
|
ideas: cmdIdeas,
|
|
2192
2212
|
plan: cmdPlan,
|
|
2213
|
+
memory: cmdMemory,
|
|
2193
2214
|
site: cmdSite,
|
|
2194
2215
|
evals: cmdEvals,
|
|
2195
2216
|
visits: cmdVisits,
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -6910,7 +6910,7 @@ const DEMO_ITEM_FLAGS = [
|
|
|
6910
6910
|
"setup",
|
|
6911
6911
|
];
|
|
6912
6912
|
const DEMO_USAGE =
|
|
6913
|
-
"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>]";
|
|
6913
|
+
"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>] | sweep [--prepare] [--pull-requests] [--recipes <id>,…] [--orgs <id>,…] [--exclude <id>,…] [--buy <recipe-id>,…] [--make-orgs] [--no-refit] | sweep --status [--id <sweep-id>]";
|
|
6914
6914
|
|
|
6915
6915
|
const readinessLines = (r) =>
|
|
6916
6916
|
r
|
|
@@ -6936,6 +6936,49 @@ const ideasRunLines = (run) =>
|
|
|
6936
6936
|
].join("\n")
|
|
6937
6937
|
: "No run yet.";
|
|
6938
6938
|
|
|
6939
|
+
const SWEEP_MARK = {
|
|
6940
|
+
ready: "READY",
|
|
6941
|
+
passed: "PASS",
|
|
6942
|
+
failed: "FAIL",
|
|
6943
|
+
waiting: "WAIT",
|
|
6944
|
+
held: "HELD",
|
|
6945
|
+
no_org: "NO ORG",
|
|
6946
|
+
not_derivable: "NO PROFILE",
|
|
6947
|
+
pending: "…",
|
|
6948
|
+
};
|
|
6949
|
+
|
|
6950
|
+
/** A recipe sweep's report (get_recipe_sweep), one block per recipe. */
|
|
6951
|
+
const recipeSweepLines = (sweep, appUrl) =>
|
|
6952
|
+
sweep
|
|
6953
|
+
? [
|
|
6954
|
+
`${sweep.summary} (${sweep.state}${sweep.note && sweep.state !== "done" ? `: ${sweep.note}` : ""})`,
|
|
6955
|
+
`${sweep.start ? "started by the sweep" : "prepared for you to start"}, ${sweep.delivery === "build" ? "previews" : "pull requests"}, by ${sweep.started_by_name ?? "staff"} at ${sweep.created_at} sweep ${sweep.id}`,
|
|
6956
|
+
...sweep.items.map((i) =>
|
|
6957
|
+
[
|
|
6958
|
+
`\n${(SWEEP_MARK[i.result] ?? i.result).padEnd(10)} ${i.name}`,
|
|
6959
|
+
i.org ? ` org: ${i.org.name} (${i.org.host})${i.org.made ? ", made for it" : ""}` : null,
|
|
6960
|
+
i.fit[0] ? ` fit: ${i.fit[0].fits}: ${i.fit[0].why}` : null,
|
|
6961
|
+
i.row
|
|
6962
|
+
? ` row: ${i.row.title}${i.row.line ? ` / ${i.row.line}` : ""}${i.row.button ? ` [${i.row.button}]` : ""}`
|
|
6963
|
+
: null,
|
|
6964
|
+
i.did
|
|
6965
|
+
? ` did: ${i.did.tools.join(", ") || "no tools"}${i.did.sandbox ? ", a sandbox" : ""}${i.did.cost_usd != null ? `, $${i.did.cost_usd}` : ""}`
|
|
6966
|
+
: null,
|
|
6967
|
+
i.verdict ? ` outcome: ${i.verdict.state}: ${i.verdict.observed}` : null,
|
|
6968
|
+
i.cause ? ` cause: ${i.cause.owner}: ${i.cause.why}` : null,
|
|
6969
|
+
i.profile_problem ? ` profile: ${i.profile_problem}` : null,
|
|
6970
|
+
i.profile?.unsettable?.length
|
|
6971
|
+
? ` can't be made on a test org: ${i.profile.unsettable.map((u) => u.key).join(", ")}`
|
|
6972
|
+
: null,
|
|
6973
|
+
i.links?.ideas ? ` open: ${appUrl}${i.links.ideas}` : null,
|
|
6974
|
+
i.links?.build ? ` build: ${appUrl}${i.links.build}` : null,
|
|
6975
|
+
]
|
|
6976
|
+
.filter(Boolean)
|
|
6977
|
+
.join("\n")
|
|
6978
|
+
),
|
|
6979
|
+
].join("\n")
|
|
6980
|
+
: "No sweep yet.";
|
|
6981
|
+
|
|
6939
6982
|
async function cmdAdminDemo(ctx) {
|
|
6940
6983
|
const { flags, positionals, io } = ctx;
|
|
6941
6984
|
const sub = positionals[0];
|
|
@@ -7194,6 +7237,30 @@ async function cmdAdminDemo(ctx) {
|
|
|
7194
7237
|
printResult(io, flags, result, (r) => ideasRunLines(r.run));
|
|
7195
7238
|
return result.run?.state === "failed" ? 1 : 0;
|
|
7196
7239
|
}
|
|
7240
|
+
case "sweep": {
|
|
7241
|
+
// Every live recipe end to end on test orgs (run_recipe_sweep), or the latest sweep's report (get_recipe_sweep).
|
|
7242
|
+
const path = `${DEMO}/sweeps`;
|
|
7243
|
+
const id = flagStr(flags.id);
|
|
7244
|
+
const result =
|
|
7245
|
+
flagBool(flags.status) || id
|
|
7246
|
+
? await staffRequest(ctx, "GET", id ? `${path}?id=${encodeURIComponent(id)}` : path)
|
|
7247
|
+
: await staffPost(
|
|
7248
|
+
ctx,
|
|
7249
|
+
path,
|
|
7250
|
+
compact({
|
|
7251
|
+
start: flagBool(flags.prepare) ? false : undefined,
|
|
7252
|
+
delivery: flagBool(flags["pull-requests"]) ? "build_pr" : undefined,
|
|
7253
|
+
recipes: flags.recipes ? flagList(flags.recipes) : undefined,
|
|
7254
|
+
orgs: flags.orgs ? flagList(flags.orgs) : undefined,
|
|
7255
|
+
exclude: flags.exclude ? flagList(flags.exclude) : undefined,
|
|
7256
|
+
purchases: flags.buy ? flagList(flags.buy) : undefined,
|
|
7257
|
+
make_orgs: flagBool(flags["make-orgs"]) || undefined,
|
|
7258
|
+
refit: flagBool(flags["no-refit"]) ? false : undefined,
|
|
7259
|
+
})
|
|
7260
|
+
);
|
|
7261
|
+
printResult(io, flags, result, (r) => recipeSweepLines(r.sweep, ctx.client.url));
|
|
7262
|
+
return result.sweep?.state === "failed" ? 1 : 0;
|
|
7263
|
+
}
|
|
7197
7264
|
default:
|
|
7198
7265
|
throw new CliError(DEMO_USAGE);
|
|
7199
7266
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// `memory` — push context into Memory from a terminal, the way an agent does it over the MCP: thin wrappers over
|
|
2
|
+
// the same tools, nothing of their own.
|
|
3
|
+
// memory ingest [--text <t>|--text-file <path|->] [--link <url>]… [--include-website] [--mcp-url <url> [--mcp-token <t>] [--mcp-focus <words>]]
|
|
4
|
+
// → ingest_context (a reviewable proposal; nothing is saved yet)
|
|
5
|
+
// memory apply <ingestion-id> --changes <change-id>[,<change-id>…] → apply_context (saves the accepted changes)
|
|
6
|
+
// memory add --section <id> (--body <t>|--body-file <path|->) [--title <t>] [--origin <where it was read>]
|
|
7
|
+
// → add_memory_entry (one entry in a custom section)
|
|
8
|
+
|
|
9
|
+
import { CliError } from "../client.mjs";
|
|
10
|
+
import { argsFromFlags, flagCsv, flagStr, printResult, readTextInput, refuseUnknownFlags } from "./shared.mjs";
|
|
11
|
+
|
|
12
|
+
export const ARGS = {
|
|
13
|
+
ingest_context: {
|
|
14
|
+
text: { value: (flags) => readTextInput(flags, "text", "text-file") },
|
|
15
|
+
links: { flag: "link", type: "list" },
|
|
16
|
+
include_website: { flag: "include-website", type: "bool" },
|
|
17
|
+
mcp_servers: {
|
|
18
|
+
value: (flags) => {
|
|
19
|
+
const url = flagStr(flags["mcp-url"]);
|
|
20
|
+
const token = flagStr(flags["mcp-token"]);
|
|
21
|
+
const focus = flagStr(flags["mcp-focus"]);
|
|
22
|
+
if (!url) return undefined;
|
|
23
|
+
return [{ url, ...(token ? { authorization_token: token } : {}), ...(focus ? { focus } : {}) }];
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
apply_context: {
|
|
28
|
+
ingestion_id: { positional: 0, required: true, label: "ingestion-id" },
|
|
29
|
+
accepted_change_ids: { value: (flags) => flagCsv(flags.changes) },
|
|
30
|
+
},
|
|
31
|
+
add_memory_entry: {
|
|
32
|
+
section_id: { flag: "section", required: true },
|
|
33
|
+
body: { value: (flags) => readTextInput(flags, "body", "body-file") },
|
|
34
|
+
title: "title",
|
|
35
|
+
origin: "origin",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export async function cmdMemory(ctx) {
|
|
40
|
+
const { client, flags, io } = ctx;
|
|
41
|
+
const sub = ctx.positionals[0];
|
|
42
|
+
const rest = ctx.positionals.slice(1);
|
|
43
|
+
if (sub === "ingest") {
|
|
44
|
+
refuseUnknownFlags(ctx, ARGS.ingest_context, ["text-file", "mcp-url", "mcp-token", "mcp-focus"]);
|
|
45
|
+
const args = argsFromFlags(ARGS.ingest_context, flags, rest);
|
|
46
|
+
if (!args.text && !args.links?.length && !args.include_website && !args.mcp_servers) {
|
|
47
|
+
throw new CliError(
|
|
48
|
+
"Give it something to read: --text, --text-file, --link, --include-website or --mcp-url."
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const result = await client.callTool("ingest_context", args);
|
|
52
|
+
printResult(io, flags, result, (r) =>
|
|
53
|
+
[
|
|
54
|
+
`Pass ${r.ingestion_id}: ${r.summary}`,
|
|
55
|
+
`${r.changes.length} proposed change${r.changes.length === 1 ? "" : "s"}. Nothing is saved yet.`,
|
|
56
|
+
...r.changes.map((c) => ` ${c.id} [${c.area}] ${c.label}`),
|
|
57
|
+
r.changes.length ? `Save the ones you accept: memory apply ${r.ingestion_id} --changes <id>,<id>` : "",
|
|
58
|
+
]
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.join("\n")
|
|
61
|
+
);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
if (sub === "apply") {
|
|
65
|
+
refuseUnknownFlags(ctx, ARGS.apply_context, ["changes"]);
|
|
66
|
+
const args = argsFromFlags(ARGS.apply_context, flags, rest);
|
|
67
|
+
if (!args.accepted_change_ids?.length) throw new CliError("Say which changes to save: --changes <id>[,<id>…].");
|
|
68
|
+
const result = await client.callTool("apply_context", args);
|
|
69
|
+
printResult(
|
|
70
|
+
io,
|
|
71
|
+
flags,
|
|
72
|
+
result,
|
|
73
|
+
(r) => `Saved ${r.applied_change_ids.length} change${r.applied_change_ids.length === 1 ? "" : "s"}.`
|
|
74
|
+
);
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
if (sub === "add") {
|
|
78
|
+
refuseUnknownFlags(ctx, ARGS.add_memory_entry, ["body-file"]);
|
|
79
|
+
const args = argsFromFlags(ARGS.add_memory_entry, flags, rest);
|
|
80
|
+
if (!args.body) throw new CliError("Say what to remember: --body <text> or --body-file <path|->.");
|
|
81
|
+
const result = await client.callTool("add_memory_entry", args);
|
|
82
|
+
printResult(io, flags, result, (r) => `Filed${r.entry?.id ? `: ${r.entry.id}` : ""}.`);
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
throw new CliError(
|
|
86
|
+
"Usage: memory ingest [--text|--text-file|--link|--include-website|--mcp-url] | memory apply <ingestion-id> --changes <ids> | memory add --section <id> --body <text>"
|
|
87
|
+
);
|
|
88
|
+
}
|
package/lib/commands.mjs
CHANGED
|
@@ -48,6 +48,7 @@ export * from "./commands/repo.mjs";
|
|
|
48
48
|
export * from "./commands/build.mjs";
|
|
49
49
|
export * from "./commands/ask.mjs";
|
|
50
50
|
export * from "./commands/plan.mjs";
|
|
51
|
+
export * from "./commands/memory.mjs";
|
|
51
52
|
export * from "./commands/site.mjs";
|
|
52
53
|
export * from "./commands/evals.mjs";
|
|
53
54
|
export * from "./commands/visits.mjs";
|
package/lib/documents.mjs
CHANGED
|
@@ -59,6 +59,8 @@ export const DOCUMENT_FLAG_PAIRS = [
|
|
|
59
59
|
["body", "file"],
|
|
60
60
|
["brief", "brief-file"],
|
|
61
61
|
["text", "text-file"],
|
|
62
|
+
// `memory add --body-file`: one entry's prose.
|
|
63
|
+
["body", "body-file"],
|
|
62
64
|
// An offering's (or the company's) manifesto is prose — a PDF or a .docx is fair game.
|
|
63
65
|
["manifesto", "manifesto-file"],
|
|
64
66
|
// A repo change's brief (repo.mjs `repo start`) — prose, so a .docx or PDF spec is fine.
|