frontpage-host 0.3.20 → 0.3.22

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
@@ -321,3 +321,7 @@ remain errors; they do not become original-file fallback successes.
321
321
  ## Native Booking
322
322
 
323
323
  Read [BOOKING.md](./BOOKING.md) before using `frontpage modules booking`. Use native settings, types/forms, members/spaces, availability, reservations and recurring blocks. Do not send new bookings through the old generic module collector. Every write requires a stable request key; updates require the current revision. Consent and private calendar-link entry stay with the human in Workspace → Connections. Ordinary owned connection management and self-hours share the workspace actions; setup requests appear in the editor status bar and overview.
324
+
325
+ ## Page screenshots
326
+
327
+ Use `frontpage screenshots capture --path /pricing --device mobile --wait --json` to inspect a built staging page, or `frontpage screenshots get CAPTURE_ID --json` to read a result. The site context comes from `--site` or `frontpage use`. Check the returned revision, coverage and warnings. Private-site images require authenticated reads; captures expire after 30 days. Use the tool when it helps, not on every turn or edit. See [SCREENSHOTS.md](SCREENSHOTS.md) for capture modes, retries, limits and image downloads.
package/BOOKING.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Use `frontpage modules booking` to read the command list. Booking settings, forms, availability and reservations use the same service as Workspace → Bookings. The CLI uses the site's existing plan and membership rules. Admins configure settings, members/spaces and booking types. Editors manage bookings and blocks. Viewers read availability and manage their own appointment hours and authorized calendar operations through the same shared actions. Consent, private calendar-link entry and private event content require the human workspace flow.
4
4
 
5
- Start with `frontpage modules booking status --site SITE --json`. An admin must set the business time zone and availability, publish a booking type with assigned members or spaces, and turn online booking on. A member must already hold a Frontpage account and have joined the site. Use the existing Viewer role when inviting someone who only needs personal availability and calendar connections. Workspace Booking tabs are Calendar, Bookings, Booking types, Spaces, Connections and Settings. Choose joined site members directly in a type; there is no Booking Members screen. Each member sets My appointment hours in Settings for this site. Spaces has its own editor.
5
+ Start with `frontpage modules booking status --site SITE --json`. Its `providerSetup.google` and `providerSetup.outlook` flags report whether new provider setup is available for this site. Unreleased options are hidden in the workspace and shared setup calls return `provider_unavailable`. Native Booking and supported iCal links do not depend on Google/Microsoft approval. Existing connection management remains available. An admin must set the business time zone and availability, publish a booking type with assigned members or spaces, and turn online booking on. A member must already hold a Frontpage account and have joined the site. Use the existing Viewer role when inviting someone who only needs personal availability and calendar connections. Workspace Booking tabs are Calendar, Bookings, Booking types, Spaces, Connections and Settings. Choose joined site members directly in a type; there is no Booking Members screen. Each member sets My appointment hours in Settings for this site. Spaces has its own editor.
6
6
 
7
7
  ## Commands
8
8
 
package/README.md CHANGED
@@ -169,3 +169,7 @@ tables, scope-changing functions, custom types and unsupported syntax reject.
169
169
  Use the [analytics reference](https://frontpage.host/developers/analytics) for
170
170
  metric definitions and examples. The internal agent and MCP expose full
171
171
  page-detail report tools; this release adds no CLI command. MCP excludes SQL.
172
+
173
+ ## Page screenshots
174
+
175
+ Use `frontpage screenshots capture --path /pricing --device mobile --wait --json` to inspect a built staging page, or `frontpage screenshots get CAPTURE_ID --json` to read a result. The site context comes from `--site` or `frontpage use`. Check the returned revision, coverage and warnings. Private-site images require authenticated reads; captures expire after 30 days. Use the tool when it helps, not on every turn or edit. See [SCREENSHOTS.md](SCREENSHOTS.md) for capture modes, retries, limits and image downloads.
package/SCREENSHOTS.md ADDED
@@ -0,0 +1,29 @@
1
+ # Page screenshots
2
+
3
+ Inspect a rendered staging page with the same capture service used by the internal agent. Capture does not edit or publish the site. Use screenshots when seeing the page helps; no capture is required every turn or after every change.
4
+
5
+ ```sh
6
+ frontpage screenshots capture --site SITE --path /pricing --device mobile --wait --json
7
+ frontpage screenshots capture --site SITE --path / --capture full_page --wait --json
8
+ frontpage screenshots capture --site SITE --path / --capture element --selector '#contact' --wait --json
9
+ frontpage screenshots get CAPTURE_ID --site SITE --json
10
+ ```
11
+
12
+ Options: `--path` defaults to `/`; `--device` is `desktop` (1280×800) or `mobile` (390×844); `--capture` is `viewport` (default), `full_page` or `element`. Only element mode accepts/requires `--selector`, which must match exactly one element. Paths cannot contain query strings, fragments or encoded characters. Full-page capture returns ordered slices up to 24,000px; check `coverage` and `warnings`.
13
+
14
+ `capture` returns an ID and queued/running/complete/failed status. `--wait` polls for up to 180 seconds. A pending result still includes its ID; use `get` to continue. A failed capture returns a nonzero exit status. `--request-key UUID` identifies one request. After an uncertain response, repeat the same input and key shown in the error; do not start another request to recover it. To make a fresh capture after a failed job, use a new key.
15
+
16
+ Results describe built staging content and include its revision. They cannot show unsaved or unbuilt edits. Use `frontpage deploy wait` after a push when you want the new build, then check the returned revision. A revision change during capture fails the request rather than labeling new pixels as the old revision.
17
+
18
+ Requires current site membership and normal CLI plan access (Scale, Enterprise or Internal). Each result is also bound to its requesting user; a capture ID is not permission. Private Frontpage subdomains use server-owned, site-bound access. No cookies, bypass headers, arbitrary origins or operator credentials are accepted. Up to six new captures per site per minute.
19
+
20
+ New site capture images are encrypted in the existing store and delivered through authenticated API paths. In a completed result, `result.images` includes ordered `{url,width,height,top,sizeBytes}` entries. `url` is relative to `https://app.frontpage.host`; fetch it using the same API bearer credential to receive PNG bytes. Never treat the raw encrypted object as an image or publish it as a site asset. Image reads recheck permission and expiry. Results expire after 30 days; the existing reaper cleans expired files and retries failed deletions. Rotation of the server audit key makes older encrypted captures unreadable; request a new capture.
21
+
22
+ ```sh
23
+ # Use an image path returned by screenshots get; keep the API key out of logs.
24
+ curl --fail --header "Authorization: Bearer $FRONTPAGE_API_KEY" \
25
+ 'https://app.frontpage.host/api/v1/sites/SITE/screenshots/CAPTURE_ID?image=0' \
26
+ --output screenshot.png
27
+ ```
28
+
29
+ REST: `POST /api/v1/sites/{slug}/screenshots` with `{path,device,capture,selector?}` and optional `Idempotency-Key` UUID; `GET /api/v1/sites/{slug}/screenshots/{id}` for status/result; the same GET with `?image=N` returns PNG bytes. Images require auth even when the source page is public. MCP screenshot integration is pending; see the MCP handoff rather than assuming the command is already an MCP tool.
package/dist/bin.js CHANGED
@@ -39,6 +39,7 @@ import { runSettings } from "./commands/settings.js";
39
39
  import { runEdit } from "./commands/edit.js";
40
40
  import { runFonts } from "./commands/fonts.js";
41
41
  import { runTokens } from "./commands/tokens.js";
42
+ import { runScreenshots } from "./commands/screenshots.js";
42
43
  import { runHistory } from "./commands/history.js";
43
44
  import { runRevert } from "./commands/revert.js";
44
45
  import { runCompletion } from "./commands/completion.js";
@@ -145,6 +146,8 @@ async function main() {
145
146
  return runFonts(args);
146
147
  case "tokens":
147
148
  return runTokens(args);
149
+ case "screenshots":
150
+ return runScreenshots(args);
148
151
  case "history":
149
152
  return runHistory(args);
150
153
  case "revert":
@@ -0,0 +1 @@
1
+ export declare function runScreenshots(args: string[]): Promise<number>;
@@ -0,0 +1,62 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { resolveSlug } from "../site.js";
3
+ import { assertNoUnknownFlags, hasFlag, takeFlagValue } from "../argv.js";
4
+ import { CliHttpError, EXIT_OK, EXIT_USAGE, v1Get, v1Post } from "../http/client.js";
5
+ import { emitErr, emitOk, isJson } from "../runtime.js";
6
+ const usage = "Usage: frontpage screenshots capture [--path /] [--device desktop|mobile] [--capture viewport|full_page|element] [--selector CSS] [--request-key UUID] [--wait] | get CAPTURE_ID [--site SITE] [--json]";
7
+ export async function runScreenshots(args) {
8
+ try {
9
+ const { slug, rest } = await resolveSlug(args), work = [...rest], action = work.shift();
10
+ const endpoint = `/api/v1/sites/${encodeURIComponent(slug)}/screenshots`;
11
+ let result;
12
+ if (action === "capture") {
13
+ const path = takeFlagValue(work, "--path") ?? "/", device = takeFlagValue(work, "--device") ?? "desktop";
14
+ const capture = takeFlagValue(work, "--capture") ?? "viewport", selector = takeFlagValue(work, "--selector");
15
+ const id = takeFlagValue(work, "--request-key") ?? randomUUID(), wait = hasFlag(work, "--wait");
16
+ assertNoUnknownFlags(work, "screenshots");
17
+ if (work.length || !["desktop", "mobile"].includes(device) || !["viewport", "full_page", "element"].includes(capture)
18
+ || (capture === "element") !== !!selector || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id))
19
+ throw new CliHttpError(usage, EXIT_USAGE, 400);
20
+ try {
21
+ result = await v1Post(endpoint, { path, device, capture, ...(selector ? { selector } : {}) }, { idempotencyKey: id, timeoutMs: 30000 });
22
+ }
23
+ catch (error) {
24
+ // The server may already have accepted the request. Give the caller the exact retry key.
25
+ if (error instanceof Error)
26
+ error.message += ` Request key: ${id}. Retry the same input with --request-key ${id}.`;
27
+ throw error;
28
+ }
29
+ if (wait) {
30
+ const deadline = Date.now() + 180000;
31
+ while (["queued", "running"].includes(result.status) && Date.now() < deadline) {
32
+ await new Promise(resolve => setTimeout(resolve, 2000));
33
+ result = await v1Get(`${endpoint}/${result.id}`, { timeoutMs: 15000 });
34
+ }
35
+ }
36
+ }
37
+ else if (action === "get") {
38
+ const id = work.shift();
39
+ assertNoUnknownFlags(work, "screenshots");
40
+ if (!id || work.length)
41
+ throw new CliHttpError(usage, EXIT_USAGE, 400);
42
+ result = await v1Get(`${endpoint}/${encodeURIComponent(id)}`, { timeoutMs: 15000 });
43
+ }
44
+ else
45
+ throw new CliHttpError(usage, EXIT_USAGE, 400);
46
+ if (isJson())
47
+ emitOk("screenshots", { slug, ...result });
48
+ else {
49
+ console.log(`${result.id} ${result.status}${result.revision ? ` revision ${result.revision}` : ""}`);
50
+ if (result.error)
51
+ console.error(result.error);
52
+ for (const image of result.result?.images ?? [])
53
+ console.log(image.url);
54
+ if (["queued", "running"].includes(result.status))
55
+ console.log(`Continue: frontpage screenshots get ${result.id} --site ${slug}`);
56
+ }
57
+ return result.status === "failed" ? EXIT_USAGE : EXIT_OK;
58
+ }
59
+ catch (error) {
60
+ return emitErr("screenshots", error);
61
+ }
62
+ }
@@ -19,6 +19,7 @@ _frontpage_completions() {
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
+ screenshots) COMPREPLY=( $(compgen -W "capture get --path --device --capture --selector --request-key --wait --site --json" -- "\${cur}") ) ;;
22
23
  images) COMPREPLY=( $(compgen -W "generate" -- "\${cur}") ) ;;
23
24
  fonts) COMPREPLY=( $(compgen -W "list search" -- "\${cur}") ) ;;
24
25
  tokens) COMPREPLY=( $(compgen -W "set" -- "\${cur}") ) ;;
@@ -68,6 +69,7 @@ _frontpage() {
68
69
  runs) _values 'subcommand' current wait cancel retry get ;;
69
70
  deploy) _values 'subcommand' wait ;;
70
71
  media) _values 'subcommand' list upload ;;
72
+ screenshots) _values 'subcommand' capture get ;;
71
73
  images) _values 'subcommand' generate ;;
72
74
  fonts) _values 'subcommand' list search ;;
73
75
  tokens) _values 'subcommand' set ;;
@@ -109,6 +111,7 @@ export function generateFishCompletion() {
109
111
  `complete -c frontpage -n "__fish_seen_subcommand_from runs" -a "current wait cancel retry get"`,
110
112
  `complete -c frontpage -n "__fish_seen_subcommand_from deploy" -a "wait"`,
111
113
  `complete -c frontpage -n "__fish_seen_subcommand_from media" -a "list upload"`,
114
+ `complete -c frontpage -n "__fish_seen_subcommand_from screenshots" -a "capture get"`,
112
115
  `complete -c frontpage -n "__fish_seen_subcommand_from images" -a "generate"`,
113
116
  `complete -c frontpage -n "__fish_seen_subcommand_from fonts" -a "list search"`,
114
117
  `complete -c frontpage -n "__fish_seen_subcommand_from tokens" -a "set"`,
@@ -40,6 +40,7 @@ export declare function v1Get<T>(path: string, opts?: {
40
40
  }): Promise<T>;
41
41
  export declare function v1Post<T>(path: string, body?: unknown, opts?: {
42
42
  timeoutMs?: number;
43
+ idempotencyKey?: string;
43
44
  }): Promise<T>;
44
45
  export declare function v1Put<T>(path: string, body?: unknown): Promise<T>;
45
46
  export declare function v1Delete<T>(path: string): Promise<T>;
@@ -89,6 +89,8 @@ async function v1Request(method, path, body, opts) {
89
89
  Authorization: `Bearer ${key}`,
90
90
  Accept: "application/json",
91
91
  };
92
+ if (opts?.idempotencyKey)
93
+ headers["Idempotency-Key"] = opts.idempotencyKey;
92
94
  let payload;
93
95
  if (body !== undefined) {
94
96
  headers["Content-Type"] = "application/json";
package/dist/registry.js CHANGED
@@ -3,6 +3,14 @@
3
3
  * Only shipped Phase 0–5 commands appear here.
4
4
  */
5
5
  export const COMMANDS = [
6
+ {
7
+ name: "screenshots", summary: "Inspect a rendered site page on desktop or mobile",
8
+ usage: "frontpage screenshots capture [--path /] [--device desktop|mobile] [--capture viewport|full_page|element] [--selector CSS] [--request-key UUID] [--wait] | get CAPTURE_ID [--site SITE] [--json]",
9
+ topics: ["site-building", "content"],
10
+ examples: ["frontpage screenshots capture --path /pricing --device mobile --wait --json", "frontpage screenshots get CAPTURE_ID --json"],
11
+ flags: ["--request-key UUID preserves the same request after an uncertain response", "--wait polls for up to 180 seconds; a pending result includes its ID", "Image URLs are authenticated API paths; use the same API key to fetch image/png bytes"],
12
+ dangers: ["Captures built staging pages, not unbuilt edits; check revision and coverage. Results expire after 30 days. Capture does not publish."],
13
+ },
6
14
  {
7
15
  name: "help",
8
16
  summary: "Show available commands",
package/llms.txt CHANGED
@@ -69,3 +69,5 @@ Articles needs no setup. Use `frontpage articles create-section --label "Blog" -
69
69
  Bookings: read packaged [BOOKING.md](./BOOKING.md) and `frontpage modules booking`. Native commands share workspace validation, per-type forms, resource availability, revisions and stable request keys. Calendar consent and private calendar-link entry use a human handoff; ordinary authorized refresh/hours/connection operations run through shared actions; never pass private feed links or OAuth secrets through CLI/agent inputs.
70
70
 
71
71
  Analytics reference: https://frontpage.host/developers/analytics. Filter type=pageview for views; count alone includes all matching events. Days presets are 1/7/30/90; bare dashboard defaults to 7, structured ops to 30. SQL dates belong in SQL; parsed reporting allowlists and site RLS apply (4000 chars, 5s, 500 returned rows). Unsafe functions/types, unrelated tables, recursion and unsupported syntax reject. Funnels count session overlap without enforcing chronological order. MCP excludes raw SQL and has its own dashboard/page-detail tools; the CLI has no new page-detail subcommand.
72
+
73
+ - SCREENSHOTS.md: site-scoped desktop/mobile, full-page and element captures; built revisions, protected images, expiry and request recovery.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "frontpage-host",
3
- "version": "0.3.20",
4
- "description": "Frontpage CLI — manage your site from the terminal",
3
+ "version": "0.3.22",
4
+ "description": "Frontpage CLI \u2014 manage your site from the terminal",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "frontpage": "dist/bin.js"
@@ -15,7 +15,8 @@
15
15
  "MAIL.md",
16
16
  "CUSTOMER-INBOX.md",
17
17
  "PAYMENTS.md",
18
- "BOOKING.md"
18
+ "BOOKING.md",
19
+ "SCREENSHOTS.md"
19
20
  ],
20
21
  "scripts": {
21
22
  "build": "tsc -p tsconfig.json",