shsu 0.0.1 → 0.0.5
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/.github/workflows/publish.yml +5 -4
- package/README.md +68 -0
- package/bin/shsu.mjs +241 -0
- package/package.json +2 -2
- package/.claude/settings.local.json +0 -7
|
@@ -8,12 +8,13 @@ on:
|
|
|
8
8
|
jobs:
|
|
9
9
|
publish:
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
id-token: write
|
|
11
14
|
steps:
|
|
12
15
|
- uses: actions/checkout@v4
|
|
13
16
|
- uses: actions/setup-node@v4
|
|
14
17
|
with:
|
|
15
|
-
node-version: '
|
|
18
|
+
node-version: '24'
|
|
16
19
|
registry-url: 'https://registry.npmjs.org'
|
|
17
|
-
- run: npm publish
|
|
18
|
-
env:
|
|
19
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
20
|
+
- run: npm publish --provenance --access public
|
package/README.md
CHANGED
|
@@ -86,6 +86,74 @@ Config is read from `package.json` "shsu" key. Environment variables override pa
|
|
|
86
86
|
| `url` / `SHSU_URL` | For `invoke` | Supabase URL |
|
|
87
87
|
| `localPath` / `SHSU_LOCAL_PATH` | No | Local functions path (default: `./supabase/functions`) |
|
|
88
88
|
|
|
89
|
+
## MCP Server
|
|
90
|
+
|
|
91
|
+
shsu can run as an MCP server for AI assistants.
|
|
92
|
+
|
|
93
|
+
### Claude Code
|
|
94
|
+
|
|
95
|
+
Add to `.mcp.json` in your project root:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"mcpServers": {
|
|
100
|
+
"shsu": {
|
|
101
|
+
"command": "npx",
|
|
102
|
+
"args": ["shsu", "mcp"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Claude Desktop
|
|
109
|
+
|
|
110
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"mcpServers": {
|
|
115
|
+
"shsu": {
|
|
116
|
+
"command": "npx",
|
|
117
|
+
"args": ["shsu", "mcp"],
|
|
118
|
+
"cwd": "/path/to/your/project"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Cursor
|
|
125
|
+
|
|
126
|
+
Add to `.cursor/mcp.json` in your project:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"mcpServers": {
|
|
131
|
+
"shsu": {
|
|
132
|
+
"command": "npx",
|
|
133
|
+
"args": ["shsu", "mcp"]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Available Tools
|
|
140
|
+
|
|
141
|
+
- `deploy` - Deploy edge functions
|
|
142
|
+
- `list` - List local and remote functions
|
|
143
|
+
- `invoke` - Invoke a function
|
|
144
|
+
- `restart` - Restart edge-runtime
|
|
145
|
+
- `new` - Create new function from template
|
|
146
|
+
- `config` - Show current configuration
|
|
147
|
+
|
|
148
|
+
## Releasing
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm version patch # or minor/major
|
|
152
|
+
git push --follow-tags
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
GitHub Actions will automatically publish to npm when the tag is pushed.
|
|
156
|
+
|
|
89
157
|
## License
|
|
90
158
|
|
|
91
159
|
MIT
|
package/bin/shsu.mjs
CHANGED
|
@@ -308,6 +308,242 @@ async function cmdInit() {
|
|
|
308
308
|
success('Added shsu config to package.json');
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
// ─────────────────────────────────────────────────────────────
|
|
312
|
+
// MCP Server
|
|
313
|
+
// ─────────────────────────────────────────────────────────────
|
|
314
|
+
async function cmdMcp() {
|
|
315
|
+
const tools = [
|
|
316
|
+
{
|
|
317
|
+
name: 'deploy',
|
|
318
|
+
description: 'Deploy edge function(s) to the server. Syncs via rsync and restarts edge-runtime.',
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: 'object',
|
|
321
|
+
properties: {
|
|
322
|
+
name: { type: 'string', description: 'Function name to deploy. If omitted, deploys all functions.' },
|
|
323
|
+
noRestart: { type: 'boolean', description: 'Skip restarting edge-runtime after deploy.' },
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: 'list',
|
|
329
|
+
description: 'List edge functions (both local and remote).',
|
|
330
|
+
inputSchema: { type: 'object', properties: {} },
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: 'invoke',
|
|
334
|
+
description: 'Invoke an edge function with optional JSON data.',
|
|
335
|
+
inputSchema: {
|
|
336
|
+
type: 'object',
|
|
337
|
+
properties: {
|
|
338
|
+
name: { type: 'string', description: 'Function name to invoke.' },
|
|
339
|
+
data: { type: 'string', description: 'JSON data to send (default: {}).' },
|
|
340
|
+
},
|
|
341
|
+
required: ['name'],
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: 'restart',
|
|
346
|
+
description: 'Restart the edge-runtime container.',
|
|
347
|
+
inputSchema: { type: 'object', properties: {} },
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: 'new',
|
|
351
|
+
description: 'Create a new edge function from template.',
|
|
352
|
+
inputSchema: {
|
|
353
|
+
type: 'object',
|
|
354
|
+
properties: {
|
|
355
|
+
name: { type: 'string', description: 'Name for the new function.' },
|
|
356
|
+
},
|
|
357
|
+
required: ['name'],
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: 'config',
|
|
362
|
+
description: 'Get current shsu configuration.',
|
|
363
|
+
inputSchema: { type: 'object', properties: {} },
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
const serverInfo = {
|
|
368
|
+
name: 'shsu',
|
|
369
|
+
version: '0.0.1',
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Helper to write JSON-RPC response
|
|
373
|
+
const respond = (id, result) => {
|
|
374
|
+
const response = { jsonrpc: '2.0', id, result };
|
|
375
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const respondError = (id, code, message) => {
|
|
379
|
+
const response = { jsonrpc: '2.0', id, error: { code, message } };
|
|
380
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// Capture output helper
|
|
384
|
+
const captureExec = (cmd) => {
|
|
385
|
+
try {
|
|
386
|
+
return execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
387
|
+
} catch (e) {
|
|
388
|
+
return e.message;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Tool handlers
|
|
393
|
+
const handleTool = async (name, args = {}) => {
|
|
394
|
+
switch (name) {
|
|
395
|
+
case 'deploy': {
|
|
396
|
+
if (!config.server || !config.remotePath) {
|
|
397
|
+
return { content: [{ type: 'text', text: 'Error: server and remotePath must be configured. Run "shsu init" first.' }] };
|
|
398
|
+
}
|
|
399
|
+
const funcName = args.name;
|
|
400
|
+
const noRestart = args.noRestart || false;
|
|
401
|
+
let output = '';
|
|
402
|
+
|
|
403
|
+
if (!funcName) {
|
|
404
|
+
output = captureExec(`rsync -avz --delete --exclude='*.test.ts' --exclude='*.spec.ts' "${config.localPath}/" "${config.server}:${config.remotePath}/"`);
|
|
405
|
+
} else {
|
|
406
|
+
const funcPath = join(config.localPath, funcName);
|
|
407
|
+
if (!existsSync(funcPath)) {
|
|
408
|
+
return { content: [{ type: 'text', text: `Error: Function not found: ${funcPath}` }] };
|
|
409
|
+
}
|
|
410
|
+
output = captureExec(`rsync -avz "${funcPath}/" "${config.server}:${config.remotePath}/${funcName}/"`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (!noRestart) {
|
|
414
|
+
output += '\n' + captureExec(`ssh ${config.server} "docker restart \\$(docker ps -q --filter 'name=edge')"`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return { content: [{ type: 'text', text: `Deployed${funcName ? ` ${funcName}` : ' all functions'}${noRestart ? ' (no restart)' : ''}\n\n${output}` }] };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
case 'list': {
|
|
421
|
+
if (!config.server || !config.remotePath) {
|
|
422
|
+
return { content: [{ type: 'text', text: 'Error: server and remotePath must be configured.' }] };
|
|
423
|
+
}
|
|
424
|
+
const remote = captureExec(`ssh ${config.server} "ls -1 ${config.remotePath} 2>/dev/null"`) || '(none)';
|
|
425
|
+
let local = '(none)';
|
|
426
|
+
if (existsSync(config.localPath)) {
|
|
427
|
+
const dirs = readdirSync(config.localPath, { withFileTypes: true })
|
|
428
|
+
.filter((d) => d.isDirectory())
|
|
429
|
+
.map((d) => d.name);
|
|
430
|
+
local = dirs.length ? dirs.join('\n') : '(none)';
|
|
431
|
+
}
|
|
432
|
+
return { content: [{ type: 'text', text: `Remote functions:\n${remote}\n\nLocal functions:\n${local}` }] };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
case 'invoke': {
|
|
436
|
+
if (!config.url) {
|
|
437
|
+
return { content: [{ type: 'text', text: 'Error: url must be configured for invoke.' }] };
|
|
438
|
+
}
|
|
439
|
+
if (!args.name) {
|
|
440
|
+
return { content: [{ type: 'text', text: 'Error: function name is required.' }] };
|
|
441
|
+
}
|
|
442
|
+
const data = args.data || '{}';
|
|
443
|
+
const output = captureExec(`curl -s -X POST "${config.url}/functions/v1/${args.name}" -H "Content-Type: application/json" -d '${data}'`);
|
|
444
|
+
return { content: [{ type: 'text', text: output }] };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
case 'restart': {
|
|
448
|
+
if (!config.server) {
|
|
449
|
+
return { content: [{ type: 'text', text: 'Error: server must be configured.' }] };
|
|
450
|
+
}
|
|
451
|
+
const output = captureExec(`ssh ${config.server} "docker restart \\$(docker ps -q --filter 'name=edge')"`);
|
|
452
|
+
return { content: [{ type: 'text', text: `Restarted edge-runtime\n\n${output}` }] };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
case 'new': {
|
|
456
|
+
if (!args.name) {
|
|
457
|
+
return { content: [{ type: 'text', text: 'Error: function name is required.' }] };
|
|
458
|
+
}
|
|
459
|
+
const funcPath = join(config.localPath, args.name);
|
|
460
|
+
if (existsSync(funcPath)) {
|
|
461
|
+
return { content: [{ type: 'text', text: `Error: Function already exists: ${args.name}` }] };
|
|
462
|
+
}
|
|
463
|
+
mkdirSync(funcPath, { recursive: true });
|
|
464
|
+
writeFileSync(
|
|
465
|
+
join(funcPath, 'index.ts'),
|
|
466
|
+
`Deno.serve(async (req) => {
|
|
467
|
+
try {
|
|
468
|
+
const { name } = await req.json()
|
|
469
|
+
|
|
470
|
+
return new Response(
|
|
471
|
+
JSON.stringify({ message: \`Hello \${name}!\` }),
|
|
472
|
+
{ headers: { "Content-Type": "application/json" } }
|
|
473
|
+
)
|
|
474
|
+
} catch (error) {
|
|
475
|
+
return new Response(
|
|
476
|
+
JSON.stringify({ error: error.message }),
|
|
477
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
})
|
|
481
|
+
`
|
|
482
|
+
);
|
|
483
|
+
return { content: [{ type: 'text', text: `Created ${funcPath}/index.ts` }] };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
case 'config': {
|
|
487
|
+
return {
|
|
488
|
+
content: [{
|
|
489
|
+
type: 'text',
|
|
490
|
+
text: `Current configuration:\n server: ${config.server || '(not set)'}\n remotePath: ${config.remotePath || '(not set)'}\n url: ${config.url || '(not set)'}\n localPath: ${config.localPath}`,
|
|
491
|
+
}],
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
default:
|
|
496
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// Process incoming messages
|
|
501
|
+
const rl = createInterface({ input: process.stdin });
|
|
502
|
+
|
|
503
|
+
for await (const line of rl) {
|
|
504
|
+
let msg;
|
|
505
|
+
try {
|
|
506
|
+
msg = JSON.parse(line);
|
|
507
|
+
} catch {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const { id, method, params } = msg;
|
|
512
|
+
|
|
513
|
+
switch (method) {
|
|
514
|
+
case 'initialize':
|
|
515
|
+
respond(id, {
|
|
516
|
+
protocolVersion: '2024-11-05',
|
|
517
|
+
capabilities: { tools: {} },
|
|
518
|
+
serverInfo,
|
|
519
|
+
});
|
|
520
|
+
break;
|
|
521
|
+
|
|
522
|
+
case 'notifications/initialized':
|
|
523
|
+
// No response needed for notifications
|
|
524
|
+
break;
|
|
525
|
+
|
|
526
|
+
case 'tools/list':
|
|
527
|
+
respond(id, { tools });
|
|
528
|
+
break;
|
|
529
|
+
|
|
530
|
+
case 'tools/call':
|
|
531
|
+
try {
|
|
532
|
+
const result = await handleTool(params.name, params.arguments || {});
|
|
533
|
+
respond(id, result);
|
|
534
|
+
} catch (e) {
|
|
535
|
+
respond(id, { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true });
|
|
536
|
+
}
|
|
537
|
+
break;
|
|
538
|
+
|
|
539
|
+
default:
|
|
540
|
+
if (id !== undefined) {
|
|
541
|
+
respondError(id, -32601, `Method not found: ${method}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
311
547
|
function cmdHelp() {
|
|
312
548
|
console.log(`
|
|
313
549
|
${c.blue('shsu')} - Self-Hosted Supabase Utilities
|
|
@@ -336,6 +572,8 @@ ${c.yellow('Commands:')}
|
|
|
336
572
|
|
|
337
573
|
env Show current configuration
|
|
338
574
|
|
|
575
|
+
mcp Start MCP server (for AI assistants)
|
|
576
|
+
|
|
339
577
|
${c.yellow('Examples:')}
|
|
340
578
|
shsu init
|
|
341
579
|
shsu deploy
|
|
@@ -386,6 +624,9 @@ async function main() {
|
|
|
386
624
|
case 'init':
|
|
387
625
|
await cmdInit();
|
|
388
626
|
break;
|
|
627
|
+
case 'mcp':
|
|
628
|
+
await cmdMcp();
|
|
629
|
+
break;
|
|
389
630
|
case 'env':
|
|
390
631
|
case 'config':
|
|
391
632
|
cmdEnv();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shsu",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "CLI for deploying and managing Supabase Edge Functions on self-hosted Supabase (Coolify, Docker Compose). Sync functions via rsync, stream logs, and invoke endpoints.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -31,4 +31,4 @@
|
|
|
31
31
|
"url": "https://github.com/YUZU-Hub/shsu/issues"
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/YUZU-Hub/shsu#readme"
|
|
34
|
-
}
|
|
34
|
+
}
|