@gopherhole/cli 0.3.1 → 0.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 (3) hide show
  1. package/dist/index.js +177 -0
  2. package/package.json +1 -1
  3. package/src/index.ts +168 -0
package/dist/index.js CHANGED
@@ -1274,6 +1274,183 @@ ${chalk_1.default.bold('Examples:')}
1274
1274
  process.exit(1);
1275
1275
  }
1276
1276
  });
1277
+ // ========== TASK COMMANDS ==========
1278
+ const taskCmd = program
1279
+ .command('task')
1280
+ .description(`Manage tasks (queued messages, pending responses)
1281
+
1282
+ ${chalk_1.default.bold('Examples:')}
1283
+ $ gopherhole task status task-abc123
1284
+ $ gopherhole task pending
1285
+ $ gopherhole task cancel task-abc123
1286
+ $ gopherhole task cancel-all
1287
+ `);
1288
+ taskCmd
1289
+ .command('status <taskId>')
1290
+ .description('Check the status of a task and get the response if completed')
1291
+ .action(async (taskId) => {
1292
+ const sessionId = config.get('sessionId');
1293
+ if (!sessionId) {
1294
+ console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
1295
+ process.exit(1);
1296
+ }
1297
+ const spinner = (0, ora_1.default)(`Checking task ${taskId}...`).start();
1298
+ try {
1299
+ const res = await fetch(`${API_URL}/../a2a`, {
1300
+ method: 'POST',
1301
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1302
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'GetTask', params: { id: taskId }, id: 1 }),
1303
+ });
1304
+ const data = await res.json();
1305
+ if (data.error)
1306
+ throw new Error(data.error.message || 'Failed');
1307
+ const task = data.result;
1308
+ const state = task?.status?.state || 'unknown';
1309
+ spinner.stop();
1310
+ console.log(`\n${chalk_1.default.bold('Task:')} ${taskId}`);
1311
+ console.log(`${chalk_1.default.bold('State:')} ${stateColor(state)(state)}`);
1312
+ if (task?.status?.timestamp)
1313
+ console.log(`${chalk_1.default.bold('Time:')} ${task.status.timestamp}`);
1314
+ if (state === 'completed' && task?.artifacts?.length) {
1315
+ const texts = task.artifacts.flatMap((a) => a.parts?.filter((p) => p.kind === 'text').map((p) => p.text) || []);
1316
+ if (texts.length)
1317
+ console.log(`\n${chalk_1.default.bold('Response:')}\n${texts.join('\n')}`);
1318
+ }
1319
+ else if (state === 'submitted') {
1320
+ console.log(chalk_1.default.yellow('\n⏳ Queued — recipient hasn\'t come online yet.'));
1321
+ }
1322
+ else if (state === 'working') {
1323
+ console.log(chalk_1.default.blue('\n⚙️ Delivered — waiting for response.'));
1324
+ }
1325
+ else if (state === 'failed') {
1326
+ const msg = task?.status?.message;
1327
+ console.log(chalk_1.default.red(`\n❌ ${typeof msg === 'string' ? msg : 'Unknown error'}`));
1328
+ }
1329
+ }
1330
+ catch (err) {
1331
+ spinner.fail(chalk_1.default.red(err.message));
1332
+ process.exit(1);
1333
+ }
1334
+ });
1335
+ taskCmd
1336
+ .command('pending')
1337
+ .description('List all queued/pending tasks')
1338
+ .option('-l, --limit <n>', 'Max tasks to show', parseInt, 20)
1339
+ .action(async (opts) => {
1340
+ const sessionId = config.get('sessionId');
1341
+ if (!sessionId) {
1342
+ console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
1343
+ process.exit(1);
1344
+ }
1345
+ const spinner = (0, ora_1.default)('Fetching pending tasks...').start();
1346
+ try {
1347
+ const res = await fetch(`${API_URL}/../a2a`, {
1348
+ method: 'POST',
1349
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1350
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'ListTasks', params: { status: 'submitted', pageSize: opts.limit || 20 }, id: 1 }),
1351
+ });
1352
+ const data = await res.json();
1353
+ if (data.error)
1354
+ throw new Error(data.error.message || 'Failed');
1355
+ const tasks = data.result?.tasks || [];
1356
+ spinner.stop();
1357
+ if (tasks.length === 0) {
1358
+ console.log(chalk_1.default.green('✅ No pending tasks.'));
1359
+ return;
1360
+ }
1361
+ console.log(chalk_1.default.bold(`\n${tasks.length} pending task(s):\n`));
1362
+ for (const t of tasks) {
1363
+ const age = Date.now() - new Date(t.status?.timestamp || 0).getTime();
1364
+ const ageMins = Math.round(age / 60000);
1365
+ console.log(` ${chalk_1.default.gray(t.id)} → ${t.serverAgentId || 'unknown'} ${chalk_1.default.yellow(`(${ageMins}m ago)`)}`);
1366
+ }
1367
+ }
1368
+ catch (err) {
1369
+ spinner.fail(chalk_1.default.red(err.message));
1370
+ process.exit(1);
1371
+ }
1372
+ });
1373
+ taskCmd
1374
+ .command('cancel <taskId>')
1375
+ .description('Cancel a specific task and purge its queued messages')
1376
+ .action(async (taskId) => {
1377
+ const sessionId = config.get('sessionId');
1378
+ if (!sessionId) {
1379
+ console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
1380
+ process.exit(1);
1381
+ }
1382
+ const spinner = (0, ora_1.default)(`Canceling task ${taskId}...`).start();
1383
+ try {
1384
+ const res = await fetch(`${API_URL}/../a2a`, {
1385
+ method: 'POST',
1386
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1387
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'CancelTask', params: { id: taskId }, id: 1 }),
1388
+ });
1389
+ const data = await res.json();
1390
+ if (data.error)
1391
+ throw new Error(data.error.message || 'Failed');
1392
+ spinner.succeed(`Task ${taskId} canceled. Queued messages purged.`);
1393
+ }
1394
+ catch (err) {
1395
+ spinner.fail(chalk_1.default.red(err.message));
1396
+ process.exit(1);
1397
+ }
1398
+ });
1399
+ taskCmd
1400
+ .command('cancel-all')
1401
+ .description('Cancel ALL pending tasks and purge all queued messages')
1402
+ .action(async () => {
1403
+ const sessionId = config.get('sessionId');
1404
+ if (!sessionId) {
1405
+ console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
1406
+ process.exit(1);
1407
+ }
1408
+ const spinner = (0, ora_1.default)('Fetching pending tasks...').start();
1409
+ try {
1410
+ const res = await fetch(`${API_URL}/../a2a`, {
1411
+ method: 'POST',
1412
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1413
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'ListTasks', params: { status: 'submitted', pageSize: 100 }, id: 1 }),
1414
+ });
1415
+ const data = await res.json();
1416
+ if (data.error)
1417
+ throw new Error(data.error.message || 'Failed');
1418
+ const tasks = data.result?.tasks || [];
1419
+ if (tasks.length === 0) {
1420
+ spinner.succeed('No pending tasks to cancel.');
1421
+ return;
1422
+ }
1423
+ spinner.text = `Canceling ${tasks.length} task(s)...`;
1424
+ let canceled = 0;
1425
+ for (const t of tasks) {
1426
+ try {
1427
+ await fetch(`${API_URL}/../a2a`, {
1428
+ method: 'POST',
1429
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1430
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'CancelTask', params: { id: t.id }, id: 1 }),
1431
+ });
1432
+ canceled++;
1433
+ }
1434
+ catch { /* skip */ }
1435
+ }
1436
+ spinner.succeed(`Canceled ${canceled}/${tasks.length} task(s). Queued messages purged.`);
1437
+ }
1438
+ catch (err) {
1439
+ spinner.fail(chalk_1.default.red(err.message));
1440
+ process.exit(1);
1441
+ }
1442
+ });
1443
+ function stateColor(state) {
1444
+ switch (state) {
1445
+ case 'completed': return chalk_1.default.green;
1446
+ case 'submitted': return chalk_1.default.yellow;
1447
+ case 'working': return chalk_1.default.blue;
1448
+ case 'failed':
1449
+ case 'canceled':
1450
+ case 'rejected': return chalk_1.default.red;
1451
+ default: return chalk_1.default.gray;
1452
+ }
1453
+ }
1277
1454
  // ========== DISCOVER COMMANDS ==========
1278
1455
  const discover = program
1279
1456
  .command('discover')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gopherhole/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "GopherHole CLI - Connect AI agents to the world",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -1412,6 +1412,174 @@ ${chalk.bold('Examples:')}
1412
1412
  }
1413
1413
  });
1414
1414
 
1415
+ // ========== TASK COMMANDS ==========
1416
+
1417
+ const taskCmd = program
1418
+ .command('task')
1419
+ .description(`Manage tasks (queued messages, pending responses)
1420
+
1421
+ ${chalk.bold('Examples:')}
1422
+ $ gopherhole task status task-abc123
1423
+ $ gopherhole task pending
1424
+ $ gopherhole task cancel task-abc123
1425
+ $ gopherhole task cancel-all
1426
+ `);
1427
+
1428
+ taskCmd
1429
+ .command('status <taskId>')
1430
+ .description('Check the status of a task and get the response if completed')
1431
+ .action(async (taskId) => {
1432
+ const sessionId = config.get('sessionId') as string;
1433
+ if (!sessionId) { console.log(chalk.yellow('Not logged in. Run: gopherhole login')); process.exit(1); }
1434
+
1435
+ const spinner = ora(`Checking task ${taskId}...`).start();
1436
+ try {
1437
+ const res = await fetch(`${API_URL}/../a2a`, {
1438
+ method: 'POST',
1439
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1440
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'GetTask', params: { id: taskId }, id: 1 }),
1441
+ });
1442
+ const data = await res.json() as any;
1443
+ if (data.error) throw new Error(data.error.message || 'Failed');
1444
+
1445
+ const task = data.result;
1446
+ const state = task?.status?.state || 'unknown';
1447
+ spinner.stop();
1448
+
1449
+ console.log(`\n${chalk.bold('Task:')} ${taskId}`);
1450
+ console.log(`${chalk.bold('State:')} ${stateColor(state)(state)}`);
1451
+ if (task?.status?.timestamp) console.log(`${chalk.bold('Time:')} ${task.status.timestamp}`);
1452
+
1453
+ if (state === 'completed' && task?.artifacts?.length) {
1454
+ const texts = task.artifacts.flatMap((a: any) => a.parts?.filter((p: any) => p.kind === 'text').map((p: any) => p.text) || []);
1455
+ if (texts.length) console.log(`\n${chalk.bold('Response:')}\n${texts.join('\n')}`);
1456
+ } else if (state === 'submitted') {
1457
+ console.log(chalk.yellow('\n⏳ Queued — recipient hasn\'t come online yet.'));
1458
+ } else if (state === 'working') {
1459
+ console.log(chalk.blue('\n⚙️ Delivered — waiting for response.'));
1460
+ } else if (state === 'failed') {
1461
+ const msg = task?.status?.message;
1462
+ console.log(chalk.red(`\n❌ ${typeof msg === 'string' ? msg : 'Unknown error'}`));
1463
+ }
1464
+ } catch (err) {
1465
+ spinner.fail(chalk.red((err as Error).message));
1466
+ process.exit(1);
1467
+ }
1468
+ });
1469
+
1470
+ taskCmd
1471
+ .command('pending')
1472
+ .description('List all queued/pending tasks')
1473
+ .option('-l, --limit <n>', 'Max tasks to show', parseInt, 20)
1474
+ .action(async (opts) => {
1475
+ const sessionId = config.get('sessionId') as string;
1476
+ if (!sessionId) { console.log(chalk.yellow('Not logged in. Run: gopherhole login')); process.exit(1); }
1477
+
1478
+ const spinner = ora('Fetching pending tasks...').start();
1479
+ try {
1480
+ const res = await fetch(`${API_URL}/../a2a`, {
1481
+ method: 'POST',
1482
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1483
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'ListTasks', params: { status: 'submitted', pageSize: opts.limit || 20 }, id: 1 }),
1484
+ });
1485
+ const data = await res.json() as any;
1486
+ if (data.error) throw new Error(data.error.message || 'Failed');
1487
+
1488
+ const tasks = data.result?.tasks || [];
1489
+ spinner.stop();
1490
+
1491
+ if (tasks.length === 0) {
1492
+ console.log(chalk.green('✅ No pending tasks.'));
1493
+ return;
1494
+ }
1495
+
1496
+ console.log(chalk.bold(`\n${tasks.length} pending task(s):\n`));
1497
+ for (const t of tasks) {
1498
+ const age = Date.now() - new Date(t.status?.timestamp || 0).getTime();
1499
+ const ageMins = Math.round(age / 60000);
1500
+ console.log(` ${chalk.gray(t.id)} → ${t.serverAgentId || 'unknown'} ${chalk.yellow(`(${ageMins}m ago)`)}`);
1501
+ }
1502
+ } catch (err) {
1503
+ spinner.fail(chalk.red((err as Error).message));
1504
+ process.exit(1);
1505
+ }
1506
+ });
1507
+
1508
+ taskCmd
1509
+ .command('cancel <taskId>')
1510
+ .description('Cancel a specific task and purge its queued messages')
1511
+ .action(async (taskId) => {
1512
+ const sessionId = config.get('sessionId') as string;
1513
+ if (!sessionId) { console.log(chalk.yellow('Not logged in. Run: gopherhole login')); process.exit(1); }
1514
+
1515
+ const spinner = ora(`Canceling task ${taskId}...`).start();
1516
+ try {
1517
+ const res = await fetch(`${API_URL}/../a2a`, {
1518
+ method: 'POST',
1519
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1520
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'CancelTask', params: { id: taskId }, id: 1 }),
1521
+ });
1522
+ const data = await res.json() as any;
1523
+ if (data.error) throw new Error(data.error.message || 'Failed');
1524
+ spinner.succeed(`Task ${taskId} canceled. Queued messages purged.`);
1525
+ } catch (err) {
1526
+ spinner.fail(chalk.red((err as Error).message));
1527
+ process.exit(1);
1528
+ }
1529
+ });
1530
+
1531
+ taskCmd
1532
+ .command('cancel-all')
1533
+ .description('Cancel ALL pending tasks and purge all queued messages')
1534
+ .action(async () => {
1535
+ const sessionId = config.get('sessionId') as string;
1536
+ if (!sessionId) { console.log(chalk.yellow('Not logged in. Run: gopherhole login')); process.exit(1); }
1537
+
1538
+ const spinner = ora('Fetching pending tasks...').start();
1539
+ try {
1540
+ const res = await fetch(`${API_URL}/../a2a`, {
1541
+ method: 'POST',
1542
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1543
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'ListTasks', params: { status: 'submitted', pageSize: 100 }, id: 1 }),
1544
+ });
1545
+ const data = await res.json() as any;
1546
+ if (data.error) throw new Error(data.error.message || 'Failed');
1547
+
1548
+ const tasks = data.result?.tasks || [];
1549
+ if (tasks.length === 0) {
1550
+ spinner.succeed('No pending tasks to cancel.');
1551
+ return;
1552
+ }
1553
+
1554
+ spinner.text = `Canceling ${tasks.length} task(s)...`;
1555
+ let canceled = 0;
1556
+ for (const t of tasks) {
1557
+ try {
1558
+ await fetch(`${API_URL}/../a2a`, {
1559
+ method: 'POST',
1560
+ headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
1561
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'CancelTask', params: { id: t.id }, id: 1 }),
1562
+ });
1563
+ canceled++;
1564
+ } catch { /* skip */ }
1565
+ }
1566
+ spinner.succeed(`Canceled ${canceled}/${tasks.length} task(s). Queued messages purged.`);
1567
+ } catch (err) {
1568
+ spinner.fail(chalk.red((err as Error).message));
1569
+ process.exit(1);
1570
+ }
1571
+ });
1572
+
1573
+ function stateColor(state: string) {
1574
+ switch (state) {
1575
+ case 'completed': return chalk.green;
1576
+ case 'submitted': return chalk.yellow;
1577
+ case 'working': return chalk.blue;
1578
+ case 'failed': case 'canceled': case 'rejected': return chalk.red;
1579
+ default: return chalk.gray;
1580
+ }
1581
+ }
1582
+
1415
1583
  // ========== DISCOVER COMMANDS ==========
1416
1584
 
1417
1585
  const discover = program