shiply-cli 0.20.2 → 0.22.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/bundle.js +4 -2
- package/dist/clients.js +48 -0
- package/dist/functions.js +17 -6
- package/dist/index.js +23 -1
- package/dist/publish.js +1 -0
- package/package.json +42 -42
- package/skill/SKILL.md +16 -1
package/dist/bundle.js
CHANGED
|
@@ -18,8 +18,10 @@ const WORKER_EXTERNALS = [
|
|
|
18
18
|
...builtinModules.flatMap((m) => [m, `${m}/*`]),
|
|
19
19
|
];
|
|
20
20
|
/** esbuild-bundle a worker entry into one self-contained ESM module, optionally
|
|
21
|
-
* wrapping it (named-fetch → default, and/or asset-first) via a synthetic entry.
|
|
22
|
-
|
|
21
|
+
* wrapping it (named-fetch → default, and/or asset-first) via a synthetic entry.
|
|
22
|
+
* Also used by `function deploy` to compile worker.ts → JS and inline npm deps
|
|
23
|
+
* (e.g. `@neondatabase/serverless`) before upload. */
|
|
24
|
+
export async function bundleWorker(entryAbs, opts = {}) {
|
|
23
25
|
const common = {
|
|
24
26
|
bundle: true,
|
|
25
27
|
format: 'esm',
|
package/dist/clients.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { api, resolveBase } from './publish.js';
|
|
2
|
+
import { loadApiKey } from './config.js';
|
|
3
|
+
const hdr = (apiKey) => ({
|
|
4
|
+
'content-type': 'application/json',
|
|
5
|
+
...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),
|
|
6
|
+
});
|
|
7
|
+
/** Turn a `--client "<name-or-email>"` flag into the API's client object.
|
|
8
|
+
* An '@' means email (create-or-find by it); otherwise a display name. */
|
|
9
|
+
export function parseClientArg(value) {
|
|
10
|
+
const v = value?.trim();
|
|
11
|
+
if (!v)
|
|
12
|
+
return undefined;
|
|
13
|
+
return v.includes('@') ? { clientEmail: v } : { clientName: v };
|
|
14
|
+
}
|
|
15
|
+
/** `shiply client ls | show <id>` — read-only view of your clients. */
|
|
16
|
+
export async function runClient(args, opts) {
|
|
17
|
+
const sub = args[0] ?? 'ls';
|
|
18
|
+
const base = resolveBase(opts.base);
|
|
19
|
+
const apiKey = opts.key ?? (await loadApiKey());
|
|
20
|
+
if (!apiKey)
|
|
21
|
+
throw new Error('`shiply client` needs an API key — run `shiply login` or pass --key');
|
|
22
|
+
if (sub === 'ls') {
|
|
23
|
+
const { clients } = await api(`${base}/api/v1/clients`, { headers: hdr(apiKey) });
|
|
24
|
+
if (clients.length === 0) {
|
|
25
|
+
console.log(' no clients yet — pass --client on publish or create an intake to add one');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const c of clients) {
|
|
29
|
+
const meta = [c.company, c.email].filter(Boolean).join(' · ');
|
|
30
|
+
console.log(` ${c.id} ${c.name}${meta ? ` (${meta})` : ''} [${c.status}]`);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (sub === 'show') {
|
|
35
|
+
const id = args[1];
|
|
36
|
+
if (!id)
|
|
37
|
+
throw new Error('usage: shiply client show <id>');
|
|
38
|
+
const r = await api(`${base}/api/v1/clients/${encodeURIComponent(id)}`, { headers: hdr(apiKey) });
|
|
39
|
+
console.log(`${r.client.name}${r.client.company ? ` · ${r.client.company}` : ''}`);
|
|
40
|
+
if (r.client.email)
|
|
41
|
+
console.log(` ${r.client.email}`);
|
|
42
|
+
console.log(` sites (${r.sites.length}): ${r.sites.map((s) => s.slug).join(', ') || '—'}`);
|
|
43
|
+
console.log(` folders (${r.drives.length}): ${r.drives.map((d) => d.name).join(', ') || '—'}`);
|
|
44
|
+
console.log(` projects (${r.projects.length}): ${r.projects.map((p) => `${p.label} [${p.status}]`).join(', ') || '—'}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`unknown client command '${sub}' — try: ls, show <id>`);
|
|
48
|
+
}
|
package/dist/functions.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* `parseArgs` in index.ts doesn't have to know about each one. */
|
|
6
6
|
import { readFile } from 'node:fs/promises';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
|
+
import { bundleWorker } from './bundle.js';
|
|
8
9
|
import { loadApiKey } from './config.js';
|
|
9
10
|
import { api, resolveBase } from './publish.js';
|
|
10
11
|
const headers = (apiKey) => ({
|
|
@@ -96,19 +97,29 @@ async function deployCmd(argv, inherited) {
|
|
|
96
97
|
}
|
|
97
98
|
const apiKey = await getApiKey(flags, 'function');
|
|
98
99
|
const base = resolveBase(flags.base);
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
100
|
+
const filename = flags.ts ? 'worker.ts' : 'worker.js';
|
|
101
|
+
const entryAbs = join(process.cwd(), filename);
|
|
102
|
+
// Pre-read for a friendly "file not found" message (esbuild's is cryptic).
|
|
102
103
|
try {
|
|
103
|
-
|
|
104
|
+
await readFile(entryAbs, 'utf8');
|
|
104
105
|
}
|
|
105
106
|
catch (e) {
|
|
106
107
|
throw new Error(`couldn't read ${filename} from ${process.cwd()} — ${e.message}\n hint: cd into the directory containing your ${filename}, or pass --ts if the file is worker.ts`);
|
|
107
108
|
}
|
|
109
|
+
// Compile (TS→JS) + bundle npm deps client-side via esbuild so imports like
|
|
110
|
+
// `@neondatabase/serverless` work; cloudflare:*/node:* stay external. We then
|
|
111
|
+
// upload plain JS — the platform can't run esbuild on workerd.
|
|
112
|
+
let source;
|
|
113
|
+
try {
|
|
114
|
+
source = await bundleWorker(entryAbs);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
throw new Error(`couldn't bundle ${filename}: ${e.message}`);
|
|
118
|
+
}
|
|
108
119
|
const row = await api(`${base}/api/v1/sites/${slug}/function`, {
|
|
109
120
|
method: 'POST',
|
|
110
121
|
headers: headers(apiKey),
|
|
111
|
-
body: JSON.stringify({ source, lang }),
|
|
122
|
+
body: JSON.stringify({ source, lang: 'js' }),
|
|
112
123
|
});
|
|
113
124
|
if (flags.json) {
|
|
114
125
|
console.log(JSON.stringify(row, null, 2));
|
|
@@ -117,7 +128,7 @@ async function deployCmd(argv, inherited) {
|
|
|
117
128
|
console.log(`✔ deployed worker for ${slug}`);
|
|
118
129
|
console.log(` script: ${row.cfScriptName}`);
|
|
119
130
|
console.log(` version: ${row.version.slice(0, 12)}`);
|
|
120
|
-
console.log(` source: ${filename} (
|
|
131
|
+
console.log(` source: ${filename} (bundled → js)`);
|
|
121
132
|
console.log(` routes: requests to https://${slug}.shiply.now/* now hit your worker first`);
|
|
122
133
|
}
|
|
123
134
|
async function getFnCmd(argv, inherited) {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { createInterface } from 'node:readline/promises';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -16,6 +16,7 @@ import { dbAttach, dbBranch, dbBranches, dbCreate, dbDelete, dbDeleteBranch, dbL
|
|
|
16
16
|
import { driveGet, driveLs, drivePublish, drivePut, driveRm } from './drive.js';
|
|
17
17
|
import { domainAdd, domainConnect, domainLs, domainPrimary, domainSubAdd, domainSync } from './domain.js';
|
|
18
18
|
import { runProject } from './projects.js';
|
|
19
|
+
import { runClient, parseClientArg } from './clients.js';
|
|
19
20
|
import { runListing } from './listings.js';
|
|
20
21
|
import { runSendingDomain } from './sending-domains.js';
|
|
21
22
|
import { contractAmend, contractDraft, contractList, contractPdf, contractRetract, contractSend, contractShow, } from './contract.js';
|
|
@@ -82,6 +83,9 @@ Usage:
|
|
|
82
83
|
Customer-intake projects + AI brief generator
|
|
83
84
|
shiply project create <label> [--customer-email <e>] [--customer-name <n>]
|
|
84
85
|
Returns the intake URL to share with the customer
|
|
86
|
+
shiply client <ls|show <id>> Your clients (freelancer view): customer-360 roll-up
|
|
87
|
+
shiply publish <dir> --client "<name|email>"
|
|
88
|
+
Group the site under a client (create-or-finds by email)
|
|
85
89
|
shiply listing <ls|create|rm> Marketplace listings (sell built sites)
|
|
86
90
|
shiply listing create <site-slug> --price <cents> --jurisdiction "<region>"
|
|
87
91
|
Requires Stripe Connect onboarding (see MCP get_connect_status)
|
|
@@ -257,6 +261,7 @@ async function main() {
|
|
|
257
261
|
'no-notify': { type: 'boolean' },
|
|
258
262
|
'notify-to': { type: 'string' },
|
|
259
263
|
from: { type: 'string' },
|
|
264
|
+
client: { type: 'string' },
|
|
260
265
|
help: { type: 'boolean', short: 'h' },
|
|
261
266
|
version: { type: 'boolean', short: 'v' },
|
|
262
267
|
},
|
|
@@ -313,6 +318,17 @@ async function main() {
|
|
|
313
318
|
if (resolved.hint && publishDir !== dir && !bareJsonFlag) {
|
|
314
319
|
console.log(`✔ ${resolved.hint.framework} project detected — publishing ${publishDir}${resolved.hint.spa ? ' with SPA mode' : ''}`);
|
|
315
320
|
}
|
|
321
|
+
// A bare worker.{ts,js} at the publish root ships as a STATIC asset here
|
|
322
|
+
// (downloadable source, never runs). Warn — it deploys separately as a
|
|
323
|
+
// per-site Worker via `shiply function deploy`.
|
|
324
|
+
if (!bareJsonFlag) {
|
|
325
|
+
for (const wf of ['worker.ts', 'worker.js']) {
|
|
326
|
+
if (existsSync(join(publishDir, wf))) {
|
|
327
|
+
console.log(`⚠ ${wf} will be uploaded as a STATIC file (its source is downloadable) and will NOT run as a worker.\n` +
|
|
328
|
+
` Deploy it as a per-site Worker instead: shiply function deploy <slug>${wf.endsWith('.ts') ? ' --ts' : ''}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
316
332
|
}
|
|
317
333
|
try {
|
|
318
334
|
// same command, same URL: reuse this directory's site automatically
|
|
@@ -347,6 +363,8 @@ async function main() {
|
|
|
347
363
|
slug,
|
|
348
364
|
attachDatabaseId,
|
|
349
365
|
previewBranchDbId,
|
|
366
|
+
// Group under a client (owned publishes only). --client "<name|email>"
|
|
367
|
+
client: apiKey ? parseClientArg(values.client) : undefined,
|
|
350
368
|
});
|
|
351
369
|
await writeState(stateDir, {
|
|
352
370
|
slug: res.slug,
|
|
@@ -734,6 +752,10 @@ async function main() {
|
|
|
734
752
|
await runProject(positionals.slice(1));
|
|
735
753
|
break;
|
|
736
754
|
}
|
|
755
|
+
case 'client': {
|
|
756
|
+
await runClient(positionals.slice(1), { base: values.base, key: values.key });
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
737
759
|
case 'listing': {
|
|
738
760
|
await runListing(positionals.slice(1));
|
|
739
761
|
break;
|
package/dist/publish.js
CHANGED
|
@@ -42,6 +42,7 @@ export async function publish(dir, opts = {}) {
|
|
|
42
42
|
...(opts.slug ? { slug: opts.slug } : {}),
|
|
43
43
|
...(opts.attachDatabaseId ? { attachDatabaseId: opts.attachDatabaseId } : {}),
|
|
44
44
|
...(opts.previewBranchDbId ? { previewBranchDbId: opts.previewBranchDbId } : {}),
|
|
45
|
+
...(opts.client ? { client: opts.client } : {}),
|
|
45
46
|
}),
|
|
46
47
|
});
|
|
47
48
|
await uploadAll(dir, created.upload.uploads);
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "shiply-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Publish static sites to shiply.now from the command line
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"shiply": "dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"skill",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc -p tsconfig.build.json",
|
|
17
|
-
"prepublishOnly": "pnpm build",
|
|
18
|
-
"test": "vitest run",
|
|
19
|
-
"typecheck": "tsc --noEmit"
|
|
20
|
-
},
|
|
21
|
-
"engines": {
|
|
22
|
-
"node": ">=18"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"shiply",
|
|
26
|
-
"hosting",
|
|
27
|
-
"static",
|
|
28
|
-
"deploy",
|
|
29
|
-
"agents",
|
|
30
|
-
"publish"
|
|
31
|
-
],
|
|
32
|
-
"homepage": "https://shiply.now",
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@types/node": "^22.15.0",
|
|
35
|
-
"typescript": "^5.8.0",
|
|
36
|
-
"vitest": "^3.1.0"
|
|
37
|
-
},
|
|
38
|
-
"dependencies": {
|
|
39
|
-
"esbuild": "^0.25.12",
|
|
40
|
-
"smol-toml": "^1.7.0"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "shiply-cli",
|
|
3
|
+
"version": "0.22.0",
|
|
4
|
+
"description": "Publish static sites to shiply.now from the command line \u00e2\u20ac\u201d instant web hosting for agents.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"shiply": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"skill",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.build.json",
|
|
17
|
+
"prepublishOnly": "pnpm build",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"shiply",
|
|
26
|
+
"hosting",
|
|
27
|
+
"static",
|
|
28
|
+
"deploy",
|
|
29
|
+
"agents",
|
|
30
|
+
"publish"
|
|
31
|
+
],
|
|
32
|
+
"homepage": "https://shiply.now",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.15.0",
|
|
35
|
+
"typescript": "^5.8.0",
|
|
36
|
+
"vitest": "^3.1.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"esbuild": "^0.25.12",
|
|
40
|
+
"smol-toml": "^1.7.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/skill/SKILL.md
CHANGED
|
@@ -18,7 +18,7 @@ description: "shiply is the production backend for anything an agent builds —
|
|
|
18
18
|
> fresh — your cached copy may be missing features like databases, projects,
|
|
19
19
|
> marketplace, or sending domains.
|
|
20
20
|
>
|
|
21
|
-
> **Last updated: 2026-06-
|
|
21
|
+
> **Last updated: 2026-06-28**
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -658,6 +658,21 @@ CLI: `shiply project ls · shiply project create <label> [--customer-email <e>]
|
|
|
658
658
|
|
|
659
659
|
Docs: https://shiply.now/docs/projects
|
|
660
660
|
|
|
661
|
+
**Group work by client (optional, for freelancers).** Building sites for
|
|
662
|
+
clients? Pass an optional `client` (a name or email) on any `publish_site`,
|
|
663
|
+
`create_drive`, or `create_project` call — shiply create-or-finds the client
|
|
664
|
+
by email and files everything you ship for them (sites, drop-box, intake,
|
|
665
|
+
contracts) under one customer view in the dashboard. A project's customer **is**
|
|
666
|
+
the site's client (same email-keyed identity), so `create_project` already
|
|
667
|
+
links automatically whenever you pass `customerEmail` — no extra step. There is
|
|
668
|
+
no `create_client` tool and never a CRM step: the client record self-assembles
|
|
669
|
+
from the calls you already make. `publish_site` echoes the `resolvedClientId` —
|
|
670
|
+
reuse it across the session so drive/intake/contract all land under the same
|
|
671
|
+
client. List calls take an optional `clientId` filter
|
|
672
|
+
(`list_sites`/`list_drives`/`list_projects`). Omit `client` for personal
|
|
673
|
+
projects; shiply works identically. CLI: `--client "<name-or-email>"` on
|
|
674
|
+
publish/drive/project, plus `shiply client ls|show`.
|
|
675
|
+
|
|
661
676
|
## Contracts — draft, sign, amend (extends Projects)
|
|
662
677
|
|
|
663
678
|
Once a project reaches `brief_ready`, the dev drafts a signed contract from
|