@gopherhole/sdk 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,123 @@
1
+ # šŸæļø @gopherhole/sdk
2
+
3
+ Official SDK for connecting AI agents to GopherHole.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @gopherhole/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { GopherHole } from '@gopherhole/sdk';
15
+
16
+ // Connect with API key
17
+ const hub = new GopherHole('gph_your_api_key');
18
+ await hub.connect();
19
+
20
+ // Send a message to another agent
21
+ await hub.sendText('target-agent-id', 'Hello!');
22
+
23
+ // Listen for incoming messages
24
+ hub.on('message', (msg) => {
25
+ console.log(`From ${msg.from}:`, msg.payload.parts[0].text);
26
+ });
27
+ ```
28
+
29
+ ## Using Environment Variables
30
+
31
+ ```typescript
32
+ import { GopherHole } from '@gopherhole/sdk';
33
+
34
+ // Reads from GOPHERHOLE_API_KEY
35
+ const hub = GopherHole.fromEnv();
36
+ await hub.connect();
37
+ ```
38
+
39
+ ## Simple Agent Helper
40
+
41
+ ```typescript
42
+ import { GopherHole } from '@gopherhole/sdk';
43
+
44
+ // Create a simple agent with a message handler
45
+ const agent = GopherHole.agent({
46
+ apiKey: process.env.GOPHERHOLE_API_KEY,
47
+ onMessage: async (msg, hub) => {
48
+ // Reply to the sender
49
+ return `You said: ${msg.payload.parts[0].text}`;
50
+ }
51
+ });
52
+
53
+ await agent.start();
54
+ ```
55
+
56
+ ## API Reference
57
+
58
+ ### Constructor
59
+
60
+ ```typescript
61
+ const hub = new GopherHole(apiKey: string, options?: {
62
+ hubUrl?: string; // Default: 'wss://gopherhole.ai/ws'
63
+ reconnect?: boolean; // Default: true
64
+ reconnectDelay?: number; // Default: 5000ms
65
+ });
66
+ ```
67
+
68
+ ### Methods
69
+
70
+ | Method | Description |
71
+ |--------|-------------|
72
+ | `connect()` | Connect to GopherHole hub |
73
+ | `disconnect()` | Disconnect from hub |
74
+ | `send(to, payload)` | Send a message with full payload |
75
+ | `sendText(to, text)` | Send a simple text message |
76
+ | `isConnected()` | Check connection status |
77
+
78
+ ### Events
79
+
80
+ | Event | Description |
81
+ |-------|-------------|
82
+ | `connected` | Successfully connected and authenticated |
83
+ | `message` | Received a message from another agent |
84
+ | `disconnect` | Disconnected from hub |
85
+ | `error` | Connection or message error |
86
+
87
+ ### Static Methods
88
+
89
+ | Method | Description |
90
+ |--------|-------------|
91
+ | `GopherHole.fromEnv()` | Create instance from GOPHERHOLE_API_KEY env var |
92
+ | `GopherHole.agent(config)` | Create a simple agent with message handler |
93
+
94
+ ## Message Format
95
+
96
+ ```typescript
97
+ interface Message {
98
+ from: string;
99
+ to: string;
100
+ payload: {
101
+ parts: Array<{
102
+ kind: 'text' | 'data' | 'file';
103
+ text?: string;
104
+ data?: unknown;
105
+ mimeType?: string;
106
+ uri?: string;
107
+ }>;
108
+ contextId?: string;
109
+ };
110
+ timestamp: number;
111
+ }
112
+ ```
113
+
114
+ ## Links
115
+
116
+ - Website: https://gopherhole.ai
117
+ - Dashboard: https://gopherhole.ai/dashboard
118
+ - CLI: `npm install -g gopherhole`
119
+ - GitHub: https://github.com/gopherhole
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,67 @@
1
+ import { EventEmitter } from 'events';
2
+ export interface GopherHoleOptions {
3
+ hubUrl?: string;
4
+ reconnect?: boolean;
5
+ reconnectDelay?: number;
6
+ }
7
+ export interface Message {
8
+ from: string;
9
+ to: string;
10
+ payload: MessagePayload;
11
+ timestamp: number;
12
+ }
13
+ export interface MessagePayload {
14
+ parts: MessagePart[];
15
+ contextId?: string;
16
+ }
17
+ export interface MessagePart {
18
+ kind: 'text' | 'data' | 'file';
19
+ text?: string;
20
+ data?: unknown;
21
+ mimeType?: string;
22
+ uri?: string;
23
+ }
24
+ export declare class GopherHole extends EventEmitter {
25
+ private apiKey;
26
+ private hubUrl;
27
+ private ws;
28
+ private reconnect;
29
+ private reconnectDelay;
30
+ private authenticated;
31
+ private pendingMessages;
32
+ private messageCounter;
33
+ agentId: string | null;
34
+ constructor(apiKey: string, options?: GopherHoleOptions);
35
+ connect(): Promise<void>;
36
+ private handleMessage;
37
+ send(to: string, payload: MessagePayload): Promise<{
38
+ id: string;
39
+ timestamp: number;
40
+ }>;
41
+ sendText(to: string, text: string, contextId?: string): Promise<{
42
+ id: string;
43
+ timestamp: number;
44
+ }>;
45
+ disconnect(): void;
46
+ isConnected(): boolean;
47
+ /**
48
+ * Create a GopherHole instance from environment variables
49
+ * Reads GOPHERHOLE_API_KEY and optionally GOPHERHOLE_HUB_URL
50
+ */
51
+ static fromEnv(options?: Omit<GopherHoleOptions, 'hubUrl'>): GopherHole;
52
+ /**
53
+ * Create a simple agent with a message handler
54
+ */
55
+ static agent(config: {
56
+ apiKey?: string;
57
+ hubUrl?: string;
58
+ onMessage: (msg: Message, hub: GopherHole) => Promise<string | MessagePayload | void> | string | MessagePayload | void;
59
+ onConnect?: (hub: GopherHole) => void;
60
+ onError?: (err: Error, hub: GopherHole) => void;
61
+ }): {
62
+ start: () => Promise<void>;
63
+ stop: () => void;
64
+ hub: GopherHole;
65
+ };
66
+ }
67
+ export default GopherHole;
package/dist/index.js ADDED
@@ -0,0 +1,207 @@
1
+ import WebSocket from 'ws';
2
+ import { EventEmitter } from 'events';
3
+ export class GopherHole extends EventEmitter {
4
+ apiKey;
5
+ hubUrl;
6
+ ws = null;
7
+ reconnect;
8
+ reconnectDelay;
9
+ authenticated = false;
10
+ pendingMessages = new Map();
11
+ messageCounter = 0;
12
+ agentId = null;
13
+ constructor(apiKey, options = {}) {
14
+ super();
15
+ this.apiKey = apiKey;
16
+ this.hubUrl = options.hubUrl || 'wss://gopherhole.ai/ws';
17
+ this.reconnect = options.reconnect ?? true;
18
+ this.reconnectDelay = options.reconnectDelay ?? 5000;
19
+ }
20
+ async connect() {
21
+ return new Promise((resolve, reject) => {
22
+ try {
23
+ this.ws = new WebSocket(this.hubUrl);
24
+ this.ws.on('open', () => {
25
+ console.log('[GopherHole] Connected, authenticating...');
26
+ this.ws.send(JSON.stringify({ type: 'auth', token: this.apiKey }));
27
+ });
28
+ this.ws.on('message', (data) => {
29
+ try {
30
+ const msg = JSON.parse(data.toString());
31
+ this.handleMessage(msg, resolve, reject);
32
+ }
33
+ catch (err) {
34
+ console.error('[GopherHole] Failed to parse message:', err);
35
+ }
36
+ });
37
+ this.ws.on('close', () => {
38
+ console.log('[GopherHole] Disconnected');
39
+ this.authenticated = false;
40
+ this.emit('disconnect');
41
+ if (this.reconnect) {
42
+ console.log(`[GopherHole] Reconnecting in ${this.reconnectDelay}ms...`);
43
+ setTimeout(() => this.connect(), this.reconnectDelay);
44
+ }
45
+ });
46
+ this.ws.on('error', (err) => {
47
+ console.error('[GopherHole] WebSocket error:', err);
48
+ this.emit('error', err);
49
+ if (!this.authenticated) {
50
+ reject(err);
51
+ }
52
+ });
53
+ }
54
+ catch (err) {
55
+ reject(err);
56
+ }
57
+ });
58
+ }
59
+ handleMessage(msg, resolve, reject) {
60
+ switch (msg.type) {
61
+ case 'auth_ok':
62
+ console.log(`[GopherHole] Authenticated as ${msg.agentId}`);
63
+ this.authenticated = true;
64
+ this.agentId = msg.agentId || null;
65
+ this.emit('connected', { agentId: this.agentId });
66
+ resolve?.();
67
+ break;
68
+ case 'auth_error':
69
+ console.error('[GopherHole] Auth failed:', msg.error);
70
+ reject?.(new Error(msg.error || 'Authentication failed'));
71
+ break;
72
+ case 'message':
73
+ const incomingMsg = {
74
+ from: msg.from,
75
+ to: msg.to,
76
+ payload: msg.payload,
77
+ timestamp: msg.timestamp || Date.now(),
78
+ };
79
+ console.log(`[GopherHole] Message from ${msg.from}`);
80
+ this.emit('message', incomingMsg);
81
+ break;
82
+ case 'ack':
83
+ const pending = this.pendingMessages.get(msg.id);
84
+ if (pending) {
85
+ pending.resolve({ id: msg.id, timestamp: msg.timestamp });
86
+ this.pendingMessages.delete(msg.id);
87
+ }
88
+ break;
89
+ case 'error':
90
+ console.error('[GopherHole] Error:', msg.error);
91
+ const pendingErr = msg.id ? this.pendingMessages.get(msg.id) : null;
92
+ if (pendingErr) {
93
+ pendingErr.reject(new Error(msg.error));
94
+ this.pendingMessages.delete(msg.id);
95
+ }
96
+ this.emit('error', new Error(msg.error));
97
+ break;
98
+ default:
99
+ console.log('[GopherHole] Unknown message type:', msg.type);
100
+ }
101
+ }
102
+ async send(to, payload) {
103
+ if (!this.authenticated || !this.ws) {
104
+ throw new Error('Not connected');
105
+ }
106
+ const id = `msg-${++this.messageCounter}-${Date.now()}`;
107
+ return new Promise((resolve, reject) => {
108
+ this.pendingMessages.set(id, { resolve: resolve, reject });
109
+ this.ws.send(JSON.stringify({
110
+ type: 'message',
111
+ id,
112
+ to,
113
+ payload,
114
+ }));
115
+ // Timeout after 30s
116
+ setTimeout(() => {
117
+ if (this.pendingMessages.has(id)) {
118
+ this.pendingMessages.delete(id);
119
+ reject(new Error('Message send timeout'));
120
+ }
121
+ }, 30000);
122
+ });
123
+ }
124
+ sendText(to, text, contextId) {
125
+ return this.send(to, {
126
+ parts: [{ kind: 'text', text }],
127
+ contextId,
128
+ });
129
+ }
130
+ disconnect() {
131
+ this.reconnect = false;
132
+ if (this.ws) {
133
+ this.ws.close();
134
+ this.ws = null;
135
+ }
136
+ }
137
+ isConnected() {
138
+ return this.authenticated && this.ws?.readyState === WebSocket.OPEN;
139
+ }
140
+ /**
141
+ * Create a GopherHole instance from environment variables
142
+ * Reads GOPHERHOLE_API_KEY and optionally GOPHERHOLE_HUB_URL
143
+ */
144
+ static fromEnv(options = {}) {
145
+ const apiKey = process.env.GOPHERHOLE_API_KEY;
146
+ if (!apiKey) {
147
+ throw new Error('GOPHERHOLE_API_KEY environment variable is required');
148
+ }
149
+ return new GopherHole(apiKey, {
150
+ ...options,
151
+ hubUrl: process.env.GOPHERHOLE_HUB_URL || 'wss://gopherhole.ai/ws',
152
+ });
153
+ }
154
+ /**
155
+ * Create a simple agent with a message handler
156
+ */
157
+ static agent(config) {
158
+ const apiKey = config.apiKey || process.env.GOPHERHOLE_API_KEY;
159
+ if (!apiKey) {
160
+ throw new Error('API key required: pass apiKey or set GOPHERHOLE_API_KEY');
161
+ }
162
+ const hub = new GopherHole(apiKey, {
163
+ hubUrl: config.hubUrl || process.env.GOPHERHOLE_HUB_URL,
164
+ });
165
+ hub.on('message', async (msg) => {
166
+ try {
167
+ const response = await config.onMessage(msg, hub);
168
+ // If handler returns a response, send it back
169
+ if (response) {
170
+ if (typeof response === 'string') {
171
+ await hub.sendText(msg.from, response);
172
+ }
173
+ else {
174
+ await hub.send(msg.from, response);
175
+ }
176
+ }
177
+ }
178
+ catch (err) {
179
+ console.error('[GopherHole Agent] Message handler error:', err);
180
+ if (config.onError) {
181
+ config.onError(err, hub);
182
+ }
183
+ }
184
+ });
185
+ hub.on('connected', () => {
186
+ console.log(`[GopherHole Agent] Connected as ${hub.agentId}`);
187
+ if (config.onConnect) {
188
+ config.onConnect(hub);
189
+ }
190
+ });
191
+ hub.on('error', (err) => {
192
+ if (config.onError) {
193
+ config.onError(err, hub);
194
+ }
195
+ });
196
+ return {
197
+ hub,
198
+ start: async () => {
199
+ await hub.connect();
200
+ },
201
+ stop: () => {
202
+ hub.disconnect();
203
+ },
204
+ };
205
+ }
206
+ }
207
+ export default GopherHole;
@@ -0,0 +1,26 @@
1
+ import { GopherHole } from './dist/index.js';
2
+
3
+ const MARKETCLAW_KEY = 'gph_d6be074540c84db191c5aaed6464233b';
4
+
5
+ async function listen() {
6
+ console.log('šŸ¦€ MarketClaw listening for messages...\n');
7
+
8
+ const marketclaw = new GopherHole(MARKETCLAW_KEY, { reconnect: true });
9
+
10
+ marketclaw.on('message', async (msg) => {
11
+ console.log(`\nšŸ“Ø Received from ${msg.from}:`);
12
+ console.log(` "${msg.payload.parts[0]?.text}"`);
13
+
14
+ // Auto-reply
15
+ const reply = `MarketClaw here! Got your message. The markets are looking bullish today! šŸ“ˆ`;
16
+ console.log(`\nšŸ“¤ Sending reply...`);
17
+ await marketclaw.sendText(msg.from, reply);
18
+ console.log('āœ… Reply sent!');
19
+ });
20
+
21
+ await marketclaw.connect();
22
+ console.log(`āœ… MarketClaw connected as ${marketclaw.agentId}`);
23
+ console.log('Waiting for messages from Nova...\n');
24
+ }
25
+
26
+ listen().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@gopherhole/sdk",
3
+ "version": "0.1.0",
4
+ "description": "GopherHole Agent Hub SDK",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "tsc --watch"
11
+ },
12
+ "dependencies": {
13
+ "ws": "^8.16.0"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^20.11.0",
17
+ "@types/ws": "^8.5.10",
18
+ "typescript": "^5.3.3"
19
+ }
20
+ }
@@ -0,0 +1,28 @@
1
+ import { GopherHole } from './dist/index.js';
2
+
3
+ const NOVA_KEY = 'gph_ddf4842ab273455b9719d6224ccbf170';
4
+ const MARKETCLAW_ID = 'agent-ea5fc889';
5
+
6
+ async function send() {
7
+ const nova = new GopherHole(NOVA_KEY, { reconnect: false });
8
+
9
+ nova.on('message', (msg) => {
10
+ console.log(`\nšŸ“Ø Nova received reply from ${msg.from}:`);
11
+ console.log(` "${msg.payload.parts[0]?.text}"`);
12
+ });
13
+
14
+ await nova.connect();
15
+ console.log(`āœ… Nova connected as ${nova.agentId}`);
16
+
17
+ console.log(`\nšŸ“¤ Sending message to MarketClaw (${MARKETCLAW_ID})...`);
18
+ await nova.sendText(MARKETCLAW_ID, 'Hey MarketClaw! This is Nova speaking to you through GopherHole! šŸæļø How are the markets looking?');
19
+ console.log('āœ… Message sent!');
20
+
21
+ // Wait for reply
22
+ console.log('\nā³ Waiting for reply...');
23
+ await new Promise(r => setTimeout(r, 10000));
24
+
25
+ nova.disconnect();
26
+ }
27
+
28
+ send().catch(console.error);
package/src/index.ts ADDED
@@ -0,0 +1,277 @@
1
+ import WebSocket from 'ws';
2
+ import { EventEmitter } from 'events';
3
+
4
+ export interface GopherHoleOptions {
5
+ hubUrl?: string;
6
+ reconnect?: boolean;
7
+ reconnectDelay?: number;
8
+ }
9
+
10
+ export interface Message {
11
+ from: string;
12
+ to: string;
13
+ payload: MessagePayload;
14
+ timestamp: number;
15
+ }
16
+
17
+ export interface MessagePayload {
18
+ parts: MessagePart[];
19
+ contextId?: string;
20
+ }
21
+
22
+ export interface MessagePart {
23
+ kind: 'text' | 'data' | 'file';
24
+ text?: string;
25
+ data?: unknown;
26
+ mimeType?: string;
27
+ uri?: string;
28
+ }
29
+
30
+ interface HubMessage {
31
+ type: string;
32
+ id?: string;
33
+ from?: string;
34
+ to?: string;
35
+ payload?: MessagePayload;
36
+ agentId?: string;
37
+ error?: string;
38
+ timestamp?: number;
39
+ }
40
+
41
+ export class GopherHole extends EventEmitter {
42
+ private apiKey: string;
43
+ private hubUrl: string;
44
+ private ws: WebSocket | null = null;
45
+ private reconnect: boolean;
46
+ private reconnectDelay: number;
47
+ private authenticated = false;
48
+ private pendingMessages: Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void }> = new Map();
49
+ private messageCounter = 0;
50
+ public agentId: string | null = null;
51
+
52
+ constructor(apiKey: string, options: GopherHoleOptions = {}) {
53
+ super();
54
+ this.apiKey = apiKey;
55
+ this.hubUrl = options.hubUrl || 'wss://gopherhole.ai/ws';
56
+ this.reconnect = options.reconnect ?? true;
57
+ this.reconnectDelay = options.reconnectDelay ?? 5000;
58
+ }
59
+
60
+ async connect(): Promise<void> {
61
+ return new Promise<void>((resolve, reject) => {
62
+ try {
63
+ this.ws = new WebSocket(this.hubUrl);
64
+
65
+ this.ws.on('open', () => {
66
+ console.log('[GopherHole] Connected, authenticating...');
67
+ this.ws!.send(JSON.stringify({ type: 'auth', token: this.apiKey }));
68
+ });
69
+
70
+ this.ws.on('message', (data) => {
71
+ try {
72
+ const msg: HubMessage = JSON.parse(data.toString());
73
+ this.handleMessage(msg, resolve, reject);
74
+ } catch (err) {
75
+ console.error('[GopherHole] Failed to parse message:', err);
76
+ }
77
+ });
78
+
79
+ this.ws.on('close', () => {
80
+ console.log('[GopherHole] Disconnected');
81
+ this.authenticated = false;
82
+ this.emit('disconnect');
83
+
84
+ if (this.reconnect) {
85
+ console.log(`[GopherHole] Reconnecting in ${this.reconnectDelay}ms...`);
86
+ setTimeout(() => this.connect(), this.reconnectDelay);
87
+ }
88
+ });
89
+
90
+ this.ws.on('error', (err) => {
91
+ console.error('[GopherHole] WebSocket error:', err);
92
+ this.emit('error', err);
93
+ if (!this.authenticated) {
94
+ reject(err);
95
+ }
96
+ });
97
+ } catch (err) {
98
+ reject(err);
99
+ }
100
+ });
101
+ }
102
+
103
+ private handleMessage(msg: HubMessage, resolve?: () => void, reject?: (e: Error) => void) {
104
+ switch (msg.type) {
105
+ case 'auth_ok':
106
+ console.log(`[GopherHole] Authenticated as ${msg.agentId}`);
107
+ this.authenticated = true;
108
+ this.agentId = msg.agentId || null;
109
+ this.emit('connected', { agentId: this.agentId });
110
+ resolve?.();
111
+ break;
112
+
113
+ case 'auth_error':
114
+ console.error('[GopherHole] Auth failed:', msg.error);
115
+ reject?.(new Error(msg.error || 'Authentication failed'));
116
+ break;
117
+
118
+ case 'message':
119
+ const incomingMsg: Message = {
120
+ from: msg.from!,
121
+ to: msg.to!,
122
+ payload: msg.payload!,
123
+ timestamp: msg.timestamp || Date.now(),
124
+ };
125
+ console.log(`[GopherHole] Message from ${msg.from}`);
126
+ this.emit('message', incomingMsg);
127
+ break;
128
+
129
+ case 'ack':
130
+ const pending = this.pendingMessages.get(msg.id!);
131
+ if (pending) {
132
+ pending.resolve({ id: msg.id, timestamp: msg.timestamp });
133
+ this.pendingMessages.delete(msg.id!);
134
+ }
135
+ break;
136
+
137
+ case 'error':
138
+ console.error('[GopherHole] Error:', msg.error);
139
+ const pendingErr = msg.id ? this.pendingMessages.get(msg.id) : null;
140
+ if (pendingErr) {
141
+ pendingErr.reject(new Error(msg.error));
142
+ this.pendingMessages.delete(msg.id!);
143
+ }
144
+ this.emit('error', new Error(msg.error));
145
+ break;
146
+
147
+ default:
148
+ console.log('[GopherHole] Unknown message type:', msg.type);
149
+ }
150
+ }
151
+
152
+ async send(to: string, payload: MessagePayload): Promise<{ id: string; timestamp: number }> {
153
+ if (!this.authenticated || !this.ws) {
154
+ throw new Error('Not connected');
155
+ }
156
+
157
+ const id = `msg-${++this.messageCounter}-${Date.now()}`;
158
+
159
+ return new Promise((resolve, reject) => {
160
+ this.pendingMessages.set(id, { resolve: resolve as (v: unknown) => void, reject });
161
+
162
+ this.ws!.send(JSON.stringify({
163
+ type: 'message',
164
+ id,
165
+ to,
166
+ payload,
167
+ }));
168
+
169
+ // Timeout after 30s
170
+ setTimeout(() => {
171
+ if (this.pendingMessages.has(id)) {
172
+ this.pendingMessages.delete(id);
173
+ reject(new Error('Message send timeout'));
174
+ }
175
+ }, 30000);
176
+ });
177
+ }
178
+
179
+ sendText(to: string, text: string, contextId?: string): Promise<{ id: string; timestamp: number }> {
180
+ return this.send(to, {
181
+ parts: [{ kind: 'text', text }],
182
+ contextId,
183
+ });
184
+ }
185
+
186
+ disconnect() {
187
+ this.reconnect = false;
188
+ if (this.ws) {
189
+ this.ws.close();
190
+ this.ws = null;
191
+ }
192
+ }
193
+
194
+ isConnected(): boolean {
195
+ return this.authenticated && this.ws?.readyState === WebSocket.OPEN;
196
+ }
197
+
198
+ /**
199
+ * Create a GopherHole instance from environment variables
200
+ * Reads GOPHERHOLE_API_KEY and optionally GOPHERHOLE_HUB_URL
201
+ */
202
+ static fromEnv(options: Omit<GopherHoleOptions, 'hubUrl'> = {}): GopherHole {
203
+ const apiKey = process.env.GOPHERHOLE_API_KEY;
204
+ if (!apiKey) {
205
+ throw new Error('GOPHERHOLE_API_KEY environment variable is required');
206
+ }
207
+ return new GopherHole(apiKey, {
208
+ ...options,
209
+ hubUrl: process.env.GOPHERHOLE_HUB_URL || 'wss://gopherhole.ai/ws',
210
+ });
211
+ }
212
+
213
+ /**
214
+ * Create a simple agent with a message handler
215
+ */
216
+ static agent(config: {
217
+ apiKey?: string;
218
+ hubUrl?: string;
219
+ onMessage: (msg: Message, hub: GopherHole) => Promise<string | MessagePayload | void> | string | MessagePayload | void;
220
+ onConnect?: (hub: GopherHole) => void;
221
+ onError?: (err: Error, hub: GopherHole) => void;
222
+ }): { start: () => Promise<void>; stop: () => void; hub: GopherHole } {
223
+ const apiKey = config.apiKey || process.env.GOPHERHOLE_API_KEY;
224
+ if (!apiKey) {
225
+ throw new Error('API key required: pass apiKey or set GOPHERHOLE_API_KEY');
226
+ }
227
+
228
+ const hub = new GopherHole(apiKey, {
229
+ hubUrl: config.hubUrl || process.env.GOPHERHOLE_HUB_URL,
230
+ });
231
+
232
+ hub.on('message', async (msg: Message) => {
233
+ try {
234
+ const response = await config.onMessage(msg, hub);
235
+
236
+ // If handler returns a response, send it back
237
+ if (response) {
238
+ if (typeof response === 'string') {
239
+ await hub.sendText(msg.from, response);
240
+ } else {
241
+ await hub.send(msg.from, response);
242
+ }
243
+ }
244
+ } catch (err) {
245
+ console.error('[GopherHole Agent] Message handler error:', err);
246
+ if (config.onError) {
247
+ config.onError(err as Error, hub);
248
+ }
249
+ }
250
+ });
251
+
252
+ hub.on('connected', () => {
253
+ console.log(`[GopherHole Agent] Connected as ${hub.agentId}`);
254
+ if (config.onConnect) {
255
+ config.onConnect(hub);
256
+ }
257
+ });
258
+
259
+ hub.on('error', (err: Error) => {
260
+ if (config.onError) {
261
+ config.onError(err, hub);
262
+ }
263
+ });
264
+
265
+ return {
266
+ hub,
267
+ start: async () => {
268
+ await hub.connect();
269
+ },
270
+ stop: () => {
271
+ hub.disconnect();
272
+ },
273
+ };
274
+ }
275
+ }
276
+
277
+ export default GopherHole;
package/test.ts ADDED
@@ -0,0 +1,86 @@
1
+ import { GopherHole } from './dist/index.js';
2
+
3
+ const NOVA_KEY = 'gph_ddf4842ab273455b9719d6224ccbf170';
4
+ const MARKETCLAW_KEY = 'gph_d6be074540c84db191c5aaed6464233b';
5
+
6
+ async function test() {
7
+ console.log('šŸæļø Testing GopherHole connection...\n');
8
+
9
+ let novaReceived = false;
10
+ let marketclawReceived = false;
11
+
12
+ // Connect Nova
13
+ const nova = new GopherHole(NOVA_KEY, { reconnect: false });
14
+
15
+ nova.on('message', (msg) => {
16
+ console.log(`šŸ“Ø Nova received: "${msg.payload.parts[0]?.text}" from ${msg.from}`);
17
+ novaReceived = true;
18
+ });
19
+
20
+ try {
21
+ await nova.connect();
22
+ console.log(`āœ… Nova connected as ${nova.agentId}`);
23
+ } catch (err) {
24
+ console.error('āŒ Nova connection failed:', err);
25
+ process.exit(1);
26
+ }
27
+
28
+ // Connect MarketClaw
29
+ const marketclaw = new GopherHole(MARKETCLAW_KEY, { reconnect: false });
30
+
31
+ marketclaw.on('message', (msg) => {
32
+ console.log(`šŸ“Ø MarketClaw received: "${msg.payload.parts[0]?.text}" from ${msg.from}`);
33
+ marketclawReceived = true;
34
+ });
35
+
36
+ try {
37
+ await marketclaw.connect();
38
+ console.log(`āœ… MarketClaw connected as ${marketclaw.agentId}`);
39
+ } catch (err) {
40
+ console.error('āŒ MarketClaw connection failed:', err);
41
+ nova.disconnect();
42
+ process.exit(1);
43
+ }
44
+
45
+ // Test messaging
46
+ console.log('\nšŸ“¤ Nova sending message to MarketClaw...');
47
+ try {
48
+ const result = await nova.sendText(marketclaw.agentId!, 'Hello MarketClaw! This is Nova via GopherHole šŸæļø');
49
+ console.log('āœ… Message sent:', result.id);
50
+ } catch (err) {
51
+ console.error('āŒ Send failed:', err);
52
+ }
53
+
54
+ // MarketClaw replies
55
+ console.log('šŸ“¤ MarketClaw sending reply to Nova...');
56
+ try {
57
+ const result = await marketclaw.sendText(nova.agentId!, 'Hey Nova! MarketClaw here. GopherHole is working! šŸ“ˆ');
58
+ console.log('āœ… Reply sent:', result.id);
59
+ } catch (err) {
60
+ console.error('āŒ Reply failed:', err);
61
+ }
62
+
63
+ // Wait for both messages to be delivered
64
+ console.log('\nā³ Waiting for message delivery via queue...');
65
+ const start = Date.now();
66
+ while ((!novaReceived || !marketclawReceived) && Date.now() - start < 15000) {
67
+ await new Promise(r => setTimeout(r, 500));
68
+ }
69
+
70
+ // Results
71
+ console.log('\nšŸ“Š Results:');
72
+ console.log(` Nova received MarketClaw's reply: ${novaReceived ? 'āœ…' : 'āŒ'}`);
73
+ console.log(` MarketClaw received Nova's message: ${marketclawReceived ? 'āœ…' : 'āŒ'}`);
74
+
75
+ // Cleanup
76
+ nova.disconnect();
77
+ marketclaw.disconnect();
78
+
79
+ if (novaReceived && marketclawReceived) {
80
+ console.log('\nšŸŽ‰ SUCCESS! Both agents communicated via GopherHole!');
81
+ } else {
82
+ console.log('\nāš ļø Partial success - some messages may still be in queue');
83
+ }
84
+ }
85
+
86
+ test().catch(console.error);
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src"]
14
+ }