@gopherhole/cli 0.1.4 → 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.
Files changed (3) hide show
  1. package/dist/index.js +190 -0
  2. package/package.json +1 -1
  3. package/src/index.ts +218 -0
package/dist/index.js CHANGED
@@ -1223,6 +1223,196 @@ ${chalk_1.default.bold('Example:')}
1223
1223
  process.exit(1);
1224
1224
  }
1225
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
+ });
1226
1416
  // ========== STATUS COMMAND ==========
1227
1417
  program
1228
1418
  .command('status')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gopherhole/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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
@@ -1360,6 +1360,224 @@ ${chalk.bold('Example:')}
1360
1360
  }
1361
1361
  });
1362
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
+
1363
1581
  // ========== STATUS COMMAND ==========
1364
1582
 
1365
1583
  program