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 +2 -0
- package/bin/cli.js +270 -8
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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('');
|