spawn-skill 1.3.5 → 1.4.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 (2) hide show
  1. package/bin/cli.js +128 -21
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -201,12 +201,14 @@ function getTypeScriptConnector(token, agentName) {
201
201
  */
202
202
 
203
203
  import WebSocket from 'ws';
204
+ import readline from 'readline';
204
205
 
205
206
  const TOKEN = '${token}';
206
207
  const NAME = '${agentName}';
207
208
  const RELAY = 'wss://spawn-relay.ngvsqdjj5r.workers.dev/v1/agent';
208
209
 
209
210
  let ws;
211
+ let pendingSpawnCallbacks = {};
210
212
 
211
213
  function send(type, payload) {
212
214
  if (ws?.readyState === WebSocket.OPEN) {
@@ -227,6 +229,42 @@ function updateStatus(status, label) {
227
229
  send('status_update', { status, label });
228
230
  }
229
231
 
232
+ // Request to spawn a sub-agent (requires user approval in app)
233
+ function requestSpawn(name, role, description, permissions = [], reason = '') {
234
+ const requestId = \`spawn_\${Date.now()}\`;
235
+
236
+ send('agent_spawn_request', {
237
+ request_id: requestId,
238
+ parent_id: NAME,
239
+ proposed_agent: {
240
+ name: name,
241
+ role: role,
242
+ description: description,
243
+ icon: 'bolt',
244
+ permissions: permissions,
245
+ lifespan: 'task',
246
+ estimated_duration: '5-10 minutes',
247
+ capabilities: []
248
+ },
249
+ reason: reason,
250
+ urgency: 'normal',
251
+ auto_approve_eligible: false,
252
+ expires_at: Math.floor(Date.now() / 1000) + 600
253
+ });
254
+
255
+ console.log(\`📤 Sent spawn request for sub-agent: \${name}\`);
256
+ return requestId;
257
+ }
258
+
259
+ // Notify that a sub-agent has finished
260
+ function terminateSubAgent(subAgentId) {
261
+ send('sub_agent_terminated', {
262
+ agent_id: subAgentId,
263
+ parent_id: NAME,
264
+ reason: 'Task completed'
265
+ });
266
+ }
267
+
230
268
  function connect() {
231
269
  ws = new WebSocket(RELAY, {
232
270
  headers: { 'Authorization': \`Bearer \${TOKEN}\` }
@@ -234,18 +272,24 @@ function connect() {
234
272
 
235
273
  ws.on('open', () => {
236
274
  console.log('Connected to Spawn.wtf!');
237
- // Auth happens via token in header - just start working
238
275
  sendText(\`\${NAME} is online and ready.\`);
239
276
  updateStatus('idle', 'Ready for commands');
277
+ console.log('');
278
+ console.log('Commands:');
279
+ console.log(' spawn <name> - Request to spawn a sub-agent');
280
+ console.log(' kill <name> - Terminate a sub-agent');
281
+ console.log(' say <text> - Send a message');
282
+ console.log(' status <s> - Update status (idle/working/thinking)');
283
+ console.log('');
240
284
  });
241
285
 
242
286
  ws.on('message', (data) => {
243
287
  const msg = JSON.parse(data.toString());
244
- console.log('Received:', msg.type);
288
+ console.log('📨 Received:', msg.type);
245
289
 
246
290
  if (msg.type === 'message') {
247
- console.log('Message from app:', msg.payload);
248
291
  const text = msg.payload?.text || '';
292
+ console.log('📱 From app:', text);
249
293
 
250
294
  updateStatus('thinking', 'Processing...');
251
295
  setTimeout(() => {
@@ -253,6 +297,19 @@ function connect() {
253
297
  updateStatus('idle', 'Ready');
254
298
  }, 500);
255
299
  }
300
+
301
+ // Handle spawn approval/rejection
302
+ if (msg.type === 'agent_spawn_response') {
303
+ const { request_id, decision } = msg.payload || {};
304
+ console.log(\`🔔 Spawn \${decision} for request \${request_id}\`);
305
+
306
+ if (decision === 'approved') {
307
+ // Spawn was approved - agent can now use the sub-agent
308
+ sendText('Sub-agent approved! Starting task...');
309
+ } else {
310
+ sendText('Sub-agent request was denied. Continuing without it.');
311
+ }
312
+ }
256
313
  });
257
314
 
258
315
  ws.on('close', () => {
@@ -264,6 +321,60 @@ function connect() {
264
321
  });
265
322
  }
266
323
 
324
+ // Interactive CLI
325
+ const rl = readline.createInterface({
326
+ input: process.stdin,
327
+ output: process.stdout
328
+ });
329
+
330
+ rl.on('line', (line) => {
331
+ const [cmd, ...args] = line.trim().split(' ');
332
+ const arg = args.join(' ');
333
+
334
+ switch (cmd) {
335
+ case 'spawn':
336
+ if (!arg) {
337
+ console.log('Usage: spawn <name>');
338
+ break;
339
+ }
340
+ requestSpawn(
341
+ arg,
342
+ 'Helper',
343
+ \`Sub-agent to help with \${arg} tasks\`,
344
+ [{ scope: 'files.read', path: '**/*', reason: 'Read project files' }],
345
+ 'Need parallel processing help'
346
+ );
347
+ break;
348
+
349
+ case 'kill':
350
+ if (!arg) {
351
+ console.log('Usage: kill <name>');
352
+ break;
353
+ }
354
+ terminateSubAgent(arg.toLowerCase().replace(/\\s+/g, '-'));
355
+ console.log(\`Terminated sub-agent: \${arg}\`);
356
+ break;
357
+
358
+ case 'say':
359
+ if (!arg) {
360
+ console.log('Usage: say <message>');
361
+ break;
362
+ }
363
+ sendText(arg);
364
+ break;
365
+
366
+ case 'status':
367
+ updateStatus(arg || 'idle', arg === 'working' ? 'Working...' : 'Ready');
368
+ console.log(\`Status updated to: \${arg || 'idle'}\`);
369
+ break;
370
+
371
+ default:
372
+ if (line.trim()) {
373
+ sendText(line.trim());
374
+ }
375
+ }
376
+ });
377
+
267
378
  // Keep alive
268
379
  setInterval(() => {
269
380
  if (ws?.readyState === WebSocket.OPEN) {
@@ -276,6 +387,7 @@ connect();
276
387
 
277
388
  process.on('SIGINT', () => {
278
389
  console.log('Shutting down...');
390
+ rl.close();
279
391
  ws?.close();
280
392
  process.exit(0);
281
393
  });
@@ -569,24 +681,19 @@ async function askMoltbot(message) {
569
681
  return;
570
682
  }
571
683
 
572
- // Build command based on whether we're using npx or direct path
573
- // Use --session-id for non-interactive mode
574
- let proc;
575
- const baseArgs = ['agent', '--message', message, '--thinking', 'high', '--session-id', 'spawn-session'];
576
-
577
- if (cmd.startsWith('npx ')) {
578
- // npx moltbot or npx clawdbot
579
- const pkgName = cmd.split(' ')[1];
580
- proc = spawn('npx', [pkgName, ...baseArgs], {
581
- shell: true,
582
- env: { ...process.env }
583
- });
584
- } else {
585
- proc = spawn(cmd, baseArgs, {
586
- shell: true,
587
- env: { ...process.env }
588
- });
589
- }
684
+ // Build command as a single string for shell execution
685
+ // Quote the message to handle spaces
686
+ const escapedMessage = message.replace(/"/g, '\\\\"');
687
+ const fullCmd = cmd.startsWith('npx ')
688
+ ? \`npx \${cmd.split(' ')[1]} agent --message "\${escapedMessage}" --thinking high --session-id spawn-session\`
689
+ : \`"\${cmd}" agent --message "\${escapedMessage}" --thinking high --session-id spawn-session\`;
690
+
691
+ console.log('Running:', fullCmd);
692
+
693
+ let proc = spawn(fullCmd, [], {
694
+ shell: true,
695
+ env: { ...process.env }
696
+ });
590
697
 
591
698
  let stdout = '';
592
699
  let stderr = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spawn-skill",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "description": "Connect your AI agent to Spawn.wtf",
5
5
  "bin": {
6
6
  "spawn-skill": "./bin/cli.js"