frontpage-host 0.2.0 → 0.2.2

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
@@ -264,3 +264,6 @@ Internal, allowlisted sites only. Use `frontpage customer-inbox get|put|post RES
264
264
 
265
265
 
266
266
  Payments, invoices and recurring billing: see packaged [PAYMENTS.md](./PAYMENTS.md), `frontpage help payments`, and https://frontpage.host/developers/payments. Version 0.2.0 removes writable currency, Store checkout mode and Store-owned money commands; Store stays cart-only. Preview/review before sends; keep stable operation keys on retries. Autopay requires the customer’s own verified authorization.
267
+
268
+
269
+ Offerings own every standalone Pay now payment. Use `frontpage payments buttons list|get|create|update|archive|restore`; create the saved offering before adding its returned `data-fp-pay` markup. Supports `--receipt-template-id UUID`, `--default-receipt`, `--active true|false`, combined price/details updates and JSON payloads. Archive deletes from active checkout but preserves history. The website agent uses the same service automatically for ordinary payment-button requests. Store stays cart-only; invoices stay saved customer-addressed records. Full instructions: PAYMENTS.md and https://frontpage.host/developers/payments.
package/CUSTOMER-INBOX.md CHANGED
@@ -28,6 +28,12 @@ Setup binds private guidance to the verified owner who requested it. That identi
28
28
 
29
29
  Settings allow `enabled`, `instructions`, `tools`, `maxSteps` (1–12), `maxOutputTokens` (256–2400), and `maxInputBytes` (4000–64000). Supported tools: `knowledge_read`, `knowledge_update`, `customer_read`, `customer_update`, `notes_read`, `note_add`, `ask_owner`, `reply_customer`, `wait`, `finish`. Keep `wait` and `finish` enabled. Defaults: 8 steps, 1200 output tokens per call, 32000 bytes of public text. Model, billing multiplier and recipients are not caller-selectable fields. The worker also enforces platform concurrency, deadlines and input limits.
30
30
 
31
+ `maxContextTokens` (12000–96000, default 32000) sets a conservative per-call input plus output allowance. UTF-8 text bytes, tool definitions, instructions, transcript and image capacity count toward admission; exact provider tokens still determine billing. Optional older history and preloaded notes drop first. Essential evidence is never clipped: if it cannot fit, the job pauses for review. History stays stored without a lifetime message-count stop. The agent receives up to 40 recent messages plus required current input and owner guidance, and can retrieve older messages in bounded pages under its existing `knowledge_read` grant.
32
+
33
+ `reviewAfterReplies` (0–200, default 0/off) and `reviewWindowHours` (1–168, default 24) request private owner guidance after that many recent automated replies since the last guidance. Pending requests deduplicate. This is a rolling work control, not a cap on returning customers or retained history.
34
+
35
+ Customer replies commit before optional knowledge, People enrichment and notes. The same durable jobs table carries separate internal maintenance with the same credits, exact receipts and 10× pricing. It cannot send messages, request owner help, change ownership or edit public content. The workspace shows unfinished internal work separately and offers the existing safe retry controls for recoverable failures. Identical note content does not create another revision.
36
+
31
37
  Aliases use 3–48 ASCII letters, numbers and single hyphens. The service normalizes case and checks collisions in one transaction. Reserved platform names and routing namespaces cannot be claimed. Retired addresses remain reserved; they never move to another site.
32
38
 
33
39
  ## Internal knowledge and approved sources
@@ -54,7 +60,7 @@ Lock a knowledge note with `locked:true`. The support agent cannot change locked
54
60
  | --- | --- | --- |
55
61
  | `conversations` | GET | Paginated list; `?limit=50&after=<id>` |
56
62
  | `conversations/<id>` | GET | Thread and latest public message |
57
- | `conversations/<id>/messages` | GET | `?after=0&limit=50&audience=all`; viewers get public history only |
63
+ | `conversations/<id>/messages` | GET | `?timeline=true&limit=50&audience=all`; follow the returned message-ID `nextCursor` with `after`; viewers get public history only |
58
64
  | `conversations/<id>/routing` | GET | Current handler/version, People link and delivery states |
59
65
  | `conversations/<id>/reply` | POST | `{"text":"Tuesday at 6:30 works. Keep staffing private.","handlerVersion":0,"privateGuidance":true}` |
60
66
  | `conversations/<id>/control` | POST | `{"action":"takeover","handlerVersion":0}` or `{"action":"return","handlerVersion":1}` |
@@ -62,11 +68,17 @@ Lock a knowledge note with `locked:true`. The support agent cannot change locked
62
68
  | `files?messageId=<id>` | GET | Bound message files; editor/admin only |
63
69
  | `files/<id>` | GET | Short-lived download grant; editor/admin only |
64
70
 
71
+ One verified customer has one continuous thread per site, across direct email, replies and verified website chat. Email subject changes do not create new cases. Closing reopens that same thread on the next message; Trash needs owner restoration. Inbox joins retained history without moving messages, files or paid receipts. Old conversation links and recipient-bound reply addresses resolve to the current thread. Public chat includes customer-visible email replies and excludes owner guidance and notes.
72
+
73
+ Use `timeline=true` for combined history. Its opaque `nextCursor` is a message ID, not a sequence or offset; pass it unchanged as `after` or `before`. `latest=true` returns the latest bounded page, and `before=<cursor>` loads older messages. A cursor must belong to that conversation and audience. Sequence-based reads without `timeline=true` remain available for the root segment; use the combined timeline for every linked segment. The CLI export follows the complete combined timeline. The widget opens the latest 50 public messages and offers **Load earlier messages**, so a returning customer does not wait for years of history to download. The support agent's `history_read` tool likewise follows its returned `nextBefore` message ID.
74
+
65
75
  Private guidance stays in the same internal thread. The agent asks by a private email and stores its question in the history. Replying by email or UI gives context and leaves the agent responsible for the customer. The agent uses that context to answer and can update unlocked internal knowledge.
66
76
 
67
77
  General owner facts, such as support hours and standard policies, update related unlocked knowledge without an explicit save request. One-time customer exceptions and private staffing details stay in the conversation. The agent reads the full note and records verified evidence before changing it. Late private guidance can still update knowledge after the customer has an answer; that work cannot send a duplicate customer reply. Locks, manual handling, paused automation and work limits still apply.
68
78
 
69
- Search results contain previews. The support agent reads the complete matching note or approved source before deciding an answer is missing. A verified owner reply can provide lasting facts on a different topic from the customer's question; the same job processes those facts before replying or finishing. The worker verifies the message kind and owner identity. Customer text cannot claim that authority. A specific offer to one customer stays in that thread even when the same owner reply also contains a general policy.
79
+ Shared knowledge must remain useful and true for other customers when the current customer, order and case are removed. Individual delivery estimates, arrangements and fee waivers stay in that conversation or its bound People contact's factual notes. A “customer-specific” disclaimer does not make them shared business policy. Maintenance can finish successfully without writing any knowledge.
80
+
81
+ Search results contain previews. The support agent reads the complete matching note or approved source before deciding an answer is missing. A verified owner reply can provide lasting facts on a different topic from the customer's question; the agent answers the customer first, then separate internal maintenance processes lasting facts. The worker verifies the message kind and owner identity. Customer text cannot claim that authority. A specific offer to one customer stays in that thread even when the same owner reply also contains a general policy.
70
82
 
71
83
  Email replies route through opaque, recipient-bound Reply-To addresses linked to the site, conversation, purpose, owner and handler version. These are local addresses on one domain, not a new subdomain per thread. Subjects and message text never select the customer thread. The service checks the exact verified sender, current owner membership, route and receiving-server DMARC proof before interpreting owner mail.
72
84
 
@@ -170,8 +182,14 @@ const identity = encoded + "." + createHmac("sha256", process.env.INBOX_IDENTITY
170
182
 
171
183
  The service verifies the signature, site, origin and expiry. Assertions can last at most five minutes. They grant customer identity only, never owner or manager authority. Every app-session request must include the current signed identity matching that session's subject and email. Put a server-rendered assertion in `data-identity`, or preferably provide `data-identity-url="/api/support-identity"`. That same-origin HTTPS endpoint authenticates the current app user and returns `{"identity":"<signed assertion>"}`. The widget refreshes it; after logout or account changes, old app sessions cannot be read without the matching identity. Anonymous widgets need no assertion. Each approved origin has its own visitor session; email continues the bound conversation.
172
184
 
185
+ Browser access lasts 30 days from creation or successful email verification/recovery. Verification links last one hour and work once; confirming rotates the credential. The widget retains access in that browser's storage and keeps it through temporary network failures. Returning browsers with valid access reopen their history automatically. When access is missing, ordinary **Continue** and email confirmation select the verified customer's continuous public-support conversation for that site. Both empty and nonempty provisional chats join only after proof; approved public origins can share the customer history through their own verified sessions. Typed email never restores history. Revoked credentials remain revoked; fresh proof does not reactivate an old token. Signed app identities remain separate by site, origin and subject. The recovery endpoint remains available, but customers need no separate Recover workflow. An empty unverified saved session with an email requests confirmation automatically; a receipt prevents duplicate requests during the link's one-hour lifetime, then rotates so an expired link cannot block a fresh confirmation.
186
+
187
+ The same conversation's customer-visible email and chat messages load together, including owner replies relayed through Inbox. They stay stored after browser access expires. Private guidance, internal notes and system notices never enter that public history. Recovery restores access to the saved conversation rather than starting its history over.
188
+
173
189
  ## Conversation state, exports and shared usage
174
190
 
191
+ Use `frontpage-host` 0.2.2 or later to export every linked history segment through message-ID cursors. Update with `npm install -g frontpage-host@latest`.
192
+
175
193
  | Resource | Method | Body or behavior |
176
194
  | --- | --- | --- |
177
195
  | `conversations?state=active` | GET | Open threads; also `closed`, `trash` or `all` |
@@ -209,11 +227,12 @@ On app logout or account change, call `window.dispatchEvent(new Event("frontpage
209
227
 
210
228
  ## Support agent tool contract
211
229
 
212
- The support agent has its own system prompt plus the versioned private business instructions. Tool calls stay in Inbox's tenant, conversation, handler, configuration and job context. All ten tools start enabled; `wait` and `finish` cannot be removed. A disabled tool fails at the server.
230
+ The support agent has its own system prompt plus the versioned private business instructions. Tool calls stay in Inbox's tenant, conversation, handler, configuration and job context. All ten configurable tool grants start enabled; `wait` and `finish` cannot be removed. A disabled tool fails at the server.
213
231
 
214
232
  | Tool | Inputs | Effect and limits |
215
233
  | --- | --- | --- |
216
234
  | `knowledge_read` | `query`, `id` or `sourceId` | Search internal notes or read complete approved evidence. |
235
+ | `history_read` | `before`, optional `query` | Retrieve up to ten older eligible messages in the assigned conversation under the `knowledge_read` grant; bounded to 6000 text characters. |
217
236
  | `knowledge_update` | Optional `id`; `title`, `body`, `expectedVersion`, `reason`, `evidenceKind`, `evidenceId`; source `evidenceVersion` | Update unlocked internal facts from verified guidance in this thread or an approved source revision. One-off guidance stays in the conversation. No public editing. |
218
237
  | `customer_read` | None | Read only this thread's verified People contact. |
219
238
  | `customer_update` | `name`/`phone`, `evidenceMessageId` | Enrich from an explicit customer statement; cannot change identity or permission. |
@@ -224,6 +243,8 @@ The support agent has its own system prompt plus the versioned private business
224
243
  | `wait` | `reason` | Pause safely without sending. |
225
244
  | `finish` | None | Complete this bounded job. |
226
245
 
246
+ Read tools can run in one batch. Inbox defers `reply_customer`, `ask_owner`, `wait` and `finish` when the model requests them alongside other tools, so it observes the results before taking a terminal action. This does not change business permissions or allow extra work beyond the existing budget and step limits.
247
+
227
248
  Decisions screens malicious customer input and interprets authenticated owner-email intent. It never chooses IDs, recipients, permissions, business answers, support tools or knowledge changes. Owner intent uses bounded labeled context, one primary Luna call, at most one bounded same-Luna control fallback, and the same exact-token, raw-cost, 10× ledger receipt as support. Shared inference utilities provide the existing same-model credential/transport fallback; no expensive model substitution or customer model picker is added.
228
249
 
229
250
  The installed widget loads two public static scripts: `/customer-inbox/widget.js` and `/customer-inbox/view.js` on `https://app.frontpage.host`. The shared renderer contains no session, owner data or credentials. If your app sets a Content Security Policy, allow those script URLs and connections to `https://frontpage-inbox-api.fly.dev`; keep the existing widget origin and identity checks. Owner Inbox pages remain authenticated.
package/PAYMENTS.md CHANGED
@@ -283,10 +283,10 @@ frontpage payments buttons archive consultation --yes
283
283
 
284
284
  These reuse current item/variant pricing and visitor `/__pay`. Creation registers
285
285
  the exact ID atomically with the item in existing Store settings; it returns
286
- markup for an explicit `data-fp-pay` button. Existing standalone references come
287
- from the site's authorized markup. A Store card/CTA, no-shipping flag or old
286
+ markup for an explicit `data-fp-pay` button. Only saved, explicitly registered
287
+ Offerings authorize standalone payments; page markup never establishes ownership. A Store card/CTA, no-shipping flag or old
288
288
  instant setting is not proof of Pay now ownership. No new button table or fleet
289
- conversion exists. Price updates are separate from detail updates. Open amount
289
+ conversion exists. Price, details, questions and receipt selection save together. Open amount
290
290
  creation uses `--amount open --min-cents N` and a minimum variant price. Item IDs
291
291
  remain existing 2–40-character kebab IDs. Lists are bounded with `nextOffset`.
292
292
 
@@ -332,3 +332,80 @@ operations. Store remains cart-only and keeps existing resource paths under
332
332
  `/shop`. Saved products/orders, Express accounts, current visitor hooks,
333
333
  `/__pay`, Connect webhooks and old browser callbacks remain intact. Old binaries
334
334
  may fail on retired endpoints; no compatibility alias or data conversion runs.
335
+
336
+
337
+ ## Offerings: the required object behind Pay now
338
+
339
+ Every website Pay now payment must reference a registered Offering. Website markup, a price attribute, a Store item's instant flag, and direct checkout requests cannot create or authorize a payment. Store carts and saved invoices retain their own platform-owned flows. The existing `payments buttons` commands manage Offerings through the same service as the workspace and website agent.
340
+
341
+ ```bash
342
+ frontpage payments buttons list
343
+ frontpage payments buttons create --id consultation --title "Initial consultation" --price-cents 10000
344
+ frontpage payments buttons get consultation
345
+ frontpage payments buttons update consultation --title "Consultation" --price-cents 12500 --active true
346
+ frontpage payments buttons update consultation --receipt-template-id TEMPLATE_UUID
347
+ frontpage payments buttons update consultation --default-receipt
348
+ frontpage payments buttons update consultation --active false
349
+ frontpage payments buttons archive consultation --yes
350
+ frontpage payments buttons list --archived true
351
+ frontpage payments buttons restore consultation --yes
352
+ ```
353
+
354
+ `--file payload.json` supports the same fields. `--receipt-template-id` and `--default-receipt` are mutually exclusive. A template must be a saved Emails template owned by the same site. Price, details and template choice save together. Archive is deletion from new checkout: buttons stop taking payments, history remains, and restore can bring the offering back. Do not delete financial history. For a customer-chosen amount, create with `--amount open --min-cents 100 --price-cents 100` (integer minor units in the account currency).
355
+
356
+ Create/get/update return the saved object; create/update also return bound markup:
357
+
358
+ ```html
359
+ <button data-fp-pay="consultation" data-fp-requires="pay">Pay now <span data-fp-pay-bind="price"></span></button>
360
+ ```
361
+
362
+ The platform loads the saved price, minimum and availability. Do not put a fixed charge in `data-amount`, build a card form, call Stripe directly or substitute a Store item ID. A deleted, paused, unregistered or wrong-site offering cannot charge. Existing connected Stripe onboarding, payment readiness, tax, fees and currency rules still apply. Saved templates send confirmations only after verified payment, subject to current email limits; they do not subscribe a payer to marketing.
363
+
364
+ The website agent can do this automatically from a normal request such as “add a $100 consultation button”: it finds/reuses or creates the offering and then writes its bound markup. The owner does not have to visit Payments or know any object IDs. Ask for a missing essential business fact, such as an unknown price, rather than invent a fixed charge.
365
+
366
+ Payments Activity uses Customer, Type, Status, Amount and Date. Date is a verified payment timestamp; unpaid invoices and recurring schedules have no payment date. Historic records without a verified timestamp remain unknown. Summary counts cover the full scoped records even when the recent activity view is capped.
367
+
368
+ ## Customer details on Offerings
369
+
370
+ Use the existing buttons create/update commands with `--file offering.json`.
371
+ The same service powers the workspace and agent. For example:
372
+
373
+ ```json
374
+ {
375
+ "title": "Home consultation",
376
+ "priceCents": 10000,
377
+ "customizations": [
378
+ { "customizationId": "job-address", "label": "Property address", "kind": "address", "required": true },
379
+ { "customizationId": "contact", "label": "Preferred contact", "kind": "choice", "values": ["Phone", "Email"] },
380
+ { "customizationId": "instructions", "label": "Access instructions", "kind": "textarea", "maxLength": 1000, "includeInReceipt": false }
381
+ ]
382
+ }
383
+ ```
384
+
385
+ ```sh
386
+ frontpage payments buttons update consultation --file offering.json --json
387
+ frontpage payments buttons get consultation --json
388
+ frontpage payments activity get ORDER_UUID --source order --json
389
+ ```
390
+
391
+ At most eight questions. Supported kinds: text (max 200 characters), textarea
392
+ (max 2000), choice (max 20 values), checkbox, email, phone, number, date and
393
+ address. Text supports maxLength; number supports min/max. Required checkboxes
394
+ need an affirmative check. Offering choices require explicit selection. Dates
395
+ use YYYY-MM-DD and express a preference, not a confirmed booking. An address
396
+ question collects a service address; Store owns shipping and Stripe owns billing
397
+ addresses. Fields cannot change the saved price, fees or currency.
398
+
399
+ Stable customizationId values survive renames and reordering. Replacing the
400
+ customizations array edits future checkouts; completed payments retain captured
401
+ labels, types and answers. Offering answers default out of confirmations; opt in
402
+ per field with includeInReceipt:true. Read captured answers through activity get.
403
+ Do not put intake answers in page markup, URL parameters or Stripe product names.
404
+ The standard payment drawer renders the saved questions; an unrelated form does
405
+ not automatically join its submission to a payment.
406
+
407
+ ## Payments drawer design
408
+
409
+ Offerings work without Store markup. The platform supplies a Payments drawer for buyer-chosen amounts and saved questions; fixed payments without questions proceed to Stripe Checkout. Never add a Cart button or Store drawer for an Offering.
410
+
411
+ For custom appearance, include one shared `<template data-fp-payment-drawer>` using the required panel, title, close, amount, fields, error and confirm hooks documented by the Payments agent module. Style it with normal site CSS and payment-specific variables. Keep dynamic questions in the saved Offering, not in page HTML. UI, CLI and agent question edits appear on the next opening without republishing. Template/style edits use normal website publishing. Payments never changes the Store cart or its saved delivery choices.
package/README.md CHANGED
@@ -63,3 +63,6 @@ Use `frontpage people overview`, `groups`, `stages`, `membership`, `rules`, `att
63
63
 
64
64
 
65
65
  Payments, invoices and recurring billing: see packaged [PAYMENTS.md](./PAYMENTS.md), `frontpage help payments`, and https://frontpage.host/developers/payments. Version 0.2.0 removes writable currency, Store checkout mode and Store-owned money commands; Store stays cart-only. Preview/review before sends; keep stable operation keys on retries. Autopay requires the customer’s own verified authorization.
66
+
67
+
68
+ Offerings own every standalone Pay now payment. Use `frontpage payments buttons list|get|create|update|archive|restore`; create the saved offering before adding its returned `data-fp-pay` markup. Supports `--receipt-template-id UUID`, `--default-receipt`, `--active true|false`, combined price/details updates and JSON payloads. Archive deletes from active checkout but preserves history. The website agent uses the same service automatically for ordinary payment-button requests. Store stays cart-only; invoices stay saved customer-addressed records. Full instructions: PAYMENTS.md and https://frontpage.host/developers/payments.
@@ -49,7 +49,7 @@ export async function runCustomerInbox(args) {
49
49
  const prefix = `/api/v1/sites/${encodeURIComponent(slug)}/customer-inbox`;
50
50
  if (operation.method === "DOWNLOAD") {
51
51
  const record = await v1Get(`${prefix}/files/${operation.resource}`);
52
- const response = await fetch(record.data.downloadUrl, {
52
+ const response = await fetch(record.downloadUrl, {
53
53
  redirect: "error",
54
54
  signal: AbortSignal.timeout(60000),
55
55
  });
@@ -64,25 +64,25 @@ export async function runCustomerInbox(args) {
64
64
  }
65
65
  else {
66
66
  const conversation = await v1Get(`${prefix}/conversations/${operation.resource}`);
67
- await handle.writeFile(JSON.stringify({ type: "conversation", value: conversation.data }) +
67
+ await handle.writeFile(JSON.stringify({ type: "conversation", value: conversation }) +
68
68
  "\n");
69
- let cursor = 0;
69
+ let cursor = null;
70
70
  for (;;) {
71
- const page = await v1Get(`${prefix}/conversations/${operation.resource}/messages?after=${cursor}&limit=100&audience=all`);
72
- for (const message of page.data.messages) {
71
+ const page = await v1Get(`${prefix}/conversations/${operation.resource}/messages?timeline=true&limit=100&audience=all${cursor ? `&after=${cursor}` : ""}`);
72
+ for (const message of page.messages) {
73
73
  const attachments = await v1Get(`${prefix}/files?messageId=${message.id}`);
74
74
  await handle.writeFile(JSON.stringify({
75
75
  type: "message",
76
76
  value: message,
77
- attachments: attachments.data,
77
+ attachments,
78
78
  }) + "\n");
79
79
  count++;
80
80
  }
81
- if (page.data.nextCursor === null)
81
+ if (page.nextCursor === null)
82
82
  break;
83
- if (page.data.nextCursor <= cursor)
83
+ if (page.nextCursor === cursor)
84
84
  throw new Error("History cursor did not advance.");
85
- cursor = page.data.nextCursor;
85
+ cursor = page.nextCursor;
86
86
  }
87
87
  }
88
88
  await handle.sync();
@@ -4,7 +4,7 @@ import { takeFlagValue as takeFlag, hasFlag, assertNoUnknownFlags } from "../arg
4
4
  import { CliHttpError, EXIT_OK, EXIT_USAGE, v1Get, v1Post, v1Patch, LONG_MODULE_TIMEOUT_MS, } from "../http/client.js";
5
5
  import { emitErr, emitOk, emitUsage, isJson, requireYesOrTty, } from "../runtime.js";
6
6
  import { openBrowser, resolveOpenUrl } from "../modules/oauth.js";
7
- const USAGE = "frontpage payments status|connect|availability|activity|tax|invoices|recurring|customers … [--site <slug>] [--json]";
7
+ const USAGE = "frontpage payments status|connect|availability|buttons|activity|tax|invoices|recurring|customers … [--site <slug>] [--json]";
8
8
  function usage(message) {
9
9
  throw new CliHttpError(message, EXIT_USAGE, 0, "bad_request");
10
10
  }
@@ -33,6 +33,7 @@ async function payload(work) {
33
33
  }
34
34
  for (const [flag, key] of [
35
35
  ["--title", "title"],
36
+ ["--receipt-template-id", "receiptTemplateId"],
36
37
  ["--amount", "amount"],
37
38
  ["--operation-key", "operationKey"],
38
39
  ["--preview-hash", "previewHash"],
@@ -83,6 +84,17 @@ async function payload(work) {
83
84
  ...(email ? { email } : {}),
84
85
  ...(name ? { name } : {}),
85
86
  };
87
+ if (hasFlag(work, "--default-receipt")) {
88
+ if (body.receiptTemplateId !== undefined)
89
+ usage("Use --receipt-template-id or --default-receipt, not both.");
90
+ body.receiptTemplateId = null;
91
+ }
92
+ const active = takeFlag(work, "--active");
93
+ if (active !== undefined) {
94
+ if (active !== "true" && active !== "false")
95
+ usage("--active requires true or false.");
96
+ body.active = active === "true";
97
+ }
86
98
  if (hasFlag(work, "--confirm-billing-contact"))
87
99
  body.confirmBillingContact = true;
88
100
  return body;
@@ -173,14 +185,14 @@ export async function runPayments(args) {
173
185
  method = "PATCH";
174
186
  body = await payload(work);
175
187
  }
176
- else if (sub === "archive") {
177
- path = `buttons/${idOf(work)}/archive`;
188
+ else if (sub === "archive" || sub === "restore") {
189
+ path = `buttons/${idOf(work)}/${sub}`;
178
190
  method = "POST";
179
191
  body = await payload(work);
180
- confirm(body, "Archive this Pay now button");
192
+ confirm(body, sub === "restore" ? "Restore this offering" : "Delete this offering from new payments");
181
193
  }
182
194
  else
183
- usage("Use payments buttons list|get|create|update|archive.");
195
+ usage("Use payments buttons list|get|create|update|archive|restore.");
184
196
  }
185
197
  else if (group === "activity") {
186
198
  const sub = work.shift() ?? "list";
@@ -188,8 +200,8 @@ export async function runPayments(args) {
188
200
  path = "activity";
189
201
  else if (sub === "get") {
190
202
  const source = takeFlag(work, "--source");
191
- if (source !== "order" && source !== "payment")
192
- usage("Use --source order|payment.");
203
+ if (!["order", "payment", "invoice", "recurring"].includes(source ?? ""))
204
+ usage("Use --source order|payment|invoice|recurring.");
193
205
  const id = idOf(work);
194
206
  path = `activity/${source}/${id}`;
195
207
  }
@@ -27,6 +27,7 @@ _frontpage_completions() {
27
27
  analytics) COMPREPLY=( $(compgen -W "dashboard summary query funnel timeseries compare sql" -- "\${cur}") ) ;;
28
28
  people) COMPREPLY=( $(compgen -W "list get add set note rm-note follow-up hide unhide" -- "\${cur}") ) ;;
29
29
  routines) COMPREPLY=( $(compgen -W "list get add set pause resume rm run runs" -- "\${cur}") ) ;;
30
+ buttons) COMPREPLY=( $(compgen -W "list get create update archive restore" -- "\${cur}") ) ;;
30
31
  payments) COMPREPLY=( $(compgen -W "status settings connect availability activity buttons tax invoices recurring customers --file --operation-key --preview-hash --person --email --description --amount-cents --cadence --first-date --time-zone --due-days --no-open --site --json --yes" -- "\${cur}") ) ;;
31
32
  store) COMPREPLY=( $(compgen -W "items variants categories settings orders import" -- "\${cur}") ) ;;
32
33
  modules)
@@ -73,6 +74,7 @@ _frontpage() {
73
74
  analytics) _values 'subcommand' dashboard summary query funnel timeseries compare sql ;;
74
75
  people) _values 'subcommand' list get add set note rm-note follow-up hide unhide ;;
75
76
  routines) _values 'subcommand' list get add set pause resume rm run runs ;;
77
+ buttons) _values 'subcommand' list get create update archive restore ;;
76
78
  payments) _values 'subcommand' status settings connect availability activity buttons tax invoices recurring customers ;;
77
79
  store) _values 'subcommand' items variants categories settings orders import ;;
78
80
  modules)
@@ -112,6 +114,7 @@ export function generateFishCompletion() {
112
114
  `complete -c frontpage -n "__fish_seen_subcommand_from analytics" -a "dashboard summary query funnel timeseries compare sql"`,
113
115
  `complete -c frontpage -n "__fish_seen_subcommand_from people" -a "list get add set note rm-note follow-up hide unhide"`,
114
116
  `complete -c frontpage -n "__fish_seen_subcommand_from routines" -a "list get add set pause resume rm run runs"`,
117
+ `complete -c frontpage -n "__fish_seen_subcommand_from buttons" -a "list get create update archive restore"`,
115
118
  `complete -c frontpage -n "__fish_seen_subcommand_from payments" -a "status settings connect availability activity buttons tax invoices recurring customers"
116
119
  complete -c frontpage -n "__fish_seen_subcommand_from store" -a "items variants categories settings orders import"`,
117
120
  `complete -c frontpage -n "__fish_seen_subcommand_from modules" -a "list docs request-setup enable disable archive config callbar forms booking signups payments articles google_reviews"`,
package/dist/registry.js CHANGED
@@ -547,7 +547,7 @@ export const COMMANDS = [
547
547
  "status; settings; connect [--no-open]. Connect returns an existing signed-in admin browser handoff; it never accepts Stripe terms or completes onboarding.",
548
548
  "availability preview|on|off. off reads affected counts before confirmed pause/void; a partial response stays off and requires another off request. on never resumes stopped billing or restores void invoices.",
549
549
  "activity list|get <id> --source order|payment. invoices and recurring have separate get commands. Source IDs never collide.",
550
- "buttons list [--limit 1..100 --offset N --archived true]; get|create|update|archive. create uses --id, --title, --price-cents, optional --amount open --min-cents; update takes an id and --file. Change price separately from details. Archive requires --yes. Only explicit Pay now references/registrations qualify; an old instant Store setting does not.",
550
+ "buttons list [--limit 1..100 --offset N --archived true]; get|create|update|archive|restore. These manage Offerings. Create uses --id, --title, --price-cents, optional --amount open --min-cents. Create/update support --description, --active true|false, --receipt-template-id UUID or --default-receipt, and --file. Price, details and template save together. Archive deletes from new payments while retaining history; archive/restore require --yes. Only registered Offerings authorize Pay now; page markup and Store flags never do.",
551
551
  "tax get; tax settings --file <file> --yes; tax registrations list|add --country US --state CA --yes|end <id> --yes. Supply the business's actual tax registrations. No automatic tax advice or raw Account Session secrets.",
552
552
  "invoices preview|send --file <file>; list [--limit 1..100 --offset N --arrangement <id>]; get|refresh|resend|void <id>. send/resend need --operation-key; send needs --preview-hash; financial writes require --yes in scripts.",
553
553
  "recurring preview|create --file <file>; list|get|refresh|cancel|resume|resend-invitation. Supported cadence: weekly, fortnightly, monthly, quarterly, yearly; firstDate YYYY-MM-DD, explicit IANA timeZone, 09:00 start. create/resume/invitation need stable operation keys. Resume requires first-date, preview-hash and --yes; no missed-period backfill.",
package/llms.txt CHANGED
@@ -40,3 +40,6 @@ Customer Inbox (internal sites): CUSTOMER-INBOX.md. Settings, widget, knowledge,
40
40
 
41
41
 
42
42
  Payments, invoices and recurring billing: see packaged [PAYMENTS.md](./PAYMENTS.md), `frontpage help payments`, and https://frontpage.host/developers/payments. Version 0.2.0 removes writable currency, Store checkout mode and Store-owned money commands; Store stays cart-only. Preview/review before sends; keep stable operation keys on retries. Autopay requires the customer’s own verified authorization.
43
+
44
+
45
+ Offerings own every standalone Pay now payment. Use `frontpage payments buttons list|get|create|update|archive|restore`; create the saved offering before adding its returned `data-fp-pay` markup. Supports `--receipt-template-id UUID`, `--default-receipt`, `--active true|false`, combined price/details updates and JSON payloads. Archive deletes from active checkout but preserves history. The website agent uses the same service automatically for ordinary payment-button requests. Store stays cart-only; invoices stay saved customer-addressed records. Full instructions: PAYMENTS.md and https://frontpage.host/developers/payments.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "frontpage-host",
3
- "version": "0.2.0",
4
- "description": "Frontpage CLI \u2014 manage your site from the terminal",
3
+ "version": "0.2.2",
4
+ "description": "Frontpage CLI — manage your site from the terminal",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "frontpage": "dist/bin.js"