shsu 0.0.1 → 0.0.6

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.
@@ -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: '20'
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  **S**elf-**H**osted **S**upabase **U**tilities
4
4
 
5
- Deploy and manage Supabase Edge Functions on Coolify-hosted Supabase.
5
+ Deploy and manage Supabase Edge Functions and migrations on Coolify-hosted Supabase.
6
6
 
7
7
  ## Install
8
8
 
@@ -27,17 +27,22 @@ This adds config to your `package.json`:
27
27
  "shsu": {
28
28
  "server": "root@your-coolify-server",
29
29
  "remotePath": "/data/coolify/services/YOUR_SERVICE_ID/volumes/functions",
30
- "url": "https://your-supabase.example.com"
30
+ "url": "https://your-supabase.example.com",
31
+ "edgeContainer": "edge",
32
+ "dbContainer": "postgres"
31
33
  }
32
34
  }
33
35
  ```
34
36
 
35
- Find your `remotePath` by running on your server:
37
+ ### Finding Configuration Values
36
38
 
39
+ **remotePath** - SSH to your server and run:
37
40
  ```bash
38
41
  docker inspect $(docker ps -q --filter 'name=edge') | grep -A 5 "Mounts"
39
42
  ```
40
43
 
44
+ **Container names** - SSH to your server and run `docker ps` to list containers. Coolify names containers using the pattern `<service>-<uuid>` (e.g., `abc123-supabase-edge-functions-1`). The filter does substring matching, so `edge` matches any container with "edge" in its name.
45
+
41
46
  ## Usage
42
47
 
43
48
  ```bash
@@ -56,6 +61,9 @@ shsu deploy hello-world
56
61
  # Deploy without restarting edge-runtime
57
62
  shsu deploy hello-world --no-restart
58
63
 
64
+ # Run database migrations
65
+ shsu migrate
66
+
59
67
  # Stream logs
60
68
  shsu logs
61
69
 
@@ -75,6 +83,21 @@ shsu new my-function
75
83
  shsu restart
76
84
  ```
77
85
 
86
+ ## Migrations
87
+
88
+ Place SQL files in `./supabase/migrations/` (or your configured `migrationsPath`). Files are sorted alphabetically and executed in order.
89
+
90
+ ```
91
+ supabase/migrations/
92
+ 001_create_users.sql
93
+ 002_add_email_index.sql
94
+ ```
95
+
96
+ Run with:
97
+ ```bash
98
+ shsu migrate
99
+ ```
100
+
78
101
  ## Configuration
79
102
 
80
103
  Config is read from `package.json` "shsu" key. Environment variables override package.json values.
@@ -85,6 +108,78 @@ Config is read from `package.json` "shsu" key. Environment variables override pa
85
108
  | `remotePath` / `SHSU_REMOTE_PATH` | Yes | Remote path to functions directory |
86
109
  | `url` / `SHSU_URL` | For `invoke` | Supabase URL |
87
110
  | `localPath` / `SHSU_LOCAL_PATH` | No | Local functions path (default: `./supabase/functions`) |
111
+ | `migrationsPath` / `SHSU_MIGRATIONS_PATH` | No | Local migrations path (default: `./supabase/migrations`) |
112
+ | `edgeContainer` / `SHSU_EDGE_CONTAINER` | No | Edge runtime container filter (default: `edge`) |
113
+ | `dbContainer` / `SHSU_DB_CONTAINER` | No | Database container filter (default: `postgres`) |
114
+
115
+ ## MCP Server
116
+
117
+ shsu can run as an MCP server for AI assistants.
118
+
119
+ ### Claude Code
120
+
121
+ Add to `.mcp.json` in your project root:
122
+
123
+ ```json
124
+ {
125
+ "mcpServers": {
126
+ "shsu": {
127
+ "command": "npx",
128
+ "args": ["shsu", "mcp"]
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ### Claude Desktop
135
+
136
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
137
+
138
+ ```json
139
+ {
140
+ "mcpServers": {
141
+ "shsu": {
142
+ "command": "npx",
143
+ "args": ["shsu", "mcp"],
144
+ "cwd": "/path/to/your/project"
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### Cursor
151
+
152
+ Add to `.cursor/mcp.json` in your project:
153
+
154
+ ```json
155
+ {
156
+ "mcpServers": {
157
+ "shsu": {
158
+ "command": "npx",
159
+ "args": ["shsu", "mcp"]
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ ### Available MCP Tools
166
+
167
+ - `deploy` - Deploy edge functions
168
+ - `migrate` - Run database migrations
169
+ - `list` - List local and remote functions
170
+ - `invoke` - Invoke a function
171
+ - `restart` - Restart edge-runtime
172
+ - `new` - Create new function from template
173
+ - `config` - Show current configuration
174
+
175
+ ## Releasing
176
+
177
+ ```bash
178
+ npm version patch # or minor/major
179
+ git push --follow-tags
180
+ ```
181
+
182
+ GitHub Actions will automatically publish to npm when the tag is pushed.
88
183
 
89
184
  ## License
90
185
 
package/bin/shsu.mjs CHANGED
@@ -27,7 +27,10 @@ function loadConfig() {
27
27
  server: process.env.SHSU_SERVER || pkgConfig.server,
28
28
  remotePath: process.env.SHSU_REMOTE_PATH || pkgConfig.remotePath,
29
29
  localPath: process.env.SHSU_LOCAL_PATH || pkgConfig.localPath || './supabase/functions',
30
+ migrationsPath: process.env.SHSU_MIGRATIONS_PATH || pkgConfig.migrationsPath || './supabase/migrations',
30
31
  url: process.env.SHSU_URL || pkgConfig.url,
32
+ edgeContainer: process.env.SHSU_EDGE_CONTAINER || pkgConfig.edgeContainer || 'edge',
33
+ dbContainer: process.env.SHSU_DB_CONTAINER || pkgConfig.dbContainer || 'postgres',
31
34
  };
32
35
  }
33
36
 
@@ -85,7 +88,7 @@ function runSync(cmd) {
85
88
  }
86
89
 
87
90
  function getEdgeContainer() {
88
- return runSync(`ssh ${config.server} "docker ps -q --filter 'name=edge'"`);
91
+ return runSync(`ssh ${config.server} "docker ps -q --filter 'name=${config.edgeContainer}'"`);
89
92
  }
90
93
 
91
94
  // ─────────────────────────────────────────────────────────────
@@ -121,7 +124,7 @@ async function cmdDeploy(funcName, noRestart = false) {
121
124
  info('Restarting edge-runtime...');
122
125
  await run('ssh', [
123
126
  config.server,
124
- `docker restart $(docker ps -q --filter 'name=edge')`,
127
+ `docker restart $(docker ps -q --filter 'name=${config.edgeContainer}')`,
125
128
  ], { stdio: ['inherit', 'pipe', 'inherit'] });
126
129
  success(`Deployed${funcName ? ` ${funcName}` : ''}`);
127
130
  } else {
@@ -136,7 +139,7 @@ async function cmdLogs(filter, lines = 100) {
136
139
 
137
140
  const sshArgs = [
138
141
  config.server,
139
- `docker logs -f $(docker ps -q --filter 'name=edge') --tail ${lines} 2>&1`,
142
+ `docker logs -f $(docker ps -q --filter 'name=${config.edgeContainer}') --tail ${lines} 2>&1`,
140
143
  ];
141
144
 
142
145
  if (filter) {
@@ -196,11 +199,62 @@ async function cmdRestart() {
196
199
  info('Restarting edge-runtime...');
197
200
  await run('ssh', [
198
201
  config.server,
199
- `docker restart $(docker ps -q --filter 'name=edge')`,
202
+ `docker restart $(docker ps -q --filter 'name=${config.edgeContainer}')`,
200
203
  ], { stdio: ['inherit', 'pipe', 'inherit'] });
201
204
  success('Restarted');
202
205
  }
203
206
 
207
+ async function cmdMigrate() {
208
+ requireServer();
209
+
210
+ if (!existsSync(config.migrationsPath)) {
211
+ error(`Migrations folder not found: ${config.migrationsPath}`);
212
+ }
213
+
214
+ // Get list of migration files
215
+ const migrations = readdirSync(config.migrationsPath)
216
+ .filter((f) => f.endsWith('.sql'))
217
+ .sort();
218
+
219
+ if (migrations.length === 0) {
220
+ info('No migration files found.');
221
+ return;
222
+ }
223
+
224
+ info(`Found ${migrations.length} migration(s): ${migrations.join(', ')}`);
225
+
226
+ // Copy migrations to server
227
+ const remoteMigrationsPath = '/tmp/shsu-migrations';
228
+ info('Syncing migrations to server...');
229
+ await run('rsync', [
230
+ '-avz', '--delete',
231
+ `${config.migrationsPath}/`,
232
+ `${config.server}:${remoteMigrationsPath}/`,
233
+ ]);
234
+
235
+ // Find the database container
236
+ const dbContainer = runSync(`ssh ${config.server} "docker ps -q --filter 'name=${config.dbContainer}'"`);
237
+ if (!dbContainer) {
238
+ error(`Database container not found (filter: ${config.dbContainer})`);
239
+ }
240
+
241
+ // Run each migration
242
+ for (const migration of migrations) {
243
+ info(`Running ${migration}...`);
244
+ try {
245
+ await run('ssh', [
246
+ config.server,
247
+ `docker exec -i ${dbContainer} psql -U postgres -d postgres -f /tmp/shsu-migrations/${migration}`,
248
+ ]);
249
+ success(`Applied ${migration}`);
250
+ } catch (e) {
251
+ error(`Failed to apply ${migration}: ${e.message}`);
252
+ }
253
+ }
254
+
255
+ success('All migrations applied');
256
+ }
257
+
204
258
  async function cmdNew(funcName) {
205
259
  if (!funcName) {
206
260
  error('Usage: shsu new <function-name>');
@@ -239,23 +293,26 @@ function cmdEnv() {
239
293
  console.log(`
240
294
  ${c.yellow('Configuration (package.json "shsu" key or environment variables):')}
241
295
 
242
- server SSH host for your Coolify server
243
- remotePath Remote path to functions directory
244
- url Supabase URL (for invoke command)
245
- localPath Local functions path (default: ./supabase/functions)
246
-
247
- ${c.yellow('Environment variables override package.json:')}
248
-
249
- SHSU_SERVER, SHSU_REMOTE_PATH, SHSU_URL, SHSU_LOCAL_PATH
296
+ server SSH host for your Coolify server
297
+ remotePath Remote path to functions directory
298
+ url Supabase URL (for invoke command)
299
+ localPath Local functions path (default: ./supabase/functions)
300
+ migrationsPath Local migrations path (default: ./supabase/migrations)
301
+ edgeContainer Edge runtime container filter (default: edge)
302
+ dbContainer Database container filter (default: postgres)
250
303
 
251
304
  ${c.yellow('Current values:')}
252
305
 
253
- server = ${config.server || c.dim('(not set)')}
254
- remotePath = ${config.remotePath || c.dim('(not set)')}
255
- url = ${config.url || c.dim('(not set)')}
256
- localPath = ${config.localPath}
306
+ server = ${config.server || c.dim('(not set)')}
307
+ remotePath = ${config.remotePath || c.dim('(not set)')}
308
+ url = ${config.url || c.dim('(not set)')}
309
+ localPath = ${config.localPath}
310
+ migrationsPath = ${config.migrationsPath}
311
+ edgeContainer = ${config.edgeContainer}
312
+ dbContainer = ${config.dbContainer}
257
313
 
258
314
  ${c.dim('Run "shsu init" to configure via prompts.')}
315
+ ${c.dim('Find container names in Coolify: Services → Your Service → look for container name prefix')}
259
316
  `);
260
317
  }
261
318
 
@@ -285,6 +342,8 @@ async function cmdInit() {
285
342
  const remotePath = await ask('Remote path to functions', config.remotePath);
286
343
  const url = await ask('Supabase URL', config.url);
287
344
  const localPath = await ask('Local functions path', config.localPath || './supabase/functions');
345
+ const edgeContainer = await ask('Edge container name filter', config.edgeContainer || 'edge');
346
+ const dbContainer = await ask('Database container name filter', config.dbContainer || 'postgres');
288
347
 
289
348
  rl.close();
290
349
 
@@ -295,6 +354,8 @@ async function cmdInit() {
295
354
  remotePath: remotePath || undefined,
296
355
  url: url || undefined,
297
356
  localPath: localPath !== './supabase/functions' ? localPath : undefined,
357
+ edgeContainer: edgeContainer !== 'edge' ? edgeContainer : undefined,
358
+ dbContainer: dbContainer !== 'postgres' ? dbContainer : undefined,
298
359
  };
299
360
 
300
361
  // Remove undefined values
@@ -308,6 +369,277 @@ async function cmdInit() {
308
369
  success('Added shsu config to package.json');
309
370
  }
310
371
 
372
+ // ─────────────────────────────────────────────────────────────
373
+ // MCP Server
374
+ // ─────────────────────────────────────────────────────────────
375
+ async function cmdMcp() {
376
+ const tools = [
377
+ {
378
+ name: 'deploy',
379
+ description: 'Deploy edge function(s) to the server. Syncs via rsync and restarts edge-runtime.',
380
+ inputSchema: {
381
+ type: 'object',
382
+ properties: {
383
+ name: { type: 'string', description: 'Function name to deploy. If omitted, deploys all functions.' },
384
+ noRestart: { type: 'boolean', description: 'Skip restarting edge-runtime after deploy.' },
385
+ },
386
+ },
387
+ },
388
+ {
389
+ name: 'list',
390
+ description: 'List edge functions (both local and remote).',
391
+ inputSchema: { type: 'object', properties: {} },
392
+ },
393
+ {
394
+ name: 'invoke',
395
+ description: 'Invoke an edge function with optional JSON data.',
396
+ inputSchema: {
397
+ type: 'object',
398
+ properties: {
399
+ name: { type: 'string', description: 'Function name to invoke.' },
400
+ data: { type: 'string', description: 'JSON data to send (default: {}).' },
401
+ },
402
+ required: ['name'],
403
+ },
404
+ },
405
+ {
406
+ name: 'restart',
407
+ description: 'Restart the edge-runtime container.',
408
+ inputSchema: { type: 'object', properties: {} },
409
+ },
410
+ {
411
+ name: 'new',
412
+ description: 'Create a new edge function from template.',
413
+ inputSchema: {
414
+ type: 'object',
415
+ properties: {
416
+ name: { type: 'string', description: 'Name for the new function.' },
417
+ },
418
+ required: ['name'],
419
+ },
420
+ },
421
+ {
422
+ name: 'config',
423
+ description: 'Get current shsu configuration.',
424
+ inputSchema: { type: 'object', properties: {} },
425
+ },
426
+ {
427
+ name: 'migrate',
428
+ description: 'Run SQL migrations on the database. Syncs migration files via rsync and executes them via psql.',
429
+ inputSchema: { type: 'object', properties: {} },
430
+ },
431
+ ];
432
+
433
+ const serverInfo = {
434
+ name: 'shsu',
435
+ version: '0.0.1',
436
+ };
437
+
438
+ // Helper to write JSON-RPC response
439
+ const respond = (id, result) => {
440
+ const response = { jsonrpc: '2.0', id, result };
441
+ process.stdout.write(JSON.stringify(response) + '\n');
442
+ };
443
+
444
+ const respondError = (id, code, message) => {
445
+ const response = { jsonrpc: '2.0', id, error: { code, message } };
446
+ process.stdout.write(JSON.stringify(response) + '\n');
447
+ };
448
+
449
+ // Capture output helper
450
+ const captureExec = (cmd) => {
451
+ try {
452
+ return execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
453
+ } catch (e) {
454
+ return e.message;
455
+ }
456
+ };
457
+
458
+ // Tool handlers
459
+ const handleTool = async (name, args = {}) => {
460
+ switch (name) {
461
+ case 'deploy': {
462
+ if (!config.server || !config.remotePath) {
463
+ return { content: [{ type: 'text', text: 'Error: server and remotePath must be configured. Run "shsu init" first.' }] };
464
+ }
465
+ const funcName = args.name;
466
+ const noRestart = args.noRestart || false;
467
+ let output = '';
468
+
469
+ if (!funcName) {
470
+ output = captureExec(`rsync -avz --delete --exclude='*.test.ts' --exclude='*.spec.ts' "${config.localPath}/" "${config.server}:${config.remotePath}/"`);
471
+ } else {
472
+ const funcPath = join(config.localPath, funcName);
473
+ if (!existsSync(funcPath)) {
474
+ return { content: [{ type: 'text', text: `Error: Function not found: ${funcPath}` }] };
475
+ }
476
+ output = captureExec(`rsync -avz "${funcPath}/" "${config.server}:${config.remotePath}/${funcName}/"`);
477
+ }
478
+
479
+ if (!noRestart) {
480
+ output += '\n' + captureExec(`ssh ${config.server} "docker restart \\$(docker ps -q --filter 'name=${config.edgeContainer}')"`);
481
+ }
482
+
483
+ return { content: [{ type: 'text', text: `Deployed${funcName ? ` ${funcName}` : ' all functions'}${noRestart ? ' (no restart)' : ''}\n\n${output}` }] };
484
+ }
485
+
486
+ case 'list': {
487
+ if (!config.server || !config.remotePath) {
488
+ return { content: [{ type: 'text', text: 'Error: server and remotePath must be configured.' }] };
489
+ }
490
+ const remote = captureExec(`ssh ${config.server} "ls -1 ${config.remotePath} 2>/dev/null"`) || '(none)';
491
+ let local = '(none)';
492
+ if (existsSync(config.localPath)) {
493
+ const dirs = readdirSync(config.localPath, { withFileTypes: true })
494
+ .filter((d) => d.isDirectory())
495
+ .map((d) => d.name);
496
+ local = dirs.length ? dirs.join('\n') : '(none)';
497
+ }
498
+ return { content: [{ type: 'text', text: `Remote functions:\n${remote}\n\nLocal functions:\n${local}` }] };
499
+ }
500
+
501
+ case 'invoke': {
502
+ if (!config.url) {
503
+ return { content: [{ type: 'text', text: 'Error: url must be configured for invoke.' }] };
504
+ }
505
+ if (!args.name) {
506
+ return { content: [{ type: 'text', text: 'Error: function name is required.' }] };
507
+ }
508
+ const data = args.data || '{}';
509
+ const output = captureExec(`curl -s -X POST "${config.url}/functions/v1/${args.name}" -H "Content-Type: application/json" -d '${data}'`);
510
+ return { content: [{ type: 'text', text: output }] };
511
+ }
512
+
513
+ case 'restart': {
514
+ if (!config.server) {
515
+ return { content: [{ type: 'text', text: 'Error: server must be configured.' }] };
516
+ }
517
+ const output = captureExec(`ssh ${config.server} "docker restart \\$(docker ps -q --filter 'name=${config.edgeContainer}')"`);
518
+ return { content: [{ type: 'text', text: `Restarted edge-runtime\n\n${output}` }] };
519
+ }
520
+
521
+ case 'new': {
522
+ if (!args.name) {
523
+ return { content: [{ type: 'text', text: 'Error: function name is required.' }] };
524
+ }
525
+ const funcPath = join(config.localPath, args.name);
526
+ if (existsSync(funcPath)) {
527
+ return { content: [{ type: 'text', text: `Error: Function already exists: ${args.name}` }] };
528
+ }
529
+ mkdirSync(funcPath, { recursive: true });
530
+ writeFileSync(
531
+ join(funcPath, 'index.ts'),
532
+ `Deno.serve(async (req) => {
533
+ try {
534
+ const { name } = await req.json()
535
+
536
+ return new Response(
537
+ JSON.stringify({ message: \`Hello \${name}!\` }),
538
+ { headers: { "Content-Type": "application/json" } }
539
+ )
540
+ } catch (error) {
541
+ return new Response(
542
+ JSON.stringify({ error: error.message }),
543
+ { status: 400, headers: { "Content-Type": "application/json" } }
544
+ )
545
+ }
546
+ })
547
+ `
548
+ );
549
+ return { content: [{ type: 'text', text: `Created ${funcPath}/index.ts` }] };
550
+ }
551
+
552
+ case 'config': {
553
+ return {
554
+ content: [{
555
+ type: 'text',
556
+ 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}\n migrationsPath: ${config.migrationsPath}\n edgeContainer: ${config.edgeContainer}\n dbContainer: ${config.dbContainer}`,
557
+ }],
558
+ };
559
+ }
560
+
561
+ case 'migrate': {
562
+ if (!config.server) {
563
+ return { content: [{ type: 'text', text: 'Error: server must be configured.' }] };
564
+ }
565
+ if (!existsSync(config.migrationsPath)) {
566
+ return { content: [{ type: 'text', text: `Error: Migrations folder not found: ${config.migrationsPath}` }] };
567
+ }
568
+ const migrations = readdirSync(config.migrationsPath)
569
+ .filter((f) => f.endsWith('.sql'))
570
+ .sort();
571
+ if (migrations.length === 0) {
572
+ return { content: [{ type: 'text', text: 'No migration files found.' }] };
573
+ }
574
+ let output = `Found ${migrations.length} migration(s): ${migrations.join(', ')}\n\n`;
575
+ // Sync migrations
576
+ output += captureExec(`rsync -avz --delete "${config.migrationsPath}/" "${config.server}:/tmp/shsu-migrations/"`) + '\n';
577
+ // Find db container
578
+ const dbContainer = captureExec(`ssh ${config.server} "docker ps -q --filter 'name=${config.dbContainer}'"`);
579
+ if (!dbContainer) {
580
+ return { content: [{ type: 'text', text: `Error: Database container not found (filter: ${config.dbContainer})` }] };
581
+ }
582
+ // Run migrations
583
+ for (const migration of migrations) {
584
+ output += `\nRunning ${migration}...\n`;
585
+ output += captureExec(`ssh ${config.server} "docker exec -i ${dbContainer} psql -U postgres -d postgres -f /tmp/shsu-migrations/${migration}"`) + '\n';
586
+ }
587
+ output += '\nAll migrations applied.';
588
+ return { content: [{ type: 'text', text: output }] };
589
+ }
590
+
591
+ default:
592
+ return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
593
+ }
594
+ };
595
+
596
+ // Process incoming messages
597
+ const rl = createInterface({ input: process.stdin });
598
+
599
+ for await (const line of rl) {
600
+ let msg;
601
+ try {
602
+ msg = JSON.parse(line);
603
+ } catch {
604
+ continue;
605
+ }
606
+
607
+ const { id, method, params } = msg;
608
+
609
+ switch (method) {
610
+ case 'initialize':
611
+ respond(id, {
612
+ protocolVersion: '2024-11-05',
613
+ capabilities: { tools: {} },
614
+ serverInfo,
615
+ });
616
+ break;
617
+
618
+ case 'notifications/initialized':
619
+ // No response needed for notifications
620
+ break;
621
+
622
+ case 'tools/list':
623
+ respond(id, { tools });
624
+ break;
625
+
626
+ case 'tools/call':
627
+ try {
628
+ const result = await handleTool(params.name, params.arguments || {});
629
+ respond(id, result);
630
+ } catch (e) {
631
+ respond(id, { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true });
632
+ }
633
+ break;
634
+
635
+ default:
636
+ if (id !== undefined) {
637
+ respondError(id, -32601, `Method not found: ${method}`);
638
+ }
639
+ }
640
+ }
641
+ }
642
+
311
643
  function cmdHelp() {
312
644
  console.log(`
313
645
  ${c.blue('shsu')} - Self-Hosted Supabase Utilities
@@ -323,6 +655,8 @@ ${c.yellow('Commands:')}
323
655
  - With name: deploy single function
324
656
  Options: --no-restart
325
657
 
658
+ migrate Run SQL migrations on database
659
+
326
660
  logs [filter] Stream edge-runtime logs
327
661
  - Optional filter string
328
662
 
@@ -336,10 +670,13 @@ ${c.yellow('Commands:')}
336
670
 
337
671
  env Show current configuration
338
672
 
673
+ mcp Start MCP server (for AI assistants)
674
+
339
675
  ${c.yellow('Examples:')}
340
676
  shsu init
341
677
  shsu deploy
342
678
  shsu deploy hello-world --no-restart
679
+ shsu migrate
343
680
  shsu logs hello-world
344
681
  shsu invoke hello-world '{"name":"Stefan"}'
345
682
  shsu new my-function
@@ -379,6 +716,11 @@ async function main() {
379
716
  case 'restart':
380
717
  await cmdRestart();
381
718
  break;
719
+ case 'migrate':
720
+ case 'migration':
721
+ case 'migrations':
722
+ await cmdMigrate();
723
+ break;
382
724
  case 'new':
383
725
  case 'create':
384
726
  await cmdNew(args[1]);
@@ -386,6 +728,9 @@ async function main() {
386
728
  case 'init':
387
729
  await cmdInit();
388
730
  break;
731
+ case 'mcp':
732
+ await cmdMcp();
733
+ break;
389
734
  case 'env':
390
735
  case 'config':
391
736
  cmdEnv();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shsu",
3
- "version": "0.0.1",
3
+ "version": "0.0.6",
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
+ }
@@ -1,7 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(node bin/shsu.mjs:*)"
5
- ]
6
- }
7
- }