spawn-skill 1.1.0 → 1.2.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.
Files changed (3) hide show
  1. package/bin/CLAUDE.md +1 -3
  2. package/bin/cli.js +138 -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,113 @@ 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
+ const moltbot = spawn('moltbot', ['agent', '--message', message, '--thinking', 'high']);
518
+ let response = '';
519
+ let error = '';
520
+
521
+ moltbot.stdout.on('data', (data) => response += data.toString());
522
+ moltbot.stderr.on('data', (data) => error += data.toString());
523
+
524
+ moltbot.on('close', (code) => {
525
+ if (code === 0) {
526
+ console.log('✓ Moltbot responded');
527
+ resolve(response.trim());
528
+ } else {
529
+ console.error('✗ Moltbot error:', error);
530
+ reject(new Error(error || \`Exit code \${code}\`));
531
+ }
532
+ });
533
+ });
534
+ }
535
+
536
+ function connect() {
537
+ ws = new WebSocket(RELAY, {
538
+ headers: { 'Authorization': \`Bearer \${TOKEN}\` }
539
+ });
540
+
541
+ ws.on('open', () => {
542
+ console.log('🦞 Connected to Spawn.wtf (Moltbot bridge mode)');
543
+ sendText(\`\${NAME} is online (powered by Moltbot 🦞)\`);
544
+ updateStatus('idle', 'Ready for commands');
545
+ });
546
+
547
+ ws.on('message', async (data) => {
548
+ const msg = JSON.parse(data.toString());
549
+
550
+ if (msg.type === 'message') {
551
+ const text = msg.payload?.text || '';
552
+ console.log('📱 From Spawn:', text);
553
+
554
+ try {
555
+ const response = await askMoltbot(text);
556
+ sendText(response);
557
+ updateStatus('idle', 'Ready');
558
+ } catch (err) {
559
+ sendText(\`Error: \${err.message}\`);
560
+ updateStatus('idle', 'Ready');
561
+ }
562
+ }
563
+ });
564
+
565
+ ws.on('close', () => {
566
+ console.log('Disconnected, reconnecting in 5s...');
567
+ setTimeout(connect, 5000);
568
+ });
569
+
570
+ ws.on('error', (err) => console.error('WebSocket error:', err.message));
571
+ }
572
+
573
+ setInterval(() => ws?.readyState === WebSocket.OPEN && send('ping', {}), 30000);
574
+
575
+ console.log('🦞 Starting Spawn → Moltbot bridge...');
576
+ connect();
577
+
578
+ process.on('SIGINT', () => { ws?.close(); process.exit(0); });
579
+ `;
580
+ }
581
+
582
+ async function createSkillFiles(token, agentName, language, useMoltbot = false) {
643
583
  const skillDir = path.join(process.cwd(), 'spawn');
644
584
 
645
585
  // Create spawn directory
@@ -655,14 +595,17 @@ async function createSkillFiles(token, agentName, language, claudeCode = false)
655
595
  name: agentName,
656
596
  token: token,
657
597
  relay: 'wss://spawn-relay.ngvsqdjj5r.workers.dev/v1/agent',
658
- language: language,
659
- claudeCode: claudeCode
598
+ language: language
660
599
  };
661
600
  fs.writeFileSync(path.join(skillDir, 'config.json'), JSON.stringify(config, null, 2));
662
601
 
663
602
  if (language === 'typescript') {
664
603
  // TypeScript/Node setup
665
- fs.writeFileSync(path.join(skillDir, 'connect.js'), getTypeScriptConnector(token, agentName));
604
+ if (useMoltbot) {
605
+ fs.writeFileSync(path.join(skillDir, 'connect.js'), getMoltbotBridge(token, agentName));
606
+ } else {
607
+ fs.writeFileSync(path.join(skillDir, 'connect.js'), getTypeScriptConnector(token, agentName));
608
+ }
666
609
 
667
610
  const packageJson = {
668
611
  name: "spawn-agent",
@@ -681,45 +624,7 @@ async function createSkillFiles(token, agentName, language, claudeCode = false)
681
624
  fs.writeFileSync(path.join(skillDir, 'requirements.txt'), 'websockets>=12.0\n');
682
625
  }
683
626
 
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 };
627
+ return { skillDir, language, useMoltbot };
723
628
  }
724
629
 
725
630
  async function main() {
@@ -785,9 +690,9 @@ async function main() {
785
690
 
786
691
  questions.push({
787
692
  type: 'confirm',
788
- name: 'claudeCode',
789
- message: 'Add Claude Code integration? (MCP server for chatting via phone)',
790
- default: true
693
+ name: 'useMoltbot',
694
+ message: 'Use Moltbot as your AI backend? (recommended)',
695
+ default: false
791
696
  });
792
697
 
793
698
  const answers = await inquirer.prompt(questions);
@@ -795,14 +700,29 @@ async function main() {
795
700
  token = token || answers.token;
796
701
  agentName = answers.agentName || agentName;
797
702
  language = language || answers.language;
798
- const claudeCode = answers.claudeCode;
703
+ const useMoltbot = answers.useMoltbot || false;
799
704
 
800
705
  console.log('');
801
706
 
707
+ // Check for Moltbot if needed
708
+ if (useMoltbot) {
709
+ const moltbotCheck = ora('Checking for Moltbot...').start();
710
+ try {
711
+ const { execSync } = await import('child_process');
712
+ execSync('which moltbot', { stdio: 'pipe' });
713
+ moltbotCheck.succeed('Moltbot found');
714
+ } catch {
715
+ moltbotCheck.warn('Moltbot not installed');
716
+ console.log(dim(' Install with: npm install -g moltbot@latest'));
717
+ console.log(dim(' Then run: moltbot onboard'));
718
+ console.log('');
719
+ }
720
+ }
721
+
802
722
  // Create files
803
723
  const createSpinner = ora('Creating skill files...').start();
804
724
  try {
805
- const { skillDir } = await createSkillFiles(token, agentName, language, claudeCode);
725
+ const { skillDir } = await createSkillFiles(token, agentName, language, useMoltbot);
806
726
  createSpinner.succeed('Created skill files');
807
727
  } catch (err) {
808
728
  createSpinner.fail('Failed to create skill files');
@@ -810,18 +730,6 @@ async function main() {
810
730
  process.exit(1);
811
731
  }
812
732
 
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
733
  console.log('');
826
734
  console.log(chalk.green.bold('✓ Spawn.wtf skill installed!'));
827
735
  console.log('');
@@ -832,20 +740,14 @@ async function main() {
832
740
  console.log(dim(' spawn/config.json'));
833
741
  console.log(dim(' spawn/connect.js'));
834
742
  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
743
  console.log('');
841
744
  console.log(' Next steps:');
842
745
  console.log('');
843
746
  console.log(' 1. ' + chalk.cyan('Install dependencies:'));
844
- console.log(' ' + dim('cd spawn'));
845
- console.log(' ' + dim('npm install'));
747
+ console.log(' ' + dim('cd spawn && npm install'));
846
748
  console.log('');
847
749
  console.log(' 2. ' + chalk.cyan('Run the connector:'));
848
- console.log(' ' + dim('node connect.js'));
750
+ console.log(' ' + dim('node spawn/connect.js'));
849
751
  } else {
850
752
  console.log(' Files created:');
851
753
  console.log(dim(' spawn/SKILL.md'));
@@ -853,49 +755,18 @@ async function main() {
853
755
  console.log(dim(' spawn/connect.py'));
854
756
  console.log(dim(' spawn/spawn_sdk.py'));
855
757
  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
758
  console.log('');
862
759
  console.log(' Next steps:');
863
760
  console.log('');
864
761
  console.log(' 1. ' + chalk.cyan('Install dependencies:'));
865
- console.log(' ' + dim('cd spawn'));
866
- console.log(' ' + dim('pip install -r requirements.txt'));
762
+ console.log(' ' + dim('cd spawn && pip install -r requirements.txt'));
867
763
  console.log('');
868
764
  console.log(' 2. ' + chalk.cyan('Run the connector:'));
869
- console.log(' ' + dim('python connect.py'));
765
+ console.log(' ' + dim('python spawn/connect.py'));
870
766
  }
871
767
 
872
768
  console.log('');
873
769
  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
770
  console.log('');
900
771
  console.log(' Docs: ' + pink('https://github.com/SpawnWTF/spawn'));
901
772
  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.0",
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
+ }