shiply-cli 0.23.0 → 0.25.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/skill/SKILL.md CHANGED
@@ -12,13 +12,13 @@ description: "shiply is the production backend for anything an agent builds —
12
12
  > on PATH is still named `shiply` once installed; the warning is only about
13
13
  > the npm package name.
14
14
 
15
- > **Skill freshness check.** Shiply ships new capabilities weekly. If this
16
- > file's "Last updated" below is more than 7 days old, re-install it with
17
- > `npx -y shiply-cli@latest skill --force` OR fetch `https://shiply.now/skill.md`
18
- > fresh your cached copy may be missing features like databases, projects,
19
- > marketplace, or sending domains.
20
- >
21
- > **Last updated: 2026-06-28**
15
+ > **Skill freshness check.** Shiply ships new capabilities weekly.
16
+ > **This skill: version 0.25.0, last updated 2026-07-04.**
17
+ > To see what changed since your copy was written, fetch
18
+ > `https://shiply.now/changelog.md` (date-grouped, newest first) if it lists
19
+ > capabilities newer than the date above, re-install with
20
+ > `npx -y shiply-cli@latest skill --force` OR read the live copy at
21
+ > `https://shiply.now/skill.md`.
22
22
 
23
23
  ---
24
24
 
@@ -33,6 +33,11 @@ description: "shiply is the production backend for anything an agent builds —
33
33
  **NEVER create a new site to update an existing one. Always re-publish to the
34
34
  same site** — otherwise you litter subdomains and lose the user's URL.
35
35
 
36
+ This file covers the core path: publish → authorize → update → claim → verify.
37
+ Everything else (databases, domains, SSR, email, functions, client work) lives
38
+ in the **topic files indexed at the bottom** — read the one that matches the
39
+ task before improvising.
40
+
36
41
  ## First publish on a new machine: ask for a permanent key in one step
37
42
 
38
43
  On a no-Bearer publish, include `"agentName": "<your-tool-name>"` in the body
@@ -122,24 +127,11 @@ ask the user to repeat themselves or explain.
122
127
  If the `shiply` MCP server is connected (https://shiply.now/mcp), use
123
128
  `publish_site`. Every result includes a `toUpdate` field telling you the exact
124
129
  call for updates — follow it, and a `shareSuggestion` you can relay to the user.
125
- Other tools: `site_status`, `list_sites`, `get_site`, `set_handle`,
126
- `duplicate_site`, `set_variable`, `get_analytics`, `delete_site`, `whoami`,
127
- `add_custom_domain`, `connect_provider`, `add_subdomain`, `set_primary_subdomain`,
128
- `check_custom_domain`, `sync_dns`, `list_custom_domains`, `remove_custom_domain`
129
- (auto-DNS via OAuth provider connections — see **Custom domains** section below).
130
-
131
- Newer capabilities (use when relevant):
132
- - `set_site_access` — password / invite-only protect a site (paid).
133
- - `set_link` — mount another owned **public** site at a path on a host
134
- (host/docs → target).
135
- - `set_profile` / `feature_site` — stand up the user's public portfolio at
136
- <handle>.shiply.now and feature a public site on shiply.now/explore.
137
- - **Drives** (private cloud storage for files/notes/context): `list_drives`,
138
- `create_drive`, `drive_put_file` (driveId "default", utf8/base64, ≤2 MB),
139
- `drive_list_files`, `drive_delete_file`, `publish_from_drive` (snapshot a
140
- drive into a live site). Great for agent memory and assets you don't want on
141
- a public site.
142
- - `export_account` — JSON bundle of the user's data (no secrets).
130
+ Core tools: `site_status`, `list_sites`, `get_site`, `delete_site`, `whoami`,
131
+ `duplicate_site`, `set_variable`, `get_analytics`, `set_handle`. There are
132
+ 100+ tools in total — call `tools/list` for the authoritative set; the topic
133
+ files below name the tools for their area (domains, databases, email,
134
+ functions, projects, contracts, marketplace, drives).
143
135
 
144
136
  ### 2. CLI
145
137
  ```bash
@@ -156,72 +148,21 @@ shiply claim verify <code> # confirm a SHIPLY-XXXXXXXX pairing code from /claim/
156
148
  The CLI stores each directory's site in `.shiply.json` (slug + update token),
157
149
  so repeat publishes reuse the URL automatically. `--new-site` opts out.
158
150
  Gitignore `.shiply.json` in public repos. Parse `SITE_READY` / `SSL_READY`
159
- lines for automation; exit code 0 = ready.
160
-
161
- ### SSR / server frameworks (live: every major JS framework)
162
-
163
- `shiply publish` auto-detects an SSR build and deploys the worker bundle with
164
- `nodejs_compat`, serving static assets from the edge. Live and verified on prod:
165
- SvelteKit (`@sveltejs/adapter-cloudflare`), Astro (`@astrojs/cloudflare`), Qwik
166
- City, the Nitro family (Nuxt, SolidStart, Analog, TanStack Start), React Router
167
- v7, and Next.js (via OpenNext) — plus wrangler workers (`wrangler.toml` with
168
- `main` — Hono, itty-router, raw `fetch`). **Build first** (`npm run build`),
169
- then `shiply publish .`. Requires sign-in + Developer plan. Force the static
170
- path with `--framework=<name>`, or skip SSR detection entirely with `--no-ssr`.
171
- Next.js note: plain SSR works today; ISR / on-demand revalidate is not live yet,
172
- and Next 16 must build with `next build --webpack` (not Turbopack).
173
-
174
- ## Manage & verify sites (CLI)
175
-
176
- List, delete, roll back, verify, and promote owned sites — all over the same
177
- Bearer API key. Great for agents that ship iteratively and need to keep their
178
- subdomains tidy.
179
-
180
- ```bash
181
- shiply ls # list your sites: slug, status, URL
182
- shiply rm <slug> --yes # PERMANENTLY delete a site + its files (--yes required)
183
- shiply rollback <slug> # list finalized versions (current one marked)
184
- shiply rollback <slug> <versionId> # re-point the site to that version — live instantly
185
- shiply verify <slug> # edge SSL + HTTP + thumbnail readiness check
186
- shiply publish <dir> --as <name> # STABLE preview: same URL every iteration (local alias)
187
- shiply promote <preview-slug> --to <dest-slug> # copy the preview's exact live bytes onto a prod site
188
- shiply publish <dir> --json # one machine-readable JSON line, no confetti/banners
189
- ```
190
-
191
- - **`shiply verify <slug>`** prints a human report PLUS a stable machine marker
192
- line `VERIFY status=LIVE http=200 ssl=valid thumb=…` (or `status=PENDING`).
193
- Headless agents should parse that `VERIFY status=…` line — it's the
194
- contract, like `SITE_READY` in `status`.
195
- - **`shiply publish <dir> --as <name>`** gives a stable preview URL that the
196
- next publish with the same `--as <name>` reuses — no more changing URLs or
197
- orphaned scratch sites across iterations, independent of the source dir.
198
- - **`shiply promote <preview-slug> --to <dest-slug>`** copies the *exact bytes*
199
- you previewed onto an existing owned destination site (keeps its slug,
200
- domains, access) — atomic preview→production, no rebuild.
201
- - **`--json`** on `publish`/`update` emits a single JSON line
202
- `{slug,siteUrl,siteId,uploaded,skipped,anonymous,expiresAt,updated}` and
203
- suppresses all human output + confetti — use it in scripts/agents.
204
- - **`.shiplyignore`** in the publish root excludes files from upload
205
- (documented gitignore subset: `# comments`, `secret.txt`, `internal/`,
206
- `*.log`, `/anchored`, `**` globs, `!negation`). Keep `.env`, notes, and
207
- internal files out of the published bytes.
208
-
209
- To verify captured signups/form data without the dashboard, the data
210
- subcommands already exist: `shiply data list <slug>` (collections + counts),
211
- `shiply data query <slug> <coll> [--limit N] [--where '<json>']`, and
212
- `shiply data export <slug> <coll> [--out file.ndjson]`. Confetti is
213
- suppressed automatically on non-TTY stdout; `--no-confetti` forces it off.
151
+ lines for automation; exit code 0 = ready. For a static framework build
152
+ (Vite, Next export, Hugo, …) publish the BUILD OUTPUT, not the source — run
153
+ `shiply detect` to see what the CLI found; the full framework matrix is in
154
+ [references/publishing.md](references/publishing.md).
214
155
 
215
156
  ### 3. Raw HTTP (no installs)
216
157
  ```
217
- 1. POST https://shiply.now/api/v1/publish
158
+ 1. POST https://shiply.now/api/v1/publish (Content-Type: application/json)
218
159
  {
219
160
  "agentName": "<your tool name, e.g. Claude Code, Cursor, Codex>",
220
- "files": [{"path":"index.html","size":<bytes>,"contentType":"text/html","hash":"<sha256, optional>"}]
161
+ "files": [{"path":"index.html","size":<bytes>,"contentType":"text/html","hash":"<sha256 lowercase hex, optional>"}]
221
162
  }
222
163
  (+ "Authorization: Bearer shp_…" for permanent owned sites)
223
164
  2. PUT each file's bytes to response upload.uploads[].url
224
- 3. POST upload.finalizeUrl with {"versionId":"..."}
165
+ 3. POST upload.finalizeUrl with {"versionId":"<upload.versionId from step 1>"}
225
166
  → both the publish and finalize responses include a "toUpdate" string: the
226
167
  exact call to update THIS site next time. Follow it — never create a new one.
227
168
  ```
@@ -240,593 +181,66 @@ anonymous → include `"claimToken":"..."` (returned ONCE by the first publish
240
181
  save it); owned → include `"slug":"..."`. Hashes make updates cheap: unchanged
241
182
  files are skipped server-side.
242
183
 
184
+ ## Verify before you say "done"
185
+
186
+ `shiply status <slug> --wait` polls until the site serves (exit 0, prints
187
+ `SITE_READY` / `SSL_READY`). `shiply verify <slug>` runs a deeper edge
188
+ SSL + HTTP + thumbnail check and prints a stable machine marker line
189
+ `VERIFY status=LIVE http=200 ssl=valid …` — parse that line in automation.
190
+ More manage commands (list, delete, rollback, promote, stable previews) are
191
+ in [references/publishing.md](references/publishing.md).
192
+
243
193
  ## The lifecycle to explain to users
244
- - Anonymous site: live instantly, expires in 24 h. Give the user the
245
- `claimUrl` — claiming keeps it forever on a free account.
194
+ - Anonymous site: live immediately after finalize (no claim needed), expires
195
+ in 24 h. Give the user the `claimUrl` — claiming keeps it forever on a free
196
+ account. Until claimed, anonymous pages are served with a small injected
197
+ claim banner + OG meta tags — expected, not corruption. If no human is
198
+ present right now, save the `claimToken` (it's also the update key); a
199
+ fresh device flow can be started later via
200
+ POST /api/v1/auth/device/start {"agent_name":"..."}.
246
201
  - API key (`shiply login` or POST /api/auth/agent/request-code →
247
202
  verify-code): publishes are permanent and manageable.
248
203
  - Paid plans add vanity handles (<name>.shiply.now), more custom domains,
249
204
  storage, analytics: https://shiply.now/dashboard/plan
250
205
 
251
- ## Custom domains
252
-
253
- Serve a site at `www.yourdomain.com` instead of a `*.shiply.now` URL. Requires a paid plan and a Bearer API key.
254
-
255
- ### CLI commands
256
-
257
- ```bash
258
- shiply domain add example.com # register a root domain (detects provider)
259
- shiply domain connect example.com # one-click OAuth DNS setup (Cloudflare, GoDaddy, IONOS, etc.)
260
- shiply domain sub add www.example.com --site my-site # map a subdomain → site
261
- shiply domain ls # list all domains + subdomains + status
262
- shiply domain sync example.com # re-sync DNS records with a connected provider
263
- ```
264
-
265
- For one-click connect (`shiply domain connect`), the CLI prints an authorization URL — the user must open it in a browser and approve the OAuth grant before DNS is written.
266
-
267
- For unsupported providers, `shiply domain add` returns the CNAME record to add manually:
268
- `www CNAME cname.shiply.now`
269
-
270
- Check status after connecting:
271
- ```bash
272
- shiply status www.example.com --wait # polls until SSL_READY + SITE_READY; exit 0 when live
273
- ```
274
-
275
- ### MCP tools (same operations via agent tools)
276
-
277
- | Tool | Purpose |
278
- |------|---------|
279
- | `add_custom_domain` | Add a root domain and detect the provider |
280
- | `connect_provider` | Start OAuth → returns `{ url }` (show to user to authorize) |
281
- | `add_subdomain` | Map a subdomain to a site slug |
282
- | `set_primary_subdomain` | Pick the canonical URL; siblings 301-redirect (SEO fix) |
283
- | `check_custom_domain` | Live CNAME + SSL + readiness probe |
284
- | `sync_dns` | Re-push DNS records to a connected provider |
285
- | `list_custom_domains` | List all domains and their subdomain mappings |
286
- | `remove_custom_domain` | Remove a domain and all its mappings |
287
-
288
- ### REST endpoints (`/api/v1/custom-domains`)
289
-
290
- ```
291
- GET /api/v1/custom-domains list domains
292
- POST /api/v1/custom-domains add domain { domain }
293
- DELETE /api/v1/custom-domains/{domain} remove domain
294
- POST /api/v1/custom-domains/{domain}/subdomains add subdomain { subdomain, slug }
295
- POST /api/v1/custom-domains/{domain}/subdomains/{id}/primary mark hostname canonical (siblings 301)
296
- POST /api/v1/custom-domains/{domain}/connect start OAuth → { url }
297
- POST /api/v1/custom-domains/{domain}/sync-dns re-sync DNS
298
- POST /api/v1/custom-domains/{domain}/check live status
299
- ```
300
-
301
- All require `Authorization: Bearer shp_…`.
302
-
303
- ## Power features (Bearer key)
304
- - **Custom domains**: see the **Custom domains** section above.
305
- REST: `POST /api/v1/custom-domains {"domain"}` → add domain + detect provider.
306
- Then `POST /api/v1/custom-domains/{domain}/connect` → OAuth one-click (show URL to user)
307
- or add the CNAME manually. Map subdomains: `POST /api/v1/custom-domains/{domain}/subdomains {"subdomain","slug"}`.
308
- Poll `POST /api/v1/custom-domains/{domain}/check` until `ready:true`.
309
- - **SSL/readiness checker**: `shiply status <slug-or-domain> --wait` or the
310
- check endpoint — confirms certificate + serving before telling the user
311
- it's done.
312
- - **Variables**: encrypted per-user KV for API keys the user's sites need
313
- (GET/PUT /api/v1/variables, DELETE /api/v1/variables/{NAME}); Supabase can
314
- be connected from the dashboard and lands here as SUPABASE_URL +
315
- SUPABASE_ANON_KEY.
316
- - **Rollback**: POST /api/v1/publish/{slug}/rollback {"versionId"} flips any
317
- finalized version live instantly.
318
- - **SPA**: pass `"spaMode":true` so deep links serve index.html.
319
-
320
- ## Proxy routes — AI/API calls WITHOUT exposing keys
321
- NEVER embed API keys in published HTML/JS. Instead: store the key as a
322
- Variable (PUT /api/v1/variables), then include `.shiply/proxy.json` in the
323
- published files declaring routes, e.g. {"proxies":{"/api/chat":{"upstream":
324
- "https://openrouter.ai/api/v1/chat/completions","method":"POST","headers":
325
- {"Authorization":"Bearer ${OPENROUTER_API_KEY}"}}}}. The page then calls the
326
- relative path (fetch('/api/chat')) and shiply injects the secret server-side.
327
- Prefix routes "/api/db/*" forward subpaths (great for Supabase REST).
328
- Owned sites only; https public upstreams only. Docs: /docs/proxy-routes
329
-
330
- ## Site Data — forms/waitlists/guestbooks with ZERO backend
331
- When the user wants to collect anything from visitors (waitlist, contact
332
- form, RSVP, guestbook): include `.shiply/data.json` in the published files,
333
- e.g. {"collections":{"signups":{"fields":{"email":{"type":"email",
334
- "required":true}},"access":{"read":"owner","insert":"public"}}}}, then have
335
- the page POST JSON to the relative path /.shiply/data/signups. Records are
336
- validated server-side; the owner reads them in the dashboard (Data section,
337
- CSV export) or GET /api/v1/publishes/<slug>/data/<collection> with a Bearer
338
- key. Owned sites only. Docs: /docs/site-data
339
-
340
- ## Agent Email — inbox, send, capture, broadcast (built into every owned site)
341
-
342
- Every owned site has a real email address and can send, receive, capture signups,
343
- and broadcast — in one line. Like AgentMail, built into the site.
344
-
345
- ### Capture (public, zero-config)
346
- ```
347
- POST /.shiply/email { "email": "user@example.com", ...anyExtraFields }
348
- ```
349
- No manifest needed. Works on the **relative path** from the published page.
350
- On success: record lands in the site inbox + owner gets a notification ping +
351
- a double-opt-in confirmation email goes to the visitor (all on by default).
352
- **Owned sites only** — anonymous sites get `403 email_requires_account` (claim
353
- the site first). A non-empty `company` field is treated as a honeypot (bot
354
- submissions are silently dropped). Caps: 16 KB body, 30 fields max.
355
-
356
- ### Send (authenticated — Bearer key only)
357
- The public page can NEVER send; only authenticated calls can.
358
- ```
359
- POST https://shiply.now/api/v1/email/send
360
- Authorization: Bearer shp_…
361
- { "slug": "my-site", "to": "user@example.com", "subject": "Hi", "html": "<p>Hi</p>", "text": "Hi" }
362
- → { "messageId": "..." }
363
- ```
364
-
365
- ### Read inbox
366
- ```
367
- GET https://shiply.now/api/v1/email/inbox # all sites
368
- GET https://shiply.now/api/v1/email/inbox?slug=my-site
369
- Authorization: Bearer shp_…
370
- ```
371
-
372
- ### Typed upgrade (optional)
373
- Declare an `email` block on a `.shiply/data.json` collection for typed fields +
374
- fine-grained control:
375
- ```json
376
- {
377
- "collections": {
378
- "signups": {
379
- "fields": { "email": { "type": "email", "required": true } },
380
- "email": {
381
- "emailField": "email",
382
- "confirm": true,
383
- "notify": true,
384
- "audience": true
385
- }
386
- }
387
- }
388
- }
389
- ```
390
- All three flags default to `true`. With this manifest, POST to
391
- `/.shiply/data/signups` as usual — the `email` block activates the email
392
- layer on that collection.
393
-
394
- ### Confirm / unsubscribe (public — in the confirmation email)
395
- ```
396
- GET /api/email/<slug>/<collection>/confirm?token=<token>
397
- GET /api/email/<slug>/<collection>/unsubscribe?email=<address>
398
- ```
399
-
400
- ### Audience + broadcast
401
- Confirmed (double-opt-in) signups form a list. Broadcast to them:
402
- ```
403
- POST /api/v1/publishes/<slug>/mailboxes/<collection>/broadcast
404
- Authorization: Bearer shp_…
405
- { "subject": "Launch day", "html": "<p>We're live!</p>" }
406
- ```
407
- Spam-checked before send; unsubscribe footer is auto-added; requires ≥1
408
- confirmed subscriber.
409
-
410
- Configure a mailbox: `GET/PATCH /api/v1/publishes/<slug>/mailboxes/<collection>`
411
- List contacts: `GET /api/v1/publishes/<slug>/mailboxes/<collection>/contacts?status=confirmed`
412
-
413
- ### MCP tools
414
- | Tool | Purpose |
415
- |---|---|
416
- | `send_email` | Send transactional email from a site (Bearer) |
417
- | `list_site_inbox` | Read the inbox (all threads or filter by slug) |
418
- | `set_mailbox` | Configure a collection's mailbox settings |
419
- | `list_mailbox_contacts` | List audience contacts (filter by status) |
420
- | `send_mailbox_broadcast` | Broadcast to confirmed audience (spam-checked) |
421
-
422
- ### CLI
423
- ```bash
424
- shiply email send --site <slug> --to <address> --subject <subject> --html <html>
425
- shiply email inbox [--site <slug>]
426
- shiply mailbox set <slug> <collection> [--confirm] [--notify] [--from <domain>]
427
- shiply mailbox broadcast <slug> <collection> --subject <subject> --html <html>
428
- ```
429
-
430
- Docs: https://shiply.now/docs/agent-email
431
-
432
- ## SQL databases (Cloudflare D1 + Neon Postgres)
433
- Two engines, same CLI/REST/MCP surface. **D1 (SQLite at the edge)** is
434
- free on every plan and queryable from the browser via a built-in fetch
435
- shim — no API key in the page. **Neon Postgres** is a developer-plan
436
- add-on with copy-on-write branching; the connection URI is encrypted at
437
- rest and surfaced to the site's Worker as `env.DATABASE_URL`.
438
-
439
- ### D1 (default)
440
- ```bash
441
- shiply db create app # provision a D1 (binding SITE_DB)
442
- shiply db ls # list (name, provider, size, attached site)
443
- shiply db sql app "SELECT 1" # one-shot query against your account
444
- shiply db sql app "SELECT * FROM t WHERE id=?1" --params '[42]'
445
- shiply db migrate app ./migrations # apply every *.sql in dir, sorted
446
- shiply db attach app --site my-site # bind an existing DB to another site
447
- shiply db delete app --yes # drop the DB and every row
448
- ```
449
-
450
- Run `shiply db create` inside a publish directory: it records the new
451
- `databaseId` into `.shiply.json` so the next `shiply publish` auto-attaches
452
- it. Site code then calls the shim:
453
-
454
- ```js
455
- fetch('/_shiply/db/SITE_DB/query', {
456
- method: 'POST',
457
- headers: { 'content-type': 'application/json' },
458
- body: JSON.stringify({ sql: 'SELECT * FROM posts', params: [] }),
459
- }).then(r => r.json()) // → { results: [...], meta: {rows_read, rows_written, duration} }
460
- ```
461
-
462
- **Public by default**: the shim has no visitor auth, so anyone who can fetch
463
- the site can run any SQL. Until a private-mode gate ships, recommend it
464
- strictly as a SELECT-only public data layer. Reads always work; writes hit
465
- 402 once the DB exceeds the plan storage cap (Founder Special 100 MB,
466
- Hobby 1 GB, Developer 10 GB).
467
-
468
- ### Neon Postgres (developer plan)
469
- Pass `--postgres` on create. One isolated Neon project per database;
470
- binding defaults to `DATABASE_URL`. Free / Hobby get `402
471
- payment_required` — point the user at https://shiply.now/dashboard/plan.
472
-
473
- ```bash
474
- shiply db create app --postgres # provision Neon (~5–10s)
475
- shiply db branch app dev # copy-on-write branch off main
476
- shiply db branches app # list branches
477
- shiply db delete-branch dev --yes # drop branch + endpoint
478
- shiply db merge dev # NO-OP — prints pg_dump tip (see below)
479
- ```
480
-
481
- After the next `shiply publish` the serving Worker receives
482
- `env.DATABASE_URL`. Minimal example:
483
-
484
- ```js
485
- import { neon } from '@neondatabase/serverless'
486
- const sql = neon(process.env.DATABASE_URL)
487
- const rows = await sql`SELECT now()`
488
- ```
489
-
490
- Anything that speaks Postgres works: `pg`, `postgres`, drizzle, kysely,
491
- prisma (with the Neon adapter).
492
-
493
- **Branching is the headline feature.** Branches are cheap, instant,
494
- copy-on-write forks of `main`. Bind a publish to a specific branch with
495
- `shiply publish --preview-branch=<branchDbId>` (use the `site_databases`
496
- row id printed by `shiply db branch`, NOT the raw Neon branch id) —
497
- great for preview deploys and safe schema rollouts.
498
-
499
- **`shiply db merge` is intentionally a no-op.** Neon has no server-side
500
- merge. To promote branch data into the parent:
501
- `pg_dump "$BRANCH_DATABASE_URL" | psql "$PARENT_DATABASE_URL"`.
502
- Run schema migrations against both branches to keep them aligned.
503
-
504
- **Not yet supported on Neon**: branch-of-branch (branches off `main`
505
- only), auto preview-branch on every publish, cross-region projects.
506
-
507
- ### REST + MCP (provider-agnostic)
508
- REST (Bearer `shp_…`): `POST /api/v1/databases` (pass
509
- `"provider":"neon"` for Postgres) to create, `POST
510
- /api/v1/databases/{id}/query` to query (works for D1 and Neon), `POST
511
- .../attach` to bind, `DELETE` to drop. Neon-only:
512
- `POST/GET/DELETE /api/v1/databases/{id}/branches[/{branchId}]` (400 on
513
- D1 rows). Per-DB MCP server (so an agent can be scoped to one DB):
514
- `https://shiply.now/api/mcp/db/<id>/sse` — tools: `db_query`,
515
- `db_list_tables`, `db_schema`. The same three tools work against both
516
- providers; `db_schema` introspects `sqlite_master` (D1) or
517
- `information_schema` (Neon). Full docs:
518
- https://shiply.now/docs/databases
519
-
520
- ## Functions (Workers Lite) — webhooks, cron, secrets, full backend (Developer plan)
521
-
522
- shiply Workers Lite lets a published site include server-side code that runs
523
- on every request. Use when the user needs:
524
- - A webhook receiver (Stripe, GitHub, etc.) with raw body + signature verification
525
- - A cron job (daily reminders, periodic sync, retention emails)
526
- - A privileged API call using secrets (Stripe key, OpenAI key) without exposing them to the browser
527
- - An authenticated mutation on D1 / Neon (do the auth check in the function before writing)
528
-
529
- ### How it works
530
-
531
- Author `worker.js` (or `worker.ts`) at the publish root. On `shiply publish`,
532
- shiply compiles (TS) + deploys it as a per-site Cloudflare Worker bound to
533
- the site's hostname. The worker handles every request; pass static asset
534
- requests through `env.ASSETS.fetch(request)`.
535
-
536
- ```ts
537
- import { verifyStripeSig, json } from 'shiply-runtime'
538
-
539
- export default {
540
- async fetch(req: Request, env: Env): Promise<Response> {
541
- const url = new URL(req.url)
542
- if (url.pathname === '/api/webhooks/stripe' && req.method === 'POST') {
543
- const body = await req.text()
544
- if (!await verifyStripeSig(body, req.headers.get('stripe-signature'), env.STRIPE_WEBHOOK_SECRET)) {
545
- return new Response('bad sig', { status: 400 })
546
- }
547
- // ... handle event, write to env.SITE_DB
548
- return new Response('ok')
549
- }
550
- return env.ASSETS.fetch(req)
551
- },
552
- async scheduled(event: ScheduledEvent, env: Env) {
553
- if (event.cron === '0 9 * * *') { /* daily job */ }
554
- }
555
- }
556
- ```
557
-
558
- `.shiply/crons.json` declares cron schedules. `.shiply/secrets.json` declares
559
- secret names (values set via CLI/MCP, never reach the publish payload).
560
-
561
- ### Bindings available in `env`
562
-
563
- - `env.ASSETS` — fall through to static (`env.ASSETS.fetch(request)`)
564
- - `env.SITE_DB` — attached D1 (if `shiply db attach` was run)
565
- - `env.<NAME>` — each shiply Variable becomes a plain-text env var
566
- - `env.<NAME>` — each secret set via `set_secret` is encrypted, accessible as env var
567
-
568
- ### MCP tools
569
-
570
- | Tool | Purpose |
571
- |---|---|
572
- | `deploy_function` | Deploy a Worker function (alternative to publish auto-detection) |
573
- | `get_function` | Read the deployed source + metadata |
574
- | `remove_function` | Strip function + secrets + crons; fall back to static |
575
- | `set_secret` / `list_secrets` / `remove_secret` | Manage CF Worker secrets |
576
- | `set_cron` / `list_crons` / `remove_cron` | Manage cron triggers |
577
- | `get_function_logs` | Read recent runtime logs (CF Observability, 7-day, newest-first) + a dashboard deep-link |
578
-
579
- ### CLI
580
-
581
- ```bash
582
- shiply publish . # auto-detects worker.js + crons.json + secrets.json
583
- shiply function deploy <slug> # alternative: upload worker.js without re-publishing
584
- shiply function deploy <slug> --ts # uploads worker.ts (server-side compile)
585
- shiply secret set <slug> STRIPE_KEY sk_xxx # set secret value
586
- shiply cron set <slug> /api/daily "0 9 * * *"
587
- shiply logs <slug> # recent worker logs (newest-first); --limit N --since MIN --json
588
- ```
589
-
590
- ### REST
591
-
592
- `POST/GET/DELETE /api/v1/sites/{slug}/function` · `POST/GET /api/v1/sites/{slug}/secrets` · `DELETE /api/v1/sites/{slug}/secrets/{name}` · `GET/POST/DELETE /api/v1/sites/{slug}/crons` · `GET /api/v1/sites/{slug}/logs?limit&since`
593
-
594
- ### Limits + plan
595
-
596
- - 1 MB compiled script size, 30s CPU per request, V8 isolate runtime (no Node APIs)
597
- - 50 secrets, 20 cron triggers per site
598
- - Plan-gated to Developer: Founder/Hobby see `402 payment_required`. Upgrade: shiply.now/dashboard/plan
599
-
600
- Docs: https://shiply.now/docs/functions
601
-
602
- ## Authentication — bring your own Clerk (or Auth.js / Lucia / Supabase Auth)
603
-
604
- shiply does **not** host customer auth. If the user's app needs sign-in, you
605
- (the agent) set up their auth provider yourself — Clerk is the fastest path.
606
-
607
- **One-time human step:** ask the user to create a Clerk app at
608
- `dashboard.clerk.com` (~2 min). They paste you the publishable key (`pk_…`) and
609
- secret key (`sk_…`).
610
-
611
- **Wire it:**
612
- \`\`\`bash
613
- shiply secret set <slug> CLERK_SECRET_KEY sk_live_xxxxxxxxxx
614
- # The publishable key can be embedded in the JS build — it's safe in the browser.
615
- \`\`\`
616
-
617
- **In `worker.ts`:** verify the session JWT against `env.CLERK_SECRET_KEY` on
618
- every API request, scope D1 rows by `payload.sub` (the Clerk user id).
619
-
620
- **In the SPA:** add `@clerk/clerk-react` to the build, wrap with `<ClerkProvider>`,
621
- attach `Authorization: Bearer <token>` to every authenticated fetch.
622
-
623
- Why not auto-integrate? shiply's own Clerk instance signs in **agents** to the
624
- shiply dashboard — it's not multi-tenant for someone else's app's users.
625
- Customers owning their own Clerk is the only correct shape.
626
-
627
- Same pattern works for Auth.js, Lucia, Supabase Auth, Firebase Auth — store the
628
- provider secret via `shiply secret set`, verify the JWT in the worker.
629
-
630
- Docs: https://shiply.now/docs/auth
631
-
632
- ## Projects — customer intake + AI brief
633
-
634
- When a dev needs to capture a real client brief, they spin up a **project**:
635
- the dashboard (or `shiply project create`) mints a one-URL intake form to
636
- share with the customer. The customer fills a 10-step wizard, attaches
637
- files, hits submit. An AI brief generator (MiniMax-M2 with Anthropic
638
- fallback) turns the answers into a structured brief the dev reviews and
639
- edits inline. All files flow into a per-project drive folder.
640
-
641
- MCP tools:
642
- ```
643
- list_projects — your projects (filter status/q)
644
- create_project — start a new intake; pass customerName/customerEmail to email them the link
645
- get_project — full project + brief
646
- update_brief — patch brief jsonb (after AI generation)
647
- regenerate_brief — re-run AI from current intake responses
648
- archive_project / restore_project — lifecycle
649
- list_project_files — files uploaded by the customer
650
- resend_intake_invite — re-email the customer their intake link (requires customerEmail on project)
651
- ```
652
-
653
- REST: `POST/GET /api/v1/projects · GET/PATCH /api/v1/projects/{id} · POST /api/v1/projects/{id}/regenerate-brief`
654
- (archive: `PATCH /api/v1/projects/{id}` with `{"status":"archived"}` — no
655
- dedicated `/archive` route).
656
-
657
- CLI: `shiply project ls · shiply project create <label> [--customer-email <e>] [--customer-name <n>] · shiply project get <id> · shiply project archive <id>`
658
-
659
- Docs: https://shiply.now/docs/projects
660
-
661
- **Group work by client (optional, for freelancers).** Building sites for
662
- clients? Pass an optional `client` (a name or email) on any `publish_site`,
663
- `create_drive`, or `create_project` call — shiply create-or-finds the client
664
- by email and files everything you ship for them (sites, drop-box, intake,
665
- contracts) under one customer view in the dashboard. A project's customer **is**
666
- the site's client (same email-keyed identity), so `create_project` already
667
- links automatically whenever you pass `customerEmail` — no extra step. There is
668
- no `create_client` tool and never a CRM step: the client record self-assembles
669
- from the calls you already make. `publish_site` echoes the `resolvedClientId` —
670
- reuse it across the session so drive/intake/contract all land under the same
671
- client. List calls take an optional `clientId` filter
672
- (`list_sites`/`list_drives`/`list_projects`). Omit `client` for personal
673
- projects; shiply works identically. CLI: `--client "<name-or-email>"` on
674
- publish/drive/project, plus `shiply client ls|show`.
675
-
676
- ## Contracts — draft, sign, amend (extends Projects)
677
-
678
- Once a project reaches `brief_ready`, the dev drafts a signed contract from
679
- the brief, the customer e-signs in the portal (typed-name + checkbox), and
680
- the dev amends it later as scope evolves. A signed contract is immutable
681
- — any change is a new amendment row chained off the parent. The portal
682
- sends the customer; the dev can resend / remind / retract. A 7-day
683
- reminder cron nudges unsigned contracts automatically.
684
-
685
- MCP tools:
686
- ```
687
- contract_draft — draft a contract from a brief_ready project (returns the new contract id)
688
- contract_send — flip status='sent', fire customer email (parent OR amendment)
689
- contract_amend — create an amendment draft on a SIGNED parent (scopeDelta required, fee + date optional)
690
- contract_status — read state + amendments (poll this to see status='signed')
691
- contract_pdf — base64-encoded signed PDF (parent + cert + signed amendments)
692
- ```
693
-
694
- Edit-before-send today is REST-only: `PATCH /api/v1/contracts/{id}` lets
695
- the dev tweak the 8 fields (scopeSummary, feeCents, currency, dates,
696
- revisionCount, revisionOverageCents, jurisdiction). After PATCH, call
697
- `contract_send` to fire it. Amendments are sent the same way (call
698
- `contract_send` with the amendment id returned by `contract_amend`).
699
-
700
- Example agent flow (draft → tweak fee → send → poll → amend):
701
- 1. `contract_draft({projectId})` → returns `{id, scopeSummary, feeCents:null, ...}`
702
- 2. `PATCH /api/v1/contracts/{id}` `{feeCents: 450000}` (Bearer key)
703
- 3. `contract_send({contractId: id})` → project flips to `contract_sent`
704
- 4. Poll `contract_status({contractId: id})` until `contract.status === 'signed'`
705
- 5. `contract_amend({parentContractId: id, scopeDelta: "Add login flow", feeDeltaCents: 50000})` → returns the amendment row
706
- 6. `contract_send({contractId: amendment.id})` (no PATCH needed if the amend fields are right)
707
-
708
- REST (Bearer for dev, portal cookie for customer):
709
- `POST /api/v1/projects/{id}/contracts` (draft) ·
710
- `GET/PATCH /api/v1/contracts/{id}` (read either-party / dev edit) ·
711
- `POST /api/v1/contracts/{id}/send` (dev) ·
712
- `POST /api/v1/contracts/{id}/view` (customer ping) ·
713
- `POST /api/v1/contracts/{id}/sign` (customer e-sign) ·
714
- `POST /api/v1/contracts/{id}/retract` (dev; `?recoverAsDraft=true` to
715
- recover the draft instead of voiding) ·
716
- `POST /api/v1/contracts/{id}/amend` (dev; signed parents only) ·
717
- `POST /api/v1/contracts/{id}/remind` ·
718
- `POST /api/v1/contracts/{id}/resend` (manual nudges, rate-limited 3/24h) ·
719
- `GET /api/v1/contracts/{id}/pdf` (binary, signed only).
720
-
721
- `contract_pdf` returns `{filename, contentType:"application/pdf", base64,
722
- byteLength}` so the MCP client can decode and save the bytes.
723
-
724
- Docs: https://shiply.now/docs/contracts
725
-
726
- ## Marketplace — sell built sites
727
-
728
- Devs can list any owned site for sale: set a price + short pitch, choose
729
- standard or custom terms, pick a jurisdiction. Stripe Connect Express
730
- handles seller payouts — sellers complete a one-time onboarding before
731
- listing (`get_connect_status` returns the onboarding URL when needed).
732
- Buyers pay via Stripe Checkout; on `checkout.session.completed` the
733
- webhook flips the order to `paid` and transfers site ownership
734
- atomically. Refunds are available within the order's refund window
735
- (`refundExpiresAt`); a refund reverts ownership to the seller.
736
-
737
- Prices are passed as `priceCents` (whole-dollar cents between 100 and
738
- 999900). `termsMode='standard'` uses shiply's template; `'custom'`
739
- requires `termsCustom` ≥50 chars.
740
-
741
- MCP tools:
742
- ```
743
- list_listings — my listings
744
- create_listing — list a site (siteSlug, priceCents, pitch?, termsMode, jurisdiction, ...)
745
- update_listing — change price/pitch/status (draft|live|paused)
746
- delete_listing — pulls off marketplace (sets status=draft)
747
- list_my_sales — orders where I'm the seller
748
- list_my_orders — orders where I'm the buyer
749
- refund_order — issue Stripe refund (within refund window; goes back to buyer)
750
- get_connect_status — Stripe Connect onboarding state + actionable URL
751
- ```
752
-
753
- REST: `POST /api/v1/listings · PATCH /api/v1/listings/{id} · POST /api/v1/listings/{id}/checkout · POST /api/v1/orders/{id}/refund · GET /api/v1/connect/status`
754
- (no GET listing index over REST yet — use the MCP `list_listings` /
755
- `list_my_sales` / `list_my_orders` tools for machine output, or the
756
- dashboard `/dashboard/sales` page for humans.)
757
-
758
- CLI: `shiply listing ls · shiply listing create <site-slug> --price <cents> --jurisdiction "<region>" · shiply listing rm <slug> --id <listing-id>`
759
-
760
- **Stripe Connect note:** Sellers must complete one-time Stripe Connect
761
- onboarding before listing. Call `get_connect_status` first — when status
762
- isn't `'ready'` it returns an `onboardingUrl` you should hand to the
763
- user as a clickable link.
764
-
765
- Docs: https://shiply.now/docs/marketplace
766
-
767
- ## Sending domains — outbound email on your domain
768
-
769
- For agents that want shiply to send emails (demand-test broadcasts,
770
- project intake invites, transactional notifications) from a custom
771
- verified domain instead of the shared shiply pool. Backed by Resend —
772
- add the DNS records they return at your registrar, then call
773
- `verify_sending_domain` to re-check.
774
-
775
- MCP tools:
776
- ```
777
- list_sending_domains — all your sending domains
778
- add_sending_domain — start: returns DNS records to add at your registrar
779
- verify_sending_domain — re-check DNS after adding records
780
- remove_sending_domain — tear down
781
- ```
782
-
783
- REST: `GET/POST /api/v1/sending-domains · POST /api/v1/sending-domains/{id}/verify · DELETE /api/v1/sending-domains/{id}`
784
-
785
- CLI: `shiply sending-domain ls · shiply sending-domain add <domain> · shiply sending-domain verify <id> · shiply sending-domain rm <id> --yes`
786
-
787
- Cannot be a `shiply.now` subdomain. Verified status flips once SPF +
788
- DKIM (+ MX for inbound) all check out.
789
-
790
- ## Make a site private (paid plans)
791
- To password-protect or restrict a site: PATCH /api/v1/publishes/<slug>/access
792
- with {"mode":"password","password":"..."} or {"mode":"restricted",
793
- "allowedEmails":[...],"allowedDomains":[...]}, or set mode "public" to open it.
794
- MCP tool set_site_access does the same. Enforced before any content is served;
795
- changing settings signs current visitors out. Docs: /docs/access-control
796
-
797
- ## Any static site works — frameworks + SSGs auto-detected
798
- shiply hosts any static site (and full SSR apps too — see the "SSR / server
799
- frameworks" section above). For a static build, publish the BUILD OUTPUT, never
800
- the source: run the build, then publish the output dir with SPA mode for
801
- client-routed apps. The CLI auto-detects 16+ frameworks and tells you the right command.
802
- Run `shiply detect` to preview what it found without uploading.
803
- - Vite (React/Vue/Svelte/Solid/Qwik): `npm run build` then `shiply publish dist --spa`
804
- - Create React App: `npm run build` then `shiply publish build --spa`
805
- - Next.js (static): set `output: "export"` in next.config, `npm run build`, then
806
- `shiply publish out` (no --spa; export emits real HTML per route). For full
807
- Next.js SSR (via OpenNext) instead, see the "SSR / server frameworks" section
808
- above — plain SSR is live (ISR/revalidate not yet; Next 16 needs
809
- `next build --webpack`).
810
- - Astro: `npm run build` then `shiply publish dist`
811
- - SvelteKit (adapter-static): `npm run build` then `shiply publish build --spa`
812
- - Nuxt: `npx nuxt generate` then `shiply publish .output/public`
813
- - Remix (client build): `npm run build` then `shiply publish build/client --spa`
814
- - Docusaurus: `npm run build` then `shiply publish build`
815
- - Hugo: `hugo` then `shiply publish public`
816
- - Jekyll: `bundle exec jekyll build` then `shiply publish _site`
817
- - Eleventy: `npx @11ty/eleventy` then `shiply publish _site`
818
- - MkDocs: `mkdocs build` then `shiply publish site`
819
- - Hexo: `npx hexo generate` then `shiply publish public`
820
- - SolidStart (static): `npm run build` then `shiply publish dist/public`
821
- - Qwik City (static adapter): `npm run build` then `shiply publish dist`
822
- - Anything else with a built `dist/`/`build/`/`out/`/`_site/`/`public/`/
823
- `.output/public/` folder: generic fallback picks it up.
824
- - Plain HTML: `shiply publish .` — no build step.
825
- Override auto-detection with `--framework=<name>`. Use the CLI (not inline
826
- MCP files) for builds — hashed bundles often exceed the 50-file inline cap,
827
- and the CLI hash-skips unchanged chunks on updates.
206
+ ## Everything else — read the topic file first
207
+
208
+ The files below sit next to this one in `references/` (installed with the
209
+ skill). Hosted copies: `https://shiply.now/skill/references/<file>`. Each is
210
+ self-contained read the one matching the task before improvising; each
211
+ covers its CLI commands, MCP tools, AND REST endpoints.
212
+
213
+ * [Publishing & site management](references/publishing.md) static framework
214
+ build matrix (Vite/Next/Hugo/…), `detect`, SPA mode, `.shiplyignore`,
215
+ list/delete/rollback/versions, `verify`, stable previews (`--as`),
216
+ `promote` preview→prod, `--json` output, form-data subcommands.
217
+ * [SSR frameworks](references/ssr-frameworks.md) deploy SvelteKit, Astro,
218
+ Qwik, Nuxt/Nitro, React Router v7, Next.js (OpenNext), Hono/raw Workers
219
+ with server-side rendering.
220
+ * [Custom domains](references/custom-domains.md) put the user's own domain
221
+ on a site: one-click OAuth DNS, manual CNAME, primary-subdomain SEO,
222
+ readiness polling.
223
+ * [Databases](references/databases.md) — per-site SQL: free D1 (SQLite at the
224
+ edge, browser shim) + Neon Postgres (branching), migrations, per-DB MCP
225
+ server.
226
+ * [Functions](references/functions.md) — `worker.js` server code on every
227
+ request: webhooks, cron triggers, secrets, runtime logs (Workers Lite,
228
+ Developer plan).
229
+ * [Email](references/email.md) — every owned site sends + receives: signup
230
+ capture, inbox, double-opt-in audiences, broadcasts, BYO sending domains.
231
+ * [Site features](references/site-features.md) — proxy routes (call AI APIs
232
+ without exposing keys), Site Data (forms/waitlists, zero backend),
233
+ password/invite-only access, Variables, Drives (private storage),
234
+ path-mounting, public profile.
235
+ * [Client work](references/client-work.md) freelancer delivery: customer
236
+ intake projects + AI briefs, group work by client, e-sign contracts,
237
+ sell sites on the marketplace.
238
+ * [Authentication for the user's app](references/site-features.md) shiply
239
+ does NOT host end-user auth; bring the user's own Clerk/Auth.js/Supabase
240
+ (pattern in Site features "Bring your own auth").
828
241
 
829
242
  ## Limits & references
830
243
  ≤1000 files/site (≤50 inline via MCP), ≤100 MiB/file, 1 GiB total.
831
- Machine guide: https://shiply.now/llms.txt · OpenAPI:
832
- https://shiply.now/openapi.json · Docs: https://shiply.now/docs
244
+ Machine index: https://shiply.now/llms.txt (full single-file version:
245
+ https://shiply.now/llms-full.txt) · What's new: https://shiply.now/changelog.md
246
+ · OpenAPI: https://shiply.now/openapi.json · Docs: https://shiply.now/docs