frontpage-host 0.1.16 → 0.1.18

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.
@@ -63,6 +63,13 @@ export const MODULE_COMMANDS = [
63
63
  extraActions: [
64
64
  "get [--instance <slug>]",
65
65
  "set --instance <slug> [--notify-email] [--confirm yes|no]",
66
+ "destinations [--instance <slug>]",
67
+ "connect-destination <privyr|hubspot|pipedrive> --token \"…\" [--lead-source \"…\"]",
68
+ "attach-destination --instance <slug> --provider <id>",
69
+ "detach-destination --instance <slug> --provider <id>",
70
+ "remove-destination <id> --yes",
71
+ "disconnect-destination <id> --yes",
72
+ "request-destination-setup <id> --reason \"…\"",
66
73
  ],
67
74
  notYet: [],
68
75
  examples: [
@@ -70,12 +77,16 @@ export const MODULE_COMMANDS = [
70
77
  "frontpage modules forms get",
71
78
  "frontpage modules forms get --instance lead-gen",
72
79
  "frontpage modules forms set --instance lead-gen --notify-email hi@example.com",
80
+ "frontpage modules forms destinations",
81
+ "frontpage modules forms connect-destination privyr --token \"…\"",
82
+ "frontpage modules forms attach-destination --instance lead-gen --provider privyr",
73
83
  "frontpage modules forms docs",
74
84
  `frontpage modules config forms --instance lead-gen --config '{"notifyEmail":"hi@example.com"}'`,
75
85
  ],
76
86
  notes: [
77
87
  "Without --instance, get lists each form (id, status, notifyEmail). Do not target `default`.",
78
88
  "Confirmation emails require a paid plan (same as Modules).",
89
+ "Destinations copy leads to HubSpot, Pipedrive, or Privyr. Connect once, then attach each form. Token write is admin (Privyr: paste the Incoming Webhook URL, not a token). request-destination-setup lights the editor amber bar (editor key). remove-destination requires --yes. Locations: https://frontpage.host/developers/modules#forms-destinations",
79
90
  ],
80
91
  },
81
92
  {
@@ -275,7 +286,19 @@ export function isCliModuleAction(key, action) {
275
286
  if (key === "google_reviews") {
276
287
  return GOOGLE_REVIEWS_CLI_ACTIONS.includes(action);
277
288
  }
278
- if (key === "callbar" || key === "forms" || key === "booking" || key === "signups") {
289
+ if (key === "forms") {
290
+ return (TYPED_CONFIG_CLI_ACTIONS.includes(action) ||
291
+ [
292
+ "destinations",
293
+ "connect-destination",
294
+ "remove-destination",
295
+ "disconnect-destination",
296
+ "attach-destination",
297
+ "detach-destination",
298
+ "request-destination-setup",
299
+ ].includes(action));
300
+ }
301
+ if (key === "callbar" || key === "booking" || key === "signups") {
279
302
  return TYPED_CONFIG_CLI_ACTIONS.includes(action);
280
303
  }
281
304
  if (key === "payments" || key === "shop") {
@@ -0,0 +1,3 @@
1
+ export declare const FORMS_DESTINATION_CLI_ACTIONS: readonly ["destinations", "connect-destination", "remove-destination", "disconnect-destination", "attach-destination", "detach-destination", "request-destination-setup"];
2
+ export declare function isFormsDestinationCliAction(action: string): boolean;
3
+ export declare function runFormsDestinations(slug: string, action: string, flags: string[]): Promise<number>;
@@ -0,0 +1,147 @@
1
+ /**
2
+ * §FD.CLI — form destination verbs.
3
+ */
4
+ import { assertNoUnknownFlags, hasFlag, takeFlag } from "../argv.js";
5
+ import { EXIT_OK, EXIT_USAGE, v1Get, v1Post } from "../http/client.js";
6
+ import { emitOk, emitUsage, isJson } from "../runtime.js";
7
+ import { formatModuleUsage } from "./commands.js";
8
+ const USAGE = formatModuleUsage("forms");
9
+ const REMOVE_CONFIRM = "This deletes the destination from Frontpage and stops every form from sending a copy. Your leads stay in Frontpage and still email you.";
10
+ function pathFor(slug) {
11
+ return `/api/v1/sites/${encodeURIComponent(slug)}/modules/forms`;
12
+ }
13
+ export const FORMS_DESTINATION_CLI_ACTIONS = [
14
+ "destinations",
15
+ "connect-destination",
16
+ "remove-destination",
17
+ "disconnect-destination",
18
+ "attach-destination",
19
+ "detach-destination",
20
+ "request-destination-setup",
21
+ ];
22
+ export function isFormsDestinationCliAction(action) {
23
+ return FORMS_DESTINATION_CLI_ACTIONS.includes(action);
24
+ }
25
+ function printDestinations(data, instanceFilter) {
26
+ const providers = data.destinations?.providers ?? [];
27
+ const connected = providers.filter((p) => p.connected);
28
+ if (!connected.length) {
29
+ console.log("(no destinations)");
30
+ return;
31
+ }
32
+ const by = data.destinations?.byInstance ?? {};
33
+ for (const p of connected) {
34
+ console.log(`${p.id.padEnd(10)} connected ${p.last4 ? `••••${p.last4}` : ""}`.trimEnd());
35
+ const slugs = instanceFilter ? [instanceFilter] : Object.keys(by);
36
+ for (const slug of slugs) {
37
+ const row = (by[slug] ?? []).find((r) => r.provider === p.id);
38
+ const sending = row?.enabled ? "sending" : "—";
39
+ const err = row?.lastError ? ` error ${row.lastError}` : "";
40
+ if (instanceFilter && !row) {
41
+ console.log(` ${slug.padEnd(16)} —`);
42
+ continue;
43
+ }
44
+ if (!instanceFilter && !row)
45
+ continue;
46
+ console.log(` ${slug.padEnd(16)} ${sending}${err}`);
47
+ }
48
+ }
49
+ }
50
+ export async function runFormsDestinations(slug, action, flags) {
51
+ const work = [...flags];
52
+ if (action === "destinations") {
53
+ const instanceId = takeFlag(work, "--instance");
54
+ assertNoUnknownFlags(work, "modules");
55
+ const data = await v1Get(pathFor(slug));
56
+ if (isJson()) {
57
+ emitOk("modules", data.destinations ?? { providers: [], byInstance: {} });
58
+ return EXIT_OK;
59
+ }
60
+ printDestinations(data, instanceId);
61
+ return EXIT_OK;
62
+ }
63
+ if (action === "connect-destination") {
64
+ const provider = work.shift();
65
+ const token = takeFlag(work, "--token");
66
+ const leadSource = takeFlag(work, "--lead-source");
67
+ assertNoUnknownFlags(work, "modules");
68
+ if (!provider || !token) {
69
+ return emitUsage("modules", `Usage: frontpage modules forms connect-destination <privyr|hubspot|pipedrive> --token "…"\n\n${USAGE}`);
70
+ }
71
+ const data = await v1Post(pathFor(slug), {
72
+ action: "destination_connect",
73
+ provider,
74
+ token,
75
+ ...(leadSource ? { leadSource } : {}),
76
+ });
77
+ if (isJson()) {
78
+ emitOk("modules", data);
79
+ return EXIT_OK;
80
+ }
81
+ const last4 = typeof data.last4 === "string" ? data.last4 : "";
82
+ console.log(`Connected ${provider}${last4 ? ` ••••${last4}` : ""}.`);
83
+ return EXIT_OK;
84
+ }
85
+ if (action === "remove-destination" || action === "disconnect-destination") {
86
+ const provider = work.shift();
87
+ const yes = hasFlag(work, "--yes");
88
+ assertNoUnknownFlags(work, "modules");
89
+ if (!provider) {
90
+ return emitUsage("modules", `Usage: frontpage modules forms ${action} <privyr|hubspot|pipedrive> --yes\n\n${USAGE}`);
91
+ }
92
+ if (!yes) {
93
+ return emitUsage("modules", REMOVE_CONFIRM, EXIT_USAGE);
94
+ }
95
+ const data = await v1Post(pathFor(slug), {
96
+ action: "destination_remove",
97
+ provider,
98
+ });
99
+ if (isJson()) {
100
+ emitOk("modules", data);
101
+ return EXIT_OK;
102
+ }
103
+ console.log(`Removed ${provider}. Leads still land in Frontpage.`);
104
+ return EXIT_OK;
105
+ }
106
+ if (action === "attach-destination" || action === "detach-destination") {
107
+ const instanceId = takeFlag(work, "--instance");
108
+ const provider = takeFlag(work, "--provider") ?? work.shift();
109
+ assertNoUnknownFlags(work, "modules");
110
+ if (!instanceId || !provider) {
111
+ return emitUsage("modules", `Usage: frontpage modules forms ${action} --instance <slug> --provider <privyr|hubspot|pipedrive>\n\n${USAGE}`);
112
+ }
113
+ const data = await v1Post(pathFor(slug), {
114
+ action: action === "attach-destination" ? "destination_attach" : "destination_detach",
115
+ provider,
116
+ instanceId,
117
+ });
118
+ if (isJson()) {
119
+ emitOk("modules", data);
120
+ return EXIT_OK;
121
+ }
122
+ console.log(action === "attach-destination"
123
+ ? `${instanceId} will also send a copy to ${provider}.`
124
+ : `Stopped sending ${instanceId} to ${provider}.`);
125
+ return EXIT_OK;
126
+ }
127
+ if (action === "request-destination-setup") {
128
+ const provider = work.shift();
129
+ const reason = takeFlag(work, "--reason") ?? "";
130
+ assertNoUnknownFlags(work, "modules");
131
+ if (!provider || !reason) {
132
+ return emitUsage("modules", `Usage: frontpage modules forms request-destination-setup <privyr|hubspot|pipedrive> --reason "…"\n\n${USAGE}`);
133
+ }
134
+ const data = await v1Post(pathFor(slug), {
135
+ action: "destination_request_setup",
136
+ provider,
137
+ reason,
138
+ });
139
+ if (isJson()) {
140
+ emitOk("modules", data);
141
+ return EXIT_OK;
142
+ }
143
+ console.log(`Setup requested for ${provider}. The owner sees an amber prompt in the Frontpage editor.`);
144
+ return EXIT_OK;
145
+ }
146
+ return emitUsage("modules", `${USAGE}\n\nUnknown action "${action}".`);
147
+ }
@@ -46,17 +46,29 @@ function printConfirmable(config, confirmations) {
46
46
  else
47
47
  printLine("confirm", yesNo(on));
48
48
  }
49
- function printFormsList(instances) {
49
+ function printFormsList(instances, destinations) {
50
50
  if (!instances.length) {
51
51
  console.log("(no forms)");
52
- return;
53
52
  }
54
- for (const i of instances) {
55
- const id = i.instance ?? "";
56
- const label = i.label && i.label !== id ? i.label : "";
57
- const status = String(i.status ?? "—");
58
- const email = typeof i.config?.notifyEmail === "string" ? i.config.notifyEmail : "(account email)";
59
- console.log(`${id.padEnd(20)} ${status.padEnd(12)} ${label ? `${label} ` : ""}${email}`);
53
+ else {
54
+ for (const i of instances) {
55
+ const id = i.instance ?? "";
56
+ const label = i.label && i.label !== id ? i.label : "";
57
+ const status = String(i.status ?? "—");
58
+ const email = typeof i.config?.notifyEmail === "string" ? i.config.notifyEmail : "(account email)";
59
+ const attached = (destinations?.byInstance?.[id] ?? [])
60
+ .filter((r) => r.enabled)
61
+ .map((r) => r.provider)
62
+ .join(",");
63
+ console.log(`${id.padEnd(20)} ${status.padEnd(12)} ${label ? `${label} ` : ""}${email.padEnd(22)} ${attached || "—"}`);
64
+ }
65
+ }
66
+ const connected = (destinations?.providers ?? []).filter((p) => p.connected);
67
+ if (connected.length) {
68
+ console.log("");
69
+ for (const p of connected) {
70
+ console.log(`${p.id} connected${p.last4 ? ` ••••${p.last4}` : ""}`);
71
+ }
60
72
  }
61
73
  }
62
74
  function parseYesNoFlag(raw, flag) {
@@ -81,7 +93,7 @@ export async function runTypedConfig(key, slug, action, flags) {
81
93
  return EXIT_OK;
82
94
  }
83
95
  if (key === "forms" && !instanceId) {
84
- printFormsList(data.instances ?? []);
96
+ printFormsList(data.instances ?? [], data.destinations);
85
97
  return EXIT_OK;
86
98
  }
87
99
  if (key === "callbar")
package/dist/registry.js CHANGED
@@ -104,7 +104,7 @@ export const COMMANDS = [
104
104
  flags: [
105
105
  "--site <slug>",
106
106
  "--message <text>",
107
- "--wait: after push, wait until no agent run is queued/running/deploying (same as deploy wait; not a Vercel oracle)",
107
+ "--wait: after push, wait until the new commit's site compile is live (agent-run idle first, then GET /deploy ready)",
108
108
  ],
109
109
  dangers: [
110
110
  "Requires prior pull (baseCommitSha)",
@@ -249,10 +249,16 @@ export const COMMANDS = [
249
249
  {
250
250
  name: "publish",
251
251
  summary: "Promote staging to production",
252
- usage: "frontpage publish [--site <slug>] [--json]",
252
+ usage: "frontpage publish [--site <slug>] [--wait] [--json]",
253
253
  topics: ["agent"],
254
+ examples: ["frontpage publish", "frontpage publish --wait"],
255
+ flags: [
256
+ "--wait: wait until the staging tip's site compile is live, then publish (refuses a previous build)",
257
+ "Prints tip SHA and serving SHA; warns if they differ",
258
+ ],
254
259
  dangers: [
255
- "Promotes the current staging tip to PRODUCTION (custom domain)",
260
+ "Promotes the last ready staging compile to PRODUCTION (custom domain)",
261
+ "409 if the tip is not compiled yet — wait, then publish",
256
262
  "Agents: never run unless the user explicitly asked to publish",
257
263
  ],
258
264
  seeAlso: ["unpublish", "deploy"],
@@ -270,13 +276,14 @@ export const COMMANDS = [
270
276
  },
271
277
  {
272
278
  name: "deploy",
273
- summary: "Wait for an in-flight build to finish (not publish)",
279
+ summary: "Wait for the current tip's site compile (not publish)",
274
280
  usage: "frontpage deploy wait [--site <slug>] [--json]",
275
281
  topics: ["agent"],
276
282
  flags: [
277
- "Optional build wait only — staging is https://<subdomain>.frontpage.host",
283
+ "Waits for editor-agent idle (if a run is in flight), then until GET /deploy ready",
284
+ "ready = the live compile SHA equals the git tip — not a historical agent run",
278
285
  "Does NOT promote to production; use publish only when the user asks",
279
- "Fail-closed: exit 2 when the waited run (or latest, if already idle) is failed/canceled",
286
+ "Fail-closed: exit 2 when the waited agent run is failed/canceled, or the compile fails or times out",
280
287
  ],
281
288
  },
282
289
  {
@@ -361,6 +368,24 @@ export const COMMANDS = [
361
368
  topics: ["agent", "content"],
362
369
  examples: ["frontpage extensions docs charts"],
363
370
  },
371
+ {
372
+ name: "images",
373
+ summary: "Generate 1–4 AI image variations of one prompt (counts against AI $ and storage)",
374
+ usage: 'frontpage images generate --prompt "…" [--count N] [--site <slug>] [--json]',
375
+ topics: ["content"],
376
+ examples: [
377
+ 'frontpage images generate --prompt "flat illustration of a red stand mixer on marble"',
378
+ 'frontpage images generate --prompt "watercolor cafe storefront at dusk" --count 4',
379
+ ],
380
+ flags: [
381
+ "One prompt; --count 1–4 is variations of that same picture (default 1)",
382
+ "Human success: sitePath first (markup-safe), then filename",
383
+ "Counts against the site AI $ budget and storage; 402 when the cycle is spent",
384
+ "--count outside 1–4 fails locally (exit 2) with no request",
385
+ "Unknown leftover flags fail closed (exit 2)",
386
+ ],
387
+ seeAlso: ["media", "prompt", "help content", "help site-building"],
388
+ },
364
389
  {
365
390
  name: "media",
366
391
  summary: "List or upload site media (images/PDF ≤25 MB, video ≤50 MB)",
@@ -388,6 +413,45 @@ export const COMMANDS = [
388
413
  topics: ["content"],
389
414
  dangers: ["--mark-read is side-effecting with no confirm"],
390
415
  },
416
+ {
417
+ name: "people",
418
+ summary: "List and manage site People (not inbox, not a module)",
419
+ usage: "frontpage people list|get|add|set|note|rm-note|follow-up|hide|unhide [--site <slug>] [--json]",
420
+ topics: ["content"],
421
+ examples: [
422
+ "frontpage people list",
423
+ "frontpage people add --email alex@example.com --name Alex",
424
+ "frontpage people set <id> --status working",
425
+ "frontpage people note <id> --body \"Called them\"",
426
+ "frontpage people follow-up <id> --at 2026-09-12T00:00:00Z",
427
+ "frontpage people hide <id>",
428
+ ],
429
+ flags: [
430
+ "list: --q --status --source --form-slug --follow-up due|upcoming|none --hidden --sort --last-seen-days --limit --cursor",
431
+ "add requires --email and/or --phone (never name alone)",
432
+ "Hide is not forget — unique email/phone stay; later ingest un-hides",
433
+ "Unknown leftover flags fail closed (exit 2)",
434
+ ],
435
+ seeAlso: ["inbox", "help content"],
436
+ },
437
+ {
438
+ name: "redirects",
439
+ summary: "List, set, or remove HTTP 301/308 path redirects",
440
+ usage: "frontpage redirects list | set --from /old --to /new [--status 301|308] | rm --from /old [--site <slug>] [--json]",
441
+ topics: ["content", "site-building"],
442
+ examples: [
443
+ "frontpage redirects list",
444
+ "frontpage redirects set --from /agents --to /agent",
445
+ "frontpage redirects set --from /old --to /new --status 308",
446
+ "frontpage redirects rm --from /old",
447
+ ],
448
+ flags: [
449
+ "Instant — no push or compile. Not a site file. frontpage revert does not undo a redirect.",
450
+ "Same-origin pathnames only. Status 301 (default) or 308. Cap 50 per site.",
451
+ "Unknown leftover flags fail closed (exit 2)",
452
+ ],
453
+ seeAlso: ["seo", "help site-building"],
454
+ },
391
455
  {
392
456
  name: "seo",
393
457
  summary: "Get or set page SEO",
@@ -477,13 +541,14 @@ export const TOPICS = {
477
541
  4. mkdir work && cd work && frontpage use <subdomain>
478
542
  5. frontpage pull
479
543
  6. Edit files under src/, public/, or content/
480
- 7. frontpage push --message "…"
481
- 8. Review staging: https://<subdomain>.frontpage.host when that subdomain is publicly shareable.
544
+ 7. frontpage push --message "…" --wait
545
+ 8. frontpage deploy wait # tip compile ready (optional; push --wait does this)
546
+ Review staging: https://<subdomain>.frontpage.host when that subdomain is publicly shareable.
482
547
  With a custom domain connected, the subdomain is often owner-private (login wall ≠ failed push);
483
548
  anonymous review is on the published custom domain after publish.
484
- 9. frontpage publish # ONLY if the user asked — promotes to production
549
+ 9. frontpage publish [--wait] # ONLY if the user asked — promotes the ready compile
485
550
 
486
- Staging is the subdomain. There is no separate staging-deploy step for agents.
551
+ Push updates the git tip. The site compile is a second clock. deploy wait is not go-live.
487
552
  Never publish/unpublish unless the user explicitly asks.
488
553
  Agents: read AGENTS.md + frontpage help site-building; use --json for structured I/O.
489
554
  More: frontpage help auth | sync | site-building | agent | content | history | isolation | exit-codes`,
@@ -502,7 +567,7 @@ export const TOPICS = {
502
567
  frontpage status # tipDrift when remote moved since last pull
503
568
  frontpage pull # downloads src/, public/, content/ into cwd
504
569
  frontpage diff # local vs remote via git blobSha (--json → dirty + tipDrift)
505
- frontpage push [--message …] [--wait] # uploads allowlisted files; CAS + active-run guard
570
+ frontpage push [--message …] [--wait] # uploads allowlisted files; deletes remote-only allowlisted paths; CAS + active-run guard
506
571
  frontpage ls [prefix]
507
572
  frontpage cat <path>
508
573
  frontpage search <pattern> [path|--path <path>] [--regex] [--case-sensitive] [--whole-word] [--files-only]
@@ -511,7 +576,7 @@ export const TOPICS = {
511
576
  Local state: .frontpage/state.json { slug, baseCommitSha }
512
577
  Pull BEFORE EDITING when tip may have moved (UI agent / another checkout) — check tipDrift.
513
578
  Pull before push so CAS has baseCommitSha. Do not push while the editor agent is building.
514
- --wait after push blocks until no agent run is queued/running/deploying (same as deploy wait).
579
+ --wait after push blocks until the new commit's site compile is live (agent idle, then GET /deploy ready).
515
580
  The UI/canvas agent does not use this CLI; only CLI checkouts need pull.
516
581
  Pushing module markup may auto-activate modules (same as the agent).
517
582
  tokens.ts may be auto-repaired at commit if it would break the build.
@@ -520,12 +585,15 @@ export const TOPICS = {
520
585
  Do not store secrets in the site tree (.env, *.pem, secrets/, credentials.json) — rejected by the API.
521
586
 
522
587
  Staging vs production:
523
- - Push/prompt/edit update STAGING: https://<subdomain>.frontpage.host
524
- - No separate "deploy to staging" step for agents — review the subdomain after the build.
525
- - frontpage deploy wait only waits for an in-flight build; it is NOT go-live.
526
- - frontpage publish promotes staging → production (custom domain). Never publish unless asked.`,
588
+ - Push/prompt/edit update the git tip. The site compile is a second clock.
589
+ - Review STAGING at https://<subdomain>.frontpage.host after GET /deploy is ready.
590
+ - frontpage deploy wait = agent-run idle (if any) + tip compile ready. It is NOT go-live.
591
+ - A file-only push has no agent run. deploy wait / push --wait must still wait for the compile.
592
+ - frontpage publish promotes the last ready staging compile. 409 if the tip is not built yet.
593
+ - frontpage publish --wait waits for that compile, then publishes. Never publish unless asked.`,
527
594
  "site-building": `Site building (Astro contract)
528
- Stack: Astro 6 + Tailwind v4. Pages in src/pages/. Every page wraps <BaseLayout title description>.
595
+ Stack: Astro 6 + Tailwind v4. Pages in src/pages/. Every page wraps <BaseLayout title description>,
596
+ or a site layout that itself wraps BaseLayout.
529
597
  Control files: src/config/tokens.ts (design tokens), src/config/site.ts (title, nav, pages map, seo).
530
598
 
531
599
  Pages map:
@@ -537,8 +605,15 @@ export const TOPICS = {
537
605
  Hard fences:
538
606
  - No API routes, serverless backends, or databases.
539
607
  - No fake lead forms / checkout / card fields — use data-fp-* modules + frontpage modules, or refuse.
608
+ - Quoted attributes: in frontmatter JS, a backslash-quote is an escape. In an Astro attribute it is
609
+ not. The quote after href= closes the string and the next token fails the compile. Put HTML in a
610
+ frontmatter const and pass it as an expression (auditBody={auditBody}), or use an expression
611
+ attribute. Never put an HTML tag or a backslash-quote inside a quoted JSX attribute.
540
612
  - Platform-owned paths are not syncable (BaseLayout overlay, module/extension shells, package.json…).
541
- - Customer layouts (DocsLayout, GuideLayout, …) are fine when site-owned.
613
+ - Customer layouts (DocsLayout, GuideLayout, …) are fine when site-owned. A wrapper MUST render
614
+ <BaseLayout> and forward SEO: description={description}, ogImage={ogImage}, and
615
+ title={seoTitle ?? derivedFallback} from a 1:1 Astro.props destructure. NEVER title={pageTitle}
616
+ with no seoTitle alias (locks Title/Share image in Search & Social for every page on that layout).
542
617
 
543
618
  Tokens & fonts:
544
619
  - Keep required colors/fonts/spacing/radii keys; style via CSS variables / token Tailwind.
@@ -552,11 +627,13 @@ export const TOPICS = {
552
627
  - Prefer src/components/Header.astro + Footer.astro (or chrome: { managed: true } in site.ts).
553
628
  - seo block in site.ts + frontpage seo get|set; customize 404.astro but keep frontpage:404 meta;
554
629
  never put /404 in nav or pages. robots/sitemap are platform-managed.
630
+ - Real HTTP 301/308s: frontpage redirects list|set|rm. Instant (no push). Do not ship Astro.redirect,
631
+ location.replace, or a meta refresh for a permanent move. frontpage revert does not undo redirects.
555
632
 
556
633
  Modules & articles (short):
557
634
  - Forms: <form data-fp-form="slug"> with named inputs; no action/onsubmit/honeypot.
558
635
  - Articles: frontpage articles …; migrate folders via frontpage prompt (don't invent mass delete+write).
559
- - Media: frontpage media upload (images/PDF ≤25 MB, video ≤50 MB). Use sitePath /__media/uploads/<slug>/… in markup (not raw blob URLs). Optional ?w= on rasters only.
636
+ - Media: frontpage media upload (images/PDF ≤25 MB, video ≤50 MB). frontpage images generate --prompt "…" [--count N] for 1–4 AI variations of one invented picture (AI $ + storage). Use sitePath /__media/uploads/<slug>/… in markup (not raw blob URLs). Optional ?w= on rasters only.
560
637
 
561
638
  Full guide: https://frontpage.host/developers/site-building
562
639
  See also: frontpage help sync | content | AGENTS.md`,
@@ -568,8 +645,8 @@ export const TOPICS = {
568
645
  frontpage runs wait [runId] # failed/canceled → exit 2; JSON ok:false + run.error
569
646
  frontpage runs cancel <runId>
570
647
  frontpage runs retry <runId>
571
- frontpage deploy wait # optional build wait; fail-closed on failed/canceled; NOT publish
572
- frontpage publish # ONLY if the user asked — production promote
648
+ frontpage deploy wait # agent idle + tip compile ready; fail-closed; NOT publish
649
+ frontpage publish [--wait] # ONLY if the user asked — promotes the ready compile
573
650
  frontpage unpublish --yes # admin only; never unless asked; --yes when non-TTY
574
651
  frontpage usage
575
652
  frontpage analytics [--days 7]
@@ -589,8 +666,11 @@ export const TOPICS = {
589
666
  Do not publish unless asked.`,
590
667
  content: `Content
591
668
  frontpage media list|upload <file> # images/PDF/video; text/CSV = inline not library; sitePath first when stored
669
+ frontpage images generate --prompt "…" [--count N] # 1–4 AI variations of one prompt; sitePath first; AI $ + storage
592
670
  frontpage inbox [leads|subscribers|orders|google_reviews|…] [--mark-read] [--csv]
671
+ frontpage people list|get|add|set|note|rm-note|follow-up|hide|unhide
593
672
  frontpage seo get [/path|--path /path] | seo set --path /
673
+ frontpage redirects list|set|rm --from /old --to /new
594
674
  frontpage articles list|content|create-section|create-article|set-draft|…
595
675
  frontpage fonts [list|search] [--slot …] [--query …]
596
676
  frontpage tokens set --group … --key … --value …
@@ -619,7 +699,8 @@ export const TOPICS = {
619
699
  revert creates a NEW commit whose tree matches the target (linear restore).
620
700
  Target SHA must be on this site's history (not another site in the monorepo).
621
701
  Refused while an agent run is active. Goes through commitSiteBranch (build gate).
622
- --yes required when stdin is not a TTY.`,
702
+ --yes required when stdin is not a TTY.
703
+ revert does not undo HTTP redirects — use frontpage redirects rm.`,
623
704
  isolation: `Isolation
624
705
  Every site-scoped call targets subdomain → branch site/<slug> only.
625
706
  No client-supplied branch, ref, repo, or clone URL is accepted.
package/llms.txt CHANGED
@@ -1,6 +1,7 @@
1
1
  # Frontpage CLI (frontpage-host)
2
2
 
3
3
  - Package: frontpage-host (npm). Binary: frontpage. Do NOT install npm package "frontpage".
4
+ - Astro quoted attributes: in frontmatter JS a backslash-quote is an escape; in an attribute it is not. Pass HTML as {auditBody}. Never HTML or a backslash-quote inside a quoted JSX attribute.
4
5
  - Docs in this package: README.md, AGENTS.md (agent operating manual).
5
6
  - Introspect: frontpage help | frontpage help --json | frontpage help <topic|command>
6
7
  - Topics: quickstart, auth, sync, site-building, agent, content, history, isolation, exit-codes
@@ -8,20 +9,22 @@
8
9
  - Fonts: frontpage fonts [--slot] [--query] → exact value → tokens set fonts.<role> → APPLY font-sans|serif|mono|display. Build bundles catalog faces from tokens.ts and/or CSS font-family contexts (not prose). Inline-only without tokens works for catalog faces after rebuild; prefer tokens+utilities for brand roles.
9
10
  - Module/extension docs: frontpage modules docs <key> | frontpage extensions docs <key>
10
11
  - Search flags: --regex --case-sensitive --whole-word --files-only --context-lines --max-results
11
- - Modules setup: wizard modules (payments/shop/ordering): frontpage modules <key> connect [--no-open] opens Frontpage (logged-in browser) then Stripe/Square; already-connected stays live. payments/shop connect = admin key; ordering = editor. payments disconnect 409s if shop still uses the shared Stripe account. request-setup remains valid for the editor amber bar. frontpage modules <key> prints that key's CLI usage. Index: https://frontpage.host/developers/modules. Typed config: frontpage modules callbar|forms|booking|signups get|set (forms set needs --instance; confirm emails paid-plan only). google_reviews: frontpage modules google_reviews connect --place-id | --maps-url (server resolve + seed; no Autocomplete; do not write placeId via config). Also status, list-reviews, hide/show, set-stars, set-sort, set-weight, reset-weights, refresh, disconnect. Docs: https://frontpage.host/developers/modules/google-reviews. Markup: data-fp-google-reviews / data-fp-google-ask + required data-fp-requires="google_reviews". Ask scores: frontpage inbox google_reviews.
12
+ - Modules setup: wizard modules (payments/shop/ordering): frontpage modules <key> connect [--no-open] opens Frontpage (logged-in browser) then Stripe/Square; already-connected stays live. payments/shop connect = admin key; ordering = editor. payments disconnect 409s if shop still uses the shared Stripe account. request-setup remains valid for the editor amber bar. frontpage modules <key> prints that key's CLI usage. Index: https://frontpage.host/developers/modules. Typed config: frontpage modules callbar|forms|booking|signups get|set (forms set needs --instance; confirm emails paid-plan only). Form destinations: frontpage modules forms destinations|connect-destination|attach-destination|detach-destination|remove-destination --yes|request-destination-setup (token = admin; request-destination-setup lights the editor bar; request-setup forms is a no-op; connect does not attach every form). Privyr: paste Incoming Webhook URL (https://www.privyr.com/api/v1/incoming-leads/…), not a token. HubSpot: Development → Keys → Service keys or Legacy apps → Auth → Show token. Pipedrive: profile → Company settings → Personal preferences → API. Docs: https://frontpage.host/developers/modules#forms-destinations. google_reviews: frontpage modules google_reviews connect --place-id | --maps-url (server resolve + seed; no Autocomplete; do not write placeId via config). Also status, list-reviews, hide/show, set-stars, set-sort, set-weight, reset-weights, refresh, disconnect. Docs: https://frontpage.host/developers/modules/google-reviews. Markup: data-fp-google-reviews / data-fp-google-ask + required data-fp-requires="google_reviews". Ask scores: frontpage inbox google_reviews.
12
13
  - Analytics ops: frontpage analytics summary|query|funnel|timeseries|compare|sql (bare analytics = dashboard)
13
14
  - Media: frontpage media upload <file> (images/PDF ≤25 MB, video ≤50 MB, SVG/text ≤4 MB); human prints sitePath first — use /__media/uploads/<slug>/… in markup. Text/CSV is inline only (not media library; --json omits full content).
14
15
  - Prompt attachments: frontpage prompt "…" --attach <file|/__media/…|https://…> [--asset-url https://…] (same media caps as media upload)
15
16
  - Version: frontpage --version | -V | version
16
17
  - SEO get: frontpage seo get [/path|--path /path]; search path filter: [path|--path <path>]; open [/path|--path /path]
18
+ - Redirects: frontpage redirects list|set|rm — HTTP 301/308, instant, not a file; revert does not undo; do not fake with Astro.redirect / meta refresh
19
+ - People: frontpage people list|get|add|set|note|rm-note|follow-up|hide|unhide — site-scoped person graph (email or phone). Inbox stays the raw ledger. Hide is not forget. Unknown leftover flags fail closed.
17
20
  - Analytics funnel: --steps JSON array or comma paths (/, /pricing); bad JSON → Invalid JSON for --steps
18
21
  - Modules: unknown key errors list Known: …; mailing list module key is signups (not subscribe)
19
- - Errors: --json API envelopes include requestId when the server returned one; local validation omits requestId/httpStatus; runs wait failed/canceled → ok:false + run.error (exit 2); deploy wait fail-closed; push --wait
22
+ - Errors: --json API envelopes include requestId when the server returned one; local validation omits requestId/httpStatus; runs wait failed/canceled → ok:false + run.error (exit 2); deploy wait = agent idle + tip compile ready (fail-closed / compile timeout → exit 2); push --wait and publish --wait wait for GET /deploy ready
20
23
  - Auth: mint fp_live_ key in Frontpage UI (Developers); frontpage login KEY or FRONTPAGE_API_KEY
21
24
  - Site id: subdomain only (frontpage use <subdomain>). Not custom domains.
22
- - Staging: https://<subdomain>.frontpage.host — push/prompt/edit update staging; no separate staging deploy step. With a custom domain, subdomain may be owner-private (login wall ≠ failed push); public review is published custom domain.
23
- - Production: custom domain after frontpage publish — NEVER publish/unpublish unless the user explicitly asks
25
+ - Three clocks: git tip (push/prompt/edit) ≠ site compile (GET /deploy ready) ≠ editor agent run (runs wait). File-only push has no agent run.
26
+ - Production: frontpage publish promotes the last ready compile (409 if tip not built). frontpage publish --wait waits then publishes. NEVER publish/unpublish unless the user explicitly asks. Verify live <meta name="frontpage:commit">.
24
27
  - Sync: allowlist src/ public/ content/; platform overlay + secret-like paths rejected; pull before edit if tip may have moved (status/diff tipDrift); pull → edit → diff (blobSha) → push [--wait] (CAS); no push during active agent run; UI agent does not use CLI pull; review on subdomain
25
28
  - Machine: global --json / --quiet; exit 0/2/3/4/5 (see help exit-codes)
26
- - deploy wait = optional build wait only (fail-closed on failed/canceled); it is NOT go-live / publish
29
+ - deploy wait = agent-run idle (if any) + tip compile ready; it is NOT go-live / publish
27
30
  - No runs continue, domains, billing, members, MCP, or git remotes in v1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontpage-host",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Frontpage CLI — manage your site from the terminal",
5
5
  "type": "module",
6
6
  "bin": {