spawn-skill 1.0.2 → 1.1.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.
package/bin/CLAUDE.md CHANGED
@@ -7,5 +7,7 @@
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 |
10
12
  | #794 | 11:11 AM | ✅ | Simplified spawn-skill CLI package.json generation to use direct ws dependency | ~353 |
11
13
  </claude-mem-context>
package/bin/cli.js CHANGED
@@ -338,6 +338,172 @@ 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
+
341
507
  function getPythonSDK() {
342
508
  return `"""
343
509
  Spawn.wtf Python SDK
@@ -473,7 +639,7 @@ class SpawnAgent:
473
639
  `;
474
640
  }
475
641
 
476
- async function createSkillFiles(token, agentName, language) {
642
+ async function createSkillFiles(token, agentName, language, claudeCode = false) {
477
643
  const skillDir = path.join(process.cwd(), 'spawn');
478
644
 
479
645
  // Create spawn directory
@@ -489,7 +655,8 @@ async function createSkillFiles(token, agentName, language) {
489
655
  name: agentName,
490
656
  token: token,
491
657
  relay: 'wss://spawn-relay.ngvsqdjj5r.workers.dev/v1/agent',
492
- language: language
658
+ language: language,
659
+ claudeCode: claudeCode
493
660
  };
494
661
  fs.writeFileSync(path.join(skillDir, 'config.json'), JSON.stringify(config, null, 2));
495
662
 
@@ -514,7 +681,45 @@ async function createSkillFiles(token, agentName, language) {
514
681
  fs.writeFileSync(path.join(skillDir, 'requirements.txt'), 'websockets>=12.0\n');
515
682
  }
516
683
 
517
- return { skillDir, language };
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 };
518
723
  }
519
724
 
520
725
  async function main() {
@@ -578,18 +783,26 @@ async function main() {
578
783
  });
579
784
  }
580
785
 
786
+ questions.push({
787
+ type: 'confirm',
788
+ name: 'claudeCode',
789
+ message: 'Add Claude Code integration? (MCP server for chatting via phone)',
790
+ default: true
791
+ });
792
+
581
793
  const answers = await inquirer.prompt(questions);
582
794
 
583
795
  token = token || answers.token;
584
796
  agentName = answers.agentName || agentName;
585
797
  language = language || answers.language;
798
+ const claudeCode = answers.claudeCode;
586
799
 
587
800
  console.log('');
588
801
 
589
802
  // Create files
590
803
  const createSpinner = ora('Creating skill files...').start();
591
804
  try {
592
- const { skillDir } = await createSkillFiles(token, agentName, language);
805
+ const { skillDir } = await createSkillFiles(token, agentName, language, claudeCode);
593
806
  createSpinner.succeed('Created skill files');
594
807
  } catch (err) {
595
808
  createSpinner.fail('Failed to create skill files');
@@ -597,6 +810,18 @@ async function main() {
597
810
  process.exit(1);
598
811
  }
599
812
 
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
+
600
825
  console.log('');
601
826
  console.log(chalk.green.bold('✓ Spawn.wtf skill installed!'));
602
827
  console.log('');
@@ -607,14 +832,20 @@ async function main() {
607
832
  console.log(dim(' spawn/config.json'));
608
833
  console.log(dim(' spawn/connect.js'));
609
834
  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
+ }
610
840
  console.log('');
611
841
  console.log(' Next steps:');
612
842
  console.log('');
613
843
  console.log(' 1. ' + chalk.cyan('Install dependencies:'));
614
- console.log(' ' + dim('cd spawn && npm install'));
844
+ console.log(' ' + dim('cd spawn'));
845
+ console.log(' ' + dim('npm install'));
615
846
  console.log('');
616
847
  console.log(' 2. ' + chalk.cyan('Run the connector:'));
617
- console.log(' ' + dim('node spawn/connect.js'));
848
+ console.log(' ' + dim('node connect.js'));
618
849
  } else {
619
850
  console.log(' Files created:');
620
851
  console.log(dim(' spawn/SKILL.md'));
@@ -622,18 +853,49 @@ async function main() {
622
853
  console.log(dim(' spawn/connect.py'));
623
854
  console.log(dim(' spawn/spawn_sdk.py'));
624
855
  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
+ }
625
861
  console.log('');
626
862
  console.log(' Next steps:');
627
863
  console.log('');
628
864
  console.log(' 1. ' + chalk.cyan('Install dependencies:'));
629
- console.log(' ' + dim('cd spawn && pip install -r requirements.txt'));
865
+ console.log(' ' + dim('cd spawn'));
866
+ console.log(' ' + dim('pip install -r requirements.txt'));
630
867
  console.log('');
631
868
  console.log(' 2. ' + chalk.cyan('Run the connector:'));
632
- console.log(' ' + dim('python spawn/connect.py'));
869
+ console.log(' ' + dim('python connect.py'));
633
870
  }
634
871
 
635
872
  console.log('');
636
873
  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
+
637
899
  console.log('');
638
900
  console.log(' Docs: ' + pink('https://github.com/SpawnWTF/spawn'));
639
901
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spawn-skill",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Connect your AI agent to Spawn.wtf",
5
5
  "bin": {
6
6
  "spawn-skill": "./bin/cli.js"