@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,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
@@ -0,0 +1,192 @@
1
+ # Recipe: prospects
2
+
3
+ Ashley's calling pipeline lives in Scribe as Clients with `status: 'prospect'`.
4
+ This recipe is about **recording and nurturing prospects she already has** —
5
+ logging what happened on a call, scheduling the next touch, and moving one
6
+ through the funnel. Finding new leads is not covered here: wherever a name comes
7
+ from (a Places sweep, a referral, a drive-by), this recipe starts once it exists.
8
+
9
+ The same operations are available two ways — `scribe prospect …` on the command
10
+ line, and the `list_prospects` / `get_prospect` / `create_prospect` /
11
+ `log_prospect_call` / `set_prospect_status` MCP tools. They are the same
12
+ endpoints; use whichever the session has.
13
+
14
+ §3 is the part to actually read. Everything else is mechanics.
15
+
16
+ ## 1. The pipeline
17
+
18
+ Four statuses, and a prospect only ever sits in the first one:
19
+ `prospect` → `active` (won) or `lost`, with `active` ↔ `churned` afterwards for
20
+ a real client. Nothing is deleted on the way through — a lost prospect keeps its
21
+ whole call log, because the reason it was lost is the point of recording it.
22
+
23
+ ```
24
+ scribe prospect create "A+ Plumbing" \
25
+ --contact-name "Dana" --phone "555-0101" \
26
+ --note "Found on the Yelp sweep — no website, 4.6 stars over 90 reviews"
27
+
28
+ scribe prospect list --due # who to call today
29
+ scribe prospect show <id> # the full call log before dialling
30
+
31
+ scribe prospect log <id> \
32
+ --note "Left a voicemail, no callback yet" \
33
+ --outcome left-message \
34
+ --next 2026-10-14
35
+
36
+ scribe prospect status <id> active # won
37
+ scribe prospect status <id> lost --reason "Nephew builds their sites"
38
+ ```
39
+
40
+ `list` defaults to a table and takes `--json` for parsing; so does every other
41
+ subcommand. `--status` and `--search` filter, and the default list is
42
+ due-sorted — soonest or most overdue first, undated prospects last.
43
+
44
+ ## 1b. Always pass `--outcome` on a real call
45
+
46
+ `--outcome` is how far the attempt got, shallowest to deepest:
47
+
48
+ | Outcome | Use when |
49
+ |---|---|
50
+ | `no-answer` | Nobody picked up |
51
+ | `left-message` | Voicemail, or a message left with someone |
52
+ | `gatekeeper` | Spoke to a person, but not the decision-maker |
53
+ | `reached` | Spoke to the decision-maker |
54
+ | `meeting-set` | Spoke to them **and** got a forward commitment |
55
+
56
+ **It is optional in the CLI and should be treated as required for any note that
57
+ describes a contact attempt.** It is the only input to the prospect's warmth, so
58
+ a call logged without it leaves the pipeline reading colder and less-worked than
59
+ it really is — and a prospect that was genuinely called three times without an
60
+ outcome recorded will never show as `stalled`, so Ashley keeps calling a dead
61
+ number.
62
+
63
+ Leave it off only for a note that wasn't an attempt: research, a finding from a
64
+ sweep, a reminder to self.
65
+
66
+ ## 1c. Reading warmth
67
+
68
+ Every prospect carries a derived `warmth` plus an attempt count. Nothing stores
69
+ it — it is computed from the call log every time it's read, so it changes on its
70
+ own as a prospect goes quiet.
71
+
72
+ | Warmth | Means | Worth more effort? |
73
+ |---|---|---|
74
+ | `hot` | Meeting set, recently | Yes — this is the closest to a sale |
75
+ | `warm` | Real conversation, recently | Yes |
76
+ | `untouched` | Nobody has called yet | Yes — needs a first attempt |
77
+ | `cold` | Tried a few times, or a conversation that went quiet | Yes — re-warmable |
78
+ | `stalled` | Called repeatedly and **never once** reached a human | No — this one is spent |
79
+
80
+ `stalled` is the only warmth that means stop. Note that a prospect you once
81
+ reached can never stall, however many no-answers follow — a conversation
82
+ happened, so going quiet is re-warmable rather than unreachable.
83
+
84
+ Sort with `--sort warm` to rank by likelihood to buy, or `--sort neglected` to
85
+ find prospects nobody has followed up on. `--sort neglected` is worth running
86
+ periodically: a prospect with no follow-up date never appears in `--due` at all,
87
+ so it's the only way those surface.
88
+
89
+ ## 1d. "Do I need more leads?"
90
+
91
+ ```
92
+ scribe prospect pipeline
93
+ ```
94
+
95
+ Reports counts per warmth and a **workable** total, which is everything except
96
+ `stalled`. Read the shape, not the total — a wall of `cold` that hasn't hit the
97
+ stall cutoff is a calling backlog and the answer is "go call them", while a wall
98
+ of `stalled` with workable near zero is a real shortage and the answer is "go
99
+ find names". A low workable count is the signal to prospect; a low total with
100
+ healthy warmth is not.
101
+
102
+ ## 2. Logging a call is the only way to reschedule
103
+
104
+ `scribe prospect log` is the one command that moves `nextFollowUpAt`. There is
105
+ no "set the follow-up date" command, deliberately: a date that changed with no
106
+ note is a reschedule nobody can explain a month later. Every change to when
107
+ Ashley next calls someone arrives attached to a dated note saying why.
108
+
109
+ - `--note <text>` is **required**. A reschedule with nothing to say is still
110
+ worth a sentence ("no answer, trying again Tuesday").
111
+ - `--next <date>` (re)schedules; `--clear-next` removes the scheduled follow-up
112
+ entirely. They're mutually exclusive.
113
+ - Omit both and the note is appended with the existing follow-up untouched.
114
+
115
+ Notes are append-only. Nothing edits or deletes one, from either surface.
116
+
117
+ ## 3. What to do unprompted, and what to ask about first
118
+
119
+ Nothing below is enforced by the CLI or the API — there is no approval flag to
120
+ set and no permission that blocks any of it. It is a judgment call every time,
121
+ and this table is the judgment. Ashley's use of Claude here is a long-term
122
+ mixture of "just handle it" and "check with me first"; getting this wrong in the
123
+ cautious direction wastes a message, and getting it wrong in the other direction
124
+ means she finds out a prospect was written off without her.
125
+
126
+ | Action | Do it, or propose it? |
127
+ |---|---|
128
+ | `prospect log` — recording a call, a voicemail, a reschedule | **Do it.** This is the routine nurture action. It's additive, it's reversible by logging again, and asking first defeats the point. Include `--outcome`. |
129
+ | `prospect list` / `prospect show` / `prospect pipeline` | **Do it.** Read-only. |
130
+ | `prospect create` | **Check for a near-duplicate first** — `prospect list --search "<part of the name>"`. A clean miss means create it. Anything that looks like the same business under a different spelling, propose it rather than creating a second record. |
131
+ | `prospect update` — a name fix, a new phone number | **Do it** when the correction is unambiguous (a typo, a number from a call she just logged). Propose a wholesale rename. |
132
+ | `prospect status … lost` | **Propose it, with the reason, and wait for a yes.** It removes the prospect from her working list. Being wrong here looks identical to her forgetting about them. |
133
+ | `prospect status … active` | **Always Ashley's call.** A won deal is a business fact she knows and the data doesn't. Never infer it from an enthusiastic note. |
134
+
135
+ The two `status` rows are the load-bearing ones. When proposing either, say which
136
+ prospect, which reason, and what the last note said — enough that she can answer
137
+ without opening the console.
138
+
139
+ ## 4. Where this is a contract, and where it's advice
140
+
141
+ | Contract | Advice |
142
+ |---|---|
143
+ | `nextFollowUpAt` changes **only** through `prospect log` — nothing else writes it | How far out to schedule a follow-up |
144
+ | Notes are append-only; no surface edits or deletes one | How much detail to put in a note |
145
+ | A status move never deletes anything, including `lost` | When to give up on a prospect |
146
+ | Deletion isn't in the CLI or the API at all — it's console-only | — |
147
+ | Warmth is derived from the call log; there's no field to set | Which warmth is worth your attention today |
148
+ | `outcome` is a **contact result** only — there is no `demo-sent` | Whether a given note needs an outcome at all |
149
+
150
+ ## Gotchas
151
+
152
+ - **A prospect's demo site is a real Scribe Site.** Ashley builds it in the CMS
153
+ because the CMS is what she's selling — own repo in the org, same as a
154
+ production site (RFC 0006). Create it with the normal `scribe site add`
155
+ flow; a won deal provisions nothing new, it just points `site.clientId` at the
156
+ now-`active` Client. RFC 0001's original summary describes a shared static
157
+ `demos` repo — that is historical and was never built. Don't repeat it.
158
+ - **Attaching that Site to the Client is console-only.** There is no `scribe`
159
+ command and no `--client` flag on `site add`: it's the **Client** box on
160
+ Staff console → **Sites** → the site. So "they bought" is two actions — the
161
+ status move, which you can run, and the attach, which you hand over as a step
162
+ for Ashley to click. Say both; an `active` Client whose Site is still attached
163
+ to nobody reads as "nobody is being billed for it" on that page.
164
+ - **`lost` is a status, not a delete.** A lost prospect keeps its whole call log
165
+ and gains a `lostReason`. It also stays in the default list — both
166
+ `prospect list` and the console's prospects view cover `prospect` **and**
167
+ `lost` — so narrow with `--status prospect` to see only live ones. What a
168
+ lost prospect does drop out of is the due queue: `--due` is prospect-only.
169
+ - **There is no `scribe prospect delete`**, and no `DELETE` under
170
+ `/api/v1/prospects`. Deleting a Client is reachable only from the staff
171
+ console, behind its confirmation dialog. If something genuinely needs
172
+ removing, that's a thing to hand to Ashley, not to work around.
173
+ - **Every prospect command needs a staff-role API key.** A non-staff key gets a
174
+ 403 from the whole `/api/v1/prospects` surface, and a read-only key gets one
175
+ from everything except `list` and `show`.
176
+ - **`--due` is "now or overdue", not "upcoming".** A prospect scheduled for next
177
+ week won't appear until the date arrives. Use `prospect list` for the whole
178
+ funnel.
179
+ - **A prospect with no follow-up scheduled sorts last, not first.** Undated
180
+ isn't urgent — but it does mean nobody has decided what happens next, so it's
181
+ worth surfacing to Ashley if one sits there. `--sort neglected` is how.
182
+ - **`demo-sent` is not an outcome**, and neither is `not-interested`. The first
183
+ is just a note; the second is `prospect status <id> lost --reason ...`.
184
+ Outcomes record how far a *contact attempt* got, nothing else.
185
+ - **Warmth decays on its own.** A `hot` prospect you don't log anything against
186
+ becomes `warm` and then `cold` with no action from anyone, because the clock
187
+ moved. That's intended — it's what stops a stale meeting looking like your
188
+ best lead — but it means warmth you read last week may differ today.
189
+ - **Don't treat warmth as a prediction.** It's a triage order. The real buying
190
+ signal is usually in the note prose ("lost a bid because the GC couldn't find
191
+ them online"), which the enum flattens. Read the notes before telling Ashley
192
+ who's closest to buying; use the sort to decide where to start reading.
@@ -0,0 +1,145 @@
1
+ # Recipe: requests
2
+
3
+ A **Request** is a Client asking the Agency to do work — a content change, a new
4
+ page, a bug report, a question (RFC 0005). It is the mirror image of a
5
+ submission, and conflating the two is the mistake to avoid:
6
+
7
+ | | Who wrote it | Who owes the answer | Where it lives |
8
+ |---|---|---|---|
9
+ | **Submission** | a Visitor, through the Client's form | the Client | `scribe submission …`, per site + collection |
10
+ | **Request** | the Client, through their dashboard | **the Agency — you** | `scribe request …`, one queue across every site |
11
+
12
+ Requests belong to the relationship rather than to a repo: nothing about one is
13
+ in the site's files, and archiving a site doesn't touch them.
14
+
15
+ §4 is the part to actually read. The rest is mechanics.
16
+
17
+ ## 1. The queue
18
+
19
+ ```
20
+ scribe request list # the queue, oldest activity first
21
+ scribe request list --unassigned # nobody has picked these up
22
+ scribe request list --mine # claimed by this key's owner
23
+ scribe request list --site rods-auto-body # one site's requests
24
+ scribe request list --status done --all # finished ones, normally hidden
25
+ scribe request show <id> # the ask + the whole thread
26
+ ```
27
+
28
+ `list` is the staff queue, so it defaults to **oldest first** — a queue is worked
29
+ from the end that has waited longest — and **hides `done`/`cancelled`**. Asking
30
+ for a closed status by name shows it anyway, so `--status done` doesn't come back
31
+ empty. `--json` on anything; the table prints the ids you need.
32
+
33
+ `total` in the JSON (and the line under the table) is the whole result, not the
34
+ page. A queue quietly showing 25 of 60 is how one stops being believed.
35
+
36
+ **Every command here needs a staff-role key.** A site user's key gets a 403 from
37
+ the whole surface, because a thread carries internal notes and claiming one only
38
+ means something from inside the Agency. A key restricted to specific sites must
39
+ pass `--site`, rather than being handed a "queue" that is silently a subset.
40
+
41
+ ## 2. The statuses, and the one that reads backwards
42
+
43
+ Fixed by the platform — unlike a form's workflow, there is nothing per-site to
44
+ read out of `.scribe.yml`.
45
+
46
+ | Status | Means | Who owes the next action |
47
+ |---|---|---|
48
+ | `open` | Filed, nobody has picked it up | **The Agency** |
49
+ | `in-progress` | Picked up, and the Client has been told | The Agency |
50
+ | `waiting-on-you` | **Waiting on the *Client*** | The Client |
51
+ | `done` | Finished | Nobody |
52
+ | `cancelled` | Dropped | Nobody |
53
+
54
+ **`waiting-on-you` is phrased from the Client's side**, because they read it on
55
+ their own dashboard — it is the Client who is being waited on, not Ashley. Read
56
+ as an agency instruction it means the opposite of what it says, and it is the one
57
+ status an agent will get backwards. The CLI prints "Waiting on client" beside it
58
+ for exactly this reason.
59
+
60
+ Use it only when the work genuinely cannot continue without an answer: it puts a
61
+ "needs you" badge on the Client's dashboard. When they reply, the request comes
62
+ back to `open` on its own.
63
+
64
+ ## 3. Working one
65
+
66
+ ```
67
+ scribe request claim <id> # take it; --release hands it back
68
+ scribe request reply <id> --message "On it — today."
69
+ scribe request reply <id> --message "DNS still at their old host" --internal
70
+ scribe request status <id> waiting-on-you
71
+ scribe request status <id> done
72
+ ```
73
+
74
+ The loop that matches how the queue is meant to read:
75
+
76
+ 1. `request show <id>` — the whole thread, before touching anything.
77
+ 2. `request claim <id>`.
78
+ 3. A short visible reply saying it's picked up. **A reply on an `open` request
79
+ moves it to `in-progress` by itself** — don't follow it with a `status` call.
80
+ 4. Do the work. Usually this is an ordinary content/design change in the site's
81
+ repo: the request names its site, and `page` often names the page.
82
+ 5. Reply with what changed, including the URL, **then** `status done`.
83
+
84
+ **Never mark a request done without a visible reply.** From the Client's side a
85
+ request that silently turns Done is indistinguishable from being ignored, and
86
+ they have no way to see what was changed.
87
+
88
+ An **internal note** (`--internal`) is Agency-only: filtered out of every read a
89
+ Client can reach, and it emails nobody. It also moves no status — thinking out
90
+ loud isn't picking something up. Use it for what you found, especially anything
91
+ that needs Dave.
92
+
93
+ ## 4. What to do unprompted, and what to show her first
94
+
95
+ Nothing below is enforced. There is no approval flag on any of these commands,
96
+ and the API will do whatever it is asked.
97
+
98
+ | Action | Do it, or propose it? |
99
+ |---|---|
100
+ | `request list` / `request show` | **Do it.** Read-only. |
101
+ | `request claim` | **Do it.** It's a label saying someone is on this, and `--release` undoes it. |
102
+ | `request reply --internal` | **Do it.** The Client never sees it, nothing is emailed, and a note nobody needed is harmless. |
103
+ | The actual work — a content change, a fix, a new page | **Do it**, to the same standard as any other edit: preview before publishing, and say what you changed. |
104
+ | `request reply` (visible) | **Show her the text first**, unless she has said otherwise this session. It is emailed to the Client the moment it's sent, it is permanent, and it speaks for her business. |
105
+ | `request status done` / `cancelled` | **Propose it.** Closing a request is a claim that the work is finished, and the Client reads the close. |
106
+ | `request status waiting-on-you` | **Propose it**, and say what you'd be asking for. It parks the request on the Client's desk. |
107
+
108
+ The visible-reply row is the load-bearing one, and it is the difference between
109
+ a useful agent and an embarrassing one. "Draft it and let me read it" is the
110
+ default posture; she can and will say "just send them" for a run of routine
111
+ ones, which is a fine thing to honour for that session.
112
+
113
+ A good way to offer the whole thing, if she asks you to work the queue: do the
114
+ changes, show each reply before sending, propose each close.
115
+
116
+ ## 5. Where this is a contract, and where it's advice
117
+
118
+ | Contract | Advice |
119
+ |---|---|
120
+ | A Request is created **only** by the Client — there is no `scribe request create`, and no POST on `/api/v1/requests` | What to do when they ask by phone instead |
121
+ | A visible reply on an `open` request moves it to `in-progress` | How quickly to acknowledge |
122
+ | Internal notes are filtered server-side from every Client-reachable read | How much to put in one |
123
+ | Messages are append-only — nothing edits or deletes one | — |
124
+ | A visible reply or a status change emails the Client; an internal note does not | — |
125
+ | `assignTo` is only ever "me" or "nobody" — assigning someone else isn't built | When to release one |
126
+ | Nothing is ever deleted, including a cancelled request | — |
127
+
128
+ ## Gotchas
129
+
130
+ - **There is no `create`.** A Request filed by the Agency on the Client's behalf
131
+ would put words in their mouth and make the queue a measure of us rather than
132
+ of them. If a Client asks for something by phone, either do it as an ordinary
133
+ edit or ask them to file it so there's a thread. Say that rather than looking
134
+ for a flag.
135
+ - **A reply is email.** Each one reaches the Client's inbox immediately; there is
136
+ no draft state and no recall. Compose it as the message it is.
137
+ - **Claiming is Agency-private.** The Client never sees who holds their request —
138
+ the assignment writes an internal note, not a thread message, so "why isn't
139
+ Ashley on this one?" is a question they can't ask.
140
+ - **Re-running a status change is safe.** A no-op transition writes no message
141
+ and sends no email, so setting `done` twice doesn't double-notify.
142
+ - **`--all` is history, not a wider filter.** It only adds `done` and
143
+ `cancelled`; everything else is in the default list already.
144
+ - **Requests survive their site being archived**, and are unreachable through
145
+ anything in the repo. Don't go looking for them in the site's files.
@@ -129,6 +129,30 @@ produced a clean commit, a green deploy, and no tracking at all.
129
129
  Cookieless, so it needs no consent banner, and `defer` keeps it off the
130
130
  critical path.
131
131
 
132
+ ## Changing a collection that already holds content
133
+
134
+ `scribe schema apply <site> --file <spec>` upserts **one whole collection by
135
+ name** — so the spec you send *is* the collection afterwards. A field you leave
136
+ out is a field you removed. Read the current definition first
137
+ (`scribe schema show <site> <collection>`), edit that, and send it back; never
138
+ write a spec from memory for a collection that already exists.
139
+
140
+ What each kind of change costs, because a schema edit and a content edit are
141
+ different risks:
142
+
143
+ | Change | Effect on existing entries |
144
+ |---|---|
145
+ | Add a field | Harmless. Existing entries read it as empty. Add `required: true` only if you're also filling it in, or every existing entry becomes invalid. |
146
+ | Remove a field | The content files keep the value, but nothing reads it and the admin stops showing it. Effectively lost. |
147
+ | Rename a field | There is no rename — it is a remove plus an add, and the old values do not follow. Moving them means editing every content file. |
148
+ | Change a field's type | The declared type generates the build's validation, so an entry whose value no longer fits **breaks the site build** rather than failing quietly. |
149
+ | Remove a collection | `scribe schema remove` **deletes its content folder too**. |
150
+
151
+ So when someone asks to "rename that field" or "change services to use a number
152
+ for price", say what it will do to the entries that already exist before you run
153
+ it. On a form collection the same applies to its submissions, which are in the
154
+ database and far harder to reconcile than files in a repo.
155
+
132
156
  ## From the CLI instead
133
157
 
134
158
  Useful when you want the schema without a checkout, or to inspect it while