shsu 0.0.6 → 0.0.8

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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/bin/shsu.mjs +177 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -171,6 +171,7 @@ Add to `.cursor/mcp.json` in your project:
171
171
  - `restart` - Restart edge-runtime
172
172
  - `new` - Create new function from template
173
173
  - `config` - Show current configuration
174
+ - `docs` - Get setup documentation
174
175
 
175
176
  ## Releasing
176
177
 
package/bin/shsu.mjs CHANGED
@@ -428,6 +428,11 @@ async function cmdMcp() {
428
428
  description: 'Run SQL migrations on the database. Syncs migration files via rsync and executes them via psql.',
429
429
  inputSchema: { type: 'object', properties: {} },
430
430
  },
431
+ {
432
+ name: 'docs',
433
+ description: 'Get documentation on how to set up and use shsu for deploying Supabase Edge Functions.',
434
+ inputSchema: { type: 'object', properties: {} },
435
+ },
431
436
  ];
432
437
 
433
438
  const serverInfo = {
@@ -460,7 +465,18 @@ async function cmdMcp() {
460
465
  switch (name) {
461
466
  case 'deploy': {
462
467
  if (!config.server || !config.remotePath) {
463
- return { content: [{ type: 'text', text: 'Error: server and remotePath must be configured. Run "shsu init" first.' }] };
468
+ return { content: [{ type: 'text', text: `Error: server and remotePath must be configured.
469
+
470
+ To fix, add to package.json:
471
+ {
472
+ "shsu": {
473
+ "server": "root@your-server.com",
474
+ "remotePath": "/data/coolify/services/xxx/volumes/functions"
475
+ }
476
+ }
477
+
478
+ Find remotePath by running on your server:
479
+ docker inspect $(docker ps -q --filter 'name=edge') | grep -A 5 "Mounts"` }] };
464
480
  }
465
481
  const funcName = args.name;
466
482
  const noRestart = args.noRestart || false;
@@ -471,7 +487,9 @@ async function cmdMcp() {
471
487
  } else {
472
488
  const funcPath = join(config.localPath, funcName);
473
489
  if (!existsSync(funcPath)) {
474
- return { content: [{ type: 'text', text: `Error: Function not found: ${funcPath}` }] };
490
+ return { content: [{ type: 'text', text: `Error: Function not found: ${funcPath}
491
+
492
+ To fix, create the function first using the 'new' tool with name: "${funcName}"` }] };
475
493
  }
476
494
  output = captureExec(`rsync -avz "${funcPath}/" "${config.server}:${config.remotePath}/${funcName}/"`);
477
495
  }
@@ -485,7 +503,18 @@ async function cmdMcp() {
485
503
 
486
504
  case 'list': {
487
505
  if (!config.server || !config.remotePath) {
488
- return { content: [{ type: 'text', text: 'Error: server and remotePath must be configured.' }] };
506
+ return { content: [{ type: 'text', text: `Error: server and remotePath must be configured.
507
+
508
+ To fix, add to package.json:
509
+ {
510
+ "shsu": {
511
+ "server": "root@your-server.com",
512
+ "remotePath": "/data/coolify/services/xxx/volumes/functions"
513
+ }
514
+ }
515
+
516
+ Find remotePath by running on your server:
517
+ docker inspect $(docker ps -q --filter 'name=edge') | grep -A 5 "Mounts"` }] };
489
518
  }
490
519
  const remote = captureExec(`ssh ${config.server} "ls -1 ${config.remotePath} 2>/dev/null"`) || '(none)';
491
520
  let local = '(none)';
@@ -500,10 +529,19 @@ async function cmdMcp() {
500
529
 
501
530
  case 'invoke': {
502
531
  if (!config.url) {
503
- return { content: [{ type: 'text', text: 'Error: url must be configured for invoke.' }] };
532
+ return { content: [{ type: 'text', text: `Error: url must be configured for invoke.
533
+
534
+ To fix, add to package.json:
535
+ {
536
+ "shsu": {
537
+ "url": "https://your-supabase.example.com"
538
+ }
539
+ }` }] };
504
540
  }
505
541
  if (!args.name) {
506
- return { content: [{ type: 'text', text: 'Error: function name is required.' }] };
542
+ return { content: [{ type: 'text', text: `Error: function name is required.
543
+
544
+ Usage: invoke tool with { "name": "function-name", "data": "{\\"key\\": \\"value\\"}" }` }] };
507
545
  }
508
546
  const data = args.data || '{}';
509
547
  const output = captureExec(`curl -s -X POST "${config.url}/functions/v1/${args.name}" -H "Content-Type: application/json" -d '${data}'`);
@@ -512,7 +550,14 @@ async function cmdMcp() {
512
550
 
513
551
  case 'restart': {
514
552
  if (!config.server) {
515
- return { content: [{ type: 'text', text: 'Error: server must be configured.' }] };
553
+ return { content: [{ type: 'text', text: `Error: server must be configured.
554
+
555
+ To fix, add to package.json:
556
+ {
557
+ "shsu": {
558
+ "server": "root@your-server.com"
559
+ }
560
+ }` }] };
516
561
  }
517
562
  const output = captureExec(`ssh ${config.server} "docker restart \\$(docker ps -q --filter 'name=${config.edgeContainer}')"`);
518
563
  return { content: [{ type: 'text', text: `Restarted edge-runtime\n\n${output}` }] };
@@ -520,11 +565,15 @@ async function cmdMcp() {
520
565
 
521
566
  case 'new': {
522
567
  if (!args.name) {
523
- return { content: [{ type: 'text', text: 'Error: function name is required.' }] };
568
+ return { content: [{ type: 'text', text: `Error: function name is required.
569
+
570
+ Usage: new tool with { "name": "my-function-name" }` }] };
524
571
  }
525
572
  const funcPath = join(config.localPath, args.name);
526
573
  if (existsSync(funcPath)) {
527
- return { content: [{ type: 'text', text: `Error: Function already exists: ${args.name}` }] };
574
+ return { content: [{ type: 'text', text: `Error: Function already exists: ${args.name}
575
+
576
+ The function already exists at ${funcPath}. To update it, edit the code and use the 'deploy' tool.` }] };
528
577
  }
529
578
  mkdirSync(funcPath, { recursive: true });
530
579
  writeFileSync(
@@ -560,16 +609,35 @@ async function cmdMcp() {
560
609
 
561
610
  case 'migrate': {
562
611
  if (!config.server) {
563
- return { content: [{ type: 'text', text: 'Error: server must be configured.' }] };
612
+ return { content: [{ type: 'text', text: `Error: server must be configured.
613
+
614
+ To fix, add to package.json:
615
+ {
616
+ "shsu": {
617
+ "server": "root@your-server.com"
618
+ }
619
+ }` }] };
564
620
  }
565
621
  if (!existsSync(config.migrationsPath)) {
566
- return { content: [{ type: 'text', text: `Error: Migrations folder not found: ${config.migrationsPath}` }] };
622
+ return { content: [{ type: 'text', text: `Error: Migrations folder not found: ${config.migrationsPath}
623
+
624
+ To fix, create the migrations directory and add .sql files:
625
+ mkdir -p ${config.migrationsPath}
626
+
627
+ Then add migration files like:
628
+ ${config.migrationsPath}/001_create_tables.sql
629
+ ${config.migrationsPath}/002_add_indexes.sql` }] };
567
630
  }
568
631
  const migrations = readdirSync(config.migrationsPath)
569
632
  .filter((f) => f.endsWith('.sql'))
570
633
  .sort();
571
634
  if (migrations.length === 0) {
572
- return { content: [{ type: 'text', text: 'No migration files found.' }] };
635
+ return { content: [{ type: 'text', text: `No migration files found in ${config.migrationsPath}
636
+
637
+ Add .sql files to the migrations folder, e.g.:
638
+ ${config.migrationsPath}/001_create_tables.sql
639
+
640
+ Files are executed alphabetically, so use numeric prefixes for ordering.` }] };
573
641
  }
574
642
  let output = `Found ${migrations.length} migration(s): ${migrations.join(', ')}\n\n`;
575
643
  // Sync migrations
@@ -577,7 +645,17 @@ async function cmdMcp() {
577
645
  // Find db container
578
646
  const dbContainer = captureExec(`ssh ${config.server} "docker ps -q --filter 'name=${config.dbContainer}'"`);
579
647
  if (!dbContainer) {
580
- return { content: [{ type: 'text', text: `Error: Database container not found (filter: ${config.dbContainer})` }] };
648
+ return { content: [{ type: 'text', text: `Error: Database container not found (filter: ${config.dbContainer})
649
+
650
+ To fix:
651
+ 1. SSH to your server and run: docker ps
652
+ 2. Find the postgres container name (e.g., abc123-supabase-db-1)
653
+ 3. Update dbContainer in package.json to match a unique part of the name:
654
+ {
655
+ "shsu": {
656
+ "dbContainer": "supabase-db"
657
+ }
658
+ }` }] };
581
659
  }
582
660
  // Run migrations
583
661
  for (const migration of migrations) {
@@ -588,6 +666,93 @@ async function cmdMcp() {
588
666
  return { content: [{ type: 'text', text: output }] };
589
667
  }
590
668
 
669
+ case 'docs': {
670
+ return {
671
+ content: [{
672
+ type: 'text',
673
+ text: `# shsu - Self-Hosted Supabase Utilities
674
+
675
+ Deploy and manage Supabase Edge Functions on Coolify-hosted Supabase.
676
+
677
+ ## Project Setup
678
+
679
+ 1. **Configure shsu** by adding to package.json:
680
+ \`\`\`json
681
+ {
682
+ "shsu": {
683
+ "server": "root@your-coolify-server",
684
+ "remotePath": "/data/coolify/services/YOUR_SERVICE_ID/volumes/functions",
685
+ "url": "https://your-supabase.example.com",
686
+ "edgeContainer": "edge",
687
+ "dbContainer": "postgres"
688
+ }
689
+ }
690
+ \`\`\`
691
+
692
+ Or run \`npx shsu init\` for interactive setup.
693
+
694
+ 2. **Find configuration values** by SSH'ing to your server:
695
+ - Container names: \`docker ps\` (Coolify uses pattern \`<service>-<uuid>\`)
696
+ - Remote path: \`docker inspect $(docker ps -q --filter 'name=edge') | grep -A 5 "Mounts"\`
697
+
698
+ ## Directory Structure
699
+
700
+ \`\`\`
701
+ your-project/
702
+ ├── package.json # Contains shsu config
703
+ ├── supabase/
704
+ │ ├── functions/ # Edge functions (default localPath)
705
+ │ │ ├── hello-world/
706
+ │ │ │ └── index.ts
707
+ │ │ └── another-func/
708
+ │ │ └── index.ts
709
+ │ └── migrations/ # SQL migrations (default migrationsPath)
710
+ │ ├── 001_create_users.sql
711
+ │ └── 002_add_indexes.sql
712
+ \`\`\`
713
+
714
+ ## Configuration Options
715
+
716
+ | Key | Required | Default | Description |
717
+ |-----|----------|---------|-------------|
718
+ | server | Yes | - | SSH host (e.g., root@server.com) |
719
+ | remotePath | Yes | - | Remote path to functions directory |
720
+ | url | For invoke | - | Supabase URL |
721
+ | localPath | No | ./supabase/functions | Local functions path |
722
+ | migrationsPath | No | ./supabase/migrations | Local migrations path |
723
+ | edgeContainer | No | edge | Edge runtime container filter |
724
+ | dbContainer | No | postgres | Database container filter |
725
+
726
+ ## Edge Function Template
727
+
728
+ Use \`new\` tool to create functions. Each function needs an index.ts:
729
+
730
+ \`\`\`typescript
731
+ Deno.serve(async (req) => {
732
+ const { name } = await req.json()
733
+ return new Response(
734
+ JSON.stringify({ message: \`Hello \${name}!\` }),
735
+ { headers: { "Content-Type": "application/json" } }
736
+ )
737
+ })
738
+ \`\`\`
739
+
740
+ ## Workflow
741
+
742
+ 1. Create function: \`new\` tool with function name
743
+ 2. Edit the function code in supabase/functions/<name>/index.ts
744
+ 3. Deploy: \`deploy\` tool (syncs via rsync, restarts edge-runtime)
745
+ 4. Test: \`invoke\` tool with JSON data
746
+ 5. Debug: Check logs on the server
747
+
748
+ ## Migrations
749
+
750
+ Place .sql files in supabase/migrations/. They execute alphabetically.
751
+ Use \`migrate\` tool to run all migrations via psql in the database container.`,
752
+ }],
753
+ };
754
+ }
755
+
591
756
  default:
592
757
  return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
593
758
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shsu",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
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"