@gopherhole/cli 0.1.4 ā 0.1.6
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 +281 -0
- package/package.json +1 -1
- package/src/index.ts +311 -0
package/dist/index.js
CHANGED
|
@@ -1223,6 +1223,287 @@ ${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('--agent <agentId>', 'Filter by agent ID')
|
|
1240
|
+
.option('--status <status>', 'Filter by status (pending, approved, rejected, all)', 'pending')
|
|
1241
|
+
.option('--search <query>', 'Search by requester name or ID')
|
|
1242
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
1243
|
+
.option('--offset <n>', 'Skip first N results', '0')
|
|
1244
|
+
.option('--json', 'Output as JSON')
|
|
1245
|
+
.action(async (options) => {
|
|
1246
|
+
const sessionId = config.get('sessionId');
|
|
1247
|
+
if (!sessionId) {
|
|
1248
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1249
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1250
|
+
process.exit(1);
|
|
1251
|
+
}
|
|
1252
|
+
const spinner = (0, ora_1.default)('Fetching access requests...').start();
|
|
1253
|
+
// Build query params
|
|
1254
|
+
const params = new URLSearchParams();
|
|
1255
|
+
if (options.agent)
|
|
1256
|
+
params.set('agent', options.agent);
|
|
1257
|
+
if (options.status && options.status !== 'all')
|
|
1258
|
+
params.set('status', options.status);
|
|
1259
|
+
if (options.search)
|
|
1260
|
+
params.set('search', options.search);
|
|
1261
|
+
params.set('limit', options.limit);
|
|
1262
|
+
params.set('offset', options.offset);
|
|
1263
|
+
log('GET /access/inbound?' + params.toString());
|
|
1264
|
+
try {
|
|
1265
|
+
const res = await fetch(`${API_URL}/access/inbound?${params}`, {
|
|
1266
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1267
|
+
});
|
|
1268
|
+
if (!res.ok) {
|
|
1269
|
+
throw new Error('Failed to fetch access requests');
|
|
1270
|
+
}
|
|
1271
|
+
const data = await res.json();
|
|
1272
|
+
spinner.stop();
|
|
1273
|
+
if (options.json) {
|
|
1274
|
+
console.log(JSON.stringify(data.grants, null, 2));
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (data.grants.length === 0) {
|
|
1278
|
+
console.log(chalk_1.default.gray(`\nNo ${options.status === 'all' ? '' : options.status + ' '}access requests found.`));
|
|
1279
|
+
if (options.agent)
|
|
1280
|
+
console.log(chalk_1.default.gray(` Agent filter: ${options.agent}`));
|
|
1281
|
+
if (options.search)
|
|
1282
|
+
console.log(chalk_1.default.gray(` Search: "${options.search}"`));
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
console.log(chalk_1.default.bold(`\nš Access Requests${options.agent ? ` for ${options.agent}` : ''}:\n`));
|
|
1286
|
+
for (const grant of data.grants) {
|
|
1287
|
+
const statusColor = grant.status === 'pending' ? chalk_1.default.yellow :
|
|
1288
|
+
grant.status === 'approved' ? chalk_1.default.green : chalk_1.default.red;
|
|
1289
|
+
console.log(` ${chalk_1.default.cyan(grant.id)}`);
|
|
1290
|
+
console.log(` From: ${grant.requester_agent_name || grant.requester_agent_id || 'Unknown'}`);
|
|
1291
|
+
console.log(` To: ${grant.target_agent_name || grant.target_agent_id}`);
|
|
1292
|
+
console.log(` Status: ${statusColor(grant.status)}`);
|
|
1293
|
+
if (grant.price_amount != null) {
|
|
1294
|
+
console.log(` Price: ${grant.price_amount} ${grant.price_currency}/${grant.price_unit}`);
|
|
1295
|
+
}
|
|
1296
|
+
if (grant.discount_percent != null) {
|
|
1297
|
+
console.log(` Discount: ${grant.discount_percent}%`);
|
|
1298
|
+
}
|
|
1299
|
+
if (grant.requested_reason) {
|
|
1300
|
+
console.log(` Reason: ${chalk_1.default.gray(grant.requested_reason)}`);
|
|
1301
|
+
}
|
|
1302
|
+
console.log(` Requested: ${new Date(grant.requested_at).toLocaleString()}`);
|
|
1303
|
+
console.log('');
|
|
1304
|
+
}
|
|
1305
|
+
// Pagination info
|
|
1306
|
+
if (data.grants.length >= parseInt(options.limit)) {
|
|
1307
|
+
console.log(chalk_1.default.gray(`Showing ${data.grants.length} results. Use --offset ${parseInt(options.offset) + parseInt(options.limit)} for next page.`));
|
|
1308
|
+
}
|
|
1309
|
+
if (options.status === 'pending' && data.grants.length > 0) {
|
|
1310
|
+
console.log(chalk_1.default.gray(`\nApprove: gopherhole access approve <id>`));
|
|
1311
|
+
console.log(chalk_1.default.gray(`Reject: gopherhole access reject <id>`));
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
catch (err) {
|
|
1315
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1316
|
+
process.exit(1);
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
access
|
|
1320
|
+
.command('approve <grantId>')
|
|
1321
|
+
.description('Approve an access request')
|
|
1322
|
+
.option('--price <amount>', 'Set custom price (e.g., 0.01)')
|
|
1323
|
+
.option('--currency <code>', 'Currency code (default: USD)', 'USD')
|
|
1324
|
+
.option('--unit <unit>', 'Price unit (request, message, task, month)', 'request')
|
|
1325
|
+
.option('--discount <percent>', 'Discount off default price (e.g., 20 for 20% off)')
|
|
1326
|
+
.option('--skill-pricing <json>', 'Per-skill pricing as JSON (e.g., \'{"translate":{"amount":0.05,"currency":"USD","unit":"request"}}\')')
|
|
1327
|
+
.action(async (grantId, options) => {
|
|
1328
|
+
const sessionId = config.get('sessionId');
|
|
1329
|
+
if (!sessionId) {
|
|
1330
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1331
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1332
|
+
process.exit(1);
|
|
1333
|
+
}
|
|
1334
|
+
const spinner = (0, ora_1.default)('Approving access request...').start();
|
|
1335
|
+
log('PUT /access/' + grantId + '/approve');
|
|
1336
|
+
try {
|
|
1337
|
+
const body = {};
|
|
1338
|
+
if (options.price) {
|
|
1339
|
+
body.price_amount = parseFloat(options.price);
|
|
1340
|
+
body.price_currency = options.currency;
|
|
1341
|
+
body.price_unit = options.unit;
|
|
1342
|
+
}
|
|
1343
|
+
if (options.discount) {
|
|
1344
|
+
body.discount_percent = parseFloat(options.discount);
|
|
1345
|
+
}
|
|
1346
|
+
if (options.skillPricing) {
|
|
1347
|
+
body.skill_pricing = JSON.parse(options.skillPricing);
|
|
1348
|
+
}
|
|
1349
|
+
const res = await fetch(`${API_URL}/access/${grantId}/approve`, {
|
|
1350
|
+
method: 'PUT',
|
|
1351
|
+
headers: {
|
|
1352
|
+
'Content-Type': 'application/json',
|
|
1353
|
+
'X-Session-ID': sessionId,
|
|
1354
|
+
},
|
|
1355
|
+
body: JSON.stringify(body),
|
|
1356
|
+
});
|
|
1357
|
+
if (!res.ok) {
|
|
1358
|
+
const err = await res.json();
|
|
1359
|
+
throw new Error(err.error || 'Failed to approve');
|
|
1360
|
+
}
|
|
1361
|
+
spinner.succeed('Access request approved');
|
|
1362
|
+
if (options.price) {
|
|
1363
|
+
console.log(chalk_1.default.gray(` Custom price: ${options.price} ${options.currency}/${options.unit}`));
|
|
1364
|
+
}
|
|
1365
|
+
if (options.discount) {
|
|
1366
|
+
console.log(chalk_1.default.gray(` Discount: ${options.discount}% off`));
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
catch (err) {
|
|
1370
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1371
|
+
process.exit(1);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
access
|
|
1375
|
+
.command('edit <grantId>')
|
|
1376
|
+
.description('Edit pricing on an existing access grant')
|
|
1377
|
+
.option('--price <amount>', 'Set custom price (e.g., 0.01)')
|
|
1378
|
+
.option('--currency <code>', 'Currency code', 'USD')
|
|
1379
|
+
.option('--unit <unit>', 'Price unit (request, message, task, month)')
|
|
1380
|
+
.option('--discount <percent>', 'Discount off default price (e.g., 20 for 20% off)')
|
|
1381
|
+
.option('--clear-discount', 'Remove discount')
|
|
1382
|
+
.option('--skill-pricing <json>', 'Per-skill pricing as JSON')
|
|
1383
|
+
.option('--clear-skill-pricing', 'Remove per-skill pricing')
|
|
1384
|
+
.action(async (grantId, options) => {
|
|
1385
|
+
const sessionId = config.get('sessionId');
|
|
1386
|
+
if (!sessionId) {
|
|
1387
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1388
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
const spinner = (0, ora_1.default)('Updating access grant...').start();
|
|
1392
|
+
log('PATCH /access/' + grantId);
|
|
1393
|
+
try {
|
|
1394
|
+
const body = {};
|
|
1395
|
+
if (options.price) {
|
|
1396
|
+
body.price_amount = parseFloat(options.price);
|
|
1397
|
+
body.price_currency = options.currency;
|
|
1398
|
+
body.price_unit = options.unit;
|
|
1399
|
+
}
|
|
1400
|
+
if (options.discount) {
|
|
1401
|
+
body.discount_percent = parseFloat(options.discount);
|
|
1402
|
+
}
|
|
1403
|
+
if (options.clearDiscount) {
|
|
1404
|
+
body.discount_percent = null;
|
|
1405
|
+
}
|
|
1406
|
+
if (options.skillPricing) {
|
|
1407
|
+
body.skill_pricing = JSON.parse(options.skillPricing);
|
|
1408
|
+
}
|
|
1409
|
+
if (options.clearSkillPricing) {
|
|
1410
|
+
body.skill_pricing = null;
|
|
1411
|
+
}
|
|
1412
|
+
const res = await fetch(`${API_URL}/access/${grantId}`, {
|
|
1413
|
+
method: 'PATCH',
|
|
1414
|
+
headers: {
|
|
1415
|
+
'Content-Type': 'application/json',
|
|
1416
|
+
'X-Session-ID': sessionId,
|
|
1417
|
+
},
|
|
1418
|
+
body: JSON.stringify(body),
|
|
1419
|
+
});
|
|
1420
|
+
if (!res.ok) {
|
|
1421
|
+
const err = await res.json();
|
|
1422
|
+
throw new Error(err.error || 'Failed to update grant');
|
|
1423
|
+
}
|
|
1424
|
+
spinner.succeed('Access grant updated');
|
|
1425
|
+
}
|
|
1426
|
+
catch (err) {
|
|
1427
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1428
|
+
process.exit(1);
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
access
|
|
1432
|
+
.command('reject <grantId>')
|
|
1433
|
+
.description('Reject an access request')
|
|
1434
|
+
.option('--reason <reason>', 'Reason for rejection')
|
|
1435
|
+
.action(async (grantId, options) => {
|
|
1436
|
+
const sessionId = config.get('sessionId');
|
|
1437
|
+
if (!sessionId) {
|
|
1438
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1439
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1440
|
+
process.exit(1);
|
|
1441
|
+
}
|
|
1442
|
+
const spinner = (0, ora_1.default)('Rejecting access request...').start();
|
|
1443
|
+
log('PUT /access/' + grantId + '/reject');
|
|
1444
|
+
try {
|
|
1445
|
+
const res = await fetch(`${API_URL}/access/${grantId}/reject`, {
|
|
1446
|
+
method: 'PUT',
|
|
1447
|
+
headers: {
|
|
1448
|
+
'Content-Type': 'application/json',
|
|
1449
|
+
'X-Session-ID': sessionId,
|
|
1450
|
+
},
|
|
1451
|
+
body: JSON.stringify({ reason: options.reason }),
|
|
1452
|
+
});
|
|
1453
|
+
if (!res.ok) {
|
|
1454
|
+
const err = await res.json();
|
|
1455
|
+
throw new Error(err.error || 'Failed to reject');
|
|
1456
|
+
}
|
|
1457
|
+
spinner.succeed('Access request rejected');
|
|
1458
|
+
}
|
|
1459
|
+
catch (err) {
|
|
1460
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1461
|
+
process.exit(1);
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
access
|
|
1465
|
+
.command('revoke <grantId>')
|
|
1466
|
+
.description('Revoke a previously approved access grant')
|
|
1467
|
+
.option('-f, --force', 'Skip confirmation')
|
|
1468
|
+
.action(async (grantId, options) => {
|
|
1469
|
+
const sessionId = config.get('sessionId');
|
|
1470
|
+
if (!sessionId) {
|
|
1471
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1472
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1473
|
+
process.exit(1);
|
|
1474
|
+
}
|
|
1475
|
+
if (!options.force) {
|
|
1476
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
1477
|
+
{
|
|
1478
|
+
type: 'confirm',
|
|
1479
|
+
name: 'confirm',
|
|
1480
|
+
message: `Revoke access grant ${chalk_1.default.cyan(grantId)}? The requester will lose access.`,
|
|
1481
|
+
default: false,
|
|
1482
|
+
},
|
|
1483
|
+
]);
|
|
1484
|
+
if (!confirm) {
|
|
1485
|
+
console.log('Cancelled.');
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
const spinner = (0, ora_1.default)('Revoking access...').start();
|
|
1490
|
+
log('DELETE /access/' + grantId);
|
|
1491
|
+
try {
|
|
1492
|
+
const res = await fetch(`${API_URL}/access/${grantId}`, {
|
|
1493
|
+
method: 'DELETE',
|
|
1494
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1495
|
+
});
|
|
1496
|
+
if (!res.ok) {
|
|
1497
|
+
const err = await res.json();
|
|
1498
|
+
throw new Error(err.error || 'Failed to revoke');
|
|
1499
|
+
}
|
|
1500
|
+
spinner.succeed('Access revoked');
|
|
1501
|
+
}
|
|
1502
|
+
catch (err) {
|
|
1503
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1504
|
+
process.exit(1);
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
1226
1507
|
// ========== STATUS COMMAND ==========
|
|
1227
1508
|
program
|
|
1228
1509
|
.command('status')
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1360,6 +1360,317 @@ ${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('--agent <agentId>', 'Filter by agent ID')
|
|
1379
|
+
.option('--status <status>', 'Filter by status (pending, approved, rejected, all)', 'pending')
|
|
1380
|
+
.option('--search <query>', 'Search by requester name or ID')
|
|
1381
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
1382
|
+
.option('--offset <n>', 'Skip first N results', '0')
|
|
1383
|
+
.option('--json', 'Output as JSON')
|
|
1384
|
+
.action(async (options) => {
|
|
1385
|
+
const sessionId = config.get('sessionId') as string;
|
|
1386
|
+
if (!sessionId) {
|
|
1387
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1388
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
const spinner = ora('Fetching access requests...').start();
|
|
1393
|
+
|
|
1394
|
+
// Build query params
|
|
1395
|
+
const params = new URLSearchParams();
|
|
1396
|
+
if (options.agent) params.set('agent', options.agent);
|
|
1397
|
+
if (options.status && options.status !== 'all') params.set('status', options.status);
|
|
1398
|
+
if (options.search) params.set('search', options.search);
|
|
1399
|
+
params.set('limit', options.limit);
|
|
1400
|
+
params.set('offset', options.offset);
|
|
1401
|
+
|
|
1402
|
+
log('GET /access/inbound?' + params.toString());
|
|
1403
|
+
|
|
1404
|
+
try {
|
|
1405
|
+
const res = await fetch(`${API_URL}/access/inbound?${params}`, {
|
|
1406
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
if (!res.ok) {
|
|
1410
|
+
throw new Error('Failed to fetch access requests');
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
const data = await res.json();
|
|
1414
|
+
spinner.stop();
|
|
1415
|
+
|
|
1416
|
+
if (options.json) {
|
|
1417
|
+
console.log(JSON.stringify(data.grants, null, 2));
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (data.grants.length === 0) {
|
|
1422
|
+
console.log(chalk.gray(`\nNo ${options.status === 'all' ? '' : options.status + ' '}access requests found.`));
|
|
1423
|
+
if (options.agent) console.log(chalk.gray(` Agent filter: ${options.agent}`));
|
|
1424
|
+
if (options.search) console.log(chalk.gray(` Search: "${options.search}"`));
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
console.log(chalk.bold(`\nš Access Requests${options.agent ? ` for ${options.agent}` : ''}:\n`));
|
|
1429
|
+
|
|
1430
|
+
for (const grant of data.grants) {
|
|
1431
|
+
const statusColor = grant.status === 'pending' ? chalk.yellow :
|
|
1432
|
+
grant.status === 'approved' ? chalk.green : chalk.red;
|
|
1433
|
+
console.log(` ${chalk.cyan(grant.id)}`);
|
|
1434
|
+
console.log(` From: ${grant.requester_agent_name || grant.requester_agent_id || 'Unknown'}`);
|
|
1435
|
+
console.log(` To: ${grant.target_agent_name || grant.target_agent_id}`);
|
|
1436
|
+
console.log(` Status: ${statusColor(grant.status)}`);
|
|
1437
|
+
if (grant.price_amount != null) {
|
|
1438
|
+
console.log(` Price: ${grant.price_amount} ${grant.price_currency}/${grant.price_unit}`);
|
|
1439
|
+
}
|
|
1440
|
+
if (grant.discount_percent != null) {
|
|
1441
|
+
console.log(` Discount: ${grant.discount_percent}%`);
|
|
1442
|
+
}
|
|
1443
|
+
if (grant.requested_reason) {
|
|
1444
|
+
console.log(` Reason: ${chalk.gray(grant.requested_reason)}`);
|
|
1445
|
+
}
|
|
1446
|
+
console.log(` Requested: ${new Date(grant.requested_at).toLocaleString()}`);
|
|
1447
|
+
console.log('');
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// Pagination info
|
|
1451
|
+
if (data.grants.length >= parseInt(options.limit)) {
|
|
1452
|
+
console.log(chalk.gray(`Showing ${data.grants.length} results. Use --offset ${parseInt(options.offset) + parseInt(options.limit)} for next page.`));
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
if (options.status === 'pending' && data.grants.length > 0) {
|
|
1456
|
+
console.log(chalk.gray(`\nApprove: gopherhole access approve <id>`));
|
|
1457
|
+
console.log(chalk.gray(`Reject: gopherhole access reject <id>`));
|
|
1458
|
+
}
|
|
1459
|
+
} catch (err) {
|
|
1460
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1461
|
+
process.exit(1);
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
access
|
|
1466
|
+
.command('approve <grantId>')
|
|
1467
|
+
.description('Approve an access request')
|
|
1468
|
+
.option('--price <amount>', 'Set custom price (e.g., 0.01)')
|
|
1469
|
+
.option('--currency <code>', 'Currency code (default: USD)', 'USD')
|
|
1470
|
+
.option('--unit <unit>', 'Price unit (request, message, task, month)', 'request')
|
|
1471
|
+
.option('--discount <percent>', 'Discount off default price (e.g., 20 for 20% off)')
|
|
1472
|
+
.option('--skill-pricing <json>', 'Per-skill pricing as JSON (e.g., \'{"translate":{"amount":0.05,"currency":"USD","unit":"request"}}\')')
|
|
1473
|
+
.action(async (grantId, options) => {
|
|
1474
|
+
const sessionId = config.get('sessionId') as string;
|
|
1475
|
+
if (!sessionId) {
|
|
1476
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1477
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1478
|
+
process.exit(1);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
const spinner = ora('Approving access request...').start();
|
|
1482
|
+
log('PUT /access/' + grantId + '/approve');
|
|
1483
|
+
|
|
1484
|
+
try {
|
|
1485
|
+
const body: any = {};
|
|
1486
|
+
if (options.price) {
|
|
1487
|
+
body.price_amount = parseFloat(options.price);
|
|
1488
|
+
body.price_currency = options.currency;
|
|
1489
|
+
body.price_unit = options.unit;
|
|
1490
|
+
}
|
|
1491
|
+
if (options.discount) {
|
|
1492
|
+
body.discount_percent = parseFloat(options.discount);
|
|
1493
|
+
}
|
|
1494
|
+
if (options.skillPricing) {
|
|
1495
|
+
body.skill_pricing = JSON.parse(options.skillPricing);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
const res = await fetch(`${API_URL}/access/${grantId}/approve`, {
|
|
1499
|
+
method: 'PUT',
|
|
1500
|
+
headers: {
|
|
1501
|
+
'Content-Type': 'application/json',
|
|
1502
|
+
'X-Session-ID': sessionId,
|
|
1503
|
+
},
|
|
1504
|
+
body: JSON.stringify(body),
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
if (!res.ok) {
|
|
1508
|
+
const err = await res.json();
|
|
1509
|
+
throw new Error(err.error || 'Failed to approve');
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
spinner.succeed('Access request approved');
|
|
1513
|
+
|
|
1514
|
+
if (options.price) {
|
|
1515
|
+
console.log(chalk.gray(` Custom price: ${options.price} ${options.currency}/${options.unit}`));
|
|
1516
|
+
}
|
|
1517
|
+
if (options.discount) {
|
|
1518
|
+
console.log(chalk.gray(` Discount: ${options.discount}% off`));
|
|
1519
|
+
}
|
|
1520
|
+
} catch (err) {
|
|
1521
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1522
|
+
process.exit(1);
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
access
|
|
1527
|
+
.command('edit <grantId>')
|
|
1528
|
+
.description('Edit pricing on an existing access grant')
|
|
1529
|
+
.option('--price <amount>', 'Set custom price (e.g., 0.01)')
|
|
1530
|
+
.option('--currency <code>', 'Currency code', 'USD')
|
|
1531
|
+
.option('--unit <unit>', 'Price unit (request, message, task, month)')
|
|
1532
|
+
.option('--discount <percent>', 'Discount off default price (e.g., 20 for 20% off)')
|
|
1533
|
+
.option('--clear-discount', 'Remove discount')
|
|
1534
|
+
.option('--skill-pricing <json>', 'Per-skill pricing as JSON')
|
|
1535
|
+
.option('--clear-skill-pricing', 'Remove per-skill pricing')
|
|
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
|
+
const spinner = ora('Updating access grant...').start();
|
|
1545
|
+
log('PATCH /access/' + grantId);
|
|
1546
|
+
|
|
1547
|
+
try {
|
|
1548
|
+
const body: any = {};
|
|
1549
|
+
if (options.price) {
|
|
1550
|
+
body.price_amount = parseFloat(options.price);
|
|
1551
|
+
body.price_currency = options.currency;
|
|
1552
|
+
body.price_unit = options.unit;
|
|
1553
|
+
}
|
|
1554
|
+
if (options.discount) {
|
|
1555
|
+
body.discount_percent = parseFloat(options.discount);
|
|
1556
|
+
}
|
|
1557
|
+
if (options.clearDiscount) {
|
|
1558
|
+
body.discount_percent = null;
|
|
1559
|
+
}
|
|
1560
|
+
if (options.skillPricing) {
|
|
1561
|
+
body.skill_pricing = JSON.parse(options.skillPricing);
|
|
1562
|
+
}
|
|
1563
|
+
if (options.clearSkillPricing) {
|
|
1564
|
+
body.skill_pricing = null;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
const res = await fetch(`${API_URL}/access/${grantId}`, {
|
|
1568
|
+
method: 'PATCH',
|
|
1569
|
+
headers: {
|
|
1570
|
+
'Content-Type': 'application/json',
|
|
1571
|
+
'X-Session-ID': sessionId,
|
|
1572
|
+
},
|
|
1573
|
+
body: JSON.stringify(body),
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
if (!res.ok) {
|
|
1577
|
+
const err = await res.json();
|
|
1578
|
+
throw new Error(err.error || 'Failed to update grant');
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
spinner.succeed('Access grant updated');
|
|
1582
|
+
} catch (err) {
|
|
1583
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1584
|
+
process.exit(1);
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
access
|
|
1589
|
+
.command('reject <grantId>')
|
|
1590
|
+
.description('Reject an access request')
|
|
1591
|
+
.option('--reason <reason>', 'Reason for rejection')
|
|
1592
|
+
.action(async (grantId, options) => {
|
|
1593
|
+
const sessionId = config.get('sessionId') as string;
|
|
1594
|
+
if (!sessionId) {
|
|
1595
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1596
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1597
|
+
process.exit(1);
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
const spinner = ora('Rejecting access request...').start();
|
|
1601
|
+
log('PUT /access/' + grantId + '/reject');
|
|
1602
|
+
|
|
1603
|
+
try {
|
|
1604
|
+
const res = await fetch(`${API_URL}/access/${grantId}/reject`, {
|
|
1605
|
+
method: 'PUT',
|
|
1606
|
+
headers: {
|
|
1607
|
+
'Content-Type': 'application/json',
|
|
1608
|
+
'X-Session-ID': sessionId,
|
|
1609
|
+
},
|
|
1610
|
+
body: JSON.stringify({ reason: options.reason }),
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
if (!res.ok) {
|
|
1614
|
+
const err = await res.json();
|
|
1615
|
+
throw new Error(err.error || 'Failed to reject');
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
spinner.succeed('Access request rejected');
|
|
1619
|
+
} catch (err) {
|
|
1620
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1621
|
+
process.exit(1);
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
access
|
|
1626
|
+
.command('revoke <grantId>')
|
|
1627
|
+
.description('Revoke a previously approved access grant')
|
|
1628
|
+
.option('-f, --force', 'Skip confirmation')
|
|
1629
|
+
.action(async (grantId, options) => {
|
|
1630
|
+
const sessionId = config.get('sessionId') as string;
|
|
1631
|
+
if (!sessionId) {
|
|
1632
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1633
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1634
|
+
process.exit(1);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
if (!options.force) {
|
|
1638
|
+
const { confirm } = await inquirer.prompt([
|
|
1639
|
+
{
|
|
1640
|
+
type: 'confirm',
|
|
1641
|
+
name: 'confirm',
|
|
1642
|
+
message: `Revoke access grant ${chalk.cyan(grantId)}? The requester will lose access.`,
|
|
1643
|
+
default: false,
|
|
1644
|
+
},
|
|
1645
|
+
]);
|
|
1646
|
+
|
|
1647
|
+
if (!confirm) {
|
|
1648
|
+
console.log('Cancelled.');
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
const spinner = ora('Revoking access...').start();
|
|
1654
|
+
log('DELETE /access/' + grantId);
|
|
1655
|
+
|
|
1656
|
+
try {
|
|
1657
|
+
const res = await fetch(`${API_URL}/access/${grantId}`, {
|
|
1658
|
+
method: 'DELETE',
|
|
1659
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
if (!res.ok) {
|
|
1663
|
+
const err = await res.json();
|
|
1664
|
+
throw new Error(err.error || 'Failed to revoke');
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
spinner.succeed('Access revoked');
|
|
1668
|
+
} catch (err) {
|
|
1669
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1670
|
+
process.exit(1);
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1363
1674
|
// ========== STATUS COMMAND ==========
|
|
1364
1675
|
|
|
1365
1676
|
program
|