frontpage-host 0.1.10 → 0.1.12
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 +5 -4
- package/dist/commands/analytics.d.ts +7 -0
- package/dist/commands/analytics.js +68 -16
- package/dist/commands/media.d.ts +3 -0
- package/dist/commands/media.js +45 -5
- package/dist/commands/modules.js +6 -8
- package/dist/commands/push.js +7 -0
- package/dist/registry.js +21 -7
- package/llms.txt +5 -3
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -29,15 +29,16 @@ Frontpage has two surfaces:
|
|
|
29
29
|
**How changes become visible on staging**
|
|
30
30
|
|
|
31
31
|
- `frontpage push`, `frontpage prompt`, and `frontpage edit` update the site branch and the **staging** surface.
|
|
32
|
-
- After a push or agent run finishes building, changes are
|
|
32
|
+
- After a push or agent run finishes building, changes are on the site branch behind `https://<subdomain>.frontpage.host`.
|
|
33
33
|
- There is **no separate “deploy to staging” step** agents must run. Do not treat `frontpage deploy wait` as “going live.” At most it waits for an in-flight build to finish before you open the subdomain to review.
|
|
34
34
|
- `frontpage open` opens the subdomain staging URL.
|
|
35
|
+
- **Custom-domain caveat:** `<subdomain>.frontpage.host` is publicly viewable (anonymous) only when the site has **no** custom domain and subdomain sharing is public. If a custom domain is connected (e.g. `arbiter.wiki`), the subdomain is often **owner-private** — an unauthenticated browser/curl may see the app login page. That login wall is **not** a failed push. Anonymous public review is then on the **published custom domain**; editing the subdomain still needs an owner browser session (CLI API key ≠ browser cookie).
|
|
35
36
|
|
|
36
37
|
**Production publish is a deliberate promote**
|
|
37
38
|
|
|
38
39
|
- `frontpage publish` copies the current **staging tip** to **production** (custom domain / published site).
|
|
39
40
|
- **Never run `publish` or `unpublish` unless the user explicitly asked**, or the user’s own automation/workflow clearly requires autonomous publishing.
|
|
40
|
-
- Default: finish work on staging, tell the user to review
|
|
41
|
+
- Default: finish work on staging, tell the user to review the staging subdomain when it is publicly shareable (see custom-domain caveat above), and let **them** publish (or ask you to publish after they approve).
|
|
41
42
|
|
|
42
43
|
## Non-interactive auth
|
|
43
44
|
|
|
@@ -100,7 +101,7 @@ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or
|
|
|
100
101
|
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
102
|
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
103
|
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.
|
|
104
|
+
5. **Tokens** in `src/config/tokens.ts` — keep required color/font/spacing/radii keys; style via CSS variables / token Tailwind. **Fonts load rule:** the build bundles catalog faces when the family string appears in `tokens.ts` and/or in a real CSS `font-family` / `fontFamily` / `font-['…']` context under `src/pages|components|styles` — not from prose alone. Preferred path: `frontpage fonts` → exact `value` → `frontpage tokens set --group fonts --key <role> --value "…"` → **APPLY** `font-sans` / `font-serif` / `font-mono` / `font-display` (or `var(--font-*)`) on elements. Inline catalog `font-family` accents can bundle after rebuild, but brand roles still prefer tokens + utilities. Off-catalog families silently fall back to system fonts (push may warn). Module how-tos: `frontpage modules docs <key>` / `frontpage extensions docs <key>`.
|
|
104
105
|
6. **Shared chrome** — prefer `src/components/Header.astro` + `Footer.astro` (or `chrome: { managed: true }` in `site.ts`).
|
|
105
106
|
7. **404** — customize `src/pages/404.astro` if needed; keep `frontpage:404` meta; never register `/404` in `nav`/`pages`.
|
|
106
107
|
8. **Platform-owned paths** stay off-limits (BaseLayout overlay, module/extension shells, `package.json`, …). Customer layouts like `DocsLayout` / `GuideLayout` are OK.
|
|
@@ -112,7 +113,7 @@ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or
|
|
|
112
113
|
- 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`.
|
|
113
114
|
- Migrate a folder into Articles: prefer `frontpage prompt "migrate … into articles"` — do not invent a mass delete+write migration.
|
|
114
115
|
- Extensions: `frontpage extensions`; if LOCKED on plan, do not fake charts/etc.
|
|
115
|
-
- Media: `frontpage media upload <file>` — images/PDF ≤25 MB, video (MP4/WebM/MOV) ≤50 MB, SVG/text ≤4 MB (sanitizing proxy). Human output prints `sitePath:` first (use that in markup — never raw blob hosts), then optional `url:`. Optional `?w=` on rasters only.
|
|
116
|
+
- Media: `frontpage media upload <file>` — images/PDF ≤25 MB, video (MP4/WebM/MOV) ≤50 MB, SVG/text ≤4 MB (sanitizing proxy). Human output prints `sitePath:` first (use that in markup — never raw blob hosts), then optional `url:`. Optional `?w=` on rasters only. **Text/CSV** uploads are inline only — **not** added to the media library and **cannot** be linked via `/__media` (`media upload --json` omits full `content`; use `inbox … --csv` for submission exports).
|
|
116
117
|
- Analytics: bare `frontpage analytics` = dashboard; also `summary|query|funnel|timeseries|compare|sql` for structured reads (SQL is guard + RLS site-scoped).
|
|
117
118
|
|
|
118
119
|
## Agent prompt loop
|
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Funnel --steps: JSON array (canonical) or comma-separated site paths (R9).
|
|
3
|
+
* Example shorthand: `--steps /, /problems, /updates`
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseFunnelSteps(raw: string): Array<{
|
|
6
|
+
path: string;
|
|
7
|
+
}>;
|
|
1
8
|
export declare function runAnalytics(args: string[]): Promise<number>;
|
|
@@ -1,24 +1,48 @@
|
|
|
1
1
|
import { resolveSlug } from "../site.js";
|
|
2
|
-
import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get, v1Post } from "../http/client.js";
|
|
2
|
+
import { CliHttpError, EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get, v1Post } from "../http/client.js";
|
|
3
|
+
import { assertNoUnknownFlags, takeFlag } from "../argv.js";
|
|
3
4
|
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
5
|
function parseJsonFlag(raw, label) {
|
|
13
6
|
if (raw === undefined)
|
|
14
7
|
return undefined;
|
|
15
8
|
try {
|
|
16
9
|
return JSON.parse(raw);
|
|
17
10
|
}
|
|
18
|
-
catch {
|
|
19
|
-
|
|
11
|
+
catch (err) {
|
|
12
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
13
|
+
throw new CliHttpError(`Invalid JSON for ${label}: ${detail}`, EXIT_USAGE, 0, "bad_request");
|
|
20
14
|
}
|
|
21
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Funnel --steps: JSON array (canonical) or comma-separated site paths (R9).
|
|
18
|
+
* Example shorthand: `--steps /, /problems, /updates`
|
|
19
|
+
*/
|
|
20
|
+
export function parseFunnelSteps(raw) {
|
|
21
|
+
const trimmed = raw.trim();
|
|
22
|
+
if (!trimmed) {
|
|
23
|
+
throw new CliHttpError(`Usage: frontpage analytics funnel --steps '[{"path":"/"},{"path":"/pricing"}]' or --steps /, /pricing`, EXIT_USAGE, 0, "bad_request");
|
|
24
|
+
}
|
|
25
|
+
if (trimmed.startsWith("[")) {
|
|
26
|
+
const parsed = parseJsonFlag(trimmed, "--steps");
|
|
27
|
+
if (!Array.isArray(parsed)) {
|
|
28
|
+
throw new CliHttpError("Invalid JSON for --steps: expected an array of { path } objects", EXIT_USAGE, 0, "bad_request");
|
|
29
|
+
}
|
|
30
|
+
return parsed;
|
|
31
|
+
}
|
|
32
|
+
const paths = trimmed
|
|
33
|
+
.split(",")
|
|
34
|
+
.map((s) => s.trim())
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
if (paths.length === 0) {
|
|
37
|
+
throw new CliHttpError("Invalid --steps: provide a JSON array or comma-separated paths like /, /pricing", EXIT_USAGE, 0, "bad_request");
|
|
38
|
+
}
|
|
39
|
+
for (const path of paths) {
|
|
40
|
+
if (!path.startsWith("/") || path.startsWith("//")) {
|
|
41
|
+
throw new CliHttpError(`Invalid funnel step path "${path}" — each step must be a site-relative path starting with /`, EXIT_USAGE, 0, "bad_request");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return paths.map((path) => ({ path }));
|
|
45
|
+
}
|
|
22
46
|
function rangeFields(work) {
|
|
23
47
|
const days = takeFlag(work, "--days");
|
|
24
48
|
const from = takeFlag(work, "--from");
|
|
@@ -35,6 +59,12 @@ function rangeFields(work) {
|
|
|
35
59
|
function printJson(data) {
|
|
36
60
|
console.log(JSON.stringify(data, null, 2));
|
|
37
61
|
}
|
|
62
|
+
function finishWork(work) {
|
|
63
|
+
assertNoUnknownFlags(work, "analytics");
|
|
64
|
+
if (work.length > 0) {
|
|
65
|
+
throw new CliHttpError(`Unexpected argument: ${work[0]}. Run: frontpage help analytics`, EXIT_USAGE, 0, "bad_request");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
38
68
|
export async function runAnalytics(args) {
|
|
39
69
|
try {
|
|
40
70
|
const { slug, rest } = await resolveSlug(args);
|
|
@@ -42,10 +72,11 @@ export async function runAnalytics(args) {
|
|
|
42
72
|
const sub = work[0];
|
|
43
73
|
const ops = new Set(["summary", "query", "funnel", "timeseries", "compare", "sql", "dashboard"]);
|
|
44
74
|
// Bare `analytics` or `analytics dashboard` → legacy GET dashboard (compat).
|
|
45
|
-
if (!sub || sub === "dashboard" || !ops.has(sub) && sub.startsWith("--")) {
|
|
75
|
+
if (!sub || sub === "dashboard" || (!ops.has(sub) && sub.startsWith("--"))) {
|
|
46
76
|
if (sub === "dashboard")
|
|
47
77
|
work.shift();
|
|
48
78
|
const days = takeFlag(work, "--days") ?? "7";
|
|
79
|
+
finishWork(work);
|
|
49
80
|
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/analytics?days=${encodeURIComponent(days)}`);
|
|
50
81
|
if (isJson()) {
|
|
51
82
|
emitOk("analytics", data);
|
|
@@ -63,6 +94,7 @@ export async function runAnalytics(args) {
|
|
|
63
94
|
if (sub === "summary") {
|
|
64
95
|
const filters = parseJsonFlag(takeFlag(work, "--filters"), "--filters");
|
|
65
96
|
const body = { op: "summary", ...rangeFields(work), ...(filters ? { filters } : {}) };
|
|
97
|
+
finishWork(work);
|
|
66
98
|
const data = await v1Post(base, body);
|
|
67
99
|
if (isJson())
|
|
68
100
|
emitOk("analytics", data);
|
|
@@ -87,6 +119,7 @@ export async function runAnalytics(args) {
|
|
|
87
119
|
...(limit ? { limit: Number(limit) } : {}),
|
|
88
120
|
...(filters ? { filters } : {}),
|
|
89
121
|
};
|
|
122
|
+
finishWork(work);
|
|
90
123
|
const data = await v1Post(base, body);
|
|
91
124
|
if (isJson())
|
|
92
125
|
emitOk("analytics", data);
|
|
@@ -95,13 +128,21 @@ export async function runAnalytics(args) {
|
|
|
95
128
|
return EXIT_OK;
|
|
96
129
|
}
|
|
97
130
|
if (sub === "funnel") {
|
|
98
|
-
|
|
131
|
+
let stepsRaw = takeFlag(work, "--steps");
|
|
99
132
|
if (!stepsRaw) {
|
|
100
|
-
console.error(`Usage: frontpage analytics funnel --steps '[{"path":"/"},{"path":"/pricing"}]' [--days N]`
|
|
133
|
+
console.error(`Usage: frontpage analytics funnel --steps '[{"path":"/"},{"path":"/pricing"}]' [--days N]\n` +
|
|
134
|
+
` or: frontpage analytics funnel --steps /, /pricing, /updates [--days N]`);
|
|
101
135
|
return EXIT_USAGE;
|
|
102
136
|
}
|
|
103
|
-
|
|
137
|
+
// Shell splits `--steps /, /pricing` into multiple argv tokens; reassemble path list.
|
|
138
|
+
if (!stepsRaw.trim().startsWith("[")) {
|
|
139
|
+
while (work[0] && work[0].startsWith("/") && !work[0].startsWith("//")) {
|
|
140
|
+
stepsRaw = `${stepsRaw},${work.shift()}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const steps = parseFunnelSteps(stepsRaw);
|
|
104
144
|
const body = { op: "funnel", steps, ...rangeFields(work) };
|
|
145
|
+
finishWork(work);
|
|
105
146
|
const data = await v1Post(base, body);
|
|
106
147
|
if (isJson())
|
|
107
148
|
emitOk("analytics", data);
|
|
@@ -120,6 +161,7 @@ export async function runAnalytics(args) {
|
|
|
120
161
|
...(interval ? { interval } : {}),
|
|
121
162
|
...(filters ? { filters } : {}),
|
|
122
163
|
};
|
|
164
|
+
finishWork(work);
|
|
123
165
|
const data = await v1Post(base, body);
|
|
124
166
|
if (isJson())
|
|
125
167
|
emitOk("analytics", data);
|
|
@@ -144,6 +186,7 @@ export async function runAnalytics(args) {
|
|
|
144
186
|
...rangeFields(work),
|
|
145
187
|
...(metric ? { metric } : {}),
|
|
146
188
|
};
|
|
189
|
+
finishWork(work);
|
|
147
190
|
const data = await v1Post(base, body);
|
|
148
191
|
if (isJson())
|
|
149
192
|
emitOk("analytics", data);
|
|
@@ -152,7 +195,16 @@ export async function runAnalytics(args) {
|
|
|
152
195
|
return EXIT_OK;
|
|
153
196
|
}
|
|
154
197
|
if (sub === "sql") {
|
|
155
|
-
const
|
|
198
|
+
const sqlFlag = takeFlag(work, "--sql");
|
|
199
|
+
const sql = sqlFlag ?? work.join(" ").trim();
|
|
200
|
+
if (sqlFlag) {
|
|
201
|
+
// Consumed via flag; reject leftover flags/args.
|
|
202
|
+
finishWork(work);
|
|
203
|
+
}
|
|
204
|
+
else if (sql) {
|
|
205
|
+
// Positional SQL consumed all remaining tokens.
|
|
206
|
+
work.length = 0;
|
|
207
|
+
}
|
|
156
208
|
if (!sql) {
|
|
157
209
|
console.error('Usage: frontpage analytics sql --sql "SELECT path, count(*) FROM analytics_events GROUP BY 1"');
|
|
158
210
|
return EXIT_USAGE;
|
package/dist/commands/media.d.ts
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
+
import { type UploadedMedia } from "../media/upload.js";
|
|
2
|
+
/** Sanitize media --json payload: omit full text body (R5); keep content for prompt --attach path. */
|
|
3
|
+
export declare function mediaJsonData(slug: string, data: UploadedMedia): Record<string, unknown>;
|
|
1
4
|
export declare function runMedia(args: string[]): Promise<number>;
|
package/dist/commands/media.js
CHANGED
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
import { resolveSlug } from "../site.js";
|
|
2
|
-
import { EXIT_OK, EXIT_USAGE, exitCodeForError } from "../http/client.js";
|
|
2
|
+
import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get } from "../http/client.js";
|
|
3
|
+
import { assertNoUnknownFlags } from "../argv.js";
|
|
3
4
|
import { emitErr, emitOk, isJson } from "../runtime.js";
|
|
4
5
|
import { uploadLocalMedia } from "../media/upload.js";
|
|
5
|
-
|
|
6
|
+
const TEXT_NOT_LIBRARY = "not added to the media library and cannot be linked via /__media. " +
|
|
7
|
+
"For form submission exports use inbox … --csv; large data files via the editor.";
|
|
8
|
+
/** Sanitize media --json payload: omit full text body (R5); keep content for prompt --attach path. */
|
|
9
|
+
export function mediaJsonData(slug, data) {
|
|
10
|
+
if (data.kind === "text") {
|
|
11
|
+
const content = data.content;
|
|
12
|
+
const contentBytes = data.sizeBytes ??
|
|
13
|
+
(typeof content === "string" ? Buffer.byteLength(content, "utf8") : 0);
|
|
14
|
+
const contentPreview = typeof content === "string"
|
|
15
|
+
? content.length > 256
|
|
16
|
+
? content.slice(0, 256)
|
|
17
|
+
: content
|
|
18
|
+
: undefined;
|
|
19
|
+
return {
|
|
20
|
+
slug,
|
|
21
|
+
kind: data.kind,
|
|
22
|
+
filename: data.filename,
|
|
23
|
+
sizeBytes: data.sizeBytes,
|
|
24
|
+
contentType: data.contentType,
|
|
25
|
+
transport: data.transport,
|
|
26
|
+
contentBytes,
|
|
27
|
+
contentPreview,
|
|
28
|
+
contentOmitted: true,
|
|
29
|
+
libraryAsset: false,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return { slug, ...data };
|
|
33
|
+
}
|
|
6
34
|
export async function runMedia(args) {
|
|
7
35
|
try {
|
|
8
36
|
const { slug, rest } = await resolveSlug(args);
|
|
9
37
|
const sub = rest[0] ?? "list";
|
|
10
38
|
if (sub === "list" || sub === "ls") {
|
|
39
|
+
const work = rest.slice(1);
|
|
40
|
+
assertNoUnknownFlags(work, "media");
|
|
41
|
+
if (work.length > 0) {
|
|
42
|
+
console.error(`Unexpected argument: ${work[0]}\nUsage: frontpage media list [--site <slug>] [--json]`);
|
|
43
|
+
return EXIT_USAGE;
|
|
44
|
+
}
|
|
11
45
|
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/media`);
|
|
12
46
|
if (isJson()) {
|
|
13
47
|
emitOk("media", { slug, ...data });
|
|
@@ -20,18 +54,24 @@ export async function runMedia(args) {
|
|
|
20
54
|
return EXIT_OK;
|
|
21
55
|
}
|
|
22
56
|
if (sub === "upload") {
|
|
23
|
-
const
|
|
57
|
+
const work = rest.slice(1);
|
|
58
|
+
const filePath = work.shift();
|
|
24
59
|
if (!filePath) {
|
|
25
60
|
console.error("Usage: frontpage media upload <file> [--site <slug>] [--json]");
|
|
26
61
|
return EXIT_USAGE;
|
|
27
62
|
}
|
|
63
|
+
assertNoUnknownFlags(work, "media");
|
|
64
|
+
if (work.length > 0) {
|
|
65
|
+
console.error(`Unexpected argument: ${work[0]}\nUsage: frontpage media upload <file> [--site <slug>] [--json]`);
|
|
66
|
+
return EXIT_USAGE;
|
|
67
|
+
}
|
|
28
68
|
const data = await uploadLocalMedia({ slug, filePath });
|
|
29
69
|
if (isJson()) {
|
|
30
|
-
emitOk("media",
|
|
70
|
+
emitOk("media", mediaJsonData(slug, data));
|
|
31
71
|
return EXIT_OK;
|
|
32
72
|
}
|
|
33
73
|
if (data.kind === "text") {
|
|
34
|
-
console.log(`Inline text (${data.filename}): ${data.sizeBytes ?? 0} bytes —
|
|
74
|
+
console.log(`Inline text (${data.filename}): ${data.sizeBytes ?? 0} bytes — ${TEXT_NOT_LIBRARY}`);
|
|
35
75
|
return EXIT_OK;
|
|
36
76
|
}
|
|
37
77
|
// Human mode: sitePath first so first-line copy/paste is markup-safe (R4).
|
package/dist/commands/modules.js
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { resolveSlug } from "../site.js";
|
|
2
2
|
import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get, v1Post } from "../http/client.js";
|
|
3
|
-
|
|
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
|
-
}
|
|
3
|
+
import { takeFlag } from "../argv.js";
|
|
11
4
|
export async function runModules(args) {
|
|
12
5
|
try {
|
|
13
6
|
const { slug, rest } = await resolveSlug(args);
|
|
@@ -22,7 +15,12 @@ export async function runModules(args) {
|
|
|
22
15
|
if (sub === "docs" || sub === "doc") {
|
|
23
16
|
const key = rest[1];
|
|
24
17
|
if (!key) {
|
|
18
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/modules`);
|
|
25
19
|
console.error("Usage: frontpage modules docs <key> [--instance <id>] [--site <slug>]");
|
|
20
|
+
console.error("Known modules (run: frontpage modules docs <key>):");
|
|
21
|
+
for (const m of data.modules) {
|
|
22
|
+
console.error(` ${m.key.padEnd(16)} ${m.name}`);
|
|
23
|
+
}
|
|
26
24
|
return EXIT_USAGE;
|
|
27
25
|
}
|
|
28
26
|
const work = rest.slice(2);
|
package/dist/commands/push.js
CHANGED
|
@@ -115,6 +115,7 @@ export async function runPush(args) {
|
|
|
115
115
|
commitSha: data.commitSha,
|
|
116
116
|
baseCommitSha: data.baseCommitSha,
|
|
117
117
|
filesChanged: data.filesChanged,
|
|
118
|
+
...(data.warnings?.length ? { warnings: data.warnings } : {}),
|
|
118
119
|
};
|
|
119
120
|
if (!wait) {
|
|
120
121
|
if (isJson()) {
|
|
@@ -123,12 +124,16 @@ export async function runPush(args) {
|
|
|
123
124
|
}
|
|
124
125
|
if (!data.changed) {
|
|
125
126
|
console.log("Nothing to commit (remote already matches).");
|
|
127
|
+
for (const w of data.warnings ?? [])
|
|
128
|
+
console.error(`Warning: ${w}`);
|
|
126
129
|
return EXIT_OK;
|
|
127
130
|
}
|
|
128
131
|
console.log(`Pushed ${data.filesChanged.length} file(s) → ${data.commitSha}`);
|
|
129
132
|
console.log("Updated .frontpage/state.json baseCommitSha");
|
|
130
133
|
console.log("Note: module markup may auto-activate modules (same as the editor agent).");
|
|
131
134
|
console.log("tokens.ts may be auto-repaired at commit if it would break the build.");
|
|
135
|
+
for (const w of data.warnings ?? [])
|
|
136
|
+
console.error(`Warning: ${w}`);
|
|
132
137
|
return EXIT_OK;
|
|
133
138
|
}
|
|
134
139
|
// --wait: after push, wait for agent-run busy to clear (same statuses as deploy wait).
|
|
@@ -141,6 +146,8 @@ export async function runPush(args) {
|
|
|
141
146
|
console.log(`Pushed ${data.filesChanged.length} file(s) → ${data.commitSha}`);
|
|
142
147
|
console.log("Updated .frontpage/state.json baseCommitSha");
|
|
143
148
|
}
|
|
149
|
+
for (const w of data.warnings ?? [])
|
|
150
|
+
console.error(`Warning: ${w}`);
|
|
144
151
|
console.error("Waiting for agent run / deploy idle…");
|
|
145
152
|
}
|
|
146
153
|
const result = await waitForAgentIdle({
|
package/dist/registry.js
CHANGED
|
@@ -168,7 +168,8 @@ export const COMMANDS = [
|
|
|
168
168
|
"frontpage fonts --query Cinzel --json",
|
|
169
169
|
],
|
|
170
170
|
flags: [
|
|
171
|
-
"Use the exact `value` string in tokens.fonts.* (via frontpage tokens set or push)",
|
|
171
|
+
"Use the exact `value` string in tokens.fonts.* (via frontpage tokens set or push), then APPLY font-* utilities",
|
|
172
|
+
"Build bundles catalog faces from tokens.ts and/or CSS font-family contexts (not prose mentions)",
|
|
172
173
|
"No --site required — catalog is account/platform scoped",
|
|
173
174
|
],
|
|
174
175
|
},
|
|
@@ -294,12 +295,16 @@ export const COMMANDS = [
|
|
|
294
295
|
"frontpage analytics summary --days 30",
|
|
295
296
|
"frontpage analytics query --dimension path --measure count --limit 10",
|
|
296
297
|
`frontpage analytics funnel --steps '[{"path":"/"},{"path":"/pricing"}]'`,
|
|
298
|
+
"frontpage analytics funnel --steps /, /pricing, /updates",
|
|
297
299
|
"frontpage analytics timeseries --metric pageviews --interval day --days 14",
|
|
298
300
|
`frontpage analytics compare --a '{"device":"mobile"}' --b '{"device":"desktop"}'`,
|
|
299
301
|
'frontpage analytics sql --sql "SELECT path, count(*)::int AS n FROM analytics_events GROUP BY 1 ORDER BY n DESC LIMIT 10"',
|
|
300
302
|
],
|
|
301
303
|
flags: [
|
|
302
304
|
"Bare analytics / dashboard = legacy GET dashboard (compat)",
|
|
305
|
+
"funnel --steps accepts JSON array or comma-separated paths (/, /pricing)",
|
|
306
|
+
"Invalid JSON for --steps / --a / --b prints an explicit parse error (not bare Usage)",
|
|
307
|
+
"Unknown leftover flags fail closed (exit 2)",
|
|
303
308
|
"Ops use POST …/analytics with allowlisted dimensions/measures; SQL is guard-validated + RLS",
|
|
304
309
|
],
|
|
305
310
|
},
|
|
@@ -316,6 +321,7 @@ export const COMMANDS = [
|
|
|
316
321
|
topics: ["agent", "content"],
|
|
317
322
|
examples: [
|
|
318
323
|
"frontpage modules docs forms",
|
|
324
|
+
"frontpage modules docs signups",
|
|
319
325
|
"frontpage modules list",
|
|
320
326
|
'frontpage modules request-setup payments --reason "You asked for a $50 deposit button — Stripe setup makes it live"',
|
|
321
327
|
],
|
|
@@ -345,7 +351,9 @@ export const COMMANDS = [
|
|
|
345
351
|
"Auto-selects proxy (≤4 MB) vs direct-to-Blob for large rasters/PDFs and all video",
|
|
346
352
|
"Human success: sitePath first (markup-safe), then url: (blob URL for debugging)",
|
|
347
353
|
"Use sitePath (/__media/uploads/<slug>/…) in markup — never raw blob hosts in pages",
|
|
354
|
+
"Text/CSV upload is inline only — not a media-library asset (no /__media link); --json omits full content",
|
|
348
355
|
"SVG stays on the sanitizing proxy path (≤4 MB)",
|
|
356
|
+
"Unknown leftover flags fail closed (exit 2)",
|
|
349
357
|
],
|
|
350
358
|
seeAlso: ["prompt", "help content", "help site-building"],
|
|
351
359
|
},
|
|
@@ -446,7 +454,9 @@ export const TOPICS = {
|
|
|
446
454
|
5. frontpage pull
|
|
447
455
|
6. Edit files under src/, public/, or content/
|
|
448
456
|
7. frontpage push --message "…"
|
|
449
|
-
8. Review https://<subdomain>.frontpage.host
|
|
457
|
+
8. Review staging: https://<subdomain>.frontpage.host when that subdomain is publicly shareable.
|
|
458
|
+
With a custom domain connected, the subdomain is often owner-private (login wall ≠ failed push);
|
|
459
|
+
anonymous review is on the published custom domain after publish.
|
|
450
460
|
9. frontpage publish # ONLY if the user asked — promotes to production
|
|
451
461
|
|
|
452
462
|
Staging is the subdomain. There is no separate staging-deploy step for agents.
|
|
@@ -508,8 +518,10 @@ export const TOPICS = {
|
|
|
508
518
|
|
|
509
519
|
Tokens & fonts:
|
|
510
520
|
- Keep required colors/fonts/spacing/radii keys; style via CSS variables / token Tailwind.
|
|
511
|
-
-
|
|
512
|
-
-
|
|
521
|
+
- Load rule: catalog faces bundle when the family string is in tokens.ts and/or a real CSS font-family / fontFamily / font-['…'] context under src/pages|components|styles — not from prose alone.
|
|
522
|
+
- Preferred path: frontpage fonts → exact value → tokens set fonts.<role> → APPLY font-sans|serif|mono|display (or var(--font-*)).
|
|
523
|
+
- Inline catalog font-family accents can load after rebuild; brand roles still prefer tokens + utilities.
|
|
524
|
+
- Setting fonts.sans alone is not enough — apply the utility on elements.
|
|
513
525
|
- Surgical token edit: frontpage tokens set --group fonts --key sans --value "…" (CAS like push).
|
|
514
526
|
|
|
515
527
|
Chrome / SEO / 404:
|
|
@@ -547,15 +559,17 @@ export const TOPICS = {
|
|
|
547
559
|
API errors include requestId in --json envelopes when the server returned one; local validation errors omit requestId and httpStatus.
|
|
548
560
|
Mid-task continuation is automatic — there is no runs continue.
|
|
549
561
|
Do not file-sync while an agent run is active (see help sync for push --wait).
|
|
550
|
-
Default after work: review https://<subdomain>.frontpage.host
|
|
562
|
+
Default after work: review staging on https://<subdomain>.frontpage.host when public;
|
|
563
|
+
with a custom domain, that subdomain may require an owner browser session (login wall ≠ failed deploy).
|
|
564
|
+
Do not publish unless asked.`,
|
|
551
565
|
content: `Content
|
|
552
|
-
frontpage media list|upload <file> # images/PDF
|
|
566
|
+
frontpage media list|upload <file> # images/PDF/video; text/CSV = inline not library; sitePath first when stored
|
|
553
567
|
frontpage inbox [leads|subscribers|orders|…] [--mark-read] [--csv]
|
|
554
568
|
frontpage seo get [/path|--path /path] | seo set --path /
|
|
555
569
|
frontpage articles list|content|create-section|create-article|set-draft|…
|
|
556
570
|
frontpage fonts [list|search] [--slot …] [--query …]
|
|
557
571
|
frontpage tokens set --group … --key … --value …
|
|
558
|
-
frontpage modules list|docs|request-setup|enable|disable|archive|config <key>
|
|
572
|
+
frontpage modules list|docs|request-setup|enable|disable|archive|config <key> # mailing list key: signups
|
|
559
573
|
frontpage extensions list|docs|enable|disable|config <key>
|
|
560
574
|
frontpage settings get|set
|
|
561
575
|
frontpage edit --path / --old "…" --new "…"
|
package/llms.txt
CHANGED
|
@@ -5,19 +5,21 @@
|
|
|
5
5
|
- Introspect: frontpage help | frontpage help --json | frontpage help <topic|command>
|
|
6
6
|
- Topics: quickstart, auth, sync, site-building, agent, content, history, isolation, exit-codes
|
|
7
7
|
- Site building: frontpage help site-building — Astro pages map, BaseLayout, tokens, no backends/fake forms
|
|
8
|
-
- Fonts: frontpage fonts [--slot] [--query]
|
|
8
|
+
- 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
9
|
- Module/extension docs: frontpage modules docs <key> | frontpage extensions docs <key>
|
|
10
10
|
- Search flags: --regex --case-sensitive --whole-word --files-only --context-lines --max-results
|
|
11
11
|
- Modules setup: frontpage modules request-setup <key> --reason "…" (owner status bar; CLI cannot show it)
|
|
12
12
|
- Analytics ops: frontpage analytics summary|query|funnel|timeseries|compare|sql (bare analytics = dashboard)
|
|
13
|
-
- 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
|
|
13
|
+
- 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
14
|
- Prompt attachments: frontpage prompt "…" --attach <file|/__media/…|https://…> [--asset-url https://…] (same media caps as media upload)
|
|
15
15
|
- Version: frontpage --version | -V | version
|
|
16
16
|
- SEO get: frontpage seo get [/path|--path /path]; search path filter: [path|--path <path>]; open [/path|--path /path]
|
|
17
|
+
- Analytics funnel: --steps JSON array or comma paths (/, /pricing); bad JSON → Invalid JSON for --steps
|
|
18
|
+
- Modules: unknown key errors list Known: …; mailing list module key is signups (not subscribe)
|
|
17
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
|
|
18
20
|
- Auth: mint fp_live_ key in Frontpage UI (Developers); frontpage login KEY or FRONTPAGE_API_KEY
|
|
19
21
|
- Site id: subdomain only (frontpage use <subdomain>). Not custom domains.
|
|
20
|
-
- Staging: https://<subdomain>.frontpage.host — push/prompt/edit update staging; no separate staging deploy step
|
|
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.
|
|
21
23
|
- Production: custom domain after frontpage publish — NEVER publish/unpublish unless the user explicitly asks
|
|
22
24
|
- 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
|
|
23
25
|
- Machine: global --json / --quiet; exit 0/2/3/4/5 (see help exit-codes)
|