@letterstory/cli 0.177.10 → 0.177.12
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 +10 -1
- package/lib/commands/memory.mjs +88 -0
- package/lib/commands/repo.mjs +19 -1
- 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.12";
|
|
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;
|
|
@@ -435,6 +436,8 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
435
436
|
"dry",
|
|
436
437
|
// admin billing emails --bounced
|
|
437
438
|
"bounced",
|
|
439
|
+
// `memory ingest --include-website` reads the org's own site too: value-less.
|
|
440
|
+
"include-website",
|
|
438
441
|
// models add: train once the links are added. models confirm: keep the found links not named, or don't train yet.
|
|
439
442
|
"train",
|
|
440
443
|
"keep-rest",
|
|
@@ -1806,6 +1809,10 @@ Insights:
|
|
|
1806
1809
|
plan later remove <queued-id> Take a queued row back off
|
|
1807
1810
|
plan strategies [--site host] [--archived] The goals typed on Ideas, each a strategy
|
|
1808
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
|
|
1809
1816
|
plan strategy archive <strategy-id> [--restore] Put one away (its goal leaves Ideas), or bring it back
|
|
1810
1817
|
insights summary [--period …] [--part <block>]... Measure Overview: AI named/cited rates, share of voice, search, targets
|
|
1811
1818
|
insights reads [--period …] [--site all|own|phantoms|<key>]... [--path <path>] [--engine <operator>]
|
|
@@ -1901,6 +1908,7 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letter GitHub App op
|
|
|
1901
1908
|
repo repos [--gitlab [--search <text>] [--page n]] Repos the Letter GitHub App can reach (--gitlab: the GitLab
|
|
1902
1909
|
projects the organization's connection can push to)
|
|
1903
1910
|
repo connect [--gitlab] Is GitHub (or GitLab) connected; the link a person opens to connect it
|
|
1911
|
+
repo disconnect --installation <id> Disconnect a GitHub account (its installationId)
|
|
1904
1912
|
repo branches --repo owner/name Branches a change can start from (default first)
|
|
1905
1913
|
repo prs [--repo owner/name] Every open PR (whoever opened it), longest-idle first
|
|
1906
1914
|
repo suggestions Fixes found and not acted on yet, and how to start each
|
|
@@ -2203,6 +2211,7 @@ const CLIENT_COMMANDS = {
|
|
|
2203
2211
|
ask: cmdAsk,
|
|
2204
2212
|
ideas: cmdIdeas,
|
|
2205
2213
|
plan: cmdPlan,
|
|
2214
|
+
memory: cmdMemory,
|
|
2206
2215
|
site: cmdSite,
|
|
2207
2216
|
evals: cmdEvals,
|
|
2208
2217
|
visits: cmdVisits,
|
|
@@ -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/repo.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// repo say → send_repo_change_message (steer a build while its agent works)
|
|
12
12
|
// repo repos → list_github_app_repos repo branches → list_repo_branches
|
|
13
13
|
// repo connect → connect_github (the link a person opens to connect GitHub)
|
|
14
|
+
// repo disconnect --installation <id> → disconnect_github_account
|
|
14
15
|
// repo repos --gitlab → list_gitlab_repos repo connect --gitlab → connect_gitlab
|
|
15
16
|
// repo prs → list_open_pull_requests (every open PR, whoever opened it)
|
|
16
17
|
// repo install-letterprove → letterprove_open_install_pr
|
|
@@ -304,6 +305,7 @@ const ARGS = {
|
|
|
304
305
|
},
|
|
305
306
|
list_github_app_repos: {},
|
|
306
307
|
connect_github: {},
|
|
308
|
+
disconnect_github_account: { installation_id: { flag: "installation", type: "num" } },
|
|
307
309
|
list_gitlab_repos: { search: "search", page: { flag: "page", type: "num" } },
|
|
308
310
|
connect_gitlab: {},
|
|
309
311
|
list_repo_branches: {
|
|
@@ -406,6 +408,8 @@ export async function cmdRepo(ctx) {
|
|
|
406
408
|
return repoRepos(rest);
|
|
407
409
|
case "connect":
|
|
408
410
|
return repoConnect(rest);
|
|
411
|
+
case "disconnect":
|
|
412
|
+
return repoDisconnect(rest);
|
|
409
413
|
case "branches":
|
|
410
414
|
return repoBranches(rest);
|
|
411
415
|
case "prs":
|
|
@@ -420,7 +424,7 @@ export async function cmdRepo(ctx) {
|
|
|
420
424
|
return repoSlackTest(rest);
|
|
421
425
|
default:
|
|
422
426
|
throw new CliError(
|
|
423
|
-
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, install-visitors, sites, set-site, reset-site, secrets, set-secret, delete-secret, status, list, publish, merge, retry, revise, say, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test`
|
|
427
|
+
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, install-visitors, sites, set-site, reset-site, secrets, set-secret, delete-secret, status, list, publish, merge, retry, revise, say, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test, connect, disconnect`
|
|
424
428
|
);
|
|
425
429
|
}
|
|
426
430
|
}
|
|
@@ -874,6 +878,20 @@ async function repoConnect(ctx) {
|
|
|
874
878
|
return 0;
|
|
875
879
|
}
|
|
876
880
|
|
|
881
|
+
/** `repo disconnect --installation <id>`: disconnect one GitHub account (its installationId on `repo repos --json`). */
|
|
882
|
+
async function repoDisconnect(ctx) {
|
|
883
|
+
const { client, flags, io } = ctx;
|
|
884
|
+
refuseUnknownFlags(ctx, {}, ["installation"]);
|
|
885
|
+
if (flags.installation === undefined)
|
|
886
|
+
throw new CliError("Pass --installation <id>: the installationId shown by `repo repos --json`.");
|
|
887
|
+
const result = await client.callTool(
|
|
888
|
+
"disconnect_github_account",
|
|
889
|
+
argsFromFlags(ARGS.disconnect_github_account, flags)
|
|
890
|
+
);
|
|
891
|
+
printResult(io, flags, result, (r) => r.message);
|
|
892
|
+
return 0;
|
|
893
|
+
}
|
|
894
|
+
|
|
877
895
|
/** `repo prs [--repo owner/name]`: every open PR, longest-idle first, and which Letter opened. */
|
|
878
896
|
async function repoPrs(ctx) {
|
|
879
897
|
const { client, flags, io } = ctx;
|
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.
|