spawn-skill 1.0.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 +7 -0
- package/bin/cli.js +619 -0
- package/package.json +24 -0
package/bin/CLAUDE.md
ADDED
package/bin/cli.js
ADDED
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
// Colors matching Spawn.wtf brand
|
|
13
|
+
const pink = chalk.hex('#ff2d92');
|
|
14
|
+
const dim = chalk.gray;
|
|
15
|
+
|
|
16
|
+
function printBanner() {
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log(pink(' ╔═══════════════════════════════════════╗'));
|
|
19
|
+
console.log(pink(' ║') + chalk.white.bold(' SPAWN.WTF Skill Setup ') + pink('║'));
|
|
20
|
+
console.log(pink(' ╚═══════════════════════════════════════╝'));
|
|
21
|
+
console.log('');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function printSuccess(message) {
|
|
25
|
+
console.log(chalk.green('✓') + ' ' + message);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const SKILL_MD = `# Spawn Integration Skill
|
|
29
|
+
|
|
30
|
+
## Overview
|
|
31
|
+
|
|
32
|
+
You are connected to **Spawn**, a mobile/desktop app that serves as your user interface. Instead of raw terminal output, your responses are rendered as rich UI components on the user's phone or computer.
|
|
33
|
+
|
|
34
|
+
This skill teaches you how to:
|
|
35
|
+
|
|
36
|
+
1. Communicate through structured UI components
|
|
37
|
+
2. Request approval for sensitive actions
|
|
38
|
+
3. Spawn and manage sub-agents
|
|
39
|
+
4. Respect the user's safety settings
|
|
40
|
+
|
|
41
|
+
**Important:** The user has configured safety settings in the Spawn app. You must respect these settings. They represent the user's explicit preferences about what you can do autonomously vs. what requires approval.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Communication Protocol
|
|
46
|
+
|
|
47
|
+
### Sending Messages
|
|
48
|
+
|
|
49
|
+
\`\`\`python
|
|
50
|
+
from spawn import ui
|
|
51
|
+
|
|
52
|
+
# Simple text
|
|
53
|
+
ui.send_text("I've analyzed the codebase. Found 3 issues.")
|
|
54
|
+
|
|
55
|
+
# Card with stats
|
|
56
|
+
ui.send_card(
|
|
57
|
+
title="Code Analysis",
|
|
58
|
+
value="3 issues",
|
|
59
|
+
style="warning",
|
|
60
|
+
fields=[
|
|
61
|
+
{"label": "Critical", "value": "1"},
|
|
62
|
+
{"label": "Warning", "value": "2"},
|
|
63
|
+
{"label": "Files scanned", "value": "47"},
|
|
64
|
+
]
|
|
65
|
+
)
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
### Status Updates
|
|
69
|
+
|
|
70
|
+
Keep the user informed about what you're doing:
|
|
71
|
+
|
|
72
|
+
\`\`\`python
|
|
73
|
+
from spawn import status
|
|
74
|
+
|
|
75
|
+
status.set("thinking", "Analyzing codebase...")
|
|
76
|
+
status.set("working", "Refactoring auth.py (3/12)")
|
|
77
|
+
status.set("idle") # When done
|
|
78
|
+
\`\`\`
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Approval Flows
|
|
83
|
+
|
|
84
|
+
### When to Ask for Approval
|
|
85
|
+
|
|
86
|
+
**Always ask** when:
|
|
87
|
+
- The action involves money or trades
|
|
88
|
+
- The action is irreversible (delete, deploy, send)
|
|
89
|
+
- You're unsure if the user would want this
|
|
90
|
+
|
|
91
|
+
**Never ask** when:
|
|
92
|
+
- The action is read-only
|
|
93
|
+
- You're just gathering information
|
|
94
|
+
|
|
95
|
+
### How to Request Approval
|
|
96
|
+
|
|
97
|
+
\`\`\`python
|
|
98
|
+
from spawn import approval
|
|
99
|
+
|
|
100
|
+
approved = await approval.confirm(
|
|
101
|
+
title="Delete old logs?",
|
|
102
|
+
message="This will remove 47 log files older than 30 days.",
|
|
103
|
+
danger_level="medium" # low, medium, high, critical
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if approved:
|
|
107
|
+
delete_logs()
|
|
108
|
+
else:
|
|
109
|
+
ui.send_text("Okay, I won't delete the logs.")
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
### Danger Levels
|
|
113
|
+
|
|
114
|
+
| Level | When to Use | UI Treatment |
|
|
115
|
+
|-------|-------------|--------------|
|
|
116
|
+
| \`low\` | Reversible, low impact | Tap to confirm |
|
|
117
|
+
| \`medium\` | Moderate impact, recoverable | Yellow card, tap to confirm |
|
|
118
|
+
| \`high\` | Significant impact, hard to reverse | Red card, slide to confirm |
|
|
119
|
+
| \`critical\` | Irreversible, financial, destructive | Red card, biometric required |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Sub-Agent Spawning
|
|
124
|
+
|
|
125
|
+
### Requesting a Sub-Agent
|
|
126
|
+
|
|
127
|
+
\`\`\`python
|
|
128
|
+
from spawn import agents
|
|
129
|
+
|
|
130
|
+
sub = await agents.request_spawn(
|
|
131
|
+
name="TestRunner",
|
|
132
|
+
role="QA Engineer",
|
|
133
|
+
description="Run tests in parallel while I refactor",
|
|
134
|
+
permissions=[
|
|
135
|
+
{"scope": "files.read", "path": "/projects/tests/**"},
|
|
136
|
+
{"scope": "process.execute", "command": "pytest"},
|
|
137
|
+
],
|
|
138
|
+
reason="I need parallel test execution to catch breaking changes quickly."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if sub is None:
|
|
142
|
+
ui.send_text("Understood, I'll run tests sequentially instead.")
|
|
143
|
+
else:
|
|
144
|
+
await sub.start()
|
|
145
|
+
\`\`\`
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Path Restrictions
|
|
150
|
+
|
|
151
|
+
Before accessing any file, check if it's allowed:
|
|
152
|
+
|
|
153
|
+
\`\`\`python
|
|
154
|
+
from spawn import policy
|
|
155
|
+
|
|
156
|
+
if policy.is_path_forbidden(path):
|
|
157
|
+
ui.send_text("I can't access that path - it's protected.")
|
|
158
|
+
return
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
### Common Forbidden Paths
|
|
162
|
+
|
|
163
|
+
- \`~/.ssh/**\` - SSH keys
|
|
164
|
+
- \`~/.aws/**\` - AWS credentials
|
|
165
|
+
- \`**/.env\` - Environment files
|
|
166
|
+
- \`**/*.pem\`, \`**/*.key\` - Private keys
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Notifications
|
|
171
|
+
|
|
172
|
+
Send push notifications for important events:
|
|
173
|
+
|
|
174
|
+
\`\`\`python
|
|
175
|
+
from spawn import notify
|
|
176
|
+
|
|
177
|
+
notify.send(
|
|
178
|
+
title="Refactoring Complete",
|
|
179
|
+
body="All 12 files updated, tests passing.",
|
|
180
|
+
priority="normal" # silent, normal, high, critical
|
|
181
|
+
)
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Best Practices
|
|
187
|
+
|
|
188
|
+
1. **Always check settings first** before taking actions
|
|
189
|
+
2. **Prefer asking over assuming** - users prefer being asked
|
|
190
|
+
3. **Keep user informed** - update status during long operations
|
|
191
|
+
4. **Graceful degradation** - if denied, explain alternatives
|
|
192
|
+
5. **Respect the spirit** - don't exploit technical loopholes
|
|
193
|
+
|
|
194
|
+
The user trusts you enough to give you capabilities. Honor that trust by respecting their configured limits.
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
function getTypeScriptConnector(token, agentName) {
|
|
198
|
+
return `/**
|
|
199
|
+
* Spawn.wtf Agent Connector
|
|
200
|
+
* Run with: node connect.js
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
const { SpawnAgent } = require('./node_modules/@spawn/agent-sdk/dist/index.js');
|
|
204
|
+
|
|
205
|
+
const agent = new SpawnAgent({
|
|
206
|
+
token: '${token}',
|
|
207
|
+
name: '${agentName}',
|
|
208
|
+
|
|
209
|
+
onConnect: () => {
|
|
210
|
+
console.log('Connected to Spawn.wtf!');
|
|
211
|
+
agent.sendText('${agentName} is online and ready.');
|
|
212
|
+
agent.updateStatus('idle', 'Ready for commands');
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
onMessage: (msg) => {
|
|
216
|
+
console.log('Message from app:', msg);
|
|
217
|
+
|
|
218
|
+
if (msg.type === 'message') {
|
|
219
|
+
const text = msg.payload.text || '';
|
|
220
|
+
agent.updateStatus('thinking', 'Processing...');
|
|
221
|
+
|
|
222
|
+
// Echo back
|
|
223
|
+
setTimeout(() => {
|
|
224
|
+
agent.sendText(\`You said: "\${text}"\`);
|
|
225
|
+
agent.updateStatus('idle', 'Ready');
|
|
226
|
+
}, 500);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
onDisconnect: () => {
|
|
231
|
+
console.log('Disconnected from Spawn.wtf');
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
onError: (err) => {
|
|
235
|
+
console.error('Error:', err.message);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
agent.connect();
|
|
240
|
+
console.log('Connecting to Spawn.wtf...');
|
|
241
|
+
|
|
242
|
+
process.on('SIGINT', () => {
|
|
243
|
+
agent.disconnect();
|
|
244
|
+
process.exit(0);
|
|
245
|
+
});
|
|
246
|
+
`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getPythonConnector(token, agentName) {
|
|
250
|
+
return `"""
|
|
251
|
+
Spawn.wtf Agent Connector
|
|
252
|
+
Run with: python connect.py
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
import asyncio
|
|
256
|
+
import json
|
|
257
|
+
import signal
|
|
258
|
+
import sys
|
|
259
|
+
from spawn_sdk import SpawnAgent
|
|
260
|
+
|
|
261
|
+
agent = SpawnAgent(
|
|
262
|
+
token='${token}',
|
|
263
|
+
name='${agentName}'
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
@agent.on('connect')
|
|
267
|
+
async def on_connect():
|
|
268
|
+
print('Connected to Spawn.wtf!')
|
|
269
|
+
await agent.send_text('${agentName} is online and ready.')
|
|
270
|
+
await agent.update_status('idle', 'Ready for commands')
|
|
271
|
+
|
|
272
|
+
@agent.on('message')
|
|
273
|
+
async def on_message(msg):
|
|
274
|
+
print(f'Message from app: {msg}')
|
|
275
|
+
|
|
276
|
+
if msg.get('type') == 'message':
|
|
277
|
+
text = msg.get('payload', {}).get('text', '')
|
|
278
|
+
await agent.update_status('thinking', 'Processing...')
|
|
279
|
+
|
|
280
|
+
# Echo back
|
|
281
|
+
await asyncio.sleep(0.5)
|
|
282
|
+
await agent.send_text(f'You said: "{text}"')
|
|
283
|
+
await agent.update_status('idle', 'Ready')
|
|
284
|
+
|
|
285
|
+
@agent.on('disconnect')
|
|
286
|
+
async def on_disconnect():
|
|
287
|
+
print('Disconnected from Spawn.wtf')
|
|
288
|
+
|
|
289
|
+
@agent.on('error')
|
|
290
|
+
async def on_error(err):
|
|
291
|
+
print(f'Error: {err}')
|
|
292
|
+
|
|
293
|
+
def signal_handler(sig, frame):
|
|
294
|
+
asyncio.create_task(agent.disconnect())
|
|
295
|
+
sys.exit(0)
|
|
296
|
+
|
|
297
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
298
|
+
|
|
299
|
+
if __name__ == '__main__':
|
|
300
|
+
print('Connecting to Spawn.wtf...')
|
|
301
|
+
asyncio.run(agent.connect())
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function getPythonSDK() {
|
|
306
|
+
return `"""
|
|
307
|
+
Spawn.wtf Python SDK
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
import asyncio
|
|
311
|
+
import json
|
|
312
|
+
import websockets
|
|
313
|
+
from typing import Callable, Optional, Dict, Any
|
|
314
|
+
|
|
315
|
+
class SpawnAgent:
|
|
316
|
+
def __init__(self, token: str, name: str, relay_url: str = 'wss://spawn-relay.ngvsqdjj5r.workers.dev/v1/agent'):
|
|
317
|
+
self.token = token
|
|
318
|
+
self.name = name
|
|
319
|
+
self.relay_url = relay_url
|
|
320
|
+
self.ws: Optional[websockets.WebSocketClientProtocol] = None
|
|
321
|
+
self._handlers: Dict[str, Callable] = {}
|
|
322
|
+
self._connected = False
|
|
323
|
+
|
|
324
|
+
def on(self, event: str):
|
|
325
|
+
"""Decorator to register event handlers"""
|
|
326
|
+
def decorator(func: Callable):
|
|
327
|
+
self._handlers[event] = func
|
|
328
|
+
return func
|
|
329
|
+
return decorator
|
|
330
|
+
|
|
331
|
+
async def connect(self):
|
|
332
|
+
"""Connect to the Spawn relay"""
|
|
333
|
+
try:
|
|
334
|
+
self.ws = await websockets.connect(
|
|
335
|
+
self.relay_url,
|
|
336
|
+
extra_headers={'Authorization': f'Bearer {self.token}'}
|
|
337
|
+
)
|
|
338
|
+
self._connected = True
|
|
339
|
+
|
|
340
|
+
# Send auth message
|
|
341
|
+
await self._send({
|
|
342
|
+
'type': 'auth',
|
|
343
|
+
'id': f'auth_{int(asyncio.get_event_loop().time() * 1000)}',
|
|
344
|
+
'ts': int(asyncio.get_event_loop().time() * 1000),
|
|
345
|
+
'payload': {
|
|
346
|
+
'token': self.token,
|
|
347
|
+
'name': self.name
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
# Start message loop
|
|
352
|
+
await self._message_loop()
|
|
353
|
+
|
|
354
|
+
except Exception as e:
|
|
355
|
+
if 'error' in self._handlers:
|
|
356
|
+
await self._handlers['error'](str(e))
|
|
357
|
+
raise
|
|
358
|
+
|
|
359
|
+
async def _message_loop(self):
|
|
360
|
+
"""Main message receiving loop"""
|
|
361
|
+
try:
|
|
362
|
+
async for message in self.ws:
|
|
363
|
+
data = json.loads(message)
|
|
364
|
+
msg_type = data.get('type', '')
|
|
365
|
+
|
|
366
|
+
if msg_type == 'auth_success':
|
|
367
|
+
if 'connect' in self._handlers:
|
|
368
|
+
await self._handlers['connect']()
|
|
369
|
+
elif msg_type == 'message':
|
|
370
|
+
if 'message' in self._handlers:
|
|
371
|
+
await self._handlers['message'](data)
|
|
372
|
+
elif msg_type == 'pong':
|
|
373
|
+
pass # Keep-alive response
|
|
374
|
+
|
|
375
|
+
except websockets.ConnectionClosed:
|
|
376
|
+
self._connected = False
|
|
377
|
+
if 'disconnect' in self._handlers:
|
|
378
|
+
await self._handlers['disconnect']()
|
|
379
|
+
|
|
380
|
+
async def _send(self, data: Dict[str, Any]):
|
|
381
|
+
"""Send a message to the relay"""
|
|
382
|
+
if self.ws and self._connected:
|
|
383
|
+
await self.ws.send(json.dumps(data))
|
|
384
|
+
|
|
385
|
+
async def send_text(self, text: str, format: str = 'plain'):
|
|
386
|
+
"""Send a text message"""
|
|
387
|
+
await self._send({
|
|
388
|
+
'type': 'message',
|
|
389
|
+
'id': f'msg_{int(asyncio.get_event_loop().time() * 1000)}',
|
|
390
|
+
'ts': int(asyncio.get_event_loop().time() * 1000),
|
|
391
|
+
'payload': {
|
|
392
|
+
'content_type': 'text',
|
|
393
|
+
'text': text,
|
|
394
|
+
'format': format
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
async def send_card(self, title: str, subtitle: str = None, style: str = 'default',
|
|
399
|
+
fields: list = None, footer: str = None):
|
|
400
|
+
"""Send a card message"""
|
|
401
|
+
await self._send({
|
|
402
|
+
'type': 'message',
|
|
403
|
+
'id': f'msg_{int(asyncio.get_event_loop().time() * 1000)}',
|
|
404
|
+
'ts': int(asyncio.get_event_loop().time() * 1000),
|
|
405
|
+
'payload': {
|
|
406
|
+
'content_type': 'card',
|
|
407
|
+
'card': {
|
|
408
|
+
'title': title,
|
|
409
|
+
'subtitle': subtitle,
|
|
410
|
+
'style': style,
|
|
411
|
+
'fields': fields or [],
|
|
412
|
+
'footer': footer
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
async def update_status(self, status: str, label: str = None):
|
|
418
|
+
"""Update agent status"""
|
|
419
|
+
await self._send({
|
|
420
|
+
'type': 'status_update',
|
|
421
|
+
'id': f'status_{int(asyncio.get_event_loop().time() * 1000)}',
|
|
422
|
+
'ts': int(asyncio.get_event_loop().time() * 1000),
|
|
423
|
+
'payload': {
|
|
424
|
+
'status': status,
|
|
425
|
+
'label': label
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
async def notify(self, title: str, body: str, priority: str = 'normal'):
|
|
430
|
+
"""Send a push notification"""
|
|
431
|
+
await self._send({
|
|
432
|
+
'type': 'notification',
|
|
433
|
+
'id': f'notif_{int(asyncio.get_event_loop().time() * 1000)}',
|
|
434
|
+
'ts': int(asyncio.get_event_loop().time() * 1000),
|
|
435
|
+
'payload': {
|
|
436
|
+
'title': title,
|
|
437
|
+
'body': body,
|
|
438
|
+
'priority': priority
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
async def disconnect(self):
|
|
443
|
+
"""Disconnect from the relay"""
|
|
444
|
+
if self.ws:
|
|
445
|
+
await self.ws.close()
|
|
446
|
+
self._connected = False
|
|
447
|
+
`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function createSkillFiles(token, agentName, language) {
|
|
451
|
+
const skillDir = path.join(process.cwd(), 'spawn');
|
|
452
|
+
|
|
453
|
+
// Create spawn directory
|
|
454
|
+
if (!fs.existsSync(skillDir)) {
|
|
455
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Create SKILL.md
|
|
459
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), SKILL_MD);
|
|
460
|
+
|
|
461
|
+
// Create config file
|
|
462
|
+
const config = {
|
|
463
|
+
name: agentName,
|
|
464
|
+
token: token,
|
|
465
|
+
relay: 'wss://spawn-relay.ngvsqdjj5r.workers.dev/v1/agent',
|
|
466
|
+
language: language
|
|
467
|
+
};
|
|
468
|
+
fs.writeFileSync(path.join(skillDir, 'config.json'), JSON.stringify(config, null, 2));
|
|
469
|
+
|
|
470
|
+
if (language === 'typescript') {
|
|
471
|
+
// TypeScript/Node setup
|
|
472
|
+
fs.writeFileSync(path.join(skillDir, 'connect.js'), getTypeScriptConnector(token, agentName));
|
|
473
|
+
|
|
474
|
+
const packageJson = {
|
|
475
|
+
name: "spawn-agent",
|
|
476
|
+
version: "1.0.0",
|
|
477
|
+
type: "commonjs",
|
|
478
|
+
dependencies: {
|
|
479
|
+
"@spawn/agent-sdk": "github:SpawnWTF/spawn#main:sdk"
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
fs.writeFileSync(path.join(skillDir, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
483
|
+
|
|
484
|
+
} else {
|
|
485
|
+
// Python setup
|
|
486
|
+
fs.writeFileSync(path.join(skillDir, 'connect.py'), getPythonConnector(token, agentName));
|
|
487
|
+
fs.writeFileSync(path.join(skillDir, 'spawn_sdk.py'), getPythonSDK());
|
|
488
|
+
fs.writeFileSync(path.join(skillDir, 'requirements.txt'), 'websockets>=12.0\n');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return { skillDir, language };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function main() {
|
|
495
|
+
printBanner();
|
|
496
|
+
|
|
497
|
+
// Parse arguments
|
|
498
|
+
const args = process.argv.slice(2);
|
|
499
|
+
let token = null;
|
|
500
|
+
let agentName = 'Claude';
|
|
501
|
+
let language = null;
|
|
502
|
+
|
|
503
|
+
for (let i = 0; i < args.length; i++) {
|
|
504
|
+
if (args[i] === '--token' || args[i] === '-t') {
|
|
505
|
+
token = args[i + 1];
|
|
506
|
+
}
|
|
507
|
+
if (args[i] === '--name' || args[i] === '-n') {
|
|
508
|
+
agentName = args[i + 1];
|
|
509
|
+
}
|
|
510
|
+
if (args[i] === '--python' || args[i] === '-py') {
|
|
511
|
+
language = 'python';
|
|
512
|
+
}
|
|
513
|
+
if (args[i] === '--node' || args[i] === '--typescript' || args[i] === '-ts') {
|
|
514
|
+
language = 'typescript';
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Interactive prompts
|
|
519
|
+
const questions = [];
|
|
520
|
+
|
|
521
|
+
if (!token) {
|
|
522
|
+
questions.push({
|
|
523
|
+
type: 'password',
|
|
524
|
+
name: 'token',
|
|
525
|
+
message: 'Enter your Spawn.wtf agent token:',
|
|
526
|
+
mask: '*',
|
|
527
|
+
validate: (input) => {
|
|
528
|
+
if (!input.startsWith('spwn_sk_')) {
|
|
529
|
+
return 'Token should start with spwn_sk_';
|
|
530
|
+
}
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
questions.push({
|
|
537
|
+
type: 'input',
|
|
538
|
+
name: 'agentName',
|
|
539
|
+
message: 'Agent name:',
|
|
540
|
+
default: agentName
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
if (!language) {
|
|
544
|
+
questions.push({
|
|
545
|
+
type: 'list',
|
|
546
|
+
name: 'language',
|
|
547
|
+
message: 'Choose your language:',
|
|
548
|
+
choices: [
|
|
549
|
+
{ name: 'TypeScript / Node.js', value: 'typescript' },
|
|
550
|
+
{ name: 'Python', value: 'python' }
|
|
551
|
+
]
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const answers = await inquirer.prompt(questions);
|
|
556
|
+
|
|
557
|
+
token = token || answers.token;
|
|
558
|
+
agentName = answers.agentName || agentName;
|
|
559
|
+
language = language || answers.language;
|
|
560
|
+
|
|
561
|
+
console.log('');
|
|
562
|
+
|
|
563
|
+
// Create files
|
|
564
|
+
const createSpinner = ora('Creating skill files...').start();
|
|
565
|
+
try {
|
|
566
|
+
const { skillDir } = await createSkillFiles(token, agentName, language);
|
|
567
|
+
createSpinner.succeed('Created skill files');
|
|
568
|
+
} catch (err) {
|
|
569
|
+
createSpinner.fail('Failed to create skill files');
|
|
570
|
+
console.error(err.message);
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
console.log('');
|
|
575
|
+
console.log(chalk.green.bold('✓ Spawn.wtf skill installed!'));
|
|
576
|
+
console.log('');
|
|
577
|
+
|
|
578
|
+
if (language === 'typescript') {
|
|
579
|
+
console.log(' Files created:');
|
|
580
|
+
console.log(dim(' spawn/SKILL.md'));
|
|
581
|
+
console.log(dim(' spawn/config.json'));
|
|
582
|
+
console.log(dim(' spawn/connect.js'));
|
|
583
|
+
console.log(dim(' spawn/package.json'));
|
|
584
|
+
console.log('');
|
|
585
|
+
console.log(' Next steps:');
|
|
586
|
+
console.log('');
|
|
587
|
+
console.log(' 1. ' + chalk.cyan('Install dependencies:'));
|
|
588
|
+
console.log(' ' + dim('cd spawn && npm install'));
|
|
589
|
+
console.log('');
|
|
590
|
+
console.log(' 2. ' + chalk.cyan('Run the connector:'));
|
|
591
|
+
console.log(' ' + dim('node spawn/connect.js'));
|
|
592
|
+
} else {
|
|
593
|
+
console.log(' Files created:');
|
|
594
|
+
console.log(dim(' spawn/SKILL.md'));
|
|
595
|
+
console.log(dim(' spawn/config.json'));
|
|
596
|
+
console.log(dim(' spawn/connect.py'));
|
|
597
|
+
console.log(dim(' spawn/spawn_sdk.py'));
|
|
598
|
+
console.log(dim(' spawn/requirements.txt'));
|
|
599
|
+
console.log('');
|
|
600
|
+
console.log(' Next steps:');
|
|
601
|
+
console.log('');
|
|
602
|
+
console.log(' 1. ' + chalk.cyan('Install dependencies:'));
|
|
603
|
+
console.log(' ' + dim('cd spawn && pip install -r requirements.txt'));
|
|
604
|
+
console.log('');
|
|
605
|
+
console.log(' 2. ' + chalk.cyan('Run the connector:'));
|
|
606
|
+
console.log(' ' + dim('python spawn/connect.py'));
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
console.log('');
|
|
610
|
+
console.log(' 3. ' + chalk.cyan('Open Spawn.wtf app') + ' - your agent should appear!');
|
|
611
|
+
console.log('');
|
|
612
|
+
console.log(' Docs: ' + pink('https://github.com/SpawnWTF/spawn'));
|
|
613
|
+
console.log('');
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
main().catch((err) => {
|
|
617
|
+
console.error('Error:', err.message);
|
|
618
|
+
process.exit(1);
|
|
619
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spawn-skill",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Connect your AI agent to Spawn.wtf",
|
|
5
|
+
"bin": {
|
|
6
|
+
"spawn-skill": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"files": [
|
|
10
|
+
"bin"
|
|
11
|
+
],
|
|
12
|
+
"keywords": ["spawn", "ai", "agent", "claude", "monitoring"],
|
|
13
|
+
"author": "Spawn.wtf",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/SpawnWTF/spawn"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"chalk": "^5.3.0",
|
|
21
|
+
"inquirer": "^9.2.12",
|
|
22
|
+
"ora": "^8.0.1"
|
|
23
|
+
}
|
|
24
|
+
}
|