@gopherhole/cli 0.1.3 ā 0.1.5
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 +1 -1
- package/dist/index.js +312 -4
- package/package.json +1 -1
- package/src/index.ts +351 -4
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -69,7 +69,7 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
69
69
|
$ gopherhole agents list
|
|
70
70
|
|
|
71
71
|
${chalk_1.default.bold('Documentation:')}
|
|
72
|
-
https://gopherhole.ai
|
|
72
|
+
https://docs.gopherhole.ai
|
|
73
73
|
`)
|
|
74
74
|
.version('0.1.0')
|
|
75
75
|
.option('-v, --verbose', 'Enable verbose output for debugging')
|
|
@@ -320,7 +320,7 @@ ${chalk_1.default.bold('Example:')}
|
|
|
320
320
|
}
|
|
321
321
|
console.log(chalk_1.default.bold('\nš Next steps:\n'));
|
|
322
322
|
console.log(` ⢠Dashboard: ${chalk_1.default.cyan('https://gopherhole.ai/dashboard')}`);
|
|
323
|
-
console.log(` ⢠Docs: ${chalk_1.default.cyan('https://gopherhole.ai
|
|
323
|
+
console.log(` ⢠Docs: ${chalk_1.default.cyan('https://docs.gopherhole.ai')}`);
|
|
324
324
|
console.log(` ⢠Find agents: ${chalk_1.default.cyan('gopherhole discover search')}`);
|
|
325
325
|
console.log(` ⢠List yours: ${chalk_1.default.cyan('gopherhole agents list')}`);
|
|
326
326
|
console.log('');
|
|
@@ -553,7 +553,7 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
553
553
|
console.log(chalk_1.default.bold('\n Quick test:'));
|
|
554
554
|
console.log(chalk_1.default.white(` curl -H "Authorization: Bearer ${data.apiKey}" \\
|
|
555
555
|
https://gopherhole.ai/a2a -d '{"jsonrpc":"2.0","method":"agent/info","id":1}'`));
|
|
556
|
-
console.log(chalk_1.default.gray('\n Full docs: https://gopherhole.ai
|
|
556
|
+
console.log(chalk_1.default.gray('\n Full docs: https://docs.gopherhole.ai'));
|
|
557
557
|
console.log('');
|
|
558
558
|
}
|
|
559
559
|
catch (err) {
|
|
@@ -609,6 +609,124 @@ ${chalk_1.default.bold('Example:')}
|
|
|
609
609
|
process.exit(1);
|
|
610
610
|
}
|
|
611
611
|
});
|
|
612
|
+
agents
|
|
613
|
+
.command('regenerate-key <agentId>')
|
|
614
|
+
.description(`Regenerate API key for an agent
|
|
615
|
+
|
|
616
|
+
${chalk_1.default.bold('Example:')}
|
|
617
|
+
$ gopherhole agents regenerate-key agent-abc123
|
|
618
|
+
|
|
619
|
+
${chalk_1.default.yellow('ā ļø Warning:')} This will invalidate the current key.
|
|
620
|
+
All connected agents using this key will stop working.
|
|
621
|
+
`)
|
|
622
|
+
.option('-f, --force', 'Skip confirmation')
|
|
623
|
+
.option('--json', 'Output as JSON')
|
|
624
|
+
.action(async (agentId, options) => {
|
|
625
|
+
const sessionId = config.get('sessionId');
|
|
626
|
+
if (!sessionId) {
|
|
627
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
628
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
if (!options.force) {
|
|
632
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
633
|
+
{
|
|
634
|
+
type: 'confirm',
|
|
635
|
+
name: 'confirm',
|
|
636
|
+
message: `Regenerate API key for ${chalk_1.default.cyan(agentId)}? This will invalidate the current key.`,
|
|
637
|
+
default: false,
|
|
638
|
+
},
|
|
639
|
+
]);
|
|
640
|
+
if (!confirm) {
|
|
641
|
+
console.log('Cancelled.');
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const spinner = (0, ora_1.default)('Regenerating API key...').start();
|
|
646
|
+
log('POST /agents/' + agentId + '/regenerate-key');
|
|
647
|
+
try {
|
|
648
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/regenerate-key`, {
|
|
649
|
+
method: 'POST',
|
|
650
|
+
headers: {
|
|
651
|
+
'Content-Type': 'application/json',
|
|
652
|
+
'X-Session-ID': sessionId,
|
|
653
|
+
},
|
|
654
|
+
body: JSON.stringify({}),
|
|
655
|
+
});
|
|
656
|
+
if (!res.ok) {
|
|
657
|
+
const err = await res.json();
|
|
658
|
+
logError('regenerate-key', err);
|
|
659
|
+
throw new Error(err.error || 'Failed to regenerate key');
|
|
660
|
+
}
|
|
661
|
+
const data = await res.json();
|
|
662
|
+
spinner.succeed('API key regenerated');
|
|
663
|
+
if (options.json) {
|
|
664
|
+
console.log(JSON.stringify({ apiKey: data.apiKey }, null, 2));
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
console.log('');
|
|
668
|
+
console.log(chalk_1.default.bold('New API Key:'));
|
|
669
|
+
console.log(chalk_1.default.green(data.apiKey));
|
|
670
|
+
console.log('');
|
|
671
|
+
console.log(chalk_1.default.yellow('ā ļø Copy this key now ā it won\'t be shown again!'));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
catch (err) {
|
|
675
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
676
|
+
process.exit(1);
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
agents
|
|
680
|
+
.command('sync-card <agentId>')
|
|
681
|
+
.description(`Sync agent card from its /.well-known/agent.json URL
|
|
682
|
+
|
|
683
|
+
${chalk_1.default.bold('What it does:')}
|
|
684
|
+
Fetches the agent card from the agent's URL and updates
|
|
685
|
+
the GopherHole registry. Use after updating your agent's
|
|
686
|
+
skills, description, or capabilities.
|
|
687
|
+
|
|
688
|
+
${chalk_1.default.bold('Examples:')}
|
|
689
|
+
$ gopherhole agents sync-card agent-abc123
|
|
690
|
+
$ gopherhole agents sync-card my-agent
|
|
691
|
+
`)
|
|
692
|
+
.action(async (agentId) => {
|
|
693
|
+
const sessionId = config.get('sessionId');
|
|
694
|
+
if (!sessionId) {
|
|
695
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
696
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
const spinner = (0, ora_1.default)('Syncing agent card...').start();
|
|
700
|
+
log('POST /agents/' + agentId + '/sync-card');
|
|
701
|
+
try {
|
|
702
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/sync-card`, {
|
|
703
|
+
method: 'POST',
|
|
704
|
+
headers: { 'X-Session-ID': sessionId },
|
|
705
|
+
});
|
|
706
|
+
if (!res.ok) {
|
|
707
|
+
const err = await res.json();
|
|
708
|
+
logError('sync-card', err);
|
|
709
|
+
throw new Error(err.error || 'Failed to sync card');
|
|
710
|
+
}
|
|
711
|
+
const data = await res.json();
|
|
712
|
+
spinner.succeed('Agent card synced!');
|
|
713
|
+
if (data.card) {
|
|
714
|
+
console.log(chalk_1.default.bold('\n Updated card:'));
|
|
715
|
+
console.log(` Name: ${brand.green(data.card.name)}`);
|
|
716
|
+
if (data.card.description) {
|
|
717
|
+
console.log(` Description: ${chalk_1.default.gray(data.card.description.slice(0, 60))}${data.card.description.length > 60 ? '...' : ''}`);
|
|
718
|
+
}
|
|
719
|
+
if (data.card.skills?.length) {
|
|
720
|
+
console.log(` Skills: ${data.card.skills.length} (${data.card.skills.map((s) => s.name).join(', ')})`);
|
|
721
|
+
}
|
|
722
|
+
console.log('');
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
catch (err) {
|
|
726
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
727
|
+
process.exit(1);
|
|
728
|
+
}
|
|
729
|
+
});
|
|
612
730
|
// ========== INIT COMMAND ==========
|
|
613
731
|
program
|
|
614
732
|
.command('init')
|
|
@@ -773,7 +891,7 @@ main().catch(console.error);
|
|
|
773
891
|
console.log(chalk_1.default.cyan(' npm start'));
|
|
774
892
|
console.log('');
|
|
775
893
|
console.log(chalk_1.default.gray(`Dashboard: https://gopherhole.ai/dashboard`));
|
|
776
|
-
console.log(chalk_1.default.gray(`Docs: https://gopherhole.ai
|
|
894
|
+
console.log(chalk_1.default.gray(`Docs: https://docs.gopherhole.ai`));
|
|
777
895
|
console.log('');
|
|
778
896
|
});
|
|
779
897
|
// ========== SEND COMMAND ==========
|
|
@@ -1105,6 +1223,196 @@ ${chalk_1.default.bold('Example:')}
|
|
|
1105
1223
|
process.exit(1);
|
|
1106
1224
|
}
|
|
1107
1225
|
});
|
|
1226
|
+
// ========== ACCESS COMMANDS ==========
|
|
1227
|
+
const access = program
|
|
1228
|
+
.command('access')
|
|
1229
|
+
.description(`Manage access requests to your agents
|
|
1230
|
+
|
|
1231
|
+
${chalk_1.default.bold('Examples:')}
|
|
1232
|
+
$ gopherhole access list # List pending requests
|
|
1233
|
+
$ gopherhole access approve <id> # Approve a request
|
|
1234
|
+
$ gopherhole access reject <id> # Reject a request
|
|
1235
|
+
`);
|
|
1236
|
+
access
|
|
1237
|
+
.command('list')
|
|
1238
|
+
.description('List access requests to your agents')
|
|
1239
|
+
.option('--status <status>', 'Filter by status (pending, approved, rejected)', 'pending')
|
|
1240
|
+
.option('--json', 'Output as JSON')
|
|
1241
|
+
.action(async (options) => {
|
|
1242
|
+
const sessionId = config.get('sessionId');
|
|
1243
|
+
if (!sessionId) {
|
|
1244
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1245
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1246
|
+
process.exit(1);
|
|
1247
|
+
}
|
|
1248
|
+
const spinner = (0, ora_1.default)('Fetching access requests...').start();
|
|
1249
|
+
log('GET /access/inbound');
|
|
1250
|
+
try {
|
|
1251
|
+
const res = await fetch(`${API_URL}/access/inbound`, {
|
|
1252
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1253
|
+
});
|
|
1254
|
+
if (!res.ok) {
|
|
1255
|
+
throw new Error('Failed to fetch access requests');
|
|
1256
|
+
}
|
|
1257
|
+
const data = await res.json();
|
|
1258
|
+
spinner.stop();
|
|
1259
|
+
// Filter by status
|
|
1260
|
+
const filtered = options.status === 'all'
|
|
1261
|
+
? data.grants
|
|
1262
|
+
: data.grants.filter((g) => g.status === options.status);
|
|
1263
|
+
if (options.json) {
|
|
1264
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
if (filtered.length === 0) {
|
|
1268
|
+
console.log(chalk_1.default.gray(`\nNo ${options.status} access requests.`));
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
console.log(chalk_1.default.bold(`\nš ${options.status.charAt(0).toUpperCase() + options.status.slice(1)} Access Requests:\n`));
|
|
1272
|
+
for (const grant of filtered) {
|
|
1273
|
+
const statusColor = grant.status === 'pending' ? chalk_1.default.yellow :
|
|
1274
|
+
grant.status === 'approved' ? chalk_1.default.green : chalk_1.default.red;
|
|
1275
|
+
console.log(` ${chalk_1.default.cyan(grant.id)}`);
|
|
1276
|
+
console.log(` From: ${grant.requester_agent_name || grant.requester_agent_id || 'Unknown'}`);
|
|
1277
|
+
console.log(` To: ${grant.target_agent_name || grant.target_agent_id}`);
|
|
1278
|
+
console.log(` Status: ${statusColor(grant.status)}`);
|
|
1279
|
+
if (grant.requested_reason) {
|
|
1280
|
+
console.log(` Reason: ${chalk_1.default.gray(grant.requested_reason)}`);
|
|
1281
|
+
}
|
|
1282
|
+
console.log(` Requested: ${new Date(grant.requested_at).toLocaleString()}`);
|
|
1283
|
+
console.log('');
|
|
1284
|
+
}
|
|
1285
|
+
if (options.status === 'pending' && filtered.length > 0) {
|
|
1286
|
+
console.log(chalk_1.default.gray(`Approve: gopherhole access approve <id>`));
|
|
1287
|
+
console.log(chalk_1.default.gray(`Reject: gopherhole access reject <id>`));
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
catch (err) {
|
|
1291
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1292
|
+
process.exit(1);
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
access
|
|
1296
|
+
.command('approve <grantId>')
|
|
1297
|
+
.description('Approve an access request')
|
|
1298
|
+
.option('--price <amount>', 'Set custom price for this requester (e.g., 0.01)')
|
|
1299
|
+
.option('--currency <code>', 'Currency code (default: USD)', 'USD')
|
|
1300
|
+
.option('--unit <unit>', 'Price unit (request, message, task, month)', 'request')
|
|
1301
|
+
.option('--json', 'Output as JSON')
|
|
1302
|
+
.action(async (grantId, options) => {
|
|
1303
|
+
const sessionId = config.get('sessionId');
|
|
1304
|
+
if (!sessionId) {
|
|
1305
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1306
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1307
|
+
process.exit(1);
|
|
1308
|
+
}
|
|
1309
|
+
const spinner = (0, ora_1.default)('Approving access request...').start();
|
|
1310
|
+
log('PUT /access/' + grantId + '/approve');
|
|
1311
|
+
try {
|
|
1312
|
+
const body = {};
|
|
1313
|
+
if (options.price) {
|
|
1314
|
+
body.price_amount = parseFloat(options.price);
|
|
1315
|
+
body.price_currency = options.currency;
|
|
1316
|
+
body.price_unit = options.unit;
|
|
1317
|
+
}
|
|
1318
|
+
const res = await fetch(`${API_URL}/access/${grantId}/approve`, {
|
|
1319
|
+
method: 'PUT',
|
|
1320
|
+
headers: {
|
|
1321
|
+
'Content-Type': 'application/json',
|
|
1322
|
+
'X-Session-ID': sessionId,
|
|
1323
|
+
},
|
|
1324
|
+
body: JSON.stringify(body),
|
|
1325
|
+
});
|
|
1326
|
+
if (!res.ok) {
|
|
1327
|
+
const err = await res.json();
|
|
1328
|
+
throw new Error(err.error || 'Failed to approve');
|
|
1329
|
+
}
|
|
1330
|
+
spinner.succeed('Access request approved');
|
|
1331
|
+
if (options.price) {
|
|
1332
|
+
console.log(chalk_1.default.gray(` Custom pricing: ${options.price} ${options.currency}/${options.unit}`));
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
catch (err) {
|
|
1336
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1337
|
+
process.exit(1);
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
access
|
|
1341
|
+
.command('reject <grantId>')
|
|
1342
|
+
.description('Reject an access request')
|
|
1343
|
+
.option('--reason <reason>', 'Reason for rejection')
|
|
1344
|
+
.action(async (grantId, options) => {
|
|
1345
|
+
const sessionId = config.get('sessionId');
|
|
1346
|
+
if (!sessionId) {
|
|
1347
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1348
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1349
|
+
process.exit(1);
|
|
1350
|
+
}
|
|
1351
|
+
const spinner = (0, ora_1.default)('Rejecting access request...').start();
|
|
1352
|
+
log('PUT /access/' + grantId + '/reject');
|
|
1353
|
+
try {
|
|
1354
|
+
const res = await fetch(`${API_URL}/access/${grantId}/reject`, {
|
|
1355
|
+
method: 'PUT',
|
|
1356
|
+
headers: {
|
|
1357
|
+
'Content-Type': 'application/json',
|
|
1358
|
+
'X-Session-ID': sessionId,
|
|
1359
|
+
},
|
|
1360
|
+
body: JSON.stringify({ reason: options.reason }),
|
|
1361
|
+
});
|
|
1362
|
+
if (!res.ok) {
|
|
1363
|
+
const err = await res.json();
|
|
1364
|
+
throw new Error(err.error || 'Failed to reject');
|
|
1365
|
+
}
|
|
1366
|
+
spinner.succeed('Access request rejected');
|
|
1367
|
+
}
|
|
1368
|
+
catch (err) {
|
|
1369
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1370
|
+
process.exit(1);
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
access
|
|
1374
|
+
.command('revoke <grantId>')
|
|
1375
|
+
.description('Revoke a previously approved access grant')
|
|
1376
|
+
.option('-f, --force', 'Skip confirmation')
|
|
1377
|
+
.action(async (grantId, options) => {
|
|
1378
|
+
const sessionId = config.get('sessionId');
|
|
1379
|
+
if (!sessionId) {
|
|
1380
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1381
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1382
|
+
process.exit(1);
|
|
1383
|
+
}
|
|
1384
|
+
if (!options.force) {
|
|
1385
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
1386
|
+
{
|
|
1387
|
+
type: 'confirm',
|
|
1388
|
+
name: 'confirm',
|
|
1389
|
+
message: `Revoke access grant ${chalk_1.default.cyan(grantId)}? The requester will lose access.`,
|
|
1390
|
+
default: false,
|
|
1391
|
+
},
|
|
1392
|
+
]);
|
|
1393
|
+
if (!confirm) {
|
|
1394
|
+
console.log('Cancelled.');
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
const spinner = (0, ora_1.default)('Revoking access...').start();
|
|
1399
|
+
log('DELETE /access/' + grantId);
|
|
1400
|
+
try {
|
|
1401
|
+
const res = await fetch(`${API_URL}/access/${grantId}`, {
|
|
1402
|
+
method: 'DELETE',
|
|
1403
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1404
|
+
});
|
|
1405
|
+
if (!res.ok) {
|
|
1406
|
+
const err = await res.json();
|
|
1407
|
+
throw new Error(err.error || 'Failed to revoke');
|
|
1408
|
+
}
|
|
1409
|
+
spinner.succeed('Access revoked');
|
|
1410
|
+
}
|
|
1411
|
+
catch (err) {
|
|
1412
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1413
|
+
process.exit(1);
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1108
1416
|
// ========== STATUS COMMAND ==========
|
|
1109
1417
|
program
|
|
1110
1418
|
.command('status')
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -76,7 +76,7 @@ ${chalk.bold('Examples:')}
|
|
|
76
76
|
$ gopherhole agents list
|
|
77
77
|
|
|
78
78
|
${chalk.bold('Documentation:')}
|
|
79
|
-
https://gopherhole.ai
|
|
79
|
+
https://docs.gopherhole.ai
|
|
80
80
|
`)
|
|
81
81
|
.version('0.1.0')
|
|
82
82
|
.option('-v, --verbose', 'Enable verbose output for debugging')
|
|
@@ -349,7 +349,7 @@ ${chalk.bold('Example:')}
|
|
|
349
349
|
|
|
350
350
|
console.log(chalk.bold('\nš Next steps:\n'));
|
|
351
351
|
console.log(` ⢠Dashboard: ${chalk.cyan('https://gopherhole.ai/dashboard')}`);
|
|
352
|
-
console.log(` ⢠Docs: ${chalk.cyan('https://gopherhole.ai
|
|
352
|
+
console.log(` ⢠Docs: ${chalk.cyan('https://docs.gopherhole.ai')}`);
|
|
353
353
|
console.log(` ⢠Find agents: ${chalk.cyan('gopherhole discover search')}`);
|
|
354
354
|
console.log(` ⢠List yours: ${chalk.cyan('gopherhole agents list')}`);
|
|
355
355
|
console.log('');
|
|
@@ -618,7 +618,7 @@ ${chalk.bold('Examples:')}
|
|
|
618
618
|
console.log(chalk.white(` curl -H "Authorization: Bearer ${data.apiKey}" \\
|
|
619
619
|
https://gopherhole.ai/a2a -d '{"jsonrpc":"2.0","method":"agent/info","id":1}'`));
|
|
620
620
|
|
|
621
|
-
console.log(chalk.gray('\n Full docs: https://gopherhole.ai
|
|
621
|
+
console.log(chalk.gray('\n Full docs: https://docs.gopherhole.ai'));
|
|
622
622
|
console.log('');
|
|
623
623
|
} catch (err) {
|
|
624
624
|
spinner.fail(chalk.red((err as Error).message));
|
|
@@ -680,6 +680,135 @@ ${chalk.bold('Example:')}
|
|
|
680
680
|
}
|
|
681
681
|
});
|
|
682
682
|
|
|
683
|
+
agents
|
|
684
|
+
.command('regenerate-key <agentId>')
|
|
685
|
+
.description(`Regenerate API key for an agent
|
|
686
|
+
|
|
687
|
+
${chalk.bold('Example:')}
|
|
688
|
+
$ gopherhole agents regenerate-key agent-abc123
|
|
689
|
+
|
|
690
|
+
${chalk.yellow('ā ļø Warning:')} This will invalidate the current key.
|
|
691
|
+
All connected agents using this key will stop working.
|
|
692
|
+
`)
|
|
693
|
+
.option('-f, --force', 'Skip confirmation')
|
|
694
|
+
.option('--json', 'Output as JSON')
|
|
695
|
+
.action(async (agentId, options) => {
|
|
696
|
+
const sessionId = config.get('sessionId') as string;
|
|
697
|
+
if (!sessionId) {
|
|
698
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
699
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
700
|
+
process.exit(1);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (!options.force) {
|
|
704
|
+
const { confirm } = await inquirer.prompt([
|
|
705
|
+
{
|
|
706
|
+
type: 'confirm',
|
|
707
|
+
name: 'confirm',
|
|
708
|
+
message: `Regenerate API key for ${chalk.cyan(agentId)}? This will invalidate the current key.`,
|
|
709
|
+
default: false,
|
|
710
|
+
},
|
|
711
|
+
]);
|
|
712
|
+
|
|
713
|
+
if (!confirm) {
|
|
714
|
+
console.log('Cancelled.');
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const spinner = ora('Regenerating API key...').start();
|
|
720
|
+
log('POST /agents/' + agentId + '/regenerate-key');
|
|
721
|
+
|
|
722
|
+
try {
|
|
723
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/regenerate-key`, {
|
|
724
|
+
method: 'POST',
|
|
725
|
+
headers: {
|
|
726
|
+
'Content-Type': 'application/json',
|
|
727
|
+
'X-Session-ID': sessionId,
|
|
728
|
+
},
|
|
729
|
+
body: JSON.stringify({}),
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
if (!res.ok) {
|
|
733
|
+
const err = await res.json();
|
|
734
|
+
logError('regenerate-key', err);
|
|
735
|
+
throw new Error(err.error || 'Failed to regenerate key');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const data = await res.json();
|
|
739
|
+
spinner.succeed('API key regenerated');
|
|
740
|
+
|
|
741
|
+
if (options.json) {
|
|
742
|
+
console.log(JSON.stringify({ apiKey: data.apiKey }, null, 2));
|
|
743
|
+
} else {
|
|
744
|
+
console.log('');
|
|
745
|
+
console.log(chalk.bold('New API Key:'));
|
|
746
|
+
console.log(chalk.green(data.apiKey));
|
|
747
|
+
console.log('');
|
|
748
|
+
console.log(chalk.yellow('ā ļø Copy this key now ā it won\'t be shown again!'));
|
|
749
|
+
}
|
|
750
|
+
} catch (err) {
|
|
751
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
752
|
+
process.exit(1);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
agents
|
|
757
|
+
.command('sync-card <agentId>')
|
|
758
|
+
.description(`Sync agent card from its /.well-known/agent.json URL
|
|
759
|
+
|
|
760
|
+
${chalk.bold('What it does:')}
|
|
761
|
+
Fetches the agent card from the agent's URL and updates
|
|
762
|
+
the GopherHole registry. Use after updating your agent's
|
|
763
|
+
skills, description, or capabilities.
|
|
764
|
+
|
|
765
|
+
${chalk.bold('Examples:')}
|
|
766
|
+
$ gopherhole agents sync-card agent-abc123
|
|
767
|
+
$ gopherhole agents sync-card my-agent
|
|
768
|
+
`)
|
|
769
|
+
.action(async (agentId) => {
|
|
770
|
+
const sessionId = config.get('sessionId') as string;
|
|
771
|
+
if (!sessionId) {
|
|
772
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
773
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const spinner = ora('Syncing agent card...').start();
|
|
778
|
+
log('POST /agents/' + agentId + '/sync-card');
|
|
779
|
+
|
|
780
|
+
try {
|
|
781
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/sync-card`, {
|
|
782
|
+
method: 'POST',
|
|
783
|
+
headers: { 'X-Session-ID': sessionId },
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
if (!res.ok) {
|
|
787
|
+
const err = await res.json();
|
|
788
|
+
logError('sync-card', err);
|
|
789
|
+
throw new Error(err.error || 'Failed to sync card');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const data = await res.json();
|
|
793
|
+
spinner.succeed('Agent card synced!');
|
|
794
|
+
|
|
795
|
+
if (data.card) {
|
|
796
|
+
console.log(chalk.bold('\n Updated card:'));
|
|
797
|
+
console.log(` Name: ${brand.green(data.card.name)}`);
|
|
798
|
+
if (data.card.description) {
|
|
799
|
+
console.log(` Description: ${chalk.gray(data.card.description.slice(0, 60))}${data.card.description.length > 60 ? '...' : ''}`);
|
|
800
|
+
}
|
|
801
|
+
if (data.card.skills?.length) {
|
|
802
|
+
console.log(` Skills: ${data.card.skills.length} (${data.card.skills.map((s: any) => s.name).join(', ')})`);
|
|
803
|
+
}
|
|
804
|
+
console.log('');
|
|
805
|
+
}
|
|
806
|
+
} catch (err) {
|
|
807
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
|
|
683
812
|
// ========== INIT COMMAND ==========
|
|
684
813
|
|
|
685
814
|
program
|
|
@@ -864,7 +993,7 @@ main().catch(console.error);
|
|
|
864
993
|
console.log(chalk.cyan(' npm start'));
|
|
865
994
|
console.log('');
|
|
866
995
|
console.log(chalk.gray(`Dashboard: https://gopherhole.ai/dashboard`));
|
|
867
|
-
console.log(chalk.gray(`Docs: https://gopherhole.ai
|
|
996
|
+
console.log(chalk.gray(`Docs: https://docs.gopherhole.ai`));
|
|
868
997
|
console.log('');
|
|
869
998
|
});
|
|
870
999
|
|
|
@@ -1231,6 +1360,224 @@ ${chalk.bold('Example:')}
|
|
|
1231
1360
|
}
|
|
1232
1361
|
});
|
|
1233
1362
|
|
|
1363
|
+
// ========== ACCESS COMMANDS ==========
|
|
1364
|
+
|
|
1365
|
+
const access = program
|
|
1366
|
+
.command('access')
|
|
1367
|
+
.description(`Manage access requests to your agents
|
|
1368
|
+
|
|
1369
|
+
${chalk.bold('Examples:')}
|
|
1370
|
+
$ gopherhole access list # List pending requests
|
|
1371
|
+
$ gopherhole access approve <id> # Approve a request
|
|
1372
|
+
$ gopherhole access reject <id> # Reject a request
|
|
1373
|
+
`);
|
|
1374
|
+
|
|
1375
|
+
access
|
|
1376
|
+
.command('list')
|
|
1377
|
+
.description('List access requests to your agents')
|
|
1378
|
+
.option('--status <status>', 'Filter by status (pending, approved, rejected)', 'pending')
|
|
1379
|
+
.option('--json', 'Output as JSON')
|
|
1380
|
+
.action(async (options) => {
|
|
1381
|
+
const sessionId = config.get('sessionId') as string;
|
|
1382
|
+
if (!sessionId) {
|
|
1383
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1384
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1385
|
+
process.exit(1);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
const spinner = ora('Fetching access requests...').start();
|
|
1389
|
+
log('GET /access/inbound');
|
|
1390
|
+
|
|
1391
|
+
try {
|
|
1392
|
+
const res = await fetch(`${API_URL}/access/inbound`, {
|
|
1393
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
if (!res.ok) {
|
|
1397
|
+
throw new Error('Failed to fetch access requests');
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
const data = await res.json();
|
|
1401
|
+
spinner.stop();
|
|
1402
|
+
|
|
1403
|
+
// Filter by status
|
|
1404
|
+
const filtered = options.status === 'all'
|
|
1405
|
+
? data.grants
|
|
1406
|
+
: data.grants.filter((g: any) => g.status === options.status);
|
|
1407
|
+
|
|
1408
|
+
if (options.json) {
|
|
1409
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
if (filtered.length === 0) {
|
|
1414
|
+
console.log(chalk.gray(`\nNo ${options.status} access requests.`));
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
console.log(chalk.bold(`\nš ${options.status.charAt(0).toUpperCase() + options.status.slice(1)} Access Requests:\n`));
|
|
1419
|
+
|
|
1420
|
+
for (const grant of filtered) {
|
|
1421
|
+
const statusColor = grant.status === 'pending' ? chalk.yellow :
|
|
1422
|
+
grant.status === 'approved' ? chalk.green : chalk.red;
|
|
1423
|
+
console.log(` ${chalk.cyan(grant.id)}`);
|
|
1424
|
+
console.log(` From: ${grant.requester_agent_name || grant.requester_agent_id || 'Unknown'}`);
|
|
1425
|
+
console.log(` To: ${grant.target_agent_name || grant.target_agent_id}`);
|
|
1426
|
+
console.log(` Status: ${statusColor(grant.status)}`);
|
|
1427
|
+
if (grant.requested_reason) {
|
|
1428
|
+
console.log(` Reason: ${chalk.gray(grant.requested_reason)}`);
|
|
1429
|
+
}
|
|
1430
|
+
console.log(` Requested: ${new Date(grant.requested_at).toLocaleString()}`);
|
|
1431
|
+
console.log('');
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
if (options.status === 'pending' && filtered.length > 0) {
|
|
1435
|
+
console.log(chalk.gray(`Approve: gopherhole access approve <id>`));
|
|
1436
|
+
console.log(chalk.gray(`Reject: gopherhole access reject <id>`));
|
|
1437
|
+
}
|
|
1438
|
+
} catch (err) {
|
|
1439
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1440
|
+
process.exit(1);
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
access
|
|
1445
|
+
.command('approve <grantId>')
|
|
1446
|
+
.description('Approve an access request')
|
|
1447
|
+
.option('--price <amount>', 'Set custom price for this requester (e.g., 0.01)')
|
|
1448
|
+
.option('--currency <code>', 'Currency code (default: USD)', 'USD')
|
|
1449
|
+
.option('--unit <unit>', 'Price unit (request, message, task, month)', 'request')
|
|
1450
|
+
.option('--json', 'Output as JSON')
|
|
1451
|
+
.action(async (grantId, options) => {
|
|
1452
|
+
const sessionId = config.get('sessionId') as string;
|
|
1453
|
+
if (!sessionId) {
|
|
1454
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1455
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1456
|
+
process.exit(1);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
const spinner = ora('Approving access request...').start();
|
|
1460
|
+
log('PUT /access/' + grantId + '/approve');
|
|
1461
|
+
|
|
1462
|
+
try {
|
|
1463
|
+
const body: any = {};
|
|
1464
|
+
if (options.price) {
|
|
1465
|
+
body.price_amount = parseFloat(options.price);
|
|
1466
|
+
body.price_currency = options.currency;
|
|
1467
|
+
body.price_unit = options.unit;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
const res = await fetch(`${API_URL}/access/${grantId}/approve`, {
|
|
1471
|
+
method: 'PUT',
|
|
1472
|
+
headers: {
|
|
1473
|
+
'Content-Type': 'application/json',
|
|
1474
|
+
'X-Session-ID': sessionId,
|
|
1475
|
+
},
|
|
1476
|
+
body: JSON.stringify(body),
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
if (!res.ok) {
|
|
1480
|
+
const err = await res.json();
|
|
1481
|
+
throw new Error(err.error || 'Failed to approve');
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
spinner.succeed('Access request approved');
|
|
1485
|
+
|
|
1486
|
+
if (options.price) {
|
|
1487
|
+
console.log(chalk.gray(` Custom pricing: ${options.price} ${options.currency}/${options.unit}`));
|
|
1488
|
+
}
|
|
1489
|
+
} catch (err) {
|
|
1490
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1491
|
+
process.exit(1);
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
access
|
|
1496
|
+
.command('reject <grantId>')
|
|
1497
|
+
.description('Reject an access request')
|
|
1498
|
+
.option('--reason <reason>', 'Reason for rejection')
|
|
1499
|
+
.action(async (grantId, options) => {
|
|
1500
|
+
const sessionId = config.get('sessionId') as string;
|
|
1501
|
+
if (!sessionId) {
|
|
1502
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1503
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1504
|
+
process.exit(1);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
const spinner = ora('Rejecting access request...').start();
|
|
1508
|
+
log('PUT /access/' + grantId + '/reject');
|
|
1509
|
+
|
|
1510
|
+
try {
|
|
1511
|
+
const res = await fetch(`${API_URL}/access/${grantId}/reject`, {
|
|
1512
|
+
method: 'PUT',
|
|
1513
|
+
headers: {
|
|
1514
|
+
'Content-Type': 'application/json',
|
|
1515
|
+
'X-Session-ID': sessionId,
|
|
1516
|
+
},
|
|
1517
|
+
body: JSON.stringify({ reason: options.reason }),
|
|
1518
|
+
});
|
|
1519
|
+
|
|
1520
|
+
if (!res.ok) {
|
|
1521
|
+
const err = await res.json();
|
|
1522
|
+
throw new Error(err.error || 'Failed to reject');
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
spinner.succeed('Access request rejected');
|
|
1526
|
+
} catch (err) {
|
|
1527
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1528
|
+
process.exit(1);
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
access
|
|
1533
|
+
.command('revoke <grantId>')
|
|
1534
|
+
.description('Revoke a previously approved access grant')
|
|
1535
|
+
.option('-f, --force', 'Skip confirmation')
|
|
1536
|
+
.action(async (grantId, options) => {
|
|
1537
|
+
const sessionId = config.get('sessionId') as string;
|
|
1538
|
+
if (!sessionId) {
|
|
1539
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1540
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1541
|
+
process.exit(1);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (!options.force) {
|
|
1545
|
+
const { confirm } = await inquirer.prompt([
|
|
1546
|
+
{
|
|
1547
|
+
type: 'confirm',
|
|
1548
|
+
name: 'confirm',
|
|
1549
|
+
message: `Revoke access grant ${chalk.cyan(grantId)}? The requester will lose access.`,
|
|
1550
|
+
default: false,
|
|
1551
|
+
},
|
|
1552
|
+
]);
|
|
1553
|
+
|
|
1554
|
+
if (!confirm) {
|
|
1555
|
+
console.log('Cancelled.');
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
const spinner = ora('Revoking access...').start();
|
|
1561
|
+
log('DELETE /access/' + grantId);
|
|
1562
|
+
|
|
1563
|
+
try {
|
|
1564
|
+
const res = await fetch(`${API_URL}/access/${grantId}`, {
|
|
1565
|
+
method: 'DELETE',
|
|
1566
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
if (!res.ok) {
|
|
1570
|
+
const err = await res.json();
|
|
1571
|
+
throw new Error(err.error || 'Failed to revoke');
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
spinner.succeed('Access revoked');
|
|
1575
|
+
} catch (err) {
|
|
1576
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1577
|
+
process.exit(1);
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1234
1581
|
// ========== STATUS COMMAND ==========
|
|
1235
1582
|
|
|
1236
1583
|
program
|