frontpage-host 0.1.3 → 0.1.5

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/AGENTS.md CHANGED
@@ -92,6 +92,27 @@ Rules:
92
92
  7. Prefer reviewing the subdomain after push; only use `deploy wait` if you need to block until a build finishes.
93
93
  8. The **UI / canvas agent does not use this CLI** — it edits the live tip in-process. Only CLI checkouts need `pull`.
94
94
 
95
+ ## Site contract (Astro)
96
+
97
+ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or https://frontpage.host/developers/site-building
98
+
99
+ 1. **Every page** wraps content in `<BaseLayout title="…" description="…">` (import from `src/layouts/BaseLayout.astro`).
100
+ 2. **`src/config/site.ts`** — keep `pages` (path registry) and `nav` in sync when adding/renaming/deleting page files. Tab title comes from BaseLayout, not the `pages` map label.
101
+ 3. **One file per URL** for similar page sets (services, guides, …). Do not default to `getStaticPaths()` over `src/data/*.ts`. Blog/news → `frontpage articles`, not a hand-rolled catalog.
102
+ 4. **No backends / databases / fake forms or checkout.** Real forms use `<form data-fp-form="slug">` + `frontpage modules`. Refuse or use modules for payments.
103
+ 5. **Tokens** in `src/config/tokens.ts` — keep required color/font/spacing/radii keys; style via CSS variables / token Tailwind. Setting a font token is not enough — apply `font-sans` / `font-serif` / `font-display`. Browse fonts with `frontpage fonts`; set one token with `frontpage tokens set --group … --key … --value …` (CAS like push). Module how-tos: `frontpage modules docs <key>` / `frontpage extensions docs <key>`.
104
+ 6. **Shared chrome** — prefer `src/components/Header.astro` + `Footer.astro` (or `chrome: { managed: true }` in `site.ts`).
105
+ 7. **404** — customize `src/pages/404.astro` if needed; keep `frontpage:404` meta; never register `/404` in `nav`/`pages`.
106
+ 8. **Platform-owned paths** stay off-limits (BaseLayout overlay, module/extension shells, `package.json`, …). Customer layouts like `DocsLayout` / `GuideLayout` are OK.
107
+
108
+ ## Modules & articles
109
+
110
+ - Forms: `data-fp-form` markup in pages + `frontpage modules` for enable/config. No submit JS / honeypot / `action`.
111
+ - Articles: `frontpage articles …` for lifecycle; bodies via sync under `content/` or `articles content get|set`. Section landing is an owned `src/pages/<section>/index.astro`.
112
+ - Migrate a folder into Articles: prefer `frontpage prompt "migrate … into articles"` — do not invent a mass delete+write migration.
113
+ - Extensions: `frontpage extensions`; if LOCKED on plan, do not fake charts/etc.
114
+ - Media in markup: `/__media/uploads/<slug>/…` from `frontpage media` (not raw blob URLs).
115
+
95
116
  ## Agent prompt loop
96
117
 
97
118
  ```bash
@@ -194,6 +215,7 @@ Safe default after finishing work: summarize what changed and give the staging U
194
215
  frontpage help
195
216
  frontpage help --json
196
217
  frontpage help sync
218
+ frontpage help site-building
197
219
  frontpage help agent
198
220
  frontpage doctor --json
199
221
  ```
package/dist/bin.js CHANGED
@@ -29,6 +29,8 @@ import { runSeo } from "./commands/seo.js";
29
29
  import { runArticles } from "./commands/articles.js";
30
30
  import { runSettings } from "./commands/settings.js";
31
31
  import { runEdit } from "./commands/edit.js";
32
+ import { runFonts } from "./commands/fonts.js";
33
+ import { runTokens } from "./commands/tokens.js";
32
34
  import { runHistory } from "./commands/history.js";
33
35
  import { runRevert } from "./commands/revert.js";
34
36
  import { runCompletion } from "./commands/completion.js";
@@ -110,6 +112,10 @@ async function main() {
110
112
  return runSettings(args);
111
113
  case "edit":
112
114
  return runEdit(args);
115
+ case "fonts":
116
+ return runFonts(args);
117
+ case "tokens":
118
+ return runTokens(args);
113
119
  case "history":
114
120
  return runHistory(args);
115
121
  case "revert":
@@ -23,6 +23,20 @@ export async function runExtensions(args) {
23
23
  }
24
24
  return EXIT_OK;
25
25
  }
26
+ if (sub === "docs" || sub === "doc") {
27
+ const key = rest[1];
28
+ if (!key) {
29
+ console.error("Usage: frontpage extensions docs <key> [--site <slug>]");
30
+ return EXIT_USAGE;
31
+ }
32
+ const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/extensions/${encodeURIComponent(key)}`);
33
+ console.log(`# ${data.key} (${data.name}) [${data.state}]`);
34
+ if (data.note)
35
+ console.log(data.note);
36
+ console.log("");
37
+ console.log(data.docs);
38
+ return EXIT_OK;
39
+ }
26
40
  if (sub === "enable" || sub === "disable" || sub === "config") {
27
41
  const key = rest[1];
28
42
  if (!key) {
@@ -50,7 +64,7 @@ export async function runExtensions(args) {
50
64
  console.log(JSON.stringify(data, null, 2));
51
65
  return EXIT_OK;
52
66
  }
53
- console.error("Usage: frontpage extensions [list|enable|disable|config] …");
67
+ console.error("Usage: frontpage extensions [list|docs|enable|disable|config] …");
54
68
  return EXIT_USAGE;
55
69
  }
56
70
  catch (err) {
@@ -0,0 +1 @@
1
+ export declare function runFonts(args: string[]): Promise<number>;
@@ -0,0 +1,71 @@
1
+ import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get } from "../http/client.js";
2
+ import { emitErr, emitOk, isJson } from "../runtime.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
+ function hasFlag(rest, name) {
12
+ const i = rest.indexOf(name);
13
+ if (i === -1)
14
+ return false;
15
+ rest.splice(i, 1);
16
+ return true;
17
+ }
18
+ export async function runFonts(args) {
19
+ try {
20
+ const work = [...args];
21
+ let sub = work[0];
22
+ if (sub === "list" || sub === "search" || sub === "ls") {
23
+ work.shift();
24
+ }
25
+ else {
26
+ sub = "list";
27
+ }
28
+ const slot = takeFlag(work, "--slot");
29
+ const query = takeFlag(work, "--query") ??
30
+ (sub === "search" ? work.shift() : undefined) ??
31
+ (work[0] && !work[0].startsWith("-") ? work.shift() : undefined);
32
+ const variableOnly = hasFlag(work, "--variable-only");
33
+ const limit = takeFlag(work, "--limit");
34
+ const offset = takeFlag(work, "--offset");
35
+ if (sub === "search" && !query) {
36
+ console.error("Usage: frontpage fonts search <query> [--slot …] [--variable-only] [--limit N]");
37
+ return EXIT_USAGE;
38
+ }
39
+ const q = new URLSearchParams();
40
+ if (slot)
41
+ q.set("slot", slot);
42
+ if (query)
43
+ q.set("query", query);
44
+ if (variableOnly)
45
+ q.set("variableOnly", "true");
46
+ if (limit)
47
+ q.set("limit", limit);
48
+ if (offset)
49
+ q.set("offset", offset);
50
+ const path = `/api/v1/fonts${q.toString() ? `?${q.toString()}` : ""}`;
51
+ const data = await v1Get(path);
52
+ if (isJson()) {
53
+ emitOk("fonts", data);
54
+ return EXIT_OK;
55
+ }
56
+ for (const f of data.fonts ?? []) {
57
+ const varMark = f.variable ? "var" : " ";
58
+ console.log(`${f.slot.padEnd(8)} ${varMark} ${f.family.padEnd(28)} ${f.value}`);
59
+ }
60
+ console.log(`(${data.returned}/${data.total} fonts${data.hasMore ? "; more available" : ""})`);
61
+ if (data.hint)
62
+ console.error(data.hint);
63
+ return EXIT_OK;
64
+ }
65
+ catch (err) {
66
+ if (isJson())
67
+ return emitErr("fonts", err);
68
+ console.error(err instanceof Error ? err.message : String(err));
69
+ return exitCodeForError(err);
70
+ }
71
+ }
@@ -19,6 +19,23 @@ export async function runModules(args) {
19
19
  }
20
20
  return EXIT_OK;
21
21
  }
22
+ if (sub === "docs" || sub === "doc") {
23
+ const key = rest[1];
24
+ if (!key) {
25
+ console.error("Usage: frontpage modules docs <key> [--instance <id>] [--site <slug>]");
26
+ return EXIT_USAGE;
27
+ }
28
+ const work = rest.slice(2);
29
+ const instanceId = takeFlag(work, "--instance");
30
+ const q = instanceId ? `?instanceId=${encodeURIComponent(instanceId)}` : "";
31
+ const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/modules/${encodeURIComponent(key)}${q}`);
32
+ console.log(`# ${data.key} (${data.status})`);
33
+ if (data.note)
34
+ console.log(data.note);
35
+ console.log("");
36
+ console.log(data.docs);
37
+ return EXIT_OK;
38
+ }
22
39
  if (sub === "enable" || sub === "disable" || sub === "archive" || sub === "config") {
23
40
  const key = rest[1];
24
41
  if (!key) {
@@ -46,7 +63,7 @@ export async function runModules(args) {
46
63
  console.log(JSON.stringify(data, null, 2));
47
64
  return EXIT_OK;
48
65
  }
49
- console.error("Usage: frontpage modules [list|enable|disable|archive|config] …");
66
+ console.error("Usage: frontpage modules [list|docs|enable|disable|archive|config] …");
50
67
  return EXIT_USAGE;
51
68
  }
52
69
  catch (err) {
@@ -1,29 +1,79 @@
1
1
  import { resolveSlug } from "../site.js";
2
2
  import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get } from "../http/client.js";
3
+ import { emitErr, emitOk, isJson } from "../runtime.js";
4
+ function takeFlag(rest, name) {
5
+ const i = rest.indexOf(name);
6
+ if (i === -1)
7
+ return undefined;
8
+ const v = rest[i + 1];
9
+ rest.splice(i, 2);
10
+ return v;
11
+ }
12
+ function hasFlag(rest, name) {
13
+ const i = rest.indexOf(name);
14
+ if (i === -1)
15
+ return false;
16
+ rest.splice(i, 1);
17
+ return true;
18
+ }
3
19
  export async function runSearch(args) {
4
20
  try {
5
21
  const { slug, rest } = await resolveSlug(args);
6
- const pattern = rest[0];
22
+ const work = [...rest];
23
+ const regex = hasFlag(work, "--regex");
24
+ const caseSensitive = hasFlag(work, "--case-sensitive");
25
+ const wholeWord = hasFlag(work, "--whole-word");
26
+ const filesOnly = hasFlag(work, "--files-only");
27
+ const contextLines = takeFlag(work, "--context-lines");
28
+ const maxResults = takeFlag(work, "--max-results");
29
+ const pattern = work[0];
7
30
  if (!pattern) {
8
- console.error("Usage: frontpage search <pattern> [--site <slug>]");
31
+ console.error("Usage: frontpage search <pattern> [path] [--regex] [--case-sensitive] [--whole-word] [--files-only] [--context-lines N] [--max-results N] [--site <slug>]");
9
32
  return EXIT_USAGE;
10
33
  }
34
+ const pathFilter = work[1] && !work[1].startsWith("-") ? work[1] : undefined;
11
35
  const q = new URLSearchParams({ pattern });
12
- const pathFilter = rest[1];
13
36
  if (pathFilter)
14
37
  q.set("path", pathFilter);
38
+ if (regex)
39
+ q.set("regex", "true");
40
+ if (caseSensitive)
41
+ q.set("caseSensitive", "true");
42
+ if (wholeWord)
43
+ q.set("wholeWord", "true");
44
+ if (filesOnly)
45
+ q.set("filesOnly", "true");
46
+ if (contextLines)
47
+ q.set("contextLines", contextLines);
48
+ if (maxResults)
49
+ q.set("maxResults", maxResults);
15
50
  const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/search?${q.toString()}`);
16
- const matches = data.matches ?? [];
17
- for (const m of matches) {
18
- console.log(`${m.file}:${m.lineNumber}: ${m.line}`);
51
+ if (isJson()) {
52
+ emitOk("search", data);
53
+ return EXIT_OK;
54
+ }
55
+ if (filesOnly || data.filesOnly) {
56
+ const files = data.files ?? [];
57
+ for (const f of files)
58
+ console.log(f);
59
+ if (files.length === 0)
60
+ console.error("(no matches)");
61
+ }
62
+ else {
63
+ const matches = data.matches ?? [];
64
+ for (const m of matches) {
65
+ console.log(`${m.file}:${m.lineNumber}: ${m.line}`);
66
+ }
67
+ if (matches.length === 0)
68
+ console.error("(no matches)");
19
69
  }
20
- if (matches.length === 0)
21
- console.error("(no matches)");
22
70
  if (data.truncated || data.scanTruncated)
23
71
  console.error("# results truncated");
24
72
  return EXIT_OK;
25
73
  }
26
74
  catch (err) {
75
+ if (isJson())
76
+ return emitErr("search", err);
27
77
  console.error(err instanceof Error ? err.message : String(err));
28
78
  return exitCodeForError(err);
29
79
  }
@@ -0,0 +1 @@
1
+ export declare function runTokens(args: string[]): Promise<number>;
@@ -0,0 +1,64 @@
1
+ import { resolveSlug } from "../site.js";
2
+ import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Post } from "../http/client.js";
3
+ import { readState, writeState } from "../state/frontpage-state.js";
4
+ import { emitErr, emitOk, emitUsage, isJson } from "../runtime.js";
5
+ function takeFlag(rest, name) {
6
+ const i = rest.indexOf(name);
7
+ if (i === -1)
8
+ return undefined;
9
+ const v = rest[i + 1];
10
+ rest.splice(i, 2);
11
+ return v;
12
+ }
13
+ export async function runTokens(args) {
14
+ try {
15
+ const { slug, rest } = await resolveSlug(args);
16
+ const sub = rest[0];
17
+ if (sub !== "set") {
18
+ console.error("Usage: frontpage tokens set --group <colors|fonts|spacing|radii|article> --key <name> --value <…> [--site <slug>]");
19
+ return EXIT_USAGE;
20
+ }
21
+ const work = rest.slice(1);
22
+ const group = takeFlag(work, "--group");
23
+ const key = takeFlag(work, "--key");
24
+ const value = takeFlag(work, "--value");
25
+ if (!group || !key || value === undefined) {
26
+ return emitUsage("tokens", "Usage: frontpage tokens set --group <…> --key <…> --value <…> [--site <slug>]", EXIT_USAGE);
27
+ }
28
+ const cwd = process.cwd();
29
+ const state = await readState(cwd);
30
+ if (!state.baseCommitSha) {
31
+ return emitUsage("tokens", "No baseCommitSha in .frontpage/state.json. Run: frontpage pull", EXIT_USAGE);
32
+ }
33
+ const data = await v1Post(`/api/v1/sites/${encodeURIComponent(slug)}/tokens`, {
34
+ group,
35
+ key,
36
+ value,
37
+ baseCommitSha: state.baseCommitSha,
38
+ expectedHeadSha: state.baseCommitSha,
39
+ });
40
+ if (data.commitSha) {
41
+ await writeState({ ...state, slug, baseCommitSha: data.commitSha }, cwd);
42
+ }
43
+ if (isJson()) {
44
+ emitOk("tokens", { slug, ...data });
45
+ return EXIT_OK;
46
+ }
47
+ if (!data.changed) {
48
+ console.log(`No change (${group}.${key} already set).`);
49
+ }
50
+ else {
51
+ console.log(`Set ${group}.${key} → ${data.commitSha}`);
52
+ console.log("Updated .frontpage/state.json baseCommitSha");
53
+ }
54
+ if (data.warning)
55
+ console.error(`Warning: ${data.warning}`);
56
+ return EXIT_OK;
57
+ }
58
+ catch (err) {
59
+ if (isJson())
60
+ return emitErr("tokens", err);
61
+ console.error(err instanceof Error ? err.message : String(err));
62
+ return exitCodeForError(err);
63
+ }
64
+ }
@@ -14,15 +14,17 @@ _frontpage_completions() {
14
14
  fi
15
15
  local cmd="\${COMP_WORDS[1]}"
16
16
  case "\${cmd}" in
17
- help) COMPREPLY=( $(compgen -W "\${cmds} quickstart auth sync agent content history isolation exit-codes" -- "\${cur}") ) ;;
17
+ help) COMPREPLY=( $(compgen -W "\${cmds} quickstart auth sync site-building agent content history isolation exit-codes" -- "\${cur}") ) ;;
18
18
  completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "\${cur}") ) ;;
19
19
  runs) COMPREPLY=( $(compgen -W "list current wait cancel retry get" -- "\${cur}") ) ;;
20
20
  deploy) COMPREPLY=( $(compgen -W "wait" -- "\${cur}") ) ;;
21
21
  media) COMPREPLY=( $(compgen -W "list upload" -- "\${cur}") ) ;;
22
+ fonts) COMPREPLY=( $(compgen -W "list search" -- "\${cur}") ) ;;
23
+ tokens) COMPREPLY=( $(compgen -W "set" -- "\${cur}") ) ;;
22
24
  seo|settings) COMPREPLY=( $(compgen -W "get set" -- "\${cur}") ) ;;
23
25
  articles) COMPREPLY=( $(compgen -W "list content create-section create-article set-draft" -- "\${cur}") ) ;;
24
- modules) COMPREPLY=( $(compgen -W "list enable disable archive config" -- "\${cur}") ) ;;
25
- extensions) COMPREPLY=( $(compgen -W "list enable disable config" -- "\${cur}") ) ;;
26
+ modules) COMPREPLY=( $(compgen -W "list docs enable disable archive config" -- "\${cur}") ) ;;
27
+ extensions) COMPREPLY=( $(compgen -W "list docs enable disable config" -- "\${cur}") ) ;;
26
28
  *) COMPREPLY=( $(compgen -W "--site --help --json --quiet --yes" -- "\${cur}") ) ;;
27
29
  esac
28
30
  }
@@ -45,8 +47,10 @@ _frontpage() {
45
47
  runs) _values 'subcommand' current wait cancel retry get ;;
46
48
  deploy) _values 'subcommand' wait ;;
47
49
  media) _values 'subcommand' list upload ;;
50
+ fonts) _values 'subcommand' list search ;;
51
+ tokens) _values 'subcommand' set ;;
48
52
  seo|settings) _values 'subcommand' get set ;;
49
- modules|extensions) _values 'subcommand' list enable disable config ;;
53
+ modules|extensions) _values 'subcommand' list docs enable disable config ;;
50
54
  esac
51
55
  ;;
52
56
  esac
@@ -63,8 +67,10 @@ export function generateFishCompletion() {
63
67
  `complete -c frontpage -n "__fish_seen_subcommand_from runs" -a "current wait cancel retry get"`,
64
68
  `complete -c frontpage -n "__fish_seen_subcommand_from deploy" -a "wait"`,
65
69
  `complete -c frontpage -n "__fish_seen_subcommand_from media" -a "list upload"`,
70
+ `complete -c frontpage -n "__fish_seen_subcommand_from fonts" -a "list search"`,
71
+ `complete -c frontpage -n "__fish_seen_subcommand_from tokens" -a "set"`,
66
72
  `complete -c frontpage -n "__fish_seen_subcommand_from seo settings" -a "get set"`,
67
- `complete -c frontpage -n "__fish_seen_subcommand_from modules extensions" -a "list enable disable config"`,
73
+ `complete -c frontpage -n "__fish_seen_subcommand_from modules extensions" -a "list docs enable disable config"`,
68
74
  `complete -c frontpage -l site -d "Site subdomain slug"`,
69
75
  ];
70
76
  return `${lines.join("\n")}\n`;
@@ -14,6 +14,6 @@ export type CommandDef = {
14
14
  seeAlso?: string[];
15
15
  };
16
16
  export declare const COMMANDS: readonly CommandDef[];
17
- export declare const TOPIC_NAMES: readonly ["quickstart", "auth", "sync", "agent", "content", "history", "isolation", "exit-codes"];
17
+ export declare const TOPIC_NAMES: readonly ["quickstart", "auth", "sync", "site-building", "agent", "content", "history", "isolation", "exit-codes"];
18
18
  export declare const TOPICS: Record<string, string>;
19
19
  export declare function findCommand(name: string): CommandDef | undefined;
package/dist/registry.js CHANGED
@@ -7,8 +7,24 @@ export const COMMANDS = [
7
7
  name: "help",
8
8
  summary: "Show available commands",
9
9
  usage: "frontpage help [command|topic] [--json]",
10
- topics: ["auth", "sync", "agent", "content", "history", "isolation", "exit-codes", "quickstart"],
11
- examples: ["frontpage help", "frontpage help sync", "frontpage help push", "frontpage help --json"],
10
+ topics: [
11
+ "auth",
12
+ "sync",
13
+ "site-building",
14
+ "agent",
15
+ "content",
16
+ "history",
17
+ "isolation",
18
+ "exit-codes",
19
+ "quickstart",
20
+ ],
21
+ examples: [
22
+ "frontpage help",
23
+ "frontpage help sync",
24
+ "frontpage help site-building",
25
+ "frontpage help push",
26
+ "frontpage help --json",
27
+ ],
12
28
  },
13
29
  {
14
30
  name: "login",
@@ -110,8 +126,47 @@ export const COMMANDS = [
110
126
  {
111
127
  name: "search",
112
128
  summary: "Search remote text files",
113
- usage: "frontpage search <pattern> [path] [--site <slug>]",
129
+ usage: "frontpage search <pattern> [path] [--regex] [--case-sensitive] [--whole-word] [--files-only] [--context-lines N] [--max-results N] [--site <slug>] [--json]",
114
130
  topics: ["sync"],
131
+ flags: [
132
+ "--regex",
133
+ "--case-sensitive",
134
+ "--whole-word",
135
+ "--files-only",
136
+ "--context-lines <n>",
137
+ "--max-results <n>",
138
+ ],
139
+ },
140
+ {
141
+ name: "fonts",
142
+ summary: "Browse or search the self-hosted font catalog",
143
+ usage: "frontpage fonts [list|search] [--slot sans|serif|mono|display] [--query …] [--variable-only] [--limit N] [--offset N] [--json]",
144
+ topics: ["site-building", "content"],
145
+ examples: [
146
+ "frontpage fonts --slot serif",
147
+ 'frontpage fonts search "elegant script"',
148
+ "frontpage fonts --query Cinzel --json",
149
+ ],
150
+ flags: [
151
+ "Use the exact `value` string in tokens.fonts.* (via frontpage tokens set or push)",
152
+ "No --site required — catalog is account/platform scoped",
153
+ ],
154
+ },
155
+ {
156
+ name: "tokens",
157
+ summary: "Set a single design token surgically (CAS + active-run)",
158
+ usage: "frontpage tokens set --group <colors|fonts|spacing|radii|article> --key <name> --value <…> [--site <slug>] [--json]",
159
+ topics: ["site-building", "content"],
160
+ examples: [
161
+ 'frontpage tokens set --group fonts --key sans --value "\\"Inter Variable\\", system-ui, sans-serif"',
162
+ ],
163
+ dangers: [
164
+ "Requires prior pull (baseCommitSha)",
165
+ "Refused while an agent run is active (exit 3)",
166
+ "CAS fail if remote tip moved (exit 3) — pull and retry",
167
+ "Off-catalog font values warn but do not block",
168
+ ],
169
+ seeAlso: ["fonts", "pull", "push"],
115
170
  },
116
171
  {
117
172
  name: "status",
@@ -203,16 +258,18 @@ export const COMMANDS = [
203
258
  },
204
259
  {
205
260
  name: "modules",
206
- summary: "List or mutate modules (secret masks only)",
207
- usage: "frontpage modules [list|enable|disable|archive|config] <key> [--instance <id>] [--config '{…}'] [--site <slug>]",
261
+ summary: "List, read docs, or mutate modules (secret masks only)",
262
+ usage: "frontpage modules [list|docs|enable|disable|archive|config] <key> [--instance <id>] [--config '{…}'] [--site <slug>]",
208
263
  topics: ["agent", "content"],
264
+ examples: ["frontpage modules docs forms", "frontpage modules list"],
209
265
  dangers: ["Secret values are never returned — masks only"],
210
266
  },
211
267
  {
212
268
  name: "extensions",
213
- summary: "List or mutate extensions (no secret values)",
214
- usage: "frontpage extensions [list|enable|disable|config] <key> [--config '{…}'] [--site <slug>]",
269
+ summary: "List, read docs, or mutate extensions (no secret values)",
270
+ usage: "frontpage extensions [list|docs|enable|disable|config] <key> [--config '{…}'] [--site <slug>]",
215
271
  topics: ["agent", "content"],
272
+ examples: ["frontpage extensions docs charts"],
216
273
  },
217
274
  {
218
275
  name: "media",
@@ -291,6 +348,7 @@ export const TOPIC_NAMES = [
291
348
  "quickstart",
292
349
  "auth",
293
350
  "sync",
351
+ "site-building",
294
352
  "agent",
295
353
  "content",
296
354
  "history",
@@ -315,8 +373,8 @@ export const TOPICS = {
315
373
 
316
374
  Staging is the subdomain. There is no separate staging-deploy step for agents.
317
375
  Never publish/unpublish unless the user explicitly asks.
318
- Agents: read AGENTS.md in the npm package; use --json for structured I/O.
319
- More: frontpage help auth | sync | agent | content | history | isolation | exit-codes`,
376
+ Agents: read AGENTS.md + frontpage help site-building; use --json for structured I/O.
377
+ More: frontpage help auth | sync | site-building | agent | content | history | isolation | exit-codes`,
320
378
  auth: `Authentication
321
379
  1. Create a key in the Frontpage editor: profile menu → Developers
322
380
  2. frontpage login
@@ -335,7 +393,7 @@ export const TOPICS = {
335
393
  frontpage push [--message …] # uploads allowlisted files; CAS + active-run guard
336
394
  frontpage ls [prefix]
337
395
  frontpage cat <path>
338
- frontpage search <pattern>
396
+ frontpage search <pattern> [path] [--regex] [--case-sensitive] [--whole-word] [--files-only]
339
397
  frontpage open # opens https://<slug>.frontpage.host/ (--prod is a no-op stub)
340
398
 
341
399
  Local state: .frontpage/state.json { slug, baseCommitSha }
@@ -353,6 +411,40 @@ export const TOPICS = {
353
411
  - No separate "deploy to staging" step for agents — review the subdomain after the build.
354
412
  - frontpage deploy wait only waits for an in-flight build; it is NOT go-live.
355
413
  - frontpage publish promotes staging → production (custom domain). Never publish unless asked.`,
414
+ "site-building": `Site building (Astro contract)
415
+ Stack: Astro 6 + Tailwind v4. Pages in src/pages/. Every page wraps <BaseLayout title description>.
416
+ Control files: src/config/tokens.ts (design tokens), src/config/site.ts (title, nav, pages map, seo).
417
+
418
+ Pages map:
419
+ - Add/rename/delete a page file ⇒ update pages (+ nav if needed) in the SAME change set.
420
+ - Real title/description live on <BaseLayout>, not as authority in the pages map.
421
+ - One discrete .astro file per URL for similar sets (services, guides…). Do NOT default to
422
+ getStaticPaths() over src/data/*.ts. Blog/news → frontpage articles.
423
+
424
+ Hard fences:
425
+ - No API routes, serverless backends, or databases.
426
+ - No fake lead forms / checkout / card fields — use data-fp-* modules + frontpage modules, or refuse.
427
+ - Platform-owned paths are not syncable (BaseLayout overlay, module/extension shells, package.json…).
428
+ - Customer layouts (DocsLayout, GuideLayout, …) are fine when site-owned.
429
+
430
+ Tokens & fonts:
431
+ - Keep required colors/fonts/spacing/radii keys; style via CSS variables / token Tailwind.
432
+ - Setting fonts.sans is not enough — apply font-sans / font-serif / font-display on elements.
433
+ - Browse fonts: frontpage fonts [--slot serif] [--query elegant] — use the exact value string.
434
+ - Surgical token edit: frontpage tokens set --group fonts --key sans --value "…" (CAS like push).
435
+
436
+ Chrome / SEO / 404:
437
+ - Prefer src/components/Header.astro + Footer.astro (or chrome: { managed: true } in site.ts).
438
+ - seo block in site.ts + frontpage seo get|set; customize 404.astro but keep frontpage:404 meta;
439
+ never put /404 in nav or pages. robots/sitemap are platform-managed.
440
+
441
+ Modules & articles (short):
442
+ - Forms: <form data-fp-form="slug"> with named inputs; no action/onsubmit/honeypot.
443
+ - Articles: frontpage articles …; migrate folders via frontpage prompt (don't invent mass delete+write).
444
+ - Media in markup: /__media/uploads/<slug>/… from frontpage media (not raw blob URLs).
445
+
446
+ Full guide: https://frontpage.host/developers/site-building
447
+ See also: frontpage help sync | content | AGENTS.md`,
356
448
  agent: `Agent + publish
357
449
  frontpage prompt "make the hero shorter"
358
450
  frontpage runs # latest + active
@@ -376,13 +468,19 @@ export const TOPICS = {
376
468
  frontpage inbox [leads|subscribers|orders|…] [--mark-read] [--csv]
377
469
  frontpage seo get|set --path /
378
470
  frontpage articles list|content|create-section|create-article|set-draft|…
379
- frontpage modules enable|disable|archive|config <key>
380
- frontpage extensions enable|disable|config <key>
471
+ frontpage fonts [list|search] [--slot …] [--query …]
472
+ frontpage tokens set --group … --key … --value …
473
+ frontpage modules list|docs|enable|disable|archive|config <key>
474
+ frontpage extensions list|docs|enable|disable|config <key>
381
475
  frontpage settings get|set
382
476
  frontpage edit --path / --old "…" --new "…"
383
477
 
478
+ Real forms (file sync): <form data-fp-form="slug"> + named inputs — no action/onsubmit/honeypot.
479
+ Do not fake payments/checkout. LOCKED extensions → do not fake; say plan required.
480
+ Migrate pages → articles: prefer frontpage prompt "migrate …" over mass delete+write.
384
481
  edit uses click-edit (exact single occurrence), not push / gated-write.
385
482
  Module OAuth and secret values are not exposed via the CLI.
483
+ Site contract: frontpage help site-building · https://frontpage.host/developers/site-building
386
484
  Domains, billing, members, and site delete are out of v1.`,
387
485
  history: `History
388
486
  frontpage history [--limit 20] [--page 1]
package/llms.txt CHANGED
@@ -3,7 +3,11 @@
3
3
  - Package: frontpage-host (npm). Binary: frontpage. Do NOT install npm package "frontpage".
4
4
  - Docs in this package: README.md, AGENTS.md (agent operating manual).
5
5
  - Introspect: frontpage help | frontpage help --json | frontpage help <topic|command>
6
- - Topics: quickstart, auth, sync, agent, content, history, isolation, exit-codes
6
+ - Topics: quickstart, auth, sync, site-building, agent, content, history, isolation, exit-codes
7
+ - Site building: frontpage help site-building — Astro pages map, BaseLayout, tokens, no backends/fake forms
8
+ - Fonts: frontpage fonts [--slot] [--query] — use exact value; tokens: frontpage tokens set --group --key --value (CAS)
9
+ - Module/extension docs: frontpage modules docs <key> | frontpage extensions docs <key>
10
+ - Search flags: --regex --case-sensitive --whole-word --files-only --context-lines --max-results
7
11
  - Auth: mint fp_live_ key in Frontpage UI (Developers); frontpage login KEY or FRONTPAGE_API_KEY
8
12
  - Site id: subdomain only (frontpage use <subdomain>). Not custom domains.
9
13
  - Staging: https://<subdomain>.frontpage.host — push/prompt/edit update staging; no separate staging deploy step for agents
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontpage-host",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Frontpage CLI — manage your site from the terminal",
5
5
  "type": "module",
6
6
  "bin": {