@gopherhole/cli 0.3.0 → 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.
- package/dist/index.js +191 -3
- package/package.json +1 -1
- package/src/index.ts +182 -3
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.
|
|
23
|
+
const VERSION = '0.3.0';
|
|
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) {
|
|
@@ -91,6 +91,7 @@ async function askAgent(client, agentId, text) {
|
|
|
91
91
|
const start = Date.now();
|
|
92
92
|
const maxWait = 60_000;
|
|
93
93
|
const poll = 1_000;
|
|
94
|
+
let printedQueued = false;
|
|
94
95
|
while (!terminalStates.includes(current.status.state)) {
|
|
95
96
|
if (current.status.state === 'input-required') {
|
|
96
97
|
throw new Error('Agent requires additional input (not supported in CLI mode)');
|
|
@@ -98,6 +99,10 @@ async function askAgent(client, agentId, text) {
|
|
|
98
99
|
if (current.status.state === 'auth-required') {
|
|
99
100
|
throw new Error('Agent requires authentication — check your API key or request access');
|
|
100
101
|
}
|
|
102
|
+
if (current.status.state === 'submitted' && !printedQueued) {
|
|
103
|
+
console.log(chalk_1.default.yellow('⏳ Message queued — recipient is offline. Waiting for delivery...'));
|
|
104
|
+
printedQueued = true;
|
|
105
|
+
}
|
|
101
106
|
if (Date.now() - start > maxWait)
|
|
102
107
|
throw new Error('Timed out waiting for agent response');
|
|
103
108
|
await new Promise(r => setTimeout(r, poll));
|
|
@@ -1216,8 +1221,11 @@ program
|
|
|
1216
1221
|
${chalk_1.default.bold('Examples:')}
|
|
1217
1222
|
$ gopherhole send echo "Hello!"
|
|
1218
1223
|
$ gopherhole send agent-abc123 "What's the weather?"
|
|
1224
|
+
$ gopherhole send agent-abc123 "Free now?" --ttl 0 # fail if offline
|
|
1225
|
+
$ gopherhole send agent-abc123 "Review this" --ttl 3600 # queue up to 1h
|
|
1219
1226
|
`)
|
|
1220
|
-
.
|
|
1227
|
+
.option('--ttl <seconds>', 'Message time-to-live in seconds (0 = no queue, omit = 30 day default)', parseInt)
|
|
1228
|
+
.action(async (agentId, message, cmdOpts) => {
|
|
1221
1229
|
const sessionId = config.get('sessionId');
|
|
1222
1230
|
if (!sessionId) {
|
|
1223
1231
|
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
@@ -1238,7 +1246,10 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
1238
1246
|
method: 'SendMessage',
|
|
1239
1247
|
params: {
|
|
1240
1248
|
message: { role: 'user', parts: [{ kind: 'text', text: message }] },
|
|
1241
|
-
configuration: {
|
|
1249
|
+
configuration: {
|
|
1250
|
+
agentId,
|
|
1251
|
+
...(cmdOpts.ttl !== undefined ? { 'x-ttl': cmdOpts.ttl } : {}),
|
|
1252
|
+
},
|
|
1242
1253
|
},
|
|
1243
1254
|
id: 1,
|
|
1244
1255
|
}),
|
|
@@ -1263,6 +1274,183 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
1263
1274
|
process.exit(1);
|
|
1264
1275
|
}
|
|
1265
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
|
+
}
|
|
1266
1454
|
// ========== DISCOVER COMMANDS ==========
|
|
1267
1455
|
const discover = program
|
|
1268
1456
|
.command('discover')
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -20,7 +20,7 @@ const brand = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
// Version
|
|
23
|
-
const VERSION = '0.
|
|
23
|
+
const VERSION = '0.3.0';
|
|
24
24
|
|
|
25
25
|
// ========== API KEY RESOLUTION ==========
|
|
26
26
|
// Precedence: --api-key flag > GOPHERHOLE_API_KEY env var > .env file in cwd
|
|
@@ -96,6 +96,7 @@ async function askAgent(client: A2AClient, agentId: string, text: string): Promi
|
|
|
96
96
|
const maxWait = 60_000;
|
|
97
97
|
const poll = 1_000;
|
|
98
98
|
|
|
99
|
+
let printedQueued = false;
|
|
99
100
|
while (!terminalStates.includes(current.status.state)) {
|
|
100
101
|
if (current.status.state === 'input-required') {
|
|
101
102
|
throw new Error('Agent requires additional input (not supported in CLI mode)');
|
|
@@ -103,6 +104,10 @@ async function askAgent(client: A2AClient, agentId: string, text: string): Promi
|
|
|
103
104
|
if (current.status.state === 'auth-required') {
|
|
104
105
|
throw new Error('Agent requires authentication — check your API key or request access');
|
|
105
106
|
}
|
|
107
|
+
if (current.status.state === 'submitted' && !printedQueued) {
|
|
108
|
+
console.log(chalk.yellow('⏳ Message queued — recipient is offline. Waiting for delivery...'));
|
|
109
|
+
printedQueued = true;
|
|
110
|
+
}
|
|
106
111
|
if (Date.now() - start > maxWait) throw new Error('Timed out waiting for agent response');
|
|
107
112
|
await new Promise(r => setTimeout(r, poll));
|
|
108
113
|
current = await client.getTask(current.id);
|
|
@@ -1347,8 +1352,11 @@ program
|
|
|
1347
1352
|
${chalk.bold('Examples:')}
|
|
1348
1353
|
$ gopherhole send echo "Hello!"
|
|
1349
1354
|
$ gopherhole send agent-abc123 "What's the weather?"
|
|
1355
|
+
$ gopherhole send agent-abc123 "Free now?" --ttl 0 # fail if offline
|
|
1356
|
+
$ gopherhole send agent-abc123 "Review this" --ttl 3600 # queue up to 1h
|
|
1350
1357
|
`)
|
|
1351
|
-
.
|
|
1358
|
+
.option('--ttl <seconds>', 'Message time-to-live in seconds (0 = no queue, omit = 30 day default)', parseInt)
|
|
1359
|
+
.action(async (agentId, message, cmdOpts) => {
|
|
1352
1360
|
const sessionId = config.get('sessionId') as string;
|
|
1353
1361
|
if (!sessionId) {
|
|
1354
1362
|
console.log(chalk.yellow('Not logged in.'));
|
|
@@ -1371,7 +1379,10 @@ ${chalk.bold('Examples:')}
|
|
|
1371
1379
|
method: 'SendMessage',
|
|
1372
1380
|
params: {
|
|
1373
1381
|
message: { role: 'user', parts: [{ kind: 'text', text: message }] },
|
|
1374
|
-
configuration: {
|
|
1382
|
+
configuration: {
|
|
1383
|
+
agentId,
|
|
1384
|
+
...(cmdOpts.ttl !== undefined ? { 'x-ttl': cmdOpts.ttl } : {}),
|
|
1385
|
+
},
|
|
1375
1386
|
},
|
|
1376
1387
|
id: 1,
|
|
1377
1388
|
}),
|
|
@@ -1401,6 +1412,174 @@ ${chalk.bold('Examples:')}
|
|
|
1401
1412
|
}
|
|
1402
1413
|
});
|
|
1403
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
|
+
|
|
1404
1583
|
// ========== DISCOVER COMMANDS ==========
|
|
1405
1584
|
|
|
1406
1585
|
const discover = program
|