frontpage-host 0.1.13 → 0.1.15

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
@@ -109,7 +109,7 @@ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or
109
109
  ## Modules & articles
110
110
 
111
111
  - Forms: `data-fp-form` markup in pages + `frontpage modules` for enable/config. No submit JS / honeypot / `action`.
112
- - Wizard / connect-once modules (payments/shop/ordering/google_reviews): 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. `frontpage modules <key>` prints that key's CLI usage (exit 2). Index: https://frontpage.host/developers/modules
112
+ - Wizard / connect-once modules (payments/shop/ordering): `frontpage modules <key> connect` opens the browser (Stripe or Square). Finish there, then `status`. `--no-open` for CI. `request-setup` remains valid when you want the **editor amber bar** instead. payments and shop share one Stripe account. Auto-active modules (forms/callbar/…) need markup, not request-setup. Typed settings: `frontpage modules callbar|forms|booking|signups get|set` (forms set needs `--instance`; do not target `default`). `frontpage modules <key>` prints that key's CLI usage (exit 2). Index: https://frontpage.host/developers/modules · payments/shop/ordering pages under /developers/modules/<key>
113
113
  - Google reviews: platform nodes only (`data-fp-google-reviews` / `data-fp-google-ask` + required `data-fp-requires="google_reviews"`). `data-layout` is `feed` (default), `badge`, or `link`. Never invent stars, quotes, a 1–10, or a Maps URL. Do not pick a Place ID via Autocomplete and do not write `placeId` through `modules config`. Owner-supplied Place ID / Maps URL: `frontpage modules google_reviews connect --place-id …` or `--maps-url …` (server resolves + seeds). Curation: `list-reviews`, `hide --id`, `show --id`, `set-stars`, `set-sort`, `set-weight`, `reset-weights`, `refresh`, `disconnect`. Usage: `frontpage modules google_reviews`. Docs: https://frontpage.host/developers/modules/google-reviews. Ask-widget scores: `frontpage inbox google_reviews`.
114
114
  - 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`.
115
115
  - Migrate a folder into Articles: prefer `frontpage prompt "migrate … into articles"` — do not invent a mass delete+write migration.
@@ -4,6 +4,8 @@ import { takeFlag } from "../argv.js";
4
4
  import { emitUsage } from "../runtime.js";
5
5
  import { formatModuleUsage, formatModulesIndex, isCliModuleAction, isDocumentedModuleKey, isGenericModuleVerb, } from "../modules/commands.js";
6
6
  import { runGoogleReviews } from "../modules/google_reviews.js";
7
+ import { isTypedConfigCliKey, runTypedConfig } from "../modules/typed_config.js";
8
+ import { isOauthCliKey, runOauthHandoff } from "../modules/oauth.js";
7
9
  /** Drop --site so we can print local usage without resolving a checkout. */
8
10
  function positionalArgs(args) {
9
11
  const rest = [];
@@ -38,7 +40,16 @@ export async function runModules(args) {
38
40
  }
39
41
  if (isCliModuleAction(peekedKey, peekedAction)) {
40
42
  const { slug } = await resolveSlug(args);
41
- return await runGoogleReviews(slug, peekedAction, positionalArgs(args).slice(2));
43
+ const extraFlags = positionalArgs(args).slice(2);
44
+ if (peekedKey === "google_reviews") {
45
+ return await runGoogleReviews(slug, peekedAction, extraFlags);
46
+ }
47
+ if (isTypedConfigCliKey(peekedKey)) {
48
+ return await runTypedConfig(peekedKey, slug, peekedAction, extraFlags);
49
+ }
50
+ if (isOauthCliKey(peekedKey)) {
51
+ return await runOauthHandoff(peekedKey, slug, peekedAction, extraFlags);
52
+ }
42
53
  }
43
54
  if (!isGenericModuleVerb(peekedAction)) {
44
55
  return emitUsage("modules", `${formatModuleUsage(peekedKey)}\n\nUnknown action "${peekedAction}". Not in this CLI yet — use Modules or the editor.`);
@@ -27,6 +27,12 @@ _frontpage_completions() {
27
27
  modules)
28
28
  if [[ \${COMP_CWORD} -ge 3 && \${COMP_WORDS[2]} == google_reviews ]]; then
29
29
  COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config connect resolve status list-reviews hide show set-stars set-sort set-weight reset-weights write-review-url refresh disconnect" -- "\${cur}") )
30
+ elif [[ \${COMP_CWORD} -ge 3 && (\${COMP_WORDS[2]} == callbar || \${COMP_WORDS[2]} == forms || \${COMP_WORDS[2]} == booking || \${COMP_WORDS[2]} == signups) ]]; then
31
+ COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config get set" -- "\${cur}") )
32
+ elif [[ \${COMP_CWORD} -ge 3 && (\${COMP_WORDS[2]} == payments || \${COMP_WORDS[2]} == shop) ]]; then
33
+ COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config status connect disconnect" -- "\${cur}") )
34
+ elif [[ \${COMP_CWORD} -ge 3 && \${COMP_WORDS[2]} == ordering ]]; then
35
+ COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config status connect disconnect sync" -- "\${cur}") )
30
36
  else
31
37
  COMPREPLY=( $(compgen -W "list docs request-setup enable disable archive config callbar forms booking signups payments shop ordering articles google_reviews" -- "\${cur}") )
32
38
  fi
@@ -62,6 +68,12 @@ _frontpage() {
62
68
  modules)
63
69
  if [[ $words[2] == google_reviews ]]; then
64
70
  _values 'subcommand' docs request-setup enable disable archive config connect resolve status list-reviews hide show set-stars set-sort set-weight reset-weights write-review-url refresh disconnect
71
+ elif [[ $words[2] == callbar || $words[2] == forms || $words[2] == booking || $words[2] == signups ]]; then
72
+ _values 'subcommand' docs request-setup enable disable archive config get set
73
+ elif [[ $words[2] == payments || $words[2] == shop ]]; then
74
+ _values 'subcommand' docs request-setup enable disable archive config status connect disconnect
75
+ elif [[ $words[2] == ordering ]]; then
76
+ _values 'subcommand' docs request-setup enable disable archive config status connect disconnect sync
65
77
  else
66
78
  _values 'subcommand' list docs request-setup enable disable archive config callbar forms booking signups payments shop ordering articles google_reviews
67
79
  fi
@@ -89,6 +101,9 @@ export function generateFishCompletion() {
89
101
  `complete -c frontpage -n "__fish_seen_subcommand_from analytics" -a "dashboard summary query funnel timeseries compare sql"`,
90
102
  `complete -c frontpage -n "__fish_seen_subcommand_from modules" -a "list docs request-setup enable disable archive config callbar forms booking signups payments shop ordering articles google_reviews"`,
91
103
  `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from google_reviews" -a "connect resolve status list-reviews hide show set-stars set-sort set-weight reset-weights write-review-url refresh disconnect"`,
104
+ `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from callbar forms booking signups" -a "get set"`,
105
+ `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from payments shop" -a "status connect disconnect"`,
106
+ `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from ordering" -a "status connect disconnect sync"`,
92
107
  `complete -c frontpage -n "__fish_seen_subcommand_from extensions" -a "list docs enable disable config"`,
93
108
  `complete -c frontpage -l site -d "Site subdomain slug"`,
94
109
  ];
@@ -17,6 +17,9 @@ export type ModuleCommandDef = {
17
17
  };
18
18
  export declare const MODULE_COMMANDS: readonly ModuleCommandDef[];
19
19
  export declare const GOOGLE_REVIEWS_CLI_ACTIONS: readonly ["connect", "resolve", "status", "list-reviews", "hide", "show", "set-stars", "set-sort", "set-weight", "reset-weights", "write-review-url", "refresh", "disconnect"];
20
+ export declare const TYPED_CONFIG_CLI_ACTIONS: readonly ["get", "set"];
21
+ export declare const STRIPE_OAUTH_CLI_ACTIONS: readonly ["status", "connect", "disconnect"];
22
+ export declare const ORDERING_OAUTH_CLI_ACTIONS: readonly ["status", "connect", "disconnect", "sync"];
20
23
  export declare function isCliModuleAction(key: DocumentedModuleKey, action: string): boolean;
21
24
  export declare function isDocumentedModuleKey(raw: string): raw is DocumentedModuleKey;
22
25
  export declare function isGenericModuleVerb(raw: string): boolean;
@@ -37,67 +37,86 @@ export const MODULE_COMMANDS = [
37
37
  {
38
38
  key: "callbar",
39
39
  name: "Call bar",
40
- summary: "Phone / SMS bar. Generic enable and JSON config only in this CLI.",
40
+ summary: "Phone / SMS bar. Typed get/set for phone, sms, and label.",
41
41
  genericActions: GENERIC_ACTIONS,
42
- extraActions: [],
43
- notYet: ["get", "set --phone"],
42
+ extraActions: [
43
+ "get",
44
+ "set --phone +1… [--sms yes|no] [--label \"Call\"]",
45
+ ],
46
+ notYet: [],
44
47
  examples: [
45
48
  "frontpage modules callbar",
49
+ "frontpage modules callbar get",
50
+ "frontpage modules callbar set --phone +14085551234 --sms no",
46
51
  "frontpage modules callbar docs",
47
- "frontpage modules callbar enable",
48
52
  `frontpage modules config callbar --config '{"phone":"+1…"}'`,
49
53
  ],
50
54
  notes: [
51
- "Typed get/set flags are not in this CLI yet — use Modules or `modules config`.",
55
+ "Typed flags write the same whitelist as `modules config`. Generic JSON config still works.",
52
56
  ],
53
57
  },
54
58
  {
55
59
  key: "forms",
56
60
  name: "Forms",
57
- summary: "Lead forms. Multi-instance; use --instance for one form.",
61
+ summary: "Lead forms. Get lists instances; set requires --instance.",
58
62
  genericActions: GENERIC_ACTIONS,
59
- extraActions: [],
60
- notYet: ["get", "set --notify-email"],
63
+ extraActions: [
64
+ "get [--instance <slug>]",
65
+ "set --instance <slug> [--notify-email] [--confirm yes|no]",
66
+ ],
67
+ notYet: [],
61
68
  examples: [
62
69
  "frontpage modules forms",
70
+ "frontpage modules forms get",
71
+ "frontpage modules forms get --instance lead-gen",
72
+ "frontpage modules forms set --instance lead-gen --notify-email hi@example.com",
63
73
  "frontpage modules forms docs",
64
- "frontpage modules forms enable --instance lead-gen",
65
74
  `frontpage modules config forms --instance lead-gen --config '{"notifyEmail":"hi@example.com"}'`,
66
75
  ],
67
76
  notes: [
68
- "Typed get/set flags are not in this CLI yet — use Modules or `modules config`.",
77
+ "Without --instance, get lists each form (id, status, notifyEmail). Do not target `default`.",
78
+ "Confirmation emails require a paid plan (same as Modules).",
69
79
  ],
70
80
  },
71
81
  {
72
82
  key: "booking",
73
83
  name: "Booking",
74
- summary: "Booking requests. Generic enable and JSON config only in this CLI.",
84
+ summary: "Booking requests. Typed get/set for notify email and hours.",
75
85
  genericActions: GENERIC_ACTIONS,
76
- extraActions: [],
77
- notYet: ["get", "set --notify-email"],
86
+ extraActions: [
87
+ "get",
88
+ "set [--notify-email] [--hours \"…\"]",
89
+ ],
90
+ notYet: [],
78
91
  examples: [
79
92
  "frontpage modules booking",
93
+ "frontpage modules booking get",
94
+ "frontpage modules booking set --notify-email book@example.com --hours \"Mon–Fri 9–5\"",
80
95
  "frontpage modules booking docs",
81
- "frontpage modules booking enable",
82
96
  ],
83
97
  notes: [
84
- "Typed get/set flags are not in this CLI yet — use Modules or `modules config`.",
98
+ "Typed flags write the same whitelist as `modules config`. Generic JSON config still works.",
85
99
  ],
86
100
  },
87
101
  {
88
102
  key: "signups",
89
103
  name: "Mailing list",
90
- summary: "Email signups. Generic enable and JSON config only in this CLI.",
104
+ summary: "Email signups. Typed get/set for notify email and confirmations.",
91
105
  genericActions: GENERIC_ACTIONS,
92
- extraActions: [],
93
- notYet: ["get", "set --notify-email"],
106
+ extraActions: [
107
+ "get",
108
+ "set [--notify-email] [--confirm yes|no]",
109
+ ],
110
+ notYet: [],
94
111
  examples: [
95
112
  "frontpage modules signups",
113
+ "frontpage modules signups get",
114
+ "frontpage modules signups set --notify-email hello@example.com --confirm no",
96
115
  "frontpage modules signups docs",
97
- "frontpage modules signups enable",
98
116
  ],
99
117
  notes: [
100
- "Typed get/set flags are not in this CLI yet — use Modules or `modules config`.",
118
+ "Confirmation emails require a paid plan (same as Modules).",
119
+ "Typed flags write the same whitelist as `modules config`. Generic JSON config still works.",
101
120
  ],
102
121
  },
103
122
  {
@@ -105,16 +124,24 @@ export const MODULE_COMMANDS = [
105
124
  name: "Payments",
106
125
  summary: "Stripe payments. Connect finishes in the browser, not the terminal.",
107
126
  genericActions: GENERIC_ACTIONS,
108
- extraActions: [],
109
- notYet: ["connect", "disconnect", "status"],
127
+ extraActions: [
128
+ "status",
129
+ "connect [--no-open]",
130
+ "disconnect [--yes]",
131
+ ],
132
+ notYet: [],
110
133
  examples: [
111
134
  "frontpage modules payments",
135
+ "frontpage modules payments status",
136
+ "frontpage modules payments connect --no-open",
137
+ "frontpage modules payments disconnect --yes",
112
138
  "frontpage modules payments docs",
113
139
  'frontpage modules payments request-setup --reason "Deposit button needs Stripe"',
114
140
  ],
115
141
  notes: [
116
- "OAuth cannot finish in this CLI. Use request-setup (editor amber bar) or the Modules hub.",
117
- "payments and shop share one Stripe account.",
142
+ "Connect opens Stripe. Finish in the browser, then re-run status. This CLI cannot complete OAuth.",
143
+ "payments and shop share one Stripe account — disconnect says so.",
144
+ "request-setup still lights the editor amber bar when you want the owner to finish there.",
118
145
  ],
119
146
  },
120
147
  {
@@ -122,16 +149,25 @@ export const MODULE_COMMANDS = [
122
149
  name: "Online store",
123
150
  summary: "Stripe shop. Shares the payments Stripe account.",
124
151
  genericActions: GENERIC_ACTIONS,
125
- extraActions: [],
126
- notYet: ["connect", "disconnect", "status"],
152
+ extraActions: [
153
+ "status",
154
+ "connect [--no-open]",
155
+ "disconnect [--yes]",
156
+ ],
157
+ notYet: [],
127
158
  examples: [
128
159
  "frontpage modules shop",
160
+ "frontpage modules shop status",
161
+ "frontpage modules shop connect --no-open",
162
+ "frontpage modules shop disconnect --yes",
129
163
  "frontpage modules shop docs",
130
164
  'frontpage modules shop request-setup --reason "You asked for a store — Stripe setup makes checkout live"',
131
165
  ],
132
166
  notes: [
133
- "OAuth cannot finish in this CLI. Use request-setup (editor amber bar) or the Modules hub.",
134
- "payments and shop share one Stripe account.",
167
+ "Connect opens Stripe. Finish in the browser, then re-run status. This CLI cannot complete OAuth.",
168
+ "payments and shop share one Stripe account — disconnect says so.",
169
+ "Shop still needs the shipping step in Modules if you have not set it.",
170
+ "request-setup still lights the editor amber bar when you want the owner to finish there.",
135
171
  ],
136
172
  },
137
173
  {
@@ -139,15 +175,27 @@ export const MODULE_COMMANDS = [
139
175
  name: "Online ordering",
140
176
  summary: "Square ordering. Connect finishes in the browser, not the terminal.",
141
177
  genericActions: GENERIC_ACTIONS,
142
- extraActions: [],
143
- notYet: ["connect", "disconnect", "sync", "status"],
178
+ extraActions: [
179
+ "status",
180
+ "connect [--no-open]",
181
+ "disconnect [--yes]",
182
+ "sync",
183
+ ],
184
+ notYet: ["fulfillment get|set"],
144
185
  examples: [
145
186
  "frontpage modules ordering",
187
+ "frontpage modules ordering status",
188
+ "frontpage modules ordering connect --no-open",
189
+ "frontpage modules ordering sync",
190
+ "frontpage modules ordering disconnect --yes",
146
191
  "frontpage modules ordering docs",
147
192
  'frontpage modules ordering request-setup --reason "You asked for online ordering — Square setup makes it live"',
148
193
  ],
149
194
  notes: [
150
- "OAuth cannot finish in this CLI. Use request-setup (editor amber bar) or the Modules hub.",
195
+ "Connect opens Square. Finish in the browser, then re-run status. This CLI cannot complete OAuth.",
196
+ "sync refreshes the Square catalog (same as the hub Sync button).",
197
+ "request-setup still lights the editor amber bar when you want the owner to finish there.",
198
+ "Fulfillment (pickup/delivery) is not in this CLI yet — use Modules or the editor.",
151
199
  ],
152
200
  },
153
201
  {
@@ -220,10 +268,22 @@ export const GOOGLE_REVIEWS_CLI_ACTIONS = [
220
268
  "refresh",
221
269
  "disconnect",
222
270
  ];
271
+ export const TYPED_CONFIG_CLI_ACTIONS = ["get", "set"];
272
+ export const STRIPE_OAUTH_CLI_ACTIONS = ["status", "connect", "disconnect"];
273
+ export const ORDERING_OAUTH_CLI_ACTIONS = ["status", "connect", "disconnect", "sync"];
223
274
  export function isCliModuleAction(key, action) {
224
275
  if (key === "google_reviews") {
225
276
  return GOOGLE_REVIEWS_CLI_ACTIONS.includes(action);
226
277
  }
278
+ if (key === "callbar" || key === "forms" || key === "booking" || key === "signups") {
279
+ return TYPED_CONFIG_CLI_ACTIONS.includes(action);
280
+ }
281
+ if (key === "payments" || key === "shop") {
282
+ return STRIPE_OAUTH_CLI_ACTIONS.includes(action);
283
+ }
284
+ if (key === "ordering") {
285
+ return ORDERING_OAUTH_CLI_ACTIONS.includes(action);
286
+ }
227
287
  return false;
228
288
  }
229
289
  export function isDocumentedModuleKey(raw) {
@@ -0,0 +1,4 @@
1
+ export declare const OAUTH_CLI_KEYS: readonly ["payments", "shop", "ordering"];
2
+ export type OauthCliKey = (typeof OAUTH_CLI_KEYS)[number];
3
+ export declare function isOauthCliKey(key: string): key is OauthCliKey;
4
+ export declare function runOauthHandoff(key: OauthCliKey, slug: string, action: string, flags: string[]): Promise<number>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * §CLI-MOD-OAUTH — payments / shop / ordering connect, status, disconnect, sync.
3
+ */
4
+ import { spawn } from "node:child_process";
5
+ import { assertNoUnknownFlags, hasFlag } from "../argv.js";
6
+ import { apiBase, EXIT_OK, v1Get, v1Post } from "../http/client.js";
7
+ import { emitOk, emitUsage, isJson, requireYesOrTty } from "../runtime.js";
8
+ import { formatModuleUsage } from "./commands.js";
9
+ export const OAUTH_CLI_KEYS = ["payments", "shop", "ordering"];
10
+ export function isOauthCliKey(key) {
11
+ return OAUTH_CLI_KEYS.includes(key);
12
+ }
13
+ const FINISH = "Finish in the browser, then re-run status. This CLI cannot complete OAuth.";
14
+ function pathFor(slug, key) {
15
+ return `/api/v1/sites/${encodeURIComponent(slug)}/modules/${encodeURIComponent(key)}`;
16
+ }
17
+ function resolveOpenUrl(raw) {
18
+ if (typeof raw !== "string" || !raw.trim())
19
+ return null;
20
+ const trimmed = raw.trim();
21
+ if (/^https?:\/\//i.test(trimmed))
22
+ return trimmed;
23
+ if (trimmed.startsWith("/"))
24
+ return `${apiBase()}${trimmed}`;
25
+ return null;
26
+ }
27
+ function openBrowser(url) {
28
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
29
+ const openerArgs = process.platform === "win32" ? ["/c", "start", "", url] : [url];
30
+ spawn(opener, openerArgs, { detached: true, stdio: "ignore" }).unref();
31
+ }
32
+ function printStatus(key, data) {
33
+ const connection = (data.connection ?? {});
34
+ const display = connection.display ?? {};
35
+ console.log(`connected ${connection.ready ? "yes" : "no"}`);
36
+ if (key === "ordering") {
37
+ console.log(`merchant ${display.merchantName ?? "—"}`);
38
+ if (display.locationCount != null)
39
+ console.log(`locations ${display.locationCount}`);
40
+ if (display.cardProcessing != null)
41
+ console.log(`cards ${display.cardProcessing ? "yes" : "no"}`);
42
+ if (display.menuSyncedAt)
43
+ console.log(`menu sync ${display.menuSyncedAt}`);
44
+ }
45
+ else {
46
+ console.log(`charges ${display.chargesEnabled == null ? "—" : display.chargesEnabled ? "yes" : "no"}`);
47
+ console.log(`account ${display.accountPresent ? "present" : "none"}`);
48
+ if (typeof data.sharedStripe === "string")
49
+ console.log(`note ${data.sharedStripe}`);
50
+ }
51
+ if (typeof data.connectUrl === "string")
52
+ console.log(`connect ${data.connectUrl}`);
53
+ }
54
+ export async function runOauthHandoff(key, slug, action, flags) {
55
+ const work = [...flags];
56
+ const usage = formatModuleUsage(key);
57
+ if (action === "status") {
58
+ assertNoUnknownFlags(work, "modules");
59
+ const data = await v1Get(pathFor(slug, key));
60
+ if (isJson()) {
61
+ emitOk("modules", data);
62
+ return EXIT_OK;
63
+ }
64
+ printStatus(key, data);
65
+ return EXIT_OK;
66
+ }
67
+ if (action === "connect") {
68
+ const noOpen = hasFlag(work, "--no-open");
69
+ assertNoUnknownFlags(work, "modules");
70
+ const data = await v1Post(pathFor(slug, key), { action: "connect" });
71
+ if (isJson()) {
72
+ emitOk("modules", data);
73
+ }
74
+ else {
75
+ const url = resolveOpenUrl(data.connectUrl);
76
+ if (url)
77
+ console.log(url);
78
+ console.log(typeof data.message === "string" ? data.message : FINISH);
79
+ if (typeof data.warning === "string")
80
+ console.log(data.warning);
81
+ }
82
+ const url = resolveOpenUrl(data.connectUrl);
83
+ if (url && !noOpen)
84
+ openBrowser(url);
85
+ return EXIT_OK;
86
+ }
87
+ if (action === "disconnect") {
88
+ assertNoUnknownFlags(work, "modules");
89
+ const label = key === "ordering"
90
+ ? "disconnect Square"
91
+ : key === "payments"
92
+ ? "disconnect payments (shared Stripe account with the store)"
93
+ : "disconnect the store (shared Stripe account with payments)";
94
+ const refuse = requireYesOrTty(label);
95
+ if (refuse)
96
+ return emitUsage("modules", refuse);
97
+ const data = await v1Post(pathFor(slug, key), { action: "disconnect" });
98
+ if (isJson()) {
99
+ emitOk("modules", data);
100
+ return EXIT_OK;
101
+ }
102
+ console.log(typeof data.message === "string" ? data.message : "Disconnected.");
103
+ if (typeof data.warning === "string" && data.warning !== data.message) {
104
+ console.log(data.warning);
105
+ }
106
+ return EXIT_OK;
107
+ }
108
+ if (action === "sync") {
109
+ if (key !== "ordering") {
110
+ return emitUsage("modules", `sync is only available for ordering.\n\n${usage}`);
111
+ }
112
+ assertNoUnknownFlags(work, "modules");
113
+ const data = await v1Post(pathFor(slug, key), { action: "sync" });
114
+ if (isJson()) {
115
+ emitOk("modules", data);
116
+ return EXIT_OK;
117
+ }
118
+ console.log(`Synced — ${data.itemCount ?? 0} items, ${data.locationCount ?? 0} locations.`);
119
+ return EXIT_OK;
120
+ }
121
+ return emitUsage("modules", `${usage}\n\nUnknown action "${action}".`);
122
+ }
@@ -0,0 +1,4 @@
1
+ export declare const TYPED_CONFIG_CLI_KEYS: readonly ["callbar", "forms", "booking", "signups"];
2
+ export type TypedConfigCliKey = (typeof TYPED_CONFIG_CLI_KEYS)[number];
3
+ export declare function isTypedConfigCliKey(key: string): key is TypedConfigCliKey;
4
+ export declare function runTypedConfig(key: TypedConfigCliKey, slug: string, action: string, flags: string[]): Promise<number>;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * §CLI-MOD-CFG — typed get/set for callbar, forms, booking, signups.
3
+ */
4
+ import { assertNoUnknownFlags, takeFlag } from "../argv.js";
5
+ import { EXIT_OK, v1Get, v1Post } from "../http/client.js";
6
+ import { emitOk, emitUsage, isJson } from "../runtime.js";
7
+ import { formatModuleUsage } from "./commands.js";
8
+ export const TYPED_CONFIG_CLI_KEYS = ["callbar", "forms", "booking", "signups"];
9
+ export function isTypedConfigCliKey(key) {
10
+ return TYPED_CONFIG_CLI_KEYS.includes(key);
11
+ }
12
+ function pathFor(slug, key) {
13
+ return `/api/v1/sites/${encodeURIComponent(slug)}/modules/${encodeURIComponent(key)}`;
14
+ }
15
+ function dash(v) {
16
+ if (v == null || v === "")
17
+ return "—";
18
+ return String(v);
19
+ }
20
+ function yesNo(v) {
21
+ if (v === true || v === "yes" || v === "true")
22
+ return "yes";
23
+ if (v === false || v === "no" || v === "false")
24
+ return "no";
25
+ return "—";
26
+ }
27
+ function printLine(label, value) {
28
+ console.log(`${label.padEnd(10)} ${value}`);
29
+ }
30
+ function printCallbar(config) {
31
+ printLine("phone", dash(config.phone));
32
+ printLine("sms", yesNo(config.sms));
33
+ printLine("label", dash(config.label));
34
+ }
35
+ function printBooking(config) {
36
+ printLine("notify", dash(config.notifyEmail));
37
+ printLine("hours", dash(config.hours));
38
+ }
39
+ function printConfirmable(config, confirmations) {
40
+ printLine("notify", dash(config.notifyEmail));
41
+ const on = config.confirmEnabled === true;
42
+ if (confirmations && !confirmations.available)
43
+ printLine("confirm", `${yesNo(on)} (coming soon)`);
44
+ else if (confirmations && !confirmations.paid)
45
+ printLine("confirm", `${yesNo(on)} (paid plan required)`);
46
+ else
47
+ printLine("confirm", yesNo(on));
48
+ }
49
+ function printFormsList(instances) {
50
+ if (!instances.length) {
51
+ console.log("(no forms)");
52
+ return;
53
+ }
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}`);
60
+ }
61
+ }
62
+ function parseYesNoFlag(raw, flag) {
63
+ if (raw == null)
64
+ return undefined;
65
+ if (raw === "yes" || raw === "true")
66
+ return true;
67
+ if (raw === "no" || raw === "false")
68
+ return false;
69
+ throw Object.assign(new Error(`${flag} must be yes or no.`), { exitCode: 2 });
70
+ }
71
+ export async function runTypedConfig(key, slug, action, flags) {
72
+ const usage = formatModuleUsage(key);
73
+ const work = [...flags];
74
+ if (action === "get") {
75
+ const instanceId = takeFlag(work, "--instance");
76
+ assertNoUnknownFlags(work, "modules");
77
+ const q = instanceId ? `?instanceId=${encodeURIComponent(instanceId)}` : "";
78
+ const data = await v1Get(`${pathFor(slug, key)}${q}`);
79
+ if (isJson()) {
80
+ emitOk("modules", data);
81
+ return EXIT_OK;
82
+ }
83
+ if (key === "forms" && !instanceId) {
84
+ printFormsList(data.instances ?? []);
85
+ return EXIT_OK;
86
+ }
87
+ if (key === "callbar")
88
+ printCallbar(data.config ?? {});
89
+ else if (key === "booking")
90
+ printBooking(data.config ?? {});
91
+ else
92
+ printConfirmable(data.config ?? {}, data.confirmations);
93
+ return EXIT_OK;
94
+ }
95
+ if (action === "set") {
96
+ const instanceId = takeFlag(work, "--instance");
97
+ const phone = takeFlag(work, "--phone");
98
+ const sms = takeFlag(work, "--sms");
99
+ const label = takeFlag(work, "--label");
100
+ const notifyEmail = takeFlag(work, "--notify-email");
101
+ const hours = takeFlag(work, "--hours");
102
+ const confirmRaw = takeFlag(work, "--confirm");
103
+ assertNoUnknownFlags(work, "modules");
104
+ let confirmEnabled;
105
+ try {
106
+ confirmEnabled = parseYesNoFlag(confirmRaw, "--confirm");
107
+ }
108
+ catch (err) {
109
+ return emitUsage("modules", `${err instanceof Error ? err.message : String(err)}\n\n${usage}`);
110
+ }
111
+ const body = { action: "set" };
112
+ if (instanceId)
113
+ body.instanceId = instanceId;
114
+ if (phone !== undefined)
115
+ body.phone = phone;
116
+ if (sms !== undefined)
117
+ body.sms = sms;
118
+ if (label !== undefined)
119
+ body.label = label;
120
+ if (notifyEmail !== undefined)
121
+ body.notifyEmail = notifyEmail;
122
+ if (hours !== undefined)
123
+ body.hours = hours;
124
+ if (confirmEnabled !== undefined)
125
+ body.confirmEnabled = confirmEnabled;
126
+ if (key === "forms" && !instanceId) {
127
+ return emitUsage("modules", `Usage: frontpage modules forms set --instance <slug> [--notify-email] [--confirm yes|no]\n\n${usage}`);
128
+ }
129
+ const data = await v1Post(pathFor(slug, key), body);
130
+ if (isJson()) {
131
+ emitOk("modules", data);
132
+ return EXIT_OK;
133
+ }
134
+ const updated = Array.isArray(data.updated) ? data.updated.join(", ") : "settings";
135
+ console.log(`Updated ${key}${instanceId ? ` ${instanceId}` : ""} — ${updated}.`);
136
+ const cfg = data.config ?? {};
137
+ if (key === "callbar")
138
+ printCallbar(cfg);
139
+ else if (key === "booking")
140
+ printBooking(cfg);
141
+ else
142
+ printConfirmable(cfg);
143
+ return EXIT_OK;
144
+ }
145
+ return emitUsage("modules", `${usage}\n\nUnknown action "${action}".`);
146
+ }
package/dist/registry.js CHANGED
@@ -324,6 +324,12 @@ export const COMMANDS = [
324
324
  "frontpage modules google_reviews connect --place-id ChIJ…",
325
325
  "frontpage modules google_reviews list-reviews --stars 4,5",
326
326
  "frontpage modules google_reviews enable",
327
+ "frontpage modules callbar get",
328
+ "frontpage modules callbar set --phone +14085551234 --sms no",
329
+ "frontpage modules forms get",
330
+ "frontpage modules forms set --instance lead-gen --notify-email hi@example.com",
331
+ "frontpage modules ordering connect --no-open",
332
+ "frontpage modules payments status",
327
333
  "frontpage modules docs forms",
328
334
  "frontpage modules docs google_reviews",
329
335
  "frontpage modules docs signups",
@@ -335,11 +341,15 @@ export const COMMANDS = [
335
341
  "Bare `frontpage modules <key>` prints that key's CLI usage (exit 2) — not agentDocs",
336
342
  "Key-first and verb-first both work: `modules google_reviews enable` = `modules enable google_reviews`",
337
343
  "google_reviews extras: connect / resolve / status / list-reviews / hide / show / set-stars / set-sort / set-weight / reset-weights / refresh / disconnect / write-review-url",
344
+ "callbar/forms/booking/signups: typed get / set (same whitelist as `modules config`)",
345
+ "payments/shop/ordering: connect opens the browser; finish there, then status. --no-open for CI. request-setup still lights the editor amber bar.",
338
346
  "See https://frontpage.host/developers/modules and /developers/modules/google-reviews",
339
347
  ],
340
348
  dangers: [
341
349
  "Secret values are never returned — masks only",
342
350
  "request-setup surfaces an owner prompt in the editor status bar (CLI cannot show it)",
351
+ "payments and shop share one Stripe account — disconnect warns before teardown",
352
+ "OAuth cannot finish in the terminal; connect only opens the vendor URL",
343
353
  "Do not write placeId through generic config — that skips listing resolve + seed",
344
354
  ],
345
355
  seeAlso: ["articles", "inbox"],
@@ -585,7 +595,9 @@ export const TOPICS = {
585
595
  frontpage fonts [list|search] [--slot …] [--query …]
586
596
  frontpage tokens set --group … --key … --value …
587
597
  frontpage modules list|docs|request-setup|enable|disable|archive|config <key>
588
- frontpage modules <key> # that key's CLI usage (generic only in this phase)
598
+ frontpage modules <key> # that key's CLI usage
599
+ frontpage modules payments|shop|ordering connect [--no-open] # opens Stripe/Square; then status
600
+ frontpage modules ordering sync
589
601
  # mailing list key: signups. Index: https://frontpage.host/developers/modules
590
602
  frontpage extensions list|docs|enable|disable|config <key>
591
603
  frontpage settings get|set
@@ -595,7 +607,7 @@ export const TOPICS = {
595
607
  Do not fake payments/checkout. LOCKED extensions → do not fake; say plan required.
596
608
  Migrate pages → articles: prefer frontpage prompt "migrate …" over mass delete+write.
597
609
  edit uses click-edit (exact single occurrence), not push / gated-write.
598
- Module OAuth and secret values are not exposed via the CLI.
610
+ Connect opens Stripe/Square in the browser; this CLI never accepts or returns OAuth secrets.
599
611
  Site contract: frontpage help site-building · https://frontpage.host/developers/site-building
600
612
  Domains, billing, members, and site delete are out of v1.`,
601
613
  history: `History
package/llms.txt CHANGED
@@ -8,7 +8,7 @@
8
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
- - Modules setup: frontpage modules request-setup <key> --reason "…" (owner status bar; CLI cannot show it). frontpage modules <key> prints that key's CLI usage. Index: https://frontpage.host/developers/modules. 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.
11
+ - Modules setup: wizard modules (payments/shop/ordering): frontpage modules <key> connect [--no-open] opens Stripe/Square; finish in the browser, then status. request-setup remains valid for the editor amber bar. payments and shop share one Stripe account. 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
12
  - Analytics ops: frontpage analytics summary|query|funnel|timeseries|compare|sql (bare analytics = dashboard)
13
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontpage-host",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Frontpage CLI — manage your site from the terminal",
5
5
  "type": "module",
6
6
  "bin": {