shiply-cli 0.4.1 → 0.5.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.
@@ -0,0 +1,52 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const exists = async (p) => stat(p).then((s) => s.isFile() || s.isDirectory(), () => false);
4
+ /** Detect a framework *source* directory (package.json, no root index.html)
5
+ * so `shiply publish .` on a React/Vue/etc project can do the right thing:
6
+ * publish the build output with SPA mode instead of raw source files. */
7
+ export async function detectFramework(dir) {
8
+ if (await exists(join(dir, 'index.html'))) {
9
+ // Vite keeps index.html at the root of the *source* too — only treat it as
10
+ // a source dir when a vite config is present alongside it.
11
+ const viteConfig = (await Promise.all(['vite.config.ts', 'vite.config.js', 'vite.config.mjs'].map((f) => exists(join(dir, f))))).some(Boolean);
12
+ if (!viteConfig)
13
+ return null;
14
+ }
15
+ let pkgRaw;
16
+ try {
17
+ pkgRaw = await readFile(join(dir, 'package.json'), 'utf8');
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ let deps;
23
+ try {
24
+ const pkg = JSON.parse(pkgRaw);
25
+ deps = { ...pkg.dependencies, ...pkg.devDependencies };
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ const has = (name) => name in deps;
31
+ if (has('next')) {
32
+ return { framework: 'Next.js', outputDir: 'out', spa: false, buildCommand: 'npm run build (needs `output: "export"` in next.config)' };
33
+ }
34
+ if (has('astro'))
35
+ return { framework: 'Astro', outputDir: 'dist', spa: false, buildCommand: 'npm run build' };
36
+ if (has('@sveltejs/kit')) {
37
+ return { framework: 'SvelteKit', outputDir: 'build', spa: true, buildCommand: 'npm run build (use adapter-static)' };
38
+ }
39
+ if (has('vite'))
40
+ return { framework: 'Vite', outputDir: 'dist', spa: true, buildCommand: 'npm run build' };
41
+ if (has('react-scripts'))
42
+ return { framework: 'Create React App', outputDir: 'build', spa: true, buildCommand: 'npm run build' };
43
+ if (has('react') || has('vue') || has('svelte')) {
44
+ return { framework: 'JavaScript app', outputDir: 'dist', spa: true, buildCommand: 'npm run build' };
45
+ }
46
+ return null;
47
+ }
48
+ /** If the detected output dir exists and has an index.html, return it. */
49
+ export async function findBuildOutput(dir, hint) {
50
+ const out = join(dir, hint.outputDir);
51
+ return (await exists(join(out, 'index.html'))) ? out : null;
52
+ }
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createInterface } from 'node:readline/promises';
3
+ import { join } from 'node:path';
3
4
  import { parseArgs } from 'node:util';
4
5
  import { confetti } from './confetti.js';
6
+ import { detectFramework, findBuildOutput } from './framework.js';
5
7
  import { loadApiKey, saveApiKey } from './config.js';
6
8
  import { api, DEFAULT_BASE, publish, resolveBase } from './publish.js';
7
9
  import { installSkill } from './skill.js';
@@ -89,8 +91,29 @@ async function main() {
89
91
  if (!dir)
90
92
  throw new Error(`usage: shiply ${cmd} <dir>`);
91
93
  const apiKey = values.anonymous ? undefined : (values.key ?? (await loadApiKey()));
94
+ // Full React/Vue/etc apps work great — but you publish the BUILD OUTPUT,
95
+ // not the source. Detect source dirs and do the right thing.
96
+ let publishDir = dir;
97
+ let spa = values.spa;
98
+ const hint = await detectFramework(dir);
99
+ if (hint) {
100
+ const out = await findBuildOutput(dir, hint);
101
+ if (out) {
102
+ publishDir = out;
103
+ if (hint.spa && spa === undefined)
104
+ spa = true;
105
+ console.log(`✔ ${hint.framework} project detected — publishing ${out}${hint.spa ? ' with SPA mode' : ''}`);
106
+ }
107
+ else {
108
+ throw new Error(`this looks like a ${hint.framework} project (source code, not a built site).\n` +
109
+ ` Build it first, then publish the output:\n` +
110
+ ` ${hint.buildCommand}\n` +
111
+ ` shiply ${cmd} ${join(dir, hint.outputDir)}${hint.spa ? ' --spa' : ''}\n` +
112
+ ` (or pass the built folder directly if it lives elsewhere)`);
113
+ }
114
+ }
92
115
  // same command, same URL: reuse this directory's site automatically
93
- const state = await readState(dir);
116
+ const state = await readState(publishDir);
94
117
  let claimToken = values['claim-token'] ?? state?.claimToken;
95
118
  let slug = state?.owned && apiKey ? state.slug : undefined;
96
119
  if (values['new-site']) {
@@ -101,14 +124,14 @@ async function main() {
101
124
  throw new Error('nothing to update here — publish first (shiply remembers the site in .shiply.json), or pass --claim-token');
102
125
  }
103
126
  const updating = Boolean(claimToken || slug);
104
- const res = await publish(dir, {
127
+ const res = await publish(publishDir, {
105
128
  apiKey,
106
129
  base: values.base,
107
- spaMode: values.spa,
130
+ spaMode: spa,
108
131
  claimToken,
109
132
  slug,
110
133
  });
111
- await writeState(dir, {
134
+ await writeState(publishDir, {
112
135
  slug: res.slug,
113
136
  siteUrl: res.siteUrl,
114
137
  ...(res.claimToken ? { claimToken: res.claimToken } : state?.claimToken && updating ? { claimToken: state.claimToken } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shiply-cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.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
@@ -1,75 +1,88 @@
1
- ---
2
- name: shiply
3
- description: Publish static sites to the web instantly with shiply.now and manage them (updates, SSL checks, custom domains, variables). Use when the user asks to publish, host, deploy, share, or update a website/page/demo/report, or wants a live URL for generated files. Triggers - "publish this", "host this", "put this online", "give me a link", "update the site", shiply.
4
- ---
5
-
6
- # shiply — instant web hosting for agents
7
-
8
- shiply.now puts files on the web in seconds. No account needed to start:
9
- anonymous sites are live immediately, last 24 hours, and can be claimed into
10
- an account to keep them. The cardinal rule:
11
-
12
- **NEVER create a new site to update an existing one. Always re-publish to the
13
- same site** — otherwise you litter subdomains and lose the user's URL.
14
-
15
- ## Pick your interface (best first)
16
-
17
- ### 1. MCP (native tools)
18
- If the `shiply` MCP server is connected (https://shiply.now/mcp), use
19
- `publish_site`. Every result includes a `toUpdate` field telling you the exact
20
- call for updates — follow it. Other tools: `site_status`, `list_sites`,
21
- `set_handle`, `set_variable`, `add_domain`, `check_domain`, `get_analytics`,
22
- `delete_site`, `rollback` via dashboard.
23
-
24
- ### 2. CLI
25
- ```bash
26
- npm i -g shiply-cli # or: curl -fsSL https://shiply.now/install.sh | bash
27
- shiply publish ./dir # live URL + confetti
28
- shiply publish ./dir # run AGAIN after edits → updates the SAME site
29
- shiply status <slug> --wait # SSL + readiness; prints SSL_READY / SITE_READY
30
- shiply login # email code → API key → sites become permanent
31
- ```
32
- The CLI stores each directory's site in `.shiply.json` (slug + update token),
33
- so repeat publishes reuse the URL automatically. `--new-site` opts out.
34
- Gitignore `.shiply.json` in public repos. Parse `SITE_READY` / `SSL_READY`
35
- lines for automation; exit code 0 = ready.
36
-
37
- ### 3. Raw HTTP (no installs)
38
- ```
39
- 1. POST https://shiply.now/api/v1/publish
40
- {"files":[{"path":"index.html","size":<bytes>,"contentType":"text/html","hash":"<sha256, optional>"}]}
41
- (+ "Authorization: Bearer shp_…" for permanent owned sites)
42
- 2. PUT each file's bytes to response upload.uploads[].url
43
- 3. POST upload.finalizeUrl with {"versionId":"..."}
44
- ```
45
- **Updates:** anonymous → include `"claimToken":"..."` (returned ONCE by the
46
- first publish — save it); owned → include `"slug":"..."`. Hashes make updates
47
- cheap: unchanged files are skipped server-side.
48
-
49
- ## The lifecycle to explain to users
50
- - Anonymous site: live instantly, expires in 24 h. Give the user the
51
- `claimUrl` — claiming keeps it forever on a free account.
52
- - API key (`shiply login` or POST /api/auth/agent/request-code →
53
- verify-code): publishes are permanent and manageable.
54
- - Paid plans add vanity handles (<name>.shiply.now), more custom domains,
55
- storage, analytics: https://shiply.now/dashboard/plan
56
-
57
- ## Power features (Bearer key)
58
- - **Custom domains**: POST /api/v1/domains {"hostname","slug"} → tell the
59
- user to CNAME the hostname to `cname.shiply.now` → cert auto-issues; poll
60
- GET /api/v1/domains/{id}/check until `ready:true`.
61
- - **SSL/readiness checker**: `shiply status <slug-or-domain> --wait` or the
62
- check endpoint — confirms certificate + serving before telling the user
63
- it's done.
64
- - **Variables**: encrypted per-user KV for API keys the user's sites need
65
- (GET/PUT /api/v1/variables, DELETE /api/v1/variables/{NAME}); Supabase can
66
- be connected from the dashboard and lands here as SUPABASE_URL +
67
- SUPABASE_ANON_KEY.
68
- - **Rollback**: POST /api/v1/publish/{slug}/rollback {"versionId"} flips any
69
- finalized version live instantly.
70
- - **SPA**: pass `"spaMode":true` so deep links serve index.html.
71
-
72
- ## Limits & references
73
- ≤1000 files/site (≤50 inline via MCP), ≤100 MiB/file, 1 GiB total.
74
- Machine guide: https://shiply.now/llms.txt · OpenAPI:
75
- https://shiply.now/openapi.json · Docs: https://shiply.now/docs
1
+ ---
2
+ name: shiply
3
+ description: Publish static sites to the web instantly with shiply.now and manage them (updates, SSL checks, custom domains, variables). Use when the user asks to publish, host, deploy, share, or update a website/page/demo/report, or wants a live URL for generated files. Triggers - "publish this", "host this", "put this online", "give me a link", "update the site", shiply.
4
+ ---
5
+
6
+ # shiply — instant web hosting for agents
7
+
8
+ shiply.now puts files on the web in seconds. No account needed to start:
9
+ anonymous sites are live immediately, last 24 hours, and can be claimed into
10
+ an account to keep them. The cardinal rule:
11
+
12
+ **NEVER create a new site to update an existing one. Always re-publish to the
13
+ same site** — otherwise you litter subdomains and lose the user's URL.
14
+
15
+ ## Pick your interface (best first)
16
+
17
+ ### 1. MCP (native tools)
18
+ If the `shiply` MCP server is connected (https://shiply.now/mcp), use
19
+ `publish_site`. Every result includes a `toUpdate` field telling you the exact
20
+ call for updates — follow it. Other tools: `site_status`, `list_sites`,
21
+ `set_handle`, `set_variable`, `add_domain`, `check_domain`, `get_analytics`,
22
+ `delete_site`, `rollback` via dashboard.
23
+
24
+ ### 2. CLI
25
+ ```bash
26
+ npm i -g shiply-cli # or: curl -fsSL https://shiply.now/install.sh | bash
27
+ shiply publish ./dir # live URL + confetti
28
+ shiply publish ./dir # run AGAIN after edits → updates the SAME site
29
+ shiply status <slug> --wait # SSL + readiness; prints SSL_READY / SITE_READY
30
+ shiply login # email code → API key → sites become permanent
31
+ ```
32
+ The CLI stores each directory's site in `.shiply.json` (slug + update token),
33
+ so repeat publishes reuse the URL automatically. `--new-site` opts out.
34
+ Gitignore `.shiply.json` in public repos. Parse `SITE_READY` / `SSL_READY`
35
+ lines for automation; exit code 0 = ready.
36
+
37
+ ### 3. Raw HTTP (no installs)
38
+ ```
39
+ 1. POST https://shiply.now/api/v1/publish
40
+ {"files":[{"path":"index.html","size":<bytes>,"contentType":"text/html","hash":"<sha256, optional>"}]}
41
+ (+ "Authorization: Bearer shp_…" for permanent owned sites)
42
+ 2. PUT each file's bytes to response upload.uploads[].url
43
+ 3. POST upload.finalizeUrl with {"versionId":"..."}
44
+ ```
45
+ **Updates:** anonymous → include `"claimToken":"..."` (returned ONCE by the
46
+ first publish — save it); owned → include `"slug":"..."`. Hashes make updates
47
+ cheap: unchanged files are skipped server-side.
48
+
49
+ ## The lifecycle to explain to users
50
+ - Anonymous site: live instantly, expires in 24 h. Give the user the
51
+ `claimUrl` — claiming keeps it forever on a free account.
52
+ - API key (`shiply login` or POST /api/auth/agent/request-code →
53
+ verify-code): publishes are permanent and manageable.
54
+ - Paid plans add vanity handles (<name>.shiply.now), more custom domains,
55
+ storage, analytics: https://shiply.now/dashboard/plan
56
+
57
+ ## Power features (Bearer key)
58
+ - **Custom domains**: POST /api/v1/domains {"hostname","slug"} → tell the
59
+ user to CNAME the hostname to `cname.shiply.now` → cert auto-issues; poll
60
+ GET /api/v1/domains/{id}/check until `ready:true`.
61
+ - **SSL/readiness checker**: `shiply status <slug-or-domain> --wait` or the
62
+ check endpoint — confirms certificate + serving before telling the user
63
+ it's done.
64
+ - **Variables**: encrypted per-user KV for API keys the user's sites need
65
+ (GET/PUT /api/v1/variables, DELETE /api/v1/variables/{NAME}); Supabase can
66
+ be connected from the dashboard and lands here as SUPABASE_URL +
67
+ SUPABASE_ANON_KEY.
68
+ - **Rollback**: POST /api/v1/publish/{slug}/rollback {"versionId"} flips any
69
+ finalized version live instantly.
70
+ - **SPA**: pass `"spaMode":true` so deep links serve index.html.
71
+
72
+ ## Full React / Vue / Svelte / Astro apps — YES, they work
73
+ shiply hosts complete framework apps. Publish the BUILD OUTPUT, never the
74
+ source: run the build, then publish the output dir with SPA mode for
75
+ client-routed apps. The CLI auto-detects source dirs and tells you the
76
+ right command.
77
+ - Vite/React/Vue/Svelte: `npm run build` then `shiply publish dist --spa`
78
+ - Create React App: `npm run build` then `shiply publish build --spa`
79
+ - Next.js: set `output: "export"` in next.config, `npm run build`, then
80
+ `shiply publish out` (no --spa; export emits real HTML per route)
81
+ - Astro/static generators: `shiply publish dist`
82
+ Use the CLI (not inline MCP files) for builds — hashed bundles often exceed
83
+ the 50-file inline cap, and the CLI hash-skips unchanged chunks on updates.
84
+
85
+ ## Limits & references
86
+ ≤1000 files/site (≤50 inline via MCP), ≤100 MiB/file, 1 GiB total.
87
+ Machine guide: https://shiply.now/llms.txt · OpenAPI:
88
+ https://shiply.now/openapi.json · Docs: https://shiply.now/docs