shiply-cli 0.14.2 → 0.14.4
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/README.md +9 -1
- package/dist/claim.js +17 -5
- package/dist/domain.js +35 -0
- package/dist/index.js +11 -2
- package/dist/publish.js +1 -0
- package/package.json +1 -1
- package/skill/SKILL.md +44 -3
package/README.md
CHANGED
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
Publish static sites to [shiply.now](https://shiply.now) from the command line —
|
|
4
4
|
instant web hosting built for agents.
|
|
5
5
|
|
|
6
|
+
> **The npm package is `shiply-cli`, not `shiply`.** A different npm package
|
|
7
|
+
> named `shiply` (an unrelated auto-commit watcher) is published by someone
|
|
8
|
+
> else — installing it does NOT give you the shiply.now CLI. Always use
|
|
9
|
+
> `shiply-cli`. The binary on PATH is still named `shiply` after install;
|
|
10
|
+
> the warning is only about the npm package name.
|
|
11
|
+
|
|
6
12
|
```bash
|
|
7
13
|
npm install -g shiply-cli
|
|
8
|
-
# or
|
|
14
|
+
# or, no install:
|
|
15
|
+
npx -y shiply-cli@latest publish ./dist
|
|
16
|
+
# or, install script:
|
|
9
17
|
curl -fsSL https://shiply.now/install.sh | bash
|
|
10
18
|
```
|
|
11
19
|
|
package/dist/claim.js
CHANGED
|
@@ -13,20 +13,32 @@ export async function runClaimVerify(args) {
|
|
|
13
13
|
message: 'No .shiply.json in current directory. Run from the directory where you published, or rerun `shiply publish`.',
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
if (!mf.
|
|
17
|
-
return { ok: false, message: '.shiply.json is missing
|
|
16
|
+
if (!mf.claimToken) {
|
|
17
|
+
return { ok: false, message: '.shiply.json is missing claimToken.' };
|
|
18
|
+
}
|
|
19
|
+
// Anonymous publishes prior to 0.14.4 didn't write siteId to .shiply.json,
|
|
20
|
+
// and the publish API didn't return it either. The server now accepts EITHER
|
|
21
|
+
// {siteId, claimToken} or {slug, claimToken} — fall back to slug when the
|
|
22
|
+
// local state file came from an older CLI.
|
|
23
|
+
if (!mf.siteId && !mf.slug) {
|
|
24
|
+
return { ok: false, message: '.shiply.json is missing siteId or slug.' };
|
|
18
25
|
}
|
|
19
26
|
if (!/^SHIPLY-[A-Z2-7]{8}$/.test(args.code)) {
|
|
20
27
|
return { ok: false, message: 'Invalid code format. Expected SHIPLY-XXXXXXXX.' };
|
|
21
28
|
}
|
|
29
|
+
const body = { claimToken: mf.claimToken };
|
|
30
|
+
if (mf.siteId)
|
|
31
|
+
body.siteId = mf.siteId;
|
|
32
|
+
else if (mf.slug)
|
|
33
|
+
body.slug = mf.slug;
|
|
22
34
|
const res = await fetch(`${origin}/api/v1/claim/pair/${encodeURIComponent(args.code)}/verify`, {
|
|
23
35
|
method: 'POST',
|
|
24
36
|
headers: { 'content-type': 'application/json' },
|
|
25
|
-
body: JSON.stringify(
|
|
37
|
+
body: JSON.stringify(body),
|
|
26
38
|
});
|
|
27
39
|
if (!res.ok) {
|
|
28
|
-
const
|
|
29
|
-
return { ok: false, message: `verify failed: ${
|
|
40
|
+
const errBody = (await res.json().catch(() => ({})));
|
|
41
|
+
return { ok: false, message: `verify failed: ${errBody.error?.message ?? res.status}` };
|
|
30
42
|
}
|
|
31
43
|
return { ok: true, message: 'Verified — switch back to your browser to finish claiming.' };
|
|
32
44
|
}
|
package/dist/domain.js
CHANGED
|
@@ -75,6 +75,41 @@ export async function domainSubAdd(ctx, hostname, slug) {
|
|
|
75
75
|
console.log(` ${d.type} ${d.host} → ${d.value}`);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
/** Mark a hostname as the primary (canonical) URL for its site. Sibling
|
|
79
|
+
* hostnames (same site) start 301-redirecting to it. Fixes duplicate-content
|
|
80
|
+
* SEO when both apex and www serve the same site. */
|
|
81
|
+
export async function domainPrimary(ctx, domain, hostname) {
|
|
82
|
+
const base = resolveBase(ctx.base);
|
|
83
|
+
// Fetch the list to resolve hostname → id.
|
|
84
|
+
const list = await api(`${base}/api/v1/custom-domains`, { headers: headers(ctx.apiKey) });
|
|
85
|
+
const h = hostname.trim().toLowerCase().replace(/\.$/, '');
|
|
86
|
+
let targetId = null;
|
|
87
|
+
for (const d of list.domains) {
|
|
88
|
+
for (const s of d.subdomains) {
|
|
89
|
+
if (s.hostname === h) {
|
|
90
|
+
targetId = s.id;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (targetId)
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
if (!targetId) {
|
|
98
|
+
throw new Error(`no subdomain mapping for ${h} — add it first with: shiply domain sub add ${h} --site <slug>`);
|
|
99
|
+
}
|
|
100
|
+
const r = await api(`${base}/api/v1/custom-domains/${enc(domain)}/subdomains/${targetId}/primary`, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: headers(ctx.apiKey),
|
|
103
|
+
body: '{}',
|
|
104
|
+
});
|
|
105
|
+
console.log(`✔ ${r.hostname} is now the primary URL`);
|
|
106
|
+
const redirected = r.siblings.filter((s) => !s.isPrimary);
|
|
107
|
+
if (redirected.length) {
|
|
108
|
+
console.log(' 301-redirects to it:');
|
|
109
|
+
for (const s of redirected)
|
|
110
|
+
console.log(` ${s.hostname}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
78
113
|
export async function domainSync(ctx, domain) {
|
|
79
114
|
const base = resolveBase(ctx.base);
|
|
80
115
|
const r = await api(`${base}/api/v1/custom-domains/${enc(domain)}/sync-dns`, { method: 'POST', headers: headers(ctx.apiKey), body: '{}' });
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { resolvePayloadDir } from './framework.js';
|
|
|
10
10
|
import { runClaimVerify } from './claim.js';
|
|
11
11
|
import { dbAttach, dbBranch, dbBranches, dbCreate, dbDelete, dbDeleteBranch, dbLs, dbMerge, dbMigrate, dbSql, } from './db.js';
|
|
12
12
|
import { driveGet, driveLs, drivePublish, drivePut, driveRm } from './drive.js';
|
|
13
|
-
import { domainAdd, domainConnect, domainLs, domainSubAdd, domainSync } from './domain.js';
|
|
13
|
+
import { domainAdd, domainConnect, domainLs, domainPrimary, domainSubAdd, domainSync } from './domain.js';
|
|
14
14
|
import { runProject } from './projects.js';
|
|
15
15
|
import { runListing } from './listings.js';
|
|
16
16
|
import { runSendingDomain } from './sending-domains.js';
|
|
@@ -64,6 +64,7 @@ Usage:
|
|
|
64
64
|
shiply domain connect <domain> One-click OAuth DNS setup (Cloudflare/Domain Connect)
|
|
65
65
|
shiply domain sub add <host> --site <slug> Map a subdomain (e.g. app.example.com) to a slug
|
|
66
66
|
shiply domain sync <domain> Re-sync DNS records for a connected domain
|
|
67
|
+
shiply domain primary <domain> <hostname> Mark hostname as primary; siblings 301-redirect (SEO)
|
|
67
68
|
shiply project <ls|create|get|archive|restore>
|
|
68
69
|
Customer-intake projects + AI brief generator
|
|
69
70
|
shiply project create <label> [--customer-email <e>] [--customer-name <n>]
|
|
@@ -255,6 +256,9 @@ async function main() {
|
|
|
255
256
|
await writeState(publishDir, {
|
|
256
257
|
slug: res.slug,
|
|
257
258
|
siteUrl: res.siteUrl,
|
|
259
|
+
// siteId only present on 0.14.4+ servers — prefer the new response,
|
|
260
|
+
// fall back to a prior state file so re-publishes don't drop it.
|
|
261
|
+
...(res.siteId ? { siteId: res.siteId } : state?.siteId ? { siteId: state.siteId } : {}),
|
|
258
262
|
...(res.claimToken ? { claimToken: res.claimToken } : state?.claimToken && updating ? { claimToken: state.claimToken } : {}),
|
|
259
263
|
owned: !res.anonymous,
|
|
260
264
|
...(state?.databaseId ? { databaseId: state.databaseId } : {}),
|
|
@@ -483,8 +487,13 @@ async function main() {
|
|
|
483
487
|
throw new Error('usage: shiply domain sync <domain>');
|
|
484
488
|
await domainSync(dctx, arg);
|
|
485
489
|
return;
|
|
490
|
+
case 'primary':
|
|
491
|
+
if (!arg || !arg2)
|
|
492
|
+
throw new Error('usage: shiply domain primary <domain> <hostname>');
|
|
493
|
+
await domainPrimary(dctx, arg, arg2);
|
|
494
|
+
return;
|
|
486
495
|
default:
|
|
487
|
-
throw new Error('usage: shiply domain <ls|add|connect|sub|sync>');
|
|
496
|
+
throw new Error('usage: shiply domain <ls|add|connect|sub|sync|primary>');
|
|
488
497
|
}
|
|
489
498
|
}
|
|
490
499
|
case 'db': {
|
package/dist/publish.js
CHANGED
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -5,6 +5,13 @@ description: Publish static sites to the web instantly with shiply.now and manag
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
> **IMPORTANT — npm package name is `shiply-cli`, NOT `shiply`.** A different
|
|
9
|
+
> npm package named `shiply` (an auto-commit watcher) is published by someone
|
|
10
|
+
> else — installing it does NOT give you the shiply.now CLI. Always use
|
|
11
|
+
> `npx -y shiply-cli@latest …` or `npm install -g shiply-cli`. The CLI binary
|
|
12
|
+
> on PATH is still named `shiply` once installed; the warning is only about
|
|
13
|
+
> the npm package name.
|
|
14
|
+
|
|
8
15
|
> **Skill freshness check.** Shiply ships new capabilities weekly. If this
|
|
9
16
|
> file's "Last updated" below is more than 7 days old, re-install it with
|
|
10
17
|
> `npx -y shiply-cli@latest skill --force` OR fetch `https://shiply.now/skill.md`
|
|
@@ -103,8 +110,8 @@ If the `shiply` MCP server is connected (https://shiply.now/mcp), use
|
|
|
103
110
|
call for updates — follow it, and a `shareSuggestion` you can relay to the user.
|
|
104
111
|
Other tools: `site_status`, `list_sites`, `get_site`, `set_handle`,
|
|
105
112
|
`duplicate_site`, `set_variable`, `get_analytics`, `delete_site`, `whoami`,
|
|
106
|
-
`add_custom_domain`, `connect_provider`, `add_subdomain`, `
|
|
107
|
-
`sync_dns`, `list_custom_domains`, `remove_custom_domain`
|
|
113
|
+
`add_custom_domain`, `connect_provider`, `add_subdomain`, `set_primary_subdomain`,
|
|
114
|
+
`check_custom_domain`, `sync_dns`, `list_custom_domains`, `remove_custom_domain`
|
|
108
115
|
(auto-DNS via OAuth provider connections — see **Custom domains** section below).
|
|
109
116
|
|
|
110
117
|
Newer capabilities (use when relevant):
|
|
@@ -122,7 +129,9 @@ Newer capabilities (use when relevant):
|
|
|
122
129
|
|
|
123
130
|
### 2. CLI
|
|
124
131
|
```bash
|
|
125
|
-
npm
|
|
132
|
+
# IMPORTANT: the npm package is `shiply-cli`, not `shiply` (different package).
|
|
133
|
+
npm i -g shiply-cli # or: npx -y shiply-cli@latest <command>
|
|
134
|
+
# or: curl -fsSL https://shiply.now/install.sh | bash
|
|
126
135
|
shiply publish ./dir # live URL + confetti
|
|
127
136
|
shiply publish ./dir # run AGAIN after edits → updates the SAME site
|
|
128
137
|
shiply status <slug> --wait # SSL + readiness; prints SSL_READY / SITE_READY
|
|
@@ -198,6 +207,7 @@ shiply status www.example.com --wait # polls until SSL_READY + SITE_READY; exi
|
|
|
198
207
|
| `add_custom_domain` | Add a root domain and detect the provider |
|
|
199
208
|
| `connect_provider` | Start OAuth → returns `{ url }` (show to user to authorize) |
|
|
200
209
|
| `add_subdomain` | Map a subdomain to a site slug |
|
|
210
|
+
| `set_primary_subdomain` | Pick the canonical URL; siblings 301-redirect (SEO fix) |
|
|
201
211
|
| `check_custom_domain` | Live CNAME + SSL + readiness probe |
|
|
202
212
|
| `sync_dns` | Re-push DNS records to a connected provider |
|
|
203
213
|
| `list_custom_domains` | List all domains and their subdomain mappings |
|
|
@@ -210,6 +220,7 @@ GET /api/v1/custom-domains list domains
|
|
|
210
220
|
POST /api/v1/custom-domains add domain { domain }
|
|
211
221
|
DELETE /api/v1/custom-domains/{domain} remove domain
|
|
212
222
|
POST /api/v1/custom-domains/{domain}/subdomains add subdomain { subdomain, slug }
|
|
223
|
+
POST /api/v1/custom-domains/{domain}/subdomains/{id}/primary mark hostname canonical (siblings 301)
|
|
213
224
|
POST /api/v1/custom-domains/{domain}/connect start OAuth → { url }
|
|
214
225
|
POST /api/v1/custom-domains/{domain}/sync-dns re-sync DNS
|
|
215
226
|
POST /api/v1/custom-domains/{domain}/check live status
|
|
@@ -423,6 +434,36 @@ shiply cron set <slug> /api/daily "0 9 * * *"
|
|
|
423
434
|
|
|
424
435
|
Docs: https://shiply.now/docs/functions
|
|
425
436
|
|
|
437
|
+
## Authentication — bring your own Clerk (or Auth.js / Lucia / Supabase Auth)
|
|
438
|
+
|
|
439
|
+
shiply does **not** host customer auth. If the user's app needs sign-in, you
|
|
440
|
+
(the agent) set up their auth provider yourself — Clerk is the fastest path.
|
|
441
|
+
|
|
442
|
+
**One-time human step:** ask the user to create a Clerk app at
|
|
443
|
+
`dashboard.clerk.com` (~2 min). They paste you the publishable key (`pk_…`) and
|
|
444
|
+
secret key (`sk_…`).
|
|
445
|
+
|
|
446
|
+
**Wire it:**
|
|
447
|
+
\`\`\`bash
|
|
448
|
+
shiply secret set <slug> CLERK_SECRET_KEY sk_live_xxxxxxxxxx
|
|
449
|
+
# The publishable key can be embedded in the JS build — it's safe in the browser.
|
|
450
|
+
\`\`\`
|
|
451
|
+
|
|
452
|
+
**In `worker.ts`:** verify the session JWT against `env.CLERK_SECRET_KEY` on
|
|
453
|
+
every API request, scope D1 rows by `payload.sub` (the Clerk user id).
|
|
454
|
+
|
|
455
|
+
**In the SPA:** add `@clerk/clerk-react` to the build, wrap with `<ClerkProvider>`,
|
|
456
|
+
attach `Authorization: Bearer <token>` to every authenticated fetch.
|
|
457
|
+
|
|
458
|
+
Why not auto-integrate? shiply's own Clerk instance signs in **agents** to the
|
|
459
|
+
shiply dashboard — it's not multi-tenant for someone else's app's users.
|
|
460
|
+
Customers owning their own Clerk is the only correct shape.
|
|
461
|
+
|
|
462
|
+
Same pattern works for Auth.js, Lucia, Supabase Auth, Firebase Auth — store the
|
|
463
|
+
provider secret via `shiply secret set`, verify the JWT in the worker.
|
|
464
|
+
|
|
465
|
+
Docs: https://shiply.now/docs/auth
|
|
466
|
+
|
|
426
467
|
## Projects — customer intake + AI brief
|
|
427
468
|
|
|
428
469
|
When a dev needs to capture a real client brief, they spin up a **project**:
|