shiply-cli 0.27.2 → 0.29.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.
@@ -31,16 +31,49 @@ list_project_files — files uploaded by the customer
31
31
  resend_intake_invite — re-email the customer their intake link (requires customerEmail on project)
32
32
  ```
33
33
 
34
- REST: `POST/GET /api/v1/projects · GET/PATCH /api/v1/projects/{id} ·
35
- POST /api/v1/projects/{id}/regenerate-brief`
36
- (archive: `PATCH /api/v1/projects/{id}` with `{"status":"archived"}` — no
37
- dedicated `/archive` route).
34
+ REST:
35
+
36
+ | Endpoint | Action |
37
+ |---|---|
38
+ | `POST/GET /api/v1/projects` | create / list |
39
+ | `GET/PATCH /api/v1/projects/{id}` | read / update brief or metadata |
40
+ | `POST /api/v1/projects/{id}/regenerate-brief` | re-run AI from current intake responses |
41
+ | `POST /api/v1/projects/{id}/archive` `{reason?}` | dedicated archive route — mirrors `archive_project` (`PATCH .../{id}` `{"status":"archived"}` still works too, back-compat) |
42
+ | `GET /api/v1/projects/{id}/files/zip` | download every customer-uploaded file as one .zip |
43
+ | `GET /api/v1/projects/{id}/files/{fileId}/download` | 302-redirect to a short-lived signed URL for one file |
44
+ | `POST /api/v1/projects/{id}/upload` | dev sends the customer a file (multipart `file` field, ≤25 MB) |
45
+ | `PATCH /api/v1/projects/{id}/status-note` `{note}` | short customer-facing status line shown in the portal header (`note:null` clears it) |
46
+ | `POST /api/v1/projects/{id}/resend-portal-link` | dev re-sends the customer's **portal** link — different from `resend_intake_invite`/`resend-invite` below, which re-sends the original **intake** invite |
38
47
 
39
48
  CLI: `shiply project ls · shiply project create <label> [--customer-email <e>]
40
49
  [--customer-name <n>] · shiply project get <id> · shiply project archive <id>`
41
50
 
42
51
  Docs: https://shiply.now/docs/projects
43
52
 
53
+ ### Intake — the customer-facing side (token auth, not Bearer)
54
+
55
+ The intake URL a customer opens is token-scoped, not account-scoped — no
56
+ Bearer key. These routes power the wizard, portal chat, and dev-side nudges:
57
+
58
+ | Endpoint | Action |
59
+ |---|---|
60
+ | `GET /api/v1/intake/{token}/state` | current status + saved wizard responses + project label |
61
+ | `PATCH /api/v1/intake/{token}` `{stepKey, data}` | save one wizard step (auto-save as the customer types) |
62
+ | `POST /api/v1/intake/{token}/submit` | run AI brief generation from the saved responses |
63
+ | `GET /api/v1/intake/{token}/contract` | the project's contract, if one has been sent to this token |
64
+ | `GET/POST /api/v1/intake/{token}/files` | list / upload customer attachments |
65
+ | `DELETE /api/v1/intake/{token}/files/{fileId}` | remove an uploaded attachment |
66
+ | `GET/POST /api/v1/intake/{token}/messages` `{body}` (POST) | portal chat thread with the dev, oldest-first, `?before=` cursor |
67
+ | `POST /api/v1/intake/{token}/nudge` | dev-triggered reminder ping to the customer |
68
+ | `POST /api/v1/intake/{token}/email-link` | re-email the customer their intake link |
69
+ | `POST /api/v1/intake/{token}/cookie` | set the portal session cookie for this token |
70
+ | `GET /api/v1/intake/{token}/activity` | activity/audit log for this intake |
71
+ | `POST /api/v1/intake/{token}/suggest` | AI suggestion helper for a wizard field |
72
+
73
+ Dev-side equivalent for re-sending the invite email is `resend_intake_invite`
74
+ (MCP) / `POST /api/v1/projects/{id}/resend-invite` — different from the
75
+ customer-token `email-link` route above.
76
+
44
77
  ## Group work by client (optional, for freelancers)
45
78
 
46
79
  Building sites for clients? Pass an optional `client` (a name or email) on any
@@ -56,7 +89,28 @@ drive/intake/contract all land under the same client. List calls take an
56
89
  optional `clientId` filter (`list_sites`/`list_drives`/`list_projects`).
57
90
  Omit `client` for personal projects; shiply works identically.
58
91
  CLI: `--client "<name-or-email>"` on publish/drive/project, plus
59
- `shiply client ls|show`.
92
+ `shiply client ls|show`. Re-assign (or clear) a client on an already-published
93
+ site directly: `PATCH /api/v1/publish/{slug}/client` `{clientId}`
94
+ (`clientId: null` clears it) — no MCP tool for this yet, REST only.
95
+
96
+ REST (Bearer; Studio plan — `assertStudioEntitled` gates `POST`):
97
+
98
+ | Endpoint | Action |
99
+ |---|---|
100
+ | `GET /api/v1/clients` | list your clients → `{clients}` |
101
+ | `POST /api/v1/clients` `{name, email?, phone?, company?, notes?}` | create a client explicitly (usually unnecessary — clients self-assemble from `client`/`customerEmail`) |
102
+ | `GET /api/v1/clients/{id}` | rollup view: sites, drives, projects, contracts, invoices under this client |
103
+ | `GET /api/v1/clients/{id}/invoices` | list invoices for this client |
104
+ | `POST /api/v1/clients/{id}/invoices` `{description, amountCents, currency?, dueDate?, projectId?}` | create an invoice |
105
+ | `POST /api/v1/invoices/{id}/send` | email the invoice to the client |
106
+ | `POST /api/v1/invoices/{id}/void` | void an unpaid invoice |
107
+ | `POST /api/v1/clients/{id}/portal` | enable the client's public portal (idempotent) → `{portalUrl}` |
108
+ | `DELETE /api/v1/clients/{id}/portal` | disable the portal — the link dies immediately |
109
+ | `POST /api/v1/clients/{id}/portal/rotate` | mint a fresh portal token (old link dies instantly) |
110
+
111
+ No `list_clients`/`get_client`/portal-management MCP tools exist yet — this
112
+ is REST + CLI (`shiply client ls|show`) only; an MCP agent reads client data
113
+ indirectly via `list_sites`/`list_drives`/`list_projects` with `clientId`.
60
114
 
61
115
  ## Contracts — draft, sign, amend (extends Projects)
62
116
 
@@ -91,7 +145,7 @@ Example agent flow (draft → tweak fee → send → poll → amend):
91
145
  6. `contract_send({contractId: amendment.id})` (no PATCH needed if the amend fields are right)
92
146
 
93
147
  REST (Bearer for dev, portal cookie for customer):
94
- `POST /api/v1/projects/{id}/contracts` (draft) ·
148
+ `POST /api/v1/projects/{id}/contract` (draft — singular; the plural `/contracts` is a GET-only read bundle) ·
95
149
  `GET/PATCH /api/v1/contracts/{id}` (read either-party / dev edit) ·
96
150
  `POST /api/v1/contracts/{id}/send` (dev) ·
97
151
  `POST /api/v1/contracts/{id}/view` (customer ping) ·
@@ -141,12 +195,13 @@ refund_order — issue Stripe refund (within refund window; goes back to buy
141
195
  get_connect_status — Stripe Connect onboarding state + actionable URL
142
196
  ```
143
197
 
144
- REST: `POST /api/v1/listings · PATCH /api/v1/listings/{id} ·
145
- POST /api/v1/listings/{id}/checkout · POST /api/v1/orders/{id}/refund ·
146
- GET /api/v1/connect/status`
147
- (no GET listing index over REST yet use the MCP `list_listings` /
148
- `list_my_sales` / `list_my_orders` tools for machine output, or the
149
- dashboard `/dashboard/sales` page for humans.)
198
+ REST: `GET/POST /api/v1/listings · PATCH /api/v1/listings/{id} ·
199
+ POST /api/v1/listings/{id}/checkout · GET /api/v1/orders ·
200
+ POST /api/v1/orders/{id}/refund · GET /api/v1/connect/status`
201
+ (`GET /api/v1/listings` and `GET /api/v1/orders` list your own listings/
202
+ orders `?limit=` supported; MCP mirrors are `list_listings` /
203
+ `list_my_orders`. Sales specifically: `GET /api/v1/listings/sales` /
204
+ `list_my_sales`.)
150
205
 
151
206
  CLI: `shiply listing ls · shiply listing create <site-slug> --price <cents>
152
207
  --jurisdiction "<region>" · shiply listing rm <slug> --id <listing-id>`
@@ -157,3 +212,29 @@ isn't `'ready'` it returns an `onboardingUrl` you should hand to the
157
212
  user as a clickable link.
158
213
 
159
214
  Docs: https://shiply.now/docs/marketplace
215
+
216
+ ## Inbox threads — reply, forward, archive, summarize, AI draft
217
+
218
+ The account-wide inbox (replies, unsubscribes, complaints, bounces across
219
+ every site) beyond the read-only MCP tools in [email.md](email.md):
220
+
221
+ MCP tools: `list_inbox` (filtered list + counts in one call — filters
222
+ `all|unread|replies|unsubscribes|complaints|bounces|archived`), `read_thread`
223
+ (marks read implicitly), `mark_thread_read`, `summarize_thread`,
224
+ `reply_to_thread`, `forward_thread`, `archive_thread`, `unarchive_thread`.
225
+
226
+ REST (Bearer):
227
+
228
+ | Endpoint | Action |
229
+ |---|---|
230
+ | `GET /api/v1/inbox/threads?filter=&limit=&offset=` | atomic snapshot: `{threads, counts}` |
231
+ | `GET /api/v1/inbox/threads/{id}` | one thread (also marks read) |
232
+ | `PATCH /api/v1/inbox/threads/{id}` `{action:"mark_read"\|"archive"\|"unarchive"}` | thread lifecycle |
233
+ | `POST /api/v1/inbox/threads/{id}/reply` `{body, subject?}` | reply from the original recipient alias, threaded via In-Reply-To |
234
+ | `POST /api/v1/inbox/threads/{id}/forward` `{to, messageId?, subject?, note?}` | forward one message to a new address |
235
+ | `POST /api/v1/inbox/threads/{id}/summarize` | AI summary of the thread |
236
+ | `POST /api/v1/inbox/threads/{id}/ai-reply` | AI-drafted reply body (review before sending — no matching MCP tool yet) |
237
+
238
+ **Gap (intentional, tracked):** the CLI has no `shiply inbox` thread-action
239
+ subcommands (reply/forward/archive/summarize) — use MCP or REST for those;
240
+ `shiply email inbox` only reads.
@@ -40,6 +40,8 @@ shiply domain sub add www.example.com --site my-site # map a subdomain → sit
40
40
  shiply domain ls # list all domains + subdomains + status
41
41
  shiply domain sync example.com # re-sync DNS records with a connected provider
42
42
  shiply domain primary <hostname> # mark hostname canonical (siblings 301)
43
+ shiply domain check example.com # re-poll cert/DNS/HTTPS readiness per subdomain
44
+ shiply domain rm example.com --yes # remove a domain (its subdomains stop resolving)
43
45
  ```
44
46
 
45
47
  For one-click connect (`shiply domain connect`), the CLI prints an
@@ -95,6 +95,11 @@ for D1 and Neon), `POST .../attach` to bind, `DELETE` to drop. Neon-only:
95
95
  `POST/GET/DELETE /api/v1/databases/{id}/branches[/{branchId}]` (400 on
96
96
  D1 rows).
97
97
 
98
+ Main MCP server (account-wide lifecycle — start here to discover a
99
+ databaseId): `list_databases`, `create_database`, `delete_database`,
100
+ `attach_database`. Then, to query/introspect one database, connect the
101
+ per-DB MCP server below with that id.
102
+
98
103
  Per-DB MCP server (so an agent can be scoped to one DB):
99
104
  `https://shiply.now/api/mcp/db/<id>/sse` (or `/mcp` for streamable-http),
100
105
  Bearer `shp_…` — tools: `db_query`, `db_list_tables`, `db_schema`. The same
@@ -0,0 +1,70 @@
1
+ ---
2
+ type: reference
3
+ title: Drives (private storage — REST, CLI, MCP)
4
+ description: Full reference for Drives — private cloud storage for agent memory, notes, and assets. Staged uploads, sharing links, client assignment, and publish-from-drive.
5
+ timestamp: 2026-07-14
6
+ ---
7
+
8
+ # Drives — private cloud storage
9
+
10
+ Part of the shiply skill (see SKILL.md for publish basics). A Drive is
11
+ private storage — files, notes, context — that is **not** served publicly
12
+ like a published site. Every account gets a `default` drive on first use;
13
+ you can also create named drives (e.g. one per client).
14
+
15
+ ## REST (Bearer `shp_...`)
16
+
17
+ | Endpoint | Action |
18
+ |---|---|
19
+ | `GET /api/v1/drives?clientId=` | list your drives (optional client filter) → `{drives}` |
20
+ | `POST /api/v1/drives` `{name, client?}` | create a named drive. `client` is the same optional `{clientId?, clientEmail?, clientName?, clientCompany?}` shape used on publish/create_project |
21
+ | `GET /api/v1/drives/default` | the account's auto-provisioned default drive (creates it on first call) |
22
+ | `GET /api/v1/drives/{id}` | drive detail |
23
+ | `DELETE /api/v1/drives/{id}` | delete a drive and its files |
24
+ | `GET /api/v1/drives/{id}/files?prefix=&cursor=` | list files (optional path-prefix filter, pagination cursor) |
25
+ | `POST /api/v1/drives/{id}/files/uploads` `{files:[{path,size,contentType?,hash}]}` | stage up to 1000 files → presigned PUT URLs (same 3-step flow as site publish) |
26
+ | `POST /api/v1/drives/{id}/files/finalize` `{files:[{path,size,contentType?,hash}]}` | confirm uploads landed — same `files` array as the uploads call |
27
+ | `GET /api/v1/drives/{id}/files/{...path}` | `{url}` — a presigned, time-limited download URL for one file |
28
+ | `DELETE /api/v1/drives/{id}/files/{...path}` | delete one file (optional `If-Match` header for optimistic concurrency) |
29
+ | `POST /api/v1/drives/{id}/assign-client` `{clientId}` | file this drive under a client (`clientId: null` clears it) |
30
+ | `POST /api/v1/drives/{id}/share` `{instructions?}` | enable a public share link for the drive |
31
+ | `DELETE /api/v1/drives/{id}/share` | disable the share link |
32
+ | `POST /api/v1/drives/{id}/share/rotate` | mint a fresh share token — the old link dies instantly |
33
+ | `POST /api/v1/drives/{id}/share/invite` `{emails:[...max 50], message?}` | email the share link to a list of addresses |
34
+
35
+ `{id}` is the drive's public id (`drv_...`), or the literal string
36
+ `default` where the endpoint supports it (only `GET /api/v1/drives/default`
37
+ resolves the alias itself — everywhere else, resolve `default` to a real
38
+ `drv_...` id first via that call).
39
+
40
+ ## CLI
41
+
42
+ The CLI only operates on the account's **default** drive — for named/
43
+ per-client drives, use REST or MCP.
44
+
45
+ ```bash
46
+ shiply drive ls [prefix] # list files (optionally under a prefix)
47
+ shiply drive put <file> [as-path] # stage + finalize a file into the default drive
48
+ shiply drive get <path> [out-file] # download; omit out-file to print to stdout
49
+ shiply drive rm <path> # delete a file
50
+ shiply drive publish [prefix] # snapshot the default drive (or a prefix) as a new live site
51
+ ```
52
+
53
+ ## MCP tools
54
+
55
+ | Tool | Purpose |
56
+ |---|---|
57
+ | `list_drives` | your drives (optional `clientId` filter) |
58
+ | `create_drive` | create a named drive (`name`, optional `client`) |
59
+ | `drive_list_files` | list files in a drive (`driveId` = `drv_...` or `"default"`, optional `prefix`) |
60
+ | `drive_put_file` | write a file inline (utf8 or base64, ≤2 MB — use the REST staged-upload flow above for bigger files) |
61
+ | `drive_delete_file` | delete a file from a drive |
62
+ | `publish_from_drive` | snapshot a drive (or a path prefix within it) into a new live site |
63
+
64
+ **Gap (intentional, tracked):** file download, drive delete, and the
65
+ share/assign-client family (`share`, `share/rotate`, `share/invite`,
66
+ `assign-client`) are REST-only today — there is no `drive_download`,
67
+ `delete_drive`, or share-management MCP tool yet. Use the REST endpoints
68
+ above from an MCP-only agent until parity lands.
69
+
70
+ Docs: https://shiply.now/docs/drives
@@ -127,6 +127,22 @@ the confirmed audience (unsubscribe link auto-added). `list_tests()` — all
127
127
  tests for this key. `resend_confirmation({ testId, email })` — re-send the
128
128
  opt-in to one address.
129
129
 
130
+ REST (Bearer, mirrors the MCP tools above):
131
+
132
+ | Endpoint | Action |
133
+ |---|---|
134
+ | `GET /api/v1/tests` | list your demand tests → `{tests}` |
135
+ | `POST /api/v1/tests` `{idea, copy:{headline, sub?, cta?}, price?, captureFields?, sendingDomainId?}` | create a test — deploys the capture page + provisions the confirmed-subscriber segment |
136
+ | `GET /api/v1/tests/{id}` | merged status object — same shape as `get_test_status` |
137
+ | `PATCH /api/v1/tests/{id}/branding` `{brandName?, logoUrl?, brandColor?, accentColor?, footerText?, replyTo?}` | customize the capture page's branding |
138
+ | `POST /api/v1/tests/{id}/broadcasts` `{subject, html, text?}` | email the confirmed audience — same as `send_broadcast` |
139
+ | `POST /api/v1/tests/suggest-copy` `{idea}` | AI-drafted `{headline, sub, cta}` from a one-line idea (MiniMax; 503 `service_unavailable` if no AI provider is configured) |
140
+
141
+ `list_tests` maps to `GET /api/v1/tests` above. `resend_confirmation` (re-send
142
+ the opt-in email to one address) is **MCP-only today** — no REST route exists
143
+ yet; use the MCP tool from a REST-only agent by proxying through an MCP
144
+ client, or ask for the confirmation link to be resent from the dashboard.
145
+
130
146
  ## Sending domains — outbound email on your domain
131
147
 
132
148
  For agents that want shiply to send emails (demand-test broadcasts, project
@@ -140,6 +156,14 @@ to add), `verify_sending_domain`, `remove_sending_domain`.
140
156
  REST: `GET/POST /api/v1/sending-domains ·
141
157
  POST /api/v1/sending-domains/{id}/verify · DELETE /api/v1/sending-domains/{id}`
142
158
 
159
+ Auto-DNS shortcuts (skip the "copy these records to your registrar" step —
160
+ user supplies a scoped, never-stored Cloudflare API token for the domain's
161
+ zone): `POST /api/v1/sending-domains/{id}/auto-dns` `{provider:"cloudflare",
162
+ token}` adds the SPF/DKIM records automatically; `POST
163
+ /api/v1/sending-domains/{id}/email-routing` `{provider:"cloudflare", token}`
164
+ additionally turns on inbound Email Routing and binds the catch-all to
165
+ shiply's inbound worker. No MCP tools for either yet — REST only.
166
+
143
167
  CLI: `shiply sending-domain ls · shiply sending-domain add <domain> ·
144
168
  shiply sending-domain verify <id> · shiply sending-domain rm <id> --yes`
145
169
 
@@ -70,7 +70,9 @@ shiply publish <dir> --json # one machine-readable JSON line, no
70
70
  - **`shiply verify <slug>`** prints a human report PLUS a stable machine marker
71
71
  line `VERIFY status=LIVE http=200 ssl=valid thumb=…` (or `status=PENDING`).
72
72
  Headless agents should parse that `VERIFY status=…` line — it's the
73
- contract, like `SITE_READY` in `status`.
73
+ contract, like `SITE_READY` in `status`. REST: `GET /api/v1/sites/{slug}/verify`
74
+ (public, no auth — mirrors `site_status`) → `{status, ssl, http, thumbnailUrl,
75
+ consoleErrorsDeepLink}`. MCP: `verify_site`.
74
76
  - **`shiply publish <dir> --as <name>`** gives a stable preview URL that the
75
77
  next publish with the same `--as <name>` reuses — no more changing URLs or
76
78
  orphaned scratch sites across iterations, independent of the source dir.
@@ -44,21 +44,36 @@ see [email.md](email.md).
44
44
 
45
45
  ## Access control — password or invite-only sites (paid)
46
46
 
47
- To password-protect or restrict a site: PATCH /api/v1/publishes/<slug>/access
47
+ CLI (preferred): `shiply access <slug>` prints the current policy (plus a
48
+ stable `ACCESS slug=… mode=…` marker line); set it with
49
+ `shiply access <slug> --password "..."`, `--restricted --email a@b.com
50
+ --domain example.com` (both flags repeatable — each set REPLACES the previous
51
+ lists), or `--public` to open it up.
52
+ REST: PATCH /api/v1/publishes/<slug>/access
48
53
  with {"mode":"password","password":"..."} or {"mode":"restricted",
49
54
  "allowedEmails":[...],"allowedDomains":[...]}, or set mode "public" to open it.
50
- GET returns the policy (never the hash). MCP tool: `set_site_access`. Enforced
55
+ GET returns the policy (never the hash). MCP tools: `get_site_access` (read),
56
+ `set_site_access` (write). Enforced
51
57
  at the edge before any content/proxy/data is served; visitors get a 7-day
52
58
  signed cookie; changing any setting signs current visitors out.
53
59
  Docs: /docs/access-control
54
60
 
55
61
  ## Variables — encrypted per-user KV
56
62
 
57
- For API keys the user's sites need: GET/PUT /api/v1/variables
58
- {"name","value"}, DELETE /api/v1/variables/{NAME}; GET ?reveal=1 returns
59
- plaintext values. MCP: `set_variable`. Referenced by proxy routes (`${VAR}`)
60
- and injected into Workers as `env.<NAME>`. Supabase can be connected from the
61
- dashboard and lands here as SUPABASE_URL + SUPABASE_ANON_KEY.
63
+ CLI (preferred): `shiply variable ls [--reveal]`, `shiply variable set <NAME>
64
+ <value>` (or `--stdin`), `shiply variable rm <NAME> --yes`.
65
+ REST: GET/PUT /api/v1/variables {"name","value"}, DELETE
66
+ /api/v1/variables/{NAME}; GET ?reveal=1 returns plaintext values.
67
+ MCP: `list_variables`, `set_variable`, `delete_variable`. Referenced by proxy
68
+ routes (`${VAR}`) and injected into Workers as `env.<NAME>`. Supabase can be
69
+ connected from the dashboard and lands here as SUPABASE_URL + SUPABASE_ANON_KEY.
70
+
71
+ Expose (or stop exposing) a saved account variable to one specific site's
72
+ Worker env, independent of proxy routes: CLI `shiply variable attach <NAME>
73
+ --site <slug>` / `shiply variable detach <NAME> --site <slug>` (both
74
+ idempotent). REST: `POST /api/v1/variables/{name}/attach` `{slug}` / `POST
75
+ /api/v1/variables/{name}/detach` `{slug}`. MCP: `attach_variable`,
76
+ `detach_variable`, `list_site_variables`.
62
77
 
63
78
  ## Drives — private cloud storage (agent memory + assets)
64
79
 
@@ -66,6 +81,8 @@ Private storage for files/notes/context you don't want on a public site.
66
81
  MCP tools: `list_drives`, `create_drive`, `drive_put_file` (driveId
67
82
  "default", utf8/base64, ≤2 MB), `drive_list_files`, `drive_delete_file`,
68
83
  `publish_from_drive` (snapshot a drive into a live site).
84
+ Full reference (REST staged uploads, sharing, client assignment, CLI):
85
+ [drives.md](drives.md).
69
86
 
70
87
  ## Path-mounting + public profile
71
88
 
@@ -73,7 +90,8 @@ MCP tools: `list_drives`, `create_drive`, `drive_put_file` (driveId
73
90
  (host/docs → target).
74
91
  - `set_profile` / `feature_site` (MCP) — stand up the user's public portfolio
75
92
  at <handle>.shiply.now and feature a public site on shiply.now/explore.
76
- - `export_account` (MCP) JSON bundle of the user's data (no secrets).
93
+ - `export_account` (MCP) / `GET /api/v1/account/export` (REST) JSON bundle
94
+ of the user's data (no secrets).
77
95
  - Site metadata: `PATCH /api/v1/publish/<slug>/metadata`
78
96
  {"title?","spaMode?","addedToProfile?"}.
79
97
 
@@ -1,57 +1,57 @@
1
- ---
2
- type: reference
3
- title: SSR frameworks
4
- description: Deploy full server-side-rendered apps — SvelteKit, Astro, Qwik, the Nitro family (Nuxt, SolidStart, Analog, TanStack Start), React Router v7, Next.js via OpenNext, and Hono/raw Workers.
5
- timestamp: 2026-07-04
6
- ---
7
-
8
- # SSR / server frameworks (live: every major JS framework)
9
-
10
- Part of the shiply skill (see SKILL.md for publish basics). Requires sign-in +
11
- Developer plan.
12
-
13
- `shiply publish` auto-detects an SSR build and deploys the worker bundle with
14
- `nodejs_compat`, serving static assets from the edge. Live and verified on prod:
15
- SvelteKit (`@sveltejs/adapter-cloudflare`), Astro (`@astrojs/cloudflare`), Qwik
16
- City, the Nitro family (Nuxt, SolidStart, Analog, TanStack Start), React Router
17
- v7, and Next.js (via OpenNext) — plus wrangler workers (`wrangler.toml` with
18
- `main` — Hono, itty-router, raw `fetch`). **Build first** (`npm run build`),
19
- then `shiply publish .`. Force the static path with `--framework=<name>`, or
20
- skip SSR detection entirely with `--no-ssr`.
21
-
22
- Per-framework build setup:
23
-
24
- - **SvelteKit** — `@sveltejs/adapter-cloudflare`, then `npm run build`,
25
- `shiply publish .`
26
- - **Astro** — `@astrojs/cloudflare` adapter, `npm run build`, `shiply publish .`
27
- - **Qwik City** — `@builder.io/qwik-city` cloudflare-pages adapter,
28
- `npm run build`, `shiply publish .`
29
- - **Nitro family (Nuxt, SolidStart, Analog, TanStack Start)** — build with the
30
- `cloudflare`(-module) Nitro preset, then `shiply publish .`
31
- - **React Router v7** — framework mode (`@react-router/dev`), `react-router
32
- build`, then `shiply publish .`
33
- - **Next.js** — build via `@opennextjs/cloudflare` (OpenNext), then
34
- `shiply publish .`. Plain SSR works today; **ISR / on-demand revalidate is
35
- not live yet**, and Next 16 must build with `next build --webpack` (not
36
- Turbopack). `shiply publish` runs a **PREFLIGHT** on Next source dirs: it
37
- auto-fixes Turbopack build scripts, translates `vercel.json` crons to
38
- `.shiply/crons.json`, and refuses to publish raw source with no worker
39
- build — read its `FIX`/`WARN` lines, do what they say, re-run.
40
-
41
- Publish output is self-describing — trust it over memory: finalize streams
42
- per-phase progress lines, a failed worker deploy prints a structured `fix:`
43
- line to execute verbatim, and after every worker deploy the server probes
44
- the live site — a non-zero exit with a `runtime error:` line means
45
- DEPLOYED BUT BROKEN (the actual worker exception + likely fix are printed,
46
- e.g. a missing `shiply secret set`); fix the printed cause and republish.
47
- - **Wrangler workers** — any repo with `wrangler.toml` declaring `main`
48
- (Hono, itty-router, raw `fetch` handler) deploys as-is.
49
-
50
- How serving works: the deployed worker receives every request; static assets
51
- are tried first (the worker calls `env.ASSETS`, 200 = asset served from the
52
- edge / 404 = your SSR handler runs). The worker gets the same bindings,
53
- secrets, and crons as Functions — see [functions.md](functions.md). Bundle
54
- cap 8 MB.
55
-
56
- Plan gate: Free/Hobby get `402 payment_required` — upgrade at
57
- https://shiply.now/dashboard/plan.
1
+ ---
2
+ type: reference
3
+ title: SSR frameworks
4
+ description: Deploy full server-side-rendered apps — SvelteKit, Astro, Qwik, the Nitro family (Nuxt, SolidStart, Analog, TanStack Start), React Router v7, Next.js via OpenNext, and Hono/raw Workers.
5
+ timestamp: 2026-07-04
6
+ ---
7
+
8
+ # SSR / server frameworks (live: every major JS framework)
9
+
10
+ Part of the shiply skill (see SKILL.md for publish basics). Requires sign-in +
11
+ Developer plan.
12
+
13
+ `shiply publish` auto-detects an SSR build and deploys the worker bundle with
14
+ `nodejs_compat`, serving static assets from the edge. Live and verified on prod:
15
+ SvelteKit (`@sveltejs/adapter-cloudflare`), Astro (`@astrojs/cloudflare`), Qwik
16
+ City, the Nitro family (Nuxt, SolidStart, Analog, TanStack Start), React Router
17
+ v7, and Next.js (via OpenNext) — plus wrangler workers (`wrangler.toml` with
18
+ `main` — Hono, itty-router, raw `fetch`). **Build first** (`npm run build`),
19
+ then `shiply publish .`. Force the static path with `--framework=<name>`, or
20
+ skip SSR detection entirely with `--no-ssr`.
21
+
22
+ Per-framework build setup:
23
+
24
+ - **SvelteKit** — `@sveltejs/adapter-cloudflare`, then `npm run build`,
25
+ `shiply publish .`
26
+ - **Astro** — `@astrojs/cloudflare` adapter, `npm run build`, `shiply publish .`
27
+ - **Qwik City** — `@builder.io/qwik-city` cloudflare-pages adapter,
28
+ `npm run build`, `shiply publish .`
29
+ - **Nitro family (Nuxt, SolidStart, Analog, TanStack Start)** — build with the
30
+ `cloudflare`(-module) Nitro preset, then `shiply publish .`
31
+ - **React Router v7** — framework mode (`@react-router/dev`), `react-router
32
+ build`, then `shiply publish .`
33
+ - **Next.js** — build via `@opennextjs/cloudflare` (OpenNext), then
34
+ `shiply publish .`. Plain SSR works today; **ISR / on-demand revalidate is
35
+ not live yet**, and Next 16 must build with `next build --webpack` (not
36
+ Turbopack). `shiply publish` runs a **PREFLIGHT** on Next source dirs: it
37
+ auto-fixes Turbopack build scripts, translates `vercel.json` crons to
38
+ `.shiply/crons.json`, and refuses to publish raw source with no worker
39
+ build — read its `FIX`/`WARN` lines, do what they say, re-run.
40
+
41
+ Publish output is self-describing — trust it over memory: finalize streams
42
+ per-phase progress lines, a failed worker deploy prints a structured `fix:`
43
+ line to execute verbatim, and after every worker deploy the server probes
44
+ the live site — a non-zero exit with a `runtime error:` line means
45
+ DEPLOYED BUT BROKEN (the actual worker exception + likely fix are printed,
46
+ e.g. a missing `shiply secret set`); fix the printed cause and republish.
47
+ - **Wrangler workers** — any repo with `wrangler.toml` declaring `main`
48
+ (Hono, itty-router, raw `fetch` handler) deploys as-is.
49
+
50
+ How serving works: the deployed worker receives every request; static assets
51
+ are tried first (the worker calls `env.ASSETS`, 200 = asset served from the
52
+ edge / 404 = your SSR handler runs). The worker gets the same bindings,
53
+ secrets, and crons as Functions — see [functions.md](functions.md). Bundle
54
+ cap 8 MB.
55
+
56
+ Plan gate: Free/Hobby get `402 payment_required` — upgrade at
57
+ https://shiply.now/dashboard/plan.