shiply-cli 0.14.2 → 0.14.3

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/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>]
@@ -483,8 +484,13 @@ async function main() {
483
484
  throw new Error('usage: shiply domain sync <domain>');
484
485
  await domainSync(dctx, arg);
485
486
  return;
487
+ case 'primary':
488
+ if (!arg || !arg2)
489
+ throw new Error('usage: shiply domain primary <domain> <hostname>');
490
+ await domainPrimary(dctx, arg, arg2);
491
+ return;
486
492
  default:
487
- throw new Error('usage: shiply domain <ls|add|connect|sub|sync>');
493
+ throw new Error('usage: shiply domain <ls|add|connect|sub|sync|primary>');
488
494
  }
489
495
  }
490
496
  case 'db': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shiply-cli",
3
- "version": "0.14.2",
3
+ "version": "0.14.3",
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
@@ -103,8 +103,8 @@ If the `shiply` MCP server is connected (https://shiply.now/mcp), use
103
103
  call for updates — follow it, and a `shareSuggestion` you can relay to the user.
104
104
  Other tools: `site_status`, `list_sites`, `get_site`, `set_handle`,
105
105
  `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`
106
+ `add_custom_domain`, `connect_provider`, `add_subdomain`, `set_primary_subdomain`,
107
+ `check_custom_domain`, `sync_dns`, `list_custom_domains`, `remove_custom_domain`
108
108
  (auto-DNS via OAuth provider connections — see **Custom domains** section below).
109
109
 
110
110
  Newer capabilities (use when relevant):
@@ -198,6 +198,7 @@ shiply status www.example.com --wait # polls until SSL_READY + SITE_READY; exi
198
198
  | `add_custom_domain` | Add a root domain and detect the provider |
199
199
  | `connect_provider` | Start OAuth → returns `{ url }` (show to user to authorize) |
200
200
  | `add_subdomain` | Map a subdomain to a site slug |
201
+ | `set_primary_subdomain` | Pick the canonical URL; siblings 301-redirect (SEO fix) |
201
202
  | `check_custom_domain` | Live CNAME + SSL + readiness probe |
202
203
  | `sync_dns` | Re-push DNS records to a connected provider |
203
204
  | `list_custom_domains` | List all domains and their subdomain mappings |
@@ -210,6 +211,7 @@ GET /api/v1/custom-domains list domains
210
211
  POST /api/v1/custom-domains add domain { domain }
211
212
  DELETE /api/v1/custom-domains/{domain} remove domain
212
213
  POST /api/v1/custom-domains/{domain}/subdomains add subdomain { subdomain, slug }
214
+ POST /api/v1/custom-domains/{domain}/subdomains/{id}/primary mark hostname canonical (siblings 301)
213
215
  POST /api/v1/custom-domains/{domain}/connect start OAuth → { url }
214
216
  POST /api/v1/custom-domains/{domain}/sync-dns re-sync DNS
215
217
  POST /api/v1/custom-domains/{domain}/check live status
@@ -423,6 +425,36 @@ shiply cron set <slug> /api/daily "0 9 * * *"
423
425
 
424
426
  Docs: https://shiply.now/docs/functions
425
427
 
428
+ ## Authentication — bring your own Clerk (or Auth.js / Lucia / Supabase Auth)
429
+
430
+ shiply does **not** host customer auth. If the user's app needs sign-in, you
431
+ (the agent) set up their auth provider yourself — Clerk is the fastest path.
432
+
433
+ **One-time human step:** ask the user to create a Clerk app at
434
+ `dashboard.clerk.com` (~2 min). They paste you the publishable key (`pk_…`) and
435
+ secret key (`sk_…`).
436
+
437
+ **Wire it:**
438
+ \`\`\`bash
439
+ shiply secret set <slug> CLERK_SECRET_KEY sk_live_xxxxxxxxxx
440
+ # The publishable key can be embedded in the JS build — it's safe in the browser.
441
+ \`\`\`
442
+
443
+ **In `worker.ts`:** verify the session JWT against `env.CLERK_SECRET_KEY` on
444
+ every API request, scope D1 rows by `payload.sub` (the Clerk user id).
445
+
446
+ **In the SPA:** add `@clerk/clerk-react` to the build, wrap with `<ClerkProvider>`,
447
+ attach `Authorization: Bearer <token>` to every authenticated fetch.
448
+
449
+ Why not auto-integrate? shiply's own Clerk instance signs in **agents** to the
450
+ shiply dashboard — it's not multi-tenant for someone else's app's users.
451
+ Customers owning their own Clerk is the only correct shape.
452
+
453
+ Same pattern works for Auth.js, Lucia, Supabase Auth, Firebase Auth — store the
454
+ provider secret via `shiply secret set`, verify the JWT in the worker.
455
+
456
+ Docs: https://shiply.now/docs/auth
457
+
426
458
  ## Projects — customer intake + AI brief
427
459
 
428
460
  When a dev needs to capture a real client brief, they spin up a **project**: