@gopherhole/cli 0.5.0 → 0.6.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 +790 -1
- package/package.json +1 -1
- package/src/index.ts +609 -1
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.6.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) {
|
|
@@ -2950,5 +2950,794 @@ wsMembers
|
|
|
2950
2950
|
process.exit(1);
|
|
2951
2951
|
}
|
|
2952
2952
|
});
|
|
2953
|
+
// ========== KEYS COMMAND ==========
|
|
2954
|
+
const keys = program
|
|
2955
|
+
.command('keys')
|
|
2956
|
+
.description(`Manage API keys
|
|
2957
|
+
|
|
2958
|
+
${chalk_1.default.bold('Examples:')}
|
|
2959
|
+
$ gopherhole keys list
|
|
2960
|
+
$ gopherhole keys create --name "prod" --agent agent-abc123
|
|
2961
|
+
$ gopherhole keys delete key-abc123
|
|
2962
|
+
`);
|
|
2963
|
+
keys
|
|
2964
|
+
.command('list')
|
|
2965
|
+
.description('List all API keys on the tenant')
|
|
2966
|
+
.option('--json', 'Output as JSON')
|
|
2967
|
+
.action(async (options) => {
|
|
2968
|
+
const sessionId = config.get('sessionId');
|
|
2969
|
+
if (!sessionId) {
|
|
2970
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
2971
|
+
process.exit(1);
|
|
2972
|
+
}
|
|
2973
|
+
const spinner = (0, ora_1.default)('Fetching API keys...').start();
|
|
2974
|
+
try {
|
|
2975
|
+
const res = await fetch(`${API_URL}/api-keys`, { headers: { 'X-Session-ID': sessionId } });
|
|
2976
|
+
if (!res.ok)
|
|
2977
|
+
throw new Error('Failed to fetch API keys');
|
|
2978
|
+
const data = await res.json();
|
|
2979
|
+
const keysList = Array.isArray(data) ? data : data.keys || [];
|
|
2980
|
+
spinner.stop();
|
|
2981
|
+
if (options.json) {
|
|
2982
|
+
console.log(JSON.stringify(keysList, null, 2));
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
if (keysList.length === 0) {
|
|
2986
|
+
console.log(chalk_1.default.yellow('\nNo API keys found.\n'));
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
console.log(chalk_1.default.bold('\nAPI Keys:\n'));
|
|
2990
|
+
for (const k of keysList) {
|
|
2991
|
+
console.log(` ${chalk_1.default.cyan(k.id)} — ${k.name || 'unnamed'}`);
|
|
2992
|
+
console.log(` Agent: ${k.agentId || k.agent_id || 'none'} | Prefix: ${chalk_1.default.gray(k.prefix || '???')} | Scopes: ${k.scopes?.join(', ') || 'default'}`);
|
|
2993
|
+
console.log('');
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
catch (err) {
|
|
2997
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
2998
|
+
process.exit(1);
|
|
2999
|
+
}
|
|
3000
|
+
});
|
|
3001
|
+
keys
|
|
3002
|
+
.command('create')
|
|
3003
|
+
.description('Create a new API key')
|
|
3004
|
+
.requiredOption('--name <name>', 'Human-readable label for this key')
|
|
3005
|
+
.requiredOption('--agent <agentId>', 'The agent this key authenticates as')
|
|
3006
|
+
.option('--scopes <scopes>', 'Comma-separated scopes (e.g., "messages:send,memory:read")')
|
|
3007
|
+
.action(async (options) => {
|
|
3008
|
+
const sessionId = config.get('sessionId');
|
|
3009
|
+
if (!sessionId) {
|
|
3010
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3011
|
+
process.exit(1);
|
|
3012
|
+
}
|
|
3013
|
+
const spinner = (0, ora_1.default)('Creating API key...').start();
|
|
3014
|
+
try {
|
|
3015
|
+
const body = { name: options.name, agentId: options.agent };
|
|
3016
|
+
if (options.scopes)
|
|
3017
|
+
body.scopes = options.scopes.split(',').map((s) => s.trim());
|
|
3018
|
+
const res = await fetch(`${API_URL}/api-keys`, {
|
|
3019
|
+
method: 'POST',
|
|
3020
|
+
headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
|
|
3021
|
+
body: JSON.stringify(body),
|
|
3022
|
+
});
|
|
3023
|
+
if (!res.ok) {
|
|
3024
|
+
const err = await res.json();
|
|
3025
|
+
throw new Error(err.error || 'Failed to create key');
|
|
3026
|
+
}
|
|
3027
|
+
const data = await res.json();
|
|
3028
|
+
spinner.succeed('API key created!');
|
|
3029
|
+
console.log('');
|
|
3030
|
+
console.log(` ID: ${chalk_1.default.cyan(data.id)}`);
|
|
3031
|
+
console.log(` Key: ${brand.green(data.key || data.secret)}`);
|
|
3032
|
+
console.log('');
|
|
3033
|
+
console.log(chalk_1.default.yellow(' Store this key securely — it will not be shown again.'));
|
|
3034
|
+
console.log('');
|
|
3035
|
+
}
|
|
3036
|
+
catch (err) {
|
|
3037
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3038
|
+
process.exit(1);
|
|
3039
|
+
}
|
|
3040
|
+
});
|
|
3041
|
+
keys
|
|
3042
|
+
.command('delete <keyId>')
|
|
3043
|
+
.description('Revoke and delete an API key')
|
|
3044
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
3045
|
+
.action(async (keyId, options) => {
|
|
3046
|
+
const sessionId = config.get('sessionId');
|
|
3047
|
+
if (!sessionId) {
|
|
3048
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3049
|
+
process.exit(1);
|
|
3050
|
+
}
|
|
3051
|
+
if (!options.yes) {
|
|
3052
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
3053
|
+
type: 'confirm', name: 'confirm', message: `Permanently revoke key ${keyId}?`, default: false,
|
|
3054
|
+
}]);
|
|
3055
|
+
if (!confirm) {
|
|
3056
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
const spinner = (0, ora_1.default)('Revoking key...').start();
|
|
3061
|
+
try {
|
|
3062
|
+
const res = await fetch(`${API_URL}/api-keys/${keyId}`, {
|
|
3063
|
+
method: 'DELETE',
|
|
3064
|
+
headers: { 'X-Session-ID': sessionId },
|
|
3065
|
+
});
|
|
3066
|
+
if (!res.ok) {
|
|
3067
|
+
const err = await res.json();
|
|
3068
|
+
throw new Error(err.error || 'Failed to delete key');
|
|
3069
|
+
}
|
|
3070
|
+
spinner.succeed(`Key ${chalk_1.default.cyan(keyId)} revoked and deleted.`);
|
|
3071
|
+
}
|
|
3072
|
+
catch (err) {
|
|
3073
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3074
|
+
process.exit(1);
|
|
3075
|
+
}
|
|
3076
|
+
});
|
|
3077
|
+
// ========== TEAM COMMAND ==========
|
|
3078
|
+
const team = program
|
|
3079
|
+
.command('team')
|
|
3080
|
+
.description(`Manage team members
|
|
3081
|
+
|
|
3082
|
+
${chalk_1.default.bold('Examples:')}
|
|
3083
|
+
$ gopherhole team list
|
|
3084
|
+
$ gopherhole team invite user@example.com --role admin
|
|
3085
|
+
$ gopherhole team remove member-abc123
|
|
3086
|
+
`);
|
|
3087
|
+
team
|
|
3088
|
+
.command('list')
|
|
3089
|
+
.description('List team members')
|
|
3090
|
+
.option('--json', 'Output as JSON')
|
|
3091
|
+
.action(async (options) => {
|
|
3092
|
+
const sessionId = config.get('sessionId');
|
|
3093
|
+
if (!sessionId) {
|
|
3094
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3095
|
+
process.exit(1);
|
|
3096
|
+
}
|
|
3097
|
+
const spinner = (0, ora_1.default)('Fetching team...').start();
|
|
3098
|
+
try {
|
|
3099
|
+
const res = await fetch(`${API_URL}/team/members`, { headers: { 'X-Session-ID': sessionId } });
|
|
3100
|
+
if (!res.ok)
|
|
3101
|
+
throw new Error('Failed to fetch team');
|
|
3102
|
+
const data = await res.json();
|
|
3103
|
+
const members = Array.isArray(data) ? data : data.members || [];
|
|
3104
|
+
spinner.stop();
|
|
3105
|
+
if (options.json) {
|
|
3106
|
+
console.log(JSON.stringify(members, null, 2));
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
if (members.length === 0) {
|
|
3110
|
+
console.log(chalk_1.default.yellow('\nNo team members.\n'));
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
console.log(chalk_1.default.bold('\nTeam Members:\n'));
|
|
3114
|
+
for (const m of members) {
|
|
3115
|
+
const role = m.role === 'admin' ? chalk_1.default.red(m.role) : chalk_1.default.gray(m.role);
|
|
3116
|
+
console.log(` ${chalk_1.default.bold(m.name || m.email)} (${chalk_1.default.cyan(m.id)})`);
|
|
3117
|
+
console.log(` Role: ${role}${m.joinedAt ? ` | Joined: ${m.joinedAt}` : ''}`);
|
|
3118
|
+
console.log('');
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
catch (err) {
|
|
3122
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3123
|
+
process.exit(1);
|
|
3124
|
+
}
|
|
3125
|
+
});
|
|
3126
|
+
team
|
|
3127
|
+
.command('invite <email>')
|
|
3128
|
+
.description('Invite someone to your tenant')
|
|
3129
|
+
.option('--role <role>', 'Role: admin, member, viewer (default: member)')
|
|
3130
|
+
.action(async (email, options) => {
|
|
3131
|
+
const sessionId = config.get('sessionId');
|
|
3132
|
+
if (!sessionId) {
|
|
3133
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3134
|
+
process.exit(1);
|
|
3135
|
+
}
|
|
3136
|
+
const spinner = (0, ora_1.default)(`Sending invite to ${email}...`).start();
|
|
3137
|
+
try {
|
|
3138
|
+
const body = { email };
|
|
3139
|
+
if (options.role)
|
|
3140
|
+
body.role = options.role;
|
|
3141
|
+
const res = await fetch(`${API_URL}/team/invites`, {
|
|
3142
|
+
method: 'POST',
|
|
3143
|
+
headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
|
|
3144
|
+
body: JSON.stringify(body),
|
|
3145
|
+
});
|
|
3146
|
+
if (!res.ok) {
|
|
3147
|
+
const err = await res.json();
|
|
3148
|
+
throw new Error(err.error || 'Failed to send invite');
|
|
3149
|
+
}
|
|
3150
|
+
spinner.succeed(`Invitation sent to ${chalk_1.default.cyan(email)} with role: ${options.role || 'member'}`);
|
|
3151
|
+
}
|
|
3152
|
+
catch (err) {
|
|
3153
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3154
|
+
process.exit(1);
|
|
3155
|
+
}
|
|
3156
|
+
});
|
|
3157
|
+
team
|
|
3158
|
+
.command('remove <memberId>')
|
|
3159
|
+
.description('Remove a team member')
|
|
3160
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
3161
|
+
.action(async (memberId, options) => {
|
|
3162
|
+
const sessionId = config.get('sessionId');
|
|
3163
|
+
if (!sessionId) {
|
|
3164
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3165
|
+
process.exit(1);
|
|
3166
|
+
}
|
|
3167
|
+
if (!options.yes) {
|
|
3168
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
3169
|
+
type: 'confirm', name: 'confirm', message: `Remove team member ${memberId}?`, default: false,
|
|
3170
|
+
}]);
|
|
3171
|
+
if (!confirm) {
|
|
3172
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
const spinner = (0, ora_1.default)('Removing member...').start();
|
|
3177
|
+
try {
|
|
3178
|
+
const res = await fetch(`${API_URL}/team/members/${memberId}`, {
|
|
3179
|
+
method: 'DELETE',
|
|
3180
|
+
headers: { 'X-Session-ID': sessionId },
|
|
3181
|
+
});
|
|
3182
|
+
if (!res.ok) {
|
|
3183
|
+
const err = await res.json();
|
|
3184
|
+
throw new Error(err.error || 'Failed to remove member');
|
|
3185
|
+
}
|
|
3186
|
+
spinner.succeed(`Team member ${chalk_1.default.cyan(memberId)} removed.`);
|
|
3187
|
+
}
|
|
3188
|
+
catch (err) {
|
|
3189
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3190
|
+
process.exit(1);
|
|
3191
|
+
}
|
|
3192
|
+
});
|
|
3193
|
+
// ========== USAGE COMMAND ==========
|
|
3194
|
+
const usage = program
|
|
3195
|
+
.command('usage')
|
|
3196
|
+
.description(`View usage statistics
|
|
3197
|
+
|
|
3198
|
+
${chalk_1.default.bold('Examples:')}
|
|
3199
|
+
$ gopherhole usage summary
|
|
3200
|
+
$ gopherhole usage summary --period week
|
|
3201
|
+
$ gopherhole usage agents
|
|
3202
|
+
`);
|
|
3203
|
+
usage
|
|
3204
|
+
.command('summary')
|
|
3205
|
+
.description('Get high-level usage overview')
|
|
3206
|
+
.option('--period <period>', 'Time window: day, week, month (default: month)')
|
|
3207
|
+
.option('--json', 'Output as JSON')
|
|
3208
|
+
.action(async (options) => {
|
|
3209
|
+
const sessionId = config.get('sessionId');
|
|
3210
|
+
if (!sessionId) {
|
|
3211
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3212
|
+
process.exit(1);
|
|
3213
|
+
}
|
|
3214
|
+
const spinner = (0, ora_1.default)('Fetching usage...').start();
|
|
3215
|
+
try {
|
|
3216
|
+
const params = new URLSearchParams();
|
|
3217
|
+
if (options.period)
|
|
3218
|
+
params.set('period', options.period);
|
|
3219
|
+
const qs = params.toString() ? `?${params}` : '';
|
|
3220
|
+
const res = await fetch(`${API_URL}/usage/summary${qs}`, { headers: { 'X-Session-ID': sessionId } });
|
|
3221
|
+
if (!res.ok)
|
|
3222
|
+
throw new Error('Failed to fetch usage');
|
|
3223
|
+
const data = await res.json();
|
|
3224
|
+
spinner.stop();
|
|
3225
|
+
if (options.json) {
|
|
3226
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3227
|
+
return;
|
|
3228
|
+
}
|
|
3229
|
+
console.log(chalk_1.default.bold(`\nUsage Summary (${options.period || 'month'}):\n`));
|
|
3230
|
+
const d = data;
|
|
3231
|
+
if (d.messagesSent !== undefined)
|
|
3232
|
+
console.log(` Messages Sent: ${brand.green(d.messagesSent)}`);
|
|
3233
|
+
if (d.messagesReceived !== undefined)
|
|
3234
|
+
console.log(` Messages Received: ${brand.green(d.messagesReceived)}`);
|
|
3235
|
+
if (d.tasksCreated !== undefined)
|
|
3236
|
+
console.log(` Tasks Created: ${brand.green(d.tasksCreated)}`);
|
|
3237
|
+
if (d.connections !== undefined)
|
|
3238
|
+
console.log(` Connections: ${brand.green(d.connections)}`);
|
|
3239
|
+
if (d.creditsUsed !== undefined)
|
|
3240
|
+
console.log(` Credits Used: ${brand.green(d.creditsUsed)}`);
|
|
3241
|
+
console.log('');
|
|
3242
|
+
}
|
|
3243
|
+
catch (err) {
|
|
3244
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3245
|
+
process.exit(1);
|
|
3246
|
+
}
|
|
3247
|
+
});
|
|
3248
|
+
usage
|
|
3249
|
+
.command('agents')
|
|
3250
|
+
.description('Per-agent usage breakdown')
|
|
3251
|
+
.option('--period <period>', 'Time window: day, week, month (default: month)')
|
|
3252
|
+
.option('--limit <n>', 'Max agents to show (default: 20)')
|
|
3253
|
+
.option('--json', 'Output as JSON')
|
|
3254
|
+
.action(async (options) => {
|
|
3255
|
+
const sessionId = config.get('sessionId');
|
|
3256
|
+
if (!sessionId) {
|
|
3257
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3258
|
+
process.exit(1);
|
|
3259
|
+
}
|
|
3260
|
+
const spinner = (0, ora_1.default)('Fetching per-agent usage...').start();
|
|
3261
|
+
try {
|
|
3262
|
+
const params = new URLSearchParams();
|
|
3263
|
+
if (options.period)
|
|
3264
|
+
params.set('period', options.period);
|
|
3265
|
+
if (options.limit)
|
|
3266
|
+
params.set('limit', options.limit);
|
|
3267
|
+
const qs = params.toString() ? `?${params}` : '';
|
|
3268
|
+
const res = await fetch(`${API_URL}/usage/agents${qs}`, { headers: { 'X-Session-ID': sessionId } });
|
|
3269
|
+
if (!res.ok)
|
|
3270
|
+
throw new Error('Failed to fetch agent usage');
|
|
3271
|
+
const data = await res.json();
|
|
3272
|
+
spinner.stop();
|
|
3273
|
+
if (options.json) {
|
|
3274
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
const agents = Array.isArray(data) ? data : data.agents || [];
|
|
3278
|
+
if (agents.length === 0) {
|
|
3279
|
+
console.log(chalk_1.default.yellow('\nNo usage data.\n'));
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
console.log(chalk_1.default.bold(`\nPer-Agent Usage (${options.period || 'month'}):\n`));
|
|
3283
|
+
for (const a of agents) {
|
|
3284
|
+
console.log(` ${chalk_1.default.bold(a.name || a.agentId)} (${chalk_1.default.cyan(a.agentId || a.id)})`);
|
|
3285
|
+
if (a.messages !== undefined)
|
|
3286
|
+
console.log(` Messages: ${a.messages}`);
|
|
3287
|
+
if (a.tasks !== undefined)
|
|
3288
|
+
console.log(` Tasks: ${a.tasks}`);
|
|
3289
|
+
if (a.credits !== undefined)
|
|
3290
|
+
console.log(` Credits: ${a.credits}`);
|
|
3291
|
+
console.log('');
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
catch (err) {
|
|
3295
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3296
|
+
process.exit(1);
|
|
3297
|
+
}
|
|
3298
|
+
});
|
|
3299
|
+
// ========== WEBHOOKS COMMAND ==========
|
|
3300
|
+
const webhooks = program
|
|
3301
|
+
.command('webhooks')
|
|
3302
|
+
.description(`Manage webhooks
|
|
3303
|
+
|
|
3304
|
+
${chalk_1.default.bold('Examples:')}
|
|
3305
|
+
$ gopherhole webhooks list
|
|
3306
|
+
$ gopherhole webhooks create --url https://example.com/hook --events message.received,task.completed
|
|
3307
|
+
$ gopherhole webhooks delete wh-abc123
|
|
3308
|
+
$ gopherhole webhooks test wh-abc123
|
|
3309
|
+
`);
|
|
3310
|
+
webhooks
|
|
3311
|
+
.command('list')
|
|
3312
|
+
.description('List configured webhooks')
|
|
3313
|
+
.option('--json', 'Output as JSON')
|
|
3314
|
+
.action(async (options) => {
|
|
3315
|
+
const sessionId = config.get('sessionId');
|
|
3316
|
+
if (!sessionId) {
|
|
3317
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3318
|
+
process.exit(1);
|
|
3319
|
+
}
|
|
3320
|
+
const spinner = (0, ora_1.default)('Fetching webhooks...').start();
|
|
3321
|
+
try {
|
|
3322
|
+
const res = await fetch(`${API_URL}/webhooks`, { headers: { 'X-Session-ID': sessionId } });
|
|
3323
|
+
if (!res.ok)
|
|
3324
|
+
throw new Error('Failed to fetch webhooks');
|
|
3325
|
+
const data = await res.json();
|
|
3326
|
+
const hooks = Array.isArray(data) ? data : data.webhooks || [];
|
|
3327
|
+
spinner.stop();
|
|
3328
|
+
if (options.json) {
|
|
3329
|
+
console.log(JSON.stringify(hooks, null, 2));
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
if (hooks.length === 0) {
|
|
3333
|
+
console.log(chalk_1.default.yellow('\nNo webhooks configured.\n'));
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
console.log(chalk_1.default.bold('\nWebhooks:\n'));
|
|
3337
|
+
for (const h of hooks) {
|
|
3338
|
+
console.log(` ${chalk_1.default.cyan(h.id)} — ${h.url}`);
|
|
3339
|
+
console.log(` Events: ${h.events?.join(', ') || 'all'}${h.description ? ` | ${chalk_1.default.gray(h.description)}` : ''}`);
|
|
3340
|
+
console.log('');
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
catch (err) {
|
|
3344
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3345
|
+
process.exit(1);
|
|
3346
|
+
}
|
|
3347
|
+
});
|
|
3348
|
+
webhooks
|
|
3349
|
+
.command('create')
|
|
3350
|
+
.description('Create a new webhook')
|
|
3351
|
+
.requiredOption('--url <url>', 'HTTPS URL to deliver events to')
|
|
3352
|
+
.requiredOption('--events <events>', 'Comma-separated event types (e.g., "message.received,task.completed")')
|
|
3353
|
+
.option('--description <text>', 'Label for this webhook')
|
|
3354
|
+
.option('--secret <secret>', 'Signing secret for HMAC-SHA256 verification')
|
|
3355
|
+
.action(async (options) => {
|
|
3356
|
+
const sessionId = config.get('sessionId');
|
|
3357
|
+
if (!sessionId) {
|
|
3358
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3359
|
+
process.exit(1);
|
|
3360
|
+
}
|
|
3361
|
+
const spinner = (0, ora_1.default)('Creating webhook...').start();
|
|
3362
|
+
try {
|
|
3363
|
+
const body = {
|
|
3364
|
+
url: options.url,
|
|
3365
|
+
events: options.events.split(',').map((e) => e.trim()),
|
|
3366
|
+
};
|
|
3367
|
+
if (options.description)
|
|
3368
|
+
body.description = options.description;
|
|
3369
|
+
if (options.secret)
|
|
3370
|
+
body.secret = options.secret;
|
|
3371
|
+
const res = await fetch(`${API_URL}/webhooks`, {
|
|
3372
|
+
method: 'POST',
|
|
3373
|
+
headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
|
|
3374
|
+
body: JSON.stringify(body),
|
|
3375
|
+
});
|
|
3376
|
+
if (!res.ok) {
|
|
3377
|
+
const err = await res.json();
|
|
3378
|
+
throw new Error(err.error || 'Failed to create webhook');
|
|
3379
|
+
}
|
|
3380
|
+
const data = await res.json();
|
|
3381
|
+
spinner.succeed('Webhook created!');
|
|
3382
|
+
console.log(`\n ID: ${chalk_1.default.cyan(data.id)}`);
|
|
3383
|
+
console.log(` URL: ${data.url || options.url}`);
|
|
3384
|
+
console.log(` Events: ${body.events.join(', ')}\n`);
|
|
3385
|
+
}
|
|
3386
|
+
catch (err) {
|
|
3387
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3388
|
+
process.exit(1);
|
|
3389
|
+
}
|
|
3390
|
+
});
|
|
3391
|
+
webhooks
|
|
3392
|
+
.command('delete <webhookId>')
|
|
3393
|
+
.description('Delete a webhook')
|
|
3394
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
3395
|
+
.action(async (webhookId, options) => {
|
|
3396
|
+
const sessionId = config.get('sessionId');
|
|
3397
|
+
if (!sessionId) {
|
|
3398
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3399
|
+
process.exit(1);
|
|
3400
|
+
}
|
|
3401
|
+
if (!options.yes) {
|
|
3402
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
3403
|
+
type: 'confirm', name: 'confirm', message: `Delete webhook ${webhookId}?`, default: false,
|
|
3404
|
+
}]);
|
|
3405
|
+
if (!confirm) {
|
|
3406
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
const spinner = (0, ora_1.default)('Deleting webhook...').start();
|
|
3411
|
+
try {
|
|
3412
|
+
const res = await fetch(`${API_URL}/webhooks/${webhookId}`, {
|
|
3413
|
+
method: 'DELETE',
|
|
3414
|
+
headers: { 'X-Session-ID': sessionId },
|
|
3415
|
+
});
|
|
3416
|
+
if (!res.ok) {
|
|
3417
|
+
const err = await res.json();
|
|
3418
|
+
throw new Error(err.error || 'Failed to delete webhook');
|
|
3419
|
+
}
|
|
3420
|
+
spinner.succeed(`Webhook ${chalk_1.default.cyan(webhookId)} deleted.`);
|
|
3421
|
+
}
|
|
3422
|
+
catch (err) {
|
|
3423
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3424
|
+
process.exit(1);
|
|
3425
|
+
}
|
|
3426
|
+
});
|
|
3427
|
+
webhooks
|
|
3428
|
+
.command('test <webhookId>')
|
|
3429
|
+
.description('Send a test event to a webhook')
|
|
3430
|
+
.action(async (webhookId) => {
|
|
3431
|
+
const sessionId = config.get('sessionId');
|
|
3432
|
+
if (!sessionId) {
|
|
3433
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3434
|
+
process.exit(1);
|
|
3435
|
+
}
|
|
3436
|
+
const spinner = (0, ora_1.default)('Sending test event...').start();
|
|
3437
|
+
try {
|
|
3438
|
+
const res = await fetch(`${API_URL}/webhooks/${webhookId}/test`, {
|
|
3439
|
+
method: 'POST',
|
|
3440
|
+
headers: { 'X-Session-ID': sessionId },
|
|
3441
|
+
});
|
|
3442
|
+
if (!res.ok) {
|
|
3443
|
+
const err = await res.json();
|
|
3444
|
+
throw new Error(err.error || 'Failed to test webhook');
|
|
3445
|
+
}
|
|
3446
|
+
const data = await res.json();
|
|
3447
|
+
spinner.succeed(`Test event sent. Response: ${data.status || data.statusCode || 'delivered'}`);
|
|
3448
|
+
}
|
|
3449
|
+
catch (err) {
|
|
3450
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3451
|
+
process.exit(1);
|
|
3452
|
+
}
|
|
3453
|
+
});
|
|
3454
|
+
// ========== TENANT COMMAND ==========
|
|
3455
|
+
const tenant = program
|
|
3456
|
+
.command('tenant')
|
|
3457
|
+
.description(`View and manage tenant settings
|
|
3458
|
+
|
|
3459
|
+
${chalk_1.default.bold('Examples:')}
|
|
3460
|
+
$ gopherhole tenant settings
|
|
3461
|
+
$ gopherhole tenant update --name "Acme Corp" --slug acme
|
|
3462
|
+
`);
|
|
3463
|
+
tenant
|
|
3464
|
+
.command('settings')
|
|
3465
|
+
.description('View current tenant settings')
|
|
3466
|
+
.option('--json', 'Output as JSON')
|
|
3467
|
+
.action(async (options) => {
|
|
3468
|
+
const sessionId = config.get('sessionId');
|
|
3469
|
+
if (!sessionId) {
|
|
3470
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3471
|
+
process.exit(1);
|
|
3472
|
+
}
|
|
3473
|
+
const spinner = (0, ora_1.default)('Fetching tenant settings...').start();
|
|
3474
|
+
try {
|
|
3475
|
+
const res = await fetch(`${API_URL}/auth/tenant/settings`, { headers: { 'X-Session-ID': sessionId } });
|
|
3476
|
+
if (!res.ok)
|
|
3477
|
+
throw new Error('Failed to fetch tenant settings');
|
|
3478
|
+
const data = await res.json();
|
|
3479
|
+
spinner.stop();
|
|
3480
|
+
if (options.json) {
|
|
3481
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3484
|
+
console.log(chalk_1.default.bold('\nTenant Settings:\n'));
|
|
3485
|
+
if (data.name)
|
|
3486
|
+
console.log(` Name: ${brand.green(data.name)}`);
|
|
3487
|
+
if (data.slug)
|
|
3488
|
+
console.log(` Slug: ${chalk_1.default.cyan(data.slug)}`);
|
|
3489
|
+
if (data.plan)
|
|
3490
|
+
console.log(` Plan: ${data.plan}`);
|
|
3491
|
+
if (data.email)
|
|
3492
|
+
console.log(` Email: ${data.email}`);
|
|
3493
|
+
if (data.id)
|
|
3494
|
+
console.log(` ID: ${chalk_1.default.gray(data.id)}`);
|
|
3495
|
+
console.log('');
|
|
3496
|
+
}
|
|
3497
|
+
catch (err) {
|
|
3498
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3499
|
+
process.exit(1);
|
|
3500
|
+
}
|
|
3501
|
+
});
|
|
3502
|
+
tenant
|
|
3503
|
+
.command('update')
|
|
3504
|
+
.description('Update tenant name or slug')
|
|
3505
|
+
.option('--name <name>', 'New display name')
|
|
3506
|
+
.option('--slug <slug>', 'New URL-safe slug')
|
|
3507
|
+
.action(async (options) => {
|
|
3508
|
+
const sessionId = config.get('sessionId');
|
|
3509
|
+
if (!sessionId) {
|
|
3510
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3511
|
+
process.exit(1);
|
|
3512
|
+
}
|
|
3513
|
+
const body = {};
|
|
3514
|
+
if (options.name)
|
|
3515
|
+
body.name = options.name;
|
|
3516
|
+
if (options.slug)
|
|
3517
|
+
body.slug = options.slug;
|
|
3518
|
+
if (Object.keys(body).length === 0) {
|
|
3519
|
+
console.log(chalk_1.default.yellow('No changes specified. Use --name or --slug.'));
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
3522
|
+
const spinner = (0, ora_1.default)('Updating tenant...').start();
|
|
3523
|
+
try {
|
|
3524
|
+
const res = await fetch(`${API_URL}/auth/tenant`, {
|
|
3525
|
+
method: 'PATCH',
|
|
3526
|
+
headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
|
|
3527
|
+
body: JSON.stringify(body),
|
|
3528
|
+
});
|
|
3529
|
+
if (!res.ok) {
|
|
3530
|
+
const err = await res.json();
|
|
3531
|
+
throw new Error(err.error || 'Failed to update tenant');
|
|
3532
|
+
}
|
|
3533
|
+
spinner.succeed('Tenant updated.');
|
|
3534
|
+
if (body.name)
|
|
3535
|
+
console.log(` Name: ${brand.green(body.name)}`);
|
|
3536
|
+
if (body.slug)
|
|
3537
|
+
console.log(` Slug: ${chalk_1.default.cyan(body.slug)}`);
|
|
3538
|
+
}
|
|
3539
|
+
catch (err) {
|
|
3540
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3541
|
+
process.exit(1);
|
|
3542
|
+
}
|
|
3543
|
+
});
|
|
3544
|
+
// ========== PROFILE COMMAND ==========
|
|
3545
|
+
program
|
|
3546
|
+
.command('profile')
|
|
3547
|
+
.description(`Update your user profile
|
|
3548
|
+
|
|
3549
|
+
${chalk_1.default.bold('Examples:')}
|
|
3550
|
+
$ gopherhole profile --name "Jane Smith"
|
|
3551
|
+
$ gopherhole profile --avatar https://example.com/photo.jpg
|
|
3552
|
+
`)
|
|
3553
|
+
.option('--name <name>', 'Display name')
|
|
3554
|
+
.option('--avatar <url>', 'Avatar image URL')
|
|
3555
|
+
.action(async (options) => {
|
|
3556
|
+
const sessionId = config.get('sessionId');
|
|
3557
|
+
if (!sessionId) {
|
|
3558
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3559
|
+
process.exit(1);
|
|
3560
|
+
}
|
|
3561
|
+
const body = {};
|
|
3562
|
+
if (options.name)
|
|
3563
|
+
body.name = options.name;
|
|
3564
|
+
if (options.avatar)
|
|
3565
|
+
body.avatarUrl = options.avatar;
|
|
3566
|
+
if (Object.keys(body).length === 0) {
|
|
3567
|
+
console.log(chalk_1.default.yellow('No changes specified. Use --name or --avatar.'));
|
|
3568
|
+
return;
|
|
3569
|
+
}
|
|
3570
|
+
const spinner = (0, ora_1.default)('Updating profile...').start();
|
|
3571
|
+
try {
|
|
3572
|
+
const res = await fetch(`${API_URL}/auth/me`, {
|
|
3573
|
+
method: 'PATCH',
|
|
3574
|
+
headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
|
|
3575
|
+
body: JSON.stringify(body),
|
|
3576
|
+
});
|
|
3577
|
+
if (!res.ok) {
|
|
3578
|
+
const err = await res.json();
|
|
3579
|
+
throw new Error(err.error || 'Failed to update profile');
|
|
3580
|
+
}
|
|
3581
|
+
spinner.succeed('Profile updated.');
|
|
3582
|
+
if (body.name)
|
|
3583
|
+
console.log(` Name: ${brand.green(body.name)}`);
|
|
3584
|
+
if (body.avatarUrl)
|
|
3585
|
+
console.log(` Avatar: updated`);
|
|
3586
|
+
}
|
|
3587
|
+
catch (err) {
|
|
3588
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3589
|
+
process.exit(1);
|
|
3590
|
+
}
|
|
3591
|
+
});
|
|
3592
|
+
// ========== SECRETS COMMAND (workspace secrets) ==========
|
|
3593
|
+
const secrets = program
|
|
3594
|
+
.command('secrets')
|
|
3595
|
+
.description(`Manage workspace secrets
|
|
3596
|
+
|
|
3597
|
+
${chalk_1.default.bold('Examples:')}
|
|
3598
|
+
$ gopherhole secrets list ws-abc123
|
|
3599
|
+
$ gopherhole secrets set ws-abc123 OPENAI_API_KEY sk-abc...
|
|
3600
|
+
$ gopherhole secrets delete ws-abc123 OPENAI_API_KEY
|
|
3601
|
+
`);
|
|
3602
|
+
secrets
|
|
3603
|
+
.command('list <workspaceId>')
|
|
3604
|
+
.description('List secret keys in a workspace (values are never shown)')
|
|
3605
|
+
.option('--json', 'Output as JSON')
|
|
3606
|
+
.action(async (workspaceId, options) => {
|
|
3607
|
+
const sessionId = config.get('sessionId');
|
|
3608
|
+
if (!sessionId) {
|
|
3609
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3610
|
+
process.exit(1);
|
|
3611
|
+
}
|
|
3612
|
+
const spinner = (0, ora_1.default)('Fetching secrets...').start();
|
|
3613
|
+
try {
|
|
3614
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/secrets`, { headers: { 'X-Session-ID': sessionId } });
|
|
3615
|
+
if (!res.ok)
|
|
3616
|
+
throw new Error('Failed to fetch secrets');
|
|
3617
|
+
const data = await res.json();
|
|
3618
|
+
const secretsList = Array.isArray(data) ? data : data.secrets || [];
|
|
3619
|
+
spinner.stop();
|
|
3620
|
+
if (options.json) {
|
|
3621
|
+
console.log(JSON.stringify(secretsList, null, 2));
|
|
3622
|
+
return;
|
|
3623
|
+
}
|
|
3624
|
+
if (secretsList.length === 0) {
|
|
3625
|
+
console.log(chalk_1.default.yellow('\nNo secrets in this workspace.\n'));
|
|
3626
|
+
return;
|
|
3627
|
+
}
|
|
3628
|
+
console.log(chalk_1.default.bold('\nWorkspace Secrets:\n'));
|
|
3629
|
+
for (const s of secretsList) {
|
|
3630
|
+
console.log(` ${chalk_1.default.cyan(s.key || s.name)}`);
|
|
3631
|
+
}
|
|
3632
|
+
console.log('');
|
|
3633
|
+
}
|
|
3634
|
+
catch (err) {
|
|
3635
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3636
|
+
process.exit(1);
|
|
3637
|
+
}
|
|
3638
|
+
});
|
|
3639
|
+
secrets
|
|
3640
|
+
.command('set <workspaceId> <key> <value>')
|
|
3641
|
+
.description('Create or update a workspace secret')
|
|
3642
|
+
.action(async (workspaceId, key, value) => {
|
|
3643
|
+
const sessionId = config.get('sessionId');
|
|
3644
|
+
if (!sessionId) {
|
|
3645
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3646
|
+
process.exit(1);
|
|
3647
|
+
}
|
|
3648
|
+
const spinner = (0, ora_1.default)(`Setting secret ${key}...`).start();
|
|
3649
|
+
try {
|
|
3650
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/secrets`, {
|
|
3651
|
+
method: 'POST',
|
|
3652
|
+
headers: { 'Content-Type': 'application/json', 'X-Session-ID': sessionId },
|
|
3653
|
+
body: JSON.stringify({ key, value }),
|
|
3654
|
+
});
|
|
3655
|
+
if (!res.ok) {
|
|
3656
|
+
const err = await res.json();
|
|
3657
|
+
throw new Error(err.error || 'Failed to set secret');
|
|
3658
|
+
}
|
|
3659
|
+
spinner.succeed(`Secret ${chalk_1.default.cyan(key)} stored.`);
|
|
3660
|
+
}
|
|
3661
|
+
catch (err) {
|
|
3662
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3663
|
+
process.exit(1);
|
|
3664
|
+
}
|
|
3665
|
+
});
|
|
3666
|
+
secrets
|
|
3667
|
+
.command('delete <workspaceId> <key>')
|
|
3668
|
+
.description('Delete a workspace secret')
|
|
3669
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
3670
|
+
.action(async (workspaceId, key, options) => {
|
|
3671
|
+
const sessionId = config.get('sessionId');
|
|
3672
|
+
if (!sessionId) {
|
|
3673
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3674
|
+
process.exit(1);
|
|
3675
|
+
}
|
|
3676
|
+
if (!options.yes) {
|
|
3677
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
3678
|
+
type: 'confirm', name: 'confirm', message: `Delete secret ${key}?`, default: false,
|
|
3679
|
+
}]);
|
|
3680
|
+
if (!confirm) {
|
|
3681
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
const spinner = (0, ora_1.default)(`Deleting secret ${key}...`).start();
|
|
3686
|
+
try {
|
|
3687
|
+
const res = await fetch(`${API_URL}/workspaces/${workspaceId}/secrets/${key}`, {
|
|
3688
|
+
method: 'DELETE',
|
|
3689
|
+
headers: { 'X-Session-ID': sessionId },
|
|
3690
|
+
});
|
|
3691
|
+
if (!res.ok) {
|
|
3692
|
+
const err = await res.json();
|
|
3693
|
+
throw new Error(err.error || 'Failed to delete secret');
|
|
3694
|
+
}
|
|
3695
|
+
spinner.succeed(`Secret ${chalk_1.default.cyan(key)} deleted.`);
|
|
3696
|
+
}
|
|
3697
|
+
catch (err) {
|
|
3698
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3699
|
+
process.exit(1);
|
|
3700
|
+
}
|
|
3701
|
+
});
|
|
3702
|
+
// ========== CREDITS COMMAND ==========
|
|
3703
|
+
program
|
|
3704
|
+
.command('credits')
|
|
3705
|
+
.description(`View credit balance
|
|
3706
|
+
|
|
3707
|
+
${chalk_1.default.bold('Examples:')}
|
|
3708
|
+
$ gopherhole credits
|
|
3709
|
+
`)
|
|
3710
|
+
.option('--json', 'Output as JSON')
|
|
3711
|
+
.action(async (options) => {
|
|
3712
|
+
const sessionId = config.get('sessionId');
|
|
3713
|
+
if (!sessionId) {
|
|
3714
|
+
console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
|
|
3715
|
+
process.exit(1);
|
|
3716
|
+
}
|
|
3717
|
+
const spinner = (0, ora_1.default)('Fetching balance...').start();
|
|
3718
|
+
try {
|
|
3719
|
+
const res = await fetch(`${API_URL}/credits/balance`, { headers: { 'X-Session-ID': sessionId } });
|
|
3720
|
+
if (!res.ok)
|
|
3721
|
+
throw new Error('Failed to fetch credits');
|
|
3722
|
+
const data = await res.json();
|
|
3723
|
+
spinner.stop();
|
|
3724
|
+
if (options.json) {
|
|
3725
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3726
|
+
return;
|
|
3727
|
+
}
|
|
3728
|
+
console.log(chalk_1.default.bold('\nCredit Balance:\n'));
|
|
3729
|
+
if (data.remaining !== undefined)
|
|
3730
|
+
console.log(` Remaining: ${brand.green(data.remaining)}`);
|
|
3731
|
+
if (data.total !== undefined)
|
|
3732
|
+
console.log(` Total: ${data.total}`);
|
|
3733
|
+
if (data.used !== undefined)
|
|
3734
|
+
console.log(` Used: ${data.used}`);
|
|
3735
|
+
console.log('');
|
|
3736
|
+
}
|
|
3737
|
+
catch (err) {
|
|
3738
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
3739
|
+
process.exit(1);
|
|
3740
|
+
}
|
|
3741
|
+
});
|
|
2953
3742
|
// Parse and run
|
|
2954
3743
|
program.parse();
|