@letterstory/cli 0.1.1 → 0.2.1
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 +146 -13
- package/bin/phantom.mjs +16 -0
- package/lib/cli.mjs +168 -18
- package/lib/client.mjs +78 -16
- package/lib/commands/auth.mjs +125 -0
- package/lib/commands/collections.mjs +93 -0
- package/lib/commands/connectors.mjs +56 -0
- package/lib/commands/deploy.mjs +387 -0
- package/lib/commands/discovery.mjs +238 -0
- package/lib/commands/flows.mjs +83 -0
- package/lib/commands/insights.mjs +51 -0
- package/lib/commands/mcp.mjs +52 -0
- package/lib/commands/posts.mjs +122 -0
- package/lib/commands/shared.mjs +95 -0
- package/lib/commands/strategy.mjs +209 -0
- package/lib/commands.mjs +16 -323
- package/lib/oauth.mjs +204 -0
- package/package.json +3 -2
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// `strategy` (company profile, positioning, competitors, sitemap import) and
|
|
2
|
+
// `onboarding` (checklist status/step). Ported from phantomstory-cli's strategy.ts,
|
|
3
|
+
// which registers `onboarding` as its own top-level command alongside `strategy` —
|
|
4
|
+
// mirrored here as two exported handlers for the same reason.
|
|
5
|
+
|
|
6
|
+
import { CliError } from "../client.mjs";
|
|
7
|
+
import {
|
|
8
|
+
flagStr,
|
|
9
|
+
requireFlag,
|
|
10
|
+
requirePositional,
|
|
11
|
+
printResult,
|
|
12
|
+
compact,
|
|
13
|
+
flagList,
|
|
14
|
+
ok,
|
|
15
|
+
readBodyInput,
|
|
16
|
+
} from "./shared.mjs";
|
|
17
|
+
|
|
18
|
+
export async function cmdStrategy(ctx) {
|
|
19
|
+
const sub = ctx.positionals[0];
|
|
20
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
21
|
+
switch (sub) {
|
|
22
|
+
case "company":
|
|
23
|
+
return strategyCompany(rest);
|
|
24
|
+
case "positioning":
|
|
25
|
+
return strategyPositioning(rest);
|
|
26
|
+
case "competitors":
|
|
27
|
+
return strategyCompetitors(rest);
|
|
28
|
+
case "sitemap":
|
|
29
|
+
return strategySitemap(rest);
|
|
30
|
+
default:
|
|
31
|
+
throw new CliError(
|
|
32
|
+
`Unknown strategy subcommand: ${sub ?? "(none)"}. Try: company, positioning, competitors, sitemap`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// -- company -------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
async function strategyCompany(ctx) {
|
|
40
|
+
const sub = ctx.positionals[0];
|
|
41
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
42
|
+
switch (sub) {
|
|
43
|
+
case "get":
|
|
44
|
+
return companyGet(rest);
|
|
45
|
+
case "set":
|
|
46
|
+
return companySet(rest);
|
|
47
|
+
default:
|
|
48
|
+
throw new CliError(`Unknown strategy company subcommand: ${sub ?? "(none)"}. Try: get, set`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function companyGet(ctx) {
|
|
53
|
+
const { client, flags, io } = ctx;
|
|
54
|
+
const result = await client.callTool("get_company_info", {});
|
|
55
|
+
printResult(io, flags, result);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function companySet(ctx) {
|
|
60
|
+
const { client, flags, io } = ctx;
|
|
61
|
+
const args = compact({ name: flagStr(flags.name), domain: flagStr(flags.domain) });
|
|
62
|
+
// The API clears the manifesto with an empty string, which compact() would drop —
|
|
63
|
+
// so set it explicitly whenever the user supplied a source (text or file).
|
|
64
|
+
const manifesto = flagStr(flags.manifesto);
|
|
65
|
+
const manifestoFile = flagStr(flags["manifesto-file"]);
|
|
66
|
+
if (manifesto !== undefined || manifestoFile !== undefined) {
|
|
67
|
+
args.manifesto = manifesto ?? readBodyInput({ file: manifestoFile }) ?? "";
|
|
68
|
+
}
|
|
69
|
+
if (Object.keys(args).length === 0) {
|
|
70
|
+
throw new CliError('Nothing to set — pass --name/--domain/--manifesto (empty --manifesto "" clears it).');
|
|
71
|
+
}
|
|
72
|
+
const result = await client.callTool("set_company_info", args);
|
|
73
|
+
ok(ctx, "Company profile updated.");
|
|
74
|
+
printResult(io, flags, result);
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// -- positioning -----------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
async function strategyPositioning(ctx) {
|
|
81
|
+
const sub = ctx.positionals[0];
|
|
82
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
83
|
+
switch (sub) {
|
|
84
|
+
case "get":
|
|
85
|
+
return positioningGet(rest);
|
|
86
|
+
case "set":
|
|
87
|
+
return positioningSet(rest);
|
|
88
|
+
default:
|
|
89
|
+
throw new CliError(`Unknown strategy positioning subcommand: ${sub ?? "(none)"}. Try: get, set`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function positioningGet(ctx) {
|
|
94
|
+
const { client, flags, io } = ctx;
|
|
95
|
+
const result = await client.callTool("get_positioning", {});
|
|
96
|
+
printResult(io, flags, result);
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function positioningSet(ctx) {
|
|
101
|
+
const { client, flags, io } = ctx;
|
|
102
|
+
const topics = flagList(flags.topic);
|
|
103
|
+
const stances = flagList(flags.stance);
|
|
104
|
+
const avoid = flagList(flags.avoid);
|
|
105
|
+
// The API clears a list with an explicit empty array (which compact() keeps).
|
|
106
|
+
const args = compact({
|
|
107
|
+
priority_topics: flags["clear-topics"] ? [] : topics.length ? topics : undefined,
|
|
108
|
+
stances: flags["clear-stances"] ? [] : stances.length ? stances : undefined,
|
|
109
|
+
avoid_topics: flags["clear-avoid"] ? [] : avoid.length ? avoid : undefined,
|
|
110
|
+
});
|
|
111
|
+
if (Object.keys(args).length === 0) {
|
|
112
|
+
throw new CliError("Pass at least one --topic/--stance/--avoid (or a --clear-* flag).");
|
|
113
|
+
}
|
|
114
|
+
const result = await client.callTool("set_positioning", args);
|
|
115
|
+
ok(ctx, "Positioning updated.");
|
|
116
|
+
printResult(io, flags, result);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// -- competitors -------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
async function strategyCompetitors(ctx) {
|
|
123
|
+
const sub = ctx.positionals[0];
|
|
124
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
125
|
+
switch (sub) {
|
|
126
|
+
case "list":
|
|
127
|
+
case "ls":
|
|
128
|
+
return competitorsList(rest);
|
|
129
|
+
case "add":
|
|
130
|
+
return competitorsAdd(rest);
|
|
131
|
+
default:
|
|
132
|
+
throw new CliError(`Unknown strategy competitors subcommand: ${sub ?? "(none)"}. Try: list, add`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function competitorsList(ctx) {
|
|
137
|
+
const { client, flags, io } = ctx;
|
|
138
|
+
const result = await client.callTool("list_competitors", {});
|
|
139
|
+
printResult(io, flags, result);
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function competitorsAdd(ctx) {
|
|
144
|
+
const { client, positionals, flags, io } = ctx;
|
|
145
|
+
const name = requirePositional(positionals, 0, "name");
|
|
146
|
+
const domain = requirePositional(positionals, 1, "domain");
|
|
147
|
+
const result = await client.callTool("add_competitor", { name, domain });
|
|
148
|
+
ok(ctx, `Added competitor ${name}.`);
|
|
149
|
+
printResult(io, flags, result);
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// -- sitemap -------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
async function strategySitemap(ctx) {
|
|
156
|
+
const { client, flags, io } = ctx;
|
|
157
|
+
const collection = requireFlag(flags, "collection");
|
|
158
|
+
const url = requireFlag(flags, "url");
|
|
159
|
+
const sub = flagList(flags.sub);
|
|
160
|
+
const args = compact({
|
|
161
|
+
collection_id: collection,
|
|
162
|
+
sitemap_url: url,
|
|
163
|
+
sitemap_urls: sub.length ? sub : undefined,
|
|
164
|
+
url_pattern: flagStr(flags.pattern),
|
|
165
|
+
});
|
|
166
|
+
const result = await client.callTool("import_sitemap", args);
|
|
167
|
+
ok(ctx, "Sitemap import started.");
|
|
168
|
+
printResult(io, flags, result);
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// --- onboarding ---------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
export async function cmdOnboarding(ctx) {
|
|
175
|
+
const sub = ctx.positionals[0];
|
|
176
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
177
|
+
switch (sub) {
|
|
178
|
+
case "status":
|
|
179
|
+
return onboardingStatus(rest);
|
|
180
|
+
case "step":
|
|
181
|
+
return onboardingStep(rest);
|
|
182
|
+
default:
|
|
183
|
+
throw new CliError(`Unknown onboarding subcommand: ${sub ?? "(none)"}. Try: status, step`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function onboardingStatus(ctx) {
|
|
188
|
+
const { client, flags, io } = ctx;
|
|
189
|
+
const result = await client.callTool("get_onboarding_status", {});
|
|
190
|
+
printResult(io, flags, result);
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function onboardingStep(ctx) {
|
|
195
|
+
const { client, flags, io } = ctx;
|
|
196
|
+
const args = compact({
|
|
197
|
+
current_step: flagStr(flags.current),
|
|
198
|
+
complete_step: flagStr(flags.complete),
|
|
199
|
+
skip_step: flagStr(flags.skip),
|
|
200
|
+
status: flagStr(flags.status),
|
|
201
|
+
});
|
|
202
|
+
if (Object.keys(args).length === 0) {
|
|
203
|
+
throw new CliError("Pass --current/--complete/--skip/--status.");
|
|
204
|
+
}
|
|
205
|
+
const result = await client.callTool("set_onboarding_step", args);
|
|
206
|
+
ok(ctx, "Onboarding updated.");
|
|
207
|
+
printResult(io, flags, result);
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
package/lib/commands.mjs
CHANGED
|
@@ -1,323 +1,16 @@
|
|
|
1
|
-
// Command handlers
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (s === undefined) return undefined;
|
|
18
|
-
const n = Number(s);
|
|
19
|
-
if (!Number.isFinite(n)) throw new CliError(`Expected a number, got "${s}"`);
|
|
20
|
-
return n;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function requireFlag(flags, name) {
|
|
24
|
-
const v = flagStr(flags[name]);
|
|
25
|
-
if (v === undefined) throw new CliError(`Missing required --${name}`);
|
|
26
|
-
return v;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function requirePositional(positionals, index, label) {
|
|
30
|
-
const v = positionals[index];
|
|
31
|
-
if (v === undefined) throw new CliError(`Missing required <${label}>`);
|
|
32
|
-
return v;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function printResult(io, flags, value, formatter) {
|
|
36
|
-
if (flags.json || !formatter) {
|
|
37
|
-
io.log(JSON.stringify(value, null, 2));
|
|
38
|
-
} else {
|
|
39
|
-
io.log(formatter(value));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function formatDeployment(d) {
|
|
44
|
-
const lines = [
|
|
45
|
-
`${d.name} [${d.deployment_id}]`,
|
|
46
|
-
` status: ${d.status}${d.phase ? ` · ${d.phase}` : ""}`,
|
|
47
|
-
` theme: ${d.theme}`,
|
|
48
|
-
` collection: ${d.collection_id ?? "(none)"}`,
|
|
49
|
-
];
|
|
50
|
-
if (d.url) lines.push(` url: ${d.url}`);
|
|
51
|
-
if (d.domain) lines.push(` domain: ${d.domain}`);
|
|
52
|
-
if (d.last_error) lines.push(` error: ${d.last_error}`);
|
|
53
|
-
return lines.join("\n");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Poll get_deployment until the blog reaches a terminal state. Emits a line only
|
|
57
|
-
// when status/phase changes so the output reads as progress, not a firehose.
|
|
58
|
-
async function pollUntilTerminal(client, id, io) {
|
|
59
|
-
const maxPolls = io.maxPolls ?? 90;
|
|
60
|
-
const interval = io.pollIntervalMs ?? 4000;
|
|
61
|
-
let last = "";
|
|
62
|
-
for (let i = 0; i < maxPolls; i++) {
|
|
63
|
-
const d = await client.callTool("get_deployment", { deployment_id: id });
|
|
64
|
-
if (d.status === "live" || d.status === "error") return d;
|
|
65
|
-
const line = `${d.status}${d.phase ? ` · ${d.phase}` : ""}`;
|
|
66
|
-
if (line !== last) {
|
|
67
|
-
io.log(` … ${line}`);
|
|
68
|
-
last = line;
|
|
69
|
-
}
|
|
70
|
-
await io.sleep(interval);
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
status: "error",
|
|
74
|
-
last_error: `Timed out waiting for it to go live. Check: letterstory deploy get ${id}`,
|
|
75
|
-
url: null,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// --- auth / config ----------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
export async function cmdLogin(ctx) {
|
|
82
|
-
const { flags, io } = ctx;
|
|
83
|
-
const key = requireFlag(flags, "key");
|
|
84
|
-
const url = flagStr(flags.url);
|
|
85
|
-
if (!key.startsWith("lb_")) {
|
|
86
|
-
io.error("Warning: Letterstory API keys usually start with 'lb_'. Saving anyway.");
|
|
87
|
-
}
|
|
88
|
-
const saved = { key, ...(url ? { url } : {}) };
|
|
89
|
-
const path = writeConfigFile(saved);
|
|
90
|
-
io.log(`Saved credentials to ${path}`);
|
|
91
|
-
|
|
92
|
-
// Verify the key works (and report the tool count) without failing the save if
|
|
93
|
-
// the network is down — a saved-but-unverified key is still usable later.
|
|
94
|
-
try {
|
|
95
|
-
const resolved = resolveConfig({ url, key });
|
|
96
|
-
const client = new LetterstoryClient({ url: resolved.url, key: resolved.key });
|
|
97
|
-
const tools = await client.listTools();
|
|
98
|
-
io.log(`Authenticated to ${resolved.url} — ${tools.length} tools available.`);
|
|
99
|
-
return 0;
|
|
100
|
-
} catch (err) {
|
|
101
|
-
io.error(`Saved, but could not verify the key: ${err.message}`);
|
|
102
|
-
return 0;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function cmdLogout(ctx) {
|
|
107
|
-
const path = clearConfigFile();
|
|
108
|
-
ctx.io.log(`Cleared credentials at ${path}`);
|
|
109
|
-
return 0;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function cmdConfig(ctx) {
|
|
113
|
-
const { config, io, flags } = ctx;
|
|
114
|
-
const masked = config.key ? `${config.key.slice(0, 6)}…${config.key.slice(-4)}` : "(none)";
|
|
115
|
-
const value = {
|
|
116
|
-
url: config.url,
|
|
117
|
-
key: masked,
|
|
118
|
-
key_source: config.keySource,
|
|
119
|
-
config_file: configPath(),
|
|
120
|
-
};
|
|
121
|
-
if (flags.json) {
|
|
122
|
-
io.log(JSON.stringify(value, null, 2));
|
|
123
|
-
} else {
|
|
124
|
-
io.log(`url: ${value.url}`);
|
|
125
|
-
io.log(`key: ${value.key} (from ${value.key_source})`);
|
|
126
|
-
io.log(`config file: ${value.config_file}`);
|
|
127
|
-
}
|
|
128
|
-
return 0;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// --- discovery / generic passthrough ----------------------------------------
|
|
132
|
-
|
|
133
|
-
export async function cmdTools(ctx) {
|
|
134
|
-
const { client, io, flags } = ctx;
|
|
135
|
-
const doc = await client.discover();
|
|
136
|
-
const tools = doc.tools ?? [];
|
|
137
|
-
if (flags.json) {
|
|
138
|
-
io.log(JSON.stringify(tools, null, 2));
|
|
139
|
-
return 0;
|
|
140
|
-
}
|
|
141
|
-
io.log(`${tools.length} tools available at ${client.url}:\n`);
|
|
142
|
-
for (const t of tools) io.log(` ${t.name} — ${t.description ?? ""}`);
|
|
143
|
-
return 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Escape hatch: call any tool by name. Args come from --args '<json>', or from
|
|
147
|
-
// individual string flags (everything after the tool name). Keeps the whole
|
|
148
|
-
// manifest reachable without a bespoke subcommand per tool.
|
|
149
|
-
export async function cmdCall(ctx) {
|
|
150
|
-
const { client, positionals, flags, io } = ctx;
|
|
151
|
-
const name = requirePositional(positionals, 0, "tool");
|
|
152
|
-
let args = {};
|
|
153
|
-
if (flags.args !== undefined) {
|
|
154
|
-
try {
|
|
155
|
-
args = JSON.parse(flagStr(flags.args) ?? "");
|
|
156
|
-
} catch {
|
|
157
|
-
throw new CliError(`--args must be a JSON object, e.g. --args '{"name":"My Blog"}'`);
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
for (const [k, v] of Object.entries(flags)) {
|
|
161
|
-
if (k === "json" || k === "url" || k === "key") continue;
|
|
162
|
-
args[k] = v;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
const result = await client.callTool(name, args);
|
|
166
|
-
io.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
|
167
|
-
return 0;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// --- deployments (phantom blogs) --------------------------------------------
|
|
171
|
-
|
|
172
|
-
export async function cmdDeploy(ctx) {
|
|
173
|
-
const sub = ctx.positionals[0];
|
|
174
|
-
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
175
|
-
switch (sub) {
|
|
176
|
-
case "create":
|
|
177
|
-
return deployCreate(rest);
|
|
178
|
-
case "list":
|
|
179
|
-
return deployList(rest);
|
|
180
|
-
case "get":
|
|
181
|
-
case "status":
|
|
182
|
-
return deployGet(rest);
|
|
183
|
-
case "rebuild":
|
|
184
|
-
return deployRebuild(rest);
|
|
185
|
-
case "diagnostics":
|
|
186
|
-
return deployDiagnostics(rest);
|
|
187
|
-
case "delete":
|
|
188
|
-
return deployDelete(rest);
|
|
189
|
-
default:
|
|
190
|
-
throw new CliError(
|
|
191
|
-
`Unknown deploy subcommand: ${sub ?? "(none)"}. Try: create, list, get, rebuild, diagnostics, delete`
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function deployCreate(ctx) {
|
|
197
|
-
const { client, flags, io } = ctx;
|
|
198
|
-
const name = requireFlag(flags, "name");
|
|
199
|
-
const args = { name };
|
|
200
|
-
const description = flagStr(flags.description);
|
|
201
|
-
const theme = flagStr(flags.theme);
|
|
202
|
-
const collection = flagStr(flags.collection);
|
|
203
|
-
if (description !== undefined) args.description = description;
|
|
204
|
-
if (theme !== undefined) args.theme = theme;
|
|
205
|
-
if (collection !== undefined) args.collection_id = collection;
|
|
206
|
-
|
|
207
|
-
const created = await client.callTool("create_deployment", args);
|
|
208
|
-
io.log(`Created deployment ${created.deployment_id} (${created.status})`);
|
|
209
|
-
|
|
210
|
-
if (flags["no-wait"]) {
|
|
211
|
-
io.log(`Provisioning in the background. Poll with: letterstory deploy get ${created.deployment_id}`);
|
|
212
|
-
return 0;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const final = await pollUntilTerminal(client, created.deployment_id, io);
|
|
216
|
-
if (final.status === "live") {
|
|
217
|
-
io.log(`\n✓ Live at ${final.url}`);
|
|
218
|
-
return 0;
|
|
219
|
-
}
|
|
220
|
-
io.error(`\n✗ Deployment ${final.status}: ${final.last_error ?? "unknown error"}`);
|
|
221
|
-
return 1;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async function deployList(ctx) {
|
|
225
|
-
const { client, flags, io } = ctx;
|
|
226
|
-
const limit = flagNum(flags.limit);
|
|
227
|
-
const result = await client.callTool("list_deployments", limit !== undefined ? { limit } : {});
|
|
228
|
-
const items = result.items ?? [];
|
|
229
|
-
if (flags.json) {
|
|
230
|
-
io.log(JSON.stringify(result, null, 2));
|
|
231
|
-
return 0;
|
|
232
|
-
}
|
|
233
|
-
if (items.length === 0) {
|
|
234
|
-
io.log("No deployments yet. Create one with: letterstory deploy create --name <name>");
|
|
235
|
-
return 0;
|
|
236
|
-
}
|
|
237
|
-
for (const d of items) {
|
|
238
|
-
io.log(`${d.status.padEnd(13)} ${d.deployment_id} ${d.name}${d.url ? ` ${d.url}` : ""}`);
|
|
239
|
-
}
|
|
240
|
-
return 0;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async function deployGet(ctx) {
|
|
244
|
-
const { client, positionals, flags, io } = ctx;
|
|
245
|
-
const id = requirePositional(positionals, 0, "deployment-id");
|
|
246
|
-
const d = await client.callTool("get_deployment", { deployment_id: id });
|
|
247
|
-
printResult(io, flags, d, formatDeployment);
|
|
248
|
-
return 0;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async function deployRebuild(ctx) {
|
|
252
|
-
const { client, positionals, io } = ctx;
|
|
253
|
-
const id = requirePositional(positionals, 0, "deployment-id");
|
|
254
|
-
await client.callTool("rebuild_deployment", { deployment_id: id });
|
|
255
|
-
io.log(`Rebuild started for ${id}. New published content will appear once the build finishes.`);
|
|
256
|
-
return 0;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function deployDiagnostics(ctx) {
|
|
260
|
-
const { client, positionals, io } = ctx;
|
|
261
|
-
const id = requirePositional(positionals, 0, "deployment-id");
|
|
262
|
-
const diag = await client.callTool("get_deployment_diagnostics", { deployment_id: id });
|
|
263
|
-
io.log(JSON.stringify(diag, null, 2));
|
|
264
|
-
return 0;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async function deployDelete(ctx) {
|
|
268
|
-
const { client, positionals, flags, io } = ctx;
|
|
269
|
-
const id = requirePositional(positionals, 0, "deployment-id");
|
|
270
|
-
if (!flags.yes) {
|
|
271
|
-
io.error(`This tears down the blog's site and revokes its content key (the collection is kept).`);
|
|
272
|
-
io.error(`Re-run with --yes to confirm: letterstory deploy delete ${id} --yes`);
|
|
273
|
-
return 1;
|
|
274
|
-
}
|
|
275
|
-
await client.callTool("delete_deployment", { deployment_id: id });
|
|
276
|
-
io.log(`Deleted deployment ${id}.`);
|
|
277
|
-
return 0;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// --- custom domains ---------------------------------------------------------
|
|
281
|
-
|
|
282
|
-
export async function cmdDomain(ctx) {
|
|
283
|
-
const sub = ctx.positionals[0];
|
|
284
|
-
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
285
|
-
switch (sub) {
|
|
286
|
-
case "check":
|
|
287
|
-
return domainCheck(rest);
|
|
288
|
-
case "buy":
|
|
289
|
-
return domainBuy(rest);
|
|
290
|
-
default:
|
|
291
|
-
throw new CliError(`Unknown domain subcommand: ${sub ?? "(none)"}. Try: check, buy`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async function domainCheck(ctx) {
|
|
296
|
-
const { client, positionals, flags, io } = ctx;
|
|
297
|
-
const domain = requirePositional(positionals, 0, "domain");
|
|
298
|
-
const quote = await client.callTool("check_domain", { domain });
|
|
299
|
-
if (flags.json) {
|
|
300
|
-
io.log(JSON.stringify(quote, null, 2));
|
|
301
|
-
return 0;
|
|
302
|
-
}
|
|
303
|
-
io.log(`${domain}`);
|
|
304
|
-
io.log(` available: ${quote.available}`);
|
|
305
|
-
if (quote.price !== undefined) io.log(` price: $${quote.price}`);
|
|
306
|
-
io.log(` purchasable: ${quote.purchasable}`);
|
|
307
|
-
return 0;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// No confirm gate: the app's BuyDomainDialog charges on a single "Get this domain"
|
|
311
|
-
// click, and the CLI mirrors the app's user-facing flow. The spend is bounded on the
|
|
312
|
-
// server, not by a client prompt — `buy_domain` is server-priced and hard-capped
|
|
313
|
-
// (≤2 domains/org, <$30, blog must be live with no domain yet).
|
|
314
|
-
async function domainBuy(ctx) {
|
|
315
|
-
const { client, positionals, flags, io } = ctx;
|
|
316
|
-
const id = requirePositional(positionals, 0, "deployment-id");
|
|
317
|
-
const domain = requirePositional(positionals, 1, "domain");
|
|
318
|
-
const result = await client.callTool("buy_domain", { deployment_id: id, domain });
|
|
319
|
-
io.log(`Purchase started for ${domain}. Attach + DNS run in the background.`);
|
|
320
|
-
io.log(`Poll with: letterstory deploy get ${id}`);
|
|
321
|
-
if (flags.json) io.log(JSON.stringify(result, null, 2));
|
|
322
|
-
return 0;
|
|
323
|
-
}
|
|
1
|
+
// Command handlers, split into focused modules under ./commands/*.mjs as the surface
|
|
2
|
+
// grew past a single-file's worth of clarity (auth, discovery, deploy/domain, posts,
|
|
3
|
+
// collections, flows, connectors, strategy, insights). This file re-exports
|
|
4
|
+
// everything so `cli.mjs` — and any test importing straight from "commands.mjs" —
|
|
5
|
+
// keep a single, stable import path regardless of how the implementation is split.
|
|
6
|
+
|
|
7
|
+
export * from "./commands/auth.mjs";
|
|
8
|
+
export * from "./commands/discovery.mjs";
|
|
9
|
+
export * from "./commands/deploy.mjs";
|
|
10
|
+
export * from "./commands/mcp.mjs";
|
|
11
|
+
export * from "./commands/posts.mjs";
|
|
12
|
+
export * from "./commands/collections.mjs";
|
|
13
|
+
export * from "./commands/flows.mjs";
|
|
14
|
+
export * from "./commands/connectors.mjs";
|
|
15
|
+
export * from "./commands/strategy.mjs";
|
|
16
|
+
export * from "./commands/insights.mjs";
|