frontpage-host 0.1.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/dist/auth/keychain.d.ts +5 -0
- package/dist/auth/keychain.js +85 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +124 -0
- package/dist/commands/analytics.d.ts +1 -0
- package/dist/commands/analytics.js +18 -0
- package/dist/commands/articles.d.ts +1 -0
- package/dist/commands/articles.js +111 -0
- package/dist/commands/cat.d.ts +1 -0
- package/dist/commands/cat.js +27 -0
- package/dist/commands/completion.d.ts +1 -0
- package/dist/commands/completion.js +19 -0
- package/dist/commands/deploy.d.ts +2 -0
- package/dist/commands/deploy.js +32 -0
- package/dist/commands/diff.d.ts +1 -0
- package/dist/commands/diff.js +93 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +26 -0
- package/dist/commands/edit.d.ts +1 -0
- package/dist/commands/edit.js +31 -0
- package/dist/commands/extensions.d.ts +1 -0
- package/dist/commands/extensions.js +60 -0
- package/dist/commands/help.d.ts +1 -0
- package/dist/commands/help.js +27 -0
- package/dist/commands/history.d.ts +1 -0
- package/dist/commands/history.js +49 -0
- package/dist/commands/inbox.d.ts +1 -0
- package/dist/commands/inbox.js +46 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +38 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +7 -0
- package/dist/commands/ls.d.ts +1 -0
- package/dist/commands/ls.js +19 -0
- package/dist/commands/media.d.ts +1 -0
- package/dist/commands/media.js +44 -0
- package/dist/commands/modules.d.ts +1 -0
- package/dist/commands/modules.js +56 -0
- package/dist/commands/open.d.ts +1 -0
- package/dist/commands/open.js +28 -0
- package/dist/commands/pages.d.ts +1 -0
- package/dist/commands/pages.js +16 -0
- package/dist/commands/prompt.d.ts +1 -0
- package/dist/commands/prompt.js +39 -0
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +28 -0
- package/dist/commands/pull.d.ts +1 -0
- package/dist/commands/pull.js +60 -0
- package/dist/commands/push.d.ts +1 -0
- package/dist/commands/push.js +117 -0
- package/dist/commands/revert.d.ts +1 -0
- package/dist/commands/revert.js +27 -0
- package/dist/commands/runs.d.ts +1 -0
- package/dist/commands/runs.js +134 -0
- package/dist/commands/search.d.ts +1 -0
- package/dist/commands/search.js +30 -0
- package/dist/commands/seo.d.ts +1 -0
- package/dist/commands/seo.js +57 -0
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +65 -0
- package/dist/commands/sites.d.ts +1 -0
- package/dist/commands/sites.js +20 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +35 -0
- package/dist/commands/usage.d.ts +1 -0
- package/dist/commands/usage.js +14 -0
- package/dist/commands/use.d.ts +1 -0
- package/dist/commands/use.js +25 -0
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +14 -0
- package/dist/completions/generate.d.ts +4 -0
- package/dist/completions/generate.js +82 -0
- package/dist/http/client.d.ts +27 -0
- package/dist/http/client.js +163 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +116 -0
- package/dist/site.d.ts +5 -0
- package/dist/site.js +32 -0
- package/dist/state/frontpage-state.d.ts +8 -0
- package/dist/state/frontpage-state.js +28 -0
- package/package.json +40 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { resolveSlug } from "../site.js";
|
|
2
|
+
import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get, v1Post } from "../http/client.js";
|
|
3
|
+
function takeFlag(rest, name) {
|
|
4
|
+
const i = rest.indexOf(name);
|
|
5
|
+
if (i === -1)
|
|
6
|
+
return undefined;
|
|
7
|
+
const v = rest[i + 1];
|
|
8
|
+
rest.splice(i, 2);
|
|
9
|
+
return v;
|
|
10
|
+
}
|
|
11
|
+
export async function runExtensions(args) {
|
|
12
|
+
try {
|
|
13
|
+
const { slug, rest } = await resolveSlug(args);
|
|
14
|
+
const sub = rest[0];
|
|
15
|
+
if (!sub || sub === "list" || sub === "ls") {
|
|
16
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/extensions`);
|
|
17
|
+
if (!data.enabled) {
|
|
18
|
+
console.log("Extensions are not enabled for this environment.");
|
|
19
|
+
return EXIT_OK;
|
|
20
|
+
}
|
|
21
|
+
for (const e of data.extensions) {
|
|
22
|
+
console.log(`${e.key.padEnd(20)} ${String(e.state).padEnd(12)} ${e.name}`);
|
|
23
|
+
}
|
|
24
|
+
return EXIT_OK;
|
|
25
|
+
}
|
|
26
|
+
if (sub === "enable" || sub === "disable" || sub === "config") {
|
|
27
|
+
const key = rest[1];
|
|
28
|
+
if (!key) {
|
|
29
|
+
console.error(`Usage: frontpage extensions ${sub} <key> [--config '{…}']`);
|
|
30
|
+
return EXIT_USAGE;
|
|
31
|
+
}
|
|
32
|
+
const work = rest.slice(2);
|
|
33
|
+
let config;
|
|
34
|
+
if (sub === "config") {
|
|
35
|
+
const raw = takeFlag(work, "--config") ?? work[0];
|
|
36
|
+
if (!raw) {
|
|
37
|
+
console.error("Usage: frontpage extensions config <key> --config '{…json…}'");
|
|
38
|
+
return EXIT_USAGE;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
config = JSON.parse(raw);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
console.error("--config must be valid JSON");
|
|
45
|
+
return EXIT_USAGE;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const action = sub === "config" ? "set_config" : sub;
|
|
49
|
+
const data = await v1Post(`/api/v1/sites/${encodeURIComponent(slug)}/extensions/${encodeURIComponent(key)}`, { action, config });
|
|
50
|
+
console.log(JSON.stringify(data, null, 2));
|
|
51
|
+
return EXIT_OK;
|
|
52
|
+
}
|
|
53
|
+
console.error("Usage: frontpage extensions [list|enable|disable|config] …");
|
|
54
|
+
return EXIT_USAGE;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
58
|
+
return exitCodeForError(err);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runHelp(args: string[]): number;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { COMMANDS, TOPICS, findCommand } from "../registry.js";
|
|
2
|
+
export function runHelp(args) {
|
|
3
|
+
const topic = args[0]?.trim();
|
|
4
|
+
if (!topic) {
|
|
5
|
+
console.log("Frontpage CLI\n");
|
|
6
|
+
console.log("Usage: frontpage <command> [options]\n");
|
|
7
|
+
console.log("Commands:");
|
|
8
|
+
for (const c of COMMANDS) {
|
|
9
|
+
console.log(` ${c.name.padEnd(10)} ${c.summary}`);
|
|
10
|
+
}
|
|
11
|
+
console.log("\nTopics: frontpage help auth | frontpage help exit-codes");
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
if (TOPICS[topic]) {
|
|
15
|
+
console.log(TOPICS[topic]);
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
const cmd = findCommand(topic);
|
|
19
|
+
if (cmd) {
|
|
20
|
+
console.log(`${cmd.name} — ${cmd.summary}`);
|
|
21
|
+
if (cmd.usage)
|
|
22
|
+
console.log(`Usage: ${cmd.usage}`);
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
console.error(`Unknown help topic: ${topic}`);
|
|
26
|
+
return 2;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runHistory(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { resolveSlug } from "../site.js";
|
|
2
|
+
import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get } from "../http/client.js";
|
|
3
|
+
export async function runHistory(args) {
|
|
4
|
+
try {
|
|
5
|
+
const { slug, rest } = await resolveSlug(args);
|
|
6
|
+
let limit = "20";
|
|
7
|
+
let page = "1";
|
|
8
|
+
for (let i = 0; i < rest.length; i++) {
|
|
9
|
+
const a = rest[i];
|
|
10
|
+
if (a === "--limit" || a === "-n") {
|
|
11
|
+
limit = rest[++i] ?? limit;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (a.startsWith("--limit=")) {
|
|
15
|
+
limit = a.slice("--limit=".length);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (a === "--page") {
|
|
19
|
+
page = rest[++i] ?? page;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (a.startsWith("--page=")) {
|
|
23
|
+
page = a.slice("--page=".length);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (a === "--help" || a === "-h") {
|
|
27
|
+
console.error("Usage: frontpage history [--limit N] [--page N] [--site <slug>]");
|
|
28
|
+
return EXIT_USAGE;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const qs = new URLSearchParams({ limit, page });
|
|
32
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/history?${qs}`);
|
|
33
|
+
for (const c of data.commits) {
|
|
34
|
+
const mark = data.stagingCommit === c.sha ? "S" : data.productionCommit === c.sha ? "P" : " ";
|
|
35
|
+
const who = c.actorEmail ?? c.actorKind ?? "";
|
|
36
|
+
console.log(`${mark} ${c.shortSha} ${c.authorDate.slice(0, 10)} ${c.subject}${who ? ` (${who})` : ""}`);
|
|
37
|
+
}
|
|
38
|
+
if (data.hasMore)
|
|
39
|
+
console.log(`… more on page ${data.page + 1}`);
|
|
40
|
+
else if (data.moreBeyondWindow) {
|
|
41
|
+
console.log(`… end of ${data.windowDays}-day history window`);
|
|
42
|
+
}
|
|
43
|
+
return EXIT_OK;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
47
|
+
return exitCodeForError(err);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runInbox(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { resolveSlug } from "../site.js";
|
|
2
|
+
import { EXIT_OK, EXIT_AUTH, EXIT_UNEXPECTED, exitCodeForError, v1Get, apiBase, CliHttpError, } from "../http/client.js";
|
|
3
|
+
import { getApiKey } from "../auth/keychain.js";
|
|
4
|
+
export async function runInbox(args) {
|
|
5
|
+
try {
|
|
6
|
+
const { slug, rest } = await resolveSlug(args);
|
|
7
|
+
let kind = "leads";
|
|
8
|
+
let format = "json";
|
|
9
|
+
let markRead = false;
|
|
10
|
+
for (const a of rest) {
|
|
11
|
+
if (a === "--csv")
|
|
12
|
+
format = "csv";
|
|
13
|
+
else if (a === "--mark-read")
|
|
14
|
+
markRead = true;
|
|
15
|
+
else if (!a.startsWith("-"))
|
|
16
|
+
kind = a;
|
|
17
|
+
}
|
|
18
|
+
const qs = new URLSearchParams({ kind, format });
|
|
19
|
+
if (markRead)
|
|
20
|
+
qs.set("markRead", "1");
|
|
21
|
+
const path = `/api/v1/sites/${encodeURIComponent(slug)}/inbox?${qs}`;
|
|
22
|
+
if (format === "csv") {
|
|
23
|
+
const key = await getApiKey();
|
|
24
|
+
if (!key)
|
|
25
|
+
throw new CliHttpError("Not logged in. Run: frontpage login", EXIT_AUTH, 401);
|
|
26
|
+
const res = await fetch(`${apiBase()}${path}`, {
|
|
27
|
+
headers: { Authorization: `Bearer ${key}`, Accept: "text/csv" },
|
|
28
|
+
});
|
|
29
|
+
const text = await res.text();
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
throw new CliHttpError(`Request failed (${res.status})`, EXIT_UNEXPECTED, res.status);
|
|
32
|
+
}
|
|
33
|
+
process.stdout.write(text);
|
|
34
|
+
if (!text.endsWith("\n"))
|
|
35
|
+
process.stdout.write("\n");
|
|
36
|
+
return EXIT_OK;
|
|
37
|
+
}
|
|
38
|
+
const data = await v1Get(path);
|
|
39
|
+
console.log(JSON.stringify(data, null, 2));
|
|
40
|
+
return EXIT_OK;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
44
|
+
return exitCodeForError(err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runLogin(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { setApiKey, maskKey } from "../auth/keychain.js";
|
|
4
|
+
import { EXIT_OK, EXIT_USAGE, v1Get } from "../http/client.js";
|
|
5
|
+
export async function runLogin(args) {
|
|
6
|
+
let secret = args[0]?.trim();
|
|
7
|
+
if (!secret) {
|
|
8
|
+
const rl = createInterface({ input, output });
|
|
9
|
+
try {
|
|
10
|
+
secret = (await rl.question("Paste your Frontpage API key: ")).trim();
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
rl.close();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (!secret) {
|
|
17
|
+
console.error("No API key provided.");
|
|
18
|
+
return EXIT_USAGE;
|
|
19
|
+
}
|
|
20
|
+
if (!secret.startsWith("fp_live_")) {
|
|
21
|
+
console.error("Key should start with fp_live_. Create one in Developers (profile menu).");
|
|
22
|
+
return EXIT_USAGE;
|
|
23
|
+
}
|
|
24
|
+
await setApiKey(secret);
|
|
25
|
+
// Verify against the API (uses FRONTPAGE_API_KEY if set, else keychain — we just wrote keychain).
|
|
26
|
+
process.env.FRONTPAGE_API_KEY = secret;
|
|
27
|
+
try {
|
|
28
|
+
const me = await v1Get("/api/v1/me");
|
|
29
|
+
console.log(`Logged in as ${me.email ?? me.userId}`);
|
|
30
|
+
console.log(`Saved key ${maskKey(secret)}`);
|
|
31
|
+
return EXIT_OK;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
35
|
+
console.error("Key was saved, but /api/v1/me failed. Check PUBLIC_CLI_ENABLED and the key.");
|
|
36
|
+
return 5;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runLogout(): Promise<number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runLs(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { resolveSlug } from "../site.js";
|
|
2
|
+
import { EXIT_OK, exitCodeForError, v1Get } from "../http/client.js";
|
|
3
|
+
export async function runLs(args) {
|
|
4
|
+
try {
|
|
5
|
+
const { slug, rest } = await resolveSlug(args);
|
|
6
|
+
const prefix = rest[0];
|
|
7
|
+
const q = prefix ? `?prefix=${encodeURIComponent(prefix)}` : "";
|
|
8
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/files${q}`);
|
|
9
|
+
for (const f of data.files) {
|
|
10
|
+
console.log(`${String(f.bytes).padStart(10)} ${f.path}`);
|
|
11
|
+
}
|
|
12
|
+
console.error(`# ${data.files.length} file(s) · ${data.slug}${data.baseCommitSha ? ` @ ${data.baseCommitSha.slice(0, 7)}` : ""}`);
|
|
13
|
+
return EXIT_OK;
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
17
|
+
return exitCodeForError(err);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runMedia(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { resolveSlug } from "../site.js";
|
|
4
|
+
import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get, v1PostMultipart } from "../http/client.js";
|
|
5
|
+
export async function runMedia(args) {
|
|
6
|
+
try {
|
|
7
|
+
const { slug, rest } = await resolveSlug(args);
|
|
8
|
+
const sub = rest[0] ?? "list";
|
|
9
|
+
if (sub === "list" || sub === "ls") {
|
|
10
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/media`);
|
|
11
|
+
for (const a of data.assets) {
|
|
12
|
+
console.log(`${a.sitePath.padEnd(48)} ${String(a.sizeBytes).padStart(8)} ${a.name}`);
|
|
13
|
+
}
|
|
14
|
+
console.log(`(${data.count} assets)`);
|
|
15
|
+
return EXIT_OK;
|
|
16
|
+
}
|
|
17
|
+
if (sub === "upload") {
|
|
18
|
+
const filePath = rest[1];
|
|
19
|
+
if (!filePath) {
|
|
20
|
+
console.error("Usage: frontpage media upload <file> [--site <slug>]");
|
|
21
|
+
return EXIT_USAGE;
|
|
22
|
+
}
|
|
23
|
+
const bytes = await readFile(filePath);
|
|
24
|
+
const form = new FormData();
|
|
25
|
+
form.append("file", new Blob([Uint8Array.from(bytes)]), basename(filePath));
|
|
26
|
+
const data = await v1PostMultipart(`/api/v1/sites/${encodeURIComponent(slug)}/media`, form);
|
|
27
|
+
if (data.kind === "text") {
|
|
28
|
+
console.log(`Inline text (${data.filename}): ${data.sizeBytes ?? 0} bytes — not stored.`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log(data.url ?? data.key ?? JSON.stringify(data));
|
|
32
|
+
if (data.key)
|
|
33
|
+
console.log(`sitePath: /__media/${data.key}`);
|
|
34
|
+
}
|
|
35
|
+
return EXIT_OK;
|
|
36
|
+
}
|
|
37
|
+
console.error("Usage: frontpage media list|upload <file> [--site <slug>]");
|
|
38
|
+
return EXIT_USAGE;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
42
|
+
return exitCodeForError(err);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runModules(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { resolveSlug } from "../site.js";
|
|
2
|
+
import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get, v1Post } from "../http/client.js";
|
|
3
|
+
function takeFlag(rest, name) {
|
|
4
|
+
const i = rest.indexOf(name);
|
|
5
|
+
if (i === -1)
|
|
6
|
+
return undefined;
|
|
7
|
+
const v = rest[i + 1];
|
|
8
|
+
rest.splice(i, 2);
|
|
9
|
+
return v;
|
|
10
|
+
}
|
|
11
|
+
export async function runModules(args) {
|
|
12
|
+
try {
|
|
13
|
+
const { slug, rest } = await resolveSlug(args);
|
|
14
|
+
const sub = rest[0];
|
|
15
|
+
if (!sub || sub === "list" || sub === "ls") {
|
|
16
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/modules`);
|
|
17
|
+
for (const m of data.modules) {
|
|
18
|
+
console.log(`${m.key.padEnd(20)} ${String(m.status).padEnd(12)} ${m.name}`);
|
|
19
|
+
}
|
|
20
|
+
return EXIT_OK;
|
|
21
|
+
}
|
|
22
|
+
if (sub === "enable" || sub === "disable" || sub === "archive" || sub === "config") {
|
|
23
|
+
const key = rest[1];
|
|
24
|
+
if (!key) {
|
|
25
|
+
console.error(`Usage: frontpage modules ${sub} <key> [--instance <id>] [--config '{…}']`);
|
|
26
|
+
return EXIT_USAGE;
|
|
27
|
+
}
|
|
28
|
+
const work = rest.slice(2);
|
|
29
|
+
const instanceId = takeFlag(work, "--instance");
|
|
30
|
+
let config;
|
|
31
|
+
if (sub === "config") {
|
|
32
|
+
const raw = takeFlag(work, "--config") ?? work[0];
|
|
33
|
+
if (!raw) {
|
|
34
|
+
console.error("Usage: frontpage modules config <key> --config '{…json…}'");
|
|
35
|
+
return EXIT_USAGE;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
config = JSON.parse(raw);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
console.error("--config must be valid JSON");
|
|
42
|
+
return EXIT_USAGE;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const data = await v1Post(`/api/v1/sites/${encodeURIComponent(slug)}/modules/${encodeURIComponent(key)}`, { action: sub, config, instanceId });
|
|
46
|
+
console.log(JSON.stringify(data, null, 2));
|
|
47
|
+
return EXIT_OK;
|
|
48
|
+
}
|
|
49
|
+
console.error("Usage: frontpage modules [list|enable|disable|archive|config] …");
|
|
50
|
+
return EXIT_USAGE;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
54
|
+
return exitCodeForError(err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runOpen(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolveSlug } from "../site.js";
|
|
3
|
+
import { EXIT_OK, EXIT_USAGE, exitCodeForError } from "../http/client.js";
|
|
4
|
+
export async function runOpen(args) {
|
|
5
|
+
try {
|
|
6
|
+
const { slug, rest } = await resolveSlug(args);
|
|
7
|
+
const prod = rest.includes("--prod") || args.includes("--prod");
|
|
8
|
+
const url = prod
|
|
9
|
+
? `https://${slug}.frontpage.host/` // staging subdomain always; custom domain is gateway-resolved
|
|
10
|
+
: `https://${slug}.frontpage.host/`;
|
|
11
|
+
// Both staging editing and public subdomain use *.frontpage.host; --prod is reserved
|
|
12
|
+
// for a future production custom-domain lookup. For Phase 1 open the site subdomain.
|
|
13
|
+
void prod;
|
|
14
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
15
|
+
const openerArgs = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
16
|
+
spawn(opener, openerArgs, { detached: true, stdio: "ignore" }).unref();
|
|
17
|
+
console.log(url);
|
|
18
|
+
return EXIT_OK;
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
if (err instanceof Error && err.message.includes("Usage")) {
|
|
22
|
+
console.error("Usage: frontpage open [--site <slug>] [--prod]");
|
|
23
|
+
return EXIT_USAGE;
|
|
24
|
+
}
|
|
25
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
26
|
+
return exitCodeForError(err);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runPages(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { resolveSlug } from "../site.js";
|
|
2
|
+
import { EXIT_OK, exitCodeForError, v1Get } from "../http/client.js";
|
|
3
|
+
export async function runPages(args) {
|
|
4
|
+
try {
|
|
5
|
+
const { slug } = await resolveSlug(args);
|
|
6
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/pages`);
|
|
7
|
+
for (const p of data.pages) {
|
|
8
|
+
console.log(`${p.path.padEnd(40)} ${p.title}`);
|
|
9
|
+
}
|
|
10
|
+
return EXIT_OK;
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
14
|
+
return exitCodeForError(err);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runPrompt(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { resolveSlug } from "../site.js";
|
|
2
|
+
import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Post } from "../http/client.js";
|
|
3
|
+
export async function runPrompt(args) {
|
|
4
|
+
try {
|
|
5
|
+
const { slug, rest } = await resolveSlug(args);
|
|
6
|
+
let currentPath = "/";
|
|
7
|
+
const promptParts = [];
|
|
8
|
+
for (let i = 0; i < rest.length; i++) {
|
|
9
|
+
const a = rest[i];
|
|
10
|
+
if (a === "--path") {
|
|
11
|
+
currentPath = rest[++i] ?? "/";
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (a.startsWith("--path=")) {
|
|
15
|
+
currentPath = a.slice("--path=".length) || "/";
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
promptParts.push(a);
|
|
19
|
+
}
|
|
20
|
+
const prompt = promptParts.join(" ").trim();
|
|
21
|
+
if (!prompt) {
|
|
22
|
+
console.error('Usage: frontpage prompt "..." [--site <slug>] [--path /page]');
|
|
23
|
+
return EXIT_USAGE;
|
|
24
|
+
}
|
|
25
|
+
if (!currentPath.startsWith("/")) {
|
|
26
|
+
console.error("currentPath must start with /");
|
|
27
|
+
return EXIT_USAGE;
|
|
28
|
+
}
|
|
29
|
+
const data = await v1Post(`/api/v1/sites/${encodeURIComponent(slug)}/agent-runs`, { prompt, currentPath });
|
|
30
|
+
console.log(data.runId);
|
|
31
|
+
if (data.coalesced)
|
|
32
|
+
console.error("(coalesced with an identical queued run)");
|
|
33
|
+
return EXIT_OK;
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
37
|
+
return exitCodeForError(err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { resolveSlug } from "../site.js";
|
|
2
|
+
import { EXIT_OK, exitCodeForError, v1Delete, v1Post } from "../http/client.js";
|
|
3
|
+
export async function runPublish(args) {
|
|
4
|
+
try {
|
|
5
|
+
const { slug } = await resolveSlug(args);
|
|
6
|
+
const data = await v1Post(`/api/v1/sites/${encodeURIComponent(slug)}/publish`);
|
|
7
|
+
console.log(`Published ${slug}`);
|
|
8
|
+
if (data.productionUrl)
|
|
9
|
+
console.log(data.productionUrl);
|
|
10
|
+
return EXIT_OK;
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
14
|
+
return exitCodeForError(err);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function runUnpublish(args) {
|
|
18
|
+
try {
|
|
19
|
+
const { slug } = await resolveSlug(args);
|
|
20
|
+
await v1Delete(`/api/v1/sites/${encodeURIComponent(slug)}/publish`);
|
|
21
|
+
console.log(`Unpublished ${slug}`);
|
|
22
|
+
return EXIT_OK;
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
26
|
+
return exitCodeForError(err);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runPull(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve, sep } from "node:path";
|
|
3
|
+
import { resolveSlug } from "../site.js";
|
|
4
|
+
import { EXIT_OK, EXIT_UNEXPECTED, exitCodeForError, v1Get } from "../http/client.js";
|
|
5
|
+
import { readState, writeState } from "../state/frontpage-state.js";
|
|
6
|
+
/** Reject absolute / traversal paths before writing into cwd (defense in depth). */
|
|
7
|
+
function assertSafePullPath(repoPath, cwd) {
|
|
8
|
+
if (!repoPath ||
|
|
9
|
+
repoPath.startsWith("/") ||
|
|
10
|
+
repoPath.includes("..") ||
|
|
11
|
+
repoPath.includes("\\") ||
|
|
12
|
+
repoPath.includes("\0")) {
|
|
13
|
+
throw new Error(`Refusing unsafe pull path: ${repoPath}`);
|
|
14
|
+
}
|
|
15
|
+
const abs = resolve(cwd, repoPath);
|
|
16
|
+
const root = resolve(cwd) + sep;
|
|
17
|
+
if (abs !== resolve(cwd) && !abs.startsWith(root)) {
|
|
18
|
+
throw new Error(`Refusing pull path outside working directory: ${repoPath}`);
|
|
19
|
+
}
|
|
20
|
+
return abs;
|
|
21
|
+
}
|
|
22
|
+
export async function runPull(args) {
|
|
23
|
+
try {
|
|
24
|
+
const { slug } = await resolveSlug(args);
|
|
25
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/pull`);
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
let written = 0;
|
|
28
|
+
for (const file of data.files) {
|
|
29
|
+
let abs;
|
|
30
|
+
try {
|
|
31
|
+
abs = assertSafePullPath(file.path, cwd);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
35
|
+
return EXIT_UNEXPECTED;
|
|
36
|
+
}
|
|
37
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
38
|
+
if (file.encoding === "base64") {
|
|
39
|
+
await writeFile(abs, Buffer.from(file.content, "base64"));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
await writeFile(abs, file.content, "utf8");
|
|
43
|
+
}
|
|
44
|
+
written++;
|
|
45
|
+
}
|
|
46
|
+
const prev = await readState(cwd);
|
|
47
|
+
await writeState({ ...prev, slug: data.slug, baseCommitSha: data.baseCommitSha ?? undefined }, cwd);
|
|
48
|
+
console.log(`Pulled ${written} file(s) for ${data.slug}`);
|
|
49
|
+
if (data.baseCommitSha)
|
|
50
|
+
console.log(`baseCommitSha: ${data.baseCommitSha}`);
|
|
51
|
+
if (data.truncated)
|
|
52
|
+
console.log("Warning: pull truncated (file or size cap). Re-run or narrow the tree.");
|
|
53
|
+
console.log("Wrote .frontpage/state.json");
|
|
54
|
+
return EXIT_OK;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
58
|
+
return exitCodeForError(err);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runPush(args: string[]): Promise<number>;
|