shiply-cli 0.5.0 → 0.6.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/index.js CHANGED
@@ -16,6 +16,7 @@ Usage:
16
16
  Re-running UPDATES the same site (state in .shiply.json)
17
17
  shiply update <dir> Same as publish when .shiply.json exists
18
18
  shiply status <slug-or-domain> [--wait] SSL + readiness check (agent-friendly output)
19
+ shiply duplicate <slug> Server-side copy of an owned site → new URL
19
20
  shiply skill [--project] Install the shiply skill for your AI agent
20
21
  (global ~/.claude/skills, or ./.claude/skills with --project)
21
22
  shiply login [--email <address>] Email a 6-digit code, mint + save an API key
@@ -192,6 +193,22 @@ async function main() {
192
193
  await new Promise((r) => setTimeout(r, 5000));
193
194
  }
194
195
  }
196
+ case 'duplicate': {
197
+ if (!dir)
198
+ throw new Error('usage: shiply duplicate <slug>');
199
+ const apiKey = values.key ?? (await loadApiKey());
200
+ if (!apiKey)
201
+ throw new Error('duplicate needs an API key — run `shiply login` first');
202
+ const base = resolveBase(values.base);
203
+ const out = await api(`${base}/api/v1/publishes/${dir}/duplicate`, {
204
+ method: 'POST',
205
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}` },
206
+ body: JSON.stringify({}),
207
+ });
208
+ console.log(`✔ duplicated ${dir} → ${out.slug} (${out.filesCount} files)`);
209
+ console.log(`\n ${out.siteUrl}\n`);
210
+ return;
211
+ }
195
212
  case 'skill': {
196
213
  const written = await installSkill(Boolean(values.project));
197
214
  for (const w of written)
package/dist/manifest.js CHANGED
@@ -36,7 +36,8 @@ export const contentTypeFor = (path) => MIME[extname(path).toLowerCase()] ?? 'ap
36
36
  const SKIP_DIRS = new Set(['node_modules']);
37
37
  /** Walk a directory into the publish manifest: posix-relative paths, byte
38
38
  * sizes, sha256 hex hashes (server hash-skips unchanged files on update).
39
- * Dot entries and node_modules are never published. */
39
+ * Dot entries and node_modules are never published — except the .shiply/
40
+ * config directory (proxy.json, data.json), which the server consumes. */
40
41
  export async function buildManifest(dir) {
41
42
  const out = [];
42
43
  await walk(dir, '', out);
@@ -45,6 +46,10 @@ export async function buildManifest(dir) {
45
46
  async function walk(abs, rel, out) {
46
47
  const entries = await readdir(abs, { withFileTypes: true });
47
48
  for (const e of entries) {
49
+ if (e.name === '.shiply' && e.isDirectory() && rel === '') {
50
+ await walk(join(abs, e.name), '.shiply', out);
51
+ continue;
52
+ }
48
53
  if (e.name.startsWith('.') || SKIP_DIRS.has(e.name))
49
54
  continue;
50
55
  const childAbs = join(abs, e.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shiply-cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
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
@@ -69,6 +69,33 @@ cheap: unchanged files are skipped server-side.
69
69
  finalized version live instantly.
70
70
  - **SPA**: pass `"spaMode":true` so deep links serve index.html.
71
71
 
72
+ ## Proxy routes — AI/API calls WITHOUT exposing keys
73
+ NEVER embed API keys in published HTML/JS. Instead: store the key as a
74
+ Variable (PUT /api/v1/variables), then include `.shiply/proxy.json` in the
75
+ published files declaring routes, e.g. {"proxies":{"/api/chat":{"upstream":
76
+ "https://openrouter.ai/api/v1/chat/completions","method":"POST","headers":
77
+ {"Authorization":"Bearer ${OPENROUTER_API_KEY}"}}}}. The page then calls the
78
+ relative path (fetch('/api/chat')) and shiply injects the secret server-side.
79
+ Prefix routes "/api/db/*" forward subpaths (great for Supabase REST).
80
+ Owned sites only; https public upstreams only. Docs: /docs/proxy-routes
81
+
82
+ ## Site Data — forms/waitlists/guestbooks with ZERO backend
83
+ When the user wants to collect anything from visitors (waitlist, contact
84
+ form, RSVP, guestbook): include `.shiply/data.json` in the published files,
85
+ e.g. {"collections":{"signups":{"fields":{"email":{"type":"email",
86
+ "required":true}},"access":{"read":"owner","insert":"public"}}}}, then have
87
+ the page POST JSON to the relative path /.shiply/data/signups. Records are
88
+ validated server-side; the owner reads them in the dashboard (Data section,
89
+ CSV export) or GET /api/v1/publishes/<slug>/data/<collection> with a Bearer
90
+ key. Owned sites only. Docs: /docs/site-data
91
+
92
+ ## Make a site private (paid plans)
93
+ To password-protect or restrict a site: PATCH /api/v1/publishes/<slug>/access
94
+ with {"mode":"password","password":"..."} or {"mode":"restricted",
95
+ "allowedEmails":[...],"allowedDomains":[...]}, or set mode "public" to open it.
96
+ MCP tool set_site_access does the same. Enforced before any content is served;
97
+ changing settings signs current visitors out. Docs: /docs/access-control
98
+
72
99
  ## Full React / Vue / Svelte / Astro apps — YES, they work
73
100
  shiply hosts complete framework apps. Publish the BUILD OUTPUT, never the
74
101
  source: run the build, then publish the output dir with SPA mode for