@letterstory/cli 0.176.44 → 0.176.46
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 +2 -1
- package/lib/commands/automate.mjs +11 -1
- package/lib/commands/automations.mjs +12 -1
- package/lib/commands/build.mjs +20 -0
- package/lib/commands/plan.mjs +57 -1
- package/lib/commands/visits.mjs +28 -1
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
import { fetchParkedSections, withoutParked } from "./parked.mjs";
|
|
85
85
|
|
|
86
86
|
// Matches cli/package.json; publish-cli.yml rewrites both to the published version.
|
|
87
|
-
export const VERSION = "0.176.
|
|
87
|
+
export const VERSION = "0.176.46";
|
|
88
88
|
|
|
89
89
|
/** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
|
|
90
90
|
export const EXIT_PAYMENT_REQUIRED = 3;
|
|
@@ -1853,6 +1853,7 @@ Visits (Visitors: AI agents reading your site, and AI answers sending people to
|
|
|
1853
1853
|
Pages one agent read that the other didn't
|
|
1854
1854
|
visits export [--since 30d] [--format json|csv] [--table agent_visits|citations]
|
|
1855
1855
|
visits sites Your Visitors sites and their install checks
|
|
1856
|
+
visits domain <domain> [--site <id>] [--name <n>] Move a site to a new domain, keeping its key
|
|
1856
1857
|
visits pages [--since 28d] [--path '/docs/*'] [--agent <id>] [--limit <n>] [--site <id>]
|
|
1857
1858
|
People and AI agents per page
|
|
1858
1859
|
visits health --site <id> Staff: how far to trust a site's numbers, each check and its fix
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Same flag/positional/io conventions as posts.mjs — no commander, no color.
|
|
6
6
|
|
|
7
7
|
import { CliError } from "../client.mjs";
|
|
8
|
-
import { printResult, ok, argsFromFlags, flagList, refuseUnknownFlags } from "./shared.mjs";
|
|
8
|
+
import { printResult, ok, argsFromFlags, flagList, flagStr, refuseUnknownFlags } from "./shared.mjs";
|
|
9
9
|
|
|
10
10
|
const TRIGGERS = ["competitor_page", "visitor_identified", "issue_found", "build_suggestion", "plan_update", "custom"];
|
|
11
11
|
const TEMPLATES = [
|
|
@@ -74,6 +74,13 @@ function factsOf(flags) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/** See `argsFromFlags` in shared.mjs; `scripts/parity-check.ts` diffs these keys against the tool. */
|
|
77
|
+
const destination = {
|
|
78
|
+
value: (flags) => {
|
|
79
|
+
const v = flagStr(flags.destination);
|
|
80
|
+
return v === undefined ? undefined : v === "none" ? null : { collection_id: v === "waiting" ? null : v };
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
77
84
|
export const ARGS = {
|
|
78
85
|
describe_automation_options: {},
|
|
79
86
|
list_event_automations: {},
|
|
@@ -81,6 +88,7 @@ export const ARGS = {
|
|
|
81
88
|
automation_id: { positional: 0, label: "automation-id", required: true },
|
|
82
89
|
},
|
|
83
90
|
create_event_automation: {
|
|
91
|
+
destination,
|
|
84
92
|
template: { flag: "template", enum: TEMPLATES },
|
|
85
93
|
name: { flag: "name" },
|
|
86
94
|
trigger: { flag: "trigger", enum: TRIGGERS },
|
|
@@ -92,6 +100,8 @@ export const ARGS = {
|
|
|
92
100
|
backfill,
|
|
93
101
|
},
|
|
94
102
|
update_event_automation: {
|
|
103
|
+
destination,
|
|
104
|
+
expected_updated_at: "expected-updated-at",
|
|
95
105
|
automation_id: { positional: 0, label: "automation-id", required: true },
|
|
96
106
|
name: { flag: "name" },
|
|
97
107
|
trigger_params: triggerParams,
|
|
@@ -86,6 +86,8 @@ export const ARGS = {
|
|
|
86
86
|
},
|
|
87
87
|
},
|
|
88
88
|
update_automation: {
|
|
89
|
+
expected_updated_at: "expected-updated-at",
|
|
90
|
+
expected_source: { value: () => undefined },
|
|
89
91
|
collection_id: { flag: "collection", required: true },
|
|
90
92
|
automation_id: { positional: 0, label: "automation-id", required: true },
|
|
91
93
|
pace_per_week: { flag: "pace", type: "num" },
|
|
@@ -116,6 +118,7 @@ export const ARGS = {
|
|
|
116
118
|
reason: { flag: "reason" },
|
|
117
119
|
},
|
|
118
120
|
delete_automation: {
|
|
121
|
+
expected_source: { value: () => undefined },
|
|
119
122
|
collection_id: { flag: "collection", required: true },
|
|
120
123
|
automation_id: { positional: 0, label: "automation-id", required: true },
|
|
121
124
|
},
|
|
@@ -163,7 +166,15 @@ export async function cmdAutomations(ctx) {
|
|
|
163
166
|
|
|
164
167
|
async function run(ctx, tool, said) {
|
|
165
168
|
const { client, flags, io } = ctx;
|
|
166
|
-
const
|
|
169
|
+
const args = argsFromFlags(ARGS[tool], flags, ctx.positionals);
|
|
170
|
+
if ((tool === "update_automation" || tool === "delete_automation") && args.automation_id?.startsWith("derived:")) {
|
|
171
|
+
const { automation: a } = await client.callTool("get_automation", {
|
|
172
|
+
collection_id: args.collection_id,
|
|
173
|
+
automation_id: args.automation_id,
|
|
174
|
+
});
|
|
175
|
+
args.expected_source = { kind: a.kind, cluster_id: a.cluster_id, target_id: a.target_id, seer_id: a.seer_id };
|
|
176
|
+
}
|
|
177
|
+
const result = await client.callTool(tool, args);
|
|
167
178
|
if (said) ok(ctx, said);
|
|
168
179
|
printResult(io, flags, result);
|
|
169
180
|
return 0;
|
package/lib/commands/build.mjs
CHANGED
|
@@ -21,6 +21,16 @@ import { attachmentUrls, uploadAttachments } from "./repo.mjs";
|
|
|
21
21
|
|
|
22
22
|
const ARGS = {
|
|
23
23
|
start_build: {
|
|
24
|
+
purpose: {
|
|
25
|
+
value: (flags) => {
|
|
26
|
+
if (typeof flags.purpose !== "string") return undefined;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(flags.purpose);
|
|
29
|
+
} catch {
|
|
30
|
+
throw new CliError("--purpose takes JSON.");
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
},
|
|
24
34
|
// The words after `build`, or --prompt for a longer ask.
|
|
25
35
|
prompt: { value: (flags, positionals) => flagStr(flags.prompt) ?? (positionals.join(" ").trim() || undefined) },
|
|
26
36
|
domain: "site",
|
|
@@ -59,6 +69,16 @@ const ARGS = {
|
|
|
59
69
|
},
|
|
60
70
|
},
|
|
61
71
|
send_build_message: {
|
|
72
|
+
work_context: {
|
|
73
|
+
value: (flags) => {
|
|
74
|
+
if (typeof flags["work-context"] !== "string") return undefined;
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(flags["work-context"]);
|
|
77
|
+
} catch {
|
|
78
|
+
throw new CliError("--work-context takes JSON.");
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
},
|
|
62
82
|
message: {
|
|
63
83
|
value: (flags, positionals) => flagStr(flags.message) ?? (positionals.join(" ").trim() || undefined),
|
|
64
84
|
},
|
package/lib/commands/plan.mjs
CHANGED
|
@@ -17,6 +17,17 @@
|
|
|
17
17
|
import { CliError } from "../client.mjs";
|
|
18
18
|
import { argsFromFlags, flagStr, printResult, refuseUnknownFlags } from "./shared.mjs";
|
|
19
19
|
|
|
20
|
+
const jsonFlag = (name) => ({
|
|
21
|
+
value: (flags) => {
|
|
22
|
+
const v = flagStr(flags[name]);
|
|
23
|
+
if (v === undefined) return undefined;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(v);
|
|
26
|
+
} catch {
|
|
27
|
+
throw new CliError("--" + name + " takes JSON.");
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
});
|
|
20
31
|
const onOff = (name) => (flags) => {
|
|
21
32
|
const v = flagStr(flags[name]);
|
|
22
33
|
if (v === undefined) return undefined;
|
|
@@ -26,6 +37,19 @@ const onOff = (name) => (flags) => {
|
|
|
26
37
|
};
|
|
27
38
|
|
|
28
39
|
export const ARGS = {
|
|
40
|
+
list_plan_work_purposes: { resource: jsonFlag("resource"), strategy_id: "strategy" },
|
|
41
|
+
set_plan_work_purpose: {
|
|
42
|
+
resource: jsonFlag("resource"),
|
|
43
|
+
purpose: jsonFlag("purpose"),
|
|
44
|
+
attached: { value: (flags) => !flags.remove },
|
|
45
|
+
},
|
|
46
|
+
get_plan_work: {
|
|
47
|
+
purpose: jsonFlag("purpose"),
|
|
48
|
+
section: { flag: "section", enum: ["writing", "watching"] },
|
|
49
|
+
resource_id: "resource",
|
|
50
|
+
offset: { flag: "offset", type: "num" },
|
|
51
|
+
limit: { flag: "limit", type: "num" },
|
|
52
|
+
},
|
|
29
53
|
get_auto_plan: { domain: "site" },
|
|
30
54
|
set_auto_plan: {
|
|
31
55
|
domain: "site",
|
|
@@ -99,6 +123,38 @@ export async function cmdPlan(ctx) {
|
|
|
99
123
|
const { client, flags, io } = ctx;
|
|
100
124
|
const sub = ctx.positionals[0];
|
|
101
125
|
const rest = ctx.positionals.slice(1);
|
|
126
|
+
if (sub === "purposes" || sub === "purpose") {
|
|
127
|
+
const tool = sub === "purposes" ? "list_plan_work_purposes" : "set_plan_work_purpose";
|
|
128
|
+
refuseUnknownFlags(ctx, ARGS[tool]);
|
|
129
|
+
const r = await client.callTool(tool, argsFromFlags(ARGS[tool], flags, rest));
|
|
130
|
+
printResult(
|
|
131
|
+
io,
|
|
132
|
+
flags,
|
|
133
|
+
r,
|
|
134
|
+
(v) =>
|
|
135
|
+
v.purposes.map((p) => p.title + " · " + p.resource.type + " " + p.resource.id).join("\n") ||
|
|
136
|
+
"No goal associations."
|
|
137
|
+
);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
if (sub === "work") {
|
|
141
|
+
refuseUnknownFlags(ctx, ARGS.get_plan_work);
|
|
142
|
+
const result = await client.callTool("get_plan_work", argsFromFlags(ARGS.get_plan_work, flags, rest));
|
|
143
|
+
printResult(
|
|
144
|
+
io,
|
|
145
|
+
flags,
|
|
146
|
+
result,
|
|
147
|
+
(r) =>
|
|
148
|
+
[
|
|
149
|
+
...r.items.map(
|
|
150
|
+
(w) => w.title + " · " + w.status_label + "\n " + (w.attention || w.summary) + "\n " + w.href
|
|
151
|
+
),
|
|
152
|
+
...r.unavailable.map((u) => u.reason),
|
|
153
|
+
...(r.next_offset !== null ? ["More: plan work --offset " + r.next_offset] : []),
|
|
154
|
+
].join("\n") || "No ongoing work."
|
|
155
|
+
);
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
102
158
|
if (sub === "auto") {
|
|
103
159
|
refuseUnknownFlags(ctx, {}, ["site", ...SETTING_FLAGS]);
|
|
104
160
|
if (rest[0] && !["activate", "disable"].includes(rest[0]))
|
|
@@ -210,6 +266,6 @@ export async function cmdPlan(ctx) {
|
|
|
210
266
|
throw new CliError('Usage: plan strategy add "<words…>" | plan strategy archive <strategy-id> [--restore]');
|
|
211
267
|
}
|
|
212
268
|
throw new CliError(
|
|
213
|
-
"Usage: plan auto [settings…] | plan pending | plan veto <proposal-id> | plan start <proposal-id> | plan preview | plan later <action-id> | plan later remove <id> | plan strategies | plan strategy add|archive"
|
|
269
|
+
"Usage: plan work [--section writing|watching] | plan auto [settings…] | plan pending | plan veto <proposal-id> | plan start <proposal-id> | plan preview | plan later <action-id> | plan later remove <id> | plan strategies | plan strategy add|archive"
|
|
214
270
|
);
|
|
215
271
|
}
|
package/lib/commands/visits.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// letterstory visits people --since 30d people who read the site, and how many were identified
|
|
12
12
|
// letterstory visits export --since 30d --format csv
|
|
13
13
|
// letterstory visits sites
|
|
14
|
+
// letterstory visits domain composal.com [--site <id>] move the site to a new domain, keeping its key
|
|
14
15
|
// letterstory visits pages --since 28d [--path '/docs/*'] [--agent openai-crawler]
|
|
15
16
|
// letterstory visits health --site <id> staff: how far to trust a site's numbers, and what to fix
|
|
16
17
|
// letterstory visits connect vercel|cloudflare send the host's request logs (no code to deploy)
|
|
@@ -26,6 +27,11 @@ const days = { value: (flags) => sinceDays(flags.since) };
|
|
|
26
27
|
|
|
27
28
|
export const ARGS = {
|
|
28
29
|
visitors_list_sites: {},
|
|
30
|
+
visitors_update_site: {
|
|
31
|
+
site_id: "site",
|
|
32
|
+
domain: { positional: 0, label: "new domain (e.g. acme.com)", required: true },
|
|
33
|
+
name: "name",
|
|
34
|
+
},
|
|
29
35
|
visitors_get_agent_visits: {
|
|
30
36
|
site_id: "site",
|
|
31
37
|
days,
|
|
@@ -99,6 +105,8 @@ export async function cmdVisits(ctx) {
|
|
|
99
105
|
return visitsExport(rest);
|
|
100
106
|
case "sites":
|
|
101
107
|
return visitsSites(rest);
|
|
108
|
+
case "domain":
|
|
109
|
+
return visitsDomain(rest);
|
|
102
110
|
case "pages":
|
|
103
111
|
return visitsPages(rest);
|
|
104
112
|
case "health":
|
|
@@ -111,7 +119,7 @@ export async function cmdVisits(ctx) {
|
|
|
111
119
|
return visitsDisconnect(rest);
|
|
112
120
|
default:
|
|
113
121
|
throw new CliError(
|
|
114
|
-
"Usage: visits [list|citations|people|compare|export|sites|pages|health|connect|connectors|disconnect]"
|
|
122
|
+
"Usage: visits [list|citations|people|compare|export|sites|domain|pages|health|connect|connectors|disconnect]"
|
|
115
123
|
);
|
|
116
124
|
}
|
|
117
125
|
}
|
|
@@ -359,6 +367,25 @@ async function visitsPages(ctx) {
|
|
|
359
367
|
return 0;
|
|
360
368
|
}
|
|
361
369
|
|
|
370
|
+
async function visitsDomain(ctx) {
|
|
371
|
+
const { flags, io } = ctx;
|
|
372
|
+
const result = await call(ctx, "visitors_update_site");
|
|
373
|
+
printResult(io, flags, result, (r) => {
|
|
374
|
+
const domain = r.site?.domain ?? "";
|
|
375
|
+
if (!r.changed) return `Already on ${domain}.`;
|
|
376
|
+
const check = r.verification;
|
|
377
|
+
return [
|
|
378
|
+
`Moved ${r.previous_domain ?? "the site"} to ${domain}; the key stays ${r.site?.key ?? ""}.`,
|
|
379
|
+
check
|
|
380
|
+
? check.found
|
|
381
|
+
? `Proven: the script was found on ${check.checked_url ?? domain}.`
|
|
382
|
+
: `Not proven yet: ${check.reason}`
|
|
383
|
+
: "Same site: its proof carried over.",
|
|
384
|
+
].join("\n");
|
|
385
|
+
});
|
|
386
|
+
return 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
362
389
|
async function visitsSites(ctx) {
|
|
363
390
|
const { flags, io } = ctx;
|
|
364
391
|
const result = await call(ctx, "visitors_list_sites");
|