@gopherhole/cli 0.2.0 → 0.3.1
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 +34 -13
- package/package.json +2 -2
- package/src/index.ts +36 -13
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.3.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) {
|
|
@@ -62,6 +62,14 @@ async function resolveAgentId(flagValue) {
|
|
|
62
62
|
catch { /* ignore */ }
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
|
+
function resolveTransport(flagValue) {
|
|
66
|
+
const value = flagValue || process.env.GOPHERHOLE_TRANSPORT || 'http';
|
|
67
|
+
if (!['http', 'ws', 'auto'].includes(value)) {
|
|
68
|
+
console.error(chalk_1.default.red(`Invalid transport: ${value}. Must be http, ws, or auto`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
65
73
|
function makeAgentClient(apiKey) {
|
|
66
74
|
return new sdk_1.A2AClient({
|
|
67
75
|
apiKey,
|
|
@@ -69,10 +77,10 @@ function makeAgentClient(apiKey) {
|
|
|
69
77
|
});
|
|
70
78
|
}
|
|
71
79
|
/** Hub client for workspace/discovery operations (does not connect WebSocket) */
|
|
72
|
-
function makeHubClient(apiKey) {
|
|
80
|
+
function makeHubClient(apiKey, transport) {
|
|
73
81
|
const apiUrl = process.env.GOPHERHOLE_API_URL || 'https://hub.gopherhole.ai';
|
|
74
82
|
const hubUrl = apiUrl.replace('https://', 'wss://').replace('http://', 'ws://') + '/ws';
|
|
75
|
-
return new sdk_1.GopherHole({ apiKey, hubUrl, autoReconnect: false });
|
|
83
|
+
return new sdk_1.GopherHole({ apiKey, hubUrl, autoReconnect: false, transport: transport || 'http' });
|
|
76
84
|
}
|
|
77
85
|
/** Send a message and poll until terminal state, return response text.
|
|
78
86
|
* Matches the MCP client pattern: sendText → poll getTask. */
|
|
@@ -83,6 +91,7 @@ async function askAgent(client, agentId, text) {
|
|
|
83
91
|
const start = Date.now();
|
|
84
92
|
const maxWait = 60_000;
|
|
85
93
|
const poll = 1_000;
|
|
94
|
+
let printedQueued = false;
|
|
86
95
|
while (!terminalStates.includes(current.status.state)) {
|
|
87
96
|
if (current.status.state === 'input-required') {
|
|
88
97
|
throw new Error('Agent requires additional input (not supported in CLI mode)');
|
|
@@ -90,6 +99,10 @@ async function askAgent(client, agentId, text) {
|
|
|
90
99
|
if (current.status.state === 'auth-required') {
|
|
91
100
|
throw new Error('Agent requires authentication — check your API key or request access');
|
|
92
101
|
}
|
|
102
|
+
if (current.status.state === 'submitted' && !printedQueued) {
|
|
103
|
+
console.log(chalk_1.default.yellow('⏳ Message queued — recipient is offline. Waiting for delivery...'));
|
|
104
|
+
printedQueued = true;
|
|
105
|
+
}
|
|
93
106
|
if (Date.now() - start > maxWait)
|
|
94
107
|
throw new Error('Timed out waiting for agent response');
|
|
95
108
|
await new Promise(r => setTimeout(r, poll));
|
|
@@ -166,6 +179,7 @@ ${chalk_1.default.bold('Documentation:')}
|
|
|
166
179
|
`)
|
|
167
180
|
.version(VERSION)
|
|
168
181
|
.option('-v, --verbose', 'Enable verbose output for debugging')
|
|
182
|
+
.option('-t, --transport <mode>', 'Transport mode: http | ws | auto (default: http)')
|
|
169
183
|
.hook('preAction', (thisCommand) => {
|
|
170
184
|
verbose = thisCommand.opts().verbose || false;
|
|
171
185
|
if (verbose) {
|
|
@@ -1207,8 +1221,11 @@ program
|
|
|
1207
1221
|
${chalk_1.default.bold('Examples:')}
|
|
1208
1222
|
$ gopherhole send echo "Hello!"
|
|
1209
1223
|
$ gopherhole send agent-abc123 "What's the weather?"
|
|
1224
|
+
$ gopherhole send agent-abc123 "Free now?" --ttl 0 # fail if offline
|
|
1225
|
+
$ gopherhole send agent-abc123 "Review this" --ttl 3600 # queue up to 1h
|
|
1210
1226
|
`)
|
|
1211
|
-
.
|
|
1227
|
+
.option('--ttl <seconds>', 'Message time-to-live in seconds (0 = no queue, omit = 30 day default)', parseInt)
|
|
1228
|
+
.action(async (agentId, message, cmdOpts) => {
|
|
1212
1229
|
const sessionId = config.get('sessionId');
|
|
1213
1230
|
if (!sessionId) {
|
|
1214
1231
|
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
@@ -1229,7 +1246,10 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
1229
1246
|
method: 'SendMessage',
|
|
1230
1247
|
params: {
|
|
1231
1248
|
message: { role: 'user', parts: [{ kind: 'text', text: message }] },
|
|
1232
|
-
configuration: {
|
|
1249
|
+
configuration: {
|
|
1250
|
+
agentId,
|
|
1251
|
+
...(cmdOpts.ttl !== undefined ? { 'x-ttl': cmdOpts.ttl } : {}),
|
|
1252
|
+
},
|
|
1233
1253
|
},
|
|
1234
1254
|
id: 1,
|
|
1235
1255
|
}),
|
|
@@ -2316,6 +2336,7 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
2316
2336
|
console.error(chalk_1.default.gray('Set GOPHERHOLE_API_KEY, pass --api-key, or run gopher init to create a .env'));
|
|
2317
2337
|
process.exit(1);
|
|
2318
2338
|
}
|
|
2339
|
+
log(`Transport: http (A2AClient is HTTP-only)`);
|
|
2319
2340
|
const spinner = (0, ora_1.default)(`Messaging ${agentId}...`).start();
|
|
2320
2341
|
try {
|
|
2321
2342
|
const client = makeAgentClient(apiKey);
|
|
@@ -2465,7 +2486,7 @@ ws
|
|
|
2465
2486
|
}
|
|
2466
2487
|
const spinner = (0, ora_1.default)('Loading workspaces...').start();
|
|
2467
2488
|
try {
|
|
2468
|
-
const client = makeHubClient(apiKey);
|
|
2489
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2469
2490
|
const result = await client.workspaceList();
|
|
2470
2491
|
spinner.stop();
|
|
2471
2492
|
if (!result.workspaces.length) {
|
|
@@ -2499,7 +2520,7 @@ ws
|
|
|
2499
2520
|
}
|
|
2500
2521
|
const spinner = (0, ora_1.default)('Creating workspace...').start();
|
|
2501
2522
|
try {
|
|
2502
|
-
const client = makeHubClient(apiKey);
|
|
2523
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2503
2524
|
const result = await client.workspaceCreate(name, options.description);
|
|
2504
2525
|
spinner.succeed(`Workspace created: ${chalk_1.default.bold(result.workspace.name)}`);
|
|
2505
2526
|
console.log(chalk_1.default.gray(` ID: ${result.workspace.id}`));
|
|
@@ -2524,7 +2545,7 @@ ws
|
|
|
2524
2545
|
}
|
|
2525
2546
|
const spinner = (0, ora_1.default)('Searching...').start();
|
|
2526
2547
|
try {
|
|
2527
|
-
const client = makeHubClient(apiKey);
|
|
2548
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2528
2549
|
const result = await client.workspaceQuery({
|
|
2529
2550
|
workspace_id: workspaceId,
|
|
2530
2551
|
query,
|
|
@@ -2565,7 +2586,7 @@ ws
|
|
|
2565
2586
|
}
|
|
2566
2587
|
const spinner = (0, ora_1.default)('Storing...').start();
|
|
2567
2588
|
try {
|
|
2568
|
-
const client = makeHubClient(apiKey);
|
|
2589
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2569
2590
|
const result = await client.workspaceStore({
|
|
2570
2591
|
workspace_id: workspaceId,
|
|
2571
2592
|
content,
|
|
@@ -2594,7 +2615,7 @@ ws
|
|
|
2594
2615
|
}
|
|
2595
2616
|
const spinner = (0, ora_1.default)('Loading memories...').start();
|
|
2596
2617
|
try {
|
|
2597
|
-
const client = makeHubClient(apiKey);
|
|
2618
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2598
2619
|
const result = await client.workspaceMemories({
|
|
2599
2620
|
workspace_id: workspaceId,
|
|
2600
2621
|
limit: parseInt(options.limit),
|
|
@@ -2637,7 +2658,7 @@ ws
|
|
|
2637
2658
|
}
|
|
2638
2659
|
const spinner = (0, ora_1.default)('Deleting...').start();
|
|
2639
2660
|
try {
|
|
2640
|
-
const client = makeHubClient(apiKey);
|
|
2661
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2641
2662
|
const result = await client.workspaceForget({
|
|
2642
2663
|
workspace_id: workspaceId,
|
|
2643
2664
|
id: options.id,
|
|
@@ -2666,7 +2687,7 @@ wsMembers
|
|
|
2666
2687
|
}
|
|
2667
2688
|
const spinner = (0, ora_1.default)('Loading members...').start();
|
|
2668
2689
|
try {
|
|
2669
|
-
const client = makeHubClient(apiKey);
|
|
2690
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2670
2691
|
const result = await client.workspaceMembersList(workspaceId);
|
|
2671
2692
|
spinner.stop();
|
|
2672
2693
|
result.members.forEach(m => {
|
|
@@ -2692,7 +2713,7 @@ wsMembers
|
|
|
2692
2713
|
}
|
|
2693
2714
|
const spinner = (0, ora_1.default)('Adding member...').start();
|
|
2694
2715
|
try {
|
|
2695
|
-
const client = makeHubClient(apiKey);
|
|
2716
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2696
2717
|
await client.workspaceMembersAdd(workspaceId, agentId, options.role);
|
|
2697
2718
|
spinner.succeed(`Added ${agentId} with role: ${options.role}`);
|
|
2698
2719
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gopherhole/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "GopherHole CLI - Connect AI agents to the world",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"author": "GopherHole",
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@gopherhole/sdk": "^0.
|
|
25
|
+
"@gopherhole/sdk": "^0.6.0",
|
|
26
26
|
"chalk": "^5.3.0",
|
|
27
27
|
"commander": "^12.0.0",
|
|
28
28
|
"conf": "^12.0.0",
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import inquirer from 'inquirer';
|
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
import open from 'open';
|
|
8
8
|
import { A2AClient, GopherHole } from '@gopherhole/sdk';
|
|
9
|
+
import type { TransportMode } from '@gopherhole/sdk';
|
|
9
10
|
|
|
10
11
|
const config = new Conf({ projectName: 'gopherhole' });
|
|
11
12
|
const API_URL = 'https://gopherhole.ai/api';
|
|
@@ -19,7 +20,7 @@ const brand = {
|
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
// Version
|
|
22
|
-
const VERSION = '0.
|
|
23
|
+
const VERSION = '0.3.0';
|
|
23
24
|
|
|
24
25
|
// ========== API KEY RESOLUTION ==========
|
|
25
26
|
// Precedence: --api-key flag > GOPHERHOLE_API_KEY env var > .env file in cwd
|
|
@@ -61,6 +62,15 @@ async function resolveAgentId(flagValue?: string): Promise<string | null> {
|
|
|
61
62
|
return null;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
function resolveTransport(flagValue?: string): TransportMode {
|
|
66
|
+
const value = flagValue || process.env.GOPHERHOLE_TRANSPORT || 'http';
|
|
67
|
+
if (!['http', 'ws', 'auto'].includes(value)) {
|
|
68
|
+
console.error(chalk.red(`Invalid transport: ${value}. Must be http, ws, or auto`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
return value as TransportMode;
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
function makeAgentClient(apiKey: string): A2AClient {
|
|
65
75
|
return new A2AClient({
|
|
66
76
|
apiKey,
|
|
@@ -69,10 +79,10 @@ function makeAgentClient(apiKey: string): A2AClient {
|
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
/** Hub client for workspace/discovery operations (does not connect WebSocket) */
|
|
72
|
-
function makeHubClient(apiKey: string): GopherHole {
|
|
82
|
+
function makeHubClient(apiKey: string, transport?: TransportMode): GopherHole {
|
|
73
83
|
const apiUrl = process.env.GOPHERHOLE_API_URL || 'https://hub.gopherhole.ai';
|
|
74
84
|
const hubUrl = apiUrl.replace('https://', 'wss://').replace('http://', 'ws://') + '/ws';
|
|
75
|
-
return new GopherHole({ apiKey, hubUrl, autoReconnect: false });
|
|
85
|
+
return new GopherHole({ apiKey, hubUrl, autoReconnect: false, transport: transport || 'http' });
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
/** Send a message and poll until terminal state, return response text.
|
|
@@ -86,6 +96,7 @@ async function askAgent(client: A2AClient, agentId: string, text: string): Promi
|
|
|
86
96
|
const maxWait = 60_000;
|
|
87
97
|
const poll = 1_000;
|
|
88
98
|
|
|
99
|
+
let printedQueued = false;
|
|
89
100
|
while (!terminalStates.includes(current.status.state)) {
|
|
90
101
|
if (current.status.state === 'input-required') {
|
|
91
102
|
throw new Error('Agent requires additional input (not supported in CLI mode)');
|
|
@@ -93,6 +104,10 @@ async function askAgent(client: A2AClient, agentId: string, text: string): Promi
|
|
|
93
104
|
if (current.status.state === 'auth-required') {
|
|
94
105
|
throw new Error('Agent requires authentication — check your API key or request access');
|
|
95
106
|
}
|
|
107
|
+
if (current.status.state === 'submitted' && !printedQueued) {
|
|
108
|
+
console.log(chalk.yellow('⏳ Message queued — recipient is offline. Waiting for delivery...'));
|
|
109
|
+
printedQueued = true;
|
|
110
|
+
}
|
|
96
111
|
if (Date.now() - start > maxWait) throw new Error('Timed out waiting for agent response');
|
|
97
112
|
await new Promise(r => setTimeout(r, poll));
|
|
98
113
|
current = await client.getTask(current.id);
|
|
@@ -176,6 +191,7 @@ ${chalk.bold('Documentation:')}
|
|
|
176
191
|
`)
|
|
177
192
|
.version(VERSION)
|
|
178
193
|
.option('-v, --verbose', 'Enable verbose output for debugging')
|
|
194
|
+
.option('-t, --transport <mode>', 'Transport mode: http | ws | auto (default: http)')
|
|
179
195
|
.hook('preAction', (thisCommand) => {
|
|
180
196
|
verbose = thisCommand.opts().verbose || false;
|
|
181
197
|
if (verbose) {
|
|
@@ -1336,8 +1352,11 @@ program
|
|
|
1336
1352
|
${chalk.bold('Examples:')}
|
|
1337
1353
|
$ gopherhole send echo "Hello!"
|
|
1338
1354
|
$ gopherhole send agent-abc123 "What's the weather?"
|
|
1355
|
+
$ gopherhole send agent-abc123 "Free now?" --ttl 0 # fail if offline
|
|
1356
|
+
$ gopherhole send agent-abc123 "Review this" --ttl 3600 # queue up to 1h
|
|
1339
1357
|
`)
|
|
1340
|
-
.
|
|
1358
|
+
.option('--ttl <seconds>', 'Message time-to-live in seconds (0 = no queue, omit = 30 day default)', parseInt)
|
|
1359
|
+
.action(async (agentId, message, cmdOpts) => {
|
|
1341
1360
|
const sessionId = config.get('sessionId') as string;
|
|
1342
1361
|
if (!sessionId) {
|
|
1343
1362
|
console.log(chalk.yellow('Not logged in.'));
|
|
@@ -1360,7 +1379,10 @@ ${chalk.bold('Examples:')}
|
|
|
1360
1379
|
method: 'SendMessage',
|
|
1361
1380
|
params: {
|
|
1362
1381
|
message: { role: 'user', parts: [{ kind: 'text', text: message }] },
|
|
1363
|
-
configuration: {
|
|
1382
|
+
configuration: {
|
|
1383
|
+
agentId,
|
|
1384
|
+
...(cmdOpts.ttl !== undefined ? { 'x-ttl': cmdOpts.ttl } : {}),
|
|
1385
|
+
},
|
|
1364
1386
|
},
|
|
1365
1387
|
id: 1,
|
|
1366
1388
|
}),
|
|
@@ -2550,6 +2572,7 @@ ${chalk.bold('Examples:')}
|
|
|
2550
2572
|
process.exit(1);
|
|
2551
2573
|
}
|
|
2552
2574
|
|
|
2575
|
+
log(`Transport: http (A2AClient is HTTP-only)`);
|
|
2553
2576
|
const spinner = ora(`Messaging ${agentId}...`).start();
|
|
2554
2577
|
try {
|
|
2555
2578
|
const client = makeAgentClient(apiKey);
|
|
@@ -2695,7 +2718,7 @@ ws
|
|
|
2695
2718
|
}
|
|
2696
2719
|
const spinner = ora('Loading workspaces...').start();
|
|
2697
2720
|
try {
|
|
2698
|
-
const client = makeHubClient(apiKey);
|
|
2721
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2699
2722
|
const result = await client.workspaceList();
|
|
2700
2723
|
spinner.stop();
|
|
2701
2724
|
if (!result.workspaces.length) {
|
|
@@ -2726,7 +2749,7 @@ ws
|
|
|
2726
2749
|
}
|
|
2727
2750
|
const spinner = ora('Creating workspace...').start();
|
|
2728
2751
|
try {
|
|
2729
|
-
const client = makeHubClient(apiKey);
|
|
2752
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2730
2753
|
const result = await client.workspaceCreate(name, options.description);
|
|
2731
2754
|
spinner.succeed(`Workspace created: ${chalk.bold(result.workspace.name)}`);
|
|
2732
2755
|
console.log(chalk.gray(` ID: ${result.workspace.id}`));
|
|
@@ -2749,7 +2772,7 @@ ws
|
|
|
2749
2772
|
}
|
|
2750
2773
|
const spinner = ora('Searching...').start();
|
|
2751
2774
|
try {
|
|
2752
|
-
const client = makeHubClient(apiKey);
|
|
2775
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2753
2776
|
const result = await client.workspaceQuery({
|
|
2754
2777
|
workspace_id: workspaceId,
|
|
2755
2778
|
query,
|
|
@@ -2787,7 +2810,7 @@ ws
|
|
|
2787
2810
|
}
|
|
2788
2811
|
const spinner = ora('Storing...').start();
|
|
2789
2812
|
try {
|
|
2790
|
-
const client = makeHubClient(apiKey);
|
|
2813
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2791
2814
|
const result = await client.workspaceStore({
|
|
2792
2815
|
workspace_id: workspaceId,
|
|
2793
2816
|
content,
|
|
@@ -2814,7 +2837,7 @@ ws
|
|
|
2814
2837
|
}
|
|
2815
2838
|
const spinner = ora('Loading memories...').start();
|
|
2816
2839
|
try {
|
|
2817
|
-
const client = makeHubClient(apiKey);
|
|
2840
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2818
2841
|
const result = await client.workspaceMemories({
|
|
2819
2842
|
workspace_id: workspaceId,
|
|
2820
2843
|
limit: parseInt(options.limit),
|
|
@@ -2854,7 +2877,7 @@ ws
|
|
|
2854
2877
|
}
|
|
2855
2878
|
const spinner = ora('Deleting...').start();
|
|
2856
2879
|
try {
|
|
2857
|
-
const client = makeHubClient(apiKey);
|
|
2880
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2858
2881
|
const result = await client.workspaceForget({
|
|
2859
2882
|
workspace_id: workspaceId,
|
|
2860
2883
|
id: options.id,
|
|
@@ -2882,7 +2905,7 @@ wsMembers
|
|
|
2882
2905
|
}
|
|
2883
2906
|
const spinner = ora('Loading members...').start();
|
|
2884
2907
|
try {
|
|
2885
|
-
const client = makeHubClient(apiKey);
|
|
2908
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2886
2909
|
const result = await client.workspaceMembersList(workspaceId);
|
|
2887
2910
|
spinner.stop();
|
|
2888
2911
|
result.members.forEach(m => {
|
|
@@ -2906,7 +2929,7 @@ wsMembers
|
|
|
2906
2929
|
}
|
|
2907
2930
|
const spinner = ora('Adding member...').start();
|
|
2908
2931
|
try {
|
|
2909
|
-
const client = makeHubClient(apiKey);
|
|
2932
|
+
const client = makeHubClient(apiKey, resolveTransport(program.opts().transport));
|
|
2910
2933
|
await client.workspaceMembersAdd(workspaceId, agentId, options.role);
|
|
2911
2934
|
spinner.succeed(`Added ${agentId} with role: ${options.role}`);
|
|
2912
2935
|
} catch (err) {
|