@gopherhole/cli 0.6.2 → 0.6.4
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 +38 -4
- package/package.json +2 -2
- package/src/index.ts +33 -4
package/dist/index.js
CHANGED
|
@@ -82,12 +82,43 @@ function makeHubClient(apiKey, transport) {
|
|
|
82
82
|
const hubUrl = apiUrl.replace('https://', 'wss://').replace('http://', 'ws://') + '/ws';
|
|
83
83
|
return new sdk_1.GopherHole({ apiKey, hubUrl, autoReconnect: false, transport: transport || 'http' });
|
|
84
84
|
}
|
|
85
|
+
/** Load secrets for a target agent from .gopherhole/secrets/{alias}.json */
|
|
86
|
+
async function loadAgentSecrets(agentId) {
|
|
87
|
+
const fs = await import('fs');
|
|
88
|
+
const path = await import('path');
|
|
89
|
+
const filePath = path.join(process.cwd(), '.gopherhole', 'secrets', `${agentId}.json`);
|
|
90
|
+
if (!fs.existsSync(filePath))
|
|
91
|
+
return null;
|
|
92
|
+
try {
|
|
93
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
94
|
+
const parsed = JSON.parse(raw);
|
|
95
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
96
|
+
return null;
|
|
97
|
+
const secrets = {};
|
|
98
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
99
|
+
if (typeof v === 'string')
|
|
100
|
+
secrets[k] = v;
|
|
101
|
+
}
|
|
102
|
+
return Object.keys(secrets).length > 0 ? secrets : null;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
85
108
|
/** Send a message and poll until terminal state, return response text.
|
|
86
109
|
* Matches the MCP client pattern: sendText → poll getTask. */
|
|
87
|
-
async function askAgent(client, agentId, text) {
|
|
88
|
-
const
|
|
110
|
+
async function askAgent(client, agentId, text, opts) {
|
|
111
|
+
const config = {};
|
|
112
|
+
if (opts?.secrets)
|
|
113
|
+
config['x-gopherhole-secrets'] = opts.secrets;
|
|
114
|
+
if (opts?.ttl !== undefined)
|
|
115
|
+
config['x-ttl'] = opts.ttl;
|
|
116
|
+
const sendConfig = Object.keys(config).length > 0 ? config : undefined;
|
|
117
|
+
const task = await client.sendText(agentId, text, sendConfig);
|
|
89
118
|
const terminalStates = ['completed', 'failed', 'canceled', 'rejected'];
|
|
90
|
-
|
|
119
|
+
// Always hydrate from GetTask — the non-blocking SendMessage response may
|
|
120
|
+
// claim 'completed' without including artifacts when the target responds inline.
|
|
121
|
+
let current = await client.getTask(task.id);
|
|
91
122
|
const start = Date.now();
|
|
92
123
|
const maxWait = 60_000;
|
|
93
124
|
const poll = 1_000;
|
|
@@ -1283,6 +1314,7 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
1283
1314
|
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1284
1315
|
process.exit(1);
|
|
1285
1316
|
}
|
|
1317
|
+
const secrets = await loadAgentSecrets(agentId);
|
|
1286
1318
|
const spinner = (0, ora_1.default)(`Sending to ${agentId}...`).start();
|
|
1287
1319
|
log('POST /a2a SendMessage', { to: agentId, message });
|
|
1288
1320
|
try {
|
|
@@ -1300,6 +1332,7 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
1300
1332
|
configuration: {
|
|
1301
1333
|
agentId,
|
|
1302
1334
|
...(cmdOpts.ttl !== undefined ? { 'x-ttl': cmdOpts.ttl } : {}),
|
|
1335
|
+
...(secrets ? { 'x-gopherhole-secrets': secrets } : {}),
|
|
1303
1336
|
},
|
|
1304
1337
|
},
|
|
1305
1338
|
id: 1,
|
|
@@ -2565,10 +2598,11 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
2565
2598
|
process.exit(1);
|
|
2566
2599
|
}
|
|
2567
2600
|
log(`Transport: http (A2AClient is HTTP-only)`);
|
|
2601
|
+
const secrets = await loadAgentSecrets(agentId);
|
|
2568
2602
|
const spinner = (0, ora_1.default)(`Messaging ${agentId}...`).start();
|
|
2569
2603
|
try {
|
|
2570
2604
|
const client = makeAgentClient(apiKey);
|
|
2571
|
-
const response = await askAgent(client, agentId, text);
|
|
2605
|
+
const response = await askAgent(client, agentId, text, secrets ? { secrets } : undefined);
|
|
2572
2606
|
spinner.stop();
|
|
2573
2607
|
console.log(response || chalk_1.default.gray('(no response)'));
|
|
2574
2608
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gopherhole/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
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.7.
|
|
25
|
+
"@gopherhole/sdk": "^0.7.5",
|
|
26
26
|
"chalk": "^5.3.0",
|
|
27
27
|
"commander": "^12.0.0",
|
|
28
28
|
"conf": "^12.0.0",
|
package/src/index.ts
CHANGED
|
@@ -85,13 +85,39 @@ function makeHubClient(apiKey: string, transport?: TransportMode): GopherHole {
|
|
|
85
85
|
return new GopherHole({ apiKey, hubUrl, autoReconnect: false, transport: transport || 'http' });
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/** Load secrets for a target agent from .gopherhole/secrets/{alias}.json */
|
|
89
|
+
async function loadAgentSecrets(agentId: string): Promise<Record<string, string> | null> {
|
|
90
|
+
const fs = await import('fs');
|
|
91
|
+
const path = await import('path');
|
|
92
|
+
const filePath = path.join(process.cwd(), '.gopherhole', 'secrets', `${agentId}.json`);
|
|
93
|
+
if (!fs.existsSync(filePath)) return null;
|
|
94
|
+
try {
|
|
95
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
96
|
+
const parsed = JSON.parse(raw);
|
|
97
|
+
if (typeof parsed !== 'object' || parsed === null) return null;
|
|
98
|
+
const secrets: Record<string, string> = {};
|
|
99
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
100
|
+
if (typeof v === 'string') secrets[k] = v;
|
|
101
|
+
}
|
|
102
|
+
return Object.keys(secrets).length > 0 ? secrets : null;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
88
108
|
/** Send a message and poll until terminal state, return response text.
|
|
89
109
|
* Matches the MCP client pattern: sendText → poll getTask. */
|
|
90
|
-
async function askAgent(client: A2AClient, agentId: string, text: string): Promise<string> {
|
|
91
|
-
const
|
|
110
|
+
async function askAgent(client: A2AClient, agentId: string, text: string, opts?: { secrets?: Record<string, string>; ttl?: number }): Promise<string> {
|
|
111
|
+
const config: Record<string, unknown> = {};
|
|
112
|
+
if (opts?.secrets) config['x-gopherhole-secrets'] = opts.secrets;
|
|
113
|
+
if (opts?.ttl !== undefined) config['x-ttl'] = opts.ttl;
|
|
114
|
+
const sendConfig = Object.keys(config).length > 0 ? config : undefined;
|
|
115
|
+
const task = await client.sendText(agentId, text, sendConfig as any);
|
|
92
116
|
|
|
93
117
|
const terminalStates = ['completed', 'failed', 'canceled', 'rejected'];
|
|
94
|
-
|
|
118
|
+
// Always hydrate from GetTask — the non-blocking SendMessage response may
|
|
119
|
+
// claim 'completed' without including artifacts when the target responds inline.
|
|
120
|
+
let current = await client.getTask(task.id);
|
|
95
121
|
const start = Date.now();
|
|
96
122
|
const maxWait = 60_000;
|
|
97
123
|
const poll = 1_000;
|
|
@@ -1418,6 +1444,7 @@ ${chalk.bold('Examples:')}
|
|
|
1418
1444
|
process.exit(1);
|
|
1419
1445
|
}
|
|
1420
1446
|
|
|
1447
|
+
const secrets = await loadAgentSecrets(agentId);
|
|
1421
1448
|
const spinner = ora(`Sending to ${agentId}...`).start();
|
|
1422
1449
|
log('POST /a2a SendMessage', { to: agentId, message });
|
|
1423
1450
|
|
|
@@ -1436,6 +1463,7 @@ ${chalk.bold('Examples:')}
|
|
|
1436
1463
|
configuration: {
|
|
1437
1464
|
agentId,
|
|
1438
1465
|
...(cmdOpts.ttl !== undefined ? { 'x-ttl': cmdOpts.ttl } : {}),
|
|
1466
|
+
...(secrets ? { 'x-gopherhole-secrets': secrets } : {}),
|
|
1439
1467
|
},
|
|
1440
1468
|
},
|
|
1441
1469
|
id: 1,
|
|
@@ -2795,10 +2823,11 @@ ${chalk.bold('Examples:')}
|
|
|
2795
2823
|
}
|
|
2796
2824
|
|
|
2797
2825
|
log(`Transport: http (A2AClient is HTTP-only)`);
|
|
2826
|
+
const secrets = await loadAgentSecrets(agentId);
|
|
2798
2827
|
const spinner = ora(`Messaging ${agentId}...`).start();
|
|
2799
2828
|
try {
|
|
2800
2829
|
const client = makeAgentClient(apiKey);
|
|
2801
|
-
const response = await askAgent(client, agentId, text);
|
|
2830
|
+
const response = await askAgent(client, agentId, text, secrets ? { secrets } : undefined);
|
|
2802
2831
|
spinner.stop();
|
|
2803
2832
|
console.log(response || chalk.gray('(no response)'));
|
|
2804
2833
|
} catch (err) {
|