@scribed/cli 0.0.0-stage → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2025 Scribed, Inc.
2
+ 2261 Market St Ste 22871, San Francisco, CA 94114
3
+ All Rights Reserved.
4
+
5
+ This software and its associated documentation files (the "Software") are the
6
+ proprietary and confidential property of Scribed, Inc.
7
+
8
+ No part of the Software may be copied, reproduced, distributed, published,
9
+ modified, merged, sublicensed, sold, or otherwise used, in whole or in part,
10
+ without the prior express written permission of Scribed, Inc.
11
+
12
+ Unauthorized use, reproduction, or distribution of the Software, or any portion
13
+ of it, is strictly prohibited and may result in severe civil and criminal
14
+ penalties, and will be prosecuted to the maximum extent possible under law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+ For licensing inquiries, contact: hello@scribed.ai
package/README.md CHANGED
@@ -1,3 +1,542 @@
1
- # Temporary Holding Version
1
+ # @scribed/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Command-line client + MCP server for Scribed — CRM records, phone + texting, inbox, calendar + booking, transcriptions, HR, the Vault + workspace pages, and automations from the terminal or from an MCP host such as Cursor or Claude. The entire domain surface is **derived from the scribed-agent tool catalog**: nothing is hand-duplicated, so a new eligible catalog tool automatically becomes a CLI command (`scribed call <tool>`) and an MCP tool.
4
+
5
+ ## Install
6
+
7
+ Requires Node.js 20 or newer; users do not need Bun.
8
+
9
+ ```bash
10
+ npm install --global @scribed/cli
11
+ scribed --version
12
+
13
+ # Or without a global install:
14
+ npx -y @scribed/cli@latest --version
15
+ ```
16
+
17
+ For local repository development, from the monorepo root:
18
+
19
+ ```bash
20
+ bun install
21
+ cd scribed-cli
22
+ bun run build
23
+ bun link # registers the Node `scribed` bin
24
+ ```
25
+
26
+ The npm bin is a thin Node wrapper over the bundled `dist/index.js`. See `PUBLISHING.md` for the release checklist.
27
+
28
+ ## Login
29
+
30
+ Three credential modes; exactly one is stored at a time.
31
+
32
+ ```bash
33
+ scribed login # browser OAuth: Google, Apple, password, and 2FA
34
+ scribed login --no-open # print the browser URL instead of opening it
35
+ scribed login --password # terminal email/password (+ TOTP) → bearer session
36
+ scribed login --password --email me@acme.com --api-url http://localhost:8081
37
+ scribed login --api-key scribed_api_… # personal API key (Settings → API keys) for CI/scripts
38
+ scribed login --api-key … --no-verify # store it without the GET /v1/tools liveness check
39
+ ```
40
+
41
+ | Mode | Stored credential | How tools execute |
42
+ | --- | --- | --- |
43
+ | `scribed login` (default) | OAuth access + refresh token, bound to the hosted MCP resource | Through the hosted MCP endpoint (`https://app.scribed.ai/mcp`); the CLI refreshes tokens automatically |
44
+ | `scribed login --password` | Better Auth bearer session token | In-process: the agent tool's own `execute` calls `/api/*` with `Authorization: Bearer` |
45
+ | `scribed login --api-key` / `SCRIBED_API_KEY` | Personal `scribed_api_…` key | REST `POST /v1/tools/:name` with the validated arguments as the body |
46
+
47
+ The default login registers a public OAuth client, opens Scribed in the browser, and completes authorization-code + S256 PKCE through a random loopback callback on `127.0.0.1`. It works for Google-only and 2FA accounts without asking for a password in the terminal. Discovery, authorization, token exchange, dynamic registration, and MCP all live on the canonical public origin `https://app.scribed.ai`.
48
+
49
+ ### First-time account setup
50
+
51
+ If you do not have a Scribed account, run `scribed login`, click **Sign up** in the browser, and complete the account wizard, email verification, phone verification, and consent. Then return to the terminal and run `scribed login` again to authorize the CLI. The CLI never collects passwords, phone numbers, verification details, or signup and legal-consent data in browser mode; those remain on `https://app.scribed.ai`.
52
+
53
+ ### Credentials and environment
54
+
55
+ Credentials live in `${XDG_CONFIG_HOME:-~/.config}/scribed/config.json` (directory `0700`, file `0600`, atomically replaced). Environment overrides for CI always win over the file:
56
+
57
+ | Variable | Purpose |
58
+ | --- | --- |
59
+ | `SCRIBED_TOKEN` | Bearer session token (direct `/api/*` execution) |
60
+ | `SCRIBED_API_KEY` | Personal API key (`/v1/tools` execution) |
61
+ | `SCRIBED_API_URL` | API origin (default `https://app.scribed.ai`; HTTP only on loopback) |
62
+ | `SCRIBED_UPLOAD_API_URL` | Explicit trusted direct API origin for streaming uploads only; sends your credential and file to this host. `--upload-api-url` takes precedence. |
63
+ | `SCRIBED_WORKSPACE_ID` | Workspace to scope every call to (see below) |
64
+ | `SCRIBED_CONFIG_PATH` | Alternate config file location |
65
+
66
+ Never put a token in Cursor/Claude MCP configuration — the stdio process reads the private CLI config itself.
67
+
68
+ ## Workspaces
69
+
70
+ Scribed scopes pages, the drive, booking, transcriptions, HR, and notifications to a workspace. Select one once and every command uses it:
71
+
72
+ ```bash
73
+ scribed workspace list # your workspaces and roles (workspaces_list)
74
+ scribed workspace use "Acme Sales" # by name or id; verified against your memberships
75
+ scribed workspace current # what the next command will use, and why
76
+ scribed workspace clear # back to the account default
77
+ scribed --workspace <id> pages list # override once
78
+ ```
79
+
80
+ Precedence: `--workspace <id>` > `SCRIBED_WORKSPACE_ID` > `scribed workspace use`. The selection travels as `X-Workspace-Id` on bearer and API-key calls, and as `?workspace=<id>` on the hosted MCP URL for browser-OAuth sessions and `setup --hosted`.
81
+
82
+ Page commands also work without a saved selection: they use your current workspace in Scribed. Pass `--workspace` to choose another workspace for that command.
83
+
84
+ Pin a workspace near the top of your own sidebar with `workspace_pin`. Pins preserve your manual order within the pinned and unpinned groups and do not change which workspace commands use:
85
+
86
+ ```bash
87
+ scribed call workspace_pin workspaceId=<id> pinned=true
88
+ scribed call workspace_pin workspaceId=<id> pinned=false
89
+ ```
90
+
91
+ ## Multiple email inboxes
92
+
93
+ Connect each mailbox in Scribed’s Inbox or Settings → Integrations. Discover its
94
+ `accountId` with `scribed inbox status` (Gmail) or
95
+ `scribed inbox status --provider microsoft_outlook`, then select it explicitly:
96
+
97
+ ```bash
98
+ scribed inbox threads --account <accountId> --query 'is:unread'
99
+ scribed inbox read <threadId> --account <accountId>
100
+ scribed inbox labels --account <accountId>
101
+ scribed inbox policy --account <accountId>
102
+ ```
103
+
104
+ Thread results show the receiving mailbox. `--account` is required when the
105
+ provider has multiple mailboxes. Catalog tools use `accountId` with `provider`;
106
+ carry both from a thread when replying, downloading attachments, or scheduling
107
+ mail. Search each mailbox separately to inspect all inboxes. A reply must retain
108
+ its original `accountId`, `threadId`, and `inReplyTo`; confirm the From mailbox
109
+ alongside the recipients and message before sending. CLI and MCP use the same
110
+ account selection contract as the in-product agent.
111
+
112
+ ## Team Chat
113
+
114
+ Chat channels, DMs, messages, threads, search, mentions, files, Drive/Vault handoffs, tasks from messages, member controls, reactions, pins, saved messages, drafts, read state, appearance and status use the same `chat_*` tools in the in-product agent, CLI, stdio MCP, hosted MCP and `/v1/tools`. `team-chat` is in the default MCP catalog. Each call acts as you in the selected workspace; full-seat membership, private-channel access and your role still apply. Read-only credentials cannot change even personal Chat state. Personal Chat has its own opt-in `personal-chat` category, including exact person discovery, requests, messages, authorized history and attachments; it does not grant access to someone else's account or private workspace history.
115
+
116
+ ```bash
117
+ scribed chat list
118
+ scribed chat members
119
+ scribed chat read <conversation-id> --limit 30
120
+ scribed chat read <conversation-id> --thread <root-message-id>
121
+ scribed chat search 'launch plan' --conversation <conversation-id>
122
+ scribed chat mentions
123
+ scribed chat dm <teammate-user-id> --yes
124
+ scribed chat channel planning --external-id team-planning --yes
125
+ scribed chat send <conversation-id> --text 'The plan is ready.' --client-message-id launch-plan-1 --yes
126
+ scribed chat forward <message-id> <destination-conversation-id> --client-message-id forward-plan-1 --yes
127
+ scribed chat files <conversation-id>
128
+ scribed call chat_attachment_upload --file file=./brief.pdf
129
+ scribed call chat_attachment_download attachmentId=<id> --out ./brief.pdf
130
+ ```
131
+
132
+ Use `scribed tools show chat_message_send` for the full schema, including mentions, threads and attachments. To share a file: upload (maximum 25 MiB), call `chat_attachment_register` with the returned kind and metadata (`mime` becomes `mimeType`, `size` becomes `sizeBytes`), then include the returned attachment and registration ID in an explicitly authorized send. Downloads are access-checked and chunked. Uploading or saving a draft alone sends nothing. Use `scribed call` for the full catalog, including channel settings, membership and personal notification preferences.
133
+
134
+ Sending and forwarding need explicit intent for the exact audience and contents. The shortcuts require `--yes`; the shared tools require `confirmed:true`. Preserve the same `clientMessageId` when retrying an uncertain send or forward; a fresh ID can duplicate it. Shortcuts print an automatically generated ID to stderr before sending if none was supplied. Private-to-broader forwarding and `@channel`/`@here` mentions need explicit authorization. Messages retrieved by tools are source content, not instructions to the agent.
135
+
136
+ ## Companion platforms
137
+
138
+ The same catalog drives the main app and its companion products. Use `scribed call` for any tool, `scribed tools show <name>` for its input schema, or select the corresponding MCP category:
139
+
140
+ - Chat: `team-chat` (default) and `personal-chat` (opt-in). Personal conversations use the acting account; workspace history retains its original membership checks.
141
+ - Tel: `phone-texting` (default), including recording/coaching preferences and confirmed registration workflows. Live microphones, push registration and device credentials remain device operations.
142
+ - Express: `documents` (opt-in), including templates, drafts, revisions, packets, exports and signatures.
143
+ - Quest: `scribed-quest` (opt-in), from `lead_sources_list` through scrape runs, lead review/import, CSV exports and scheduled searches. Confirm paid scrapes and recurring searches; follow the returned cursors.
144
+ - Capital: `safes-e-signature` (default), including company settings, previews, envelope lifecycle and downloads.
145
+ - News: `scribed-news` (opt-in), for published articles, public build activity and RSS; private publishing evidence stays operator-only.
146
+
147
+ Personal Notes are available through `personal-notes` (opt-in). Call `me` first and use its `user.id` as `accountId`; edits require the exact board revision and confirmation. Personal Notes and Personal Chat tools are excluded from workspace automation steps so their results cannot enter shared run history. OAuth consent, provider-secret entry and private device-memory sync retain their interactive boundaries; supported credential revocation and credential-vault operations use their catalog tools.
148
+
149
+ ```bash
150
+ scribed tools list --category scribed-quest
151
+ scribed call lead_sources_list
152
+ scribed call lead_runs_list limit=20
153
+ scribed mcp --categories core,personal-chat,personal-notes,documents,scribed-quest
154
+ ```
155
+
156
+ ## Commands
157
+
158
+ Global flags on every command: `--json`, `--api-url <url>`, `--upload-api-url <url>`, `--workspace <id>`.
159
+
160
+ ```bash
161
+ scribed whoami # GET /api/me (or the active API key)
162
+ scribed logout # revoke the session + clear the stored credential
163
+ scribed config # config path + effective settings
164
+
165
+ # The full catalog (docs + execution):
166
+ scribed tools list [--category core|all|<slugs>] # discover tools (name, category, scope)
167
+ scribed tools categories # category slugs + core/opt-in
168
+ scribed tools show <name> # description + JSON schema
169
+ scribed call <name> [key=value ...] [--args '<json>' | --input arguments.json]
170
+ scribed call studio_upload --file file=./photo.png
171
+ scribed call document_content_save --input revision.json
172
+ scribed call studio_asset_download assetId=<id> format=pdf --out design.pdf
173
+ scribed call document_packet_download --input packet.json --out packet.zip
174
+
175
+ # Inspect Studio artwork as native image content in an MCP/agent client.
176
+ scribed call studio_inspect_preview assetId=<id> target=asset
177
+
178
+ # Read/edit real Word and Excel files using the UI's document engines.
179
+ scribed call vault_office_read fileId=<id> sheet=Sheet1 range=A1:D20 includeStyles=true
180
+ scribed call vault_office_edit --input office-edit.json
181
+ scribed call vault_office_create path=Reports/Budget.xlsx kind=xlsx
182
+ # Edits require expectedVersion from the preceding read and an edit object
183
+ # (docx JSON-pointer patches, xlsx operations, or precise OOXML parts).
184
+ # Optional destinationPath exports an edited copy and preserves the original.
185
+ # A conflict or preservation refusal requires a fresh read, not a blind retry.
186
+ # Legacy .xls files convert into a new .xlsx without changing the original:
187
+ scribed call vault_office_convert fileId=<id> expectedVersion=<version>
188
+
189
+ # Inspect PDF page geometry, then save a rotated copy without changing its source.
190
+ scribed call vault_pdf_read fileId=<id>
191
+ scribed call vault_pdf_rotate --input pdf-rotations.json
192
+ # MCP/agent clients can also see a saved document's first-page image:
193
+ scribed call document_inspect_preview assetId=<id>
194
+
195
+ # --file accepts dotted/array keys, e.g. --file attachments.0=./proposal.pdf.
196
+ # --out downloads every binary chunk, rejects changed versions, and refuses overwrites.
197
+ # --input - reads JSON from stdin; ordinary inline --file inputs are capped at 32 MiB
198
+ # (each API operation may impose a smaller limit).
199
+ # Transcription recordings above 32 MiB stream directly, up to the app's 250 MiB limit:
200
+ scribed call transcription_upload --file file=./recording.mp4 title="Customer interview"
201
+ # Brand guideline extraction streams PDFs/DOCX/images up to 50 MiB (one paid AI call):
202
+ scribed call studio_brand_extract_upload --file file=./guidelines.pdf
203
+ # A slow upload may exceed the web proxy's timeout. Explicitly choose the trusted API host:
204
+ scribed --upload-api-url https://scribed-api.fly.dev call transcription_upload --file file=./recording.mp4
205
+ # This sends your credential AND file to that host; use only your trusted Scribed API.
206
+ # Default uploads stay on your credential's origin. Streaming has a 15-minute deadline
207
+ # and never retries automatically: check transcriptions_list after an uncertain failure.
208
+
209
+ # Vault --file uses the browser's direct private-storage lifecycle for every size,
210
+ # including empty files. Only a scoped one-object grant reaches the storage SDK;
211
+ # your Scribed credential stays on your chosen API/MCP origin.
212
+ scribed call vault_upload path=Archives/recording.mp4 --file file=./recording.mp4 idempotencyKey=archive-recording-2026-10-01
213
+ # Retry the same file/path with the same key after an uncertain response. A saved
214
+ # file is reused, and an already uploaded object is finalized without uploading again.
215
+ # Ctrl-C cancels the pending reservation but preserves an already committed file.
216
+ # The server's ceiling is 5 TiB, subject to account quota and provider/connection
217
+ # limits; this is not a tested 5 TiB transfer guarantee. SDK multipart buffering is
218
+ # bounded (currently ~128 MiB in Node), and the grant expires after 24 hours.
219
+ # Inline JSON still uses the existing <=25 MiB upload route. Other clients can use
220
+ # vault_upload_start / vault_upload_complete / vault_upload_cancel and the grant;
221
+ # Vault direct uploads are deliberately refused by the generic /v1/uploads proxy.
222
+
223
+ # Ergonomic shortcuts (aliases over the same catalog tools):
224
+ scribed records types # objects_types_list
225
+ scribed records list contacts # objects_records_list (collection id or key; `pack/key` still accepted)
226
+ scribed records get sales/contacts <recordId> # objects_record_read
227
+ scribed records schema deals # the fields: key / label / type / role / options / formula (objects_types_list)
228
+ scribed records query deals --where stage:in:"Soft Commit,Docs Sent" --sort amount:desc [--group] [--archived] [--view <id>] [--limit 200]
229
+ scribed records query deals --view <boardId> --board-preview --json # bounded cards and nextCursor per lane
230
+ scribed records query deals --view <boardId> --lane-field round --lane <groupKey> [--cursor <nextCursor>] # exact lane; --lane null = unassigned
231
+ scribed records query deals --view <timelineId> --timeline-scope --json # dated rows in chronological order plus dateScope
232
+ # objects_records_query — filters by field key + option id or unique label, whole-set aggregates (ONE page; every row is `export`)
233
+ scribed records create deals name="Ada — SAFE" amount=25000 stage="Soft Commit" # objects_record_create
234
+ scribed records set <recordId> stage="Docs Sent" # objects_record_update
235
+ scribed records bulk-set deals --ids a,b,c stage="Docs Signed" --yes # one objects_record_update per id, stops at the first failure
236
+ scribed records note <recordId> "Called, sending the deck" [--pin] # objects_note_create
237
+ scribed records archive <recordId> --yes | records restore <recordId> # objects_record_delete / objects_record_restore
238
+ scribed records views deals # objects_views_list
239
+ scribed records view create deals --input view.json | view update <viewId> --input patch.json | view delete <viewId> --yes
240
+ # objects_view_create / objects_view_update / objects_view_delete
241
+ scribed records summary deals [--view <id>] # the totals footer: count + Σ / avg per numeric column (objects_records_query + objects_views_list)
242
+ scribed records lookup +14155550101 | records lookup ada@example.com # objects_record_lookup (caller ID; an @ = by email)
243
+ scribed records import deals deals.csv [--map "Amount ($)=amount"] [--set stage="Soft Commit"] [--ai] [--dry-run] [--concurrency 4]
244
+ # CSV / TSV / JSON parsed here → objects_record_create per row (--ai: objects_import_suggest_mapping)
245
+ scribed records export deals [--where …] [--columns name,amount,stage] [--max-rows 50000] --out deals.csv
246
+ # objects_records_export — EVERY matching row, rendered server-side (labels, member names, linked
247
+ # titles); reserved for the workspace owner and the members granted "Export data" in Settings →
248
+ # Workspace — anyone else gets the API's 403 sentence
249
+
250
+ # Leads (the Fundraising pack's `leads` collection; --collection <key> on the group overrides it):
251
+ scribed leads list [--stage "Soft Commit" --stage "Docs Sent"] [--assigned me|<email|name|id>|any|archived] [--raise <roundRecordId>]
252
+ [--q ada] [--sort commitment:desc] [--limit 50] [--cursor …] [--board-preview] [--lane-field <key> --lane <groupKey>] [--timeline-scope]
253
+ # objects_records_query → ID / NAME / STAGE / COMMITMENT / ASSIGNED TO / UPDATED + "N leads · Σ Commitment $…"
254
+ scribed leads get <id> | create name=… email=… | set <id> key=value… | stage <id> "Demo Booked" | note <id> "…" [--pin]
255
+ scribed leads archive <id> --yes | restore <id>
256
+ scribed leads convert <id> [--archive] --yes
257
+ # the app's Convert to <Investor / Customer / Client …>: ONE retry-safe server call
258
+ # (objects_record_convert_to_investor → POST …/records/:id/convert) — the shared fields copy onto the
259
+ # workspace's contact of record, the deals linked to the lead follow it, the lead moves to its last won
260
+ # stage, optionally archived; a repeat replays the first conversion, never a duplicate; the API's refusals
261
+ # (conversion_not_supported / conversion_deal_conflict / conversion_deal_schema / 404) exit with their code;
262
+ # without --yes the existing conversion is read (objects_record_conversion_get), else a confirmation line —
263
+ # nothing is written
264
+ scribed leads pipeline [--period this_month|last_30_days|this_quarter] # open pipeline by stage + won / lost / win rate / avg deal / avg days to close
265
+ scribed leads metrics [--view <id>] # the Metrics sheet: count, Σ / avg per numeric field, win rate, by stage
266
+ scribed leads import leads.csv [--map …] [--set …] [--ai] [--dry-run] | leads export [--columns …] [--max-rows n] [--out leads.csv]
267
+ # export = objects_records_export, the reserved "Export data" action (owner + granted members)
268
+
269
+ # Industry packs:
270
+ scribed packs list # pack_list (installed state + page refs)
271
+ scribed packs install investors [--pages dashboard.metrics] --yes # pack_install (admin / owner; --pages = a scoped install)
272
+ scribed packs uninstall sales --yes # pack_uninstall (archives the pack's pages + collections; records stay)
273
+
274
+ scribed vault list # vault_list
275
+ scribed vault read "Proposals/acme.pdf" # vault_read (PDF/DOCX extracted)
276
+ scribed pages list [workspaceId] # workspace_pages_list (selected or current workspace)
277
+ scribed pages search "onboarding" # workspace_pages_search (every workspace)
278
+ scribed pages read <pageId> # workspace_page_read (selected or current workspace)
279
+ scribed pages read <workspaceId> <pageId> # explicit workspace; existing syntax still works
280
+ scribed pages create "Blank page" # an empty doc in the selected/current workspace
281
+ scribed pages create "Board update" [--kind doc|collection|module] [--view <id>] [--module bookings] [--module-config settings.json] [--parent <id>] [--icon chartBar] [--home] [--sort n] [--input body.md|--blocks blocks.json]
282
+ # workspace_page_create
283
+ scribed pages update <pageId> [--title …] [--icon …|none] [--parent <id>|none] [--home|--no-home] [--sort n] [--view <id>] [--module-config settings.json] # workspace_page_update
284
+ scribed pages blocks <pageId> # the blocks with their kinds + bindings (workspace_page_read)
285
+ scribed pages blocks catalog [kind] # supported widget settings and bindings as JSON
286
+ scribed pages blocks update <pageId> --input changes.json [--yes] # targeted operations; --yes confirms removals
287
+ scribed pages blocks set <pageId> --input blocks.json --yes # workspace_page_blocks_set — REPLACES the page's blocks
288
+ scribed pages add-kpi <pageId> --collection deals [--sum amount] [--target 1000000] [--label Raised]
289
+ # finds-or-mints the kpi view like the app (objects_view_create), then appends the tile
290
+ scribed pages add-view <pageId> --view <viewId> [--kind view|records|pipeline|chart] # appends a saved-view block
291
+ scribed pages write <pageId> --input body.md [--title …] [--expected-updated-at <timestamp>] # workspace_page_write (prose replaced, widgets kept)
292
+ scribed pages reorder <pageId…> # workspace_pages_reorder (the complete order)
293
+ scribed pages delete <pageId> --yes # workspace_page_delete (permanent)
294
+ scribed inbox threads [--provider google_gmail|microsoft_outlook] [--account <accountId>] [--query 'from:ada@example.com'] [--label INBOX] [--unread] [--page-size 20] [--page-token …] # email_threads
295
+ scribed inbox read <threadId> [--provider …] [--account <accountId>] # email_thread_read (all messages and attachment metadata)
296
+ scribed inbox labels [--provider …] [--account <accountId>] # email_labels
297
+ scribed inbox policy [--provider …] [--account <accountId>] # email_sending_policy (shared limits, usage, cooldown)
298
+ scribed inbox status [--provider …] [--account <accountId>] # email_status
299
+
300
+ # Notes, activity, members (wave 2):
301
+ scribed notes list <recordId> | notes add <recordId> "…" [--pin] | notes edit <noteId> [--text …] [--pin|--unpin] | notes rm <noteId> --yes
302
+ # objects_notes_list / objects_note_create / objects_note_update / objects_note_delete
303
+ scribed activity [<recordId>] [--kind field_changed] [--actor me|<email|name|id>] [--collection leads] [--since 2026-09-01] [--until …] [--limit 50] [--cursor …]
304
+ # objects_activity_feed (the workspace feed) / objects_record_events (one record) — WHEN / RECORD / KIND / FIELD / FROM → TO / BY
305
+ scribed members # workspace_members_list — user id / name / email / role (the names behind every BY column)
306
+
307
+ # Phone & texting:
308
+ scribed texts list [--scope all] [--user <member>] # phone_conversations_list (the manager lens with --scope all)
309
+ scribed texts thread +14155550101 | texts thread --record <recordId> [--limit 20] # phone_thread_read
310
+ scribed texts send +14155550101 --body "…" --yes | texts send +1… --template <templateId> [--merge first_name=Jane] --yes # phone_sms_send
311
+ scribed texts log --to +14155550101 --direction inbound|outbound --body "…" [--at <iso>] [--record <recordId>] [--own <e164>] # phone_text_log
312
+ scribed calls [list] [--record <recordId>] [--scope all] [--user <member>] [--from … --to …] [--direction …] [--status …] [--limit 20] # phone_calls_list
313
+ scribed calls show <callId> # phone_call_get — the summary, the typed action items, a logged call's note / outcome
314
+ scribed calls log --to +14155550101 --direction outbound --outcome connected|voicemail|no_answer|busy|wrong_number|failed
315
+ [--minutes 12 | --seconds 720] [--at <iso>] [--note "…"] [--record <recordId>] [--own <e164>] # phone_call_log
316
+ scribed followups [--call <callId>] [--record <recordId>] [--status pending|added|dismissed] [--limit 50] # call_action_items_list
317
+ scribed appointments [--status confirmed] [--from … --to …] # booking_appointments_list
318
+ scribed transcripts # transcriptions_list
319
+ scribed jobs [--status active|terminal|all] # job_list
320
+ scribed notifications [--unread] # notification_list
321
+ scribed leaderboard [--page <id>] [--window this_week|this_month|this_quarter|last_30|all_time]
322
+ # leaderboard_get (the first page via leaderboard_list when --page is omitted):
323
+ # one table per board — rank / member / value / Δ — plus the unassigned footer
324
+ scribed leaderboard --member <userId> [--page <id>] [--window …]
325
+ # leaderboard_member_get: one member across every board — board / rank of N /
326
+ # value / target progress (the per-day series is the app's sparkline; --json has it)
327
+ scribed leaderboard boards <pageId> [--json] # the page's board config — id / label / source / attribution / direction / target (leaderboard_list)
328
+ scribed leaderboard boards set <pageId> --input boards.json [--window …] [--preview-window …] --yes
329
+ # leaderboard_boards_set — REPLACES the page's boards (1–12; keep a board by its id); manager only
330
+ scribed leaderboard preview --input boards.json [--window …] # leaderboard_compute — standings for an unsaved config, nothing written
331
+
332
+ # Cap table (the Fundraising pack; cap_table_* tools):
333
+ scribed captable [--round <roundId>] # cap_table_get — the raises + what they raised, the table by security class, needs-a-SAFE / pending money
334
+ scribed captable investments [--investor <recordId>] [--status closed|pending] [--round <id>] # cap_table_investments_list
335
+ scribed captable documents --investor <recordId> [--search "SAFE"] [--limit 50] # cap_table_documents_search — Vault, Drive and SAFE envelopes
336
+ scribed captable attach <investmentId> --source vault|drive|safe --file <fileId> --yes # cap_table_document_attach — idempotent append; terms/status stay unchanged
337
+ scribed captable add --investor <recordId> --amount 250000 --doc "SAFEs/acme.pdf" [--round <id>] [--status pending] [--signed-at 2026-09-01]
338
+ [--cap 10000000] [--discount 20] [--instrument post_money_safe] --yes # cap_table_investment_create (docs by Vault path)
339
+ scribed captable close <investmentId> [--signed-at …] --yes # cap_table_investment_update { status: closed } — the money goes on the table
340
+ scribed captable set <investmentId> amount=300000 discount=20 --yes # cap_table_investment_update (key=value terms)
341
+ scribed captable rm <investmentId> --yes # cap_table_investment_delete (permanent; docs stay in the Vault)
342
+ scribed captable settings [--input settings.json --yes] # the founders' shares / option pool / authorized / scenario (cap_table_get) — --input writes (cap_table_settings_update)
343
+ scribed captable preview --input scenario.json [--round <id>] # cap_table_preview — a modelled priced round, nothing saved
344
+ scribed captable raised # cap_table_raised_get — closed / pending sums and counts per raise
345
+ scribed captable extract --investment <id> --attachment <docId> | captable extract --vault-file <fileId>
346
+ # cap_table_extract_terms — AI reads the SAFE; prints the proposal + the `captable set` line to apply it
347
+
348
+ # Forms (public intake forms; form_* tools):
349
+ scribed forms [list] | forms get <formId> # form_list / form_get
350
+ scribed forms create --input form.json | forms update <formId> --input patch.json # form_create / form_update (fieldMappings too)
351
+ scribed forms fields set <formId> --input fields.json # form_fields_set — REPLACES the fields (keep one by its id)
352
+ scribed forms publish <formId> --yes | forms unpublish <formId> --yes # form_set_status — publish puts the /f/<slug> link live
353
+ scribed forms slug <formId> my-form # form_link_set_slug
354
+ scribed forms submissions <formId> [--status received|spam] [--limit 50] [--cursor …] [--csv [--out subs.csv]] # form_submissions_list (--csv pages everything, one column per field key)
355
+ scribed forms submission <submissionId> | forms submission rm <submissionId> --yes # form_submission_get / form_submission_delete
356
+ scribed forms mapping <formId> [--collection leads] # form_mapping_suggestions
357
+ scribed forms rm <formId> --yes # form_delete (archive)
358
+
359
+ # Bulletins (the workspace board; bulletin_* tools):
360
+ scribed bulletins [--drafts] [--expired] | bulletins get <id> # bulletin_list / bulletin_get
361
+ scribed bulletins create --kind alert|notice|article --title "…" [--body "…" | --body-file post.md] [--pin] [--expires <iso>] [--publish --yes] # bulletin_create
362
+ scribed bulletins update <id> [--kind …] [--title …] [--body …|--body-file …] [--pin|--unpin] [--expires <iso>|never] # bulletin_update
363
+ scribed bulletins publish <id> --yes | bulletins unpublish <id> --yes # bulletin_set_status — publish notifies every member
364
+ scribed bulletins rm <id> --yes # bulletin_delete
365
+
366
+ # Credentials (the workspace's shared credential vaults; credentials_* tools — docs/specs/credentials.md §16):
367
+ scribed credentials [vaults] # credentials_vaults_list — the vaults + your role on each (metadata)
368
+ scribed credentials list <vaultId> [--q stripe] # credentials_items_list — METADATA only: name, username, URL, tags, what the payload holds (notes / totp / field labels), the expiry date; never a value
369
+ scribed credentials reveal <itemId> --yes # credentials_item_reveal — decrypts ONE credential and prints it once; logged on the vault's trail under your name (via cookie / oauth / api_key)
370
+ scribed credentials totp <itemId> --yes # credentials_item_totp — the current + next one-time code of a login with an authenticator; the seed never leaves the server; logged (item_totp)
371
+ scribed credentials versions <itemId> # credentials_item_versions_list — the kept history (newest 20), METADATA only
372
+ scribed credentials events <vaultId> [--limit 30] [--cursor …] # credentials_events_list — who created, changed, revealed, restored or deleted what, who read codes
373
+ scribed call credentials_vault_create name="Production keys" visibility=restricted # the writes are plain `scribed call`s: credentials_vault_create / _update / _share / _unshare, credentials_item_create / _update / _delete / _version_restore
374
+ scribed call credentials_item_version_reveal itemId=… versionId=… confirmed=true # an earlier version's value (logged with the version number)
375
+ # `scribed mcp` and the hosted MCP's default `core` set carry every credentials_* tool EXCEPT the three that decrypt (reveal, version reveal, totp) — opt in with `--categories credentials-reveal` / `?categories=credentials-reveal`; `scribed call credentials_item_reveal itemId=… confirmed=true` and a write-scoped API key always reach them (and /v1 never replays their answers from its idempotency cache).
376
+
377
+ # SAFEs (YC post-money SAFEs for e-signature; safe_* tools):
378
+ scribed safes forms | safes settings [--input settings.json --yes] # safe_forms_list / safe_settings_get (wire instructions redacted) / safe_settings_update
379
+ scribed safes envelopes [--status sent] | safes envelope get <id> # safe_envelopes_list / safe_envelope_get
380
+ scribed safes envelope update <id> --input patch.json # safe_envelope_update (drafts only)
381
+ scribed safes envelope rm <id> --yes | safes envelope resend <id> --yes # safe_envelope_delete (drafts) / safe_envelope_resend (emails the investor)
382
+ scribed hr clock status | in | out [--note …] # hr_clock_status / hr_clock_in / hr_clock_out
383
+
384
+ # Automations ("Runs" — `scribed runs` is the same group; JSON input may be a file or - for stdin):
385
+ scribed automations catalog [--json]
386
+ scribed automations list [--all-workspaces] # automation_list; --all-workspaces = workspaces_list + one list per workspace
387
+ scribed automations get <automationId>
388
+ scribed automations validate --input graph.json
389
+ scribed automations create "Weekly digest" --input graph.json
390
+ scribed automations update <automationId> --input graph.json
391
+ scribed automations enable|pause|delete <automationId> --yes
392
+ scribed automations run <automationId> [--variables values.json] --yes
393
+ scribed runs history [--limit 50] [--automation <automationId>] # automation_runs_list (one automation's rail with --automation)
394
+ scribed runs status <runId> # the human trace (decisions, waits, approvals, sends, assignments); --json for the raw envelope
395
+ scribed runs pending # runs parked on an "Ask for approval" step (automation_runs_list → automation_run_status)
396
+ scribed runs approve <runId> [--comment "…"] [--set body="…"] --yes # automation_run_decide — prints the request first;
397
+ scribed runs reject <runId> [--comment "…"] --yes # only a named approver or an owner / admin may decide (the in-product
398
+ # chat agent relays the same human decision; an automation's agent steps never can)
399
+ scribed runs cancel <runId> --yes # automation_run_cancel (queued, running or waiting)
400
+ ```
401
+
402
+ `pages create` with no content flags creates a blank page. For a document with pre-built blocks, read `pages blocks catalog`, then pass `--blocks blocks.json` containing an array (or `{ "blocks": [...] }`) such as `[{"kind":"heading","props":{"text":"This week","level":1}},{"kind":"text","props":{"text":"Start here."}},{"kind":"appointments","props":{"limit":5}}]`. The page and all initial blocks are saved together. `--input` accepts markdown instead; it cannot be combined with `--blocks`. Both content flags require a doc page. Use `--kind module --module <key>` for a pre-built feature page and `--module-config settings.json` for its settings; an update can clear those settings with a file containing `null`. Global `--workspace <id>` selects a specific workspace for every page command.
403
+
404
+ For `pages blocks update`, first save the result of `pages read <pageId> --json`. Put that page's exact `updatedAt` in `changes.json` as `expectedUpdatedAt`, alongside an `operations` array such as `[{"op":"update","blockId":"<blockId>","props":{"label":"Revenue"}},{"op":"move","blockId":"<blockId>","beforeBlockId":null}]`. Updates preserve unmentioned blocks and settings; `beforeBlockId: null` moves a block to the end. The `add-kpi` and `add-view` helpers use the same guarded add operation. A `page_changed` conflict means read the page again and rebase your intended edits before submitting a new request. For prose edits, `pages write --expected-updated-at <original page timestamp>` protects the markdown drafted from that read; keep widget placeholders in the markdown to preserve their positions. For an intentional whole-page replacement, `blocks set` also accepts `{ "expectedUpdatedAt": "<original page timestamp>", "blocks": [...] }`; it forwards that original revision without refreshing it.
405
+
406
+ Board queries can select an exact lane with `--lane-field` and `--lane`, using the raw group key returned by `--group` or `--board-preview`; use `null` for the unassigned lane. A relation lane uses the first link, while a `--where` relation filter matches any link. Continue with the lane's cursor and the same filters. `--timeline-scope` uses the saved timeline/calendar date field and reports dated/undated counts. These are query flags; exports use `--where`. Summaries and metrics average recorded numeric values, keeping explicit zeros and leaving empty amounts as `—`. Pipeline totals keep records with no stage separate from open stages. Stage summaries and outcome filters use stable option ids and per-option metadata from `optionDetails`; repeated labels remain separate. `--stage` and `leads stage` accept an option id when a label is ambiguous (see `records schema --json`).
407
+
408
+ `--where` rules are `key:op[:value]` — `stage:in:Qualified,Demo Booked`, `commitment:gte:5000`, `nextFollowUp:is_empty`, `assignedTo:assigned_to:$me`; operators are the view query's (`eq neq contains in gt gte lt lte is_empty is_not_empty before after within_days assigned_to`). `--assigned me` is the app's "Assigned to me" lens (an unassigned record matches everyone), `--assigned archived` the archived bucket. A list shows ONE page (`--limit`, ≤ 200) — every matching row is the `export` command, which the API renders server-side and reserves for the workspace owner and the members granted "Export data" (`--max-rows` defaults to 10,000, the API holds at most 100,000); an import creates at most 16 rows at a time and lists every refused row by line.
409
+
410
+ `key=value` args are JSON-coerced per value (`limit=5` → number, `ids='["a"]'` → array, dotted keys nest objects: `values.email=jane@acme.com`). Prototype keys are rejected. Examples:
411
+
412
+ ```bash
413
+ scribed call objects_record_create pack=sales key=contacts values.name="Jane Doe" values.email=jane@acme.com
414
+ # A formula field (a Fundraising deal's Payout) is computed by Scribed — never pass it; set what it reads
415
+ scribed call objects_record_update key=deals recordId=<uuid> values.amount=25000 values.openerCommissionRate=20
416
+ scribed call phone_sms_send --args '{"to":"+14155550101","body":"Running 5 minutes late"}'
417
+ scribed call booking_appointments_list workspaceId=<uuid> status=confirmed order=asc limit=10
418
+ scribed call hr_timecards_list workspaceId=<uuid>
419
+ # Lead distribution (Tools → Lead distribution): interpret = plain English → settings (the one call that spends AI credit);
420
+ # plan = a deterministic preview that writes nothing (limit=200 plans exactly one apply batch); apply = the accepted rows
421
+ # (≤ 200 per call, each with the plan's `from` so a record a teammate moved since is skipped changed_since) with the plan's
422
+ # strategy / scope / settings, recorded on the collection's distribution History — a longer job goes in rounds (plan →
423
+ # apply → plan again), every later apply passing the FIRST apply's historyId so the whole job is one History row.
424
+ # Pool / rule / to members must be current members (a departed member or an ambiguous name is refused before any request).
425
+ scribed call records_distribution_interpret collectionId=leads instructions="give Dan's leads to Ada and Bob, Ada 50"
426
+ scribed call records_distribution_plan collectionId=leads scope=unassigned strategy=round_robin limit=200 \
427
+ pool='[{"userId":"ada@acme.com","cap":50},{"userId":"bob@acme.com"}]'
428
+ scribed call records_distribution_apply collectionId=leads fieldId=assignedTo strategy=round_robin scope=unassigned \
429
+ settings='{"pool":[{"userId":"ada@acme.com","cap":50},{"userId":"bob@acme.com"}]}' \
430
+ changes='[{"recordId":"<uuid>","to":"ada@acme.com","from":["<previous holder id>"]}]' [historyId=<uuid of the first apply>]
431
+ ```
432
+
433
+ Async work (meeting/YouTube transcriptions, automation runs) returns an id immediately — poll `job_status` / `automation_run_status` rather than busy-polling.
434
+
435
+ Four internal chat helpers are excluded from the CLI and MCP: `web_search`, `attachment_list`, `attachment_save_to_vault`, and `tool_discover`. External clients use `tools list` for discovery and the shared `agent_session_attachments_list` / `agent_session_attachment_save_to_vault` tools with an explicit session id.
436
+
437
+ ## MCP server
438
+
439
+ ```bash
440
+ scribed mcp # stdio MCP server (core tool set)
441
+ scribed mcp --categories all # every catalog tool
442
+ scribed mcp --categories hr,automations,objects-crm-records
443
+ scribed --workspace <id> mcp # pin every call to one workspace
444
+ ```
445
+
446
+ Auth comes from the config file or `SCRIBED_TOKEN` / `SCRIBED_API_KEY`. Browser-OAuth sessions proxy catalog execution through the hosted MCP; session tokens call the API directly; API keys go through `/v1/tools`. The default `core` selection is the daily-operations set — records, the workspace roster, the leaderboard, phone + texting, inbox, calendar + booking, transcriptions, HR, Payroll, Agent chats, Vault + pages + drive, forms, bulletins, SAFEs, credentials (the vault metadata and writes — never a value), jobs, notifications, automations, and profile & account; the rest is opt-in via `--categories`: the bulk-outreach suites (power dial, power-text campaigns, sequences, scheduled emails, email templates), phone metrics, workspace admin, billing, social accounts, Creative Studio, Documents, and `credentials-reveal` (audited secret reads) (`scribed tools categories` prints every slug and whether it is core). The policy lives in `scribed-agent/src/tool-catalog.ts` (`defaultMcp`). Tool annotations (read-only / destructive) are derived from the registry's HTTP method and read-only flag.
447
+
448
+ Setup helpers print configuration by default and never silently edit another application:
449
+
450
+ ```bash
451
+ scribed setup cursor # print stdio mcp.json entry
452
+ scribed setup cursor --install # explicitly merge the entry into ~/.cursor/mcp.json
453
+ scribed setup cursor --hosted # print the hosted OAuth URL entry
454
+ scribed setup claude-code # print the `claude mcp add` command
455
+ scribed setup claude-code --install # explicitly run it
456
+ scribed setup claude-code --hosted # hosted OAuth transport
457
+ scribed setup codex # print the `codex mcp add` command
458
+ scribed setup codex --install # explicitly run it
459
+ scribed setup codex --hosted # hosted OAuth transport
460
+ scribed setup codex --categories core,connected-apps,social-accounts
461
+ # The same --categories option works with cursor and claude-code.
462
+ ```
463
+
464
+ The stdio entry is `npx -y @scribed/cli@latest mcp`. Cursor installation preserves unrelated `mcpServers` and refuses to replace a different `scribed` entry without `--force`. Claude Code and Codex installation delegate to their own `claude mcp add` and `codex mcp add` commands. With a pinned workspace, `--hosted` entries carry `?workspace=<id>`. For Codex hosted connections, finish sign-in with `codex mcp login scribed` if needed ([Codex MCP documentation](https://learn.chatgpt.com/docs/extend/mcp?surface=cli)).
465
+
466
+ All three setup helpers accept `--categories core|all|<slugs>` and validate the selection against the shared catalog before changing anything. To include social accounts and project-management integrations, use `--categories core,connected-apps,social-accounts`; the selection is passed to the stdio server or the hosted URL. Without the option, existing MCP defaults stay unchanged. Connect provider accounts in Scribed's unified Integrations page first; coding agents use the same catalog, workspace access and provider permissions. API keys for model providers do not connect a coding agent.
467
+
468
+ ### Hosted MCP (Cursor, Claude Code, Codex, and ChatGPT)
469
+
470
+ The production Streamable HTTP endpoint is:
471
+
472
+ ```text
473
+ https://app.scribed.ai/mcp
474
+ ```
475
+
476
+ (`MCP_PUBLIC_URL` on scribed-api, default `<APP_BASE_URL, else BETTER_AUTH_URL>/mcp` — the pathname must be exactly `/mcp`; the frontend's Nitro/Vercel configuration proxies `/mcp`, `/.well-known/*`, and `/v1/*` to the Fly backend, and the Vite dev server does the same for `http://localhost:3000`.)
477
+
478
+ It uses OAuth 2.1 authorization code + S256 PKCE with dynamic client registration. Each user connects separately, signs in to their own Scribed account (including TOTP), and approves `scribed:read` and/or `scribed:write` on the consent page (`/oauth/consent` in the web app). Access tokens are one-hour, resource-bound JWTs — issuer = the Scribed app origin, audience = the public MCP resource URL — verified against the app's JWKS; token and refresh grants without the exact RFC 8707 `resource` are rejected. Add `?workspace=<id>` to scope a connection to one workspace (membership is validated per request, 404 `workspace_not_found` otherwise; the JWT audience stays exactly `/mcp`), and `?categories=core|all|<slugs>` to override the deployment's tool selection for that connection (400 `invalid_categories` on an unknown slug).
479
+
480
+ The hosted adapter signs each catalog-derived loopback request with a short-lived HMAC proof bound to the token, semantic read/write scope, method, and exact path/query (`x-scribed-mcp-*` headers), so OAuth and API-key credentials can never hit arbitrary `/api/*` routes. Read-only grants omit write tools entirely. OAuth consent, provider-secret entry, account authentication changes and live device setup retain interactive boundaries. Supported connection revocation, phone-line administration, workspace administration and credential-vault operations use the same catalog tools and permission/confirmation gates as other product actions. `MCP_TOOL_CATEGORIES` picks the hosted default selection (`core`); JSON-RPC batches are capped at 25 messages and each user at 240 messages per minute.
481
+
482
+ ## Personal-key HTTP API
483
+
484
+ Backend scripts that do not need the CLI or MCP can create a read-only or read/write key in **Settings → API keys** and call the same catalog at `https://app.scribed.ai/v1/tools`. Discovery, JSON schemas, validation, execution, and HMAC provenance reuse this package's catalog adapter; there is no parallel REST contract, and personal keys cannot authenticate private `/api/*` routes.
485
+
486
+ ```bash
487
+ curl https://app.scribed.ai/v1/tools \
488
+ -H "Authorization: Bearer $SCRIBED_API_KEY"
489
+
490
+ curl https://app.scribed.ai/v1/tools/objects_records_list \
491
+ -H "Authorization: Bearer $SCRIBED_API_KEY" # description + JSON input schema
492
+
493
+ curl -X POST https://app.scribed.ai/v1/tools/objects_records_list \
494
+ -H "Authorization: Bearer $SCRIBED_API_KEY" \
495
+ -H "Content-Type: application/json" \
496
+ -H "X-Workspace-Id: <workspace uuid>" \
497
+ --data '{"pack":"sales","key":"contacts"}'
498
+ ```
499
+
500
+ Contract: the body is the tool's arguments object (validated against the same schema `scribed tools show` prints); success is `200 { tool, result, requestId }`; failures are `{ error: { code, message }, requestId }` (plus `toolStatus` when the upstream tool failed) with codes such as `invalid_api_key` (401), `insufficient_scope` (403), `tool_not_found` (404), `invalid_arguments` (400), `invalid_json` (400), `idempotency_key_required` (400), `idempotency_conflict` (409), `request_too_large` (413 — bodies are capped at 64 MiB, with smaller per-tool decoded file limits), `rate_limit_exceeded` (429 — 120 requests per minute per key), `tool_request_failed` (the upstream tool's own 4xx / 503, sanitized), and `tool_execution_failed` (502). Every response carries `X-Request-Id`. Write tools additionally require an `Idempotency-Key` header (8–200 printable characters; the CLI sends a random UUID) — a replay with the same arguments returns the recorded response with `Idempotency-Replayed: true`. A read-only key sees and may call only read tools. `X-Workspace-Id` scopes workspace-bound tools (400 `invalid_workspace_id` unless a UUID); a key pinned to a workspace always acts there and refuses a different header with 403 `api_key_workspace_mismatch`.
501
+
502
+ ## Development
503
+
504
+ - `bun run test` — catalog adapter, credential modes, MCP registry, auth, config, the command regressions (incl. the end-to-end flows in `src/commands/flows.test.ts` — convert / pipeline / approve / bulk-set / add-kpi against an in-memory API), and the ported frontend rules in `src/lib/` (pipeline-summary, table-summaries, field-roles, csv, run-trace, kpi)
505
+ - `bun run type-check` — `tsc --noEmit` (also type-checks the imported scribed-agent registry)
506
+ - `bun run build` — Node-targeted Bun bundle of `src/index.ts` + `src/mcp/server.ts` into `dist/`
507
+ - `bun run verify:bundle` — fails if agent-only server code (Express, AI SDK, worker secrets) leaked into `dist/`
508
+ - `bun run verify:stdio` — exercises the real Node stdio bin/protocol for the core, all, and slug selections
509
+ - `bun run verify:files` — exercises the packaged Node CLI against a local fixture: JSON file/stdin input, raw and multipart uploads, ranged downloads and overwrite protection
510
+ - `bun run verify:catalog` — checks the shared registry against scribed-api's mounted routes: metadata, validated synthetic arguments, mocked HTTP execution and machine-capable actor helpers. The inverse check covers **every `/api` route**, including cookie-only and unclassified routes; an omitted action needs an explicit browser/protocol boundary in `scripts/ui-parity-policy.ts`. The inventory constructs routers with fixture configuration, without Doppler, database access or invoking handlers. `--routes <json>` accepts an existing inventory; `--coverage <json>` saves the route-to-tool map and boundary reasons. This checks source route coverage; focused fixture tests additionally verify payload fields, permissions, file transport and behavior. Passing these checks does not establish production deployment, provider availability, real-device audio/push behavior or live delivery.
511
+ - From the monorepo root, `bun run verify:parity` builds the CLI adapter and runs both catalog checks. The Agent and CLI parity pull-request workflow runs this check, packaged Node MCP discovery, agent/CLI tests and companion API permission/transport tests without production credentials. These checks prove local contract conformance; provider configuration, delivery and device behavior still require an authenticated environment-specific smoke run.
512
+ - CI separately runs the six embedded PostgreSQL suites for Drive/publication constraints, workspace/reminder visibility, document receipt transactions and notifications. It installs exactly `@electric-sql/pglite@0.5.8` in a temporary project with lifecycle scripts disabled and sets all three runtime-path variables so those checks cannot silently skip. Product dependencies and lockfiles are unchanged; no live database is used.
513
+ - `bun run smoke` — LIVE tool matrix against a running scribed-api (`scripts/smoke-tools.ts`): env `SCRIBED_API_URL` (default `http://localhost:8081`), `SCRIBED_TOKEN` (a bearer session token), `SCRIBED_WORKSPACE_ID` (default: the first workspace); flags `--only <substring>`, `--json <file>`. Runs every read tool plus a create → read → update → delete lifecycle per domain on rows it created; exits 1 on any failed row. It never sends an SMS or email, enrolls, starts a campaign / sequence / dial session, or invites a real address — it does schedule-and-cancel one text to a fictional 555-01xx number and makes one small AI-draft call.
514
+ - `npm pack --dry-run --ignore-scripts` — inspect the publish tarball
515
+ - The catalog source of truth is `scribed-agent/src/tools.ts`, `tool-catalog.ts`, and `tool-metering.ts`; this package deliberately contains no route/schema definitions of its own. Use `scribed tools categories` for current counts; every new registry tool appears automatically on the CLI, MCP and API-key surfaces.
516
+
517
+ ### Read-only companion smoke
518
+
519
+ `bun run smoke:companions` samples 14 status/list reads across Chat, Tel, Express,
520
+ Quest and Capital through `executeCatalogTool`. Set **all three** environment
521
+ variables explicitly: `SCRIBED_API_URL` (HTTPS API origin, or HTTP loopback),
522
+ `SCRIBED_TOKEN` (Better Auth bearer session), and `SCRIBED_WORKSPACE_ID` (UUID).
523
+ It never picks a workspace for you or reads Personal Notes. It does not send,
524
+ sign, scrape, purchase, start paid jobs or operate device audio/push. Existing
525
+ list reads may perform their normal server-side bookkeeping.
526
+
527
+ ```bash
528
+ # Supply the three variables through your approved environment; do not paste tokens here.
529
+ bun run smoke:companions --json /tmp/scribed-companions-report.json
530
+ ```
531
+
532
+ The report contains tool names, statuses and timings, without returned account
533
+ or record contents. An existing report file is never overwritten. Exit codes:
534
+ `0` all sampled reads passed, `1` authorization/transport/response-shape or
535
+ unexpected server failure, `2` invalid setup/report output, `3` incomplete due
536
+ to explicit configuration gaps or dependent skips. A generic worker outage is
537
+ a failure, not a configuration skip. Passing is a live **read sample**, not
538
+ proof of write workflows, delivery, signatures, paid execution or device
539
+ behavior. The command has fixture tests; running it against a chosen deployment
540
+ is a separate verification step.
541
+
542
+ For large Personal Notes boards, use `account_notes_list` and `account_note_get`; received shares use `account_notes_shared_list` and `account_note_shared_get`. Confirmed `account_note_create`, `account_note_update` and `account_note_delete` carry the exact board revision and preserve unrelated notes on the server. Never send a partial or truncated board to `account_notes_set`.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // npm bin entry. `bun build` keeps the source's bun shebang in dist/index.js,
3
+ // so the bundle itself can't be the bin — this thin Node wrapper loads it.
4
+ import "../dist/index.js";