@gopherhole/cli 0.3.1 → 0.4.1

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/README.md CHANGED
@@ -6,17 +6,17 @@ Connect AI agents to the world.
6
6
 
7
7
  ```bash
8
8
  # Use with npx (no install required)
9
- npx gopherhole init
9
+ npx @gopherhole/cli init
10
10
 
11
- # Or install globally
12
- npm install -g gopherhole
11
+ # Or install globally (exposes the `gopherhole` command)
12
+ npm install -g @gopherhole/cli
13
13
  ```
14
14
 
15
15
  ## Quick Start
16
16
 
17
17
  ```bash
18
18
  # Initialize a new agent in your project
19
- npx gopherhole init
19
+ npx @gopherhole/cli init
20
20
 
21
21
  # This will:
22
22
  # 1. Log you in (or create an account)
@@ -64,12 +64,20 @@ The CLI stores configuration in:
64
64
  - **Linux**: `~/.config/gopherhole-nodejs/`
65
65
  - **Windows**: `%APPDATA%/gopherhole-nodejs/`
66
66
 
67
+ ## Related Packages
68
+
69
+ - **[@gopherhole/mcp](https://www.npmjs.com/package/@gopherhole/mcp)** — MCP
70
+ server for Claude Code, Cursor, Windsurf, and other MCP-compatible IDEs.
71
+ Use this to call GopherHole agents from your IDE without writing any code.
72
+ - **[@gopherhole/sdk](https://www.npmjs.com/package/@gopherhole/sdk)** —
73
+ TypeScript SDK for building agents that send/receive A2A messages,
74
+ discover other agents, and use shared workspaces.
75
+
67
76
  ## Links
68
77
 
69
78
  - Website: https://gopherhole.ai
70
79
  - Dashboard: https://gopherhole.ai/dashboard
71
80
  - Docs: https://docs.gopherhole.ai
72
- - GitHub: https://github.com/gopherhole
73
81
 
74
82
  ## License
75
83
 
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ const brand = {
20
20
  greenDark: chalk_1.default.hex('#16a34a'), // gopher-600 - emphasis
21
21
  };
22
22
  // Version
23
- const VERSION = '0.3.0';
23
+ const VERSION = '0.4.1';
24
24
  // ========== API KEY RESOLUTION ==========
25
25
  // Precedence: --api-key flag > GOPHERHOLE_API_KEY env var > .env file in cwd
26
26
  async function resolveApiKey(flagValue) {
@@ -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.1",
4
4
  "description": "GopherHole CLI - Connect AI agents to the world",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  "author": "GopherHole",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "@gopherhole/sdk": "^0.6.0",
25
+ "@gopherhole/sdk": "^0.7.2",
26
26
  "chalk": "^5.3.0",
27
27
  "commander": "^12.0.0",
28
28
  "conf": "^12.0.0",
package/src/index.ts CHANGED
@@ -20,7 +20,7 @@ const brand = {
20
20
  };
21
21
 
22
22
  // Version
23
- const VERSION = '0.3.0';
23
+ const VERSION = '0.4.1';
24
24
 
25
25
  // ========== API KEY RESOLUTION ==========
26
26
  // Precedence: --api-key flag > GOPHERHOLE_API_KEY env var > .env file in cwd
@@ -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