frontpage-host 0.2.8 → 0.2.10

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
@@ -56,7 +56,7 @@ frontpage whoami --json
56
56
  frontpage doctor --json
57
57
  ```
58
58
 
59
- - Keys are minted only in the Frontpage UI (profile → Developers). Prefix `fp_live_`.
59
+ - Keys are minted only in the Frontpage UI (Workspace → account menu → Developers). Prefix `fp_live_`.
60
60
  - Keys act as the minting user on **every** site they can access. Treat like passwords.
61
61
  - `PUBLIC_CLI_ENABLED` is server-side; the CLI cannot turn it on. Bare “not found” on `/api/v1/me` usually means the flag is off (exit 5).
62
62
 
@@ -129,7 +129,7 @@ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or
129
129
  - Analytics: bare `frontpage analytics` = dashboard; also `summary|query|funnel|timeseries|compare|sql` for structured reads (SQL is guard + RLS site-scoped).
130
130
  - People CRM: `frontpage people list|get|add|set|note|rm-note|follow-up|hide|unhide|tag|export`. Site-scoped person graph (email or phone). Inbox stays the raw ledger. Hide is not forget. List export is CSV (not a one-person legal dump). Full guide: https://frontpage.host/developers/people
131
131
  - Routines: `frontpage routines list|get|add|set|pause|resume|rm|run|runs`. One routine can hold several hours and several days. `8am,12pm,4pm` on `tue,thu,fri` stores hours `[8, 12, 16]` and weekdays `[2, 4, 5]`. Ask for an IANA timezone. Do not guess UTC. Full guide: https://frontpage.host/developers/routines
132
- - Store catalog: `frontpage store items|variants|categories|settings|orders|import`. Integer cents only (`$49.99` is `4999`). `--price-cents` is the usual cost. Optional `--discount-cents` is the sale the buyer pays (must be lower). End a sale with `frontpage store variants update <itemId> --id <variantId> --clear-discount`. This sends discountPriceCents: null and keeps the regular price. Omit both sale flags on update to preserve the current sale. Do not combine --clear-discount with --discount-cents; 0 means free, not removal. Read the item back to verify. Create a category before assigning it. Photos are a site path `/__media/uploads/<this-slug>/…` or an HTTPS URL already saved on this site. `frontpage payments connect [--no-open]` opens Frontpage then Stripe. Store always uses Add to cart. Use Payments for standalone Pay now and invoices. Service fee is `settings set-checkout-fee`. The cart starts hidden. Product pages are the default: unless they said they do not want product pages, write one `src/pages/products/item.astro` (or shop) with `data-fp-store-product`. If they said no, skip that file and do not bind `href` or `url`. Description is a small markdown subset; product-page bind is `<div data-fp-store-bind="description">`. Every store control carries `data-fp-requires="store"`. Do not bake titles or prices. Full guide: https://frontpage.host/developers/store
132
+ - Store catalog: `frontpage store items|variants|categories|settings|orders|import`. Integer cents only (`$49.99` is `4999`). `--price-cents` is the usual cost. Optional `--discount-cents` is the sale the buyer pays (must be lower). End a sale with `frontpage store variants update <itemId> --id <variantId> --clear-discount`. This sends discountPriceCents: null and keeps the regular price. Omit both sale flags on update to preserve the current sale. Do not combine --clear-discount with --discount-cents; 0 means free, not removal. Read the item back to verify. Stock: use --stock unlimited on variants update or items stock --set unlimited; zero is sold out. Omit stock on update to preserve it. Rename option types or return to one SKU with items update --file, preserving the surviving variant’s regular price, sale and stock. Create a category before assigning it. Photos are a site path `/__media/uploads/<this-slug>/…` or an HTTPS URL already saved on this site. `frontpage payments connect [--no-open]` opens Frontpage then Stripe. Store always uses Add to cart. Use Payments for standalone Pay now and invoices. Service fee is `settings set-checkout-fee`. The cart starts hidden. Product pages are the default: unless they said they do not want product pages, write one `src/pages/products/item.astro` (or shop) with `data-fp-store-product`. If they said no, skip that file and do not bind `href` or `url`. Description is a small markdown subset; product-page bind is `<div data-fp-store-bind="description">`. Every store control carries `data-fp-requires="store"`. Do not bake titles or prices. Full guide: https://frontpage.host/developers/store
133
133
 
134
134
  ## Agent prompt loop
135
135
 
@@ -266,8 +266,10 @@ Scale and internal sites, after owner activation. No pilot allowlist. Use `front
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
267
 
268
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.
269
+ Offerings own every standalone Pay now payment. Use `frontpage payments buttons list|get|create|update|archive|restore|receipt-preview`; 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. Create/update/restore return setup.status and the existing Stripe handoff, without enabling Store or overriding Payments off. Preview a bound receipt with buttons receipt-preview; it never sends or charges. Digital delivery uses approved file links in the saved purchase receipt; keep the offering inactive until resources are ready. Full instructions: PAYMENTS.md and https://frontpage.host/developers/payments.
270
270
 
271
271
  Currency is owned by Payments and read by Store, Offerings and invoices. Offering create/get/update responses include exact receiptVariables. For custom confirmations, declare those names with existing mail template commands, attach receiptTemplateId, and verify both records. Only include expressly selected answers; keep private answers excluded. Forms still use input-name tokens and confirmTemplateId.
272
272
 
273
273
  Customer Inbox setup: enable website chat through Inbox settings; Frontpage public sites load it without a file edit or publish. Other websites use the explicit widget snippet. `get website` reports managed addresses/public availability. Website knowledge is default-on, read-only public retrieval; `websiteKnowledge.enabled:false` disables it. Source toggles, attention filtering and receipt cursors use the documented versioned resources.
274
+
275
+ Customer Inbox team guidance: administrators may set `guidanceEmail` through the versioned settings write. The owner toolbar has the same setting. Teammates reply as the configured mailbox; no teammate account or separate mailbox activation is needed. Do not set it to the public Inbox address. Conversation management uses close/reopen; do not use read/trash/restore.
package/CUSTOMER-INBOX.md CHANGED
@@ -24,9 +24,9 @@ Reads use `get`. Writes use `put` or `post`, a UTF-8 JSON object file, and an 8
24
24
  | `settings` | GET | Returns `version`, `value`, fixed model and multiplier |
25
25
  | `settings` | PUT | `{"expectedVersion":0,"value":{"instructions":"Ask me if unsure."}}` |
26
26
 
27
- Setup binds private guidance to the verified owner who requested it. That identity comes from Core, never model/customer fields. The agent manages new conversations by default after setup; the platform's Inbox switches control transport and automation independently of site activation. Set `value.enabled:false` for manual handling. Settings changes cancel stale automatic jobs and queued automatic sends.
27
+ Setup defaults private guidance to the verified administrator who requested it. An administrator can set `value.guidanceEmail` to a team mailbox, such as `support@example.com`; no separate activation link or teammate Frontpage account is required. Teammates reply as that exact mailbox. Private routes and receiving authentication still protect replies. Changing the destination revokes old private routes and cancels queued mail to them. `guidanceToMe:true` restores your verified account email. The destination cannot be the Inbox’s own public support alias. The agent manages new conversations by default after setup; the platform's Inbox switches control transport and automation independently of site activation. Set `value.enabled:false` for manual handling. Settings changes cancel stale automatic jobs and queued automatic sends.
28
28
 
29
- Settings allow `enabled`, `instructions`, `tools`, `maxSteps` (1–12), `maxOutputTokens` (256–2400), and `maxInputBytes` (4000–64000). Supported tools: `knowledge_read`, `knowledge_update`, `customer_read`, `verify_customer`, `customer_update`, `notes_read`, `note_add`, `ask_owner`, `reply_customer`, `wait`, `finish`. Keep `wait` and `finish` enabled. Mailbox verification remains available whenever customer-data tools are enabled; it cannot be bypassed by settings. 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.
29
+ Settings allow `enabled`, `instructions`, `tools`, `maxSteps` (1–12), `maxOutputTokens` (256–2400), and `maxInputBytes` (4000–64000). Supported tools: `knowledge_read`, `knowledge_update`, `customer_read`, `verify_customer`, `customer_update`, `notes_read`, `note_add`, `ask_owner`, `reply_customer`, `wait`, `finish`. Keep `wait` and `finish` enabled. Mailbox verification remains available whenever customer-data tools are enabled; it cannot be bypassed by settings. Defaults: 8 steps, 1200 output tokens per call, 32000 bytes of public text. Model and billing multiplier are fixed. Only an administrator can choose the private guidance mailbox; the support agent cannot choose recipients. The worker also enforces platform concurrency, deadlines and input limits.
30
30
 
31
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
32
 
@@ -83,7 +83,7 @@ Lock a knowledge note with `locked:true`. Neither the support agent nor the owne
83
83
  | `files?messageId=<id>` | GET | Bound message files; editor/admin only |
84
84
  | `files/<id>` | GET | Short-lived download grant; editor/admin only |
85
85
 
86
- 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 after identity is proven. Fresh mail to the public alias remains provisional until confirmation, even when From and DMARC match an existing customer. Recipient-bound secret reply routes continue the established thread. Typed email, display names and subjects never authorize merging or history access. 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.
86
+ 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 after identity is proven. Fresh mail to the public alias remains provisional until confirmation, even when From and DMARC match an existing customer. Recipient-bound secret reply routes continue the established thread. Typed email, display names and subjects never authorize merging or history access. Closing reopens that same thread on the next message. Legacy trashed threads can be reopened from Closed. 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.
87
87
 
88
88
  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.
89
89
 
@@ -272,7 +272,7 @@ Update settings using the current `expectedVersion`:
272
272
  }
273
273
  ```
274
274
 
275
- Use exact HTTPS origins without paths or credentials. This controls cross-origin browser access, not owner permissions. Required intake fields collect details; they do not prove email ownership. The customer must confirm the emailed link before email continuation or authenticated People access. A persistent verification card explains that public business questions can continue while private customer/account data stay locked. The branded HTML email uses the shared transactional renderer and its Confirm email button opens app.frontpage.host/customer-inbox/confirm. GET never consumes a proof; an explicit same-origin POST reaches Inbox through a signed server bridge. Confirmation rotates the visitor credential. Recovery uses a new email proof and returns no history based on a typed address alone.
275
+ Use exact HTTPS origins without paths or credentials. This controls cross-origin browser access, not owner permissions. Required intake fields collect details; they do not prove email ownership. The customer must confirm the emailed link before email continuation or authenticated People access. A compact verification prompt appears only while confirmation needs action. Public business questions can continue while private customer/account data stay locked. After confirmation, the widget removes the prompt and enables email follow-up automatically; there is no separate continuation switch. The branded HTML email uses the shared transactional renderer and its Confirm email button opens app.frontpage.host/customer-inbox/confirm. GET never consumes a proof; an explicit same-origin POST reaches Inbox through a signed server bridge. Confirmation rotates the visitor credential. Recovery uses a new email proof and returns no history based on a typed address alone.
276
276
 
277
277
  The agent reads help rules in its support prompt and uses `ask_owner`. Work caps remain server-enforced. Owner copies are private emails of future agent replies. Replying gives private guidance unless the owner explicitly requests takeover. Neither copies nor guidance put owner and customer on a shared CC thread.
278
278
 
@@ -316,11 +316,10 @@ Use `frontpage-host` 0.2.2 or later to export every linked history segment throu
316
316
  | `conversations?state=active` | GET | Open threads; also `closed`, `trash` or `all` |
317
317
  | `conversations/<id>/messages?latest=true&limit=50&audience=all` | GET | Latest page in message order |
318
318
  | `conversations/<id>/messages?before=<sequence>&limit=50&audience=all` | GET | Earlier page; do not combine with `after` or `latest` |
319
- | `conversations/<id>/state` | POST | `{"action":"close","expectedVersion":0}`; actions: `close`, `reopen`, `trash`, `restore`, `read` |
320
- | `conversations/<id>/state` | POST | Mark read with current state version and `"sequence":42` |
319
+ | `conversations/<id>/state` | POST | `{"action":"close","expectedVersion":0}`; actions: `close`, `reopen` |
321
320
  | `conversations/<id>/revoke-sessions` | POST | `{}`; revoke existing visitor credentials |
322
321
 
323
- Trash retains history and file references. Restore reopens the thread; it does not erase prior handling decisions. State writes reject stale versions. Closing cancels queued sends and refuses unresolved provider outcomes. New customer input reopens a closed thread; trashed threads require owner restoration. Visitor revocation also disables recovery of that session.
322
+ Close retains history and file references. Reopen does not erase prior handling decisions. State writes reject stale versions. Closing cancels queued sends and refuses unresolved provider outcomes. New customer input reopens a closed thread. Legacy trashed threads appear under Closed and can be reopened there. Visitor revocation also disables recovery of that session.
324
323
 
325
324
  Export the full authorized history and file manifest, one JSON record per line:
326
325
 
@@ -338,7 +337,7 @@ Inbox sends count in the site's existing email allowance. Provider-accepted send
338
337
 
339
338
  Email capture keeps the original MIME message, attachments and provider receipt. Email and chat image uploads keep the original file and a smaller agent preview. Repeated upload/message receipts retain the same file/message IDs. Validated raster customer images up to 12 megapixels use the existing public bucket. Larger or undecodable files remain downloadable and require human review. Other files and private mail originals use authenticated downloads over encrypted bytes in that same bucket. No Blob firewall changes or separate bucket are part of this release.
340
339
 
341
- The existing backup process enumerates the whole bucket. The existing reaper scans Inbox files in bounded pages and asks Inbox for exact retained references; it does not read message bodies. Unknown reference results protect Inbox files while legacy cleanup continues. Live history, Trash, capture holds and original/preview links retain their files. The existing archive/restore/purge coordinator claims a file in Inbox before changing bytes. A claim blocks new links and remains held after an unknown outcome. Accepted email custody, message links, Trash and capture holds protect files; abandoned chat uploads can follow the existing grace/archive rules. Unknown file records stay protected. Site finalization freezes Inbox writes before moving its bytes through the same archive process. Broader shared-reaper performance work remains a later phase.
340
+ The existing backup process enumerates the whole bucket. The existing reaper scans Inbox files in bounded pages and asks Inbox for exact retained references; it does not read message bodies. Unknown reference results protect Inbox files while legacy cleanup continues. Live and closed history, legacy Trash, capture holds and original/preview links retain their files. The existing archive/restore/purge coordinator claims a file in Inbox before changing bytes. A claim blocks new links and remains held after an unknown outcome. Accepted email custody, message links, closed/legacy trashed conversations and capture holds protect files; abandoned chat uploads can follow the existing grace/archive rules. Unknown file records stay protected. Site finalization freezes Inbox writes before moving its bytes through the same archive process. Broader shared-reaper performance work remains a later phase.
342
341
 
343
342
 
344
343
  On app logout or account change, call `window.dispatchEvent(new Event("frontpage-inbox:reset"))`. This clears the displayed history, stored chat credential and pending input. The next request fetches a fresh assertion from `data-identity-url`; authenticated app sessions require the same current app identity on every request. Do not use a long-lived static assertion for an app with account switching. Verification returns to the same origin and the stored page path. Query strings and arbitrary redirect URLs are not accepted.
@@ -360,7 +359,7 @@ The support agent has its own system prompt plus the versioned private business
360
359
  | `customer_update` | `name`/`phone`, `evidenceMessageId` | Enrich from an explicit customer statement; cannot change identity or permission. |
361
360
  | `notes_read` | Optional `before` | Read only this verified customer's factual People notes, 25 per page. |
362
361
  | `note_add` | `text` | Save a private factual action note in People, with an idempotent receipt. Do not store hidden reasoning. |
363
- | `ask_owner` | `question` | Save the private question in the thread, email the verified configured owner and pause. Guidance does not take over. |
362
+ | `ask_owner` | `question` | Save the private question in the thread, email the administrator-authorized guidance mailbox and pause. Guidance does not take over. |
364
363
  | `reply_customer` | `text` | Save a public answer and queue email to the bound verified customer when available. Never choose recipients. |
365
364
  | `wait` | `reason` | Pause safely without sending. |
366
365
  | `finish` | None | Complete this bounded job. |
@@ -375,6 +374,92 @@ The installed widget loads two public static scripts: `/customer-inbox/widget.js
375
374
 
376
375
  The workspace uses a short authenticated stream through its cookie bridge; the installed widget uses authenticated fetch with its existing visitor/app-identity headers. Streams carry empty invalidation hints, never message text, private counters, recipients or file metadata. Clients re-read their authorized history/status. One restricted Postgres listener per Inbox process wakes the existing outbox publisher and fans out hints. Notifications are best-effort; durable rows, leases, recovery scans and receipt reconciliation remain authoritative. Disconnected clients refresh and use bounded fallback polling. No token enters a stream URL, and each short stream reauthorizes on reconnect.
377
376
 
378
- A new unverified chat with an email requests a generic verification email automatically. It does not send private history or assume that typing an address proves identity. The owner can still answer in chat while the visitor verifies. Confirmation rotates the session and binds future public replies to that email; if the latest customer input already has an unsent public answer, Inbox queues that one answer once. It does not mail a backlog or an answer that predates a newer customer question. Verified replies mirror to email whether the page is open or closed; no presence-tracking service is needed.
377
+ A new unverified chat with an email requests a generic verification email automatically. It does not send private history or assume that typing an address proves identity. The owner can still answer in chat while the visitor verifies. Confirmation rotates the session and binds future public replies to that email; if the latest customer input already has an unsent public answer, Inbox queues that one answer once. It does not mail a backlog or an answer that predates a newer customer question. Browser replies enter the existing mail outbox with a 30-second grace period. The open, visible widget acknowledges public replies in its transcript viewport using its current verified session. This cancels only that customer’s queued, unattempted follow-up email; the reply stays in history and still counts as answered. Hidden/minimized chat, lost connections and failed acknowledgments leave email queued. The existing publisher sends it after the grace period, normally on its next sweep (up to about 15 seconds later, plus provider time). Direct inbound-email replies keep immediate delivery. No extra continuation switch or presence service is required. A late read cannot cancel an already attempted or uncertain send; retries keep the same provider identity and accounting receipt.
379
378
 
380
379
  The workspace shows customer name/email first, latest public message in each row, and a copyable reference in conversation details. Conversation list and history scroll independently inside a bounded viewport. Settings use shared workspace controls and a presentation-only preview built from the same renderer as the installed widget. Preview changes send no email or model request. Internal note history/locks/revert stay on the note's own page.
380
+
381
+
382
+ ## Shared team guidance
383
+
384
+ Read settings and use the returned version. This example assumes `version: 3`:
385
+
386
+ ```json
387
+ {"expectedVersion":3,"value":{"guidanceEmail":"support@example.com"}}
388
+ ```
389
+
390
+ ```bash
391
+ frontpage customer-inbox put settings --file ./team-guidance.json --request-key team_guidance_01
392
+ frontpage customer-inbox get settings
393
+ ```
394
+
395
+ The settings response has `version: 4`, `value.guidanceOwnerEmail: "support@example.com"`, and the authorizing administrator in `value.guidanceOwnerId`. Other settings remain unchanged. Repeating the exact write with the same key does not apply it twice. A stale version with a new key returns `settings_changed`. Editors cannot change the mailbox. This is the private team destination, distinct from the public address customers email.
396
+
397
+ When the agent asks for help, anyone with shared-mailbox access can reply as `support@example.com`. Guidance remains private and keeps the agent handling the customer. An explicit takeover request changes handling; the customer relay stays within Inbox. Replies from an unrelated address or a revoked private route cannot act as team guidance. The audit records the actual sending mailbox; a shared sender does not identify the individual teammate.
398
+
399
+ ## Simple conversation management
400
+
401
+ Use Open and Closed. Close preserves history; a later customer message reopens the same conversation. Legacy trashed conversations appear under Closed and can be reopened. Mark read and Trash are no longer supported management actions.
402
+
403
+ ```json
404
+ {"action":"close","expectedVersion":2}
405
+ ```
406
+
407
+ ```bash
408
+ frontpage customer-inbox post conversations/CONVERSATION_ID/state --file ./close.json --request-key close_conversation_01
409
+ ```
410
+
411
+ Read the conversation’s current `stateVersion` first. The response increments it and sets `closedAt`; reopening clears `closedAt` and any legacy trash marker. Closing does not transfer handling or delete messages. Take over before sending a human customer reply; private guidance does not take over.
412
+
413
+ ## Setup and knowledge behavior
414
+
415
+ Claim the public support address before relying on account confirmation, owner guidance or email replies. General browser chat can work without it. Missing setup returns `address_required`; the widget explains that account confirmation is unavailable and keeps customer data locked. Finish setup, then retry the saved request from Inbox.
416
+
417
+ The support agent receives a dedicated system prompt, private business instructions, bounded conversation context and permitted tool schemas. It reads saved business notes with `knowledge_read`, live public pages with `website_search` / `website_read`, and saved approved webpage snapshots with `knowledge_read {sourceId}`. Search excerpts require a complete read before relying on them. Website search does not create internal notes. Only separate server-authorized learning from team guidance or approved source revisions may update unlocked shared knowledge. Customer assertions never authorize those writes.
418
+
419
+
420
+ ## Compact chat and workspace controls
421
+
422
+ - The chat header says “We’ll reply here, or email you if you’re away.” Confirmed
423
+ email needs no permanent success banner. The file and send buttons use icons
424
+ with accessible labels; Enter sends and Shift+Enter adds a line. The message
425
+ input grows within a fixed bound. Selected files can be removed before sending.
426
+ - Before a support address exists, Chat & settings shows **Set up your support
427
+ email** and **Create support address**. After assignment, it shows the active
428
+ address, its site, **Copy address**, and **Change address**. The replacement
429
+ form opens only after Change. Saving a replacement stops new mail to the old
430
+ alias; it does not delete conversation history. Reserved aliases stay protected.
431
+ - While the agent handles a conversation, the composer offers **Guide your agent**
432
+ and **Private note**. After takeover it offers **Reply to customer** and
433
+ **Private note**. Guidance, public reply and note drafts have separate keys;
434
+ changing control never makes a private draft public. A hand marks **Take over**;
435
+ the return arrow marks **Return to agent**. Only the current owner can send a
436
+ manual public reply. Existing API/CLI guidance and control operations remain available.
437
+
438
+ ### Widget read acknowledgment
439
+
440
+ The installed widget uses this automatically; owners do not need to call it.
441
+ The endpoint accepts only the current origin-bound verified browser credential
442
+ (and a current app identity assertion when the session belongs to an app).
443
+
444
+ ```http
445
+ POST /chat/sites/<site-id>/seen
446
+ Origin: https://your-approved-website.example
447
+ Authorization: Bearer <verified-visitor-session>
448
+ Idempotency-Key: seen_<unique-request-key>
449
+ Content-Type: application/json
450
+
451
+ {"messageIds":["11111111-1111-4111-8111-111111111111"]}
452
+ ```
453
+
454
+ ```json
455
+ {"acknowledged":true}
456
+ ```
457
+
458
+ Use one to fifty unique public message IDs from this customer’s conversation.
459
+ The session, site, origin, current email, app binding and public audience must
460
+ match. A typed but unverified email returns `verification_required`; a foreign
461
+ conversation or private message returns `message_unavailable`. Mixed valid and
462
+ invalid IDs change nothing. Retries are harmless. This endpoint neither exposes
463
+ message content nor changes ownership, knowledge, model work or charges. Canceled
464
+ follow-ups show **Seen in chat · email not needed** in the workspace. If the request
465
+ fails, normal email delivery remains the fallback.
package/PAYMENTS.md CHANGED
@@ -467,3 +467,44 @@ payment count, and average payment, grouped by currency. Gross is before fees an
467
467
  refunds. Schedules are not received money. Metrics cover the full recorded period,
468
468
  not only the capped Activity rows. Dates use verified successful-payment times;
469
469
  older records without proof show that the date is unavailable.
470
+
471
+
472
+ ## Standalone setup and digital download receipts
473
+
474
+ An Offering is a standalone payment. Creating, updating or restoring it requests
475
+ Payments setup if needed. The shared response includes `checkout:"standalone"`,
476
+ `requiresFulfillment:false`, the saved `button`, and `setup.status` plus the existing
477
+ signed-in owner Stripe handoff. Store availability, shipping and pickup do not gate
478
+ an Offering. Payments off stays off. A saved `button.active:false` is a draft;
479
+ accepting payments requires both an active Offering and `setup.status.ready:true`.
480
+ `get` reports the same readiness under `payment`. A repeated create with the same
481
+ ID returns 409; read that ID and update it rather than creating another payment.
482
+
483
+ For an e-book, consultation resource or other download, reuse uploaded files or
484
+ owner-approved URLs. One combined file or several files can work. Add those links
485
+ to an Emails template, attach it using `--receipt-template-id`, and preview it:
486
+
487
+ ```bash
488
+ frontpage payments buttons create --id digital-bundle --title "E-book + workbook" --price-cents 2500 --active false --json
489
+ frontpage payments buttons update digital-bundle --receipt-template-id TEMPLATE_UUID --json
490
+ frontpage payments buttons receipt-preview digital-bundle --json
491
+ frontpage payments buttons update digital-bundle --active true --json
492
+ ```
493
+
494
+ `receipt-preview` requires CLI 0.2.10 or later. The other commands work in earlier
495
+ versions. The owner must supply the real file links before enabling the Offering.
496
+ A thank-you-only template does not deliver a download. Public uploaded links can
497
+ be forwarded; this workflow does not create buyer-only or expiring downloads or
498
+ native email attachments. Never manually send a receipt or subscribe the buyer to
499
+ marketing. Verified payment triggers the existing receipt job and send limits.
500
+
501
+ The preview endpoint is `POST /api/v1/sites/{slug}/payments/buttons/{id}/receipt-preview`.
502
+ It requires editor access and accepts optional sample values in
503
+ `{"vars":{"buyer_name":"Casey"}}`. Values must use the Offering's returned
504
+ `receiptVariables`, with strings up to 32,000 characters. Excluded private answers
505
+ cannot be supplied. No customer lookup, send or charge occurs. The response is
506
+ `{"preview":true,"receipt":"custom","itemId":"digital-bundle","receiptTemplateId":"…","subject":"…","html":"…","text":"…"}`.
507
+ With no custom template it returns `receipt:"stock"` and explains the standard
508
+ purchase receipt; it does not fabricate a custom preview. Purchase previews use
509
+ the actual transactional renderer. Marketing's postal-address requirement remains
510
+ in place for marketing sends; its preview warning does not block purchase receipts.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Requires Node.js ≥ 20.
18
18
 
19
19
  ## 60-second path
20
20
 
21
- 1. In the Frontpage editor: profile menu → **Developers** → create an API key (`fp_live_…`).
21
+ 1. In Workspace: account menu → **Developers** → create an API key (`fp_live_…`).
22
22
  2. `frontpage login` (or `frontpage login "$FRONTPAGE_API_KEY"`).
23
23
  3. `frontpage sites` — note the **subdomain** (not a custom domain).
24
24
  4. `mkdir my-site && cd my-site && frontpage use <subdomain>`
@@ -77,3 +77,16 @@ frontpage store items get camp-hat --site my-site --json
77
77
  ```
78
78
 
79
79
  `--price-cents` sets the regular price. `--discount-cents` sets a lower sale price. `--clear-discount` removes the sale and keeps the regular price; it sends `discountPriceCents: null`. Omit both sale flags on update to keep the current sale. Do not combine them. A sale price of `0` means free, not sale removal. Repeat for each live variant when ending a sale across the catalog. Store prices update live; a page publish or banner removal does not clear discounts.
80
+
81
+
82
+ ### Store inventory and product options
83
+
84
+ CLI 0.2.9 adds `items update --file`; explicit flags override file values.
85
+
86
+ ```bash
87
+ frontpage store variants update camp-hat --id default --stock unlimited --site my-site
88
+ frontpage store items stock camp-hat --set unlimited --site my-site
89
+ frontpage store items get camp-hat --site my-site --json
90
+ ```
91
+
92
+ A whole stock count of `0` means sold out; `unlimited` sends JSON null. Omit stock on update to keep it. To rename existing option types, pass an `optionNames` list with the same number of names through `items update --file`. To return to one default SKU, pass `{"optionNames":[]}`. The existing first live variant supplies the regular price, sale price (including no sale), and stock (including unlimited). Inspect the live variants first and read back the result; other live variants are archived. Use `variants add` to turn options on. Do not recreate the product.
@@ -18,7 +18,7 @@ export async function runLogin(args) {
18
18
  return EXIT_USAGE;
19
19
  }
20
20
  if (!secret.startsWith("fp_live_")) {
21
- console.error("Key should start with fp_live_. Create one in Developers (profile menu).");
21
+ console.error("Key should start with fp_live_. Create one in Developers (Workspace account menu).");
22
22
  return EXIT_USAGE;
23
23
  }
24
24
  await setApiKey(secret);
@@ -185,6 +185,11 @@ export async function runPayments(args) {
185
185
  method = "PATCH";
186
186
  body = await payload(work);
187
187
  }
188
+ else if (sub === "receipt-preview") {
189
+ path = `buttons/${idOf(work)}/receipt-preview`;
190
+ method = "POST";
191
+ body = await payload(work);
192
+ }
188
193
  else if (sub === "archive" || sub === "restore") {
189
194
  path = `buttons/${idOf(work)}/${sub}`;
190
195
  method = "POST";
@@ -192,7 +197,7 @@ export async function runPayments(args) {
192
197
  confirm(body, sub === "restore" ? "Restore this offering" : "Delete this offering from new payments");
193
198
  }
194
199
  else
195
- usage("Use payments buttons list|get|create|update|archive|restore.");
200
+ usage("Use payments buttons list|get|create|update|archive|restore|receipt-preview.");
196
201
  }
197
202
  else if (group === "activity") {
198
203
  const sub = work.shift() ?? "list";
@@ -275,6 +275,7 @@ export async function runShop(args) {
275
275
  }
276
276
  if (sub === "update") {
277
277
  const itemId = requireShopId(work);
278
+ const file = takeFlag(work, "--file");
278
279
  const title = takeFlag(work, "--title");
279
280
  const description = takeFlag(work, "--description");
280
281
  const amount = takeFlag(work, "--amount");
@@ -284,7 +285,13 @@ export async function runShop(args) {
284
285
  const categories = parseCsv(takeFlag(work, "--categories"));
285
286
  const images = parseUrls(work);
286
287
  assertNoUnknownFlags(work, "store");
287
- const body = {};
288
+ const body = file ? await readJsonFile(file) : {};
289
+ if (file) {
290
+ const allowed = new Set(["title", "description", "images", "checkout", "amount", "minCents", "requiresShipping", "extraShippingCents", "active", "optionNames", "categories", "categorySlugs", "customizations"]);
291
+ const unknown = Object.keys(body).filter(key => !allowed.has(key));
292
+ if (unknown.length)
293
+ throw new CliHttpError(`Unsupported items update file field: ${unknown.join(", ")}. Use variant commands for SKU changes.`, EXIT_USAGE, 0, "bad_request");
294
+ }
288
295
  if (title !== undefined)
289
296
  body.title = title;
290
297
  if (description !== undefined)
package/dist/registry.js CHANGED
@@ -520,6 +520,7 @@ export const COMMANDS = [
520
520
  "variants update <itemId> --id <variantId> --clear-discount ends a sale and preserves the regular price. --discount-cents sets a sale; omit both on update to preserve it. The flags cannot be combined. 0 means free, not removal. Verify with items get. Prices update live.",
521
521
  "Descriptions use a small markdown subset. Bind data-fp-store-bind=description for the full product story; do not bake titles, prices or images into the page. Store is cart-only; use Payments for explicit Pay now.",
522
522
  "items stock uses --add/--remove as a signed delta, or --set/--unlimited. Never combine these verbs. Product pages use data-fp-store-product and the platform cart templates. Store prices read the shared currency owned by Payments.",
523
+ "variants update --stock unlimited or items stock --set unlimited restores unlimited stock; 0 means sold out; omit stock on update to preserve it. items update --file supports optionNames: same-length rename or [] to return to one SKU, preserving the first live variant price/sale/stock. Read items get before and after.",
523
524
  "variants add|update|archive|restore; categories list|create|rename|archive. Create categories before assigning their slugs.",
524
525
  "settings get|set-fulfillment|set-checkout-fee|set-receipt|availability get|on|off. Fee mode flat uses --cents; percent uses --bps (250 is 2.5%). Receipt takes --template <id> or --stock. Availability requires admin and --yes in scripts.",
525
526
  "orders list|get; import explicitly reads the existing site product files. No fleet import or data conversion occurs.",
@@ -548,7 +549,7 @@ export const COMMANDS = [
548
549
  "status; settings; connect [--no-open]. Connect returns an existing signed-in admin browser handoff; it never accepts Stripe terms or completes onboarding.",
549
550
  "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.",
550
551
  "activity list|get <id> --source order|payment. invoices and recurring have separate get commands. Source IDs never collide.",
551
- "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.",
552
+ "buttons list [--limit 1..100 --offset N --archived true]; get|create|update|archive|restore|receipt-preview. These manage Offerings. receipt-preview <id> [--file samples.json] renders the attached purchase receipt without sending or charging; samples.json can contain sample receipt variables under vars. 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. Saves request Payments setup when needed and return setup.status; they never enable Store or override Payments off. Only registered Offerings authorize Pay now; page markup and Store flags never do.",
552
553
  "Offering create/get/update JSON includes receiptVariables. Use exact names in existing mail templates, then attach --receipt-template-id. Included answers expose answer_<customizationId>; private answers are excluded. Currency is owned by Payments, not Store.",
553
554
  "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.",
554
555
  "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.",
@@ -693,7 +694,7 @@ export const TOPICS = {
693
694
  # Binary is: frontpage
694
695
  # Do NOT install the unrelated npm package named "frontpage"
695
696
 
696
- 1. Create a key: Frontpage editor → profile → Developers (fp_live_…)
697
+ 1. Create a key: Workspace → account menu → Developers (fp_live_…)
697
698
  2. frontpage login # or: frontpage login "$FRONTPAGE_API_KEY"
698
699
  3. frontpage sites # use the subdomain column
699
700
  4. mkdir work && cd work && frontpage use <subdomain>
@@ -711,7 +712,7 @@ export const TOPICS = {
711
712
  Agents: read AGENTS.md + frontpage help site-building; use --json for structured I/O.
712
713
  More: frontpage help auth | sync | site-building | agent | content | history | isolation | exit-codes`,
713
714
  auth: `Authentication
714
- 1. Create a key in the Frontpage editor: profile menu → Developers
715
+ 1. Create a key in Workspace: account menu → Developers
715
716
  2. frontpage login
716
717
  3. frontpage sites
717
718
  4. frontpage use <subdomain> (e.g. asimov-press, not a custom domain)
package/llms.txt CHANGED
@@ -18,7 +18,7 @@
18
18
  - Redirects: frontpage redirects list|set|rm — HTTP 301/308, instant, not a file; revert does not undo; do not fake with Astro.redirect / meta refresh
19
19
  - People CRM: frontpage people list|get|add|set|note|rm-note|follow-up|hide|unhide|tag|export. Site-scoped person graph (email or phone). Inbox stays the raw ledger. Hide is not forget. List export is CSV. Unknown leftover flags fail closed. Guide: https://frontpage.host/developers/people
20
20
  - Routines: frontpage routines list|get|add|set|pause|resume|rm|run|runs. Several hours and several days are one routine. 8am,12pm,4pm and tue,thu,fri store hours [8, 12, 16] and weekdays [2, 4, 5]. Timezone is IANA. Do not guess UTC. Guide: https://frontpage.host/developers/routines
21
- - Store catalog: frontpage store items|variants|categories|settings|orders|import. Integer cents only ($49.99 is 4999). --price-cents is the usual cost. Optional --discount-cents is the sale the buyer pays (must be lower). End a sale with `frontpage store variants update <itemId> --id <variantId> --clear-discount`. This sends discountPriceCents: null and keeps the regular price. Omit both sale flags on update to preserve the current sale. Do not combine --clear-discount with --discount-cents; 0 means free, not removal. Read the item back to verify. Create a category before assigning it. Photos are /__media/uploads/<this-slug>/… or an HTTPS URL already saved on this site. frontpage payments connect [--no-open] opens Frontpage then Stripe. Store is cart-only; currency comes from Stripe and cannot be set. Use frontpage payments for standalone Pay now and invoices. Service fee is settings set-checkout-fee. The cart starts hidden. Product pages are the default: unless they said they do not want product pages, write one src/pages/products/item.astro (or shop) with data-fp-store-product. If they said no, skip that file and do not bind href or url. Description is a small markdown subset; product-page bind is <div data-fp-store-bind="description">. Every store control carries data-fp-requires="store". Leave data-fp-store-variants empty. Do not bake titles or prices. Guide: https://frontpage.host/developers/store
21
+ - Store catalog: frontpage store items|variants|categories|settings|orders|import. Integer cents only ($49.99 is 4999). --price-cents is the usual cost. Optional --discount-cents is the sale the buyer pays (must be lower). End a sale with `frontpage store variants update <itemId> --id <variantId> --clear-discount`. This sends discountPriceCents: null and keeps the regular price. Omit both sale flags on update to preserve the current sale. Do not combine --clear-discount with --discount-cents; 0 means free, not removal. Read the item back to verify. Stock: use --stock unlimited on variants update or items stock --set unlimited; zero is sold out. Omit stock on update to preserve it. Rename option types or return to one SKU with items update --file, preserving the surviving variant’s regular price, sale and stock. Create a category before assigning it. Photos are /__media/uploads/<this-slug>/… or an HTTPS URL already saved on this site. frontpage payments connect [--no-open] opens Frontpage then Stripe. Store is cart-only; currency comes from Stripe and cannot be set. Use frontpage payments for standalone Pay now and invoices. Service fee is settings set-checkout-fee. The cart starts hidden. Product pages are the default: unless they said they do not want product pages, write one src/pages/products/item.astro (or shop) with data-fp-store-product. If they said no, skip that file and do not bind href or url. Description is a small markdown subset; product-page bind is <div data-fp-store-bind="description">. Every store control carries data-fp-requires="store". Leave data-fp-store-variants empty. Do not bake titles or prices. Guide: https://frontpage.host/developers/store
22
22
  - Analytics funnel: --steps JSON array or comma paths (/, /pricing); bad JSON → Invalid JSON for --steps
23
23
  - Modules: unknown key errors list Known: …; mailing list module key is signups (not subscribe)
24
24
  - 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 = agent idle + tip compile ready (fail-closed / compile timeout → exit 2); push --wait and publish --wait wait for GET /deploy ready
@@ -42,7 +42,7 @@ Customer Inbox (Scale and internal sites): CUSTOMER-INBOX.md. Settings, widget,
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
43
 
44
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.
45
+ Offerings own every standalone Pay now payment. Use `frontpage payments buttons list|get|create|update|archive|restore|receipt-preview`; 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. Create/update/restore return setup.status and the existing Stripe handoff, without enabling Store or overriding Payments off. Preview a bound receipt with buttons receipt-preview; it never sends or charges. Digital delivery uses approved file links in the saved purchase receipt; keep the offering inactive until resources are ready. Full instructions: PAYMENTS.md and https://frontpage.host/developers/payments.
46
46
 
47
47
  Payments owns Stripe currency. Read Offering receiptVariables before creating a confirmation template; attach it through Payments. Forms use submitted input names. Full receipt contract: PAYMENTS.md.
48
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontpage-host",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Frontpage CLI — manage your site from the terminal",
5
5
  "type": "module",
6
6
  "bin": {