shiply-cli 0.6.0 → 0.7.1
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/drive.js +77 -0
- package/dist/index.js +37 -0
- package/package.json +1 -1
- package/skill/SKILL.md +17 -3
package/dist/drive.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
import { api, resolveBase } from './publish.js';
|
|
5
|
+
const sha256 = (b) => createHash('sha256').update(b).digest('hex');
|
|
6
|
+
const headers = (apiKey) => ({ 'content-type': 'application/json', authorization: `Bearer ${apiKey}` });
|
|
7
|
+
async function defaultDrive(ctx) {
|
|
8
|
+
const base = resolveBase(ctx.base);
|
|
9
|
+
const d = await api(`${base}/api/v1/drives/default`, { headers: headers(ctx.apiKey) });
|
|
10
|
+
return d.publicId;
|
|
11
|
+
}
|
|
12
|
+
export async function driveLs(ctx, prefix = '') {
|
|
13
|
+
const base = resolveBase(ctx.base);
|
|
14
|
+
const id = await defaultDrive(ctx);
|
|
15
|
+
const out = await api(`${base}/api/v1/drives/${id}/files?prefix=${encodeURIComponent(prefix)}&limit=200`, { headers: headers(ctx.apiKey) });
|
|
16
|
+
if (out.files.length === 0) {
|
|
17
|
+
console.log(' (empty)');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (const f of out.files)
|
|
21
|
+
console.log(` ${f.path} ${Math.ceil(f.size / 1024)}KB`);
|
|
22
|
+
}
|
|
23
|
+
export async function drivePut(ctx, file, asPath) {
|
|
24
|
+
const base = resolveBase(ctx.base);
|
|
25
|
+
const id = await defaultDrive(ctx);
|
|
26
|
+
const body = await readFile(file);
|
|
27
|
+
const path = asPath ?? basename(file);
|
|
28
|
+
const hash = sha256(body);
|
|
29
|
+
const meta = { path, size: body.length, contentType: 'application/octet-stream', hash };
|
|
30
|
+
const stage = await api(`${base}/api/v1/drives/${id}/files/uploads`, { method: 'POST', headers: headers(ctx.apiKey), body: JSON.stringify({ files: [meta] }) });
|
|
31
|
+
for (const u of stage.uploads) {
|
|
32
|
+
const res = await fetch(u.url, { method: 'PUT', body: new Uint8Array(body) });
|
|
33
|
+
if (!res.ok)
|
|
34
|
+
throw new Error(`upload failed for ${u.path}`);
|
|
35
|
+
}
|
|
36
|
+
await api(`${base}/api/v1/drives/${id}/files/finalize`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: headers(ctx.apiKey),
|
|
39
|
+
body: JSON.stringify({ files: [meta] }),
|
|
40
|
+
});
|
|
41
|
+
console.log(`✔ put ${path}`);
|
|
42
|
+
}
|
|
43
|
+
export async function driveGet(ctx, path, out) {
|
|
44
|
+
const base = resolveBase(ctx.base);
|
|
45
|
+
const id = await defaultDrive(ctx);
|
|
46
|
+
const { url } = await api(`${base}/api/v1/drives/${id}/files/${path.split('/').map(encodeURIComponent).join('/')}`, { headers: headers(ctx.apiKey) });
|
|
47
|
+
const res = await fetch(url);
|
|
48
|
+
if (!res.ok)
|
|
49
|
+
throw new Error(`download failed (${res.status})`);
|
|
50
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
51
|
+
if (out) {
|
|
52
|
+
await writeFile(out, buf);
|
|
53
|
+
console.log(`✔ saved ${out}`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
process.stdout.write(buf);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export async function driveRm(ctx, path) {
|
|
60
|
+
const base = resolveBase(ctx.base);
|
|
61
|
+
const id = await defaultDrive(ctx);
|
|
62
|
+
await api(`${base}/api/v1/drives/${id}/files/${path.split('/').map(encodeURIComponent).join('/')}`, {
|
|
63
|
+
method: 'DELETE',
|
|
64
|
+
headers: headers(ctx.apiKey),
|
|
65
|
+
});
|
|
66
|
+
console.log(`✔ removed ${path}`);
|
|
67
|
+
}
|
|
68
|
+
export async function drivePublish(ctx, prefix) {
|
|
69
|
+
const base = resolveBase(ctx.base);
|
|
70
|
+
const id = await defaultDrive(ctx);
|
|
71
|
+
const out = await api(`${base}/api/v1/publish/from-drive`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: headers(ctx.apiKey),
|
|
74
|
+
body: JSON.stringify({ driveId: id, ...(prefix ? { prefix } : {}) }),
|
|
75
|
+
});
|
|
76
|
+
console.log(`✔ published drive (${out.filesCount} files)\n\n ${out.siteUrl}\n`);
|
|
77
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
5
|
import { confetti } from './confetti.js';
|
|
6
6
|
import { detectFramework, findBuildOutput } from './framework.js';
|
|
7
|
+
import { driveGet, driveLs, drivePublish, drivePut, driveRm } from './drive.js';
|
|
7
8
|
import { loadApiKey, saveApiKey } from './config.js';
|
|
8
9
|
import { api, DEFAULT_BASE, publish, resolveBase } from './publish.js';
|
|
9
10
|
import { installSkill } from './skill.js';
|
|
@@ -17,6 +18,7 @@ Usage:
|
|
|
17
18
|
shiply update <dir> Same as publish when .shiply.json exists
|
|
18
19
|
shiply status <slug-or-domain> [--wait] SSL + readiness check (agent-friendly output)
|
|
19
20
|
shiply duplicate <slug> Server-side copy of an owned site → new URL
|
|
21
|
+
shiply drive <ls|put|get|rm|publish> Private cloud storage (drive publish → live site)
|
|
20
22
|
shiply skill [--project] Install the shiply skill for your AI agent
|
|
21
23
|
(global ~/.claude/skills, or ./.claude/skills with --project)
|
|
22
24
|
shiply login [--email <address>] Email a 6-digit code, mint + save an API key
|
|
@@ -77,6 +79,7 @@ async function main() {
|
|
|
77
79
|
'new-site': { type: 'boolean' },
|
|
78
80
|
project: { type: 'boolean' },
|
|
79
81
|
timeout: { type: 'string' },
|
|
82
|
+
out: { type: 'string', short: 'o' },
|
|
80
83
|
'no-confetti': { type: 'boolean' },
|
|
81
84
|
help: { type: 'boolean', short: 'h' },
|
|
82
85
|
},
|
|
@@ -209,6 +212,40 @@ async function main() {
|
|
|
209
212
|
console.log(`\n ${out.siteUrl}\n`);
|
|
210
213
|
return;
|
|
211
214
|
}
|
|
215
|
+
case 'drive': {
|
|
216
|
+
const apiKey = values.key ?? (await loadApiKey());
|
|
217
|
+
if (!apiKey)
|
|
218
|
+
throw new Error('drive commands need an API key — run `shiply login` first');
|
|
219
|
+
const dctx = { base: values.base, apiKey };
|
|
220
|
+
const sub = dir; // positionals[1]
|
|
221
|
+
const arg = positionals[2];
|
|
222
|
+
const arg2 = positionals[3];
|
|
223
|
+
switch (sub) {
|
|
224
|
+
case 'ls':
|
|
225
|
+
await driveLs(dctx, arg);
|
|
226
|
+
return;
|
|
227
|
+
case 'put':
|
|
228
|
+
if (!arg)
|
|
229
|
+
throw new Error('usage: shiply drive put <file> [as-path]');
|
|
230
|
+
await drivePut(dctx, arg, arg2);
|
|
231
|
+
return;
|
|
232
|
+
case 'get':
|
|
233
|
+
if (!arg)
|
|
234
|
+
throw new Error('usage: shiply drive get <path> [out-file] (omit out-file to print to stdout)');
|
|
235
|
+
await driveGet(dctx, arg, values.out ?? arg2);
|
|
236
|
+
return;
|
|
237
|
+
case 'rm':
|
|
238
|
+
if (!arg)
|
|
239
|
+
throw new Error('usage: shiply drive rm <path>');
|
|
240
|
+
await driveRm(dctx, arg);
|
|
241
|
+
return;
|
|
242
|
+
case 'publish':
|
|
243
|
+
await drivePublish(dctx, arg);
|
|
244
|
+
return;
|
|
245
|
+
default:
|
|
246
|
+
throw new Error('usage: shiply drive <ls|put|get|rm|publish>');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
212
249
|
case 'skill': {
|
|
213
250
|
const written = await installSkill(Boolean(values.project));
|
|
214
251
|
for (const w of written)
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -17,9 +17,23 @@ same site** — otherwise you litter subdomains and lose the user's URL.
|
|
|
17
17
|
### 1. MCP (native tools)
|
|
18
18
|
If the `shiply` MCP server is connected (https://shiply.now/mcp), use
|
|
19
19
|
`publish_site`. Every result includes a `toUpdate` field telling you the exact
|
|
20
|
-
call for updates — follow it
|
|
21
|
-
|
|
22
|
-
`
|
|
20
|
+
call for updates — follow it, and a `shareSuggestion` you can relay to the user.
|
|
21
|
+
Other tools: `site_status`, `list_sites`, `get_site`, `set_handle`,
|
|
22
|
+
`duplicate_site`, `set_variable`, `add_domain`/`list_domains`/`check_domain`/
|
|
23
|
+
`remove_domain`, `get_analytics`, `delete_site`.
|
|
24
|
+
|
|
25
|
+
Newer capabilities (use when relevant):
|
|
26
|
+
- `set_site_access` — password / invite-only protect a site (paid).
|
|
27
|
+
- `set_link` — mount another owned **public** site at a path on a host
|
|
28
|
+
(host/docs → target).
|
|
29
|
+
- `set_profile` / `feature_site` — stand up the user's public portfolio at
|
|
30
|
+
<handle>.shiply.now and feature a public site on shiply.now/explore.
|
|
31
|
+
- **Drives** (private cloud storage for files/notes/context): `list_drives`,
|
|
32
|
+
`create_drive`, `drive_put_file` (driveId "default", utf8/base64, ≤2 MB),
|
|
33
|
+
`drive_list_files`, `drive_delete_file`, `publish_from_drive` (snapshot a
|
|
34
|
+
drive into a live site). Great for agent memory and assets you don't want on
|
|
35
|
+
a public site.
|
|
36
|
+
- `export_account` — JSON bundle of the user's data (no secrets).
|
|
23
37
|
|
|
24
38
|
### 2. CLI
|
|
25
39
|
```bash
|