frontpage-host 0.1.5 → 0.1.6
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 +3 -1
- package/dist/commands/analytics.js +166 -8
- package/dist/commands/modules.js +29 -1
- package/dist/completions/generate.js +8 -3
- package/dist/registry.js +28 -6
- package/llms.txt +2 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -108,10 +108,12 @@ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or
|
|
|
108
108
|
## Modules & articles
|
|
109
109
|
|
|
110
110
|
- Forms: `data-fp-form` markup in pages + `frontpage modules` for enable/config. No submit JS / honeypot / `action`.
|
|
111
|
+
- Wizard modules (payments/shop/ordering/instagram): after building gated markup, `frontpage modules request-setup <key> --reason "…"` once — owner finishes in the **editor status bar** (CLI cannot show it). Auto-active modules (forms/callbar/…) need markup, not request-setup.
|
|
111
112
|
- 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
113
|
- Migrate a folder into Articles: prefer `frontpage prompt "migrate … into articles"` — do not invent a mass delete+write migration.
|
|
113
114
|
- Extensions: `frontpage extensions`; if LOCKED on plan, do not fake charts/etc.
|
|
114
115
|
- Media in markup: `/__media/uploads/<slug>/…` from `frontpage media` (not raw blob URLs).
|
|
116
|
+
- Analytics: bare `frontpage analytics` = dashboard; also `summary|query|funnel|timeseries|compare|sql` for structured reads (SQL is guard + RLS site-scoped).
|
|
115
117
|
|
|
116
118
|
## Agent prompt loop
|
|
117
119
|
|
|
@@ -128,7 +130,7 @@ Mid-task continuation is automatic. There is **no** `runs continue` in v1.
|
|
|
128
130
|
These already return JSON bodies (wrap further with global `--json` where supported):
|
|
129
131
|
|
|
130
132
|
- `usage`, `analytics`, `seo`, `articles`, `settings`, `inbox` (default JSON)
|
|
131
|
-
- Mutate: `modules enable|disable|archive|config`, `extensions …`
|
|
133
|
+
- Mutate: `modules enable|disable|archive|config|request-setup`, `extensions …`
|
|
132
134
|
|
|
133
135
|
Click-edit (exact single occurrence), **not** file push:
|
|
134
136
|
|
|
@@ -1,17 +1,175 @@
|
|
|
1
1
|
import { resolveSlug } from "../site.js";
|
|
2
|
-
import { EXIT_OK, exitCodeForError, v1Get } from "../http/client.js";
|
|
2
|
+
import { EXIT_OK, EXIT_USAGE, exitCodeForError, v1Get, v1Post } 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 parseJsonFlag(raw, label) {
|
|
13
|
+
if (raw === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error(`${label} must be valid JSON`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function rangeFields(work) {
|
|
23
|
+
const days = takeFlag(work, "--days");
|
|
24
|
+
const from = takeFlag(work, "--from");
|
|
25
|
+
const to = takeFlag(work, "--to");
|
|
26
|
+
const out = {};
|
|
27
|
+
if (days)
|
|
28
|
+
out.days = Number(days);
|
|
29
|
+
if (from)
|
|
30
|
+
out.from = from;
|
|
31
|
+
if (to)
|
|
32
|
+
out.to = to;
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function printJson(data) {
|
|
36
|
+
console.log(JSON.stringify(data, null, 2));
|
|
37
|
+
}
|
|
3
38
|
export async function runAnalytics(args) {
|
|
4
39
|
try {
|
|
5
40
|
const { slug, rest } = await resolveSlug(args);
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
41
|
+
const work = [...rest];
|
|
42
|
+
const sub = work[0];
|
|
43
|
+
const ops = new Set(["summary", "query", "funnel", "timeseries", "compare", "sql", "dashboard"]);
|
|
44
|
+
// Bare `analytics` or `analytics dashboard` → legacy GET dashboard (compat).
|
|
45
|
+
if (!sub || sub === "dashboard" || !ops.has(sub) && sub.startsWith("--")) {
|
|
46
|
+
if (sub === "dashboard")
|
|
47
|
+
work.shift();
|
|
48
|
+
const days = takeFlag(work, "--days") ?? "7";
|
|
49
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/analytics?days=${encodeURIComponent(days)}`);
|
|
50
|
+
if (isJson()) {
|
|
51
|
+
emitOk("analytics", data);
|
|
52
|
+
return EXIT_OK;
|
|
53
|
+
}
|
|
54
|
+
printJson(data.dashboard);
|
|
55
|
+
return EXIT_OK;
|
|
56
|
+
}
|
|
57
|
+
if (!ops.has(sub)) {
|
|
58
|
+
console.error("Usage: frontpage analytics [dashboard|summary|query|funnel|timeseries|compare|sql] …");
|
|
59
|
+
return EXIT_USAGE;
|
|
60
|
+
}
|
|
61
|
+
work.shift();
|
|
62
|
+
const base = `/api/v1/sites/${encodeURIComponent(slug)}/analytics`;
|
|
63
|
+
if (sub === "summary") {
|
|
64
|
+
const filters = parseJsonFlag(takeFlag(work, "--filters"), "--filters");
|
|
65
|
+
const body = { op: "summary", ...rangeFields(work), ...(filters ? { filters } : {}) };
|
|
66
|
+
const data = await v1Post(base, body);
|
|
67
|
+
if (isJson())
|
|
68
|
+
emitOk("analytics", data);
|
|
69
|
+
else
|
|
70
|
+
printJson(data);
|
|
71
|
+
return EXIT_OK;
|
|
72
|
+
}
|
|
73
|
+
if (sub === "query") {
|
|
74
|
+
const dimension = takeFlag(work, "--dimension") ?? work.shift();
|
|
75
|
+
if (!dimension) {
|
|
76
|
+
console.error("Usage: frontpage analytics query --dimension <path|source|…> [--measure count] [--limit N] [--filters '{…}'] [--days N]");
|
|
77
|
+
return EXIT_USAGE;
|
|
78
|
+
}
|
|
79
|
+
const measure = takeFlag(work, "--measure");
|
|
80
|
+
const limit = takeFlag(work, "--limit");
|
|
81
|
+
const filters = parseJsonFlag(takeFlag(work, "--filters"), "--filters");
|
|
82
|
+
const body = {
|
|
83
|
+
op: "query",
|
|
84
|
+
dimension,
|
|
85
|
+
...rangeFields(work),
|
|
86
|
+
...(measure ? { measure } : {}),
|
|
87
|
+
...(limit ? { limit: Number(limit) } : {}),
|
|
88
|
+
...(filters ? { filters } : {}),
|
|
89
|
+
};
|
|
90
|
+
const data = await v1Post(base, body);
|
|
91
|
+
if (isJson())
|
|
92
|
+
emitOk("analytics", data);
|
|
93
|
+
else
|
|
94
|
+
printJson(data);
|
|
95
|
+
return EXIT_OK;
|
|
96
|
+
}
|
|
97
|
+
if (sub === "funnel") {
|
|
98
|
+
const stepsRaw = takeFlag(work, "--steps");
|
|
99
|
+
if (!stepsRaw) {
|
|
100
|
+
console.error(`Usage: frontpage analytics funnel --steps '[{"path":"/"},{"path":"/pricing"}]' [--days N]`);
|
|
101
|
+
return EXIT_USAGE;
|
|
102
|
+
}
|
|
103
|
+
const steps = parseJsonFlag(stepsRaw, "--steps");
|
|
104
|
+
const body = { op: "funnel", steps, ...rangeFields(work) };
|
|
105
|
+
const data = await v1Post(base, body);
|
|
106
|
+
if (isJson())
|
|
107
|
+
emitOk("analytics", data);
|
|
108
|
+
else
|
|
109
|
+
printJson(data);
|
|
110
|
+
return EXIT_OK;
|
|
111
|
+
}
|
|
112
|
+
if (sub === "timeseries") {
|
|
113
|
+
const metric = takeFlag(work, "--metric");
|
|
114
|
+
const interval = takeFlag(work, "--interval");
|
|
115
|
+
const filters = parseJsonFlag(takeFlag(work, "--filters"), "--filters");
|
|
116
|
+
const body = {
|
|
117
|
+
op: "timeseries",
|
|
118
|
+
...rangeFields(work),
|
|
119
|
+
...(metric ? { metric } : {}),
|
|
120
|
+
...(interval ? { interval } : {}),
|
|
121
|
+
...(filters ? { filters } : {}),
|
|
122
|
+
};
|
|
123
|
+
const data = await v1Post(base, body);
|
|
124
|
+
if (isJson())
|
|
125
|
+
emitOk("analytics", data);
|
|
126
|
+
else
|
|
127
|
+
printJson(data);
|
|
128
|
+
return EXIT_OK;
|
|
129
|
+
}
|
|
130
|
+
if (sub === "compare") {
|
|
131
|
+
const aRaw = takeFlag(work, "--a");
|
|
132
|
+
const bRaw = takeFlag(work, "--b");
|
|
133
|
+
if (!aRaw || !bRaw) {
|
|
134
|
+
console.error(`Usage: frontpage analytics compare --a '{"device":"mobile"}' --b '{"device":"desktop"}' [--metric uniqueVisitors] [--days N]`);
|
|
135
|
+
return EXIT_USAGE;
|
|
136
|
+
}
|
|
137
|
+
const a = parseJsonFlag(aRaw, "--a");
|
|
138
|
+
const b = parseJsonFlag(bRaw, "--b");
|
|
139
|
+
const metric = takeFlag(work, "--metric");
|
|
140
|
+
const body = {
|
|
141
|
+
op: "compare",
|
|
142
|
+
a,
|
|
143
|
+
b,
|
|
144
|
+
...rangeFields(work),
|
|
145
|
+
...(metric ? { metric } : {}),
|
|
146
|
+
};
|
|
147
|
+
const data = await v1Post(base, body);
|
|
148
|
+
if (isJson())
|
|
149
|
+
emitOk("analytics", data);
|
|
150
|
+
else
|
|
151
|
+
printJson(data);
|
|
152
|
+
return EXIT_OK;
|
|
153
|
+
}
|
|
154
|
+
if (sub === "sql") {
|
|
155
|
+
const sql = takeFlag(work, "--sql") ?? work.join(" ").trim();
|
|
156
|
+
if (!sql) {
|
|
157
|
+
console.error('Usage: frontpage analytics sql --sql "SELECT path, count(*) FROM analytics_events GROUP BY 1"');
|
|
158
|
+
return EXIT_USAGE;
|
|
159
|
+
}
|
|
160
|
+
const data = await v1Post(base, { op: "sql", sql });
|
|
161
|
+
if (isJson())
|
|
162
|
+
emitOk("analytics", data);
|
|
163
|
+
else
|
|
164
|
+
printJson(data);
|
|
165
|
+
return EXIT_OK;
|
|
166
|
+
}
|
|
167
|
+
console.error("Usage: frontpage analytics [dashboard|summary|query|funnel|timeseries|compare|sql] …");
|
|
168
|
+
return EXIT_USAGE;
|
|
13
169
|
}
|
|
14
170
|
catch (err) {
|
|
171
|
+
if (isJson())
|
|
172
|
+
return emitErr("analytics", err);
|
|
15
173
|
console.error(err instanceof Error ? err.message : String(err));
|
|
16
174
|
return exitCodeForError(err);
|
|
17
175
|
}
|
package/dist/commands/modules.js
CHANGED
|
@@ -36,6 +36,34 @@ export async function runModules(args) {
|
|
|
36
36
|
console.log(data.docs);
|
|
37
37
|
return EXIT_OK;
|
|
38
38
|
}
|
|
39
|
+
if (sub === "request-setup" || sub === "request_setup") {
|
|
40
|
+
const key = rest[1];
|
|
41
|
+
if (!key) {
|
|
42
|
+
console.error("Usage: frontpage modules request-setup <key> --reason \"…\" [--prefill '{…}'] [--instance <id>] [--site <slug>]");
|
|
43
|
+
return EXIT_USAGE;
|
|
44
|
+
}
|
|
45
|
+
const work = rest.slice(2);
|
|
46
|
+
const instanceId = takeFlag(work, "--instance");
|
|
47
|
+
const reason = takeFlag(work, "--reason");
|
|
48
|
+
const prefillRaw = takeFlag(work, "--prefill");
|
|
49
|
+
if (!reason) {
|
|
50
|
+
console.error("Usage: frontpage modules request-setup <key> --reason \"…\" [--prefill '{…}']");
|
|
51
|
+
return EXIT_USAGE;
|
|
52
|
+
}
|
|
53
|
+
let prefill;
|
|
54
|
+
if (prefillRaw) {
|
|
55
|
+
try {
|
|
56
|
+
prefill = JSON.parse(prefillRaw);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
console.error("--prefill must be valid JSON");
|
|
60
|
+
return EXIT_USAGE;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const data = await v1Post(`/api/v1/sites/${encodeURIComponent(slug)}/modules/${encodeURIComponent(key)}`, { action: "request-setup", reason, prefill, instanceId });
|
|
64
|
+
console.log(JSON.stringify(data, null, 2));
|
|
65
|
+
return EXIT_OK;
|
|
66
|
+
}
|
|
39
67
|
if (sub === "enable" || sub === "disable" || sub === "archive" || sub === "config") {
|
|
40
68
|
const key = rest[1];
|
|
41
69
|
if (!key) {
|
|
@@ -63,7 +91,7 @@ export async function runModules(args) {
|
|
|
63
91
|
console.log(JSON.stringify(data, null, 2));
|
|
64
92
|
return EXIT_OK;
|
|
65
93
|
}
|
|
66
|
-
console.error("Usage: frontpage modules [list|docs|enable|disable|archive|config] …");
|
|
94
|
+
console.error("Usage: frontpage modules [list|docs|request-setup|enable|disable|archive|config] …");
|
|
67
95
|
return EXIT_USAGE;
|
|
68
96
|
}
|
|
69
97
|
catch (err) {
|
|
@@ -23,7 +23,8 @@ _frontpage_completions() {
|
|
|
23
23
|
tokens) COMPREPLY=( $(compgen -W "set" -- "\${cur}") ) ;;
|
|
24
24
|
seo|settings) COMPREPLY=( $(compgen -W "get set" -- "\${cur}") ) ;;
|
|
25
25
|
articles) COMPREPLY=( $(compgen -W "list content create-section create-article set-draft" -- "\${cur}") ) ;;
|
|
26
|
-
|
|
26
|
+
analytics) COMPREPLY=( $(compgen -W "dashboard summary query funnel timeseries compare sql" -- "\${cur}") ) ;;
|
|
27
|
+
modules) COMPREPLY=( $(compgen -W "list docs request-setup enable disable archive config" -- "\${cur}") ) ;;
|
|
27
28
|
extensions) COMPREPLY=( $(compgen -W "list docs enable disable config" -- "\${cur}") ) ;;
|
|
28
29
|
*) COMPREPLY=( $(compgen -W "--site --help --json --quiet --yes" -- "\${cur}") ) ;;
|
|
29
30
|
esac
|
|
@@ -50,7 +51,9 @@ _frontpage() {
|
|
|
50
51
|
fonts) _values 'subcommand' list search ;;
|
|
51
52
|
tokens) _values 'subcommand' set ;;
|
|
52
53
|
seo|settings) _values 'subcommand' get set ;;
|
|
53
|
-
|
|
54
|
+
analytics) _values 'subcommand' dashboard summary query funnel timeseries compare sql ;;
|
|
55
|
+
modules) _values 'subcommand' list docs request-setup enable disable archive config ;;
|
|
56
|
+
extensions) _values 'subcommand' list docs enable disable config ;;
|
|
54
57
|
esac
|
|
55
58
|
;;
|
|
56
59
|
esac
|
|
@@ -70,7 +73,9 @@ export function generateFishCompletion() {
|
|
|
70
73
|
`complete -c frontpage -n "__fish_seen_subcommand_from fonts" -a "list search"`,
|
|
71
74
|
`complete -c frontpage -n "__fish_seen_subcommand_from tokens" -a "set"`,
|
|
72
75
|
`complete -c frontpage -n "__fish_seen_subcommand_from seo settings" -a "get set"`,
|
|
73
|
-
`complete -c frontpage -n "__fish_seen_subcommand_from
|
|
76
|
+
`complete -c frontpage -n "__fish_seen_subcommand_from analytics" -a "dashboard summary query funnel timeseries compare sql"`,
|
|
77
|
+
`complete -c frontpage -n "__fish_seen_subcommand_from modules" -a "list docs request-setup enable disable archive config"`,
|
|
78
|
+
`complete -c frontpage -n "__fish_seen_subcommand_from extensions" -a "list docs enable disable config"`,
|
|
74
79
|
`complete -c frontpage -l site -d "Site subdomain slug"`,
|
|
75
80
|
];
|
|
76
81
|
return `${lines.join("\n")}\n`;
|
package/dist/registry.js
CHANGED
|
@@ -246,9 +246,22 @@ export const COMMANDS = [
|
|
|
246
246
|
},
|
|
247
247
|
{
|
|
248
248
|
name: "analytics",
|
|
249
|
-
summary: "
|
|
250
|
-
usage: "frontpage analytics [
|
|
249
|
+
summary: "Analytics dashboard or structured query ops",
|
|
250
|
+
usage: "frontpage analytics [dashboard|summary|query|funnel|timeseries|compare|sql] … [--site <slug>] [--json]",
|
|
251
251
|
topics: ["agent"],
|
|
252
|
+
examples: [
|
|
253
|
+
"frontpage analytics --days 7",
|
|
254
|
+
"frontpage analytics summary --days 30",
|
|
255
|
+
"frontpage analytics query --dimension path --measure count --limit 10",
|
|
256
|
+
`frontpage analytics funnel --steps '[{"path":"/"},{"path":"/pricing"}]'`,
|
|
257
|
+
"frontpage analytics timeseries --metric pageviews --interval day --days 14",
|
|
258
|
+
`frontpage analytics compare --a '{"device":"mobile"}' --b '{"device":"desktop"}'`,
|
|
259
|
+
'frontpage analytics sql --sql "SELECT path, count(*)::int AS n FROM analytics_events GROUP BY 1 ORDER BY n DESC LIMIT 10"',
|
|
260
|
+
],
|
|
261
|
+
flags: [
|
|
262
|
+
"Bare analytics / dashboard = legacy GET dashboard (compat)",
|
|
263
|
+
"Ops use POST …/analytics with allowlisted dimensions/measures; SQL is guard-validated + RLS",
|
|
264
|
+
],
|
|
252
265
|
},
|
|
253
266
|
{
|
|
254
267
|
name: "pages",
|
|
@@ -259,10 +272,17 @@ export const COMMANDS = [
|
|
|
259
272
|
{
|
|
260
273
|
name: "modules",
|
|
261
274
|
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>]",
|
|
275
|
+
usage: "frontpage modules [list|docs|request-setup|enable|disable|archive|config] <key> [--instance <id>] [--config '{…}'] [--site <slug>]",
|
|
263
276
|
topics: ["agent", "content"],
|
|
264
|
-
examples: [
|
|
265
|
-
|
|
277
|
+
examples: [
|
|
278
|
+
"frontpage modules docs forms",
|
|
279
|
+
"frontpage modules list",
|
|
280
|
+
'frontpage modules request-setup payments --reason "You asked for a $50 deposit button — Stripe setup makes it live"',
|
|
281
|
+
],
|
|
282
|
+
dangers: [
|
|
283
|
+
"Secret values are never returned — masks only",
|
|
284
|
+
"request-setup surfaces an owner prompt in the editor status bar (CLI cannot show it)",
|
|
285
|
+
],
|
|
266
286
|
},
|
|
267
287
|
{
|
|
268
288
|
name: "extensions",
|
|
@@ -456,8 +476,10 @@ export const TOPICS = {
|
|
|
456
476
|
frontpage unpublish --yes # admin only; never unless asked; --yes when non-TTY
|
|
457
477
|
frontpage usage
|
|
458
478
|
frontpage analytics [--days 7]
|
|
479
|
+
frontpage analytics summary|query|funnel|timeseries|compare|sql …
|
|
459
480
|
frontpage pages
|
|
460
481
|
frontpage modules
|
|
482
|
+
frontpage modules request-setup <key> --reason "…"
|
|
461
483
|
frontpage extensions
|
|
462
484
|
|
|
463
485
|
Mid-task continuation is automatic — there is no runs continue.
|
|
@@ -470,7 +492,7 @@ export const TOPICS = {
|
|
|
470
492
|
frontpage articles list|content|create-section|create-article|set-draft|…
|
|
471
493
|
frontpage fonts [list|search] [--slot …] [--query …]
|
|
472
494
|
frontpage tokens set --group … --key … --value …
|
|
473
|
-
frontpage modules list|docs|enable|disable|archive|config <key>
|
|
495
|
+
frontpage modules list|docs|request-setup|enable|disable|archive|config <key>
|
|
474
496
|
frontpage extensions list|docs|enable|disable|config <key>
|
|
475
497
|
frontpage settings get|set
|
|
476
498
|
frontpage edit --path / --old "…" --new "…"
|
package/llms.txt
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
- Fonts: frontpage fonts [--slot] [--query] — use exact value; tokens: frontpage tokens set --group --key --value (CAS)
|
|
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
|
+
- Modules setup: frontpage modules request-setup <key> --reason "…" (owner status bar; CLI cannot show it)
|
|
12
|
+
- Analytics ops: frontpage analytics summary|query|funnel|timeseries|compare|sql (bare analytics = dashboard)
|
|
11
13
|
- Auth: mint fp_live_ key in Frontpage UI (Developers); frontpage login KEY or FRONTPAGE_API_KEY
|
|
12
14
|
- Site id: subdomain only (frontpage use <subdomain>). Not custom domains.
|
|
13
15
|
- Staging: https://<subdomain>.frontpage.host — push/prompt/edit update staging; no separate staging deploy step for agents
|