@liveport/agent-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,609 @@
1
+ # @liveport/agent-sdk
2
+
3
+ > TypeScript SDK for AI agents to discover and access LivePort tunnels
4
+
5
+ Enable AI agents and automated testing frameworks to seamlessly access localhost applications through secure LivePort tunnels. Perfect for E2E testing, AI-powered development tools, and automated workflows.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@liveport/agent-sdk.svg)](https://www.npmjs.com/package/@liveport/agent-sdk)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ## Features
11
+
12
+ - **๐Ÿ” Auto-Discovery** - Wait for tunnels to become available automatically
13
+ - **๐Ÿค– AI-First Design** - Built specifically for AI agents and automation
14
+ - **โฑ๏ธ Configurable Timeouts** - Fine-tune waiting and polling behavior
15
+ - **๐Ÿ” Secure Authentication** - Bridge key-based authentication
16
+ - **๐Ÿ“Š Multiple Tunnels** - List and manage multiple active tunnels
17
+ - **๐Ÿงช Testing Ready** - Perfect for CI/CD and automated testing
18
+ - **TypeScript Native** - Full type safety and IntelliSense support
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @liveport/agent-sdk
24
+ # or
25
+ pnpm add @liveport/agent-sdk
26
+ # or
27
+ yarn add @liveport/agent-sdk
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```typescript
33
+ import { LivePortAgent } from '@liveport/agent-sdk';
34
+
35
+ // Create agent instance
36
+ const agent = new LivePortAgent({
37
+ key: process.env.LIVEPORT_KEY!
38
+ });
39
+
40
+ // Wait for tunnel to become available
41
+ const tunnel = await agent.waitForTunnel({ timeout: 30000 });
42
+ console.log(`Testing at: ${tunnel.url}`);
43
+
44
+ // Run your tests against tunnel.url
45
+ const response = await fetch(`${tunnel.url}/api/health`);
46
+ console.log(await response.json());
47
+
48
+ // Clean up when done
49
+ await agent.disconnect();
50
+ ```
51
+
52
+ ## API Reference
53
+
54
+ ### `LivePortAgent`
55
+
56
+ Main SDK class for interacting with LivePort tunnels.
57
+
58
+ #### Constructor
59
+
60
+ ```typescript
61
+ new LivePortAgent(config: LivePortAgentConfig)
62
+ ```
63
+
64
+ **Config options:**
65
+
66
+ | Option | Type | Required | Default | Description |
67
+ |--------|------|----------|---------|-------------|
68
+ | `key` | string | โœ… | - | Bridge key for authentication |
69
+ | `apiUrl` | string | โŒ | `https://app.liveport.dev` | API base URL |
70
+ | `timeout` | number | โŒ | `30000` | Default timeout in milliseconds |
71
+
72
+ **Example:**
73
+
74
+ ```typescript
75
+ const agent = new LivePortAgent({
76
+ key: 'lpk_abc123...',
77
+ apiUrl: 'https://app.liveport.dev',
78
+ timeout: 60000
79
+ });
80
+ ```
81
+
82
+ ---
83
+
84
+ #### `waitForTunnel(options?)`
85
+
86
+ Wait for a tunnel to become available. Long-polls the API until a tunnel is ready or timeout is reached.
87
+
88
+ ```typescript
89
+ async waitForTunnel(options?: WaitForTunnelOptions): Promise<AgentTunnel>
90
+ ```
91
+
92
+ **Options:**
93
+
94
+ | Option | Type | Default | Description |
95
+ |--------|------|---------|-------------|
96
+ | `timeout` | number | `30000` | Maximum wait time in milliseconds |
97
+ | `pollInterval` | number | `1000` | Poll interval in milliseconds |
98
+
99
+ **Returns:** `Promise<AgentTunnel>`
100
+
101
+ **Throws:**
102
+ - `TunnelTimeoutError` - If no tunnel becomes available within timeout
103
+ - `ApiError` - If the API request fails
104
+
105
+ **Example:**
106
+
107
+ ```typescript
108
+ try {
109
+ const tunnel = await agent.waitForTunnel({
110
+ timeout: 60000, // Wait up to 60 seconds
111
+ pollInterval: 2000 // Check every 2 seconds
112
+ });
113
+
114
+ console.log(`Tunnel available at: ${tunnel.url}`);
115
+ } catch (error) {
116
+ if (error instanceof TunnelTimeoutError) {
117
+ console.log('No tunnel available yet');
118
+ }
119
+ }
120
+ ```
121
+
122
+ ---
123
+
124
+ #### `listTunnels()`
125
+
126
+ List all active tunnels for this bridge key.
127
+
128
+ ```typescript
129
+ async listTunnels(): Promise<AgentTunnel[]>
130
+ ```
131
+
132
+ **Returns:** `Promise<AgentTunnel[]>` - Array of active tunnels
133
+
134
+ **Throws:** `ApiError` - If the API request fails
135
+
136
+ **Example:**
137
+
138
+ ```typescript
139
+ const tunnels = await agent.listTunnels();
140
+
141
+ for (const tunnel of tunnels) {
142
+ console.log(`${tunnel.subdomain}: ${tunnel.url} (port ${tunnel.localPort})`);
143
+ }
144
+
145
+ // Find specific tunnel by port
146
+ const apiTunnel = tunnels.find(t => t.localPort === 3000);
147
+ if (apiTunnel) {
148
+ console.log(`API server at: ${apiTunnel.url}`);
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ #### `disconnect()`
155
+
156
+ Disconnect and clean up. Cancels any pending `waitForTunnel` calls.
157
+
158
+ ```typescript
159
+ async disconnect(): Promise<void>
160
+ ```
161
+
162
+ **Example:**
163
+
164
+ ```typescript
165
+ // Always clean up in a finally block
166
+ try {
167
+ const tunnel = await agent.waitForTunnel();
168
+ // Use tunnel...
169
+ } finally {
170
+ await agent.disconnect();
171
+ }
172
+ ```
173
+
174
+ ---
175
+
176
+ ### Types
177
+
178
+ #### `AgentTunnel`
179
+
180
+ Represents a tunnel connection.
181
+
182
+ ```typescript
183
+ interface AgentTunnel {
184
+ tunnelId: string; // Unique tunnel identifier
185
+ subdomain: string; // Subdomain (e.g., "swift-fox-a7x2")
186
+ url: string; // Full public URL
187
+ localPort: number; // Local port being tunneled
188
+ createdAt: Date; // When tunnel was created
189
+ expiresAt: Date; // When tunnel will expire
190
+ }
191
+ ```
192
+
193
+ #### `TunnelTimeoutError`
194
+
195
+ Thrown when `waitForTunnel` times out.
196
+
197
+ ```typescript
198
+ class TunnelTimeoutError extends Error {
199
+ constructor(timeout: number);
200
+ }
201
+ ```
202
+
203
+ #### `ApiError`
204
+
205
+ Thrown when an API request fails.
206
+
207
+ ```typescript
208
+ class ApiError extends Error {
209
+ statusCode: number; // HTTP status code
210
+ code: string; // Error code (e.g., "INVALID_KEY")
211
+
212
+ constructor(statusCode: number, code: string, message: string);
213
+ }
214
+ ```
215
+
216
+ **Error Codes:**
217
+ - `INVALID_KEY` - Bridge key is invalid
218
+ - `EXPIRED_KEY` - Bridge key has expired
219
+ - `REVOKED_KEY` - Bridge key was revoked
220
+ - `USAGE_LIMIT_EXCEEDED` - Key usage limit reached
221
+ - `RATE_LIMIT_EXCEEDED` - Too many requests
222
+ - `TIMEOUT` - Server timeout
223
+ - `TUNNEL_SERVER_ERROR` - Tunnel server error
224
+ - `INTERNAL_ERROR` - Internal server error
225
+
226
+ ---
227
+
228
+ ## Use Cases
229
+
230
+ ### 1. Automated Testing
231
+
232
+ ```typescript
233
+ import { LivePortAgent } from '@liveport/agent-sdk';
234
+ import { test, expect } from 'vitest';
235
+
236
+ let tunnelUrl: string;
237
+
238
+ beforeAll(async () => {
239
+ const agent = new LivePortAgent({ key: process.env.LIVEPORT_KEY! });
240
+ const tunnel = await agent.waitForTunnel({ timeout: 60000 });
241
+ tunnelUrl = tunnel.url;
242
+ });
243
+
244
+ test('API returns healthy status', async () => {
245
+ const response = await fetch(`${tunnelUrl}/api/health`);
246
+ expect(response.ok).toBe(true);
247
+
248
+ const data = await response.json();
249
+ expect(data.status).toBe('healthy');
250
+ });
251
+ ```
252
+
253
+ ### 2. AI Agent Workflow
254
+
255
+ ```typescript
256
+ import { LivePortAgent } from '@liveport/agent-sdk';
257
+
258
+ async function runAITests(bridgeKey: string) {
259
+ const agent = new LivePortAgent({ key: bridgeKey, timeout: 120000 });
260
+
261
+ console.log('Waiting for developer to start tunnel...');
262
+ const tunnel = await agent.waitForTunnel();
263
+
264
+ console.log(`Connected to ${tunnel.url}`);
265
+
266
+ // AI agent can now interact with the local application
267
+ await discoverEndpoints(tunnel.url);
268
+ await runPerformanceTests(tunnel.url);
269
+ await analyzeAPIs(tunnel.url);
270
+ await generateDocumentation(tunnel.url);
271
+
272
+ await agent.disconnect();
273
+ }
274
+ ```
275
+
276
+ ### 3. CI/CD Integration
277
+
278
+ ```typescript
279
+ // tests/setup.ts
280
+ import { LivePortAgent } from '@liveport/agent-sdk';
281
+
282
+ export async function setupTunnel() {
283
+ const agent = new LivePortAgent({
284
+ key: process.env.CI_LIVEPORT_KEY!,
285
+ timeout: 60000
286
+ });
287
+
288
+ // Wait for tunnel (developer must start it before running tests)
289
+ const tunnel = await agent.waitForTunnel();
290
+
291
+ // Make tunnel URL available to tests
292
+ process.env.TEST_BASE_URL = tunnel.url;
293
+
294
+ return { agent, tunnel };
295
+ }
296
+
297
+ export async function teardownTunnel(agent: LivePortAgent) {
298
+ await agent.disconnect();
299
+ }
300
+ ```
301
+
302
+ ### 4. Multi-Service Testing
303
+
304
+ ```typescript
305
+ import { LivePortAgent } from '@liveport/agent-sdk';
306
+
307
+ async function testMicroservices() {
308
+ const agent = new LivePortAgent({ key: process.env.LIVEPORT_KEY! });
309
+
310
+ // List all available tunnels
311
+ const tunnels = await agent.listTunnels();
312
+
313
+ // Find specific services by port
314
+ const api = tunnels.find(t => t.localPort === 3000);
315
+ const web = tunnels.find(t => t.localPort === 3001);
316
+ const db = tunnels.find(t => t.localPort === 5432);
317
+
318
+ if (!api || !web) {
319
+ throw new Error('Required services not available');
320
+ }
321
+
322
+ // Test API
323
+ const apiHealth = await fetch(`${api.url}/health`);
324
+ console.log(`API: ${apiHealth.status}`);
325
+
326
+ // Test Web
327
+ const webHome = await fetch(web.url);
328
+ console.log(`Web: ${webHome.status}`);
329
+
330
+ await agent.disconnect();
331
+ }
332
+ ```
333
+
334
+ ### 5. Webhook Testing
335
+
336
+ ```typescript
337
+ import { LivePortAgent } from '@liveport/agent-sdk';
338
+
339
+ async function testWebhooks() {
340
+ const agent = new LivePortAgent({ key: process.env.LIVEPORT_KEY! });
341
+ const tunnel = await agent.waitForTunnel();
342
+
343
+ // Register webhook with external service
344
+ await registerWebhook(`${tunnel.url}/webhooks/stripe`);
345
+
346
+ // Trigger webhook event
347
+ await triggerTestEvent();
348
+
349
+ // Verify webhook was received
350
+ const response = await fetch(`${tunnel.url}/webhooks/verify`);
351
+ const result = await response.json();
352
+
353
+ expect(result.received).toBe(true);
354
+
355
+ await agent.disconnect();
356
+ }
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Error Handling
362
+
363
+ ### Handling Timeouts
364
+
365
+ ```typescript
366
+ import { LivePortAgent, TunnelTimeoutError } from '@liveport/agent-sdk';
367
+
368
+ try {
369
+ const tunnel = await agent.waitForTunnel({ timeout: 30000 });
370
+ } catch (error) {
371
+ if (error instanceof TunnelTimeoutError) {
372
+ console.error('No tunnel available. Did you forget to run:');
373
+ console.error(' liveport connect <port>');
374
+ process.exit(1);
375
+ }
376
+ throw error;
377
+ }
378
+ ```
379
+
380
+ ### Handling API Errors
381
+
382
+ ```typescript
383
+ import { ApiError } from '@liveport/agent-sdk';
384
+
385
+ try {
386
+ const tunnels = await agent.listTunnels();
387
+ } catch (error) {
388
+ if (error instanceof ApiError) {
389
+ console.error(`API Error [${error.code}]: ${error.message}`);
390
+ console.error(`Status: ${error.statusCode}`);
391
+
392
+ if (error.code === 'INVALID_KEY') {
393
+ console.error('Get a new key at: https://liveport.dev/keys');
394
+ }
395
+ }
396
+ throw error;
397
+ }
398
+ ```
399
+
400
+ ### Robust Error Handling Pattern
401
+
402
+ ```typescript
403
+ async function robustTunnelAccess() {
404
+ const agent = new LivePortAgent({ key: process.env.LIVEPORT_KEY! });
405
+
406
+ try {
407
+ const tunnel = await agent.waitForTunnel({ timeout: 60000 });
408
+
409
+ // Your logic here
410
+ await runTests(tunnel.url);
411
+
412
+ } catch (error) {
413
+ if (error instanceof TunnelTimeoutError) {
414
+ console.error('Timeout waiting for tunnel');
415
+ process.exit(1);
416
+ } else if (error instanceof ApiError) {
417
+ console.error(`API Error: ${error.message}`);
418
+ process.exit(1);
419
+ } else {
420
+ console.error('Unexpected error:', error);
421
+ process.exit(1);
422
+ }
423
+ } finally {
424
+ await agent.disconnect();
425
+ }
426
+ }
427
+ ```
428
+
429
+ ---
430
+
431
+ ## Examples
432
+
433
+ Comprehensive examples are available in the [`examples/`](./examples/) directory:
434
+
435
+ - **[01-basic-usage.ts](./examples/01-basic-usage.ts)** - Simple tunnel connection
436
+ - **[02-testing-integration.ts](./examples/02-testing-integration.ts)** - Vitest integration
437
+ - **[03-ai-agent-workflow.ts](./examples/03-ai-agent-workflow.ts)** - AI agent implementation
438
+ - **[04-multiple-tunnels.ts](./examples/04-multiple-tunnels.ts)** - Multi-service testing
439
+ - **[05-error-handling.ts](./examples/05-error-handling.ts)** - Robust error handling
440
+
441
+ Run examples:
442
+
443
+ ```bash
444
+ # Set your bridge key
445
+ export LIVEPORT_KEY=lpk_your_key_here
446
+
447
+ # Start a tunnel in another terminal
448
+ liveport connect 3000
449
+
450
+ # Run an example
451
+ npx tsx examples/01-basic-usage.ts
452
+ ```
453
+
454
+ ---
455
+
456
+ ## Best Practices
457
+
458
+ ### โœ… Do's
459
+
460
+ 1. **Set reasonable timeouts**
461
+ ```typescript
462
+ // Good: 60 second timeout for CI
463
+ const tunnel = await agent.waitForTunnel({ timeout: 60000 });
464
+ ```
465
+
466
+ 2. **Always clean up**
467
+ ```typescript
468
+ try {
469
+ const tunnel = await agent.waitForTunnel();
470
+ // Use tunnel...
471
+ } finally {
472
+ await agent.disconnect(); // Always cleanup
473
+ }
474
+ ```
475
+
476
+ 3. **Handle specific errors**
477
+ ```typescript
478
+ try {
479
+ const tunnel = await agent.waitForTunnel();
480
+ } catch (error) {
481
+ if (error instanceof TunnelTimeoutError) {
482
+ // Provide helpful message
483
+ }
484
+ }
485
+ ```
486
+
487
+ 4. **Use environment variables**
488
+ ```typescript
489
+ // Good
490
+ const agent = new LivePortAgent({
491
+ key: process.env.LIVEPORT_KEY!
492
+ });
493
+
494
+ // Bad - never hardcode keys
495
+ const agent = new LivePortAgent({ key: 'lpk_...' });
496
+ ```
497
+
498
+ 5. **Configure appropriate timeouts**
499
+ ```typescript
500
+ // Local development: short timeout
501
+ const tunnel = await agent.waitForTunnel({ timeout: 10000 });
502
+
503
+ // CI/CD: longer timeout
504
+ const tunnel = await agent.waitForTunnel({ timeout: 120000 });
505
+ ```
506
+
507
+ ### โŒ Don'ts
508
+
509
+ 1. **Don't commit bridge keys**
510
+ ```bash
511
+ # Use .env.local (git-ignored)
512
+ LIVEPORT_KEY=lpk_your_key_here
513
+ ```
514
+
515
+ 2. **Don't forget to disconnect**
516
+ ```typescript
517
+ // Bad - missing cleanup
518
+ const tunnel = await agent.waitForTunnel();
519
+ // ... tests ...
520
+ // Missing: await agent.disconnect()
521
+ ```
522
+
523
+ 3. **Don't ignore errors**
524
+ ```typescript
525
+ // Bad - swallowing errors
526
+ try {
527
+ const tunnel = await agent.waitForTunnel();
528
+ } catch (error) {
529
+ // Silent failure
530
+ }
531
+ ```
532
+
533
+ 4. **Don't use production keys in tests**
534
+ ```bash
535
+ # Separate keys for different environments
536
+ LIVEPORT_DEV_KEY=lpk_dev_...
537
+ LIVEPORT_CI_KEY=lpk_ci_...
538
+ LIVEPORT_PROD_KEY=lpk_prod_...
539
+ ```
540
+
541
+ ---
542
+
543
+ ## TypeScript Support
544
+
545
+ This package is written in TypeScript and includes full type definitions.
546
+
547
+ ```typescript
548
+ import type {
549
+ LivePortAgent,
550
+ LivePortAgentConfig,
551
+ AgentTunnel,
552
+ WaitForTunnelOptions,
553
+ TunnelTimeoutError,
554
+ ApiError
555
+ } from '@liveport/agent-sdk';
556
+
557
+ // Full IntelliSense support
558
+ const config: LivePortAgentConfig = {
559
+ key: 'lpk_...',
560
+ timeout: 30000
561
+ };
562
+
563
+ const agent: LivePortAgent = new LivePortAgent(config);
564
+ const tunnel: AgentTunnel = await agent.waitForTunnel();
565
+ ```
566
+
567
+ ---
568
+
569
+ ## Testing
570
+
571
+ The SDK includes comprehensive tests. Run them with:
572
+
573
+ ```bash
574
+ pnpm test
575
+ ```
576
+
577
+ ---
578
+
579
+ ## Related Packages
580
+
581
+ - **[@liveport/cli](https://www.npmjs.com/package/@liveport/cli)** - CLI for creating tunnels
582
+ - **[Dashboard](https://liveport.dev/dashboard)** - Web interface for managing keys and tunnels
583
+
584
+ ---
585
+
586
+ ## Resources
587
+
588
+ - **Documentation**: [liveport.dev/docs](https://liveport.dev/docs)
589
+ - **API Reference**: [liveport.dev/docs](https://liveport.dev/docs)
590
+ - **Dashboard**: [liveport.dev/dashboard](https://liveport.dev/dashboard)
591
+ - **Examples**: [examples/](./examples/)
592
+ - **Support**: [GitHub Issues](https://github.com/dundas/liveport/issues)
593
+ - **Website**: [liveport.dev](https://liveport.dev)
594
+
595
+ ---
596
+
597
+ ## Contributing
598
+
599
+ Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) first.
600
+
601
+ ---
602
+
603
+ ## License
604
+
605
+ MIT ยฉ LivePort
606
+
607
+ ---
608
+
609
+ **Built with โค๏ธ for AI agents and developers**
@@ -0,0 +1,105 @@
1
+ export { Tunnel } from '@liveport/shared';
2
+
3
+ /**
4
+ * LivePort Agent SDK
5
+ *
6
+ * TypeScript SDK for AI agents to wait for and access localhost tunnels.
7
+ */
8
+ interface LivePortAgentConfig {
9
+ /** Bridge key for authentication */
10
+ key: string;
11
+ /** API base URL (default: https://app.liveport.dev) */
12
+ apiUrl?: string;
13
+ /** Default timeout in milliseconds (default: 30000) */
14
+ timeout?: number;
15
+ }
16
+ interface WaitForTunnelOptions {
17
+ /** Timeout in milliseconds (default: 30000) */
18
+ timeout?: number;
19
+ /** Poll interval in milliseconds (default: 1000) */
20
+ pollInterval?: number;
21
+ }
22
+ interface AgentTunnel {
23
+ tunnelId: string;
24
+ subdomain: string;
25
+ url: string;
26
+ localPort: number;
27
+ createdAt: Date;
28
+ expiresAt: Date;
29
+ }
30
+ /** Error thrown when tunnel wait times out */
31
+ declare class TunnelTimeoutError extends Error {
32
+ constructor(timeout: number);
33
+ }
34
+ /** Error thrown when API request fails */
35
+ declare class ApiError extends Error {
36
+ readonly statusCode: number;
37
+ readonly code: string;
38
+ constructor(statusCode: number, code: string, message: string);
39
+ }
40
+ /**
41
+ * LivePort Agent SDK
42
+ *
43
+ * Allows AI agents to wait for and access localhost tunnels.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * import { LivePortAgent } from '@liveport/agent-sdk';
48
+ *
49
+ * const agent = new LivePortAgent({
50
+ * key: process.env.LIVEPORT_BRIDGE_KEY!
51
+ * });
52
+ *
53
+ * // Wait for a tunnel to become available
54
+ * const tunnel = await agent.waitForTunnel({ timeout: 30000 });
55
+ * console.log(`Testing at: ${tunnel.url}`);
56
+ *
57
+ * // Run your tests against tunnel.url
58
+ *
59
+ * // Clean up when done
60
+ * await agent.disconnect();
61
+ * ```
62
+ */
63
+ declare class LivePortAgent {
64
+ private config;
65
+ private abortController;
66
+ constructor(config: LivePortAgentConfig);
67
+ /**
68
+ * Wait for a tunnel to become available
69
+ *
70
+ * Long-polls the API until a tunnel is ready or timeout is reached.
71
+ *
72
+ * @param options - Wait options
73
+ * @returns The tunnel info once available
74
+ * @throws TunnelTimeoutError if no tunnel becomes available within timeout
75
+ * @throws ApiError if the API request fails
76
+ */
77
+ waitForTunnel(options?: WaitForTunnelOptions): Promise<AgentTunnel>;
78
+ /**
79
+ * List all active tunnels for this bridge key
80
+ *
81
+ * @returns Array of active tunnels
82
+ * @throws ApiError if the API request fails
83
+ */
84
+ listTunnels(): Promise<AgentTunnel[]>;
85
+ /**
86
+ * Disconnect and clean up
87
+ *
88
+ * Cancels any pending waitForTunnel calls.
89
+ */
90
+ disconnect(): Promise<void>;
91
+ /**
92
+ * Make an authenticated API request
93
+ */
94
+ private makeRequest;
95
+ /**
96
+ * Parse tunnel response into AgentTunnel
97
+ */
98
+ private parseTunnel;
99
+ /**
100
+ * Sleep helper
101
+ */
102
+ private sleep;
103
+ }
104
+
105
+ export { type AgentTunnel, ApiError, LivePortAgent, type LivePortAgentConfig, TunnelTimeoutError, type WaitForTunnelOptions };
@@ -0,0 +1,105 @@
1
+ export { Tunnel } from '@liveport/shared';
2
+
3
+ /**
4
+ * LivePort Agent SDK
5
+ *
6
+ * TypeScript SDK for AI agents to wait for and access localhost tunnels.
7
+ */
8
+ interface LivePortAgentConfig {
9
+ /** Bridge key for authentication */
10
+ key: string;
11
+ /** API base URL (default: https://app.liveport.dev) */
12
+ apiUrl?: string;
13
+ /** Default timeout in milliseconds (default: 30000) */
14
+ timeout?: number;
15
+ }
16
+ interface WaitForTunnelOptions {
17
+ /** Timeout in milliseconds (default: 30000) */
18
+ timeout?: number;
19
+ /** Poll interval in milliseconds (default: 1000) */
20
+ pollInterval?: number;
21
+ }
22
+ interface AgentTunnel {
23
+ tunnelId: string;
24
+ subdomain: string;
25
+ url: string;
26
+ localPort: number;
27
+ createdAt: Date;
28
+ expiresAt: Date;
29
+ }
30
+ /** Error thrown when tunnel wait times out */
31
+ declare class TunnelTimeoutError extends Error {
32
+ constructor(timeout: number);
33
+ }
34
+ /** Error thrown when API request fails */
35
+ declare class ApiError extends Error {
36
+ readonly statusCode: number;
37
+ readonly code: string;
38
+ constructor(statusCode: number, code: string, message: string);
39
+ }
40
+ /**
41
+ * LivePort Agent SDK
42
+ *
43
+ * Allows AI agents to wait for and access localhost tunnels.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * import { LivePortAgent } from '@liveport/agent-sdk';
48
+ *
49
+ * const agent = new LivePortAgent({
50
+ * key: process.env.LIVEPORT_BRIDGE_KEY!
51
+ * });
52
+ *
53
+ * // Wait for a tunnel to become available
54
+ * const tunnel = await agent.waitForTunnel({ timeout: 30000 });
55
+ * console.log(`Testing at: ${tunnel.url}`);
56
+ *
57
+ * // Run your tests against tunnel.url
58
+ *
59
+ * // Clean up when done
60
+ * await agent.disconnect();
61
+ * ```
62
+ */
63
+ declare class LivePortAgent {
64
+ private config;
65
+ private abortController;
66
+ constructor(config: LivePortAgentConfig);
67
+ /**
68
+ * Wait for a tunnel to become available
69
+ *
70
+ * Long-polls the API until a tunnel is ready or timeout is reached.
71
+ *
72
+ * @param options - Wait options
73
+ * @returns The tunnel info once available
74
+ * @throws TunnelTimeoutError if no tunnel becomes available within timeout
75
+ * @throws ApiError if the API request fails
76
+ */
77
+ waitForTunnel(options?: WaitForTunnelOptions): Promise<AgentTunnel>;
78
+ /**
79
+ * List all active tunnels for this bridge key
80
+ *
81
+ * @returns Array of active tunnels
82
+ * @throws ApiError if the API request fails
83
+ */
84
+ listTunnels(): Promise<AgentTunnel[]>;
85
+ /**
86
+ * Disconnect and clean up
87
+ *
88
+ * Cancels any pending waitForTunnel calls.
89
+ */
90
+ disconnect(): Promise<void>;
91
+ /**
92
+ * Make an authenticated API request
93
+ */
94
+ private makeRequest;
95
+ /**
96
+ * Parse tunnel response into AgentTunnel
97
+ */
98
+ private parseTunnel;
99
+ /**
100
+ * Sleep helper
101
+ */
102
+ private sleep;
103
+ }
104
+
105
+ export { type AgentTunnel, ApiError, LivePortAgent, type LivePortAgentConfig, TunnelTimeoutError, type WaitForTunnelOptions };
package/dist/index.js ADDED
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ApiError: () => ApiError,
24
+ LivePortAgent: () => LivePortAgent,
25
+ TunnelTimeoutError: () => TunnelTimeoutError
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+ var TunnelTimeoutError = class extends Error {
29
+ constructor(timeout) {
30
+ super(`Tunnel not available within ${timeout}ms timeout`);
31
+ this.name = "TunnelTimeoutError";
32
+ }
33
+ };
34
+ var ApiError = class extends Error {
35
+ statusCode;
36
+ code;
37
+ constructor(statusCode, code, message) {
38
+ super(message);
39
+ this.name = "ApiError";
40
+ this.statusCode = statusCode;
41
+ this.code = code;
42
+ }
43
+ };
44
+ var LivePortAgent = class {
45
+ config;
46
+ abortController = null;
47
+ constructor(config) {
48
+ if (!config.key) {
49
+ throw new Error("Bridge key is required");
50
+ }
51
+ this.config = {
52
+ key: config.key,
53
+ apiUrl: config.apiUrl || "https://app.liveport.dev",
54
+ timeout: config.timeout || 3e4
55
+ };
56
+ }
57
+ /**
58
+ * Wait for a tunnel to become available
59
+ *
60
+ * Long-polls the API until a tunnel is ready or timeout is reached.
61
+ *
62
+ * @param options - Wait options
63
+ * @returns The tunnel info once available
64
+ * @throws TunnelTimeoutError if no tunnel becomes available within timeout
65
+ * @throws ApiError if the API request fails
66
+ */
67
+ async waitForTunnel(options) {
68
+ const timeout = options?.timeout ?? this.config.timeout;
69
+ const pollInterval = options?.pollInterval ?? 1e3;
70
+ this.abortController = new AbortController();
71
+ const startTime = Date.now();
72
+ while (Date.now() - startTime < timeout) {
73
+ try {
74
+ const response = await this.makeRequest(
75
+ `/api/agent/tunnels/wait?timeout=${Math.min(pollInterval * 5, timeout - (Date.now() - startTime))}`,
76
+ { signal: this.abortController.signal }
77
+ );
78
+ if (response.ok) {
79
+ const data = await response.json();
80
+ if (data.tunnel) {
81
+ return this.parseTunnel(data.tunnel);
82
+ }
83
+ } else if (response.status === 408) {
84
+ } else {
85
+ const error = await response.json().catch(() => ({ code: "UNKNOWN", message: "Request failed" }));
86
+ throw new ApiError(response.status, error.code, error.message);
87
+ }
88
+ } catch (err) {
89
+ if (err instanceof ApiError) throw err;
90
+ if (err.name === "AbortError") {
91
+ throw new Error("Wait cancelled");
92
+ }
93
+ }
94
+ await this.sleep(pollInterval);
95
+ }
96
+ throw new TunnelTimeoutError(timeout);
97
+ }
98
+ /**
99
+ * List all active tunnels for this bridge key
100
+ *
101
+ * @returns Array of active tunnels
102
+ * @throws ApiError if the API request fails
103
+ */
104
+ async listTunnels() {
105
+ const response = await this.makeRequest("/api/agent/tunnels");
106
+ if (!response.ok) {
107
+ const error = await response.json().catch(() => ({ code: "UNKNOWN", message: "Request failed" }));
108
+ throw new ApiError(response.status, error.code, error.message);
109
+ }
110
+ const data = await response.json();
111
+ return (data.tunnels || []).map((t) => this.parseTunnel(t));
112
+ }
113
+ /**
114
+ * Disconnect and clean up
115
+ *
116
+ * Cancels any pending waitForTunnel calls.
117
+ */
118
+ async disconnect() {
119
+ if (this.abortController) {
120
+ this.abortController.abort();
121
+ this.abortController = null;
122
+ }
123
+ }
124
+ /**
125
+ * Make an authenticated API request
126
+ */
127
+ async makeRequest(path, options = {}) {
128
+ const url = `${this.config.apiUrl}${path}`;
129
+ return fetch(url, {
130
+ ...options,
131
+ headers: {
132
+ "Authorization": `Bearer ${this.config.key}`,
133
+ "Content-Type": "application/json",
134
+ ...options.headers
135
+ }
136
+ });
137
+ }
138
+ /**
139
+ * Parse tunnel response into AgentTunnel
140
+ */
141
+ parseTunnel(data) {
142
+ return {
143
+ tunnelId: data.tunnelId || data.id,
144
+ subdomain: data.subdomain,
145
+ url: data.url,
146
+ localPort: data.localPort,
147
+ createdAt: new Date(data.createdAt),
148
+ expiresAt: new Date(data.expiresAt)
149
+ };
150
+ }
151
+ /**
152
+ * Sleep helper
153
+ */
154
+ sleep(ms) {
155
+ return new Promise((resolve) => setTimeout(resolve, ms));
156
+ }
157
+ };
158
+ // Annotate the CommonJS export names for ESM import in node:
159
+ 0 && (module.exports = {
160
+ ApiError,
161
+ LivePortAgent,
162
+ TunnelTimeoutError
163
+ });
164
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * LivePort Agent SDK\n *\n * TypeScript SDK for AI agents to wait for and access localhost tunnels.\n */\n\nimport type { Tunnel } from \"@liveport/shared\";\n\nexport interface LivePortAgentConfig {\n /** Bridge key for authentication */\n key: string;\n /** API base URL (default: https://app.liveport.dev) */\n apiUrl?: string;\n /** Default timeout in milliseconds (default: 30000) */\n timeout?: number;\n}\n\nexport interface WaitForTunnelOptions {\n /** Timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Poll interval in milliseconds (default: 1000) */\n pollInterval?: number;\n}\n\nexport interface AgentTunnel {\n tunnelId: string;\n subdomain: string;\n url: string;\n localPort: number;\n createdAt: Date;\n expiresAt: Date;\n}\n\n/** Error thrown when tunnel wait times out */\nexport class TunnelTimeoutError extends Error {\n constructor(timeout: number) {\n super(`Tunnel not available within ${timeout}ms timeout`);\n this.name = \"TunnelTimeoutError\";\n }\n}\n\n/** Error thrown when API request fails */\nexport class ApiError extends Error {\n public readonly statusCode: number;\n public readonly code: string;\n\n constructor(statusCode: number, code: string, message: string) {\n super(message);\n this.name = \"ApiError\";\n this.statusCode = statusCode;\n this.code = code;\n }\n}\n\n/**\n * LivePort Agent SDK\n *\n * Allows AI agents to wait for and access localhost tunnels.\n *\n * @example\n * ```typescript\n * import { LivePortAgent } from '@liveport/agent-sdk';\n *\n * const agent = new LivePortAgent({\n * key: process.env.LIVEPORT_BRIDGE_KEY!\n * });\n *\n * // Wait for a tunnel to become available\n * const tunnel = await agent.waitForTunnel({ timeout: 30000 });\n * console.log(`Testing at: ${tunnel.url}`);\n *\n * // Run your tests against tunnel.url\n *\n * // Clean up when done\n * await agent.disconnect();\n * ```\n */\nexport class LivePortAgent {\n private config: Required<LivePortAgentConfig>;\n private abortController: AbortController | null = null;\n\n constructor(config: LivePortAgentConfig) {\n if (!config.key) {\n throw new Error(\"Bridge key is required\");\n }\n\n this.config = {\n key: config.key,\n apiUrl: config.apiUrl || \"https://app.liveport.dev\",\n timeout: config.timeout || 30000,\n };\n }\n\n /**\n * Wait for a tunnel to become available\n *\n * Long-polls the API until a tunnel is ready or timeout is reached.\n *\n * @param options - Wait options\n * @returns The tunnel info once available\n * @throws TunnelTimeoutError if no tunnel becomes available within timeout\n * @throws ApiError if the API request fails\n */\n async waitForTunnel(options?: WaitForTunnelOptions): Promise<AgentTunnel> {\n const timeout = options?.timeout ?? this.config.timeout;\n const pollInterval = options?.pollInterval ?? 1000;\n\n this.abortController = new AbortController();\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeout) {\n try {\n const response = await this.makeRequest(\n `/api/agent/tunnels/wait?timeout=${Math.min(pollInterval * 5, timeout - (Date.now() - startTime))}`,\n { signal: this.abortController.signal }\n );\n\n if (response.ok) {\n const data = await response.json() as { tunnel?: Record<string, unknown> };\n if (data.tunnel) {\n return this.parseTunnel(data.tunnel);\n }\n } else if (response.status === 408) {\n // Timeout from server, continue polling\n } else {\n const error = await response.json().catch(() => ({ code: \"UNKNOWN\", message: \"Request failed\" })) as { code: string; message: string };\n throw new ApiError(response.status, error.code, error.message);\n }\n } catch (err) {\n if (err instanceof ApiError) throw err;\n if ((err as Error).name === \"AbortError\") {\n throw new Error(\"Wait cancelled\");\n }\n // Network error, wait and retry\n }\n\n // Wait before next poll\n await this.sleep(pollInterval);\n }\n\n throw new TunnelTimeoutError(timeout);\n }\n\n /**\n * List all active tunnels for this bridge key\n *\n * @returns Array of active tunnels\n * @throws ApiError if the API request fails\n */\n async listTunnels(): Promise<AgentTunnel[]> {\n const response = await this.makeRequest(\"/api/agent/tunnels\");\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ code: \"UNKNOWN\", message: \"Request failed\" })) as { code: string; message: string };\n throw new ApiError(response.status, error.code, error.message);\n }\n\n const data = await response.json() as { tunnels?: Record<string, unknown>[] };\n return (data.tunnels || []).map((t) => this.parseTunnel(t));\n }\n\n /**\n * Disconnect and clean up\n *\n * Cancels any pending waitForTunnel calls.\n */\n async disconnect(): Promise<void> {\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n }\n\n /**\n * Make an authenticated API request\n */\n private async makeRequest(\n path: string,\n options: RequestInit = {}\n ): Promise<Response> {\n const url = `${this.config.apiUrl}${path}`;\n\n return fetch(url, {\n ...options,\n headers: {\n \"Authorization\": `Bearer ${this.config.key}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n }\n\n /**\n * Parse tunnel response into AgentTunnel\n */\n private parseTunnel(data: Record<string, unknown>): AgentTunnel {\n return {\n tunnelId: data.tunnelId as string || data.id as string,\n subdomain: data.subdomain as string,\n url: data.url as string,\n localPort: data.localPort as number,\n createdAt: new Date(data.createdAt as string),\n expiresAt: new Date(data.expiresAt as string),\n };\n }\n\n /**\n * Sleep helper\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n// Re-export types\nexport type { Tunnel } from \"@liveport/shared\";\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,137 @@
1
+ // src/index.ts
2
+ var TunnelTimeoutError = class extends Error {
3
+ constructor(timeout) {
4
+ super(`Tunnel not available within ${timeout}ms timeout`);
5
+ this.name = "TunnelTimeoutError";
6
+ }
7
+ };
8
+ var ApiError = class extends Error {
9
+ statusCode;
10
+ code;
11
+ constructor(statusCode, code, message) {
12
+ super(message);
13
+ this.name = "ApiError";
14
+ this.statusCode = statusCode;
15
+ this.code = code;
16
+ }
17
+ };
18
+ var LivePortAgent = class {
19
+ config;
20
+ abortController = null;
21
+ constructor(config) {
22
+ if (!config.key) {
23
+ throw new Error("Bridge key is required");
24
+ }
25
+ this.config = {
26
+ key: config.key,
27
+ apiUrl: config.apiUrl || "https://app.liveport.dev",
28
+ timeout: config.timeout || 3e4
29
+ };
30
+ }
31
+ /**
32
+ * Wait for a tunnel to become available
33
+ *
34
+ * Long-polls the API until a tunnel is ready or timeout is reached.
35
+ *
36
+ * @param options - Wait options
37
+ * @returns The tunnel info once available
38
+ * @throws TunnelTimeoutError if no tunnel becomes available within timeout
39
+ * @throws ApiError if the API request fails
40
+ */
41
+ async waitForTunnel(options) {
42
+ const timeout = options?.timeout ?? this.config.timeout;
43
+ const pollInterval = options?.pollInterval ?? 1e3;
44
+ this.abortController = new AbortController();
45
+ const startTime = Date.now();
46
+ while (Date.now() - startTime < timeout) {
47
+ try {
48
+ const response = await this.makeRequest(
49
+ `/api/agent/tunnels/wait?timeout=${Math.min(pollInterval * 5, timeout - (Date.now() - startTime))}`,
50
+ { signal: this.abortController.signal }
51
+ );
52
+ if (response.ok) {
53
+ const data = await response.json();
54
+ if (data.tunnel) {
55
+ return this.parseTunnel(data.tunnel);
56
+ }
57
+ } else if (response.status === 408) {
58
+ } else {
59
+ const error = await response.json().catch(() => ({ code: "UNKNOWN", message: "Request failed" }));
60
+ throw new ApiError(response.status, error.code, error.message);
61
+ }
62
+ } catch (err) {
63
+ if (err instanceof ApiError) throw err;
64
+ if (err.name === "AbortError") {
65
+ throw new Error("Wait cancelled");
66
+ }
67
+ }
68
+ await this.sleep(pollInterval);
69
+ }
70
+ throw new TunnelTimeoutError(timeout);
71
+ }
72
+ /**
73
+ * List all active tunnels for this bridge key
74
+ *
75
+ * @returns Array of active tunnels
76
+ * @throws ApiError if the API request fails
77
+ */
78
+ async listTunnels() {
79
+ const response = await this.makeRequest("/api/agent/tunnels");
80
+ if (!response.ok) {
81
+ const error = await response.json().catch(() => ({ code: "UNKNOWN", message: "Request failed" }));
82
+ throw new ApiError(response.status, error.code, error.message);
83
+ }
84
+ const data = await response.json();
85
+ return (data.tunnels || []).map((t) => this.parseTunnel(t));
86
+ }
87
+ /**
88
+ * Disconnect and clean up
89
+ *
90
+ * Cancels any pending waitForTunnel calls.
91
+ */
92
+ async disconnect() {
93
+ if (this.abortController) {
94
+ this.abortController.abort();
95
+ this.abortController = null;
96
+ }
97
+ }
98
+ /**
99
+ * Make an authenticated API request
100
+ */
101
+ async makeRequest(path, options = {}) {
102
+ const url = `${this.config.apiUrl}${path}`;
103
+ return fetch(url, {
104
+ ...options,
105
+ headers: {
106
+ "Authorization": `Bearer ${this.config.key}`,
107
+ "Content-Type": "application/json",
108
+ ...options.headers
109
+ }
110
+ });
111
+ }
112
+ /**
113
+ * Parse tunnel response into AgentTunnel
114
+ */
115
+ parseTunnel(data) {
116
+ return {
117
+ tunnelId: data.tunnelId || data.id,
118
+ subdomain: data.subdomain,
119
+ url: data.url,
120
+ localPort: data.localPort,
121
+ createdAt: new Date(data.createdAt),
122
+ expiresAt: new Date(data.expiresAt)
123
+ };
124
+ }
125
+ /**
126
+ * Sleep helper
127
+ */
128
+ sleep(ms) {
129
+ return new Promise((resolve) => setTimeout(resolve, ms));
130
+ }
131
+ };
132
+ export {
133
+ ApiError,
134
+ LivePortAgent,
135
+ TunnelTimeoutError
136
+ };
137
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * LivePort Agent SDK\n *\n * TypeScript SDK for AI agents to wait for and access localhost tunnels.\n */\n\nimport type { Tunnel } from \"@liveport/shared\";\n\nexport interface LivePortAgentConfig {\n /** Bridge key for authentication */\n key: string;\n /** API base URL (default: https://app.liveport.dev) */\n apiUrl?: string;\n /** Default timeout in milliseconds (default: 30000) */\n timeout?: number;\n}\n\nexport interface WaitForTunnelOptions {\n /** Timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Poll interval in milliseconds (default: 1000) */\n pollInterval?: number;\n}\n\nexport interface AgentTunnel {\n tunnelId: string;\n subdomain: string;\n url: string;\n localPort: number;\n createdAt: Date;\n expiresAt: Date;\n}\n\n/** Error thrown when tunnel wait times out */\nexport class TunnelTimeoutError extends Error {\n constructor(timeout: number) {\n super(`Tunnel not available within ${timeout}ms timeout`);\n this.name = \"TunnelTimeoutError\";\n }\n}\n\n/** Error thrown when API request fails */\nexport class ApiError extends Error {\n public readonly statusCode: number;\n public readonly code: string;\n\n constructor(statusCode: number, code: string, message: string) {\n super(message);\n this.name = \"ApiError\";\n this.statusCode = statusCode;\n this.code = code;\n }\n}\n\n/**\n * LivePort Agent SDK\n *\n * Allows AI agents to wait for and access localhost tunnels.\n *\n * @example\n * ```typescript\n * import { LivePortAgent } from '@liveport/agent-sdk';\n *\n * const agent = new LivePortAgent({\n * key: process.env.LIVEPORT_BRIDGE_KEY!\n * });\n *\n * // Wait for a tunnel to become available\n * const tunnel = await agent.waitForTunnel({ timeout: 30000 });\n * console.log(`Testing at: ${tunnel.url}`);\n *\n * // Run your tests against tunnel.url\n *\n * // Clean up when done\n * await agent.disconnect();\n * ```\n */\nexport class LivePortAgent {\n private config: Required<LivePortAgentConfig>;\n private abortController: AbortController | null = null;\n\n constructor(config: LivePortAgentConfig) {\n if (!config.key) {\n throw new Error(\"Bridge key is required\");\n }\n\n this.config = {\n key: config.key,\n apiUrl: config.apiUrl || \"https://app.liveport.dev\",\n timeout: config.timeout || 30000,\n };\n }\n\n /**\n * Wait for a tunnel to become available\n *\n * Long-polls the API until a tunnel is ready or timeout is reached.\n *\n * @param options - Wait options\n * @returns The tunnel info once available\n * @throws TunnelTimeoutError if no tunnel becomes available within timeout\n * @throws ApiError if the API request fails\n */\n async waitForTunnel(options?: WaitForTunnelOptions): Promise<AgentTunnel> {\n const timeout = options?.timeout ?? this.config.timeout;\n const pollInterval = options?.pollInterval ?? 1000;\n\n this.abortController = new AbortController();\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeout) {\n try {\n const response = await this.makeRequest(\n `/api/agent/tunnels/wait?timeout=${Math.min(pollInterval * 5, timeout - (Date.now() - startTime))}`,\n { signal: this.abortController.signal }\n );\n\n if (response.ok) {\n const data = await response.json() as { tunnel?: Record<string, unknown> };\n if (data.tunnel) {\n return this.parseTunnel(data.tunnel);\n }\n } else if (response.status === 408) {\n // Timeout from server, continue polling\n } else {\n const error = await response.json().catch(() => ({ code: \"UNKNOWN\", message: \"Request failed\" })) as { code: string; message: string };\n throw new ApiError(response.status, error.code, error.message);\n }\n } catch (err) {\n if (err instanceof ApiError) throw err;\n if ((err as Error).name === \"AbortError\") {\n throw new Error(\"Wait cancelled\");\n }\n // Network error, wait and retry\n }\n\n // Wait before next poll\n await this.sleep(pollInterval);\n }\n\n throw new TunnelTimeoutError(timeout);\n }\n\n /**\n * List all active tunnels for this bridge key\n *\n * @returns Array of active tunnels\n * @throws ApiError if the API request fails\n */\n async listTunnels(): Promise<AgentTunnel[]> {\n const response = await this.makeRequest(\"/api/agent/tunnels\");\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ code: \"UNKNOWN\", message: \"Request failed\" })) as { code: string; message: string };\n throw new ApiError(response.status, error.code, error.message);\n }\n\n const data = await response.json() as { tunnels?: Record<string, unknown>[] };\n return (data.tunnels || []).map((t) => this.parseTunnel(t));\n }\n\n /**\n * Disconnect and clean up\n *\n * Cancels any pending waitForTunnel calls.\n */\n async disconnect(): Promise<void> {\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n }\n\n /**\n * Make an authenticated API request\n */\n private async makeRequest(\n path: string,\n options: RequestInit = {}\n ): Promise<Response> {\n const url = `${this.config.apiUrl}${path}`;\n\n return fetch(url, {\n ...options,\n headers: {\n \"Authorization\": `Bearer ${this.config.key}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n }\n\n /**\n * Parse tunnel response into AgentTunnel\n */\n private parseTunnel(data: Record<string, unknown>): AgentTunnel {\n return {\n tunnelId: data.tunnelId as string || data.id as string,\n subdomain: data.subdomain as string,\n url: data.url as string,\n localPort: data.localPort as number,\n createdAt: new Date(data.createdAt as string),\n expiresAt: new Date(data.expiresAt as string),\n };\n }\n\n /**\n * Sleep helper\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n// Re-export types\nexport type { Tunnel } from \"@liveport/shared\";\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@liveport/agent-sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for AI agents to access LivePort tunnels",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "lint": "eslint src/",
23
+ "test": "vitest run --passWithNoTests",
24
+ "test:watch": "vitest",
25
+ "clean": "rm -rf dist",
26
+ "prepublishOnly": "pnpm build"
27
+ },
28
+ "keywords": [
29
+ "sdk",
30
+ "ai-agents",
31
+ "tunnel",
32
+ "localhost",
33
+ "liveport",
34
+ "testing",
35
+ "claude",
36
+ "gpt",
37
+ "automation"
38
+ ],
39
+ "author": "LivePort",
40
+ "license": "MIT",
41
+ "homepage": "https://liveport.dev",
42
+ "bugs": {
43
+ "url": "https://github.com/dundas/liveport/issues"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/dundas/liveport.git",
48
+ "directory": "packages/agent-sdk"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^20.10.0",
58
+ "tsup": "^8.0.0",
59
+ "typescript": "^5.3.0",
60
+ "vitest": "^2.0.0"
61
+ },
62
+ "dependencies": {
63
+ "@liveport/shared": "workspace:*"
64
+ }
65
+ }