spawn-skill 1.0.1 → 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 +279 -30
- 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
|
@@ -233,18 +233,15 @@ function connect() {
|
|
|
233
233
|
});
|
|
234
234
|
|
|
235
235
|
ws.on('open', () => {
|
|
236
|
-
console.log('Connected to
|
|
237
|
-
|
|
236
|
+
console.log('Connected to Spawn.wtf!');
|
|
237
|
+
// Auth happens via token in header - just start working
|
|
238
|
+
sendText(\`\${NAME} is online and ready.\`);
|
|
239
|
+
updateStatus('idle', 'Ready for commands');
|
|
238
240
|
});
|
|
239
241
|
|
|
240
242
|
ws.on('message', (data) => {
|
|
241
243
|
const msg = JSON.parse(data.toString());
|
|
242
|
-
|
|
243
|
-
if (msg.type === 'auth_success') {
|
|
244
|
-
console.log('Authenticated! Agent is online.');
|
|
245
|
-
sendText(\`\${NAME} is online and ready.\`);
|
|
246
|
-
updateStatus('idle', 'Ready for commands');
|
|
247
|
-
}
|
|
244
|
+
console.log('Received:', msg.type);
|
|
248
245
|
|
|
249
246
|
if (msg.type === 'message') {
|
|
250
247
|
console.log('Message from app:', msg.payload);
|
|
@@ -341,6 +338,172 @@ if __name__ == '__main__':
|
|
|
341
338
|
`;
|
|
342
339
|
}
|
|
343
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
|
+
|
|
344
507
|
function getPythonSDK() {
|
|
345
508
|
return `"""
|
|
346
509
|
Spawn.wtf Python SDK
|
|
@@ -376,16 +539,9 @@ class SpawnAgent:
|
|
|
376
539
|
)
|
|
377
540
|
self._connected = True
|
|
378
541
|
|
|
379
|
-
#
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
'id': f'auth_{int(asyncio.get_event_loop().time() * 1000)}',
|
|
383
|
-
'ts': int(asyncio.get_event_loop().time() * 1000),
|
|
384
|
-
'payload': {
|
|
385
|
-
'token': self.token,
|
|
386
|
-
'name': self.name
|
|
387
|
-
}
|
|
388
|
-
})
|
|
542
|
+
# Auth happens via token header - trigger connect immediately
|
|
543
|
+
if 'connect' in self._handlers:
|
|
544
|
+
await self._handlers['connect']()
|
|
389
545
|
|
|
390
546
|
# Start message loop
|
|
391
547
|
await self._message_loop()
|
|
@@ -402,10 +558,7 @@ class SpawnAgent:
|
|
|
402
558
|
data = json.loads(message)
|
|
403
559
|
msg_type = data.get('type', '')
|
|
404
560
|
|
|
405
|
-
if msg_type == '
|
|
406
|
-
if 'connect' in self._handlers:
|
|
407
|
-
await self._handlers['connect']()
|
|
408
|
-
elif msg_type == 'message':
|
|
561
|
+
if msg_type == 'message':
|
|
409
562
|
if 'message' in self._handlers:
|
|
410
563
|
await self._handlers['message'](data)
|
|
411
564
|
elif msg_type == 'pong':
|
|
@@ -486,7 +639,7 @@ class SpawnAgent:
|
|
|
486
639
|
`;
|
|
487
640
|
}
|
|
488
641
|
|
|
489
|
-
async function createSkillFiles(token, agentName, language) {
|
|
642
|
+
async function createSkillFiles(token, agentName, language, claudeCode = false) {
|
|
490
643
|
const skillDir = path.join(process.cwd(), 'spawn');
|
|
491
644
|
|
|
492
645
|
// Create spawn directory
|
|
@@ -502,7 +655,8 @@ async function createSkillFiles(token, agentName, language) {
|
|
|
502
655
|
name: agentName,
|
|
503
656
|
token: token,
|
|
504
657
|
relay: 'wss://spawn-relay.ngvsqdjj5r.workers.dev/v1/agent',
|
|
505
|
-
language: language
|
|
658
|
+
language: language,
|
|
659
|
+
claudeCode: claudeCode
|
|
506
660
|
};
|
|
507
661
|
fs.writeFileSync(path.join(skillDir, 'config.json'), JSON.stringify(config, null, 2));
|
|
508
662
|
|
|
@@ -527,7 +681,45 @@ async function createSkillFiles(token, agentName, language) {
|
|
|
527
681
|
fs.writeFileSync(path.join(skillDir, 'requirements.txt'), 'websockets>=12.0\n');
|
|
528
682
|
}
|
|
529
683
|
|
|
530
|
-
|
|
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 };
|
|
531
723
|
}
|
|
532
724
|
|
|
533
725
|
async function main() {
|
|
@@ -591,18 +783,26 @@ async function main() {
|
|
|
591
783
|
});
|
|
592
784
|
}
|
|
593
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
|
+
|
|
594
793
|
const answers = await inquirer.prompt(questions);
|
|
595
794
|
|
|
596
795
|
token = token || answers.token;
|
|
597
796
|
agentName = answers.agentName || agentName;
|
|
598
797
|
language = language || answers.language;
|
|
798
|
+
const claudeCode = answers.claudeCode;
|
|
599
799
|
|
|
600
800
|
console.log('');
|
|
601
801
|
|
|
602
802
|
// Create files
|
|
603
803
|
const createSpinner = ora('Creating skill files...').start();
|
|
604
804
|
try {
|
|
605
|
-
const { skillDir } = await createSkillFiles(token, agentName, language);
|
|
805
|
+
const { skillDir } = await createSkillFiles(token, agentName, language, claudeCode);
|
|
606
806
|
createSpinner.succeed('Created skill files');
|
|
607
807
|
} catch (err) {
|
|
608
808
|
createSpinner.fail('Failed to create skill files');
|
|
@@ -610,6 +810,18 @@ async function main() {
|
|
|
610
810
|
process.exit(1);
|
|
611
811
|
}
|
|
612
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
|
+
|
|
613
825
|
console.log('');
|
|
614
826
|
console.log(chalk.green.bold('✓ Spawn.wtf skill installed!'));
|
|
615
827
|
console.log('');
|
|
@@ -620,14 +832,20 @@ async function main() {
|
|
|
620
832
|
console.log(dim(' spawn/config.json'));
|
|
621
833
|
console.log(dim(' spawn/connect.js'));
|
|
622
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
|
+
}
|
|
623
840
|
console.log('');
|
|
624
841
|
console.log(' Next steps:');
|
|
625
842
|
console.log('');
|
|
626
843
|
console.log(' 1. ' + chalk.cyan('Install dependencies:'));
|
|
627
|
-
console.log(' ' + dim('cd spawn
|
|
844
|
+
console.log(' ' + dim('cd spawn'));
|
|
845
|
+
console.log(' ' + dim('npm install'));
|
|
628
846
|
console.log('');
|
|
629
847
|
console.log(' 2. ' + chalk.cyan('Run the connector:'));
|
|
630
|
-
console.log(' ' + dim('node
|
|
848
|
+
console.log(' ' + dim('node connect.js'));
|
|
631
849
|
} else {
|
|
632
850
|
console.log(' Files created:');
|
|
633
851
|
console.log(dim(' spawn/SKILL.md'));
|
|
@@ -635,18 +853,49 @@ async function main() {
|
|
|
635
853
|
console.log(dim(' spawn/connect.py'));
|
|
636
854
|
console.log(dim(' spawn/spawn_sdk.py'));
|
|
637
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
|
+
}
|
|
638
861
|
console.log('');
|
|
639
862
|
console.log(' Next steps:');
|
|
640
863
|
console.log('');
|
|
641
864
|
console.log(' 1. ' + chalk.cyan('Install dependencies:'));
|
|
642
|
-
console.log(' ' + dim('cd spawn
|
|
865
|
+
console.log(' ' + dim('cd spawn'));
|
|
866
|
+
console.log(' ' + dim('pip install -r requirements.txt'));
|
|
643
867
|
console.log('');
|
|
644
868
|
console.log(' 2. ' + chalk.cyan('Run the connector:'));
|
|
645
|
-
console.log(' ' + dim('python
|
|
869
|
+
console.log(' ' + dim('python connect.py'));
|
|
646
870
|
}
|
|
647
871
|
|
|
648
872
|
console.log('');
|
|
649
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
|
+
|
|
650
899
|
console.log('');
|
|
651
900
|
console.log(' Docs: ' + pink('https://github.com/SpawnWTF/spawn'));
|
|
652
901
|
console.log('');
|