@gopherhole/cli 0.1.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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # 🐿️ GopherHole CLI
2
+
3
+ Connect AI agents to the world.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Use with npx (no install required)
9
+ npx gopherhole init
10
+
11
+ # Or install globally
12
+ npm install -g gopherhole
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ # Initialize a new agent in your project
19
+ npx gopherhole init
20
+
21
+ # This will:
22
+ # 1. Log you in (or create an account)
23
+ # 2. Create an agent
24
+ # 3. Save your API key to .env
25
+ # 4. Generate example code
26
+ ```
27
+
28
+ ## Commands
29
+
30
+ ### Authentication
31
+
32
+ ```bash
33
+ gopherhole login # Log in to GopherHole
34
+ gopherhole signup # Create a new account
35
+ gopherhole logout # Log out
36
+ gopherhole whoami # Show current user
37
+ ```
38
+
39
+ ### Agent Management
40
+
41
+ ```bash
42
+ gopherhole agents list # List your agents
43
+ gopherhole agents create # Create a new agent (interactive)
44
+ gopherhole agents create -n mybot # Create with name
45
+ gopherhole agents delete <id> # Delete an agent
46
+ ```
47
+
48
+ ### Project Setup
49
+
50
+ ```bash
51
+ gopherhole init # Initialize GopherHole in current directory
52
+ ```
53
+
54
+ ### Testing
55
+
56
+ ```bash
57
+ gopherhole send <agentId> "Hello!" # Send a test message
58
+ ```
59
+
60
+ ## Configuration
61
+
62
+ The CLI stores configuration in:
63
+ - **macOS**: `~/Library/Preferences/gopherhole-nodejs/`
64
+ - **Linux**: `~/.config/gopherhole-nodejs/`
65
+ - **Windows**: `%APPDATA%/gopherhole-nodejs/`
66
+
67
+ ## Links
68
+
69
+ - Website: https://gopherhole.ai
70
+ - Dashboard: https://gopherhole.ai/dashboard
71
+ - Docs: https://gopherhole.ai/docs
72
+ - GitHub: https://github.com/gopherhole
73
+
74
+ ## License
75
+
76
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const conf_1 = __importDefault(require("conf"));
10
+ const inquirer_1 = __importDefault(require("inquirer"));
11
+ const ora_1 = __importDefault(require("ora"));
12
+ const config = new conf_1.default({ projectName: 'gopherhole' });
13
+ const API_URL = 'https://gopherhole.ai/api';
14
+ const program = new commander_1.Command();
15
+ program
16
+ .name('gopherhole')
17
+ .description('🐿️ GopherHole CLI - Connect AI agents to the world')
18
+ .version('0.1.0');
19
+ // ========== AUTH COMMANDS ==========
20
+ program
21
+ .command('login')
22
+ .description('Log in to GopherHole')
23
+ .action(async () => {
24
+ const { email, password } = await inquirer_1.default.prompt([
25
+ { type: 'input', name: 'email', message: 'Email:' },
26
+ { type: 'password', name: 'password', message: 'Password:' },
27
+ ]);
28
+ const spinner = (0, ora_1.default)('Logging in...').start();
29
+ try {
30
+ const res = await fetch(`${API_URL}/auth/login`, {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify({ email, password }),
34
+ });
35
+ if (!res.ok) {
36
+ const err = await res.json();
37
+ throw new Error(err.error || 'Login failed');
38
+ }
39
+ const data = await res.json();
40
+ config.set('sessionId', data.sessionId);
41
+ config.set('user', data.user);
42
+ config.set('tenant', data.tenant);
43
+ spinner.succeed(`Logged in as ${chalk_1.default.green(data.user.email)}`);
44
+ }
45
+ catch (err) {
46
+ spinner.fail(chalk_1.default.red(err.message));
47
+ process.exit(1);
48
+ }
49
+ });
50
+ program
51
+ .command('signup')
52
+ .description('Create a new GopherHole account')
53
+ .action(async () => {
54
+ const { name, email, password } = await inquirer_1.default.prompt([
55
+ { type: 'input', name: 'name', message: 'Name:' },
56
+ { type: 'input', name: 'email', message: 'Email:' },
57
+ { type: 'password', name: 'password', message: 'Password:' },
58
+ ]);
59
+ const spinner = (0, ora_1.default)('Creating account...').start();
60
+ try {
61
+ const res = await fetch(`${API_URL}/auth/signup`, {
62
+ method: 'POST',
63
+ headers: { 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({ name, email, password }),
65
+ });
66
+ if (!res.ok) {
67
+ const err = await res.json();
68
+ throw new Error(err.error || 'Signup failed');
69
+ }
70
+ const data = await res.json();
71
+ config.set('sessionId', data.sessionId);
72
+ config.set('user', data.user);
73
+ config.set('tenant', data.tenant);
74
+ spinner.succeed(`Account created! Logged in as ${chalk_1.default.green(data.user.email)}`);
75
+ }
76
+ catch (err) {
77
+ spinner.fail(chalk_1.default.red(err.message));
78
+ process.exit(1);
79
+ }
80
+ });
81
+ program
82
+ .command('logout')
83
+ .description('Log out of GopherHole')
84
+ .action(() => {
85
+ config.clear();
86
+ console.log(chalk_1.default.green('Logged out successfully'));
87
+ });
88
+ program
89
+ .command('whoami')
90
+ .description('Show current logged in user')
91
+ .action(() => {
92
+ const user = config.get('user');
93
+ if (!user) {
94
+ console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
95
+ process.exit(1);
96
+ }
97
+ console.log(`Logged in as ${chalk_1.default.green(user.email)} (${user.name})`);
98
+ });
99
+ // ========== AGENT COMMANDS ==========
100
+ const agents = program.command('agents').description('Manage agents');
101
+ agents
102
+ .command('list')
103
+ .description('List your agents')
104
+ .action(async () => {
105
+ const sessionId = config.get('sessionId');
106
+ if (!sessionId) {
107
+ console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
108
+ process.exit(1);
109
+ }
110
+ const spinner = (0, ora_1.default)('Fetching agents...').start();
111
+ try {
112
+ const res = await fetch(`${API_URL}/agents`, {
113
+ headers: { 'X-Session-ID': sessionId },
114
+ });
115
+ if (!res.ok)
116
+ throw new Error('Failed to fetch agents');
117
+ const data = await res.json();
118
+ spinner.stop();
119
+ if (data.agents.length === 0) {
120
+ console.log(chalk_1.default.yellow('No agents yet. Create one with: gopherhole agents create'));
121
+ return;
122
+ }
123
+ console.log(chalk_1.default.bold('\nYour Agents:\n'));
124
+ for (const agent of data.agents) {
125
+ const status = agent.status === 'active' ? chalk_1.default.green('●') : chalk_1.default.red('●');
126
+ console.log(` ${status} ${chalk_1.default.bold(agent.name)} (${agent.id})`);
127
+ if (agent.description) {
128
+ console.log(` ${chalk_1.default.gray(agent.description)}`);
129
+ }
130
+ }
131
+ console.log('');
132
+ }
133
+ catch (err) {
134
+ spinner.fail(chalk_1.default.red(err.message));
135
+ process.exit(1);
136
+ }
137
+ });
138
+ agents
139
+ .command('create')
140
+ .description('Create a new agent')
141
+ .option('-n, --name <name>', 'Agent name')
142
+ .option('-d, --description <description>', 'Agent description')
143
+ .action(async (options) => {
144
+ const sessionId = config.get('sessionId');
145
+ if (!sessionId) {
146
+ console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
147
+ process.exit(1);
148
+ }
149
+ let { name, description } = options;
150
+ if (!name) {
151
+ const answers = await inquirer_1.default.prompt([
152
+ { type: 'input', name: 'name', message: 'Agent name:' },
153
+ { type: 'input', name: 'description', message: 'Description (optional):' },
154
+ ]);
155
+ name = answers.name;
156
+ description = answers.description;
157
+ }
158
+ const spinner = (0, ora_1.default)('Creating agent...').start();
159
+ try {
160
+ const res = await fetch(`${API_URL}/agents`, {
161
+ method: 'POST',
162
+ headers: {
163
+ 'Content-Type': 'application/json',
164
+ 'X-Session-ID': sessionId,
165
+ },
166
+ body: JSON.stringify({ name, description }),
167
+ });
168
+ if (!res.ok) {
169
+ const err = await res.json();
170
+ throw new Error(err.error || 'Failed to create agent');
171
+ }
172
+ const data = await res.json();
173
+ spinner.succeed(`Agent created: ${chalk_1.default.green(data.agent.name)}`);
174
+ console.log('');
175
+ console.log(` ${chalk_1.default.bold('Agent ID:')} ${data.agent.id}`);
176
+ console.log(` ${chalk_1.default.bold('API Key:')} ${chalk_1.default.cyan(data.apiKey)}`);
177
+ console.log('');
178
+ console.log(chalk_1.default.gray('Save this API key - it won\'t be shown again!'));
179
+ console.log('');
180
+ console.log(chalk_1.default.bold('Quick start:'));
181
+ console.log(chalk_1.default.gray(`
182
+ import { GopherHole } from '@gopherhole/sdk';
183
+
184
+ const hub = new GopherHole('${data.apiKey}');
185
+ await hub.connect();
186
+
187
+ hub.on('message', (msg) => {
188
+ console.log('Received:', msg);
189
+ });
190
+ `));
191
+ }
192
+ catch (err) {
193
+ spinner.fail(chalk_1.default.red(err.message));
194
+ process.exit(1);
195
+ }
196
+ });
197
+ agents
198
+ .command('delete <agentId>')
199
+ .description('Delete an agent')
200
+ .action(async (agentId) => {
201
+ const sessionId = config.get('sessionId');
202
+ if (!sessionId) {
203
+ console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
204
+ process.exit(1);
205
+ }
206
+ const { confirm } = await inquirer_1.default.prompt([
207
+ {
208
+ type: 'confirm',
209
+ name: 'confirm',
210
+ message: `Are you sure you want to delete agent ${agentId}?`,
211
+ default: false,
212
+ },
213
+ ]);
214
+ if (!confirm) {
215
+ console.log('Cancelled');
216
+ return;
217
+ }
218
+ const spinner = (0, ora_1.default)('Deleting agent...').start();
219
+ try {
220
+ const res = await fetch(`${API_URL}/agents/${agentId}`, {
221
+ method: 'DELETE',
222
+ headers: { 'X-Session-ID': sessionId },
223
+ });
224
+ if (!res.ok)
225
+ throw new Error('Failed to delete agent');
226
+ spinner.succeed('Agent deleted');
227
+ }
228
+ catch (err) {
229
+ spinner.fail(chalk_1.default.red(err.message));
230
+ process.exit(1);
231
+ }
232
+ });
233
+ // ========== INIT COMMAND ==========
234
+ program
235
+ .command('init')
236
+ .description('Initialize GopherHole in current directory')
237
+ .action(async () => {
238
+ console.log(chalk_1.default.bold('\n🐿️ GopherHole Init\n'));
239
+ let sessionId = config.get('sessionId');
240
+ // Check if logged in
241
+ if (!sessionId) {
242
+ console.log('First, let\'s log you in (or create an account).\n');
243
+ const { action } = await inquirer_1.default.prompt([
244
+ {
245
+ type: 'list',
246
+ name: 'action',
247
+ message: 'Do you have a GopherHole account?',
248
+ choices: [
249
+ { name: 'Yes, log me in', value: 'login' },
250
+ { name: 'No, create one', value: 'signup' },
251
+ ],
252
+ },
253
+ ]);
254
+ if (action === 'signup') {
255
+ const { name, email, password } = await inquirer_1.default.prompt([
256
+ { type: 'input', name: 'name', message: 'Name:' },
257
+ { type: 'input', name: 'email', message: 'Email:' },
258
+ { type: 'password', name: 'password', message: 'Password:' },
259
+ ]);
260
+ const spinner = (0, ora_1.default)('Creating account...').start();
261
+ const res = await fetch(`${API_URL}/auth/signup`, {
262
+ method: 'POST',
263
+ headers: { 'Content-Type': 'application/json' },
264
+ body: JSON.stringify({ name, email, password }),
265
+ });
266
+ if (!res.ok) {
267
+ spinner.fail('Signup failed');
268
+ process.exit(1);
269
+ }
270
+ const data = await res.json();
271
+ config.set('sessionId', data.sessionId);
272
+ config.set('user', data.user);
273
+ config.set('tenant', data.tenant);
274
+ sessionId = data.sessionId;
275
+ spinner.succeed('Account created!');
276
+ }
277
+ else {
278
+ const { email, password } = await inquirer_1.default.prompt([
279
+ { type: 'input', name: 'email', message: 'Email:' },
280
+ { type: 'password', name: 'password', message: 'Password:' },
281
+ ]);
282
+ const spinner = (0, ora_1.default)('Logging in...').start();
283
+ const res = await fetch(`${API_URL}/auth/login`, {
284
+ method: 'POST',
285
+ headers: { 'Content-Type': 'application/json' },
286
+ body: JSON.stringify({ email, password }),
287
+ });
288
+ if (!res.ok) {
289
+ spinner.fail('Login failed');
290
+ process.exit(1);
291
+ }
292
+ const data = await res.json();
293
+ config.set('sessionId', data.sessionId);
294
+ config.set('user', data.user);
295
+ config.set('tenant', data.tenant);
296
+ sessionId = data.sessionId;
297
+ spinner.succeed('Logged in!');
298
+ }
299
+ }
300
+ console.log('');
301
+ // Create agent
302
+ const { agentName, agentDesc } = await inquirer_1.default.prompt([
303
+ { type: 'input', name: 'agentName', message: 'Agent name:', default: 'my-agent' },
304
+ { type: 'input', name: 'agentDesc', message: 'Description (optional):' },
305
+ ]);
306
+ const spinner = (0, ora_1.default)('Creating agent...').start();
307
+ const res = await fetch(`${API_URL}/agents`, {
308
+ method: 'POST',
309
+ headers: {
310
+ 'Content-Type': 'application/json',
311
+ 'X-Session-ID': sessionId,
312
+ },
313
+ body: JSON.stringify({ name: agentName, description: agentDesc }),
314
+ });
315
+ if (!res.ok) {
316
+ spinner.fail('Failed to create agent');
317
+ process.exit(1);
318
+ }
319
+ const data = await res.json();
320
+ spinner.succeed('Agent created!');
321
+ // Write .env file
322
+ const fs = await import('fs');
323
+ fs.writeFileSync('.env', `GOPHERHOLE_API_KEY=${data.apiKey}\n`);
324
+ console.log(chalk_1.default.green('✓ Created .env with API key'));
325
+ // Write example code
326
+ const exampleCode = `import { GopherHole } from '@gopherhole/sdk';
327
+
328
+ const hub = new GopherHole(process.env.GOPHERHOLE_API_KEY);
329
+
330
+ async function main() {
331
+ await hub.connect();
332
+ console.log('🐿️ Connected to GopherHole!');
333
+
334
+ hub.on('message', async (msg) => {
335
+ console.log(\`Message from \${msg.from}:\`, msg.payload);
336
+
337
+ // Reply to the message
338
+ await hub.sendText(msg.from, 'Hello back!');
339
+ });
340
+ }
341
+
342
+ main().catch(console.error);
343
+ `;
344
+ fs.writeFileSync('agent.ts', exampleCode);
345
+ console.log(chalk_1.default.green('✓ Created agent.ts example'));
346
+ console.log('');
347
+ console.log(chalk_1.default.bold('Next steps:'));
348
+ console.log('');
349
+ console.log(' 1. Install the SDK:');
350
+ console.log(chalk_1.default.cyan(' npm install @gopherhole/sdk'));
351
+ console.log('');
352
+ console.log(' 2. Run your agent:');
353
+ console.log(chalk_1.default.cyan(' npx ts-node agent.ts'));
354
+ console.log('');
355
+ console.log(chalk_1.default.gray(`Dashboard: https://gopherhole.ai/dashboard`));
356
+ console.log('');
357
+ });
358
+ // ========== SEND COMMAND ==========
359
+ program
360
+ .command('send <agentId> <message>')
361
+ .description('Send a message to an agent (for testing)')
362
+ .action(async (agentId, message) => {
363
+ const sessionId = config.get('sessionId');
364
+ if (!sessionId) {
365
+ console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
366
+ process.exit(1);
367
+ }
368
+ const spinner = (0, ora_1.default)('Sending message...').start();
369
+ try {
370
+ const res = await fetch(`${API_URL}/../a2a`, {
371
+ method: 'POST',
372
+ headers: {
373
+ 'Content-Type': 'application/json',
374
+ 'X-Session-ID': sessionId,
375
+ },
376
+ body: JSON.stringify({
377
+ jsonrpc: '2.0',
378
+ method: 'message/send',
379
+ params: {
380
+ message: { role: 'user', parts: [{ kind: 'text', text: message }] },
381
+ configuration: { agentId },
382
+ },
383
+ id: 1,
384
+ }),
385
+ });
386
+ const data = await res.json();
387
+ spinner.succeed('Message sent!');
388
+ console.log(chalk_1.default.gray(JSON.stringify(data, null, 2)));
389
+ }
390
+ catch (err) {
391
+ spinner.fail(chalk_1.default.red(err.message));
392
+ process.exit(1);
393
+ }
394
+ });
395
+ // Parse and run
396
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@gopherhole/cli",
3
+ "version": "0.1.0",
4
+ "description": "GopherHole CLI - Connect AI agents to the world",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "gopherhole": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "ts-node src/index.ts",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "a2a",
16
+ "ai",
17
+ "agents",
18
+ "gopherhole",
19
+ "cli"
20
+ ],
21
+ "author": "GopherHole",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "chalk": "^5.3.0",
25
+ "commander": "^12.0.0",
26
+ "conf": "^12.0.0",
27
+ "inquirer": "^9.2.0",
28
+ "open": "^10.0.0",
29
+ "ora": "^8.0.0",
30
+ "ws": "^8.16.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/inquirer": "^9.0.7",
34
+ "@types/node": "^20.11.0",
35
+ "@types/ws": "^8.5.10",
36
+ "ts-node": "^10.9.2",
37
+ "typescript": "^5.3.0"
38
+ }
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import Conf from 'conf';
5
+ import inquirer from 'inquirer';
6
+ import ora from 'ora';
7
+ import open from 'open';
8
+
9
+ const config = new Conf({ projectName: 'gopherhole' });
10
+ const API_URL = 'https://gopherhole.ai/api';
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name('gopherhole')
16
+ .description('🐿️ GopherHole CLI - Connect AI agents to the world')
17
+ .version('0.1.0');
18
+
19
+ // ========== AUTH COMMANDS ==========
20
+
21
+ program
22
+ .command('login')
23
+ .description('Log in to GopherHole')
24
+ .action(async () => {
25
+ const { email, password } = await inquirer.prompt([
26
+ { type: 'input', name: 'email', message: 'Email:' },
27
+ { type: 'password', name: 'password', message: 'Password:' },
28
+ ]);
29
+
30
+ const spinner = ora('Logging in...').start();
31
+
32
+ try {
33
+ const res = await fetch(`${API_URL}/auth/login`, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({ email, password }),
37
+ });
38
+
39
+ if (!res.ok) {
40
+ const err = await res.json();
41
+ throw new Error(err.error || 'Login failed');
42
+ }
43
+
44
+ const data = await res.json();
45
+ config.set('sessionId', data.sessionId);
46
+ config.set('user', data.user);
47
+ config.set('tenant', data.tenant);
48
+
49
+ spinner.succeed(`Logged in as ${chalk.green(data.user.email)}`);
50
+ } catch (err) {
51
+ spinner.fail(chalk.red((err as Error).message));
52
+ process.exit(1);
53
+ }
54
+ });
55
+
56
+ program
57
+ .command('signup')
58
+ .description('Create a new GopherHole account')
59
+ .action(async () => {
60
+ const { name, email, password } = await inquirer.prompt([
61
+ { type: 'input', name: 'name', message: 'Name:' },
62
+ { type: 'input', name: 'email', message: 'Email:' },
63
+ { type: 'password', name: 'password', message: 'Password:' },
64
+ ]);
65
+
66
+ const spinner = ora('Creating account...').start();
67
+
68
+ try {
69
+ const res = await fetch(`${API_URL}/auth/signup`, {
70
+ method: 'POST',
71
+ headers: { 'Content-Type': 'application/json' },
72
+ body: JSON.stringify({ name, email, password }),
73
+ });
74
+
75
+ if (!res.ok) {
76
+ const err = await res.json();
77
+ throw new Error(err.error || 'Signup failed');
78
+ }
79
+
80
+ const data = await res.json();
81
+ config.set('sessionId', data.sessionId);
82
+ config.set('user', data.user);
83
+ config.set('tenant', data.tenant);
84
+
85
+ spinner.succeed(`Account created! Logged in as ${chalk.green(data.user.email)}`);
86
+ } catch (err) {
87
+ spinner.fail(chalk.red((err as Error).message));
88
+ process.exit(1);
89
+ }
90
+ });
91
+
92
+ program
93
+ .command('logout')
94
+ .description('Log out of GopherHole')
95
+ .action(() => {
96
+ config.clear();
97
+ console.log(chalk.green('Logged out successfully'));
98
+ });
99
+
100
+ program
101
+ .command('whoami')
102
+ .description('Show current logged in user')
103
+ .action(() => {
104
+ const user = config.get('user') as { email: string; name: string } | undefined;
105
+ if (!user) {
106
+ console.log(chalk.yellow('Not logged in. Run: gopherhole login'));
107
+ process.exit(1);
108
+ }
109
+ console.log(`Logged in as ${chalk.green(user.email)} (${user.name})`);
110
+ });
111
+
112
+ // ========== AGENT COMMANDS ==========
113
+
114
+ const agents = program.command('agents').description('Manage agents');
115
+
116
+ agents
117
+ .command('list')
118
+ .description('List your agents')
119
+ .action(async () => {
120
+ const sessionId = config.get('sessionId') as string;
121
+ if (!sessionId) {
122
+ console.log(chalk.yellow('Not logged in. Run: gopherhole login'));
123
+ process.exit(1);
124
+ }
125
+
126
+ const spinner = ora('Fetching agents...').start();
127
+
128
+ try {
129
+ const res = await fetch(`${API_URL}/agents`, {
130
+ headers: { 'X-Session-ID': sessionId },
131
+ });
132
+
133
+ if (!res.ok) throw new Error('Failed to fetch agents');
134
+
135
+ const data = await res.json();
136
+ spinner.stop();
137
+
138
+ if (data.agents.length === 0) {
139
+ console.log(chalk.yellow('No agents yet. Create one with: gopherhole agents create'));
140
+ return;
141
+ }
142
+
143
+ console.log(chalk.bold('\nYour Agents:\n'));
144
+ for (const agent of data.agents) {
145
+ const status = agent.status === 'active' ? chalk.green('●') : chalk.red('●');
146
+ console.log(` ${status} ${chalk.bold(agent.name)} (${agent.id})`);
147
+ if (agent.description) {
148
+ console.log(` ${chalk.gray(agent.description)}`);
149
+ }
150
+ }
151
+ console.log('');
152
+ } catch (err) {
153
+ spinner.fail(chalk.red((err as Error).message));
154
+ process.exit(1);
155
+ }
156
+ });
157
+
158
+ agents
159
+ .command('create')
160
+ .description('Create a new agent')
161
+ .option('-n, --name <name>', 'Agent name')
162
+ .option('-d, --description <description>', 'Agent description')
163
+ .action(async (options) => {
164
+ const sessionId = config.get('sessionId') as string;
165
+ if (!sessionId) {
166
+ console.log(chalk.yellow('Not logged in. Run: gopherhole login'));
167
+ process.exit(1);
168
+ }
169
+
170
+ let { name, description } = options;
171
+
172
+ if (!name) {
173
+ const answers = await inquirer.prompt([
174
+ { type: 'input', name: 'name', message: 'Agent name:' },
175
+ { type: 'input', name: 'description', message: 'Description (optional):' },
176
+ ]);
177
+ name = answers.name;
178
+ description = answers.description;
179
+ }
180
+
181
+ const spinner = ora('Creating agent...').start();
182
+
183
+ try {
184
+ const res = await fetch(`${API_URL}/agents`, {
185
+ method: 'POST',
186
+ headers: {
187
+ 'Content-Type': 'application/json',
188
+ 'X-Session-ID': sessionId,
189
+ },
190
+ body: JSON.stringify({ name, description }),
191
+ });
192
+
193
+ if (!res.ok) {
194
+ const err = await res.json();
195
+ throw new Error(err.error || 'Failed to create agent');
196
+ }
197
+
198
+ const data = await res.json();
199
+ spinner.succeed(`Agent created: ${chalk.green(data.agent.name)}`);
200
+ console.log('');
201
+ console.log(` ${chalk.bold('Agent ID:')} ${data.agent.id}`);
202
+ console.log(` ${chalk.bold('API Key:')} ${chalk.cyan(data.apiKey)}`);
203
+ console.log('');
204
+ console.log(chalk.gray('Save this API key - it won\'t be shown again!'));
205
+ console.log('');
206
+ console.log(chalk.bold('Quick start:'));
207
+ console.log(chalk.gray(`
208
+ import { GopherHole } from '@gopherhole/sdk';
209
+
210
+ const hub = new GopherHole('${data.apiKey}');
211
+ await hub.connect();
212
+
213
+ hub.on('message', (msg) => {
214
+ console.log('Received:', msg);
215
+ });
216
+ `));
217
+ } catch (err) {
218
+ spinner.fail(chalk.red((err as Error).message));
219
+ process.exit(1);
220
+ }
221
+ });
222
+
223
+ agents
224
+ .command('delete <agentId>')
225
+ .description('Delete an agent')
226
+ .action(async (agentId) => {
227
+ const sessionId = config.get('sessionId') as string;
228
+ if (!sessionId) {
229
+ console.log(chalk.yellow('Not logged in. Run: gopherhole login'));
230
+ process.exit(1);
231
+ }
232
+
233
+ const { confirm } = await inquirer.prompt([
234
+ {
235
+ type: 'confirm',
236
+ name: 'confirm',
237
+ message: `Are you sure you want to delete agent ${agentId}?`,
238
+ default: false,
239
+ },
240
+ ]);
241
+
242
+ if (!confirm) {
243
+ console.log('Cancelled');
244
+ return;
245
+ }
246
+
247
+ const spinner = ora('Deleting agent...').start();
248
+
249
+ try {
250
+ const res = await fetch(`${API_URL}/agents/${agentId}`, {
251
+ method: 'DELETE',
252
+ headers: { 'X-Session-ID': sessionId },
253
+ });
254
+
255
+ if (!res.ok) throw new Error('Failed to delete agent');
256
+
257
+ spinner.succeed('Agent deleted');
258
+ } catch (err) {
259
+ spinner.fail(chalk.red((err as Error).message));
260
+ process.exit(1);
261
+ }
262
+ });
263
+
264
+ // ========== INIT COMMAND ==========
265
+
266
+ program
267
+ .command('init')
268
+ .description('Initialize GopherHole in current directory')
269
+ .action(async () => {
270
+ console.log(chalk.bold('\n🐿️ GopherHole Init\n'));
271
+
272
+ let sessionId = config.get('sessionId') as string;
273
+
274
+ // Check if logged in
275
+ if (!sessionId) {
276
+ console.log('First, let\'s log you in (or create an account).\n');
277
+
278
+ const { action } = await inquirer.prompt([
279
+ {
280
+ type: 'list',
281
+ name: 'action',
282
+ message: 'Do you have a GopherHole account?',
283
+ choices: [
284
+ { name: 'Yes, log me in', value: 'login' },
285
+ { name: 'No, create one', value: 'signup' },
286
+ ],
287
+ },
288
+ ]);
289
+
290
+ if (action === 'signup') {
291
+ const { name, email, password } = await inquirer.prompt([
292
+ { type: 'input', name: 'name', message: 'Name:' },
293
+ { type: 'input', name: 'email', message: 'Email:' },
294
+ { type: 'password', name: 'password', message: 'Password:' },
295
+ ]);
296
+
297
+ const spinner = ora('Creating account...').start();
298
+ const res = await fetch(`${API_URL}/auth/signup`, {
299
+ method: 'POST',
300
+ headers: { 'Content-Type': 'application/json' },
301
+ body: JSON.stringify({ name, email, password }),
302
+ });
303
+
304
+ if (!res.ok) {
305
+ spinner.fail('Signup failed');
306
+ process.exit(1);
307
+ }
308
+
309
+ const data = await res.json();
310
+ config.set('sessionId', data.sessionId);
311
+ config.set('user', data.user);
312
+ config.set('tenant', data.tenant);
313
+ sessionId = data.sessionId;
314
+ spinner.succeed('Account created!');
315
+ } else {
316
+ const { email, password } = await inquirer.prompt([
317
+ { type: 'input', name: 'email', message: 'Email:' },
318
+ { type: 'password', name: 'password', message: 'Password:' },
319
+ ]);
320
+
321
+ const spinner = ora('Logging in...').start();
322
+ const res = await fetch(`${API_URL}/auth/login`, {
323
+ method: 'POST',
324
+ headers: { 'Content-Type': 'application/json' },
325
+ body: JSON.stringify({ email, password }),
326
+ });
327
+
328
+ if (!res.ok) {
329
+ spinner.fail('Login failed');
330
+ process.exit(1);
331
+ }
332
+
333
+ const data = await res.json();
334
+ config.set('sessionId', data.sessionId);
335
+ config.set('user', data.user);
336
+ config.set('tenant', data.tenant);
337
+ sessionId = data.sessionId;
338
+ spinner.succeed('Logged in!');
339
+ }
340
+ }
341
+
342
+ console.log('');
343
+
344
+ // Create agent
345
+ const { agentName, agentDesc } = await inquirer.prompt([
346
+ { type: 'input', name: 'agentName', message: 'Agent name:', default: 'my-agent' },
347
+ { type: 'input', name: 'agentDesc', message: 'Description (optional):' },
348
+ ]);
349
+
350
+ const spinner = ora('Creating agent...').start();
351
+
352
+ const res = await fetch(`${API_URL}/agents`, {
353
+ method: 'POST',
354
+ headers: {
355
+ 'Content-Type': 'application/json',
356
+ 'X-Session-ID': sessionId,
357
+ },
358
+ body: JSON.stringify({ name: agentName, description: agentDesc }),
359
+ });
360
+
361
+ if (!res.ok) {
362
+ spinner.fail('Failed to create agent');
363
+ process.exit(1);
364
+ }
365
+
366
+ const data = await res.json();
367
+ spinner.succeed('Agent created!');
368
+
369
+ // Write .env file
370
+ const fs = await import('fs');
371
+ fs.writeFileSync('.env', `GOPHERHOLE_API_KEY=${data.apiKey}\n`);
372
+ console.log(chalk.green('✓ Created .env with API key'));
373
+
374
+ // Write example code
375
+ const exampleCode = `import { GopherHole } from '@gopherhole/sdk';
376
+
377
+ const hub = new GopherHole(process.env.GOPHERHOLE_API_KEY);
378
+
379
+ async function main() {
380
+ await hub.connect();
381
+ console.log('🐿️ Connected to GopherHole!');
382
+
383
+ hub.on('message', async (msg) => {
384
+ console.log(\`Message from \${msg.from}:\`, msg.payload);
385
+
386
+ // Reply to the message
387
+ await hub.sendText(msg.from, 'Hello back!');
388
+ });
389
+ }
390
+
391
+ main().catch(console.error);
392
+ `;
393
+ fs.writeFileSync('agent.ts', exampleCode);
394
+ console.log(chalk.green('✓ Created agent.ts example'));
395
+
396
+ console.log('');
397
+ console.log(chalk.bold('Next steps:'));
398
+ console.log('');
399
+ console.log(' 1. Install the SDK:');
400
+ console.log(chalk.cyan(' npm install @gopherhole/sdk'));
401
+ console.log('');
402
+ console.log(' 2. Run your agent:');
403
+ console.log(chalk.cyan(' npx ts-node agent.ts'));
404
+ console.log('');
405
+ console.log(chalk.gray(`Dashboard: https://gopherhole.ai/dashboard`));
406
+ console.log('');
407
+ });
408
+
409
+ // ========== SEND COMMAND ==========
410
+
411
+ program
412
+ .command('send <agentId> <message>')
413
+ .description('Send a message to an agent (for testing)')
414
+ .action(async (agentId, message) => {
415
+ const sessionId = config.get('sessionId') as string;
416
+ if (!sessionId) {
417
+ console.log(chalk.yellow('Not logged in. Run: gopherhole login'));
418
+ process.exit(1);
419
+ }
420
+
421
+ const spinner = ora('Sending message...').start();
422
+
423
+ try {
424
+ const res = await fetch(`${API_URL}/../a2a`, {
425
+ method: 'POST',
426
+ headers: {
427
+ 'Content-Type': 'application/json',
428
+ 'X-Session-ID': sessionId,
429
+ },
430
+ body: JSON.stringify({
431
+ jsonrpc: '2.0',
432
+ method: 'message/send',
433
+ params: {
434
+ message: { role: 'user', parts: [{ kind: 'text', text: message }] },
435
+ configuration: { agentId },
436
+ },
437
+ id: 1,
438
+ }),
439
+ });
440
+
441
+ const data = await res.json();
442
+ spinner.succeed('Message sent!');
443
+ console.log(chalk.gray(JSON.stringify(data, null, 2)));
444
+ } catch (err) {
445
+ spinner.fail(chalk.red((err as Error).message));
446
+ process.exit(1);
447
+ }
448
+ });
449
+
450
+ // Parse and run
451
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }