@letterstory/cli 0.129.2 → 0.131.0
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/README.md +2 -0
- package/lib/cli.mjs +9 -3
- package/lib/commands/automations.mjs +7 -2
- package/lib/commands/insights.mjs +23 -3
- package/lib/commands/phantom.mjs +15 -1
- package/lib/commands/repo.mjs +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,6 +118,8 @@ letterstory phantom config <phantom-id> --theme commons # patch the sit
|
|
|
118
118
|
letterstory phantom config <phantom-id> --collection-config '{"cadence":{"posts_per_week":4}}'
|
|
119
119
|
letterstory phantom config <phantom-id> --configure --dry-run # what re-configuring would decide, applying nothing
|
|
120
120
|
letterstory phantom rebuild <phantom-id> # after publishing outside a collection run
|
|
121
|
+
letterstory phantom pause <phantom-id> # stop new posts and probing; the site stays live (resume to undo)
|
|
122
|
+
letterstory phantom resume <phantom-id>
|
|
121
123
|
letterstory phantom diagnostics <phantom-id> # why is my site empty?
|
|
122
124
|
letterstory phantom balance # sites live / granted, and the cadence cap
|
|
123
125
|
letterstory phantom spread-looks [--apply] # a different look per site; dry run unless --apply
|
package/lib/cli.mjs
CHANGED
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
} from "./commands.mjs";
|
|
63
63
|
|
|
64
64
|
// Keep in sync with cli/package.json.
|
|
65
|
-
export const VERSION = "0.
|
|
65
|
+
export const VERSION = "0.131.0";
|
|
66
66
|
|
|
67
67
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
68
68
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
@@ -447,6 +447,7 @@ Reviewers:
|
|
|
447
447
|
reviewers assigned Posts waiting on your review, across letterbases
|
|
448
448
|
automations list --collection <id> What a letterbase writes, how often, and why
|
|
449
449
|
automations show <automation-id> --collection <id> What it covers, its queued posts, its next ideas
|
|
450
|
+
automations ideas --collection <id> The ideas waiting, grouped by the automation that owns each
|
|
450
451
|
automations suggest --collection <id> What it could add: topics, series formats, signals
|
|
451
452
|
automations preview topic|series --collection <id> [--name <name>|--topic <id>] [--brief <text>]
|
|
452
453
|
What it would write, before adding it
|
|
@@ -1305,6 +1306,9 @@ Mail:
|
|
|
1305
1306
|
|
|
1306
1307
|
Insights:
|
|
1307
1308
|
insights summary [--period …] [--part <block>]... Measure Overview: AI named/cited rates, share of voice, search, targets
|
|
1309
|
+
insights reads [--period …] [--site all|own|phantoms|<key>]... [--path <path>] [--engine <operator>]
|
|
1310
|
+
Measure → Reads: AI engines and people reading your sites, counted one way
|
|
1311
|
+
insights questions [--period 14d|30d|90d] Measure → Questions: every question we ask, named / share / change / cited
|
|
1308
1312
|
insights competitors [--period 14d|30d|90d] GEO → Competitors: share of mentions per brand, is it moving, by engine/question
|
|
1309
1313
|
insights question <key> [--period 14d|30d|90d] [--filter all|named_you|rival_not_you|no_one] [--limit n]
|
|
1310
1314
|
One By-question row: the wordings asked and the AI answers
|
|
@@ -1316,6 +1320,7 @@ Insights:
|
|
|
1316
1320
|
AI Visibility Bank: named / cited / breakthrough evidence (all time by default)
|
|
1317
1321
|
insights access [--period 14d|30d|90d] [--collection <uuid>]
|
|
1318
1322
|
Page access: who's reading your phantom sites
|
|
1323
|
+
insights activity [--phantom <uuid>] [--limit <n>] What hit your phantom sites in the last 7 days, newest first
|
|
1319
1324
|
|
|
1320
1325
|
The Site (one model of your whole marketing site, keyed by host and path):
|
|
1321
1326
|
site list Mapped hosts, and hosts you already have elsewhere
|
|
@@ -1350,10 +1355,11 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
|
|
|
1350
1355
|
repo suggestions Fixes found and not acted on yet, and how to start each
|
|
1351
1356
|
repo settings [--action build|build_pr|build_merge|slack|linear] [--branch <name>|none] [--slack-agent <name|U…>|none]
|
|
1352
1357
|
[--slack-channel <C…>|none] [--slack-reports off|as_found|daily] [--reports-channel <C…>|none] [--autopilot-channel <C…>|none] [--listen <C…,C…>|none] [--autopilot on|off] [--autopilot-weekly <1-20>]
|
|
1353
|
-
[--autopilot-severity critical|warning]
|
|
1358
|
+
[--autopilot-severity critical|warning] [--auto-merge 1h|12h|24h|3d|off]
|
|
1354
1359
|
The org's defaults for every fix: build (preview first),
|
|
1355
1360
|
build_pr, or slack (post it to --slack-channel for your
|
|
1356
|
-
own agent, tagging --slack-agent), and the branch changes go to
|
|
1361
|
+
own agent, tagging --slack-agent), and the branch changes go to;
|
|
1362
|
+
--auto-merge merges Letter's PRs once open that long
|
|
1357
1363
|
repo slack-targets Slack channels and people Send to Slack can use, with ids
|
|
1358
1364
|
repo slack-test Post a test into the Send to Slack channel
|
|
1359
1365
|
repo start --repo <owner/name> --instructions <text>|--instructions-file <path|->
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// `automations` — a letterbase's automations (docs/architecture/automations.md): what it writes,
|
|
2
|
-
// how often, and why. list / show / suggest / preview / more / add / set / dismiss / remove, through the same tools the app uses.
|
|
2
|
+
// how often, and why. list / show / ideas / suggest / preview / more / add / set / dismiss / remove, through the same tools the app uses.
|
|
3
3
|
// Same flag/positional/io conventions as posts.mjs — no commander, no color.
|
|
4
4
|
|
|
5
5
|
import { CliError } from "../client.mjs";
|
|
@@ -10,6 +10,9 @@ export const ARGS = {
|
|
|
10
10
|
list_automations: {
|
|
11
11
|
collection_id: { flag: "collection", required: true },
|
|
12
12
|
},
|
|
13
|
+
list_automation_ideas: {
|
|
14
|
+
collection_id: { flag: "collection", required: true },
|
|
15
|
+
},
|
|
13
16
|
suggest_automations: {
|
|
14
17
|
collection_id: { flag: "collection", required: true },
|
|
15
18
|
},
|
|
@@ -74,6 +77,8 @@ export async function cmdAutomations(ctx) {
|
|
|
74
77
|
return run(rest, "list_automations");
|
|
75
78
|
case "show":
|
|
76
79
|
return run(rest, "get_automation");
|
|
80
|
+
case "ideas":
|
|
81
|
+
return run(rest, "list_automation_ideas");
|
|
77
82
|
case "suggest":
|
|
78
83
|
return run(rest, "suggest_automations");
|
|
79
84
|
case "preview":
|
|
@@ -92,7 +97,7 @@ export async function cmdAutomations(ctx) {
|
|
|
92
97
|
return run(rest, "delete_automation", "Removed.");
|
|
93
98
|
default:
|
|
94
99
|
throw new CliError(
|
|
95
|
-
`Unknown automations subcommand: ${sub ?? "(none)"}. Try: list, show, suggest, preview, more, add, set, dismiss, remove`
|
|
100
|
+
`Unknown automations subcommand: ${sub ?? "(none)"}. Try: list, show, ideas, suggest, preview, more, add, set, dismiss, remove`
|
|
96
101
|
);
|
|
97
102
|
}
|
|
98
103
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// `insights` — Search Console impressions/clicks across the phantom network, at the
|
|
2
2
|
// network, single-post, and top-posts levels (ported from phantomstory-cli's
|
|
3
3
|
// insights.ts); plus Measure's Overview (`summary`), GEO → Competitors
|
|
4
|
-
// (`competitors`), AI Visibility Bank (`bank`) and phantom Page Access (`access`)
|
|
5
|
-
//
|
|
4
|
+
// (`competitors`), AI Visibility Bank (`bank`) and phantom Page Access (`access`);
|
|
5
|
+
// Measure → Reads (`reads`) and Questions (`questions`), and the live feed of what
|
|
6
|
+
// is reading the phantom sites (`activity`) — the same reads the web app renders.
|
|
6
7
|
|
|
7
8
|
import { CliError } from "../client.mjs";
|
|
8
9
|
import { flagStr, flagNum, requirePositional, printResult, compact, argsFromFlags } from "./shared.mjs";
|
|
@@ -28,6 +29,19 @@ export const ARGS = {
|
|
|
28
29
|
period: { flag: "period", enum: PERIODS },
|
|
29
30
|
},
|
|
30
31
|
get_brand_detection: {},
|
|
32
|
+
get_reads: {
|
|
33
|
+
period: { flag: "period", enum: PERIODS },
|
|
34
|
+
sites: { flag: "site", type: "list" },
|
|
35
|
+
path: { flag: "path" },
|
|
36
|
+
engine: { flag: "engine" },
|
|
37
|
+
},
|
|
38
|
+
list_measure_questions: {
|
|
39
|
+
period: { flag: "period", enum: PERIODS },
|
|
40
|
+
},
|
|
41
|
+
get_recent_activity: {
|
|
42
|
+
phantom_id: { flag: "phantom" },
|
|
43
|
+
limit: { flag: "limit", type: "num" },
|
|
44
|
+
},
|
|
31
45
|
get_share_of_voice_question: {
|
|
32
46
|
question_key: { positional: 0, label: "question-key", required: true },
|
|
33
47
|
period: { flag: "period", enum: PERIODS },
|
|
@@ -58,9 +72,15 @@ export async function cmdInsights(ctx) {
|
|
|
58
72
|
return call(rest, "get_share_of_voice_question");
|
|
59
73
|
case "detection":
|
|
60
74
|
return call(rest, "get_brand_detection");
|
|
75
|
+
case "reads":
|
|
76
|
+
return call(rest, "get_reads");
|
|
77
|
+
case "questions":
|
|
78
|
+
return call(rest, "list_measure_questions");
|
|
79
|
+
case "activity":
|
|
80
|
+
return call(rest, "get_recent_activity");
|
|
61
81
|
default:
|
|
62
82
|
throw new CliError(
|
|
63
|
-
`Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, competitors, question, detection, site, post, top, bank, access`
|
|
83
|
+
`Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, reads, questions, competitors, question, detection, activity, site, post, top, bank, access`
|
|
64
84
|
);
|
|
65
85
|
}
|
|
66
86
|
}
|
package/lib/commands/phantom.mjs
CHANGED
|
@@ -111,6 +111,11 @@ export const ARGS = {
|
|
|
111
111
|
},
|
|
112
112
|
delete_phantom: { phantom_id: { positional: 0, label: "phantom-id", required: true } },
|
|
113
113
|
rebuild_phantom: { phantom_id: { positional: 0, label: "phantom-id", required: true } },
|
|
114
|
+
// `phantom pause <id>` / `phantom resume <id>` — the verb sets `paused`.
|
|
115
|
+
set_phantom_paused: {
|
|
116
|
+
phantom_id: { positional: 0, label: "phantom-id", required: true },
|
|
117
|
+
paused: { value: (flags, positionals, extra) => extra.paused },
|
|
118
|
+
},
|
|
114
119
|
get_phantom_diagnostics: { phantom_id: { positional: 0, label: "phantom-id", required: true } },
|
|
115
120
|
get_phantom_balance: {},
|
|
116
121
|
spread_phantom_looks: { apply: { flag: "apply", type: "bool" } },
|
|
@@ -154,6 +159,9 @@ export async function cmdPhantom(ctx) {
|
|
|
154
159
|
return call(rest, "delete_phantom");
|
|
155
160
|
case "rebuild":
|
|
156
161
|
return call(rest, "rebuild_phantom");
|
|
162
|
+
case "pause":
|
|
163
|
+
case "resume":
|
|
164
|
+
return phantomPause(rest, sub === "pause");
|
|
157
165
|
case "diagnostics":
|
|
158
166
|
case "diag":
|
|
159
167
|
return call(rest, "get_phantom_diagnostics");
|
|
@@ -165,11 +173,17 @@ export async function cmdPhantom(ctx) {
|
|
|
165
173
|
return phantomDomain(rest);
|
|
166
174
|
default:
|
|
167
175
|
throw new CliError(
|
|
168
|
-
`Unknown phantom subcommand: ${sub ?? "(none)"}. Try: list, get, new, config, visitors, delete, rebuild, diagnostics, balance, spread-looks, domain`
|
|
176
|
+
`Unknown phantom subcommand: ${sub ?? "(none)"}. Try: list, get, new, config, visitors, delete, rebuild, pause, resume, diagnostics, balance, spread-looks, domain`
|
|
169
177
|
);
|
|
170
178
|
}
|
|
171
179
|
}
|
|
172
180
|
|
|
181
|
+
async function phantomPause(ctx, paused) {
|
|
182
|
+
const args = argsFromFlags(ARGS.set_phantom_paused, ctx.flags, ctx.positionals, { paused });
|
|
183
|
+
printResult(ctx.io, ctx.flags, await ctx.client.callTool("set_phantom_paused", args));
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
173
187
|
async function call(ctx, tool) {
|
|
174
188
|
const result = await ctx.client.callTool(tool, argsFromFlags(ARGS[tool], ctx.flags, ctx.positionals));
|
|
175
189
|
printResult(ctx.io, ctx.flags, result);
|
package/lib/commands/repo.mjs
CHANGED
|
@@ -165,6 +165,8 @@ const ARGS = {
|
|
|
165
165
|
value: (flags) => (flags["autopilot-weekly"] === undefined ? undefined : Number(flags["autopilot-weekly"])),
|
|
166
166
|
},
|
|
167
167
|
autopilot_min_severity: { flag: "autopilot-severity", enum: ["critical", "warning"] },
|
|
168
|
+
// --auto-merge 1h|12h|24h|3d (or the hours: 1, 12, 24, 72), or --auto-merge off
|
|
169
|
+
auto_merge_after_hours: { value: (flags) => autoMergeHours(flags["auto-merge"]) },
|
|
168
170
|
},
|
|
169
171
|
get_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
170
172
|
get_repo_change_activity: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
@@ -195,6 +197,16 @@ const ARGS = {
|
|
|
195
197
|
},
|
|
196
198
|
};
|
|
197
199
|
|
|
200
|
+
/** `--auto-merge`: off → null, 1h / 12h / 24h / 3d or plain hours → the hours; anything else is passed on for the tool to refuse. */
|
|
201
|
+
function autoMergeHours(value) {
|
|
202
|
+
if (value === undefined) return undefined;
|
|
203
|
+
const text = String(value).trim().toLowerCase();
|
|
204
|
+
if (["off", "none", "false", "0"].includes(text)) return null;
|
|
205
|
+
const match = /^(\d+)\s*(h|hours?|d|days?)?$/.exec(text);
|
|
206
|
+
if (!match) return text;
|
|
207
|
+
return Number(match[1]) * (match[2]?.startsWith("d") ? 24 : 1);
|
|
208
|
+
}
|
|
209
|
+
|
|
198
210
|
const TERMINAL = new Set(["succeeded", "no_changes", "failed", "cancelled"]);
|
|
199
211
|
|
|
200
212
|
function describe(run) {
|
|
@@ -634,6 +646,7 @@ async function repoSettings(ctx) {
|
|
|
634
646
|
"autopilot",
|
|
635
647
|
"autopilot-weekly",
|
|
636
648
|
"autopilot-severity",
|
|
649
|
+
"auto-merge",
|
|
637
650
|
];
|
|
638
651
|
refuseUnknownFlags(ctx, {}, settingFlags);
|
|
639
652
|
const setting = settingFlags.some((f) => flags[f] !== undefined);
|