shiply-cli 0.7.1 → 0.12.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/skill/SKILL.md CHANGED
@@ -1,129 +1,371 @@
1
- ---
2
- name: shiply
3
- description: Publish static sites to the web instantly with shiply.now and manage them (updates, SSL checks, custom domains, variables). Use when the user asks to publish, host, deploy, share, or update a website/page/demo/report, or wants a live URL for generated files. Triggers - "publish this", "host this", "put this online", "give me a link", "update the site", shiply.
4
- ---
5
-
6
- # shiply — instant web hosting for agents
7
-
8
- shiply.now puts files on the web in seconds. No account needed to start:
9
- anonymous sites are live immediately, last 24 hours, and can be claimed into
10
- an account to keep them. The cardinal rule:
11
-
12
- **NEVER create a new site to update an existing one. Always re-publish to the
13
- same site** — otherwise you litter subdomains and lose the user's URL.
14
-
15
- ## Pick your interface (best first)
16
-
17
- ### 1. MCP (native tools)
18
- If the `shiply` MCP server is connected (https://shiply.now/mcp), use
19
- `publish_site`. Every result includes a `toUpdate` field telling you the exact
20
- call for updates — follow it, and a `shareSuggestion` you can relay to the user.
21
- Other tools: `site_status`, `list_sites`, `get_site`, `set_handle`,
22
- `duplicate_site`, `set_variable`, `add_domain`/`list_domains`/`check_domain`/
23
- `remove_domain`, `get_analytics`, `delete_site`.
24
-
25
- Newer capabilities (use when relevant):
26
- - `set_site_access` — password / invite-only protect a site (paid).
27
- - `set_link` — mount another owned **public** site at a path on a host
28
- (host/docs → target).
29
- - `set_profile` / `feature_site` — stand up the user's public portfolio at
30
- <handle>.shiply.now and feature a public site on shiply.now/explore.
31
- - **Drives** (private cloud storage for files/notes/context): `list_drives`,
32
- `create_drive`, `drive_put_file` (driveId "default", utf8/base64, ≤2 MB),
33
- `drive_list_files`, `drive_delete_file`, `publish_from_drive` (snapshot a
34
- drive into a live site). Great for agent memory and assets you don't want on
35
- a public site.
36
- - `export_account` — JSON bundle of the user's data (no secrets).
37
-
38
- ### 2. CLI
39
- ```bash
40
- npm i -g shiply-cli # or: curl -fsSL https://shiply.now/install.sh | bash
41
- shiply publish ./dir # live URL + confetti
42
- shiply publish ./dir # run AGAIN after edits → updates the SAME site
43
- shiply status <slug> --wait # SSL + readiness; prints SSL_READY / SITE_READY
44
- shiply login # email code → API key → sites become permanent
45
- ```
46
- The CLI stores each directory's site in `.shiply.json` (slug + update token),
47
- so repeat publishes reuse the URL automatically. `--new-site` opts out.
48
- Gitignore `.shiply.json` in public repos. Parse `SITE_READY` / `SSL_READY`
49
- lines for automation; exit code 0 = ready.
50
-
51
- ### 3. Raw HTTP (no installs)
52
- ```
53
- 1. POST https://shiply.now/api/v1/publish
54
- {"files":[{"path":"index.html","size":<bytes>,"contentType":"text/html","hash":"<sha256, optional>"}]}
55
- (+ "Authorization: Bearer shp_…" for permanent owned sites)
56
- 2. PUT each file's bytes to response upload.uploads[].url
57
- 3. POST upload.finalizeUrl with {"versionId":"..."}
58
- ```
59
- **Updates:** anonymousinclude `"claimToken":"..."` (returned ONCE by the
60
- first publish save it); owned include `"slug":"..."`. Hashes make updates
61
- cheap: unchanged files are skipped server-side.
62
-
63
- ## The lifecycle to explain to users
64
- - Anonymous site: live instantly, expires in 24 h. Give the user the
65
- `claimUrl` claiming keeps it forever on a free account.
66
- - API key (`shiply login` or POST /api/auth/agent/request-code →
67
- verify-code): publishes are permanent and manageable.
68
- - Paid plans add vanity handles (<name>.shiply.now), more custom domains,
69
- storage, analytics: https://shiply.now/dashboard/plan
70
-
71
- ## Power features (Bearer key)
72
- - **Custom domains**: POST /api/v1/domains {"hostname","slug"} tell the
73
- user to CNAME the hostname to `cname.shiply.now` cert auto-issues; poll
74
- GET /api/v1/domains/{id}/check until `ready:true`.
75
- - **SSL/readiness checker**: `shiply status <slug-or-domain> --wait` or the
76
- check endpoint confirms certificate + serving before telling the user
77
- it's done.
78
- - **Variables**: encrypted per-user KV for API keys the user's sites need
79
- (GET/PUT /api/v1/variables, DELETE /api/v1/variables/{NAME}); Supabase can
80
- be connected from the dashboard and lands here as SUPABASE_URL +
81
- SUPABASE_ANON_KEY.
82
- - **Rollback**: POST /api/v1/publish/{slug}/rollback {"versionId"} flips any
83
- finalized version live instantly.
84
- - **SPA**: pass `"spaMode":true` so deep links serve index.html.
85
-
86
- ## Proxy routes AI/API calls WITHOUT exposing keys
87
- NEVER embed API keys in published HTML/JS. Instead: store the key as a
88
- Variable (PUT /api/v1/variables), then include `.shiply/proxy.json` in the
89
- published files declaring routes, e.g. {"proxies":{"/api/chat":{"upstream":
90
- "https://openrouter.ai/api/v1/chat/completions","method":"POST","headers":
91
- {"Authorization":"Bearer ${OPENROUTER_API_KEY}"}}}}. The page then calls the
92
- relative path (fetch('/api/chat')) and shiply injects the secret server-side.
93
- Prefix routes "/api/db/*" forward subpaths (great for Supabase REST).
94
- Owned sites only; https public upstreams only. Docs: /docs/proxy-routes
95
-
96
- ## Site Dataforms/waitlists/guestbooks with ZERO backend
97
- When the user wants to collect anything from visitors (waitlist, contact
98
- form, RSVP, guestbook): include `.shiply/data.json` in the published files,
99
- e.g. {"collections":{"signups":{"fields":{"email":{"type":"email",
100
- "required":true}},"access":{"read":"owner","insert":"public"}}}}, then have
101
- the page POST JSON to the relative path /.shiply/data/signups. Records are
102
- validated server-side; the owner reads them in the dashboard (Data section,
103
- CSV export) or GET /api/v1/publishes/<slug>/data/<collection> with a Bearer
104
- key. Owned sites only. Docs: /docs/site-data
105
-
106
- ## Make a site private (paid plans)
107
- To password-protect or restrict a site: PATCH /api/v1/publishes/<slug>/access
108
- with {"mode":"password","password":"..."} or {"mode":"restricted",
109
- "allowedEmails":[...],"allowedDomains":[...]}, or set mode "public" to open it.
110
- MCP tool set_site_access does the same. Enforced before any content is served;
111
- changing settings signs current visitors out. Docs: /docs/access-control
112
-
113
- ## Full React / Vue / Svelte / Astro apps — YES, they work
114
- shiply hosts complete framework apps. Publish the BUILD OUTPUT, never the
115
- source: run the build, then publish the output dir with SPA mode for
116
- client-routed apps. The CLI auto-detects source dirs and tells you the
117
- right command.
118
- - Vite/React/Vue/Svelte: `npm run build` then `shiply publish dist --spa`
119
- - Create React App: `npm run build` then `shiply publish build --spa`
120
- - Next.js: set `output: "export"` in next.config, `npm run build`, then
121
- `shiply publish out` (no --spa; export emits real HTML per route)
122
- - Astro/static generators: `shiply publish dist`
123
- Use the CLI (not inline MCP files) for builds — hashed bundles often exceed
124
- the 50-file inline cap, and the CLI hash-skips unchanged chunks on updates.
125
-
126
- ## Limits & references
127
- ≤1000 files/site (≤50 inline via MCP), ≤100 MiB/file, 1 GiB total.
128
- Machine guide: https://shiply.now/llms.txt · OpenAPI:
129
- https://shiply.now/openapi.json · Docs: https://shiply.now/docs
1
+ ---
2
+ name: shiply
3
+ description: Publish static sites to the web instantly with shiply.now and manage them (updates, SSL checks, custom domains, variables). Use when the user asks to publish, host, deploy, share, or update a website/page/demo/report, or wants a live URL for generated files. Triggers - "publish this", "host this", "put this online", "give me a link", "update the site", shiply.
4
+ ---
5
+
6
+ # shiply — instant web hosting for agents
7
+
8
+ shiply.now puts files on the web in seconds. No account needed to start:
9
+ anonymous sites are live immediately, last 24 hours, and can be claimed into
10
+ an account to keep them. The cardinal rule:
11
+
12
+ **NEVER create a new site to update an existing one. Always re-publish to the
13
+ same site** — otherwise you litter subdomains and lose the user's URL.
14
+
15
+ ## First publish on a new machine: ask for a permanent key in one step
16
+
17
+ On a no-Bearer publish, include `"agentName": "<your-tool-name>"` in the body
18
+ (e.g. `"Claude Code"`, `"Cursor"`). The response will include a `deviceAuth`
19
+ object alongside the usual `claimUrl`:
20
+
21
+ ```
22
+ "deviceAuth": {
23
+ "user_code": "ABCD-EFGH",
24
+ "device_code": "<long opaque token, keep private>",
25
+ "verification_url": "https://shiply.now/auth/ABCD-EFGH",
26
+ "poll_url": "https://shiply.now/api/v1/auth/device/poll",
27
+ "expires_in": 900,
28
+ "interval": 2
29
+ }
30
+ ```
31
+
32
+ Tell the user once, in plain language:
33
+
34
+ > Your site is live at <siteUrl>. To keep it forever AND let me publish more
35
+ > without re-pasting, open <verification_url> and click Allow.
36
+
37
+ Then immediately poll `verification_url`'s sibling endpoint until the user
38
+ acts:
39
+
40
+ ```
41
+ POST https://shiply.now/api/v1/auth/device/poll
42
+ {"device_code": "<the device_code from the response>"}
43
+ ```
44
+
45
+ The response is one of `{status:"pending"}` (keep polling at `interval`s),
46
+ `{status:"approved", api_key:"shp_...", slug_claimed:"..."}` (save the key as
47
+ `SHIPLY_API_KEY`, the site is now permanent Allow does both jobs in one
48
+ click), `{status:"expired"|"denied"|"consumed"}` (stop). Cap your polling at
49
+ the announced `expires_in` and respect `interval` don't tight-loop.
50
+
51
+ After approval, every future publish uses `Authorization: Bearer <api_key>`
52
+ and the user's dashboard shows their sites without any further claiming.
53
+
54
+ ## When the user pastes you something shiply-shaped, do this
55
+
56
+ These are the patterns you will see. Match the input, run the action — do not
57
+ ask the user to repeat themselves or explain.
58
+
59
+ - **Code like `SHIPLY-XXXXXXXX`** it's a pairing code from
60
+ `shiply.now/claim/<slug>?pair=1`. The user wants to attach an anonymous
61
+ site to their account. Run `shiply claim verify SHIPLY-XXXXXXXX` from the
62
+ directory you originally published from (it reads `.shiply.json`). No CLI?
63
+ POST `https://shiply.now/api/v1/claim/pair/SHIPLY-XXXXXXXX/verify` with
64
+ `{"siteId":"...","claimToken":"..."}` taken from your earlier publish
65
+ response or `.shiply.json`. The user's browser auto-redirects on success;
66
+ tell them to switch back to it.
67
+ - **URL like `https://shiply.now/claim/<slug>?token=...`** one-click claim
68
+ link. Tell the user to open it in the browser they are signed in to —
69
+ do NOT call any API yourself.
70
+ - **User says "claim my site" / "save it forever" with no code or URL** →
71
+ give them the `claimUrl` from your last publish response. If you no longer
72
+ have that response but `.shiply.json` is in the publish dir, reconstruct it
73
+ as `https://shiply.now/claim/<slug>?token=<claimToken>` from those fields.
74
+ Either way, also suggest `shiply login` so future publishes mint to their
75
+ account directly that's the long-term fix and how the dashboard works.
76
+ - **User says "I have an API key now, use it from now on"** → save it as
77
+ `SHIPLY_API_KEY` in your environment / config and send
78
+ `Authorization: Bearer <key>` on every future publish. All future sites
79
+ are then permanent and appear in their dashboard automatically.
80
+ - **User opens an `https://shiply.now/auth/XXXX-YYYY` URL you printed** no
81
+ action from you; they're on the device-flow consent screen. Keep polling
82
+ `poll_url` (see "First publish on a new machine" above). The poll response
83
+ will flip from `pending` to `approved` (with `api_key`) the moment they
84
+ click Allow.
85
+
86
+ ## Pick your interface (best first)
87
+
88
+ ### 1. MCP (native tools)
89
+ If the `shiply` MCP server is connected (https://shiply.now/mcp), use
90
+ `publish_site`. Every result includes a `toUpdate` field telling you the exact
91
+ call for updates follow it, and a `shareSuggestion` you can relay to the user.
92
+ Other tools: `site_status`, `list_sites`, `get_site`, `set_handle`,
93
+ `duplicate_site`, `set_variable`, `get_analytics`, `delete_site`, `whoami`,
94
+ `add_custom_domain`, `connect_provider`, `add_subdomain`, `check_custom_domain`,
95
+ `sync_dns`, `list_custom_domains`, `remove_custom_domain`
96
+ (auto-DNS via OAuth provider connections see **Custom domains** section below).
97
+
98
+ Newer capabilities (use when relevant):
99
+ - `set_site_access` — password / invite-only protect a site (paid).
100
+ - `set_link` — mount another owned **public** site at a path on a host
101
+ (host/docs target).
102
+ - `set_profile` / `feature_site` stand up the user's public portfolio at
103
+ <handle>.shiply.now and feature a public site on shiply.now/explore.
104
+ - **Drives** (private cloud storage for files/notes/context): `list_drives`,
105
+ `create_drive`, `drive_put_file` (driveId "default", utf8/base64, ≤2 MB),
106
+ `drive_list_files`, `drive_delete_file`, `publish_from_drive` (snapshot a
107
+ drive into a live site). Great for agent memory and assets you don't want on
108
+ a public site.
109
+ - `export_account` JSON bundle of the user's data (no secrets).
110
+
111
+ ### 2. CLI
112
+ ```bash
113
+ npm i -g shiply-cli # or: curl -fsSL https://shiply.now/install.sh | bash
114
+ shiply publish ./dir # live URL + confetti
115
+ shiply publish ./dir # run AGAIN after edits updates the SAME site
116
+ shiply status <slug> --wait # SSL + readiness; prints SSL_READY / SITE_READY
117
+ shiply login # email code → API key → sites become permanent
118
+ shiply claim verify <code> # confirm a SHIPLY-XXXXXXXX pairing code from /claim/<slug>?pair=1
119
+ # (uses .shiply.json from CWD; pairs agent session to user's browser)
120
+ ```
121
+ The CLI stores each directory's site in `.shiply.json` (slug + update token),
122
+ so repeat publishes reuse the URL automatically. `--new-site` opts out.
123
+ Gitignore `.shiply.json` in public repos. Parse `SITE_READY` / `SSL_READY`
124
+ lines for automation; exit code 0 = ready.
125
+
126
+ ### 3. Raw HTTP (no installs)
127
+ ```
128
+ 1. POST https://shiply.now/api/v1/publish
129
+ {
130
+ "agentName": "<your tool name, e.g. Claude Code, Cursor, Codex>",
131
+ "files": [{"path":"index.html","size":<bytes>,"contentType":"text/html","hash":"<sha256, optional>"}]
132
+ }
133
+ (+ "Authorization: Bearer shp_…" for permanent owned sites)
134
+ 2. PUT each file's bytes to response upload.uploads[].url
135
+ 3. POST upload.finalizeUrl with {"versionId":"..."}
136
+ ```
137
+
138
+ **ALWAYS include `agentName` on anonymous publishes.** The response then
139
+ carries `deviceAuth` ({user_code, verification_url, poll_url, device_code,
140
+ expires_in, interval}) alongside `claimUrl`. Show the user `verification_url`,
141
+ poll `poll_url` every `interval` seconds with the `device_code`, and on
142
+ `{status:"approved"}` you get a permanent `api_key` PLUS the linked site
143
+ is claimed in the same Allow click. Without `agentName` the user has to
144
+ do a separate manual claim step every time.
145
+
146
+ **Updates:** anonymous → include `"claimToken":"..."` (returned ONCE by the
147
+ first publish — save it); owned → include `"slug":"..."`. Hashes make updates
148
+ cheap: unchanged files are skipped server-side.
149
+
150
+ ## The lifecycle to explain to users
151
+ - Anonymous site: live instantly, expires in 24 h. Give the user the
152
+ `claimUrl` — claiming keeps it forever on a free account.
153
+ - API key (`shiply login` or POST /api/auth/agent/request-code →
154
+ verify-code): publishes are permanent and manageable.
155
+ - Paid plans add vanity handles (<name>.shiply.now), more custom domains,
156
+ storage, analytics: https://shiply.now/dashboard/plan
157
+
158
+ ## Custom domains
159
+
160
+ Serve a site at `www.yourdomain.com` instead of a `*.shiply.now` URL. Requires a paid plan and a Bearer API key.
161
+
162
+ ### CLI commands
163
+
164
+ ```bash
165
+ shiply domain add example.com # register a root domain (detects provider)
166
+ shiply domain connect example.com # one-click OAuth DNS setup (Cloudflare, GoDaddy, IONOS, etc.)
167
+ shiply domain sub add www.example.com --site my-site # map a subdomain → site
168
+ shiply domain ls # list all domains + subdomains + status
169
+ shiply domain sync example.com # re-sync DNS records with a connected provider
170
+ ```
171
+
172
+ 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.
173
+
174
+ For unsupported providers, `shiply domain add` returns the CNAME record to add manually:
175
+ `www CNAME cname.shiply.now`
176
+
177
+ Check status after connecting:
178
+ ```bash
179
+ shiply status www.example.com --wait # polls until SSL_READY + SITE_READY; exit 0 when live
180
+ ```
181
+
182
+ ### MCP tools (same operations via agent tools)
183
+
184
+ | Tool | Purpose |
185
+ |------|---------|
186
+ | `add_custom_domain` | Add a root domain and detect the provider |
187
+ | `connect_provider` | Start OAuth → returns `{ url }` (show to user to authorize) |
188
+ | `add_subdomain` | Map a subdomain to a site slug |
189
+ | `check_custom_domain` | Live CNAME + SSL + readiness probe |
190
+ | `sync_dns` | Re-push DNS records to a connected provider |
191
+ | `list_custom_domains` | List all domains and their subdomain mappings |
192
+ | `remove_custom_domain` | Remove a domain and all its mappings |
193
+
194
+ ### REST endpoints (`/api/v1/custom-domains`)
195
+
196
+ ```
197
+ GET /api/v1/custom-domains list domains
198
+ POST /api/v1/custom-domains add domain { domain }
199
+ DELETE /api/v1/custom-domains/{domain} remove domain
200
+ POST /api/v1/custom-domains/{domain}/subdomains add subdomain { subdomain, slug }
201
+ POST /api/v1/custom-domains/{domain}/connect start OAuth → { url }
202
+ POST /api/v1/custom-domains/{domain}/sync-dns re-sync DNS
203
+ POST /api/v1/custom-domains/{domain}/check live status
204
+ ```
205
+
206
+ All require `Authorization: Bearer shp_…`.
207
+
208
+ ## Power features (Bearer key)
209
+ - **Custom domains**: see the **Custom domains** section above.
210
+ REST: `POST /api/v1/custom-domains {"domain"}` → add domain + detect provider.
211
+ Then `POST /api/v1/custom-domains/{domain}/connect` → OAuth one-click (show URL to user)
212
+ or add the CNAME manually. Map subdomains: `POST /api/v1/custom-domains/{domain}/subdomains {"subdomain","slug"}`.
213
+ Poll `POST /api/v1/custom-domains/{domain}/check` until `ready:true`.
214
+ - **SSL/readiness checker**: `shiply status <slug-or-domain> --wait` or the
215
+ check endpoint — confirms certificate + serving before telling the user
216
+ it's done.
217
+ - **Variables**: encrypted per-user KV for API keys the user's sites need
218
+ (GET/PUT /api/v1/variables, DELETE /api/v1/variables/{NAME}); Supabase can
219
+ be connected from the dashboard and lands here as SUPABASE_URL +
220
+ SUPABASE_ANON_KEY.
221
+ - **Rollback**: POST /api/v1/publish/{slug}/rollback {"versionId"} flips any
222
+ finalized version live instantly.
223
+ - **SPA**: pass `"spaMode":true` so deep links serve index.html.
224
+
225
+ ## Proxy routes — AI/API calls WITHOUT exposing keys
226
+ NEVER embed API keys in published HTML/JS. Instead: store the key as a
227
+ Variable (PUT /api/v1/variables), then include `.shiply/proxy.json` in the
228
+ published files declaring routes, e.g. {"proxies":{"/api/chat":{"upstream":
229
+ "https://openrouter.ai/api/v1/chat/completions","method":"POST","headers":
230
+ {"Authorization":"Bearer ${OPENROUTER_API_KEY}"}}}}. The page then calls the
231
+ relative path (fetch('/api/chat')) and shiply injects the secret server-side.
232
+ Prefix routes "/api/db/*" forward subpaths (great for Supabase REST).
233
+ Owned sites only; https public upstreams only. Docs: /docs/proxy-routes
234
+
235
+ ## Site Data — forms/waitlists/guestbooks with ZERO backend
236
+ When the user wants to collect anything from visitors (waitlist, contact
237
+ form, RSVP, guestbook): include `.shiply/data.json` in the published files,
238
+ e.g. {"collections":{"signups":{"fields":{"email":{"type":"email",
239
+ "required":true}},"access":{"read":"owner","insert":"public"}}}}, then have
240
+ the page POST JSON to the relative path /.shiply/data/signups. Records are
241
+ validated server-side; the owner reads them in the dashboard (Data section,
242
+ CSV export) or GET /api/v1/publishes/<slug>/data/<collection> with a Bearer
243
+ key. Owned sites only. Docs: /docs/site-data
244
+
245
+ ## SQL databases (Cloudflare D1 + Neon Postgres)
246
+ Two engines, same CLI/REST/MCP surface. **D1 (SQLite at the edge)** is
247
+ free on every plan and queryable from the browser via a built-in fetch
248
+ shim — no API key in the page. **Neon Postgres** is a developer-plan
249
+ add-on with copy-on-write branching; the connection URI is encrypted at
250
+ rest and surfaced to the site's Worker as `env.DATABASE_URL`.
251
+
252
+ ### D1 (default)
253
+ ```bash
254
+ shiply db create app # provision a D1 (binding APP_DB)
255
+ shiply db ls # list (name, provider, size, attached site)
256
+ shiply db sql app "SELECT 1" # one-shot query against your account
257
+ shiply db sql app "SELECT * FROM t WHERE id=?1" --params '[42]'
258
+ shiply db migrate app ./migrations # apply every *.sql in dir, sorted
259
+ shiply db attach app --site my-site # bind an existing DB to another site
260
+ shiply db delete app --yes # drop the DB and every row
261
+ ```
262
+
263
+ Run `shiply db create` inside a publish directory: it records the new
264
+ `databaseId` into `.shiply.json` so the next `shiply publish` auto-attaches
265
+ it. Site code then calls the shim:
266
+
267
+ ```js
268
+ fetch('/_shiply/db/APP_DB/query', {
269
+ method: 'POST',
270
+ headers: { 'content-type': 'application/json' },
271
+ body: JSON.stringify({ sql: 'SELECT * FROM posts', params: [] }),
272
+ }).then(r => r.json()) // → { results: [...], meta: {rows_read, rows_written, duration} }
273
+ ```
274
+
275
+ **Public by default**: the shim has no visitor auth, so anyone who can fetch
276
+ the site can run any SQL. Until a private-mode gate ships, recommend it
277
+ strictly as a SELECT-only public data layer. Reads always work; writes hit
278
+ 402 once the DB exceeds the plan storage cap (Founder Special 100 MB,
279
+ Hobby 1 GB, Developer 10 GB).
280
+
281
+ ### Neon Postgres (developer plan)
282
+ Pass `--postgres` on create. One isolated Neon project per database;
283
+ binding defaults to `DATABASE_URL`. Free / Hobby get `402
284
+ payment_required` — point the user at https://shiply.now/dashboard/plan.
285
+
286
+ ```bash
287
+ shiply db create app --postgres # provision Neon (~5–10s)
288
+ shiply db branch app dev # copy-on-write branch off main
289
+ shiply db branches app # list branches
290
+ shiply db delete-branch dev --yes # drop branch + endpoint
291
+ shiply db merge dev # NO-OP — prints pg_dump tip (see below)
292
+ ```
293
+
294
+ After the next `shiply publish` the serving Worker receives
295
+ `env.DATABASE_URL`. Minimal example:
296
+
297
+ ```js
298
+ import { neon } from '@neondatabase/serverless'
299
+ const sql = neon(process.env.DATABASE_URL)
300
+ const rows = await sql`SELECT now()`
301
+ ```
302
+
303
+ Anything that speaks Postgres works: `pg`, `postgres`, drizzle, kysely,
304
+ prisma (with the Neon adapter).
305
+
306
+ **Branching is the headline feature.** Branches are cheap, instant,
307
+ copy-on-write forks of `main`. Bind a publish to a specific branch with
308
+ `shiply publish --preview-branch=<branchDbId>` (use the `site_databases`
309
+ row id printed by `shiply db branch`, NOT the raw Neon branch id) —
310
+ great for preview deploys and safe schema rollouts.
311
+
312
+ **`shiply db merge` is intentionally a no-op.** Neon has no server-side
313
+ merge. To promote branch data into the parent:
314
+ `pg_dump "$BRANCH_DATABASE_URL" | psql "$PARENT_DATABASE_URL"`.
315
+ Run schema migrations against both branches to keep them aligned.
316
+
317
+ **Not yet supported on Neon**: branch-of-branch (branches off `main`
318
+ only), auto preview-branch on every publish, cross-region projects.
319
+
320
+ ### REST + MCP (provider-agnostic)
321
+ REST (Bearer `shp_…`): `POST /api/v1/databases` (pass
322
+ `"provider":"neon"` for Postgres) to create, `POST
323
+ /api/v1/databases/{id}/query` to query (works for D1 and Neon), `POST
324
+ .../attach` to bind, `DELETE` to drop. Neon-only:
325
+ `POST/GET/DELETE /api/v1/databases/{id}/branches[/{branchId}]` (400 on
326
+ D1 rows). Per-DB MCP server (so an agent can be scoped to one DB):
327
+ `https://shiply.now/api/mcp/db/<id>/sse` — tools: `db_query`,
328
+ `db_list_tables`, `db_schema`. The same three tools work against both
329
+ providers; `db_schema` introspects `sqlite_master` (D1) or
330
+ `information_schema` (Neon). Full docs:
331
+ https://shiply.now/docs/databases
332
+
333
+ ## Make a site private (paid plans)
334
+ To password-protect or restrict a site: PATCH /api/v1/publishes/<slug>/access
335
+ with {"mode":"password","password":"..."} or {"mode":"restricted",
336
+ "allowedEmails":[...],"allowedDomains":[...]}, or set mode "public" to open it.
337
+ MCP tool set_site_access does the same. Enforced before any content is served;
338
+ changing settings signs current visitors out. Docs: /docs/access-control
339
+
340
+ ## Any static site works — frameworks + SSGs auto-detected
341
+ shiply hosts any static site. Publish the BUILD OUTPUT, never the source:
342
+ run the build, then publish the output dir with SPA mode for client-routed
343
+ apps. The CLI auto-detects 16+ frameworks and tells you the right command.
344
+ Run `shiply detect` to preview what it found without uploading.
345
+ - Vite (React/Vue/Svelte/Solid/Qwik): `npm run build` then `shiply publish dist --spa`
346
+ - Create React App: `npm run build` then `shiply publish build --spa`
347
+ - Next.js: set `output: "export"` in next.config, `npm run build`, then
348
+ `shiply publish out` (no --spa; export emits real HTML per route)
349
+ - Astro: `npm run build` then `shiply publish dist`
350
+ - SvelteKit (adapter-static): `npm run build` then `shiply publish build --spa`
351
+ - Nuxt: `npx nuxt generate` then `shiply publish .output/public`
352
+ - Remix (client build): `npm run build` then `shiply publish build/client --spa`
353
+ - Docusaurus: `npm run build` then `shiply publish build`
354
+ - Hugo: `hugo` then `shiply publish public`
355
+ - Jekyll: `bundle exec jekyll build` then `shiply publish _site`
356
+ - Eleventy: `npx @11ty/eleventy` then `shiply publish _site`
357
+ - MkDocs: `mkdocs build` then `shiply publish site`
358
+ - Hexo: `npx hexo generate` then `shiply publish public`
359
+ - SolidStart (static): `npm run build` then `shiply publish dist/public`
360
+ - Qwik City (static adapter): `npm run build` then `shiply publish dist`
361
+ - Anything else with a built `dist/`/`build/`/`out/`/`_site/`/`public/`/
362
+ `.output/public/` folder: generic fallback picks it up.
363
+ - Plain HTML: `shiply publish .` — no build step.
364
+ Override auto-detection with `--framework=<name>`. Use the CLI (not inline
365
+ MCP files) for builds — hashed bundles often exceed the 50-file inline cap,
366
+ and the CLI hash-skips unchanged chunks on updates.
367
+
368
+ ## Limits & references
369
+ ≤1000 files/site (≤50 inline via MCP), ≤100 MiB/file, 1 GiB total.
370
+ Machine guide: https://shiply.now/llms.txt · OpenAPI:
371
+ https://shiply.now/openapi.json · Docs: https://shiply.now/docs