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 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.siteId || !mf.claimToken) {
17
- return { ok: false, message: '.shiply.json is missing siteId or claimToken.' };
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({ siteId: mf.siteId, claimToken: mf.claimToken }),
37
+ body: JSON.stringify(body),
26
38
  });
27
39
  if (!res.ok) {
28
- const body = (await res.json().catch(() => ({})));
29
- return { ok: false, message: `verify failed: ${body.error?.message ?? res.status}` };
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
@@ -51,6 +51,7 @@ export async function publish(dir, opts = {}) {
51
51
  body: JSON.stringify({ versionId: created.upload.versionId }),
52
52
  });
53
53
  return {
54
+ siteId: created.siteId,
54
55
  slug: created.slug,
55
56
  siteUrl: created.siteUrl,
56
57
  claimToken: created.claimToken,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shiply-cli",
3
- "version": "0.14.2",
3
+ "version": "0.14.4",
4
4
  "description": "Publish static sites to shiply.now from the command line — instant web hosting for agents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
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`, `check_custom_domain`,
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 i -g shiply-cli # or: curl -fsSL https://shiply.now/install.sh | bash
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**: