@scribe-platform/cli 0.6.0 → 0.8.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 (47) hide show
  1. package/README.md +12 -3
  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/prospect.d.ts +27 -0
  6. package/dist/commands/prospect.d.ts.map +1 -0
  7. package/dist/commands/prospect.js +213 -0
  8. package/dist/commands/prospect.js.map +1 -0
  9. package/dist/commands/request.d.ts +38 -0
  10. package/dist/commands/request.d.ts.map +1 -0
  11. package/dist/commands/request.js +214 -0
  12. package/dist/commands/request.js.map +1 -0
  13. package/dist/commands/site.d.ts +11 -0
  14. package/dist/commands/site.d.ts.map +1 -1
  15. package/dist/commands/site.js +85 -6
  16. package/dist/commands/site.js.map +1 -1
  17. package/dist/commands/update.d.ts +10 -0
  18. package/dist/commands/update.d.ts.map +1 -1
  19. package/dist/commands/update.js +128 -31
  20. package/dist/commands/update.js.map +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +4 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/lib/claude-hook.d.ts +74 -0
  25. package/dist/lib/claude-hook.d.ts.map +1 -0
  26. package/dist/lib/claude-hook.js +108 -0
  27. package/dist/lib/claude-hook.js.map +1 -0
  28. package/dist/lib/client.d.ts +199 -1
  29. package/dist/lib/client.d.ts.map +1 -1
  30. package/dist/lib/client.js +91 -2
  31. package/dist/lib/client.js.map +1 -1
  32. package/dist/lib/skill.d.ts +13 -0
  33. package/dist/lib/skill.d.ts.map +1 -1
  34. package/dist/lib/skill.js +33 -2
  35. package/dist/lib/skill.js.map +1 -1
  36. package/dist/lib/update-check.d.ts +46 -0
  37. package/dist/lib/update-check.d.ts.map +1 -0
  38. package/dist/lib/update-check.js +103 -0
  39. package/dist/lib/update-check.js.map +1 -0
  40. package/package.json +1 -1
  41. package/skills/scribe-site-builder/SKILL.md +207 -52
  42. package/skills/scribe-site-builder/recipes/README.md +10 -3
  43. package/skills/scribe-site-builder/recipes/domains.md +151 -0
  44. package/skills/scribe-site-builder/recipes/forms.md +5 -2
  45. package/skills/scribe-site-builder/recipes/prospects.md +192 -0
  46. package/skills/scribe-site-builder/recipes/requests.md +145 -0
  47. package/skills/scribe-site-builder/recipes/schema.md +24 -0
@@ -0,0 +1,46 @@
1
+ export declare const PACKAGE_NAME = "@scribe-platform/cli";
2
+ /**
3
+ * Compares two dotted numeric versions. Returns true when `candidate` is newer
4
+ * than `current`.
5
+ *
6
+ * Deliberately not a semver dependency: this package publishes plain
7
+ * `major.minor.patch` and the one judgement call — what to do with a
8
+ * prerelease suffix — is answered by ignoring it. A `0.8.0-beta.1` compares
9
+ * equal to `0.8.0`, so someone already on the beta is not told to "update" to
10
+ * the release they effectively have, and someone on `0.7.0` is offered it.
11
+ * Shipping a prerelease to `latest` is not something this project does.
12
+ */
13
+ export declare function isNewer(candidate: string, current: string): boolean;
14
+ export interface UpdateCheck {
15
+ current: string;
16
+ /** Null when the registry couldn't be reached. */
17
+ latest: string | null;
18
+ updateAvailable: boolean;
19
+ /**
20
+ * False when the registry could not be reached.
21
+ *
22
+ * Reported separately from `latest` so a caller can't say "up to date" when
23
+ * it means "couldn't ask" — the two are the same shape and only one of them
24
+ * is a fact.
25
+ */
26
+ reachable: boolean;
27
+ }
28
+ /**
29
+ * Whether a newer CLI has been published.
30
+ *
31
+ * Runs on **every** session start, with no cache. An earlier version cached
32
+ * the answer for a day to keep a round trip off the front of each
33
+ * conversation; measuring it made that look like the wrong trade — 210ms
34
+ * against being up to a day late on a fix, plus a cache that silently
35
+ * swallowed the environment override this is tested with. Checking every time
36
+ * is both faster to the user and less code.
37
+ */
38
+ export declare function checkForUpdate(current: string): Promise<UpdateCheck>;
39
+ /**
40
+ * What `npm install -g` is pointed at.
41
+ *
42
+ * Overridable so the whole update path can be exercised against a local
43
+ * `npm pack` tarball before anything is published — see TESTING-UPDATES.md.
44
+ */
45
+ export declare function installSpec(): string;
46
+ //# sourceMappingURL=update-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-check.d.ts","sourceRoot":"","sources":["../../src/lib/update-check.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,yBAAyB,CAAC;AAsBnD;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAiBnE;AAuBD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAe1E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
@@ -0,0 +1,103 @@
1
+ export const PACKAGE_NAME = '@scribe-platform/cli';
2
+ /**
3
+ * The published package is **public**, so this is a plain unauthenticated GET
4
+ * — no `npm view` subprocess, no auth, no dependency on the admin app knowing
5
+ * which CLI version is current.
6
+ *
7
+ * The `-/package/<name>/dist-tags` route rather than the package document:
8
+ * it answers in ~200ms with 18 bytes, where the full packument is 30KB of
9
+ * every version ever published. At that price the check can run at every
10
+ * session start instead of being cached, which is the difference between
11
+ * Ashley getting a fix now and getting it tomorrow.
12
+ */
13
+ const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
14
+ /**
15
+ * Short on purpose. This runs at session start, in front of someone waiting to
16
+ * type, and a registry that is slow is indistinguishable from one that is down
17
+ * — in both cases the right answer is to stop waiting and carry on unupdated.
18
+ */
19
+ const FETCH_TIMEOUT_MS = 3_000;
20
+ /**
21
+ * Compares two dotted numeric versions. Returns true when `candidate` is newer
22
+ * than `current`.
23
+ *
24
+ * Deliberately not a semver dependency: this package publishes plain
25
+ * `major.minor.patch` and the one judgement call — what to do with a
26
+ * prerelease suffix — is answered by ignoring it. A `0.8.0-beta.1` compares
27
+ * equal to `0.8.0`, so someone already on the beta is not told to "update" to
28
+ * the release they effectively have, and someone on `0.7.0` is offered it.
29
+ * Shipping a prerelease to `latest` is not something this project does.
30
+ */
31
+ export function isNewer(candidate, current) {
32
+ const parts = (v) => v
33
+ .split('-')[0]
34
+ .split('.')
35
+ .map((n) => Number.parseInt(n, 10))
36
+ .map((n) => (Number.isNaN(n) ? 0 : n));
37
+ const a = parts(candidate);
38
+ const b = parts(current);
39
+ for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
40
+ const left = a[i] ?? 0;
41
+ const right = b[i] ?? 0;
42
+ if (left !== right)
43
+ return left > right;
44
+ }
45
+ return false;
46
+ }
47
+ /** The registry's `latest` dist-tag, or null if it can't be reached in time. */
48
+ async function fetchLatest() {
49
+ const registry = process.env.SCRIBE_CLI_REGISTRY ?? DEFAULT_REGISTRY;
50
+ const url = `${registry}/-/package/${PACKAGE_NAME.replace('/', '%2F')}/dist-tags`;
51
+ const controller = new AbortController();
52
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
53
+ try {
54
+ const res = await fetch(url, { signal: controller.signal });
55
+ if (!res.ok)
56
+ return null;
57
+ const body = (await res.json());
58
+ return body.latest ?? null;
59
+ }
60
+ catch {
61
+ // Offline, slow, rate-limited, or a registry that answered with something
62
+ // unexpected. All the same from here: no opinion about updates.
63
+ return null;
64
+ }
65
+ finally {
66
+ clearTimeout(timer);
67
+ }
68
+ }
69
+ /**
70
+ * Whether a newer CLI has been published.
71
+ *
72
+ * Runs on **every** session start, with no cache. An earlier version cached
73
+ * the answer for a day to keep a round trip off the front of each
74
+ * conversation; measuring it made that look like the wrong trade — 210ms
75
+ * against being up to a day late on a fix, plus a cache that silently
76
+ * swallowed the environment override this is tested with. Checking every time
77
+ * is both faster to the user and less code.
78
+ */
79
+ export async function checkForUpdate(current) {
80
+ // Ahead of the fetch and never persisted. This is the knob the local test
81
+ // recipe turns — see TESTING-UPDATES.md.
82
+ const forced = process.env.SCRIBE_CLI_LATEST;
83
+ if (forced) {
84
+ return { current, latest: forced, updateAvailable: isNewer(forced, current), reachable: true };
85
+ }
86
+ const latest = await fetchLatest();
87
+ return {
88
+ current,
89
+ latest,
90
+ updateAvailable: latest !== null && isNewer(latest, current),
91
+ reachable: latest !== null,
92
+ };
93
+ }
94
+ /**
95
+ * What `npm install -g` is pointed at.
96
+ *
97
+ * Overridable so the whole update path can be exercised against a local
98
+ * `npm pack` tarball before anything is published — see TESTING-UPDATES.md.
99
+ */
100
+ export function installSpec() {
101
+ return process.env.SCRIBE_CLI_PACKAGE ?? `${PACKAGE_NAME}@latest`;
102
+ }
103
+ //# sourceMappingURL=update-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-check.js","sourceRoot":"","sources":["../../src/lib/update-check.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scribe-platform/cli",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Command-line interface for Scribe CMS — scaffold sites, edit content, and triage form submissions from the terminal",
5
5
  "license": "MIT",
6
6
  "author": "Dave Weaver",
@@ -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, 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), and for the agency's prospect pipeline (logging calls, follow-ups, won/lost). 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, 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,6 +27,9 @@ 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) |
25
33
 
26
34
  [`recipes/README.md`](./recipes/README.md) lists the five things that are a hard
27
35
  contract (because Scribe writes into the repo) as opposed to advice.
@@ -32,6 +40,16 @@ contract (because Scribe writes into the repo) as opposed to advice.
32
40
  repo. The admin app and the `scribe` CLI are both just clients that read/write
33
41
  files in it via the GitHub API; a local `git clone` is a third client reading/writing
34
42
  the same files via git.
43
+ - **Scribe owns the repo's GitHub Pages settings — never set them by hand.** The
44
+ custom domain, `public/CNAME`, HTTPS enforcement and the build's base path are
45
+ all written by `scribe site promote`, in one idempotent run. Do **not** reach for
46
+ `gh api .../pages`, the GitHub web UI, or a hand-written `public/CNAME`: setting
47
+ one without the others leaves the site 404ing, and the Pages call needs repo
48
+ admin that a site user doesn't have. The CLI doesn't need it — the admin app
49
+ makes that call with Scribe's own org token, so a write-scoped API key is enough.
50
+ If something about a domain looks like it needs a GitHub permission you lack,
51
+ that's the signal you're on the wrong path, not that you need Dave.
52
+ See [`recipes/domains.md`](./recipes/domains.md).
35
53
  - **Form submissions are the exception — they live in MongoDB, not the repo** (RFC
36
54
  0004). A Visitor's name, email and message used to be committed as a YAML file into
37
55
  the site's public repo, which published them to the open internet. Nothing writes
@@ -60,41 +78,61 @@ contract (because Scribe writes into the repo) as opposed to advice.
60
78
 
61
79
  ## Setup (once)
62
80
 
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.
81
+ - **CLI**: confirm `scribe --help` works. If it's not found:
82
+ `npm install -g @scribe-platform/cli`. (Working inside a clone of the Scribe
83
+ repo itself? `npm link` from `packages/cli` instead — but that is a
84
+ maintainer's setup, not a user's.)
65
85
  - **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
86
+ admin UI (sign in → account menu → **API Tokens** → New token). This is the
87
+ whole machine setup: it writes `~/.scribe/config.json`, installs this skill,
88
+ and turns on auto-updates. Defaults to the hosted admin at
89
+ `https://scribe.weaverdigital.io`; add `--api-url <url>` only to point at a
90
+ different instance (a local dev/Docker admin). Every other command reads that
91
+ config, no per-invocation `--token` flag. If a command fails with a raw `fetch failed`, that's not an API
71
92
  error — it means `apiUrl` in that config doesn't point at anything listening;
72
93
  re-run `scribe login` with the right URL.
73
94
 
74
95
  ## Keeping Scribe up to date
75
96
 
76
- `scribe update` updates the CLI **and** reinstalls this skill, which ships inside
77
- the `@scribe-platform/cli` npm package:
97
+ **This normally happens by itself and needs no attention.** `scribe login`
98
+ installs this skill and registers a Claude Code `SessionStart` hook that checks npm for
99
+ a newer `@scribe-platform/cli` at the start of **every** session and installs
100
+ it — CLI and this skill together — *before* the session's skills are scanned.
101
+ So a session either started on the current version or was brought to it before
102
+ anyone typed. That is the whole reason the check runs at session start rather
103
+ than mid-conversation: there is never a new skill sitting on disk that the
104
+ running session can't see.
105
+
106
+ If an update did install, the hook says so in this session's context. Don't
107
+ relay it; it's already done.
108
+
109
+ If the hook reports that an update **failed** to install, that is for Dave, not
110
+ for the user — it will be an npm permissions or registry problem. Say so plainly
111
+ and carry on; the old version still works.
112
+
113
+ `scribe update` also runs the whole thing by hand:
78
114
 
79
115
  ```
80
- scribe update
116
+ scribe update # update the CLI + this skill now
117
+ scribe update --check # just report whether a newer version exists
118
+ scribe update --install-hook # repair the auto-update hook if it went missing
81
119
  ```
82
120
 
83
- Two things to know:
121
+ One caveat: this **only works for a global npm install** (`npm install -g
122
+ @scribe-platform/cli`). In a dev checkout of the scribe repo it refuses, and if
123
+ `~/.claude/skills/scribe-site-builder` is a symlink into a checkout it leaves it
124
+ alone — that's an author editing the skill in place.
84
125
 
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.
126
+ Run it by hand when the user says there's a new version, or when a CLI command
127
+ fails in a way that suggests this skill is behind the API (an option the CLI
128
+ doesn't recognise, a command that no longer exists) — the session-start check
129
+ runs before the session, so anything published since then is not here yet.
94
130
 
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).
131
+ **If a hand-run update installs a new version mid-session**, tell the user in one
132
+ sentence, in their words: *"I've updated Scribe — close this chat and start a new
133
+ one so I pick up the new instructions."* Then stop relying on anything in this
134
+ file that the update might have changed; if you need the new text now, read
135
+ `~/.claude/skills/scribe-site-builder/SKILL.md` from disk.
98
136
 
99
137
  ## Workflow: building a new site
100
138
 
@@ -168,6 +206,11 @@ recognise, a command that no longer exists).
168
206
  8. **Domain cutover — only when a customer's own hostname is ready.** Skip this
169
207
  entirely for demo sites; the GitHub Pages default already works.
170
208
 
209
+ If it *isn't* ready — the client may not own a domain, may not know who holds
210
+ it, or may have mail on it — that part is the bulk of the work and is in
211
+ [`recipes/domains.md`](./recipes/domains.md) under "Before the cutover". The
212
+ client buys their own domain, at their own registrar, in their own name.
213
+
171
214
  Check what DNS the domain needs first. This changes nothing:
172
215
  ```
173
216
  scribe site promote my-site --domain example.com --dns-only
@@ -201,12 +244,22 @@ recognise, a command that no longer exists).
201
244
  sync — fix it in the Cloudflare dashboard, or the contact form silently rejects
202
245
  real visitors on that domain.
203
246
 
247
+ **You do not need GitHub repo admin rights for any of this.** The Pages API call
248
+ is made by the admin app using Scribe's org token, so a write-scoped API key is
249
+ enough. If you find yourself checking whether the user can administer the repo,
250
+ you've left the supported path.
251
+
204
252
  `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.
253
+ and the Turnstile widget. Use it to correct a domain, never to cut a site over —
254
+ the CLI now refuses that and points at `promote`. Deeper reference, including how
255
+ to repair a half-finished cutover: [`recipes/domains.md`](./recipes/domains.md).
206
256
 
207
257
  ## Working an existing site
208
258
 
209
259
  ```
260
+ scribe site promote <site> --domain <domain> [--dns-only] # take it live on a real domain
261
+ scribe site update <site> --turnstile-key <key> # correct stored settings only
262
+
210
263
  scribe content list <site> [--collection <name>] [--json]
211
264
  scribe content read <site> <path> [--json]
212
265
  scribe content publish <site> <file> # validates against schema
@@ -223,8 +276,19 @@ scribe submission list <site> <collection> [--status <status>] [--page <n>] [--j
223
276
  scribe submission update <site> <collection> <id> [--status <s>] [--note <n>]
224
277
  scribe submission export <site> <collection> [--format csv|json] [--status <s>] [--out <file>]
225
278
  scribe submission delete <site> <collection> <id> [--yes] # staff only
279
+
280
+ scribe request list [--site <slug>] [--status <s>] [--type <t>] [--mine|--unassigned] [--all] [--page <n>]
281
+ scribe request show <id> # the ask + the whole thread, internal notes included
282
+ scribe request claim <id> [--release]
283
+ scribe request reply <id> --message <text> [--internal]
284
+ scribe request status <id> <open|in-progress|waiting-on-you|done|cancelled>
226
285
  ```
227
286
 
287
+ - **"Publish/launch the site at example.com" means `scribe site promote`** — the full
288
+ domain cutover in one command — step 8 of the build workflow above, and in full in
289
+ [`recipes/domains.md`](./recipes/domains.md). It is not `content publish`, and
290
+ it is not `site update --domain` (which only rewrites the stored record and would
291
+ leave the site 404ing on the new host — the CLI refuses that now and tells you so).
228
292
  - `scribe schema apply` is the way to add a form (or any collection with fields): it
229
293
  takes a YAML/JSON file describing **one whole collection**, upserts it by `name`,
230
294
  and validates through the admin's own parser first — so a reserved `status` field,
@@ -254,6 +318,53 @@ scribe submission delete <site> <collection> <id> [--yes] # staff only
254
318
  **staff** role — a site user's API key gets a 403. It prompts for the id unless
255
319
  `--yes` is passed. There is deliberately no equivalent in the admin UI, so don't go
256
320
  looking for a delete button on the inbox.
321
+ - **`scribe request` is the other queue, and the opposite direction.** A submission is
322
+ a Visitor contacting the *client*; a Request is the **client contacting the agency**
323
+ to ask for work. One cross-site queue, staff-only, nothing in any repo. Three things
324
+ to know before touching it, with the rest in
325
+ [`recipes/requests.md`](./recipes/requests.md):
326
+ - **`waiting-on-you` means waiting on the _client_** — every status is phrased for
327
+ the Client's own dashboard. Read as an instruction to the agency it means the
328
+ opposite of what it says.
329
+ - **A visible `reply` is an email to the client**, sent immediately and permanently.
330
+ Show the user the text before sending unless they've said not to bother.
331
+ `--internal` is the Agency-only note: no email, invisible to the client, moves no
332
+ status.
333
+ - **There is no `create`.** A Request comes from the Client; filing one for them
334
+ would put words in their mouth. Say so rather than hunting for a flag.
335
+
336
+ ## A site's two names, and the jobs only the admin UI can do
337
+
338
+ A site has a **slug** and a **display name**, and only one of them moves.
339
+
340
+ - The **slug** (`riverside-dental`) is the site's identity: the repo name, the
341
+ Pages URL, `?site=` on the submissions endpoint, and the argument to every
342
+ `scribe` command. **There is no rename** — not in the CLI, not in the admin.
343
+ Moving a site to a different slug means provisioning a new site. So when
344
+ someone is choosing a slug, it is worth one sentence of care up front.
345
+ - The **display name** is a label, and is editable any time: in the Staff console,
346
+ **Sites → the site → the Name field** at the top of the Details card, saved on
347
+ blur. It is allowed on archived sites too. `scribe site add --name` sets it at
348
+ creation; nothing in the CLI changes it afterwards.
349
+
350
+ A rename reaches the consoles and notification subjects and **nothing a Visitor
351
+ sees** — not the URL, and not `.scribe.yml`'s `siteName`, which is the website's
352
+ own copy of its name in a repo the rename doesn't write to. If the name is wrong
353
+ on the published pages too, that's a separate content edit in the repo.
354
+
355
+ Three other things live only in the Staff console, so route the user there rather
356
+ than hunting for a flag:
357
+
358
+ | Job | Where |
359
+ |---|---|
360
+ | Rename (display name) | Sites → the site → Name |
361
+ | Attach a site to a Client, or detach it | Sites → the site → **Client** |
362
+ | 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 |
363
+
364
+ And one that isn't available at all: **creating a login for a client's own
365
+ people**. There is no `scribe` command for it and no self-serve invite — user
366
+ management is super-admin only. "Give the client access to edit their content" is
367
+ a request to hand to Dave, with the site slug and who needs the login.
257
368
 
258
369
  ## Images — never hotlink, always commit the file
259
370
 
@@ -291,29 +402,62 @@ placeholder / link out to somewhere else on purpose. Don't default to that — t
291
402
  "add a photo of X" as "go get a real image and commit it," not "find any URL that
292
403
  renders."
293
404
 
294
- ## Deleting a site
405
+ ## Taking a site down: archive, restore, delete
295
406
 
296
407
  ```
297
- scribe site delete <slug> [--yes]
408
+ scribe site archive <slug> # offline + read-only; deletes nothing
409
+ scribe site restore <slug> # back online, exactly as it was
410
+ scribe site list --archived # the archived ones (plain `list` hides them)
411
+ scribe site delete <slug> [--yes] # permanent; only works on an archived site
298
412
  ```
299
413
 
300
- Permanent — deletes the GitHub repo (schema, content, Astro source) and every Mongo
301
- record for the site, which since RFC 0004 includes its submissions and their event
302
- history. No undo. Prompts to retype the
303
- slug unless `--yes` is passed. Requires an **unrestricted** API key (no
304
- `siteSlugs` restriction) — same rule as `site add`; a key scoped to only that
305
- site is rejected. Same action is in the admin UI: account menu → **Settings**
306
- (super-admin only) → **Danger zone**.
307
-
308
- Deleting also drops the site's domain from the Turnstile widget — but only if no
309
- other site still uses that domain, so removing one demo never breaks the contact
310
- form on the rest. A `Warning:` in the output means that cleanup didn't happen and
311
- the stale domain is still on the widget; harmless, but tidy it up in Cloudflare.
312
-
313
- Side effect worth knowing: if an API key's `siteSlugs` was only that one site,
314
- deleting it leaves that key with an empty `siteSlugs` array — which the API treats
315
- as **unrestricted** (every site), not inert. A key scoped to exactly the site you
316
- delete comes out of it more powerful, not revoked.
414
+ **Archive is how a site comes down** (RFC 0026). It turns GitHub Pages off, so the
415
+ website stops loading. It archives the repo on GitHub, so it is read-only but still
416
+ there. It stops the site's forms accepting submissions and removes the site from
417
+ every list people work from. **Nothing is deleted**: content, schema, submissions and
418
+ their history, requests, metrics and who had access all stay. `scribe site restore`
419
+ brings it back, turning Pages on and starting a deploy, so the site is live again
420
+ in a minute or two. Both need a read-write key belonging to Agency staff. A site
421
+ user's key is refused, even for their own site.
422
+
423
+ **When someone asks you to delete, remove or take down a site, archive it.** It
424
+ can be undone, so this is an ordinary request for you to carry out, not one to hand
425
+ back:
426
+
427
+ 1. Run `scribe site list` and name the exact slug and its live URL back to them,
428
+ along with anything that sounds like a sibling they want kept (the `-v2` they're
429
+ removing versus the `v1` that's live).
430
+ 2. Tell them what archiving does: the site goes offline and nothing is deleted. It
431
+ only touches that one site, not Scribe, their other sites or their account.
432
+ 3. When they confirm the slug, run `scribe site archive <slug>` and report the output,
433
+ including how to bring it back.
434
+
435
+ `scribe site delete` is a different thing, and it is permanent. It deletes the GitHub
436
+ repo (Astro source, schema, content) and every record Scribe holds for the site,
437
+ including its submissions and their history. There is no undo. It refuses a live site
438
+ with a 409 telling you to archive first, so a single command can never destroy a live
439
+ site. Only run it when the person has explicitly asked for permanent deletion of a
440
+ site that is already archived. "Delete" on its own almost always means "take it
441
+ down", which is what archive does. Without `--yes` it prompts for the slug to be
442
+ retyped. From a shell with no terminal (yours) it exits instead of waiting, so after
443
+ that explicit request it's `--yes`. It needs an **unrestricted** API key (no
444
+ `siteSlugs` restriction), the same rule as `site add`. In the admin, archiving is
445
+ under the site's **Settings → Danger zone**. Restore and **Delete permanently** are on
446
+ the archived site's page in the Staff console: **Sites → Archived**.
447
+
448
+ An archived site's other commands (`content`, `schema`, `submission`, `build`, …)
449
+ return a 409 saying it's archived, because its repo is read-only. Restore it first.
450
+ Its slug stays taken while it's archived, so `site add` with the same slug says so.
451
+
452
+ Archiving and deleting both drop the site's domain from the Turnstile widget, but
453
+ only if no other *live* site still uses it, so removing one demo never breaks the
454
+ contact form on the rest. Restoring adds it back. A `Warning:` in the output means
455
+ that cleanup didn't happen. It's harmless, but tidy it up in Cloudflare.
456
+
457
+ Side effect of the permanent delete worth knowing: if an API key's `siteSlugs` was
458
+ only that one site, deleting it leaves that key with an empty `siteSlugs` array,
459
+ which the API treats as **unrestricted** (every site), not inert. Archiving doesn't
460
+ touch keys at all.
317
461
 
318
462
  ## `.scribe.yml` field reference
319
463
 
@@ -350,10 +494,21 @@ On a **form collection** specifically, since RFC 0004 moved submissions into Mon
350
494
  - **An `image` field is rejected** on a form collection. The old path uploaded a
351
495
  Visitor's file straight into the public repo, and a Mongo document is the wrong place
352
496
  for a blob. Deferred to RFC 0007 (object storage).
353
- - **`notify:` does not send email.** It runs, but through a `ConsoleMailer` that only
354
- logs — no address has ever received anything. Real delivery is RFC 0009. Don't
355
- promise a client they'll be emailed on a new submission; the inbox and its
356
- `needs-you` badge are the actual notification today.
497
+ - **New submissions are emailed** (RFC 0009, on RFC 0021's mailer), and
498
+ `notify:` is no longer how a *person* subscribes. Who gets one:
499
+ - **Site users on that Site are subscribed by default** — a lead nobody hears
500
+ about is the bug this closes, so the people whose business it is are opted in
501
+ unless they opt out.
502
+ - **Agency admins are not**, because they can reach every Site. They opt in per
503
+ Site per collection, under **Your Profile → Email notifications** in the admin.
504
+ - **`notify: [...]` now means addresses with no Scribe account** — a shared
505
+ `office@` inbox. Don't put a Scribe user's address in it; they manage their own.
506
+
507
+ The mail is best-effort and never fails the Visitor's POST, and `Reply-To` is
508
+ the Visitor's own address when the form has a field named `email` — that exact
509
+ name is the only signal, so a form whose address field is called something else
510
+ gets no `Reply-To`. So a client *can* be promised email on a new submission. The
511
+ inbox and its `needs-you` badge remain the thing of record.
357
512
 
358
513
  Per field: `name`, `type`, `label`, `required`, `default`, `pattern`, `hidden`,
359
514
  `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,14 @@ 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
+
18
+ `requests.md` and `prospects.md` are the odd ones out — they're about the
19
+ agency's own work (its queue and its sales pipeline) rather than anything in a
20
+ Site's repo, and they live here because the `scribe` CLI is the interface for
21
+ all of it.
15
22
 
16
23
  ## The part that isn't advice
17
24