shiply-cli 0.24.0 → 0.25.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/dist/connect.js +189 -0
- package/dist/data.js +17 -3
- package/dist/index.js +78 -5
- package/dist/skill.js +16 -13
- package/package.json +42 -42
- package/skill/CHANGELOG.md +90 -0
- package/skill/SKILL.md +78 -664
- package/skill/references/client-work.md +159 -0
- package/skill/references/custom-domains.md +87 -0
- package/skill/references/databases.md +108 -0
- package/skill/references/email.md +147 -0
- package/skill/references/functions.md +100 -0
- package/skill/references/publishing.md +103 -0
- package/skill/references/site-features.md +110 -0
- package/skill/references/ssr-frameworks.md +47 -0
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.
|
|
16
|
-
>
|
|
17
|
-
>
|
|
18
|
-
>
|
|
19
|
-
>
|
|
20
|
-
>
|
|
21
|
-
>
|
|
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
|
-
|
|
126
|
-
`duplicate_site`, `set_variable`, `get_analytics`, `
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
|
245
|
-
`claimUrl` — claiming keeps it forever on a free
|
|
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
|
-
##
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
832
|
-
https://shiply.now/
|
|
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
|