@scribe-platform/cli 0.7.0 → 0.9.0

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.
Files changed (65) hide show
  1. package/README.md +28 -0
  2. package/dist/commands/login.d.ts.map +1 -1
  3. package/dist/commands/login.js +15 -1
  4. package/dist/commands/login.js.map +1 -1
  5. package/dist/commands/proof.d.ts +5 -0
  6. package/dist/commands/proof.d.ts.map +1 -0
  7. package/dist/commands/proof.js +440 -0
  8. package/dist/commands/proof.js.map +1 -0
  9. package/dist/commands/prospect.d.ts +27 -0
  10. package/dist/commands/prospect.d.ts.map +1 -0
  11. package/dist/commands/prospect.js +222 -0
  12. package/dist/commands/prospect.js.map +1 -0
  13. package/dist/commands/request.d.ts +38 -0
  14. package/dist/commands/request.d.ts.map +1 -0
  15. package/dist/commands/request.js +214 -0
  16. package/dist/commands/request.js.map +1 -0
  17. package/dist/commands/site.d.ts +11 -0
  18. package/dist/commands/site.d.ts.map +1 -1
  19. package/dist/commands/site.js +41 -3
  20. package/dist/commands/site.js.map +1 -1
  21. package/dist/commands/update.d.ts +10 -0
  22. package/dist/commands/update.d.ts.map +1 -1
  23. package/dist/commands/update.js +128 -31
  24. package/dist/commands/update.js.map +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +15 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/lib/claude-hook.d.ts +74 -0
  29. package/dist/lib/claude-hook.d.ts.map +1 -0
  30. package/dist/lib/claude-hook.js +108 -0
  31. package/dist/lib/claude-hook.js.map +1 -0
  32. package/dist/lib/client.d.ts +351 -0
  33. package/dist/lib/client.d.ts.map +1 -1
  34. package/dist/lib/client.js +159 -0
  35. package/dist/lib/client.js.map +1 -1
  36. package/dist/lib/proof-folder.d.ts +86 -0
  37. package/dist/lib/proof-folder.d.ts.map +1 -0
  38. package/dist/lib/proof-folder.js +226 -0
  39. package/dist/lib/proof-folder.js.map +1 -0
  40. package/dist/lib/proof-preview.d.ts +39 -0
  41. package/dist/lib/proof-preview.d.ts.map +1 -0
  42. package/dist/lib/proof-preview.js +318 -0
  43. package/dist/lib/proof-preview.js.map +1 -0
  44. package/dist/lib/proof-push.d.ts +42 -0
  45. package/dist/lib/proof-push.d.ts.map +1 -0
  46. package/dist/lib/proof-push.js +91 -0
  47. package/dist/lib/proof-push.js.map +1 -0
  48. package/dist/lib/skill.d.ts +13 -0
  49. package/dist/lib/skill.d.ts.map +1 -1
  50. package/dist/lib/skill.js +33 -2
  51. package/dist/lib/skill.js.map +1 -1
  52. package/dist/lib/update-check.d.ts +46 -0
  53. package/dist/lib/update-check.d.ts.map +1 -0
  54. package/dist/lib/update-check.js +103 -0
  55. package/dist/lib/update-check.js.map +1 -0
  56. package/package.json +5 -3
  57. package/runtime/proof.js +139 -0
  58. package/skills/scribe-site-builder/SKILL.md +175 -33
  59. package/skills/scribe-site-builder/recipes/README.md +11 -3
  60. package/skills/scribe-site-builder/recipes/domains.md +151 -0
  61. package/skills/scribe-site-builder/recipes/forms.md +5 -2
  62. package/skills/scribe-site-builder/recipes/proofs.md +311 -0
  63. package/skills/scribe-site-builder/recipes/prospects.md +192 -0
  64. package/skills/scribe-site-builder/recipes/requests.md +145 -0
  65. package/skills/scribe-site-builder/recipes/schema.md +24 -0
@@ -1,14 +1,19 @@
1
1
  ---
2
2
  name: scribe-site-builder
3
- description: Guides building a website on Scribe CMS end to end — designing the Astro site freely against Scribe as its backend, editing the `.scribe.yml` schema, adding one or many forms, the local dev loop, pushing content, and deploying — using the `scribe` CLI and the recipes in `recipes/`. Also the reference for working an existing Scribe site (adding fields/collections, publishing content, triaging form submissions, archiving, restoring or deleting a site). Use whenever the user wants to build/create/start a new website or site with Scribe, add a form to a site, work on an existing Scribe site, or asks about the `scribe` CLI.
3
+ description: Guides building a website on Scribe CMS end to end — designing the Astro site freely against Scribe as its backend, editing the `.scribe.yml` schema, adding one or many forms, the local dev loop, pushing content, and deploying — using the `scribe` CLI and the recipes in `recipes/`. Also the reference for working an existing Scribe site (adding fields/collections, publishing content, triaging form submissions and inquiries, renaming, archiving, restoring or deleting a site), for taking a site live on a custom domain — checking whether a client has a domain, buying one, DNS/CNAME setup, GitHub Pages domain config, HTTPS — for the queue of requests clients have sent the agency (triaging, claiming, replying, closing), for the agency's prospect pipeline (logging calls, follow-ups, won/lost), and for proofs — sending a client work to review (logo boards, mockups, colour schemes), reading what they said, and sending the next version. Use whenever the user wants to build/create/start a new website or site with Scribe, add a form to a site, work on an existing Scribe site, publish/launch/go live with a site, point a site at a domain, set up or fix a custom domain or DNS for a Scribe site, work the inquiries a site has received, triage or reply to a client's request for work, log a call or follow-up with a prospect or client, wants to send a client something to look at or approve (logo ideas, mockups, options, a proof), asks what a client said about work they were sent, wants to do another round or version of something a client reviewed, or asks about the `scribe` CLI.
4
4
  ---
5
5
 
6
6
  # Building a site with Scribe
7
7
 
8
- Source of truth: this file is the merged, agent-facing version of
9
- `docs/BUILDER-GUIDE.md` and `docs/AGENT-GUIDE.md` in the
10
- `ai-xerro-agent/projects/scribe` repo. If those docs and this one ever
11
- disagree, the repo docs win — this file may be behind.
8
+ **This file and the `recipes/` beside it are the whole reference.** They ship
9
+ inside the `@scribe-platform/cli` npm package and are installed with it, so
10
+ everything needed to drive Scribe is here or in `scribe <command> --help`.
11
+ There is no repo to consult and no wiki to look up — if something seems to be
12
+ missing, it is a gap worth telling Dave about, not a file to go hunting for.
13
+
14
+ (It is generated from `docs/BUILDER-GUIDE.md` and `docs/AGENT-GUIDE.md` in the
15
+ Scribe repo. That matters only to whoever edits it there; those paths do not
16
+ exist on a machine that installed the CLI from npm.)
12
17
 
13
18
  ## Recipes — read the one you need
14
19
 
@@ -22,16 +27,49 @@ instead of reading all of them:
22
27
  | [`recipes/collections.md`](./recipes/collections.md) | rendering editor-managed content |
23
28
  | [`recipes/forms.md`](./recipes/forms.md) | adding one or more forms |
24
29
  | [`recipes/deploy.md`](./recipes/deploy.md) | build/deploy setup or debugging |
30
+ | [`recipes/domains.md`](./recipes/domains.md) | anything about a domain — whether the client has one, getting them one, the DNS, taking a site live on it, fixing one |
31
+ | [`recipes/requests.md`](./recipes/requests.md) | working the queue of jobs clients have asked the agency for |
32
+ | [`recipes/prospects.md`](./recipes/prospects.md) | logging calls and follow-ups on prospects (not site-building) |
33
+ | [`recipes/proofs.md`](./recipes/proofs.md) | sending a client work to review — logo boards, mockups, options — and working the rounds |
25
34
 
26
35
  [`recipes/README.md`](./recipes/README.md) lists the five things that are a hard
27
36
  contract (because Scribe writes into the repo) as opposed to advice.
28
37
 
38
+ ## Sending a client work to look at: it's a Proof, never an artifact
39
+
40
+ If the ask is "send them some logo ideas", "show the client these options",
41
+ "get her to approve the mockup" or anything else where a **client looks at
42
+ work and reacts to it**, that is a Proof: read
43
+ [`recipes/proofs.md`](./recipes/proofs.md).
44
+
45
+ Three rules, in full, because getting them wrong is expensive:
46
+
47
+ 1. **Never run `scribe proof send` without an explicit yes to that send.** It
48
+ emails a real person and freezes a version permanently. Everything else in
49
+ the proof workflow is reversible.
50
+ 2. **Never use the Artifact tool for client review work**, even when the word
51
+ "artifact" is used. A claude.ai artifact needs an account to open, is
52
+ branded Claude rather than the agency, and nothing a client says on it
53
+ reaches their record — the three problems Proofs exist to fix.
54
+ 3. **A Proof needs a client to belong to.** No prospect yet? Offer to create
55
+ one first.
56
+
29
57
  ## Mental model
30
58
 
31
59
  - **A site = a GitHub repo.** Schema, content, and the Astro source all live in one
32
60
  repo. The admin app and the `scribe` CLI are both just clients that read/write
33
61
  files in it via the GitHub API; a local `git clone` is a third client reading/writing
34
62
  the same files via git.
63
+ - **Scribe owns the repo's GitHub Pages settings — never set them by hand.** The
64
+ custom domain, `public/CNAME`, HTTPS enforcement and the build's base path are
65
+ all written by `scribe site promote`, in one idempotent run. Do **not** reach for
66
+ `gh api .../pages`, the GitHub web UI, or a hand-written `public/CNAME`: setting
67
+ one without the others leaves the site 404ing, and the Pages call needs repo
68
+ admin that a site user doesn't have. The CLI doesn't need it — the admin app
69
+ makes that call with Scribe's own org token, so a write-scoped API key is enough.
70
+ If something about a domain looks like it needs a GitHub permission you lack,
71
+ that's the signal you're on the wrong path, not that you need Dave.
72
+ See [`recipes/domains.md`](./recipes/domains.md).
35
73
  - **Form submissions are the exception — they live in MongoDB, not the repo** (RFC
36
74
  0004). A Visitor's name, email and message used to be committed as a YAML file into
37
75
  the site's public repo, which published them to the open internet. Nothing writes
@@ -60,41 +98,61 @@ contract (because Scribe writes into the repo) as opposed to advice.
60
98
 
61
99
  ## Setup (once)
62
100
 
63
- - **CLI**: confirm `scribe --help` works. If it's not found, `npm link` from
64
- `packages/cli` in the `ai-xerro-agent/projects/scribe` repo.
101
+ - **CLI**: confirm `scribe --help` works. If it's not found:
102
+ `npm install -g @scribe-platform/cli`. (Working inside a clone of the Scribe
103
+ repo itself? `npm link` from `packages/cli` instead — but that is a
104
+ maintainer's setup, not a user's.)
65
105
  - **Login**: `scribe login --token <key>` — mint the key from the
66
- admin UI (sign in → account menu → **API Tokens** → New token). Defaults to the
67
- hosted admin at `https://scribe.weaverdigital.io`; add `--api-url <url>` only to
68
- point at a different instance (a local dev/Docker admin). Writes
69
- `~/.scribe/config.json`; every other command reads from there, no per-invocation
70
- `--token` flag. If a command fails with a raw `fetch failed`, that's not an API
106
+ admin UI (sign in → account menu → **API Tokens** → New token). This is the
107
+ whole machine setup: it writes `~/.scribe/config.json`, installs this skill,
108
+ and turns on auto-updates. Defaults to the hosted admin at
109
+ `https://scribe.weaverdigital.io`; add `--api-url <url>` only to point at a
110
+ different instance (a local dev/Docker admin). Every other command reads that
111
+ config, no per-invocation `--token` flag. If a command fails with a raw `fetch failed`, that's not an API
71
112
  error — it means `apiUrl` in that config doesn't point at anything listening;
72
113
  re-run `scribe login` with the right URL.
73
114
 
74
115
  ## Keeping Scribe up to date
75
116
 
76
- `scribe update` updates the CLI **and** reinstalls this skill, which ships inside
77
- the `@scribe-platform/cli` npm package:
117
+ **This normally happens by itself and needs no attention.** `scribe login`
118
+ installs this skill and registers a Claude Code `SessionStart` hook that checks npm for
119
+ a newer `@scribe-platform/cli` at the start of **every** session and installs
120
+ it — CLI and this skill together — *before* the session's skills are scanned.
121
+ So a session either started on the current version or was brought to it before
122
+ anyone typed. That is the whole reason the check runs at session start rather
123
+ than mid-conversation: there is never a new skill sitting on disk that the
124
+ running session can't see.
125
+
126
+ If an update did install, the hook says so in this session's context. Don't
127
+ relay it; it's already done.
128
+
129
+ If the hook reports that an update **failed** to install, that is for Dave, not
130
+ for the user — it will be an npm permissions or registry problem. Say so plainly
131
+ and carry on; the old version still works.
132
+
133
+ `scribe update` also runs the whole thing by hand:
78
134
 
79
135
  ```
80
- scribe update
136
+ scribe update # update the CLI + this skill now
137
+ scribe update --check # just report whether a newer version exists
138
+ scribe update --install-hook # repair the auto-update hook if it went missing
81
139
  ```
82
140
 
83
- Two things to know:
141
+ One caveat: this **only works for a global npm install** (`npm install -g
142
+ @scribe-platform/cli`). In a dev checkout of the scribe repo it refuses, and if
143
+ `~/.claude/skills/scribe-site-builder` is a symlink into a checkout it leaves it
144
+ alone — that's an author editing the skill in place.
84
145
 
85
- - **Tell the user to restart Claude Code afterwards.** Claude Code only discovers
86
- skills at session start, so the session that ran the update is still reading the
87
- old copy of this file. If you need the new instructions in the current session,
88
- read `~/.claude/skills/scribe-site-builder/SKILL.md` from disk instead of waiting
89
- for a restart.
90
- - **It only works for a global npm install** (`npm install -g
91
- @scribe-platform/cli`). In a dev checkout of the scribe repo it refuses, and if
92
- `~/.claude/skills/scribe-site-builder` is a symlink into a checkout it leaves it
93
- alone — that's an author editing the skill in place.
146
+ Run it by hand when the user says there's a new version, or when a CLI command
147
+ fails in a way that suggests this skill is behind the API (an option the CLI
148
+ doesn't recognise, a command that no longer exists) — the session-start check
149
+ runs before the session, so anything published since then is not here yet.
94
150
 
95
- Run it when the user says there's a new version, or when a CLI command fails in a
96
- way that suggests this skill is behind the API (an option the CLI doesn't
97
- recognise, a command that no longer exists).
151
+ **If a hand-run update installs a new version mid-session**, tell the user in one
152
+ sentence, in their words: *"I've updated Scribe — close this chat and start a new
153
+ one so I pick up the new instructions."* Then stop relying on anything in this
154
+ file that the update might have changed; if you need the new text now, read
155
+ `~/.claude/skills/scribe-site-builder/SKILL.md` from disk.
98
156
 
99
157
  ## Workflow: building a new site
100
158
 
@@ -168,6 +226,11 @@ recognise, a command that no longer exists).
168
226
  8. **Domain cutover — only when a customer's own hostname is ready.** Skip this
169
227
  entirely for demo sites; the GitHub Pages default already works.
170
228
 
229
+ If it *isn't* ready — the client may not own a domain, may not know who holds
230
+ it, or may have mail on it — that part is the bulk of the work and is in
231
+ [`recipes/domains.md`](./recipes/domains.md) under "Before the cutover". The
232
+ client buys their own domain, at their own registrar, in their own name.
233
+
171
234
  Check what DNS the domain needs first. This changes nothing:
172
235
  ```
173
236
  scribe site promote my-site --domain example.com --dns-only
@@ -201,12 +264,22 @@ recognise, a command that no longer exists).
201
264
  sync — fix it in the Cloudflare dashboard, or the contact form silently rejects
202
265
  real visitors on that domain.
203
266
 
267
+ **You do not need GitHub repo admin rights for any of this.** The Pages API call
268
+ is made by the admin app using Scribe's org token, so a write-scoped API key is
269
+ enough. If you find yourself checking whether the user can administer the repo,
270
+ you've left the supported path.
271
+
204
272
  `scribe site update my-site --domain example.com` only edits the stored record
205
- and the Turnstile widget. Use it to correct a domain, never to cut a site over.
273
+ and the Turnstile widget. Use it to correct a domain, never to cut a site over —
274
+ the CLI now refuses that and points at `promote`. Deeper reference, including how
275
+ to repair a half-finished cutover: [`recipes/domains.md`](./recipes/domains.md).
206
276
 
207
277
  ## Working an existing site
208
278
 
209
279
  ```
280
+ scribe site promote <site> --domain <domain> [--dns-only] # take it live on a real domain
281
+ scribe site update <site> --turnstile-key <key> # correct stored settings only
282
+
210
283
  scribe content list <site> [--collection <name>] [--json]
211
284
  scribe content read <site> <path> [--json]
212
285
  scribe content publish <site> <file> # validates against schema
@@ -223,8 +296,19 @@ scribe submission list <site> <collection> [--status <status>] [--page <n>] [--j
223
296
  scribe submission update <site> <collection> <id> [--status <s>] [--note <n>]
224
297
  scribe submission export <site> <collection> [--format csv|json] [--status <s>] [--out <file>]
225
298
  scribe submission delete <site> <collection> <id> [--yes] # staff only
299
+
300
+ scribe request list [--site <slug>] [--status <s>] [--type <t>] [--mine|--unassigned] [--all] [--page <n>]
301
+ scribe request show <id> # the ask + the whole thread, internal notes included
302
+ scribe request claim <id> [--release]
303
+ scribe request reply <id> --message <text> [--internal]
304
+ scribe request status <id> <open|in-progress|waiting-on-you|done|cancelled>
226
305
  ```
227
306
 
307
+ - **"Publish/launch the site at example.com" means `scribe site promote`** — the full
308
+ domain cutover in one command — step 8 of the build workflow above, and in full in
309
+ [`recipes/domains.md`](./recipes/domains.md). It is not `content publish`, and
310
+ it is not `site update --domain` (which only rewrites the stored record and would
311
+ leave the site 404ing on the new host — the CLI refuses that now and tells you so).
228
312
  - `scribe schema apply` is the way to add a form (or any collection with fields): it
229
313
  takes a YAML/JSON file describing **one whole collection**, upserts it by `name`,
230
314
  and validates through the admin's own parser first — so a reserved `status` field,
@@ -254,6 +338,53 @@ scribe submission delete <site> <collection> <id> [--yes] # staff only
254
338
  **staff** role — a site user's API key gets a 403. It prompts for the id unless
255
339
  `--yes` is passed. There is deliberately no equivalent in the admin UI, so don't go
256
340
  looking for a delete button on the inbox.
341
+ - **`scribe request` is the other queue, and the opposite direction.** A submission is
342
+ a Visitor contacting the *client*; a Request is the **client contacting the agency**
343
+ to ask for work. One cross-site queue, staff-only, nothing in any repo. Three things
344
+ to know before touching it, with the rest in
345
+ [`recipes/requests.md`](./recipes/requests.md):
346
+ - **`waiting-on-you` means waiting on the _client_** — every status is phrased for
347
+ the Client's own dashboard. Read as an instruction to the agency it means the
348
+ opposite of what it says.
349
+ - **A visible `reply` is an email to the client**, sent immediately and permanently.
350
+ Show the user the text before sending unless they've said not to bother.
351
+ `--internal` is the Agency-only note: no email, invisible to the client, moves no
352
+ status.
353
+ - **There is no `create`.** A Request comes from the Client; filing one for them
354
+ would put words in their mouth. Say so rather than hunting for a flag.
355
+
356
+ ## A site's two names, and the jobs only the admin UI can do
357
+
358
+ A site has a **slug** and a **display name**, and only one of them moves.
359
+
360
+ - The **slug** (`riverside-dental`) is the site's identity: the repo name, the
361
+ Pages URL, `?site=` on the submissions endpoint, and the argument to every
362
+ `scribe` command. **There is no rename** — not in the CLI, not in the admin.
363
+ Moving a site to a different slug means provisioning a new site. So when
364
+ someone is choosing a slug, it is worth one sentence of care up front.
365
+ - The **display name** is a label, and is editable any time: in the Staff console,
366
+ **Sites → the site → the Name field** at the top of the Details card, saved on
367
+ blur. It is allowed on archived sites too. `scribe site add --name` sets it at
368
+ creation; nothing in the CLI changes it afterwards.
369
+
370
+ A rename reaches the consoles and notification subjects and **nothing a Visitor
371
+ sees** — not the URL, and not `.scribe.yml`'s `siteName`, which is the website's
372
+ own copy of its name in a repo the rename doesn't write to. If the name is wrong
373
+ on the published pages too, that's a separate content edit in the repo.
374
+
375
+ Three other things live only in the Staff console, so route the user there rather
376
+ than hunting for a flag:
377
+
378
+ | Job | Where |
379
+ |---|---|
380
+ | Rename (display name) | Sites → the site → Name |
381
+ | Attach a site to a Client, or detach it | Sites → the site → **Client** |
382
+ | Star a site (sorts it to the top of the Sites list, and is what the staff Home page's Sites card shows) | Sites → the star |
383
+
384
+ And one that isn't available at all: **creating a login for a client's own
385
+ people**. There is no `scribe` command for it and no self-serve invite — user
386
+ management is super-admin only. "Give the client access to edit their content" is
387
+ a request to hand to Dave, with the site slug and who needs the login.
257
388
 
258
389
  ## Images — never hotlink, always commit the file
259
390
 
@@ -383,10 +514,21 @@ On a **form collection** specifically, since RFC 0004 moved submissions into Mon
383
514
  - **An `image` field is rejected** on a form collection. The old path uploaded a
384
515
  Visitor's file straight into the public repo, and a Mongo document is the wrong place
385
516
  for a blob. Deferred to RFC 0007 (object storage).
386
- - **`notify:` does not send email.** It runs, but through a `ConsoleMailer` that only
387
- logs — no address has ever received anything. Real delivery is RFC 0009. Don't
388
- promise a client they'll be emailed on a new submission; the inbox and its
389
- `needs-you` badge are the actual notification today.
517
+ - **New submissions are emailed** (RFC 0009, on RFC 0021's mailer), and
518
+ `notify:` is no longer how a *person* subscribes. Who gets one:
519
+ - **Site users on that Site are subscribed by default** — a lead nobody hears
520
+ about is the bug this closes, so the people whose business it is are opted in
521
+ unless they opt out.
522
+ - **Agency admins are not**, because they can reach every Site. They opt in per
523
+ Site per collection, under **Your Profile → Email notifications** in the admin.
524
+ - **`notify: [...]` now means addresses with no Scribe account** — a shared
525
+ `office@` inbox. Don't put a Scribe user's address in it; they manage their own.
526
+
527
+ The mail is best-effort and never fails the Visitor's POST, and `Reply-To` is
528
+ the Visitor's own address when the form has a field named `email` — that exact
529
+ name is the only signal, so a form whose address field is called something else
530
+ gets no `Reply-To`. So a client *can* be promised email on a new submission. The
531
+ inbox and its `needs-you` badge remain the thing of record.
390
532
 
391
533
  Per field: `name`, `type`, `label`, `required`, `default`, `pattern`, `hidden`,
392
534
  `readonly`, `isBody` (this field is the markdown body, not frontmatter), `list`
@@ -1,8 +1,7 @@
1
1
  # Recipes
2
2
 
3
- Patterns for building an Astro site against Scribe as the backend. Per
4
- [RFC 0010](../../../docs/proposals/0010-agent-authored-forms.md) these are
5
- **advice, not enforcement** — read the one you need, adapt it to the site you're
3
+ Patterns for building an Astro site against Scribe as the backend. Per RFC
4
+ 0010 these are **advice, not enforcement** — read the one you need, adapt it to the site you're
6
5
  building, and ignore the rest. There is no template to conform to and no
7
6
  generated config to keep in sync.
8
7
 
@@ -12,6 +11,15 @@ generated config to keep in sync.
12
11
  | [`collections.md`](./collections.md) | rendering content (posts, products, events) that editors manage in Scribe |
13
12
  | [`forms.md`](./forms.md) | putting one or more forms on the Site |
14
13
  | [`deploy.md`](./deploy.md) | setting up or debugging the build and deploy |
14
+ | [`domains.md`](./domains.md) | finding out whether a client has a domain, getting them one, the DNS, taking a Site live on it, or repairing one |
15
+ | [`requests.md`](./requests.md) | working the queue of jobs clients have asked the agency for |
16
+ | [`prospects.md`](./prospects.md) | recording and nurturing prospects in the calling pipeline |
17
+ | [`proofs.md`](./proofs.md) | sending a client work to review (logo boards, mockups) and working the rounds |
18
+
19
+ `requests.md`, `prospects.md` and `proofs.md` are the odd ones out — they're
20
+ about the agency's own work (its queue, its sales pipeline, and the work it
21
+ sends clients to review) rather than anything in a Site's repo, and they live
22
+ here because the `scribe` CLI is the interface for all of it.
15
23
 
16
24
  ## The part that isn't advice
17
25
 
@@ -0,0 +1,151 @@
1
+ # Taking a site live on a custom domain
2
+
3
+ One command does all of it. Reach for `scribe site promote` whenever the ask is
4
+ "publish the site at example.com", "point the domain at the site", "take it
5
+ live", or "set up the custom domain".
6
+
7
+ ```
8
+ scribe site promote <site> --domain example.com --dns-only # check DNS, change nothing
9
+ scribe site promote <site> --domain example.com # cut over
10
+ ```
11
+
12
+ ## Before the cutover: does the client even have a domain?
13
+
14
+ The agency user drives this whole thing themselves and is not a developer, so the
15
+ part that needs help is usually everything *before* `promote`. Work it in this
16
+ order, and say which step you're on.
17
+
18
+ **1. Find out what exists.** Don't take "we have a website" as "we have DNS we can
19
+ edit".
20
+
21
+ ```
22
+ dig +short NS example.com # who runs the DNS — this is the one that matters
23
+ dig +short example.com # where the name points today, if anywhere
24
+ dig +short MX example.com # mail. See the warning below.
25
+ whois example.com | head -30 # registrar, expiry, status (may be privacy-masked)
26
+ ```
27
+
28
+ The registrar on the invoice and the company running the DNS are often different.
29
+ **Follow the nameservers**, because that is where the records have to be added.
30
+ A `whois` that finds nothing registered is also the availability answer.
31
+
32
+ **2. If they don't own one, the client buys it.** They pay, it is registered in
33
+ their name, and they own it outright — never buy it on their behalf and never put
34
+ it in the agency's registrar. Help with the choice (business name, `.com` or the
35
+ local equivalent first, short, sayable on the phone, no hyphens or clever
36
+ spellings, nothing that pins them to one service or street), check candidates with
37
+ `whois`, and suggest they turn on auto-renew. Then they register it and come back
38
+ with access.
39
+
40
+ **3. Get DNS access.** Either their registrar login, or a call where they click
41
+ while you read out the records. Either is fine; the second is often faster. What
42
+ they're looking for is the screen listing records by type (`A`, `CNAME`, `MX`,
43
+ `TXT`) with an "Add record" button — every registrar names that screen
44
+ differently ("DNS", "Advanced DNS", "Manage DNS", "Nameservers").
45
+
46
+ Teach the shape of the task, not one provider's clicks. There is no supported
47
+ registrar; the client uses whatever they already have.
48
+
49
+ **4. Protect their email before you touch anything.** If `dig MX` returns records,
50
+ that domain carries their mail. The cutover only needs `A`/`CNAME` records for
51
+ the website — **say out loud that the `MX` and mail-related `TXT` records stay
52
+ exactly as they are**, and then make sure that is true. Breaking a client's email
53
+ is far worse than a late website, and it is the one mistake in this flow with no
54
+ quick undo from your side.
55
+
56
+ **5. Mind the old site.** If the domain currently serves a live website, the
57
+ switch is publicly visible. Agree a time rather than surprising them mid-trading.
58
+
59
+ Only then move on to `--dns-only` and the cutover below.
60
+
61
+ ## You do not need GitHub admin rights
62
+
63
+ The Pages configuration call (`PUT /repos/<org>/<repo>/pages`) is made by the
64
+ **admin app**, using Scribe's org-scoped token — not by the signed-in user's
65
+ GitHub account. A site user with a write-scoped API key can run the whole
66
+ cutover. If you catch yourself checking whether someone can administer the repo,
67
+ you are off the supported path: go back to `scribe site promote`.
68
+
69
+ Corollary: **never** set any of this by hand — not `gh api .../pages`, not the
70
+ GitHub Pages settings page, not a hand-written `public/CNAME`. Each one alone
71
+ produces a broken site (see "Repairing a half-finished cutover" below).
72
+
73
+ ## What promote actually does
74
+
75
+ In order, each step asserting current state before it writes:
76
+
77
+ 1. Checks that DNS already resolves to GitHub Pages — and refuses to continue if
78
+ not, because step 2 kills the old project-page URL.
79
+ 2. Commits `public/CNAME`. The deploy workflow keys off that file: present means
80
+ Astro builds with `base: '/'`, absent means `base: '/<repo>'`.
81
+ 3. Registers the domain with GitHub Pages via the API. **Both step 2 and step 3
82
+ are required.** The repo's workflow uses `build_type: workflow`, and Pages
83
+ does not read `CNAME` out of a build artifact — so the file alone changes the
84
+ base path without ever requesting a certificate, and the API call alone
85
+ serves the site from the root with every asset still pointing at `/<repo>/`.
86
+ 4. Sets `PUBLIC_SCRIBE_API_URL` so the contact form reaches the admin app, and
87
+ clears `PUBLIC_BASE_PATH`.
88
+ 5. Updates the `Site` record — this is the hostname the public submissions
89
+ endpoint accepts CORS from.
90
+ 6. Adds the domain to the Turnstile widget's allowed-domain list.
91
+ 7. Re-enforces HTTPS, and dispatches a deploy.
92
+
93
+ ## DNS
94
+
95
+ `--dns-only` prints the exact records. In short:
96
+
97
+ | Hostname | Records |
98
+ |---|---|
99
+ | Bare domain (`example.com`) | all four GitHub Pages **A** records |
100
+ | Subdomain (`www.example.com`) | one **CNAME** → `<org>.github.io` |
101
+
102
+ **In Cloudflare the record must be DNS-only (grey cloud), not proxied.** The
103
+ orange-cloud proxy blocks the ACME challenge GitHub uses to issue the
104
+ certificate, and promote will refuse with a `cloudflare-proxied` message.
105
+
106
+ `www` is not automatic. If a bare domain is the target and the client also wants
107
+ `www.example.com` to work, add a `www` CNAME → `<org>.github.io` at the
108
+ registrar; GitHub then redirects it to the apex on its own. Nothing in Scribe
109
+ needs to change for that.
110
+
111
+ ## Re-running is the fix
112
+
113
+ Promotion is idempotent and there is deliberately no `site demote`. If a run
114
+ fails partway it prints which steps completed; fix the cause and run the same
115
+ command again.
116
+
117
+ The most common "failure" isn't one: setting a custom domain clears
118
+ `https_enforced`, and GitHub refuses to turn it back on until the certificate
119
+ exists. Promote retries at the end and warns if it isn't ready. Wait a few
120
+ minutes, re-run, done. Until then `http://` won't serve the site.
121
+
122
+ ## Repairing a half-finished cutover
123
+
124
+ The three pieces of state — the `Site` record's domain, the Pages custom domain,
125
+ and `public/CNAME` — can each be set independently, and nothing in
126
+ `scribe site verify` currently compares them. A half-state looks healthy and
127
+ serves nothing.
128
+
129
+ | Symptom | What's missing |
130
+ |---|---|
131
+ | Domain 404s, no certificate ever issued | Pages custom domain not registered |
132
+ | Page loads but every asset/link 404s | `public/CNAME` missing, so the build still uses `base=/<repo>` |
133
+ | Old `github.io/<repo>` URL broke and the new domain isn't up | `public/CNAME` landed before the Pages domain was set |
134
+ | Form rejected with a CORS error on the new host | `Site` record still names the old domain |
135
+
136
+ The fix for all four is the same: `scribe site promote <site> --domain
137
+ example.com`. It reconciles whatever is already correct and writes the rest.
138
+
139
+ ## `site update --domain` is not this
140
+
141
+ ```
142
+ scribe site update <site> --domain example.com # record + Turnstile ONLY
143
+ ```
144
+
145
+ It rewrites the stored hostname and syncs the Turnstile widget. It does not
146
+ touch DNS, `public/CNAME`, or Pages. Using it to go live leaves a site that
147
+ claims a domain serving nothing — the exact half-state above. The CLI now
148
+ refuses when it detects a cutover (a site still on `<org>.github.io` being moved
149
+ to a real hostname) and points at `promote`; `--record-only` overrides it.
150
+
151
+ Legitimate use: correcting a typo in a domain that is already live.
@@ -310,8 +310,11 @@ inbox already saying so.
310
310
 
311
311
  - **Two forms, one page:** fine, as long as element ids are unique. The snippet
312
312
  above scopes everything to `scribe-form-<collection>`.
313
- - **`notify:` does not send email.** It's parsed and logged by a console mailer
314
- only. Don't promise a client notifications (RFC 0009 is the real thing).
313
+ - **New submissions are emailed** (RFC 0009), but `notify:` is only for addresses
314
+ with **no Scribe account** — a shared `office@` inbox. Site users on the Site are
315
+ subscribed by default and manage it themselves under Your Profile → Email
316
+ notifications; agency admins are opted out by default and opt in per Site. Adding
317
+ a Scribe user's address to `notify:` is redundant, not helpful.
315
318
  - **Rate limiting is per Site and IP, not per form**, so hammering one form while
316
319
  testing will also throttle the others.
317
320
  - **An unchecked checkbox in `FormData` is absent, not `false`.** The payload