spawn-skill 1.1.0 → 1.2.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.
Files changed (3) hide show
  1. package/bin/CLAUDE.md +1 -3
  2. package/bin/cli.js +147 -267
  3. package/package.json +10 -3
package/bin/CLAUDE.md CHANGED
@@ -7,7 +7,5 @@
7
7
 
8
8
  | ID | Time | T | Title | Read |
9
9
  |----|------|---|-------|------|
10
- | #905 | 9:07 PM | 🔵 | Spawn-Skill CLI Tool Complete Implementation | ~576 |
11
- | #894 | 8:55 PM | 🔵 | spawn-skill CLI contains embedded skill documentation | ~435 |
12
- | #794 | 11:11 AM | ✅ | Simplified spawn-skill CLI package.json generation to use direct ws dependency | ~353 |
10
+ | #913 | 10:14 PM | 🔵 | Spawn-skill CLI tool provides Python SDK implementation | ~343 |
13
11
  </claude-mem-context>
package/bin/cli.js CHANGED
@@ -338,172 +338,6 @@ if __name__ == '__main__':
338
338
  `;
339
339
  }
340
340
 
341
- function getMCPServer(token) {
342
- return `#!/usr/bin/env node
343
- /**
344
- * Spawn.wtf MCP Server
345
- * Bridges Spawn.wtf with Claude Code
346
- */
347
-
348
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
349
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
350
- import {
351
- CallToolRequestSchema,
352
- ListToolsRequestSchema,
353
- } from '@modelcontextprotocol/sdk/types.js';
354
- import WebSocket from 'ws';
355
-
356
- const RELAY_URL = 'wss://spawn-relay.ngvsqdjj5r.workers.dev/v1/agent';
357
- const TOKEN = process.env.SPAWN_TOKEN || '${token}';
358
-
359
- const messageQueue = [];
360
- let ws = null;
361
- let connected = false;
362
- let connectionError = null;
363
-
364
- function connectToRelay() {
365
- if (!TOKEN) {
366
- connectionError = 'SPAWN_TOKEN not set';
367
- return;
368
- }
369
-
370
- ws = new WebSocket(RELAY_URL, {
371
- headers: { 'Authorization': \\\`Bearer \\\${TOKEN}\\\` }
372
- });
373
-
374
- ws.on('open', () => {
375
- connected = true;
376
- connectionError = null;
377
- sendMessage('status_update', { status: 'idle', label: 'Ready' });
378
- });
379
-
380
- ws.on('message', (data) => {
381
- try {
382
- const msg = JSON.parse(data.toString());
383
- if (msg.type === 'message') {
384
- messageQueue.push({
385
- id: msg.id,
386
- timestamp: msg.ts || Date.now(),
387
- text: msg.payload?.text || '',
388
- from: msg.payload?.from || 'user'
389
- });
390
- }
391
- } catch (e) {}
392
- });
393
-
394
- ws.on('close', () => {
395
- connected = false;
396
- setTimeout(connectToRelay, 5000);
397
- });
398
-
399
- ws.on('error', (err) => {
400
- connectionError = err.message;
401
- });
402
- }
403
-
404
- function sendMessage(type, payload) {
405
- if (ws?.readyState === WebSocket.OPEN) {
406
- ws.send(JSON.stringify({
407
- type,
408
- id: \\\`msg_\\\${Date.now()}\\\`,
409
- ts: Date.now(),
410
- payload
411
- }));
412
- }
413
- }
414
-
415
- setInterval(() => {
416
- if (ws?.readyState === WebSocket.OPEN) {
417
- sendMessage('ping', {});
418
- }
419
- }, 30000);
420
-
421
- connectToRelay();
422
-
423
- const server = new Server(
424
- { name: 'spawn', version: '1.0.0' },
425
- { capabilities: { tools: {} } }
426
- );
427
-
428
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
429
- tools: [
430
- {
431
- name: 'spawn_check_messages',
432
- description: 'Check for new messages from the Spawn.wtf iOS app',
433
- inputSchema: { type: 'object', properties: {}, required: [] }
434
- },
435
- {
436
- name: 'spawn_respond',
437
- description: 'Send a response back to the user through Spawn.wtf',
438
- inputSchema: {
439
- type: 'object',
440
- properties: {
441
- text: { type: 'string', description: 'Message to send' },
442
- format: { type: 'string', enum: ['plain', 'markdown'], default: 'plain' }
443
- },
444
- required: ['text']
445
- }
446
- },
447
- {
448
- name: 'spawn_status',
449
- description: 'Update your status in Spawn.wtf (idle/thinking/working)',
450
- inputSchema: {
451
- type: 'object',
452
- properties: {
453
- status: { type: 'string', enum: ['idle', 'thinking', 'working'] },
454
- label: { type: 'string', description: 'Status label' }
455
- },
456
- required: ['status']
457
- }
458
- },
459
- {
460
- name: 'spawn_connection_info',
461
- description: 'Get Spawn.wtf connection status',
462
- inputSchema: { type: 'object', properties: {}, required: [] }
463
- }
464
- ]
465
- }));
466
-
467
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
468
- const { name, arguments: args } = request.params;
469
-
470
- switch (name) {
471
- case 'spawn_check_messages': {
472
- const messages = [...messageQueue];
473
- messageQueue.length = 0;
474
- if (messages.length > 0) {
475
- sendMessage('status_update', { status: 'thinking', label: 'Reading messages' });
476
- }
477
- return { content: [{ type: 'text', text: JSON.stringify({ messages, count: messages.length }) }] };
478
- }
479
- case 'spawn_respond': {
480
- if (!connected) {
481
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Not connected' }) }] };
482
- }
483
- sendMessage('message', { content_type: 'text', text: args.text, format: args.format || 'plain' });
484
- sendMessage('status_update', { status: 'idle', label: 'Ready' });
485
- return { content: [{ type: 'text', text: JSON.stringify({ success: true }) }] };
486
- }
487
- case 'spawn_status': {
488
- if (!connected) {
489
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Not connected' }) }] };
490
- }
491
- sendMessage('status_update', { status: args.status, label: args.label });
492
- return { content: [{ type: 'text', text: JSON.stringify({ success: true }) }] };
493
- }
494
- case 'spawn_connection_info': {
495
- return { content: [{ type: 'text', text: JSON.stringify({ connected, error: connectionError, pendingMessages: messageQueue.length }) }] };
496
- }
497
- default:
498
- throw new Error(\\\`Unknown tool: \\\${name}\\\`);
499
- }
500
- });
501
-
502
- const transport = new StdioServerTransport();
503
- server.connect(transport);
504
- `;
505
- }
506
-
507
341
  function getPythonSDK() {
508
342
  return `"""
509
343
  Spawn.wtf Python SDK
@@ -639,7 +473,122 @@ class SpawnAgent:
639
473
  `;
640
474
  }
641
475
 
642
- async function createSkillFiles(token, agentName, language, claudeCode = false) {
476
+ function getMoltbotBridge(token, agentName) {
477
+ return `/**
478
+ * Spawn.wtf → Moltbot Bridge
479
+ * Routes messages from Spawn iOS app to your Moltbot backend
480
+ * Run with: node connect.js
481
+ */
482
+
483
+ import WebSocket from 'ws';
484
+ import { spawn } from 'child_process';
485
+
486
+ const TOKEN = '${token}';
487
+ const NAME = '${agentName}';
488
+ const RELAY = 'wss://spawn-relay.ngvsqdjj5r.workers.dev/v1/agent';
489
+
490
+ let ws;
491
+
492
+ function send(type, payload) {
493
+ if (ws?.readyState === WebSocket.OPEN) {
494
+ ws.send(JSON.stringify({
495
+ type,
496
+ id: \`msg_\${Date.now()}\`,
497
+ ts: Date.now(),
498
+ payload
499
+ }));
500
+ }
501
+ }
502
+
503
+ function sendText(text) {
504
+ send('message', { content_type: 'text', text, format: 'plain' });
505
+ }
506
+
507
+ function updateStatus(status, label) {
508
+ send('status_update', { status, label });
509
+ }
510
+
511
+ // Forward message to Moltbot and return response
512
+ async function askMoltbot(message) {
513
+ return new Promise((resolve, reject) => {
514
+ console.log('📨 Forwarding to Moltbot:', message);
515
+ updateStatus('thinking', 'Asking Moltbot...');
516
+
517
+ // Use shell: true to inherit PATH and find globally installed moltbot
518
+ const moltbot = spawn('moltbot', ['agent', '--message', message, '--thinking', 'high'], {
519
+ shell: true,
520
+ env: { ...process.env }
521
+ });
522
+ let response = '';
523
+ let error = '';
524
+
525
+ moltbot.stdout.on('data', (data) => response += data.toString());
526
+ moltbot.stderr.on('data', (data) => error += data.toString());
527
+
528
+ moltbot.on('error', (err) => {
529
+ console.error('✗ Failed to start Moltbot:', err.message);
530
+ reject(new Error('Moltbot not found. Install with: npm install -g moltbot@latest'));
531
+ });
532
+
533
+ moltbot.on('close', (code) => {
534
+ if (code === 0) {
535
+ console.log('✓ Moltbot responded');
536
+ resolve(response.trim());
537
+ } else {
538
+ console.error('✗ Moltbot error:', error);
539
+ reject(new Error(error || \`Exit code \${code}\`));
540
+ }
541
+ });
542
+ });
543
+ }
544
+
545
+ function connect() {
546
+ ws = new WebSocket(RELAY, {
547
+ headers: { 'Authorization': \`Bearer \${TOKEN}\` }
548
+ });
549
+
550
+ ws.on('open', () => {
551
+ console.log('🦞 Connected to Spawn.wtf (Moltbot bridge mode)');
552
+ sendText(\`\${NAME} is online (powered by Moltbot 🦞)\`);
553
+ updateStatus('idle', 'Ready for commands');
554
+ });
555
+
556
+ ws.on('message', async (data) => {
557
+ const msg = JSON.parse(data.toString());
558
+
559
+ if (msg.type === 'message') {
560
+ const text = msg.payload?.text || '';
561
+ console.log('📱 From Spawn:', text);
562
+
563
+ try {
564
+ const response = await askMoltbot(text);
565
+ sendText(response);
566
+ updateStatus('idle', 'Ready');
567
+ } catch (err) {
568
+ sendText(\`Error: \${err.message}\`);
569
+ updateStatus('idle', 'Ready');
570
+ }
571
+ }
572
+ });
573
+
574
+ ws.on('close', () => {
575
+ console.log('Disconnected, reconnecting in 5s...');
576
+ setTimeout(connect, 5000);
577
+ });
578
+
579
+ ws.on('error', (err) => console.error('WebSocket error:', err.message));
580
+ }
581
+
582
+ setInterval(() => ws?.readyState === WebSocket.OPEN && send('ping', {}), 30000);
583
+
584
+ console.log('🦞 Starting Spawn → Moltbot bridge...');
585
+ connect();
586
+
587
+ process.on('SIGINT', () => { ws?.close(); process.exit(0); });
588
+ `;
589
+ }
590
+
591
+ async function createSkillFiles(token, agentName, language, useMoltbot = false) {
643
592
  const skillDir = path.join(process.cwd(), 'spawn');
644
593
 
645
594
  // Create spawn directory
@@ -655,14 +604,17 @@ async function createSkillFiles(token, agentName, language, claudeCode = false)
655
604
  name: agentName,
656
605
  token: token,
657
606
  relay: 'wss://spawn-relay.ngvsqdjj5r.workers.dev/v1/agent',
658
- language: language,
659
- claudeCode: claudeCode
607
+ language: language
660
608
  };
661
609
  fs.writeFileSync(path.join(skillDir, 'config.json'), JSON.stringify(config, null, 2));
662
610
 
663
611
  if (language === 'typescript') {
664
612
  // TypeScript/Node setup
665
- fs.writeFileSync(path.join(skillDir, 'connect.js'), getTypeScriptConnector(token, agentName));
613
+ if (useMoltbot) {
614
+ fs.writeFileSync(path.join(skillDir, 'connect.js'), getMoltbotBridge(token, agentName));
615
+ } else {
616
+ fs.writeFileSync(path.join(skillDir, 'connect.js'), getTypeScriptConnector(token, agentName));
617
+ }
666
618
 
667
619
  const packageJson = {
668
620
  name: "spawn-agent",
@@ -681,45 +633,7 @@ async function createSkillFiles(token, agentName, language, claudeCode = false)
681
633
  fs.writeFileSync(path.join(skillDir, 'requirements.txt'), 'websockets>=12.0\n');
682
634
  }
683
635
 
684
- // Claude Code MCP integration
685
- if (claudeCode) {
686
- const mcpDir = path.join(skillDir, 'mcp');
687
- if (!fs.existsSync(mcpDir)) {
688
- fs.mkdirSync(mcpDir, { recursive: true });
689
- }
690
-
691
- // Create MCP server
692
- fs.writeFileSync(path.join(mcpDir, 'server.js'), getMCPServer(token));
693
-
694
- // Create MCP package.json
695
- const mcpPackageJson = {
696
- name: "spawn-mcp-server",
697
- version: "1.0.0",
698
- type: "module",
699
- dependencies: {
700
- "@modelcontextprotocol/sdk": "^1.0.0",
701
- "ws": "^8.16.0"
702
- }
703
- };
704
- fs.writeFileSync(path.join(mcpDir, 'package.json'), JSON.stringify(mcpPackageJson, null, 2));
705
-
706
- // Create Claude Code settings snippet
707
- const mcpFullPath = path.resolve(mcpDir, 'server.js');
708
- const settingsSnippet = {
709
- mcpServers: {
710
- spawn: {
711
- command: "node",
712
- args: [mcpFullPath],
713
- env: {
714
- SPAWN_TOKEN: token
715
- }
716
- }
717
- }
718
- };
719
- fs.writeFileSync(path.join(mcpDir, 'claude-settings.json'), JSON.stringify(settingsSnippet, null, 2));
720
- }
721
-
722
- return { skillDir, language, claudeCode };
636
+ return { skillDir, language, useMoltbot };
723
637
  }
724
638
 
725
639
  async function main() {
@@ -785,9 +699,9 @@ async function main() {
785
699
 
786
700
  questions.push({
787
701
  type: 'confirm',
788
- name: 'claudeCode',
789
- message: 'Add Claude Code integration? (MCP server for chatting via phone)',
790
- default: true
702
+ name: 'useMoltbot',
703
+ message: 'Use Moltbot as your AI backend? (recommended)',
704
+ default: false
791
705
  });
792
706
 
793
707
  const answers = await inquirer.prompt(questions);
@@ -795,14 +709,29 @@ async function main() {
795
709
  token = token || answers.token;
796
710
  agentName = answers.agentName || agentName;
797
711
  language = language || answers.language;
798
- const claudeCode = answers.claudeCode;
712
+ const useMoltbot = answers.useMoltbot || false;
799
713
 
800
714
  console.log('');
801
715
 
716
+ // Check for Moltbot if needed
717
+ if (useMoltbot) {
718
+ const moltbotCheck = ora('Checking for Moltbot...').start();
719
+ try {
720
+ const { execSync } = await import('child_process');
721
+ execSync('which moltbot', { stdio: 'pipe' });
722
+ moltbotCheck.succeed('Moltbot found');
723
+ } catch {
724
+ moltbotCheck.warn('Moltbot not installed');
725
+ console.log(dim(' Install with: npm install -g moltbot@latest'));
726
+ console.log(dim(' Then run: moltbot onboard'));
727
+ console.log('');
728
+ }
729
+ }
730
+
802
731
  // Create files
803
732
  const createSpinner = ora('Creating skill files...').start();
804
733
  try {
805
- const { skillDir } = await createSkillFiles(token, agentName, language, claudeCode);
734
+ const { skillDir } = await createSkillFiles(token, agentName, language, useMoltbot);
806
735
  createSpinner.succeed('Created skill files');
807
736
  } catch (err) {
808
737
  createSpinner.fail('Failed to create skill files');
@@ -810,18 +739,6 @@ async function main() {
810
739
  process.exit(1);
811
740
  }
812
741
 
813
- // Install MCP dependencies if needed
814
- if (claudeCode) {
815
- const mcpSpinner = ora('Installing MCP dependencies...').start();
816
- try {
817
- const { execSync } = await import('child_process');
818
- execSync('npm install', { cwd: path.join(process.cwd(), 'spawn', 'mcp'), stdio: 'pipe' });
819
- mcpSpinner.succeed('Installed MCP dependencies');
820
- } catch (err) {
821
- mcpSpinner.warn('Run "cd spawn/mcp && npm install" to finish MCP setup');
822
- }
823
- }
824
-
825
742
  console.log('');
826
743
  console.log(chalk.green.bold('✓ Spawn.wtf skill installed!'));
827
744
  console.log('');
@@ -832,20 +749,14 @@ async function main() {
832
749
  console.log(dim(' spawn/config.json'));
833
750
  console.log(dim(' spawn/connect.js'));
834
751
  console.log(dim(' spawn/package.json'));
835
- if (claudeCode) {
836
- console.log(dim(' spawn/mcp/server.js'));
837
- console.log(dim(' spawn/mcp/package.json'));
838
- console.log(dim(' spawn/mcp/claude-settings.json'));
839
- }
840
752
  console.log('');
841
753
  console.log(' Next steps:');
842
754
  console.log('');
843
755
  console.log(' 1. ' + chalk.cyan('Install dependencies:'));
844
- console.log(' ' + dim('cd spawn'));
845
- console.log(' ' + dim('npm install'));
756
+ console.log(' ' + dim('cd spawn && npm install'));
846
757
  console.log('');
847
758
  console.log(' 2. ' + chalk.cyan('Run the connector:'));
848
- console.log(' ' + dim('node connect.js'));
759
+ console.log(' ' + dim('node spawn/connect.js'));
849
760
  } else {
850
761
  console.log(' Files created:');
851
762
  console.log(dim(' spawn/SKILL.md'));
@@ -853,49 +764,18 @@ async function main() {
853
764
  console.log(dim(' spawn/connect.py'));
854
765
  console.log(dim(' spawn/spawn_sdk.py'));
855
766
  console.log(dim(' spawn/requirements.txt'));
856
- if (claudeCode) {
857
- console.log(dim(' spawn/mcp/server.js'));
858
- console.log(dim(' spawn/mcp/package.json'));
859
- console.log(dim(' spawn/mcp/claude-settings.json'));
860
- }
861
767
  console.log('');
862
768
  console.log(' Next steps:');
863
769
  console.log('');
864
770
  console.log(' 1. ' + chalk.cyan('Install dependencies:'));
865
- console.log(' ' + dim('cd spawn'));
866
- console.log(' ' + dim('pip install -r requirements.txt'));
771
+ console.log(' ' + dim('cd spawn && pip install -r requirements.txt'));
867
772
  console.log('');
868
773
  console.log(' 2. ' + chalk.cyan('Run the connector:'));
869
- console.log(' ' + dim('python connect.py'));
774
+ console.log(' ' + dim('python spawn/connect.py'));
870
775
  }
871
776
 
872
777
  console.log('');
873
778
  console.log(' 3. ' + chalk.cyan('Open Spawn.wtf app') + ' - your agent should appear!');
874
-
875
- if (claudeCode) {
876
- console.log('');
877
- console.log(pink(' ═══ Claude Code Integration ═══'));
878
- console.log('');
879
- console.log(' Add to your ' + chalk.cyan('.claude/settings.json') + ':');
880
- console.log('');
881
- const mcpPath = path.resolve(process.cwd(), 'spawn', 'mcp', 'server.js');
882
- console.log(dim(' {'));
883
- console.log(dim(' "mcpServers": {'));
884
- console.log(dim(' "spawn": {'));
885
- console.log(dim(' "command": "node",'));
886
- console.log(dim(` "args": ["${mcpPath}"],`));
887
- console.log(dim(' "env": { "SPAWN_TOKEN": "' + token.slice(0, 12) + '..." }'));
888
- console.log(dim(' }'));
889
- console.log(dim(' }'));
890
- console.log(dim(' }'));
891
- console.log('');
892
- console.log(' Or copy from: ' + dim('spawn/mcp/claude-settings.json'));
893
- console.log('');
894
- console.log(' Then restart Claude Code and ask:');
895
- console.log(' ' + chalk.white('"Check Spawn messages"'));
896
- console.log(' ' + chalk.white('"Respond: Hello from Claude!"'));
897
- }
898
-
899
779
  console.log('');
900
780
  console.log(' Docs: ' + pink('https://github.com/SpawnWTF/spawn'));
901
781
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spawn-skill",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Connect your AI agent to Spawn.wtf",
5
5
  "bin": {
6
6
  "spawn-skill": "./bin/cli.js"
@@ -9,7 +9,14 @@
9
9
  "files": [
10
10
  "bin"
11
11
  ],
12
- "keywords": ["spawn", "ai", "agent", "claude", "monitoring"],
12
+ "keywords": [
13
+ "spawn",
14
+ "ai",
15
+ "agent",
16
+ "claude",
17
+ "monitoring",
18
+ "moltbot"
19
+ ],
13
20
  "author": "Spawn.wtf",
14
21
  "license": "MIT",
15
22
  "repository": {
@@ -21,4 +28,4 @@
21
28
  "inquirer": "^9.2.12",
22
29
  "ora": "^8.0.1"
23
30
  }
24
- }
31
+ }