instavm 0.2.1 → 0.3.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 CHANGED
@@ -75,6 +75,29 @@ const execution = await client.execute('python /remote/path/script.py', {
75
75
  console.log(execution.output);
76
76
  ```
77
77
 
78
+ ### File Download
79
+
80
+ ```typescript
81
+ import { InstaVM } from 'instavm';
82
+ import fs from 'fs';
83
+
84
+ const client = new InstaVM('your_api_key');
85
+
86
+ // Create a file in the remote environment
87
+ await client.execute(`
88
+ import pandas as pd
89
+ df = pd.DataFrame({'name': ['Alice', 'Bob'], 'age': [25, 30]})
90
+ df.to_csv('data.csv', index=False)
91
+ `);
92
+
93
+ // Download the file
94
+ const result = await client.download('data.csv');
95
+ console.log(`Downloaded ${result.size} bytes`);
96
+
97
+ // Save to local file
98
+ fs.writeFileSync('local-data.csv', result.content);
99
+ ```
100
+
78
101
  ### Error Handling
79
102
 
80
103
  ```typescript
@@ -432,6 +455,39 @@ console.log('Articles:', articles);
432
455
  console.log('Form fields:', formData);
433
456
  ```
434
457
 
458
+ ### LLM-Friendly Content Extraction
459
+
460
+ ```typescript
461
+ // Extract clean, LLM-optimized content from a webpage
462
+ const content = await session.extractContent({
463
+ includeInteractive: true, // Include clickable/typeable elements
464
+ includeAnchors: true, // Include content-to-selector mappings
465
+ maxAnchors: 50 // Limit number of anchors
466
+ });
467
+
468
+ // Get clean article text (no ads, no navigation, no scripts)
469
+ console.log('Title:', content.readableContent.title);
470
+ console.log('Article:', content.readableContent.content);
471
+ console.log('Author:', content.readableContent.byline);
472
+
473
+ // Find interactive elements (buttons, links, inputs)
474
+ const loginButton = content.interactiveElements?.find(
475
+ el => el.text?.toLowerCase().includes('login')
476
+ );
477
+ if (loginButton) {
478
+ await session.click(loginButton.selector);
479
+ }
480
+
481
+ // Use content anchors to map text to selectors
482
+ // Perfect for LLM agents that need to "read then click"
483
+ const signupLink = content.contentAnchors?.find(
484
+ anchor => anchor.text.toLowerCase().includes('sign up')
485
+ );
486
+ if (signupLink) {
487
+ await session.click(signupLink.selector);
488
+ }
489
+ ```
490
+
435
491
  ## Error Handling Reference
436
492
 
437
493
  ### Error Types
@@ -495,7 +551,8 @@ class InstaVM {
495
551
 
496
552
  // File operations
497
553
  upload(files: FileUpload[], options?: UploadOptions): Promise<UploadResult>
498
-
554
+ download(filename: string, options?: DownloadOptions): Promise<DownloadResult>
555
+
499
556
  // Session management
500
557
  createSession(): Promise<string>
501
558
  closeSession(sessionId?: string): Promise<void>
@@ -565,7 +622,8 @@ class BrowserSession extends EventEmitter {
565
622
  // Data extraction
566
623
  screenshot(options?: ScreenshotOptions): Promise<string>
567
624
  extractElements(selector: string, attributes?: string[]): Promise<ExtractedElement[]>
568
-
625
+ extractContent(options?: ExtractContentOptions): Promise<ExtractedContent>
626
+
569
627
  // Utilities
570
628
  wait(condition: WaitCondition, timeout?: number): Promise<void>
571
629
  close(): Promise<void>
@@ -750,11 +808,14 @@ main().catch(console.error);
750
808
  # Install dependencies
751
809
  npm install
752
810
 
753
- # Run unit tests
754
- npm test
811
+ # Run unit tests (no API key required)
812
+ npm run test:unit
755
813
 
756
814
  # Run integration tests (requires API key)
757
- INSTAVM_API_KEY=your_key npm run test:integration
815
+ INSTAVM_API_KEY=your_api_key npm run test:integration
816
+
817
+ # Run all tests
818
+ npm test
758
819
 
759
820
  # Build the package
760
821
  npm run build
@@ -763,6 +824,8 @@ npm run build
763
824
  npm run type-check
764
825
  ```
765
826
 
827
+ **Note:** Integration tests require a valid InstaVM API key. Set the `INSTAVM_API_KEY` environment variable before running integration tests. Unit tests do not require an API key.
828
+
766
829
  ### Contributing
767
830
 
768
831
  This is an official SDK. For issues and feature requests, please use the GitHub repository.
@@ -782,6 +845,22 @@ All rights reserved. No redistribution or modification permitted.
782
845
 
783
846
  ## Changelog
784
847
 
848
+ ### Version 0.3.0
849
+
850
+ - ✅ **NEW**: File download functionality - Download files from remote VM
851
+ - ✅ **NEW**: LLM-friendly content extraction - Extract clean, readable content with interactive element mapping
852
+ - ✅ Enhanced browser automation with content anchors for intelligent LLM agents
853
+ - ✅ Full API parity with Python SDK
854
+
855
+ ### Version 0.2.1
856
+
857
+ - ✅ Bug fixes and improvements
858
+
859
+ ### Version 0.2.0
860
+
861
+ - ✅ Enhanced session management
862
+ - ✅ Improved error handling
863
+
785
864
  ### Version 0.1.0
786
865
 
787
866
  - ✅ Code execution fully functional (Python, Bash)
@@ -0,0 +1,495 @@
1
+ import FormData from 'form-data';
2
+ import { EventEmitter } from 'eventemitter3';
3
+
4
+ interface ApiResponse<T = any> {
5
+ success?: boolean;
6
+ data?: T;
7
+ error?: string;
8
+ message?: string;
9
+ }
10
+ interface HttpClientConfig {
11
+ baseURL: string;
12
+ timeout: number;
13
+ maxRetries: number;
14
+ retryDelay: number;
15
+ apiKey: string;
16
+ }
17
+ interface RequestConfig {
18
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
19
+ url: string;
20
+ headers?: Record<string, string>;
21
+ data?: any;
22
+ params?: Record<string, any>;
23
+ timeout?: number;
24
+ }
25
+ interface RetryConfig {
26
+ retries: number;
27
+ retryDelay: number;
28
+ retryCondition?: (error: any) => boolean;
29
+ }
30
+
31
+ /**
32
+ * HTTP client with authentication, retry logic, and error handling
33
+ */
34
+ declare class HTTPClient {
35
+ private client;
36
+ private config;
37
+ get apiKey(): string;
38
+ constructor(config: HttpClientConfig);
39
+ private setupInterceptors;
40
+ /**
41
+ * Make an HTTP request with retry logic
42
+ */
43
+ request<T = any>(requestConfig: RequestConfig): Promise<T>;
44
+ /**
45
+ * POST request with JSON body
46
+ */
47
+ post<T = any>(url: string, data?: any, headers?: Record<string, string>): Promise<T>;
48
+ /**
49
+ * POST request for code execution (uses X-API-Key header like Python client)
50
+ */
51
+ postExecution<T = any>(url: string, data: any, headers?: Record<string, string>): Promise<T>;
52
+ /**
53
+ * POST request with form data (for file uploads)
54
+ */
55
+ postFormData<T = any>(url: string, formData: FormData, headers?: Record<string, string>): Promise<T>;
56
+ /**
57
+ * GET request
58
+ */
59
+ get<T = any>(url: string, params?: Record<string, any>, headers?: Record<string, string>): Promise<T>;
60
+ /**
61
+ * DELETE request
62
+ */
63
+ delete<T = any>(url: string, headers?: Record<string, string>): Promise<T>;
64
+ /**
65
+ * POST request that returns raw binary data (for file downloads)
66
+ */
67
+ postRaw(url: string, data?: any, headers?: Record<string, string>): Promise<ArrayBuffer>;
68
+ }
69
+
70
+ interface BrowserSessionOptions {
71
+ viewportWidth?: number;
72
+ viewportHeight?: number;
73
+ userAgent?: string;
74
+ }
75
+ interface BrowserSessionInfo {
76
+ sessionId: string;
77
+ status: 'active' | 'inactive' | 'closed';
78
+ createdAt: string;
79
+ viewportWidth: number;
80
+ viewportHeight: number;
81
+ userAgent?: string;
82
+ }
83
+ interface NavigateOptions {
84
+ waitTimeout?: number;
85
+ waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';
86
+ }
87
+ interface NavigationResult {
88
+ success: boolean;
89
+ url: string;
90
+ title?: string;
91
+ status?: number;
92
+ }
93
+ interface ClickOptions {
94
+ timeout?: number;
95
+ button?: 'left' | 'right' | 'middle';
96
+ clickCount?: number;
97
+ force?: boolean;
98
+ }
99
+ interface TypeOptions {
100
+ timeout?: number;
101
+ delay?: number;
102
+ clear?: boolean;
103
+ }
104
+ interface FillOptions {
105
+ timeout?: number;
106
+ force?: boolean;
107
+ }
108
+ interface ScrollOptions {
109
+ x?: number;
110
+ y?: number;
111
+ behavior?: 'auto' | 'smooth';
112
+ }
113
+ interface ScreenshotOptions {
114
+ fullPage?: boolean;
115
+ format?: 'png' | 'jpeg';
116
+ quality?: number;
117
+ clip?: {
118
+ x: number;
119
+ y: number;
120
+ width: number;
121
+ height: number;
122
+ };
123
+ }
124
+ type WaitCondition = {
125
+ type: 'visible';
126
+ selector: string;
127
+ } | {
128
+ type: 'hidden';
129
+ selector: string;
130
+ } | {
131
+ type: 'attached';
132
+ selector: string;
133
+ } | {
134
+ type: 'detached';
135
+ selector: string;
136
+ } | {
137
+ type: 'timeout';
138
+ ms: number;
139
+ };
140
+ interface ExtractedElement {
141
+ text?: string;
142
+ href?: string;
143
+ src?: string;
144
+ class?: string;
145
+ id?: string;
146
+ tagName?: string;
147
+ [attribute: string]: any;
148
+ }
149
+ interface ExtractOptions {
150
+ attributes?: string[];
151
+ maxResults?: number;
152
+ }
153
+ interface ExtractContentOptions {
154
+ includeInteractive?: boolean;
155
+ includeAnchors?: boolean;
156
+ maxAnchors?: number;
157
+ }
158
+ interface InteractiveElement {
159
+ type: 'button' | 'link' | 'input' | 'select' | 'textarea';
160
+ selector: string;
161
+ text?: string;
162
+ label?: string;
163
+ placeholder?: string;
164
+ value?: string;
165
+ }
166
+ interface ContentAnchor {
167
+ text: string;
168
+ selector: string;
169
+ }
170
+ interface ExtractedContent {
171
+ readableContent: {
172
+ content: string;
173
+ title?: string;
174
+ byline?: string;
175
+ excerpt?: string;
176
+ siteName?: string;
177
+ };
178
+ interactiveElements?: InteractiveElement[];
179
+ contentAnchors?: ContentAnchor[];
180
+ }
181
+
182
+ /**
183
+ * Browser session for automation
184
+ */
185
+ declare class BrowserSession extends EventEmitter {
186
+ readonly sessionId: string;
187
+ private httpClient;
188
+ private _isActive;
189
+ constructor(sessionId: string, httpClient: HTTPClient);
190
+ /**
191
+ * Navigate to a URL
192
+ */
193
+ navigate(url: string, options?: NavigateOptions): Promise<NavigationResult>;
194
+ /**
195
+ * Click an element
196
+ */
197
+ click(selector: string, options?: ClickOptions): Promise<void>;
198
+ /**
199
+ * Type text into an element
200
+ */
201
+ type(selector: string, text: string, options?: TypeOptions): Promise<void>;
202
+ /**
203
+ * Fill a form field
204
+ */
205
+ fill(selector: string, value: string, options?: FillOptions): Promise<void>;
206
+ /**
207
+ * Scroll the page
208
+ */
209
+ scroll(options?: ScrollOptions): Promise<void>;
210
+ /**
211
+ * Take a screenshot
212
+ */
213
+ screenshot(options?: ScreenshotOptions): Promise<string>;
214
+ /**
215
+ * Extract elements from the page
216
+ */
217
+ extractElements(selector: string, attributes?: string[], options?: ExtractOptions): Promise<ExtractedElement[]>;
218
+ /**
219
+ * Extract LLM-friendly content from the current page
220
+ *
221
+ * Returns clean article content, interactive elements, and content anchors
222
+ * for intelligent browser automation with LLMs.
223
+ *
224
+ * @param options - Content extraction options
225
+ * @returns Extracted content with readable text, interactive elements, and content anchors
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * const content = await session.extractContent();
230
+ *
231
+ * // LLM reads clean content
232
+ * const article = content.readableContent.content;
233
+ *
234
+ * // LLM finds "Sign Up" in content and uses anchors to get selector
235
+ * const signUpAnchor = content.contentAnchors?.find(
236
+ * anchor => anchor.text.toLowerCase().includes('sign up')
237
+ * );
238
+ * if (signUpAnchor) {
239
+ * await session.click(signUpAnchor.selector);
240
+ * }
241
+ * ```
242
+ */
243
+ extractContent(options?: ExtractContentOptions): Promise<ExtractedContent>;
244
+ /**
245
+ * Wait for a condition
246
+ */
247
+ wait(condition: WaitCondition, timeout?: number): Promise<void>;
248
+ /**
249
+ * Close the browser session
250
+ */
251
+ close(): Promise<void>;
252
+ /**
253
+ * Check if session is active
254
+ */
255
+ get isActive(): boolean;
256
+ /**
257
+ * Ensure session is active before operations
258
+ */
259
+ private ensureActive;
260
+ }
261
+
262
+ /**
263
+ * Browser automation manager
264
+ */
265
+ declare class BrowserManager {
266
+ private httpClient;
267
+ private activeSessions;
268
+ constructor(httpClient: HTTPClient);
269
+ /**
270
+ * Create a new browser session
271
+ */
272
+ createSession(options?: BrowserSessionOptions): Promise<BrowserSession>;
273
+ /**
274
+ * Get information about a specific session
275
+ */
276
+ getSession(sessionId: string): Promise<BrowserSessionInfo>;
277
+ /**
278
+ * List all browser sessions
279
+ */
280
+ listSessions(): Promise<BrowserSessionInfo[]>;
281
+ /**
282
+ * Get a locally tracked session
283
+ */
284
+ getLocalSession(sessionId: string): BrowserSession | undefined;
285
+ /**
286
+ * Get all locally tracked sessions
287
+ */
288
+ getLocalSessions(): BrowserSession[];
289
+ /**
290
+ * Close all active sessions
291
+ */
292
+ closeAllSessions(): Promise<void>;
293
+ /**
294
+ * Clean up resources
295
+ */
296
+ dispose(): Promise<void>;
297
+ }
298
+
299
+ interface ExecuteOptions {
300
+ language?: 'python' | 'bash';
301
+ timeout?: number;
302
+ sessionId?: string;
303
+ }
304
+ interface ExecutionResult {
305
+ output: string;
306
+ success: boolean;
307
+ executionTime: number;
308
+ sessionId?: string;
309
+ error?: string;
310
+ }
311
+ interface AsyncExecutionResult {
312
+ taskId: string;
313
+ status: 'pending' | 'running' | 'completed' | 'failed';
314
+ output?: string;
315
+ success?: boolean;
316
+ executionTime?: number;
317
+ sessionId?: string;
318
+ error?: string;
319
+ }
320
+ interface FileUpload {
321
+ name: string;
322
+ content: Buffer | string;
323
+ path?: string;
324
+ }
325
+ interface UploadOptions {
326
+ sessionId?: string;
327
+ remotePath?: string;
328
+ recursive?: boolean;
329
+ }
330
+ interface UploadResult {
331
+ success: boolean;
332
+ files: string[];
333
+ message?: string;
334
+ }
335
+ interface UsageStats {
336
+ sessionsUsed: number;
337
+ executionTime: number;
338
+ quotaRemaining: number;
339
+ quotaLimit: number;
340
+ }
341
+ interface DownloadOptions {
342
+ sessionId?: string;
343
+ }
344
+ interface DownloadResult {
345
+ success: boolean;
346
+ filename: string;
347
+ content: Buffer;
348
+ size: number;
349
+ }
350
+
351
+ interface InstaVMOptions {
352
+ baseURL?: string;
353
+ timeout?: number;
354
+ maxRetries?: number;
355
+ retryDelay?: number;
356
+ }
357
+ /**
358
+ * Main InstaVM client class
359
+ */
360
+ declare class InstaVM {
361
+ private httpClient;
362
+ private _sessionId;
363
+ readonly browser: BrowserManager;
364
+ constructor(apiKey: string, options?: InstaVMOptions);
365
+ /**
366
+ * Execute code synchronously
367
+ */
368
+ execute(command: string, options?: ExecuteOptions): Promise<ExecutionResult>;
369
+ /**
370
+ * Execute code asynchronously
371
+ */
372
+ executeAsync(command: string, options?: ExecuteOptions): Promise<AsyncExecutionResult>;
373
+ /**
374
+ * Upload files to the execution environment
375
+ */
376
+ upload(files: FileUpload[], options?: UploadOptions): Promise<UploadResult>;
377
+ /**
378
+ * Create a new execution session
379
+ */
380
+ createSession(): Promise<string>;
381
+ /**
382
+ * Close a session
383
+ */
384
+ closeSession(sessionId?: string): Promise<void>;
385
+ /**
386
+ * Get usage statistics for a session
387
+ */
388
+ getUsage(sessionId?: string): Promise<UsageStats>;
389
+ /**
390
+ * Download a file from the remote VM
391
+ */
392
+ download(filename: string, options?: DownloadOptions): Promise<DownloadResult>;
393
+ /**
394
+ * Get the current session ID
395
+ */
396
+ get sessionId(): string | null;
397
+ /**
398
+ * Clean up resources
399
+ */
400
+ dispose(): Promise<void>;
401
+ }
402
+
403
+ /**
404
+ * Base error class for all InstaVM SDK errors
405
+ */
406
+ declare class InstaVMError extends Error {
407
+ readonly name: string;
408
+ readonly code?: string;
409
+ readonly statusCode?: number;
410
+ readonly response?: any;
411
+ constructor(message: string, options?: {
412
+ code?: string;
413
+ statusCode?: number;
414
+ response?: any;
415
+ cause?: Error;
416
+ });
417
+ }
418
+ /**
419
+ * Authentication-related errors (401, invalid API key, etc.)
420
+ */
421
+ declare class AuthenticationError extends InstaVMError {
422
+ constructor(message?: string, options?: any);
423
+ }
424
+ /**
425
+ * Rate limiting errors (429)
426
+ */
427
+ declare class RateLimitError extends InstaVMError {
428
+ readonly retryAfter?: number;
429
+ constructor(message?: string, retryAfter?: number, options?: any);
430
+ }
431
+ /**
432
+ * Quota/usage limit exceeded errors
433
+ */
434
+ declare class QuotaExceededError extends InstaVMError {
435
+ constructor(message?: string, options?: any);
436
+ }
437
+ /**
438
+ * Network-related errors (timeouts, connection issues, 5xx errors)
439
+ */
440
+ declare class NetworkError extends InstaVMError {
441
+ constructor(message?: string, options?: any);
442
+ }
443
+ /**
444
+ * Code execution errors
445
+ */
446
+ declare class ExecutionError extends InstaVMError {
447
+ readonly executionOutput?: string;
448
+ readonly executionTime?: number;
449
+ constructor(message: string, executionOutput?: string, executionTime?: number, options?: any);
450
+ }
451
+ /**
452
+ * Session management errors
453
+ */
454
+ declare class SessionError extends InstaVMError {
455
+ constructor(message?: string, options?: any);
456
+ }
457
+ /**
458
+ * Browser automation base error
459
+ */
460
+ declare class BrowserError extends InstaVMError {
461
+ constructor(message?: string, options?: any);
462
+ }
463
+ /**
464
+ * Browser session management errors
465
+ */
466
+ declare class BrowserSessionError extends BrowserError {
467
+ constructor(message?: string, options?: any);
468
+ }
469
+ /**
470
+ * Browser interaction errors (click, type, etc.)
471
+ */
472
+ declare class BrowserInteractionError extends BrowserError {
473
+ constructor(message?: string, options?: any);
474
+ }
475
+ /**
476
+ * Browser timeout errors
477
+ */
478
+ declare class BrowserTimeoutError extends BrowserError {
479
+ constructor(message?: string, options?: any);
480
+ }
481
+ /**
482
+ * Browser navigation errors
483
+ */
484
+ declare class BrowserNavigationError extends BrowserError {
485
+ constructor(message?: string, options?: any);
486
+ }
487
+ /**
488
+ * Element not found errors
489
+ */
490
+ declare class ElementNotFoundError extends BrowserError {
491
+ readonly selector?: string;
492
+ constructor(message?: string, selector?: string, options?: any);
493
+ }
494
+
495
+ export { type ApiResponse, type AsyncExecutionResult, AuthenticationError, BrowserError, BrowserInteractionError, BrowserManager, BrowserNavigationError, BrowserSession, BrowserSessionError, type BrowserSessionInfo, type BrowserSessionOptions, BrowserTimeoutError, type ClickOptions, type ContentAnchor, type DownloadOptions, type DownloadResult, ElementNotFoundError, type ExecuteOptions, ExecutionError, type ExecutionResult, type ExtractContentOptions, type ExtractOptions, type ExtractedContent, type ExtractedElement, type FileUpload, type FillOptions, type HttpClientConfig, InstaVM, InstaVMError, type InstaVMOptions, type InteractiveElement, type NavigateOptions, type NavigationResult, NetworkError, QuotaExceededError, RateLimitError, type RequestConfig, type RetryConfig, type ScreenshotOptions, type ScrollOptions, SessionError, type TypeOptions, type UploadOptions, type UploadResult, type UsageStats, type WaitCondition };