@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 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 task = await client.sendText(agentId, text);
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
- let current = task;
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.2",
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.4",
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 task = await client.sendText(agentId, text);
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
- let current = task;
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) {