shiply-cli 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connect.js +189 -0
- package/dist/data.js +16 -2
- package/dist/index.js +78 -5
- package/dist/publish.js +42 -2
- package/dist/skill.js +16 -13
- package/package.json +42 -42
- package/skill/CHANGELOG.md +83 -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
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
title: Client work (projects, clients, contracts, marketplace)
|
|
4
|
+
description: Freelancer delivery — customer intake projects with AI briefs, grouping work by client, e-sign contracts with amendments, and selling built sites on the marketplace via Stripe Connect.
|
|
5
|
+
timestamp: 2026-07-04
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Client work — projects, clients, contracts, marketplace
|
|
9
|
+
|
|
10
|
+
Part of the shiply skill (see SKILL.md for publish basics). All of it Bearer-
|
|
11
|
+
key authenticated.
|
|
12
|
+
|
|
13
|
+
## Projects — customer intake + AI brief
|
|
14
|
+
|
|
15
|
+
When a dev needs to capture a real client brief, they spin up a **project**:
|
|
16
|
+
the dashboard (or `shiply project create`) mints a one-URL intake form to
|
|
17
|
+
share with the customer. The customer fills a 10-step wizard, attaches
|
|
18
|
+
files, hits submit. An AI brief generator (MiniMax-M2 with Anthropic
|
|
19
|
+
fallback) turns the answers into a structured brief the dev reviews and
|
|
20
|
+
edits inline. All files flow into a per-project drive folder.
|
|
21
|
+
|
|
22
|
+
MCP tools:
|
|
23
|
+
```
|
|
24
|
+
list_projects — your projects (filter status/q)
|
|
25
|
+
create_project — start a new intake; pass customerName/customerEmail to email them the link
|
|
26
|
+
get_project — full project + brief
|
|
27
|
+
update_brief — patch brief jsonb (after AI generation)
|
|
28
|
+
regenerate_brief — re-run AI from current intake responses
|
|
29
|
+
archive_project / restore_project — lifecycle
|
|
30
|
+
list_project_files — files uploaded by the customer
|
|
31
|
+
resend_intake_invite — re-email the customer their intake link (requires customerEmail on project)
|
|
32
|
+
```
|
|
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).
|
|
38
|
+
|
|
39
|
+
CLI: `shiply project ls · shiply project create <label> [--customer-email <e>]
|
|
40
|
+
[--customer-name <n>] · shiply project get <id> · shiply project archive <id>`
|
|
41
|
+
|
|
42
|
+
Docs: https://shiply.now/docs/projects
|
|
43
|
+
|
|
44
|
+
## Group work by client (optional, for freelancers)
|
|
45
|
+
|
|
46
|
+
Building sites for clients? Pass an optional `client` (a name or email) on any
|
|
47
|
+
`publish_site`, `create_drive`, or `create_project` call — shiply
|
|
48
|
+
create-or-finds the client by email and files everything you ship for them
|
|
49
|
+
(sites, drop-box, intake, contracts) under one customer view in the dashboard.
|
|
50
|
+
A project's customer **is** the site's client (same email-keyed identity), so
|
|
51
|
+
`create_project` already links automatically whenever you pass
|
|
52
|
+
`customerEmail` — no extra step. There is no `create_client` tool and never a
|
|
53
|
+
CRM step: the client record self-assembles from the calls you already make.
|
|
54
|
+
`publish_site` echoes the `resolvedClientId` — reuse it across the session so
|
|
55
|
+
drive/intake/contract all land under the same client. List calls take an
|
|
56
|
+
optional `clientId` filter (`list_sites`/`list_drives`/`list_projects`).
|
|
57
|
+
Omit `client` for personal projects; shiply works identically.
|
|
58
|
+
CLI: `--client "<name-or-email>"` on publish/drive/project, plus
|
|
59
|
+
`shiply client ls|show`.
|
|
60
|
+
|
|
61
|
+
## Contracts — draft, sign, amend (extends Projects)
|
|
62
|
+
|
|
63
|
+
Once a project reaches `brief_ready`, the dev drafts a signed contract from
|
|
64
|
+
the brief, the customer e-signs in the portal (typed-name + checkbox), and
|
|
65
|
+
the dev amends it later as scope evolves. A signed contract is immutable
|
|
66
|
+
— any change is a new amendment row chained off the parent. The portal
|
|
67
|
+
sends the customer; the dev can resend / remind / retract. A 7-day
|
|
68
|
+
reminder cron nudges unsigned contracts automatically.
|
|
69
|
+
|
|
70
|
+
MCP tools:
|
|
71
|
+
```
|
|
72
|
+
contract_draft — draft a contract from a brief_ready project (returns the new contract id)
|
|
73
|
+
contract_send — flip status='sent', fire customer email (parent OR amendment)
|
|
74
|
+
contract_amend — create an amendment draft on a SIGNED parent (scopeDelta required, fee + date optional)
|
|
75
|
+
contract_status — read state + amendments (poll this to see status='signed')
|
|
76
|
+
contract_pdf — base64-encoded signed PDF (parent + cert + signed amendments)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Edit-before-send today is REST-only: `PATCH /api/v1/contracts/{id}` lets
|
|
80
|
+
the dev tweak the 8 fields (scopeSummary, feeCents, currency, dates,
|
|
81
|
+
revisionCount, revisionOverageCents, jurisdiction). After PATCH, call
|
|
82
|
+
`contract_send` to fire it. Amendments are sent the same way (call
|
|
83
|
+
`contract_send` with the amendment id returned by `contract_amend`).
|
|
84
|
+
|
|
85
|
+
Example agent flow (draft → tweak fee → send → poll → amend):
|
|
86
|
+
1. `contract_draft({projectId})` → returns `{id, scopeSummary, feeCents:null, ...}`
|
|
87
|
+
2. `PATCH /api/v1/contracts/{id}` `{feeCents: 450000}` (Bearer key)
|
|
88
|
+
3. `contract_send({contractId: id})` → project flips to `contract_sent`
|
|
89
|
+
4. Poll `contract_status({contractId: id})` until `contract.status === 'signed'`
|
|
90
|
+
5. `contract_amend({parentContractId: id, scopeDelta: "Add login flow", feeDeltaCents: 50000})` → returns the amendment row
|
|
91
|
+
6. `contract_send({contractId: amendment.id})` (no PATCH needed if the amend fields are right)
|
|
92
|
+
|
|
93
|
+
REST (Bearer for dev, portal cookie for customer):
|
|
94
|
+
`POST /api/v1/projects/{id}/contracts` (draft) ·
|
|
95
|
+
`GET/PATCH /api/v1/contracts/{id}` (read either-party / dev edit) ·
|
|
96
|
+
`POST /api/v1/contracts/{id}/send` (dev) ·
|
|
97
|
+
`POST /api/v1/contracts/{id}/view` (customer ping) ·
|
|
98
|
+
`POST /api/v1/contracts/{id}/sign` (customer e-sign) ·
|
|
99
|
+
`POST /api/v1/contracts/{id}/retract` (dev; `?recoverAsDraft=true` to
|
|
100
|
+
recover the draft instead of voiding) ·
|
|
101
|
+
`POST /api/v1/contracts/{id}/amend` (dev; signed parents only) ·
|
|
102
|
+
`POST /api/v1/contracts/{id}/remind` ·
|
|
103
|
+
`POST /api/v1/contracts/{id}/resend` (manual nudges, rate-limited 3/24h) ·
|
|
104
|
+
`GET /api/v1/contracts/{id}/pdf` (binary, signed only).
|
|
105
|
+
|
|
106
|
+
`contract_pdf` returns `{filename, contentType:"application/pdf", base64,
|
|
107
|
+
byteLength}` so the MCP client can decode and save the bytes.
|
|
108
|
+
|
|
109
|
+
CLI: `shiply contract list <project-id>`, `shiply contract draft
|
|
110
|
+
<project-id>`, `shiply contract show <contract-id>`, `shiply contract send
|
|
111
|
+
<contract-id>`, `shiply contract pdf <contract-id> -o file.pdf`, `shiply
|
|
112
|
+
contract amend <id> --scope "…" [--fee-delta N] [--target-date YYYY-MM-DD]`,
|
|
113
|
+
`shiply contract retract <id> [--keep-as-draft]`.
|
|
114
|
+
|
|
115
|
+
Docs: https://shiply.now/docs/contracts
|
|
116
|
+
|
|
117
|
+
## Marketplace — sell built sites
|
|
118
|
+
|
|
119
|
+
Devs can list any owned site for sale: set a price + short pitch, choose
|
|
120
|
+
standard or custom terms, pick a jurisdiction. Stripe Connect Express
|
|
121
|
+
handles seller payouts — sellers complete a one-time onboarding before
|
|
122
|
+
listing (`get_connect_status` returns the onboarding URL when needed).
|
|
123
|
+
Buyers pay via Stripe Checkout; on `checkout.session.completed` the
|
|
124
|
+
webhook flips the order to `paid` and transfers site ownership
|
|
125
|
+
atomically. Refunds are available within the order's refund window
|
|
126
|
+
(`refundExpiresAt`); a refund reverts ownership to the seller.
|
|
127
|
+
|
|
128
|
+
Prices are passed as `priceCents` (whole-dollar cents between 100 and
|
|
129
|
+
999900). `termsMode='standard'` uses shiply's template; `'custom'`
|
|
130
|
+
requires `termsCustom` ≥50 chars.
|
|
131
|
+
|
|
132
|
+
MCP tools:
|
|
133
|
+
```
|
|
134
|
+
list_listings — my listings
|
|
135
|
+
create_listing — list a site (siteSlug, priceCents, pitch?, termsMode, jurisdiction, ...)
|
|
136
|
+
update_listing — change price/pitch/status (draft|live|paused)
|
|
137
|
+
delete_listing — pulls off marketplace (sets status=draft)
|
|
138
|
+
list_my_sales — orders where I'm the seller
|
|
139
|
+
list_my_orders — orders where I'm the buyer
|
|
140
|
+
refund_order — issue Stripe refund (within refund window; goes back to buyer)
|
|
141
|
+
get_connect_status — Stripe Connect onboarding state + actionable URL
|
|
142
|
+
```
|
|
143
|
+
|
|
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.)
|
|
150
|
+
|
|
151
|
+
CLI: `shiply listing ls · shiply listing create <site-slug> --price <cents>
|
|
152
|
+
--jurisdiction "<region>" · shiply listing rm <slug> --id <listing-id>`
|
|
153
|
+
|
|
154
|
+
**Stripe Connect note:** Sellers must complete one-time Stripe Connect
|
|
155
|
+
onboarding before listing. Call `get_connect_status` first — when status
|
|
156
|
+
isn't `'ready'` it returns an `onboardingUrl` you should hand to the
|
|
157
|
+
user as a clickable link.
|
|
158
|
+
|
|
159
|
+
Docs: https://shiply.now/docs/marketplace
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
title: Custom domains
|
|
4
|
+
description: Serve a site at the user's own domain — one-click OAuth DNS (Cloudflare, GoDaddy, IONOS), manual CNAME fallback, subdomain mapping, primary-hostname SEO, readiness polling.
|
|
5
|
+
timestamp: 2026-07-04
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Custom domains
|
|
9
|
+
|
|
10
|
+
Part of the shiply skill (see SKILL.md for publish basics). Serve a site at
|
|
11
|
+
`www.yourdomain.com` instead of a `*.shiply.now` URL. Requires a paid plan and
|
|
12
|
+
a Bearer API key.
|
|
13
|
+
|
|
14
|
+
## The agent sequence (do it in this order)
|
|
15
|
+
|
|
16
|
+
1. Confirm the user is authenticated and on a paid plan (`whoami` over MCP, or
|
|
17
|
+
`shiply status`).
|
|
18
|
+
2. Add the root domain — this registers it and detects the DNS provider
|
|
19
|
+
(cloudflare, godaddy, ionos, or manual).
|
|
20
|
+
3. **Supported provider detected** → start the one-click OAuth connect; it
|
|
21
|
+
returns a URL. SHOW THE USER that URL and ask them to open it in their
|
|
22
|
+
browser to authorize DNS writes. Wait for their confirmation.
|
|
23
|
+
**Provider is "manual"** → show the user the CNAME record returned by
|
|
24
|
+
step 2 (target: `cname.shiply.now`) and ask them to add it at their
|
|
25
|
+
registrar. Wait for confirmation.
|
|
26
|
+
4. Map the subdomain to the site. The first subdomain you add for a site is
|
|
27
|
+
primary by default; later mappings start non-primary and 301-redirect to it.
|
|
28
|
+
5. (Optional, SEO) Set the primary hostname between apex and www — sibling
|
|
29
|
+
hostnames 301-redirect to it, preserving path + query. Fixes duplicate
|
|
30
|
+
content.
|
|
31
|
+
6. Poll the check endpoint until `ready: true` (usually 1–5 minutes), then
|
|
32
|
+
tell the user their domain is live.
|
|
33
|
+
|
|
34
|
+
## CLI commands
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
shiply domain add example.com # register a root domain (detects provider)
|
|
38
|
+
shiply domain connect example.com # one-click OAuth DNS setup (Cloudflare, GoDaddy, IONOS, etc.)
|
|
39
|
+
shiply domain sub add www.example.com --site my-site # map a subdomain → site
|
|
40
|
+
shiply domain ls # list all domains + subdomains + status
|
|
41
|
+
shiply domain sync example.com # re-sync DNS records with a connected provider
|
|
42
|
+
shiply domain primary <hostname> # mark hostname canonical (siblings 301)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For one-click connect (`shiply domain connect`), the CLI prints an
|
|
46
|
+
authorization URL — the user must open it in a browser and approve the OAuth
|
|
47
|
+
grant before DNS is written.
|
|
48
|
+
|
|
49
|
+
For unsupported providers, `shiply domain add` returns the CNAME record to add
|
|
50
|
+
manually: `www CNAME cname.shiply.now`
|
|
51
|
+
|
|
52
|
+
Check status after connecting:
|
|
53
|
+
```bash
|
|
54
|
+
shiply status www.example.com --wait # polls until SSL_READY + SITE_READY; exit 0 when live
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## MCP tools
|
|
58
|
+
|
|
59
|
+
| Tool | Purpose |
|
|
60
|
+
|------|---------|
|
|
61
|
+
| `add_custom_domain` | Add a root domain and detect the provider |
|
|
62
|
+
| `connect_provider` | Start OAuth → returns `{ url }` (show to user to authorize) |
|
|
63
|
+
| `add_subdomain` | Map a subdomain to a site slug |
|
|
64
|
+
| `set_primary_subdomain` | Pick the canonical URL; siblings 301-redirect (SEO fix) |
|
|
65
|
+
| `check_custom_domain` | Live CNAME + SSL + readiness probe |
|
|
66
|
+
| `sync_dns` | Re-push DNS records to a connected provider |
|
|
67
|
+
| `list_custom_domains` | List all domains and their subdomain mappings |
|
|
68
|
+
| `remove_custom_domain` | Remove a domain and all its mappings |
|
|
69
|
+
|
|
70
|
+
## REST endpoints (`/api/v1/custom-domains`)
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
GET /api/v1/custom-domains list domains
|
|
74
|
+
POST /api/v1/custom-domains add domain { domain }
|
|
75
|
+
DELETE /api/v1/custom-domains/{domain} remove domain
|
|
76
|
+
POST /api/v1/custom-domains/{domain}/subdomains add subdomain { subdomain, slug }
|
|
77
|
+
POST /api/v1/custom-domains/{domain}/subdomains/{id}/primary mark hostname canonical (siblings 301)
|
|
78
|
+
POST /api/v1/custom-domains/{domain}/connect start OAuth → { url }
|
|
79
|
+
POST /api/v1/custom-domains/{domain}/sync-dns re-sync DNS
|
|
80
|
+
POST /api/v1/custom-domains/{domain}/check live status → {cname, ssl:{valid,issuer,daysLeft}, http, ready}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
All require `Authorization: Bearer shp_…`. Poll the check endpoint until
|
|
84
|
+
`ready:true` before telling the user it's done.
|
|
85
|
+
|
|
86
|
+
Vanity handle (paid, no domain needed): `POST /api/v1/publish/<slug>/handle
|
|
87
|
+
{"handle"}` → `<handle>.shiply.now`. MCP: `set_handle`.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
title: Databases (D1 + Neon Postgres)
|
|
4
|
+
description: Per-site SQL — free Cloudflare D1 (SQLite at the edge, browser query shim) and Neon Postgres (developer plan, copy-on-write branching), migrations, attach, per-DB MCP server.
|
|
5
|
+
timestamp: 2026-07-04
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# SQL databases (Cloudflare D1 + Neon Postgres)
|
|
9
|
+
|
|
10
|
+
Part of the shiply skill (see SKILL.md for publish basics). Two engines, same
|
|
11
|
+
CLI/REST/MCP surface. **D1 (SQLite at the edge)** is free on every plan and
|
|
12
|
+
queryable from the browser via a built-in fetch shim — no API key in the page.
|
|
13
|
+
**Neon Postgres** is a developer-plan add-on with copy-on-write branching; the
|
|
14
|
+
connection URI is encrypted at rest and surfaced to the site's Worker as
|
|
15
|
+
`env.DATABASE_URL`.
|
|
16
|
+
|
|
17
|
+
## D1 (default)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
shiply db create app # provision a D1 (binding SITE_DB)
|
|
21
|
+
shiply db ls # list (name, provider, size, attached site)
|
|
22
|
+
shiply db sql app "SELECT 1" # one-shot query against your account
|
|
23
|
+
shiply db sql app "SELECT * FROM t WHERE id=?1" --params '[42]'
|
|
24
|
+
shiply db migrate app ./migrations # apply every *.sql in dir, sorted
|
|
25
|
+
shiply db attach app --site my-site # bind an existing DB to another site
|
|
26
|
+
shiply db delete app --yes # drop the DB and every row
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Run `shiply db create` inside a publish directory: it records the new
|
|
30
|
+
`databaseId` into `.shiply.json` so the next `shiply publish` auto-attaches
|
|
31
|
+
it. Site code then calls the shim:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
fetch('/_shiply/db/SITE_DB/query', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'content-type': 'application/json' },
|
|
37
|
+
body: JSON.stringify({ sql: 'SELECT * FROM posts', params: [] }),
|
|
38
|
+
}).then(r => r.json()) // → { results: [...], meta: {rows_read, rows_written, duration} }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Public by default**: the shim has no visitor auth, so anyone who can fetch
|
|
42
|
+
the site can run any SQL. Until a private-mode gate ships, recommend it
|
|
43
|
+
strictly as a SELECT-only public data layer. Reads always work; writes hit
|
|
44
|
+
402 once the DB exceeds the plan storage cap (Founder Special 100 MB,
|
|
45
|
+
Hobby 1 GB, Developer 10 GB).
|
|
46
|
+
|
|
47
|
+
D1 caveats: SQLite dialect, no cross-DB joins, 10 GB per-DB ceiling.
|
|
48
|
+
|
|
49
|
+
## Neon Postgres (developer plan)
|
|
50
|
+
|
|
51
|
+
Pass `--postgres` on create. One isolated Neon project per database;
|
|
52
|
+
binding defaults to `DATABASE_URL`. Free / Hobby get `402
|
|
53
|
+
payment_required` — point the user at https://shiply.now/dashboard/plan.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
shiply db create app --postgres # provision Neon (~5–10s)
|
|
57
|
+
shiply db branch app dev # copy-on-write branch off main
|
|
58
|
+
shiply db branches app # list branches
|
|
59
|
+
shiply db delete-branch dev --yes # drop branch + endpoint
|
|
60
|
+
shiply db merge dev # NO-OP — prints pg_dump tip (see below)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
After the next `shiply publish` the serving Worker receives
|
|
64
|
+
`env.DATABASE_URL`. Minimal example:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import { neon } from '@neondatabase/serverless'
|
|
68
|
+
const sql = neon(process.env.DATABASE_URL)
|
|
69
|
+
const rows = await sql`SELECT now()`
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Anything that speaks Postgres works: `pg`, `postgres`, drizzle, kysely,
|
|
73
|
+
prisma (with the Neon adapter). Connection URIs are AES-256-GCM encrypted
|
|
74
|
+
at rest.
|
|
75
|
+
|
|
76
|
+
**Branching is the headline feature.** Branches are cheap, instant,
|
|
77
|
+
copy-on-write forks of `main`. Bind a publish to a specific branch with
|
|
78
|
+
`shiply publish --preview-branch=<branchDbId>` (use the `site_databases`
|
|
79
|
+
row id printed by `shiply db branch`, NOT the raw Neon branch id) —
|
|
80
|
+
great for preview deploys and safe schema rollouts.
|
|
81
|
+
|
|
82
|
+
**`shiply db merge` is intentionally a no-op.** Neon has no server-side
|
|
83
|
+
merge. To promote branch data into the parent:
|
|
84
|
+
`pg_dump "$BRANCH_DATABASE_URL" | psql "$PARENT_DATABASE_URL"`.
|
|
85
|
+
Run schema migrations against both branches to keep them aligned.
|
|
86
|
+
|
|
87
|
+
**Not yet supported on Neon**: branch-of-branch (branches off `main`
|
|
88
|
+
only), auto preview-branch on every publish, cross-region projects.
|
|
89
|
+
|
|
90
|
+
## REST + MCP (provider-agnostic)
|
|
91
|
+
|
|
92
|
+
REST (Bearer `shp_…`): `POST /api/v1/databases` (pass `"provider":"neon"`
|
|
93
|
+
for Postgres) to create, `POST /api/v1/databases/{id}/query` to query (works
|
|
94
|
+
for D1 and Neon), `POST .../attach` to bind, `DELETE` to drop. Neon-only:
|
|
95
|
+
`POST/GET/DELETE /api/v1/databases/{id}/branches[/{branchId}]` (400 on
|
|
96
|
+
D1 rows).
|
|
97
|
+
|
|
98
|
+
Per-DB MCP server (so an agent can be scoped to one DB):
|
|
99
|
+
`https://shiply.now/api/mcp/db/<id>/sse` (or `/mcp` for streamable-http),
|
|
100
|
+
Bearer `shp_…` — tools: `db_query`, `db_list_tables`, `db_schema`. The same
|
|
101
|
+
three tools work against both providers; `db_schema` introspects
|
|
102
|
+
`sqlite_master` (D1) or `information_schema` (Neon).
|
|
103
|
+
|
|
104
|
+
Plan quotas: every account gets 1 free D1 (Founder Special: 100 MB; Hobby:
|
|
105
|
+
5 × 1 GB; Developer: unlimited × 10 GB). Size sampled every 15 min; writes
|
|
106
|
+
blocked at 402 over plan cap; reads still work.
|
|
107
|
+
|
|
108
|
+
Full docs: https://shiply.now/docs/databases
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
title: Email (capture, send, inbox, broadcast, sending domains)
|
|
4
|
+
description: Every owned site has a real email address — public signup capture with double opt-in, authenticated send, inbox reading, audience broadcasts, and BYO verified sending domains.
|
|
5
|
+
timestamp: 2026-07-04
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Agent Email — inbox, send, capture, broadcast (built into every owned site)
|
|
9
|
+
|
|
10
|
+
Part of the shiply skill (see SKILL.md for publish basics). Every owned site
|
|
11
|
+
has a real email address and can send, receive, capture signups, and broadcast
|
|
12
|
+
— in one line. Like AgentMail, built into the site.
|
|
13
|
+
|
|
14
|
+
## Capture (public, zero-config)
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
POST /.shiply/email { "email": "user@example.com", ...anyExtraFields }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
No manifest needed. Works on the **relative path** from the published page.
|
|
21
|
+
On success: record lands in the site inbox + owner gets a notification ping +
|
|
22
|
+
a double-opt-in confirmation email goes to the visitor (all on by default).
|
|
23
|
+
**Owned sites only** — anonymous sites get `403 email_requires_account` (claim
|
|
24
|
+
the site first). A non-empty `company` field is treated as a honeypot (bot
|
|
25
|
+
submissions are silently dropped). Caps: 16 KB body, 30 fields max.
|
|
26
|
+
|
|
27
|
+
## Send (authenticated — Bearer key only)
|
|
28
|
+
|
|
29
|
+
The public page can NEVER send; only authenticated calls can.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
POST https://shiply.now/api/v1/email/send
|
|
33
|
+
Authorization: Bearer shp_…
|
|
34
|
+
{ "slug": "my-site", "to": "user@example.com", "subject": "Hi", "html": "<p>Hi</p>", "text": "Hi" }
|
|
35
|
+
→ { "messageId": "..." }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Read inbox
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
GET https://shiply.now/api/v1/email/inbox # all sites
|
|
42
|
+
GET https://shiply.now/api/v1/email/inbox?slug=my-site
|
|
43
|
+
Authorization: Bearer shp_…
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Typed upgrade (optional)
|
|
47
|
+
|
|
48
|
+
Declare an `email` block on a `.shiply/data.json` collection for typed fields +
|
|
49
|
+
fine-grained control (see [site-features.md](site-features.md) for Site Data):
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"collections": {
|
|
54
|
+
"signups": {
|
|
55
|
+
"fields": { "email": { "type": "email", "required": true } },
|
|
56
|
+
"email": {
|
|
57
|
+
"emailField": "email",
|
|
58
|
+
"confirm": true,
|
|
59
|
+
"notify": true,
|
|
60
|
+
"audience": true
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
All three flags default to `true`. With this manifest, POST to
|
|
68
|
+
`/.shiply/data/signups` as usual — the `email` block activates the email
|
|
69
|
+
layer on that collection.
|
|
70
|
+
|
|
71
|
+
## Confirm / unsubscribe (public — in the confirmation email)
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
GET /api/email/<slug>/<collection>/confirm?token=<token>
|
|
75
|
+
GET /api/email/<slug>/<collection>/unsubscribe?email=<address>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Audience + broadcast
|
|
79
|
+
|
|
80
|
+
Confirmed (double-opt-in) signups form a list. Broadcast to them:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
POST /api/v1/publishes/<slug>/mailboxes/<collection>/broadcast
|
|
84
|
+
Authorization: Bearer shp_…
|
|
85
|
+
{ "subject": "Launch day", "html": "<p>We're live!</p>" }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Spam-checked before send; unsubscribe footer is auto-added; requires ≥1
|
|
89
|
+
confirmed subscriber.
|
|
90
|
+
|
|
91
|
+
Configure a mailbox: `GET/PATCH /api/v1/publishes/<slug>/mailboxes/<collection>`
|
|
92
|
+
List contacts: `GET /api/v1/publishes/<slug>/mailboxes/<collection>/contacts?status=confirmed`
|
|
93
|
+
|
|
94
|
+
## MCP tools
|
|
95
|
+
|
|
96
|
+
| Tool | Purpose |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `send_email` | Send transactional email from a site (Bearer) |
|
|
99
|
+
| `list_site_inbox` | Read the inbox (all threads or filter by slug) |
|
|
100
|
+
| `set_mailbox` | Configure a collection's mailbox settings |
|
|
101
|
+
| `list_mailbox_contacts` | List audience contacts (filter by status) |
|
|
102
|
+
| `send_mailbox_broadcast` | Broadcast to confirmed audience (spam-checked) |
|
|
103
|
+
|
|
104
|
+
## CLI
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
shiply email send --site <slug> --to <address> --subject <subject> --html <html>
|
|
108
|
+
shiply email inbox [--site <slug>]
|
|
109
|
+
shiply mailbox set <slug> <collection> [--confirm] [--notify] [--from <domain>]
|
|
110
|
+
shiply mailbox broadcast <slug> <collection> --subject <subject> --html <html>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Docs: https://shiply.now/docs/agent-email
|
|
114
|
+
|
|
115
|
+
## Email demand tests (agent-managed — no Resend account needed)
|
|
116
|
+
|
|
117
|
+
Validate an idea end-to-end: `create_test({ idea, headline, sub?, cta?,
|
|
118
|
+
price? })` (MCP) deploys a capture landing page + provisions a confirmed-
|
|
119
|
+
subscriber segment; returns `testId` + live `siteUrl`. Share the siteUrl.
|
|
120
|
+
Visitors submit the form → shiply sends double-opt-in confirmation
|
|
121
|
+
automatically → they click to confirm. `get_test_status({ testId })` → ONE
|
|
122
|
+
merged object: funnel (views/signups/confirmed/conversionRate), email
|
|
123
|
+
(delivered/opened/clicked/bounced), verdict (signal:
|
|
124
|
+
strong|weak|inconclusive, confirmedSignupRate). CONFIRMED is the real demand
|
|
125
|
+
signal (double opt-in). `send_broadcast({ testId, subject, html })` emails
|
|
126
|
+
the confirmed audience (unsubscribe link auto-added). `list_tests()` — all
|
|
127
|
+
tests for this key. `resend_confirmation({ testId, email })` — re-send the
|
|
128
|
+
opt-in to one address.
|
|
129
|
+
|
|
130
|
+
## Sending domains — outbound email on your domain
|
|
131
|
+
|
|
132
|
+
For agents that want shiply to send emails (demand-test broadcasts, project
|
|
133
|
+
intake invites, transactional notifications) from a custom verified domain
|
|
134
|
+
instead of the shared shiply pool. Backed by Resend — add the DNS records
|
|
135
|
+
they return at your registrar, then verify to re-check.
|
|
136
|
+
|
|
137
|
+
MCP tools: `list_sending_domains`, `add_sending_domain` (returns DNS records
|
|
138
|
+
to add), `verify_sending_domain`, `remove_sending_domain`.
|
|
139
|
+
|
|
140
|
+
REST: `GET/POST /api/v1/sending-domains ·
|
|
141
|
+
POST /api/v1/sending-domains/{id}/verify · DELETE /api/v1/sending-domains/{id}`
|
|
142
|
+
|
|
143
|
+
CLI: `shiply sending-domain ls · shiply sending-domain add <domain> ·
|
|
144
|
+
shiply sending-domain verify <id> · shiply sending-domain rm <id> --yes`
|
|
145
|
+
|
|
146
|
+
Cannot be a `shiply.now` subdomain. Verified status flips once SPF +
|
|
147
|
+
DKIM (+ MX for inbound) all check out.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
title: Functions (Workers Lite)
|
|
4
|
+
description: Server-side worker.js/worker.ts on every request — webhooks with signature verification, cron triggers, encrypted secrets, runtime logs. Developer plan.
|
|
5
|
+
timestamp: 2026-07-04
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Functions (Workers Lite) — webhooks, cron, secrets, full backend
|
|
9
|
+
|
|
10
|
+
Part of the shiply skill (see SKILL.md for publish basics). Plan-gated to
|
|
11
|
+
Developer: Founder/Hobby see `402 payment_required`
|
|
12
|
+
(upgrade: shiply.now/dashboard/plan).
|
|
13
|
+
|
|
14
|
+
shiply Workers Lite lets a published site include server-side code that runs
|
|
15
|
+
on every request. Use when the user needs:
|
|
16
|
+
- A webhook receiver (Stripe, GitHub, etc.) with raw body + signature verification
|
|
17
|
+
- A cron job (daily reminders, periodic sync, retention emails)
|
|
18
|
+
- A privileged API call using secrets (Stripe key, OpenAI key) without exposing them to the browser
|
|
19
|
+
- An authenticated mutation on D1 / Neon (do the auth check in the function before writing)
|
|
20
|
+
|
|
21
|
+
## How it works
|
|
22
|
+
|
|
23
|
+
Author `worker.js` (or `worker.ts`) at the publish root. On `shiply publish`,
|
|
24
|
+
shiply compiles (TS) + deploys it as a per-site Cloudflare Worker bound to
|
|
25
|
+
the site's hostname. The worker handles every request; pass static asset
|
|
26
|
+
requests through `env.ASSETS.fetch(request)`.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { verifyStripeSig, json } from 'shiply-runtime'
|
|
30
|
+
|
|
31
|
+
export default {
|
|
32
|
+
async fetch(req: Request, env: Env): Promise<Response> {
|
|
33
|
+
const url = new URL(req.url)
|
|
34
|
+
if (url.pathname === '/api/webhooks/stripe' && req.method === 'POST') {
|
|
35
|
+
const body = await req.text()
|
|
36
|
+
if (!await verifyStripeSig(body, req.headers.get('stripe-signature'), env.STRIPE_WEBHOOK_SECRET)) {
|
|
37
|
+
return new Response('bad sig', { status: 400 })
|
|
38
|
+
}
|
|
39
|
+
// ... handle event, write to env.SITE_DB
|
|
40
|
+
return new Response('ok')
|
|
41
|
+
}
|
|
42
|
+
return env.ASSETS.fetch(req)
|
|
43
|
+
},
|
|
44
|
+
async scheduled(event: ScheduledEvent, env: Env) {
|
|
45
|
+
if (event.cron === '0 9 * * *') { /* daily job */ }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`.shiply/crons.json` declares cron schedules. `.shiply/secrets.json` declares
|
|
51
|
+
secret names (values set via CLI/MCP, never reach the publish payload).
|
|
52
|
+
|
|
53
|
+
Runtime: V8 isolate — no Node APIs, no Buffer; use Web Crypto + Web Fetch.
|
|
54
|
+
Companion npm package `shiply-runtime` exports `verifyStripeSig`, `readJson`,
|
|
55
|
+
`json`, `errorResponse`.
|
|
56
|
+
|
|
57
|
+
## Bindings available in `env`
|
|
58
|
+
|
|
59
|
+
- `env.ASSETS` — fall through to static (`env.ASSETS.fetch(request)`)
|
|
60
|
+
- `env.SITE_DB` — attached D1 (if `shiply db attach` was run)
|
|
61
|
+
- `env.<NAME>` — each shiply Variable becomes a plain-text env var
|
|
62
|
+
- `env.<NAME>` — each secret set via `set_secret` is encrypted, accessible as env var
|
|
63
|
+
|
|
64
|
+
## MCP tools
|
|
65
|
+
|
|
66
|
+
| Tool | Purpose |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `deploy_function` | Deploy a Worker function (alternative to publish auto-detection) |
|
|
69
|
+
| `get_function` | Read the deployed source + metadata |
|
|
70
|
+
| `remove_function` | Strip function + secrets + crons; fall back to static |
|
|
71
|
+
| `set_secret` / `list_secrets` / `remove_secret` | Manage CF Worker secrets |
|
|
72
|
+
| `set_cron` / `list_crons` / `remove_cron` | Manage cron triggers |
|
|
73
|
+
| `get_function_logs` | Read recent runtime logs (CF Observability, 7-day, newest-first) + a dashboard deep-link |
|
|
74
|
+
|
|
75
|
+
## CLI
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
shiply publish . # auto-detects worker.js + crons.json + secrets.json
|
|
79
|
+
shiply function deploy <slug> # alternative: upload worker.js without re-publishing
|
|
80
|
+
shiply function deploy <slug> --ts # uploads worker.ts (server-side compile)
|
|
81
|
+
shiply secret set <slug> STRIPE_KEY sk_xxx # set secret value
|
|
82
|
+
shiply cron set <slug> /api/daily "0 9 * * *"
|
|
83
|
+
shiply logs <slug> # recent worker logs (newest-first); --limit N --since MIN --json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## REST
|
|
87
|
+
|
|
88
|
+
`POST/GET/DELETE /api/v1/sites/{slug}/function` (body: {source, lang?, crons?})
|
|
89
|
+
· `POST/GET /api/v1/sites/{slug}/secrets` {"name","value"}
|
|
90
|
+
· `DELETE /api/v1/sites/{slug}/secrets/{name}`
|
|
91
|
+
· `GET/POST/DELETE /api/v1/sites/{slug}/crons` {"path","schedule"}
|
|
92
|
+
· `GET /api/v1/sites/{slug}/logs?limit&since` (recent runtime logs, newest-first)
|
|
93
|
+
|
|
94
|
+
## Limits + plan
|
|
95
|
+
|
|
96
|
+
- 1 MB compiled script size, 30s CPU per request, V8 isolate runtime (no Node APIs)
|
|
97
|
+
- 50 secrets, 20 cron triggers per site
|
|
98
|
+
- Plan-gated to Developer
|
|
99
|
+
|
|
100
|
+
Docs: https://shiply.now/docs/functions
|