@scribed/cli 0.0.0-stage → 0.1.1
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 +24 -0
- package/README.md +603 -2
- package/bin/scribed.mjs +4 -0
- package/dist/index.js +100614 -0
- package/dist/mcp/server.js +91610 -0
- package/package.json +45 -4
- package/types/mcp.d.ts +163 -0
package/README.md
CHANGED
|
@@ -1,3 +1,604 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @scribed/cli
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
### Install Scribed desktop apps
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
scribed apps list --json
|
|
32
|
+
scribed apps download talk # verified download only
|
|
33
|
+
scribed apps install chat --yes # install a new app into ~/Applications
|
|
34
|
+
scribed apps install tel --yes
|
|
35
|
+
scribed apps download chat --directory ./downloads
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
These local commands need no Scribed login. They read the official Talk, Chat and Tel release feeds, check this computer's hardware and operating system, and offer only published installers. Talk requires Apple silicon and macOS 14 or later; Chat and Tel support Apple silicon or Intel Macs on macOS 14 or later, and Windows x64 when its installer is published. A release can raise its minimum OS version. Linux and Windows ARM are unsupported. Rosetta on an Apple silicon Mac selects the native ARM build. Optional local AI model requirements do not block installing Chat or Tel.
|
|
39
|
+
|
|
40
|
+
Downloads use a new private folder, verify the published SHA256 and exact HTTP download size, and never open an installer. Mac installs additionally verify Scribed's signing identity, Gatekeeper acceptance, bundle identity, version and architecture, then verify the staged copy before moving it into `~/Applications`. Existing apps, including registered renamed or moved apps, are preserved; use their update flow. Apps and their data are never removed or replaced. No administrator password, Gatekeeper bypass, or automatic app launch is used.
|
|
41
|
+
|
|
42
|
+
On Windows, the CLI verifies Authenticode trust and reports the signer before opening the published native installer. `installer-opened` means installation is still pending in that window. JSON reports `downloaded`, `installed`, `already-installed`, or `installer-opened`; list results distinguish `available`, `unsupported`, and `unavailable` release metadata. App sign-in, billing and microphone/Accessibility permissions remain separate and are completed in the app. These operating-system commands are not hosted API or MCP tools.
|
|
43
|
+
|
|
44
|
+
## Login
|
|
45
|
+
|
|
46
|
+
Three credential modes; exactly one is stored at a time.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
scribed login # browser OAuth: Google, Apple, password, and 2FA
|
|
50
|
+
scribed login --no-open # print the browser URL instead of opening it
|
|
51
|
+
scribed login --password # terminal email/password (+ TOTP) → bearer session
|
|
52
|
+
scribed login --password --email me@acme.com --api-url http://localhost:8081
|
|
53
|
+
scribed login --api-key scribed_api_… # personal API key (Settings → API keys) for CI/scripts
|
|
54
|
+
scribed login --api-key … --no-verify # store it without the GET /v1/tools liveness check
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Mode | Stored credential | How tools execute |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `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 |
|
|
60
|
+
| `scribed login --password` | Better Auth bearer session token | In-process: the agent tool's own `execute` calls `/api/*` with `Authorization: Bearer` |
|
|
61
|
+
| `scribed login --api-key` / `SCRIBED_API_KEY` | Personal `scribed_api_…` key | REST `POST /v1/tools/:name` with the validated arguments as the body |
|
|
62
|
+
|
|
63
|
+
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`.
|
|
64
|
+
|
|
65
|
+
### First-time account setup
|
|
66
|
+
|
|
67
|
+
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`.
|
|
68
|
+
|
|
69
|
+
### Credentials and environment
|
|
70
|
+
|
|
71
|
+
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:
|
|
72
|
+
|
|
73
|
+
| Variable | Purpose |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `SCRIBED_TOKEN` | Bearer session token (direct `/api/*` execution) |
|
|
76
|
+
| `SCRIBED_API_KEY` | Personal API key (`/v1/tools` execution) |
|
|
77
|
+
| `SCRIBED_API_URL` | API origin (default `https://app.scribed.ai`; HTTP only on loopback) |
|
|
78
|
+
| `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. |
|
|
79
|
+
| `SCRIBED_WORKSPACE_ID` | Workspace to scope every call to (see below) |
|
|
80
|
+
| `SCRIBED_CONFIG_PATH` | Alternate config file location |
|
|
81
|
+
|
|
82
|
+
Never put a token in Cursor/Claude MCP configuration — the stdio process reads the private CLI config itself.
|
|
83
|
+
|
|
84
|
+
## Workspaces
|
|
85
|
+
|
|
86
|
+
Scribed scopes pages, the drive, booking, transcriptions, HR, and notifications to a workspace. Select one once and every command uses it:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
scribed workspace list # your workspaces and roles (workspaces_list)
|
|
90
|
+
scribed workspace use "Acme Sales" # by name or id; verified against your memberships
|
|
91
|
+
scribed workspace current # what the next command will use, and why
|
|
92
|
+
scribed workspace clear # back to the account default
|
|
93
|
+
scribed --workspace <id> pages list # override once
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
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`.
|
|
97
|
+
|
|
98
|
+
Page commands also work without a saved selection: they use your current workspace in Scribed. Pass `--workspace` to choose another workspace for that command.
|
|
99
|
+
|
|
100
|
+
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:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
scribed call workspace_pin workspaceId=<id> pinned=true
|
|
104
|
+
scribed call workspace_pin workspaceId=<id> pinned=false
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Multiple email inboxes
|
|
108
|
+
|
|
109
|
+
Connect each mailbox in Scribed’s Inbox or Settings → Integrations. Discover its
|
|
110
|
+
`accountId` with `scribed inbox status` (Gmail) or
|
|
111
|
+
`scribed inbox status --provider microsoft_outlook`, then select it explicitly:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
scribed inbox threads --account <accountId> --query 'is:unread'
|
|
115
|
+
scribed inbox read <threadId> --account <accountId>
|
|
116
|
+
scribed inbox labels --account <accountId>
|
|
117
|
+
scribed inbox policy --account <accountId>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Thread results show the receiving mailbox. `--account` is required when the
|
|
121
|
+
provider has multiple mailboxes. Catalog tools use `accountId` with `provider`;
|
|
122
|
+
carry both from a thread when replying, downloading attachments, or scheduling
|
|
123
|
+
mail. Search each mailbox separately to inspect all inboxes. A reply must retain
|
|
124
|
+
its original `accountId`, `threadId`, and `inReplyTo`; confirm the From mailbox
|
|
125
|
+
alongside the recipients and message before sending. CLI and MCP use the same
|
|
126
|
+
account selection contract as the in-product agent.
|
|
127
|
+
|
|
128
|
+
## Team Chat
|
|
129
|
+
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
scribed chat list
|
|
134
|
+
scribed chat members
|
|
135
|
+
scribed chat read <conversation-id> --limit 30
|
|
136
|
+
scribed chat read <conversation-id> --thread <root-message-id>
|
|
137
|
+
scribed chat search 'launch plan' --conversation <conversation-id>
|
|
138
|
+
scribed chat mentions
|
|
139
|
+
scribed chat dm <teammate-user-id> --yes
|
|
140
|
+
scribed chat channel planning --external-id team-planning --yes
|
|
141
|
+
scribed chat send <conversation-id> --text 'The plan is ready.' --client-message-id launch-plan-1 --yes
|
|
142
|
+
scribed chat forward <message-id> <destination-conversation-id> --client-message-id forward-plan-1 --yes
|
|
143
|
+
scribed chat files <conversation-id>
|
|
144
|
+
scribed call chat_attachment_upload --file file=./brief.pdf
|
|
145
|
+
scribed call chat_attachment_download attachmentId=<id> --out ./brief.pdf
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
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.
|
|
149
|
+
|
|
150
|
+
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.
|
|
151
|
+
|
|
152
|
+
## Companion platforms
|
|
153
|
+
|
|
154
|
+
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:
|
|
155
|
+
|
|
156
|
+
- Chat: `team-chat` (default) and `personal-chat` (opt-in). Personal conversations use the acting account; workspace history retains its original membership checks.
|
|
157
|
+
- Tel: `phone-texting` (default), including recording/coaching preferences and confirmed registration workflows. Live microphones, push registration and device credentials remain device operations.
|
|
158
|
+
- Express: `documents` (opt-in), including templates, drafts, revisions, packets, exports and signatures.
|
|
159
|
+
- 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.
|
|
160
|
+
- Capital: `safes-e-signature` (default), including company settings, previews, envelope lifecycle and downloads.
|
|
161
|
+
- News: `scribed-news` (opt-in), for published articles, public build activity and RSS; private publishing evidence stays operator-only.
|
|
162
|
+
|
|
163
|
+
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.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
scribed tools list --category scribed-quest
|
|
167
|
+
scribed call lead_sources_list
|
|
168
|
+
scribed call lead_runs_list limit=20
|
|
169
|
+
scribed mcp --categories core,personal-chat,personal-notes,documents,scribed-quest
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Commands
|
|
173
|
+
|
|
174
|
+
Global flags on every command: `--json`, `--api-url <url>`, `--upload-api-url <url>`, `--workspace <id>`.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
scribed whoami # GET /api/me (or the active API key)
|
|
178
|
+
scribed logout # revoke the session + clear the stored credential
|
|
179
|
+
scribed config # config path + effective settings
|
|
180
|
+
|
|
181
|
+
# The full catalog (docs + execution):
|
|
182
|
+
scribed tools list [--category core|all|<slugs>] [--search <text>] [--offline]
|
|
183
|
+
scribed tools categories [--offline] # available category slugs + core/opt-in
|
|
184
|
+
scribed tools show <name> [--offline] # description + JSON schema
|
|
185
|
+
scribed call <name> [key=value ...] [--args '<json>' | --input arguments.json]
|
|
186
|
+
scribed call studio_upload --file file=./photo.png
|
|
187
|
+
scribed call document_content_save --input revision.json
|
|
188
|
+
scribed call studio_asset_download assetId=<id> format=pdf --out design.pdf
|
|
189
|
+
scribed call document_packet_download --input packet.json --out packet.zip
|
|
190
|
+
|
|
191
|
+
# Inspect Studio artwork as native image content in an MCP/agent client.
|
|
192
|
+
scribed call studio_inspect_preview assetId=<id> target=asset
|
|
193
|
+
|
|
194
|
+
# Read/edit real Word and Excel files using the UI's document engines.
|
|
195
|
+
scribed call vault_office_read fileId=<id> sheet=Sheet1 range=A1:D20 includeStyles=true
|
|
196
|
+
scribed call vault_office_edit --input office-edit.json
|
|
197
|
+
scribed call vault_office_create path=Reports/Budget.xlsx kind=xlsx
|
|
198
|
+
# Edits require expectedVersion from the preceding read and an edit object
|
|
199
|
+
# (docx JSON-pointer patches, xlsx operations, or precise OOXML parts).
|
|
200
|
+
# Optional destinationPath exports an edited copy and preserves the original.
|
|
201
|
+
# A conflict or preservation refusal requires a fresh read, not a blind retry.
|
|
202
|
+
# Legacy .xls files convert into a new .xlsx without changing the original:
|
|
203
|
+
scribed call vault_office_convert fileId=<id> expectedVersion=<version>
|
|
204
|
+
|
|
205
|
+
# Inspect PDF page geometry, then save a rotated copy without changing its source.
|
|
206
|
+
scribed call vault_pdf_read fileId=<id>
|
|
207
|
+
scribed call vault_pdf_rotate --input pdf-rotations.json
|
|
208
|
+
# MCP/agent clients can also see a saved document's first-page image:
|
|
209
|
+
scribed call document_inspect_preview assetId=<id>
|
|
210
|
+
|
|
211
|
+
# --file accepts dotted/array keys, e.g. --file attachments.0=./proposal.pdf.
|
|
212
|
+
# --out downloads every binary chunk, rejects changed versions, and refuses overwrites.
|
|
213
|
+
# --input - reads JSON from stdin; ordinary inline --file inputs are capped at 32 MiB
|
|
214
|
+
# (each API operation may impose a smaller limit).
|
|
215
|
+
# Transcription recordings above 32 MiB stream directly, up to the app's 250 MiB limit:
|
|
216
|
+
scribed call transcription_upload --file file=./recording.mp4 title="Customer interview"
|
|
217
|
+
# Brand guideline extraction streams PDFs/DOCX/images up to 50 MiB (one paid AI call):
|
|
218
|
+
scribed call studio_brand_extract_upload --file file=./guidelines.pdf
|
|
219
|
+
# A slow upload may exceed the web proxy's timeout. Explicitly choose the trusted API host:
|
|
220
|
+
scribed --upload-api-url https://scribed-api.fly.dev call transcription_upload --file file=./recording.mp4
|
|
221
|
+
# This sends your credential AND file to that host; use only your trusted Scribed API.
|
|
222
|
+
# Default uploads stay on your credential's origin. Streaming has a 15-minute deadline
|
|
223
|
+
# and never retries automatically: check transcriptions_list after an uncertain failure.
|
|
224
|
+
# API-key and browser OAuth writes accept a stable gateway operation key:
|
|
225
|
+
scribed call transcription_upload --file file=./recording.mp4 --idempotency-key interview-2026-10-09
|
|
226
|
+
# Raw gateway uploads carry a SHA-256 digest measured from the selected file.
|
|
227
|
+
# The server verifies those bytes before execution; retain the same file and key.
|
|
228
|
+
|
|
229
|
+
# Vault --file uses the browser's direct private-storage lifecycle for every size,
|
|
230
|
+
# including empty files. Only a scoped one-object grant reaches the storage SDK;
|
|
231
|
+
# your Scribed credential stays on your chosen API/MCP origin.
|
|
232
|
+
scribed call vault_upload path=Archives/recording.mp4 --file file=./recording.mp4 idempotencyKey=archive-recording-2026-10-01
|
|
233
|
+
# Retry the same file/path with the same key after an uncertain response. A saved
|
|
234
|
+
# file is reused, and an already uploaded object is finalized without uploading again.
|
|
235
|
+
# Ctrl-C cancels the pending reservation but preserves an already committed file.
|
|
236
|
+
# The server's ceiling is 5 TiB, subject to account quota and provider/connection
|
|
237
|
+
# limits; this is not a tested 5 TiB transfer guarantee. SDK multipart buffering is
|
|
238
|
+
# bounded (currently ~128 MiB in Node), and the grant expires after 24 hours.
|
|
239
|
+
# Inline JSON still uses the existing <=25 MiB upload route. Other clients can use
|
|
240
|
+
# vault_upload_start / vault_upload_complete / vault_upload_cancel and the grant;
|
|
241
|
+
# Vault direct uploads are deliberately refused by the generic /v1/uploads proxy.
|
|
242
|
+
|
|
243
|
+
# Ergonomic shortcuts (aliases over the same catalog tools):
|
|
244
|
+
scribed records types # objects_types_list
|
|
245
|
+
scribed records list contacts # objects_records_list (collection id or key; `pack/key` still accepted)
|
|
246
|
+
scribed records get sales/contacts <recordId> # objects_record_read
|
|
247
|
+
scribed records schema deals # the fields: key / label / type / role / options / formula (objects_types_list)
|
|
248
|
+
scribed records query deals --where stage:in:"Soft Commit,Docs Sent" --sort amount:desc [--group] [--archived] [--view <id>] [--limit 200]
|
|
249
|
+
scribed records query deals --view <boardId> --board-preview --json # bounded cards and nextCursor per lane
|
|
250
|
+
scribed records query deals --view <boardId> --lane-field round --lane <groupKey> [--cursor <nextCursor>] # exact lane; --lane null = unassigned
|
|
251
|
+
scribed records query deals --view <timelineId> --timeline-scope --json # dated rows in chronological order plus dateScope
|
|
252
|
+
# objects_records_query — filters by field key + option id or unique label, whole-set aggregates (ONE page; every row is `export`)
|
|
253
|
+
scribed records create deals name="Ada — SAFE" amount=25000 stage="Soft Commit" # objects_record_create
|
|
254
|
+
scribed records set <recordId> stage="Docs Sent" # objects_record_update
|
|
255
|
+
scribed records bulk-set deals --ids a,b,c stage="Docs Signed" --yes # one objects_record_update per id, stops at the first failure
|
|
256
|
+
scribed records note <recordId> "Called, sending the deck" [--pin] # objects_note_create
|
|
257
|
+
scribed records archive <recordId> --yes | records restore <recordId> # objects_record_delete / objects_record_restore
|
|
258
|
+
scribed records views deals # objects_views_list
|
|
259
|
+
scribed records view create deals --input view.json | view update <viewId> --input patch.json | view delete <viewId> --yes
|
|
260
|
+
# objects_view_create / objects_view_update / objects_view_delete
|
|
261
|
+
scribed records summary deals [--view <id>] # the totals footer: count + Σ / avg per numeric column (objects_records_query + objects_views_list)
|
|
262
|
+
scribed records lookup +14155550101 | records lookup ada@example.com # objects_record_lookup (caller ID; an @ = by email)
|
|
263
|
+
scribed records import deals deals.csv [--map "Amount ($)=amount"] [--set stage="Soft Commit"] [--ai] [--dry-run] [--concurrency 4]
|
|
264
|
+
# CSV / TSV / JSON parsed here → objects_record_create per row (--ai: objects_import_suggest_mapping)
|
|
265
|
+
scribed records export deals [--where …] [--columns name,amount,stage] [--max-rows 50000] --out deals.csv
|
|
266
|
+
# objects_records_export — EVERY matching row, rendered server-side (labels, member names, linked
|
|
267
|
+
# titles); reserved for the workspace owner and the members granted "Export data" in Settings →
|
|
268
|
+
# Workspace — anyone else gets the API's 403 sentence
|
|
269
|
+
|
|
270
|
+
# Leads (the Fundraising pack's `leads` collection; --collection <key> on the group overrides it):
|
|
271
|
+
scribed leads list [--stage "Soft Commit" --stage "Docs Sent"] [--assigned me|<email|name|id>|any|archived] [--raise <roundRecordId>]
|
|
272
|
+
[--q ada] [--sort commitment:desc] [--limit 50] [--cursor …] [--board-preview] [--lane-field <key> --lane <groupKey>] [--timeline-scope]
|
|
273
|
+
# objects_records_query → ID / NAME / STAGE / COMMITMENT / ASSIGNED TO / UPDATED + "N leads · Σ Commitment $…"
|
|
274
|
+
scribed leads get <id> | create name=… email=… | set <id> key=value… | stage <id> "Demo Booked" | note <id> "…" [--pin]
|
|
275
|
+
scribed leads archive <id> --yes | restore <id>
|
|
276
|
+
scribed leads convert <id> [--archive] --yes
|
|
277
|
+
# the app's Convert to <Investor / Customer / Client …>: ONE retry-safe server call
|
|
278
|
+
# (objects_record_convert_to_investor → POST …/records/:id/convert) — the shared fields copy onto the
|
|
279
|
+
# workspace's contact of record, the deals linked to the lead follow it, the lead moves to its last won
|
|
280
|
+
# stage, optionally archived; a repeat replays the first conversion, never a duplicate; the API's refusals
|
|
281
|
+
# (conversion_not_supported / conversion_deal_conflict / conversion_deal_schema / 404) exit with their code;
|
|
282
|
+
# without --yes the existing conversion is read (objects_record_conversion_get), else a confirmation line —
|
|
283
|
+
# nothing is written
|
|
284
|
+
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
|
|
285
|
+
scribed leads metrics [--view <id>] # the Metrics sheet: count, Σ / avg per numeric field, win rate, by stage
|
|
286
|
+
scribed leads import leads.csv [--map …] [--set …] [--ai] [--dry-run] | leads export [--columns …] [--max-rows n] [--out leads.csv]
|
|
287
|
+
# export = objects_records_export, the reserved "Export data" action (owner + granted members)
|
|
288
|
+
|
|
289
|
+
# Industry packs:
|
|
290
|
+
scribed packs list # pack_list (installed state + page refs)
|
|
291
|
+
scribed packs install investors [--pages dashboard.metrics] --yes # pack_install (admin / owner; --pages = a scoped install)
|
|
292
|
+
# (a pack is copy-on-create and owns nothing after install — there is no uninstall; delete its pages / collections directly)
|
|
293
|
+
|
|
294
|
+
scribed vault list # vault_list
|
|
295
|
+
scribed vault read "Proposals/acme.pdf" # vault_read (PDF/DOCX extracted)
|
|
296
|
+
scribed pages list [workspaceId] # workspace_pages_list (selected or current workspace)
|
|
297
|
+
scribed pages search "onboarding" # workspace_pages_search (every workspace)
|
|
298
|
+
scribed pages read <pageId> # workspace_page_read (selected or current workspace)
|
|
299
|
+
scribed pages read <workspaceId> <pageId> # explicit workspace; existing syntax still works
|
|
300
|
+
scribed pages create "Blank page" # an empty doc in the selected/current workspace
|
|
301
|
+
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]
|
|
302
|
+
# workspace_page_create
|
|
303
|
+
scribed pages update <pageId> [--title …] [--icon …|none] [--parent <id>|none] [--home|--no-home] [--sort n] [--view <id>] [--module-config settings.json] # workspace_page_update
|
|
304
|
+
scribed pages blocks <pageId> # the blocks with their kinds + bindings (workspace_page_read)
|
|
305
|
+
scribed pages blocks catalog [kind] # supported widget settings and bindings as JSON
|
|
306
|
+
scribed pages blocks update <pageId> --input changes.json [--yes] # targeted operations; --yes confirms removals
|
|
307
|
+
scribed pages blocks set <pageId> --input blocks.json --yes # workspace_page_blocks_set — REPLACES the page's blocks
|
|
308
|
+
scribed pages add-kpi <pageId> --collection deals [--sum amount] [--target 1000000] [--label Raised]
|
|
309
|
+
# finds-or-mints the kpi view like the app (objects_view_create), then appends the tile
|
|
310
|
+
scribed pages add-view <pageId> --view <viewId> [--kind view|records|pipeline|chart] # appends a saved-view block
|
|
311
|
+
scribed pages write <pageId> --input body.md [--title …] [--expected-updated-at <timestamp>] # workspace_page_write (prose replaced, widgets kept)
|
|
312
|
+
scribed pages reorder <pageId…> # workspace_pages_reorder (the complete order)
|
|
313
|
+
scribed pages delete <pageId> --yes # workspace_page_delete (permanent)
|
|
314
|
+
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
|
|
315
|
+
scribed inbox read <threadId> [--provider …] [--account <accountId>] # email_thread_read (all messages and attachment metadata)
|
|
316
|
+
scribed inbox labels [--provider …] [--account <accountId>] # email_labels
|
|
317
|
+
scribed inbox policy [--provider …] [--account <accountId>] # email_sending_policy (shared limits, usage, cooldown)
|
|
318
|
+
scribed inbox status [--provider …] [--account <accountId>] # email_status
|
|
319
|
+
|
|
320
|
+
# Notes, activity, members (wave 2):
|
|
321
|
+
scribed notes list <recordId> | notes add <recordId> "…" [--pin] | notes edit <noteId> [--text …] [--pin|--unpin] | notes rm <noteId> --yes
|
|
322
|
+
# objects_notes_list / objects_note_create / objects_note_update / objects_note_delete
|
|
323
|
+
scribed activity [<recordId>] [--kind field_changed] [--actor me|<email|name|id>] [--collection leads] [--since 2026-09-01] [--until …] [--limit 50] [--cursor …]
|
|
324
|
+
# objects_activity_feed (the workspace feed) / objects_record_events (one record) — WHEN / RECORD / KIND / FIELD / FROM → TO / BY
|
|
325
|
+
scribed members # workspace_members_list — user id / name / email / role (the names behind every BY column)
|
|
326
|
+
|
|
327
|
+
# Phone & texting:
|
|
328
|
+
scribed texts list [--scope all] [--user <member>] # phone_conversations_list (the manager lens with --scope all)
|
|
329
|
+
scribed texts thread +14155550101 | texts thread --record <recordId> [--limit 20] # phone_thread_read
|
|
330
|
+
scribed texts send +14155550101 --body "…" --yes | texts send +1… --template <templateId> [--merge first_name=Jane] --yes # phone_sms_send
|
|
331
|
+
scribed texts log --to +14155550101 --direction inbound|outbound --body "…" [--at <iso>] [--record <recordId>] [--own <e164>] # phone_text_log
|
|
332
|
+
scribed calls [list] [--record <recordId>] [--scope all] [--user <member>] [--from … --to …] [--direction …] [--status …] [--limit 20] # phone_calls_list
|
|
333
|
+
scribed calls show <callId> # phone_call_get — the summary, the typed action items, a logged call's note / outcome
|
|
334
|
+
scribed calls log --to +14155550101 --direction outbound --outcome connected|voicemail|no_answer|busy|wrong_number|failed
|
|
335
|
+
[--minutes 12 | --seconds 720] [--at <iso>] [--note "…"] [--record <recordId>] [--own <e164>] # phone_call_log
|
|
336
|
+
scribed followups [--call <callId>] [--record <recordId>] [--status pending|added|dismissed] [--limit 50] # call_action_items_list
|
|
337
|
+
scribed appointments [--status confirmed] [--from … --to …] # booking_appointments_list
|
|
338
|
+
scribed transcripts # transcriptions_list
|
|
339
|
+
scribed jobs [--status active|terminal|all] # job_list
|
|
340
|
+
scribed notifications [--unread] # notification_list
|
|
341
|
+
scribed leaderboard [--page <id>] [--window this_week|this_month|this_quarter|last_30|all_time]
|
|
342
|
+
# leaderboard_get (the first page via leaderboard_list when --page is omitted):
|
|
343
|
+
# one table per board — rank / member / value / Δ — plus the unassigned footer
|
|
344
|
+
scribed leaderboard --member <userId> [--page <id>] [--window …]
|
|
345
|
+
# leaderboard_member_get: one member across every board — board / rank of N /
|
|
346
|
+
# value / target progress (the per-day series is the app's sparkline; --json has it)
|
|
347
|
+
scribed leaderboard boards <pageId> [--json] # the page's board config — id / label / source / attribution / direction / target (leaderboard_list)
|
|
348
|
+
scribed leaderboard boards set <pageId> --input boards.json [--window …] [--preview-window …] --yes
|
|
349
|
+
# leaderboard_boards_set — REPLACES the page's boards (1–12; keep a board by its id); manager only
|
|
350
|
+
scribed leaderboard preview --input boards.json [--window …] # leaderboard_compute — standings for an unsaved config, nothing written
|
|
351
|
+
|
|
352
|
+
# Cap table (the Fundraising pack; cap_table_* tools):
|
|
353
|
+
scribed captable [--round <roundId>] # cap_table_get — the raises + what they raised, the table by security class, needs-a-SAFE / pending money
|
|
354
|
+
scribed captable investments [--investor <recordId>] [--status closed|pending] [--round <id>] # cap_table_investments_list
|
|
355
|
+
scribed captable documents --investor <recordId> [--search "SAFE"] [--limit 50] # cap_table_documents_search — Vault, Drive and SAFE envelopes
|
|
356
|
+
scribed captable attach <investmentId> --source vault|drive|safe --file <fileId> --yes # cap_table_document_attach — idempotent append; terms/status stay unchanged
|
|
357
|
+
scribed captable add --investor <recordId> --amount 250000 --doc "SAFEs/acme.pdf" [--round <id>] [--status pending] [--signed-at 2026-09-01]
|
|
358
|
+
[--cap 10000000] [--discount 20] [--instrument post_money_safe] --yes # cap_table_investment_create (docs by Vault path)
|
|
359
|
+
scribed captable close <investmentId> [--signed-at …] --yes # cap_table_investment_update { status: closed } — the money goes on the table
|
|
360
|
+
scribed captable set <investmentId> amount=300000 discount=20 --yes # cap_table_investment_update (key=value terms)
|
|
361
|
+
scribed captable rm <investmentId> --yes # cap_table_investment_delete (permanent; docs stay in the Vault)
|
|
362
|
+
scribed captable settings [--input settings.json --yes] # the founders' shares / option pool / authorized / scenario (cap_table_get) — --input writes (cap_table_settings_update)
|
|
363
|
+
scribed captable preview --input scenario.json [--round <id>] # cap_table_preview — a modelled priced round, nothing saved
|
|
364
|
+
scribed captable raised # cap_table_raised_get — closed / pending sums and counts per raise
|
|
365
|
+
scribed captable extract --investment <id> --attachment <docId> | captable extract --vault-file <fileId>
|
|
366
|
+
# cap_table_extract_terms — AI reads the SAFE; prints the proposal + the `captable set` line to apply it
|
|
367
|
+
|
|
368
|
+
# Forms (public intake forms; form_* tools):
|
|
369
|
+
scribed forms [list] | forms get <formId> # form_list / form_get
|
|
370
|
+
scribed forms create --input form.json | forms update <formId> --input patch.json # form_create / form_update (fieldMappings too)
|
|
371
|
+
scribed forms fields set <formId> --input fields.json # form_fields_set — REPLACES the fields (keep one by its id)
|
|
372
|
+
scribed forms publish <formId> --yes | forms unpublish <formId> --yes # form_set_status — publish puts the /f/<slug> link live
|
|
373
|
+
scribed forms slug <formId> my-form # form_link_set_slug
|
|
374
|
+
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)
|
|
375
|
+
scribed forms submission <submissionId> | forms submission rm <submissionId> --yes # form_submission_get / form_submission_delete
|
|
376
|
+
scribed forms mapping <formId> [--collection leads] # form_mapping_suggestions
|
|
377
|
+
scribed forms rm <formId> --yes # form_delete (archive)
|
|
378
|
+
|
|
379
|
+
# Bulletins (the workspace board; bulletin_* tools):
|
|
380
|
+
scribed bulletins [--drafts] [--expired] | bulletins get <id> # bulletin_list / bulletin_get
|
|
381
|
+
scribed bulletins create --kind alert|notice|article --title "…" [--body "…" | --body-file post.md] [--pin] [--expires <iso>] [--publish --yes] # bulletin_create
|
|
382
|
+
scribed bulletins update <id> [--kind …] [--title …] [--body …|--body-file …] [--pin|--unpin] [--expires <iso>|never] # bulletin_update
|
|
383
|
+
scribed bulletins publish <id> --yes | bulletins unpublish <id> --yes # bulletin_set_status — publish notifies every member
|
|
384
|
+
scribed bulletins rm <id> --yes # bulletin_delete
|
|
385
|
+
|
|
386
|
+
# Credentials (the workspace's shared credential vaults; credentials_* tools — docs/specs/credentials.md §16):
|
|
387
|
+
scribed credentials [vaults] # credentials_vaults_list — the vaults + your role on each (metadata)
|
|
388
|
+
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
|
|
389
|
+
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)
|
|
390
|
+
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)
|
|
391
|
+
scribed credentials versions <itemId> # credentials_item_versions_list — the kept history (newest 20), METADATA only
|
|
392
|
+
scribed credentials events <vaultId> [--limit 30] [--cursor …] # credentials_events_list — who created, changed, revealed, restored or deleted what, who read codes
|
|
393
|
+
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
|
|
394
|
+
scribed call credentials_item_version_reveal itemId=… versionId=… confirmed=true # an earlier version's value (logged with the version number)
|
|
395
|
+
# `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 an API key with write scope and explicit tool access can reach them (and /v1 never replays their answers from stored execution receipts).
|
|
396
|
+
|
|
397
|
+
# SAFEs (YC post-money SAFEs for e-signature; safe_* tools):
|
|
398
|
+
scribed safes forms | safes settings [--input settings.json --yes] # safe_forms_list / safe_settings_get (wire instructions redacted) / safe_settings_update
|
|
399
|
+
scribed safes envelopes [--status sent] | safes envelope get <id> # safe_envelopes_list / safe_envelope_get
|
|
400
|
+
scribed safes envelope update <id> --input patch.json # safe_envelope_update (drafts only)
|
|
401
|
+
scribed safes envelope rm <id> --yes | safes envelope resend <id> --yes # safe_envelope_delete (drafts) / safe_envelope_resend (emails the investor)
|
|
402
|
+
scribed hr clock status | in | out [--note …] # hr_clock_status / hr_clock_in / hr_clock_out
|
|
403
|
+
|
|
404
|
+
# Automations ("Runs" — `scribed runs` is the same group; JSON input may be a file or - for stdin):
|
|
405
|
+
scribed automations catalog [--json]
|
|
406
|
+
scribed automations list [--all-workspaces] # automation_list; --all-workspaces = workspaces_list + one list per workspace
|
|
407
|
+
scribed automations get <automationId>
|
|
408
|
+
scribed automations validate --input graph.json
|
|
409
|
+
scribed automations create "Weekly digest" --input graph.json
|
|
410
|
+
scribed automations update <automationId> --input graph.json
|
|
411
|
+
scribed automations enable|pause|delete <automationId> --yes
|
|
412
|
+
scribed automations run <automationId> [--variables values.json] --yes
|
|
413
|
+
scribed runs history [--limit 50] [--automation <automationId>] # automation_runs_list (one automation's rail with --automation)
|
|
414
|
+
scribed runs status <runId> # the human trace (decisions, waits, approvals, sends, assignments); --json for the raw envelope
|
|
415
|
+
scribed runs pending # runs parked on an "Ask for approval" step (automation_runs_list → automation_run_status)
|
|
416
|
+
scribed runs approve <runId> [--comment "…"] [--set body="…"] --yes # automation_run_decide — prints the request first;
|
|
417
|
+
scribed runs reject <runId> [--comment "…"] --yes # only a named approver or an owner / admin may decide (the in-product
|
|
418
|
+
# chat agent relays the same human decision; an automation's agent steps never can)
|
|
419
|
+
scribed runs cancel <runId> --yes # automation_run_cancel (queued, running or waiting)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
`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.
|
|
423
|
+
|
|
424
|
+
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.
|
|
425
|
+
|
|
426
|
+
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`).
|
|
427
|
+
|
|
428
|
+
`--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.
|
|
429
|
+
|
|
430
|
+
`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:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
scribed call objects_record_create pack=sales key=contacts values.name="Jane Doe" values.email=jane@acme.com
|
|
434
|
+
# A formula field (a Fundraising deal's Payout) is computed by Scribed — never pass it; set what it reads
|
|
435
|
+
scribed call objects_record_update key=deals recordId=<uuid> values.amount=25000 values.openerCommissionRate=20
|
|
436
|
+
scribed call phone_sms_send --args '{"to":"+14155550101","body":"Running 5 minutes late"}'
|
|
437
|
+
scribed call booking_appointments_list workspaceId=<uuid> status=confirmed order=asc limit=10
|
|
438
|
+
scribed call hr_timecards_list workspaceId=<uuid>
|
|
439
|
+
# Lead distribution (Tools → Lead distribution): interpret = plain English → settings (the one call that spends AI credit);
|
|
440
|
+
# plan = a deterministic preview that writes nothing (limit=200 plans exactly one apply batch); apply = the accepted rows
|
|
441
|
+
# (≤ 200 per call, each with the plan's `from` so a record a teammate moved since is skipped changed_since) with the plan's
|
|
442
|
+
# strategy / scope / settings, recorded on the collection's distribution History — a longer job goes in rounds (plan →
|
|
443
|
+
# apply → plan again), every later apply passing the FIRST apply's historyId so the whole job is one History row.
|
|
444
|
+
# Pool / rule / to members must be current members (a departed member or an ambiguous name is refused before any request).
|
|
445
|
+
scribed call records_distribution_interpret collectionId=leads instructions="give Dan's leads to Ada and Bob, Ada 50"
|
|
446
|
+
scribed call records_distribution_plan collectionId=leads scope=unassigned strategy=round_robin limit=200 \
|
|
447
|
+
pool='[{"userId":"ada@acme.com","cap":50},{"userId":"bob@acme.com"}]'
|
|
448
|
+
scribed call records_distribution_apply collectionId=leads fieldId=assignedTo strategy=round_robin scope=unassigned \
|
|
449
|
+
settings='{"pool":[{"userId":"ada@acme.com","cap":50},{"userId":"bob@acme.com"}]}' \
|
|
450
|
+
changes='[{"recordId":"<uuid>","to":"ada@acme.com","from":["<previous holder id>"]}]' [historyId=<uuid of the first apply>]
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Async work (meeting/YouTube transcriptions, automation runs) returns an id immediately — poll `job_status` / `automation_run_status` rather than busy-polling.
|
|
454
|
+
|
|
455
|
+
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.
|
|
456
|
+
|
|
457
|
+
## MCP server
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
scribed mcp # stdio MCP server (core tool set)
|
|
461
|
+
scribed mcp --categories all # every catalog tool
|
|
462
|
+
scribed mcp --categories hr,automations,objects-crm-records
|
|
463
|
+
scribed --workspace <id> mcp # pin every call to one workspace
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Auth comes from the config file or `SCRIBED_TOKEN` / `SCRIBED_API_KEY`. When using an API key, the local MCP server advertises only the intersection of its selected categories and the key’s permitted tools; restart it after changing those permissions. Every call is authorized again by the API. 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.
|
|
467
|
+
|
|
468
|
+
Setup helpers print configuration by default and never silently edit another application:
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
scribed setup cursor # print stdio mcp.json entry
|
|
472
|
+
scribed setup cursor --install # explicitly merge the entry into ~/.cursor/mcp.json
|
|
473
|
+
scribed setup cursor --hosted # print the hosted OAuth URL entry
|
|
474
|
+
scribed setup claude-code # print the `claude mcp add` command
|
|
475
|
+
scribed setup claude-code --install # explicitly run it
|
|
476
|
+
scribed setup claude-code --hosted # hosted OAuth transport
|
|
477
|
+
scribed setup codex # print the `codex mcp add` command
|
|
478
|
+
scribed setup codex --install # explicitly run it
|
|
479
|
+
scribed setup codex --hosted # hosted OAuth transport
|
|
480
|
+
scribed setup codex --categories core,connected-apps,social-accounts
|
|
481
|
+
# The same --categories option works with cursor and claude-code.
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
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)).
|
|
485
|
+
|
|
486
|
+
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.
|
|
487
|
+
|
|
488
|
+
### Hosted MCP (Cursor, Claude Code, Codex, and ChatGPT)
|
|
489
|
+
|
|
490
|
+
The production Streamable HTTP endpoint is:
|
|
491
|
+
|
|
492
|
+
```text
|
|
493
|
+
https://app.scribed.ai/mcp
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
(`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`.)
|
|
497
|
+
|
|
498
|
+
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).
|
|
499
|
+
|
|
500
|
+
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.
|
|
501
|
+
|
|
502
|
+
## Personal-key HTTP API
|
|
503
|
+
|
|
504
|
+
An API key belongs to **your Scribed account** and lets a script or unattended integration act as you through the shared tool API. Browser sign-in with `scribed login` and the hosted MCP connection do not need one. An API key is optional for the CLI and local `scribed mcp`; hosted `/mcp` uses OAuth. Create and manage keys in **Settings → Account → API keys**.
|
|
505
|
+
|
|
506
|
+
Choose a name, expiry, read-only or read/write access, and the exact tools the integration needs. Selected tools stay fixed when new tools are released. “All tools” includes future tools within the key's read/write scope; existing keys keep that behavior until edited. Read/write permission alone never overrides a tool restriction, your current account access, workspace membership, role, seat or billing rules. A workspace restriction limits workspace operations to one workspace you belong to; the key is still account-owned and permitted account tools still act on your personal account. To exclude those tools, leave them out of the tool selection.
|
|
507
|
+
|
|
508
|
+
Store the secret in your deployment's secret manager as `SCRIBED_API_KEY`. Start with the **Account basics** preset or explicitly allow `workspaces_list` for this read-only connection check:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
curl https://app.scribed.ai/v1/tools \
|
|
512
|
+
-H "Authorization: Bearer $SCRIBED_API_KEY"
|
|
513
|
+
|
|
514
|
+
curl -X POST https://app.scribed.ai/v1/tools/workspaces_list \
|
|
515
|
+
-H "Authorization: Bearer $SCRIBED_API_KEY" \
|
|
516
|
+
-H "Content-Type: application/json" \
|
|
517
|
+
--data '{}'
|
|
518
|
+
|
|
519
|
+
# Find permitted tools, then read the exact input schema before calling one.
|
|
520
|
+
curl 'https://app.scribed.ai/v1/tools?q=records' \
|
|
521
|
+
-H "Authorization: Bearer $SCRIBED_API_KEY"
|
|
522
|
+
|
|
523
|
+
curl https://app.scribed.ai/v1/tools/objects_records_list \
|
|
524
|
+
-H "Authorization: Bearer $SCRIBED_API_KEY"
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
The catalog lists only tools allowed by the key and accepts `q` and `category` filters (category display name or slug). A schema lookup for an excluded tool returns `404`; an attempted call returns `403 api_key_tool_not_allowed`. For workspace tools, pass `X-Workspace-Id: <workspace UUID>` or set `--workspace` in the CLI. A key's workspace restriction cannot be overridden by a header or tool argument. Account tools do not need a workspace. Keys cannot authenticate private `/api/*` routes.
|
|
528
|
+
|
|
529
|
+
With an API key selected, `scribed tools list`, `categories`, and `show` use authenticated discovery from the API. `--offline` explicitly shows the bundled reference catalog, which does not establish your permissions. Invalid keys and network failures do not silently fall back to a broader catalog. If a tool is newer than your installed CLI, update the CLI before calling it. Some shortcuts use several tools: direct Vault file uploads need `vault_upload_start`, `vault_upload_complete` and `vault_upload_cancel`, not just `vault_upload`.
|
|
530
|
+
|
|
531
|
+
You can edit a key's name, tool selection, read/write access and expiry without replacing its secret. Replacement offers immediate revocation, a one-hour overlap, or a 24-hour overlap; an existing expiry can shorten that window. Only create and replace reveal the secret, once. Revoke immediately if a key is compromised. Recent write activity shows tool names, request IDs, times and outcomes across replacements; it excludes request/response contents, read calls and secret-reveal calls. It is a troubleshooting view of durable write receipts, not a complete audit of every request.
|
|
532
|
+
|
|
533
|
+
The POST body is the tool's arguments object, validated against its schema.
|
|
534
|
+
First execution returns `200 { tool, result, requestId }`; failures return
|
|
535
|
+
`{ error: { code, message }, requestId }`, with `toolCode`, `toolStatus`,
|
|
536
|
+
`retryAfterSeconds` and a non-content execution receipt when available. Each
|
|
537
|
+
response carries `X-Request-Id`.
|
|
538
|
+
|
|
539
|
+
Writes require `Idempotency-Key` (8–200 non-space ASCII characters).
|
|
540
|
+
`scribed call --idempotency-key <key>` supplies it; when omitted, the adapter
|
|
541
|
+
generates one UUID for that invocation and exposes it if the outcome is
|
|
542
|
+
uncertain. Browser OAuth carries the same identity in MCP request metadata
|
|
543
|
+
`_meta['scribed/idempotencyKey']`. Local stdio MCP accepts that metadata too.
|
|
544
|
+
Third-party MCP hosts must retain that metadata key across transport retries.
|
|
545
|
+
Unkeyed calls receive a new identity each time; the gateway cannot recognize
|
|
546
|
+
two separately submitted unkeyed calls as the same operation.
|
|
547
|
+
Retain the same `Idempotency-Key`, arguments and selected file when checking an uncertain
|
|
548
|
+
operation; replacements made with the new rotation flow retain the same duplicate-write identity
|
|
549
|
+
(older, already-replaced keys are not retroactively combined); the CLI never automatically retries a write. Direct session calls
|
|
550
|
+
and direct-provider Vault transfers use their tool-specific idempotency
|
|
551
|
+
arguments and reject this transport flag.
|
|
552
|
+
|
|
553
|
+
Repeated writes return receipt state `running`, `completed`, `failed`, `unknown`
|
|
554
|
+
or `retryable`, plus the original request ID and timestamps. A completed replay
|
|
555
|
+
returns `{ receipt, message }` with `resultAvailable:false`; it does not replay
|
|
556
|
+
the original result or bypass current authorization. Inspect current state
|
|
557
|
+
with the appropriate read tool. An unknown or failed receipt does not authorize
|
|
558
|
+
a second operation with a fresh key. A server-confirmed retryable outcome may
|
|
559
|
+
be retried with the same key after its stated cooldown. Raw `/v1/uploads`
|
|
560
|
+
additionally requires `X-Scribed-Content-SHA256`; CLI `--file` measures the
|
|
561
|
+
selected file through the same handle it streams. The API checks bytes while
|
|
562
|
+
streaming and withholds the final chunk until the length and digest match.
|
|
563
|
+
|
|
564
|
+
## Development
|
|
565
|
+
|
|
566
|
+
- `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)
|
|
567
|
+
- `bun run type-check` — `tsc --noEmit` (also type-checks the imported scribed-agent registry)
|
|
568
|
+
- `bun run build` — Node-targeted Bun bundle of `src/index.ts` + `src/mcp/server.ts` into `dist/`
|
|
569
|
+
- `bun run verify:bundle` — fails if agent-only server code (Express, AI SDK, worker secrets) leaked into `dist/`
|
|
570
|
+
- `bun run verify:stdio` — exercises the real Node stdio bin/protocol for the core, all, and slug selections
|
|
571
|
+
- `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
|
|
572
|
+
- `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.
|
|
573
|
+
- 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.
|
|
574
|
+
- 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.
|
|
575
|
+
- `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.
|
|
576
|
+
- From the monorepo root, `bun run ci:cli` runs the fleet's CLI release checks. The main-branch release workflow waits for matching production deployments, builds a versioned tarball, verifies it under Node 20, and publishes through npm's short-lived GitHub identity exchange. See [PUBLISHING.md](PUBLISHING.md) for setup, packed export normalization and retries.
|
|
577
|
+
- 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 is available to the CLI, MCP and API-key surfaces, subject to each credential’s permissions and category selection.
|
|
578
|
+
|
|
579
|
+
### Read-only companion smoke
|
|
580
|
+
|
|
581
|
+
`bun run smoke:companions` samples 14 status/list reads across Chat, Tel, Express,
|
|
582
|
+
Quest and Capital through `executeCatalogTool`. Set **all three** environment
|
|
583
|
+
variables explicitly: `SCRIBED_API_URL` (HTTPS API origin, or HTTP loopback),
|
|
584
|
+
`SCRIBED_TOKEN` (Better Auth bearer session), and `SCRIBED_WORKSPACE_ID` (UUID).
|
|
585
|
+
It never picks a workspace for you or reads Personal Notes. It does not send,
|
|
586
|
+
sign, scrape, purchase, start paid jobs or operate device audio/push. Existing
|
|
587
|
+
list reads may perform their normal server-side bookkeeping.
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
# Supply the three variables through your approved environment; do not paste tokens here.
|
|
591
|
+
bun run smoke:companions --json /tmp/scribed-companions-report.json
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
The report contains tool names, statuses and timings, without returned account
|
|
595
|
+
or record contents. An existing report file is never overwritten. Exit codes:
|
|
596
|
+
`0` all sampled reads passed, `1` authorization/transport/response-shape or
|
|
597
|
+
unexpected server failure, `2` invalid setup/report output, `3` incomplete due
|
|
598
|
+
to explicit configuration gaps or dependent skips. A generic worker outage is
|
|
599
|
+
a failure, not a configuration skip. Passing is a live **read sample**, not
|
|
600
|
+
proof of write workflows, delivery, signatures, paid execution or device
|
|
601
|
+
behavior. The command has fixture tests; running it against a chosen deployment
|
|
602
|
+
is a separate verification step.
|
|
603
|
+
|
|
604
|
+
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`.
|