instavm 0.2.2 → 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)
package/dist/index.d.mts CHANGED
@@ -46,7 +46,7 @@ declare class HTTPClient {
46
46
  */
47
47
  post<T = any>(url: string, data?: any, headers?: Record<string, string>): Promise<T>;
48
48
  /**
49
- * POST request for code execution (X-API-Key header is automatically added)
49
+ * POST request for code execution (uses X-API-Key header like Python client)
50
50
  */
51
51
  postExecution<T = any>(url: string, data: any, headers?: Record<string, string>): Promise<T>;
52
52
  /**
@@ -61,6 +61,10 @@ declare class HTTPClient {
61
61
  * DELETE request
62
62
  */
63
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>;
64
68
  }
65
69
 
66
70
  interface BrowserSessionOptions {
@@ -146,6 +150,34 @@ interface ExtractOptions {
146
150
  attributes?: string[];
147
151
  maxResults?: number;
148
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
+ }
149
181
 
150
182
  /**
151
183
  * Browser session for automation
@@ -183,6 +215,32 @@ declare class BrowserSession extends EventEmitter {
183
215
  * Extract elements from the page
184
216
  */
185
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>;
186
244
  /**
187
245
  * Wait for a condition
188
246
  */
@@ -280,6 +338,15 @@ interface UsageStats {
280
338
  quotaRemaining: number;
281
339
  quotaLimit: number;
282
340
  }
341
+ interface DownloadOptions {
342
+ sessionId?: string;
343
+ }
344
+ interface DownloadResult {
345
+ success: boolean;
346
+ filename: string;
347
+ content: Buffer;
348
+ size: number;
349
+ }
283
350
 
284
351
  interface InstaVMOptions {
285
352
  baseURL?: string;
@@ -319,6 +386,10 @@ declare class InstaVM {
319
386
  * Get usage statistics for a session
320
387
  */
321
388
  getUsage(sessionId?: string): Promise<UsageStats>;
389
+ /**
390
+ * Download a file from the remote VM
391
+ */
392
+ download(filename: string, options?: DownloadOptions): Promise<DownloadResult>;
322
393
  /**
323
394
  * Get the current session ID
324
395
  */
@@ -421,4 +492,4 @@ declare class ElementNotFoundError extends BrowserError {
421
492
  constructor(message?: string, selector?: string, options?: any);
422
493
  }
423
494
 
424
- export { type ApiResponse, type AsyncExecutionResult, AuthenticationError, BrowserError, BrowserInteractionError, BrowserManager, BrowserNavigationError, BrowserSession, BrowserSessionError, type BrowserSessionInfo, type BrowserSessionOptions, BrowserTimeoutError, type ClickOptions, ElementNotFoundError, type ExecuteOptions, ExecutionError, type ExecutionResult, type ExtractOptions, type ExtractedElement, type FileUpload, type FillOptions, type HttpClientConfig, InstaVM, InstaVMError, type InstaVMOptions, 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -46,7 +46,7 @@ declare class HTTPClient {
46
46
  */
47
47
  post<T = any>(url: string, data?: any, headers?: Record<string, string>): Promise<T>;
48
48
  /**
49
- * POST request for code execution (X-API-Key header is automatically added)
49
+ * POST request for code execution (uses X-API-Key header like Python client)
50
50
  */
51
51
  postExecution<T = any>(url: string, data: any, headers?: Record<string, string>): Promise<T>;
52
52
  /**
@@ -61,6 +61,10 @@ declare class HTTPClient {
61
61
  * DELETE request
62
62
  */
63
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>;
64
68
  }
65
69
 
66
70
  interface BrowserSessionOptions {
@@ -146,6 +150,34 @@ interface ExtractOptions {
146
150
  attributes?: string[];
147
151
  maxResults?: number;
148
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
+ }
149
181
 
150
182
  /**
151
183
  * Browser session for automation
@@ -183,6 +215,32 @@ declare class BrowserSession extends EventEmitter {
183
215
  * Extract elements from the page
184
216
  */
185
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>;
186
244
  /**
187
245
  * Wait for a condition
188
246
  */
@@ -280,6 +338,15 @@ interface UsageStats {
280
338
  quotaRemaining: number;
281
339
  quotaLimit: number;
282
340
  }
341
+ interface DownloadOptions {
342
+ sessionId?: string;
343
+ }
344
+ interface DownloadResult {
345
+ success: boolean;
346
+ filename: string;
347
+ content: Buffer;
348
+ size: number;
349
+ }
283
350
 
284
351
  interface InstaVMOptions {
285
352
  baseURL?: string;
@@ -319,6 +386,10 @@ declare class InstaVM {
319
386
  * Get usage statistics for a session
320
387
  */
321
388
  getUsage(sessionId?: string): Promise<UsageStats>;
389
+ /**
390
+ * Download a file from the remote VM
391
+ */
392
+ download(filename: string, options?: DownloadOptions): Promise<DownloadResult>;
322
393
  /**
323
394
  * Get the current session ID
324
395
  */
@@ -421,4 +492,4 @@ declare class ElementNotFoundError extends BrowserError {
421
492
  constructor(message?: string, selector?: string, options?: any);
422
493
  }
423
494
 
424
- export { type ApiResponse, type AsyncExecutionResult, AuthenticationError, BrowserError, BrowserInteractionError, BrowserManager, BrowserNavigationError, BrowserSession, BrowserSessionError, type BrowserSessionInfo, type BrowserSessionOptions, BrowserTimeoutError, type ClickOptions, ElementNotFoundError, type ExecuteOptions, ExecutionError, type ExecutionResult, type ExtractOptions, type ExtractedElement, type FileUpload, type FillOptions, type HttpClientConfig, InstaVM, InstaVMError, type InstaVMOptions, 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 };
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 };
package/dist/index.js CHANGED
@@ -6,9 +6,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __commonJS = (cb, mod) => function __require() {
10
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
- };
12
9
  var __export = (target, all) => {
13
10
  for (var name in all)
14
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -31,92 +28,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
28
  ));
32
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
30
 
34
- // package.json
35
- var require_package = __commonJS({
36
- "package.json"(exports2, module2) {
37
- module2.exports = {
38
- name: "instavm",
39
- version: "0.2.2",
40
- description: "Official JavaScript SDK for InstaVM API",
41
- main: "dist/index.js",
42
- module: "dist/index.mjs",
43
- types: "dist/index.d.ts",
44
- exports: {
45
- ".": {
46
- types: "./dist/index.d.ts",
47
- import: "./dist/index.mjs",
48
- require: "./dist/index.js"
49
- },
50
- "./integrations/openai": {
51
- types: "./dist/integrations/openai.d.ts",
52
- import: "./dist/integrations/openai.mjs",
53
- require: "./dist/integrations/openai.js"
54
- }
55
- },
56
- files: [
57
- "dist"
58
- ],
59
- scripts: {
60
- dev: "tsup --watch",
61
- build: "tsup",
62
- "type-check": "tsc --noEmit",
63
- test: "vitest",
64
- "test:unit": "vitest run tests/unit",
65
- "test:integration": "vitest run tests/integration",
66
- "test:watch": "vitest --watch",
67
- "test:coverage": "vitest --coverage",
68
- lint: "eslint src --ext .ts,.tsx",
69
- "lint:fix": "eslint src --ext .ts,.tsx --fix",
70
- format: "prettier --write src/**/*.ts",
71
- prepublishOnly: "npm run build && npm run test:unit",
72
- clean: "rm -rf dist"
73
- },
74
- keywords: [
75
- "instavm",
76
- "api",
77
- "sdk",
78
- "browser-automation",
79
- "code-execution",
80
- "microvm",
81
- "cloud"
82
- ],
83
- author: "InstaVM",
84
- license: "UNLICENSED",
85
- repository: {
86
- type: "git",
87
- url: "https://github.com/instavm/js.git"
88
- },
89
- bugs: {
90
- url: "https://github.com/instavm/js/issues"
91
- },
92
- homepage: "https://github.com/instavm/js#readme",
93
- engines: {
94
- node: ">=16.0.0"
95
- },
96
- dependencies: {
97
- axios: "^1.7.9",
98
- eventemitter3: "^5.0.1",
99
- "form-data": "^4.0.1",
100
- ws: "^8.18.0"
101
- },
102
- devDependencies: {
103
- "@types/node": "^22.10.5",
104
- "@types/ws": "^8.5.13",
105
- "@typescript-eslint/eslint-plugin": "^8.18.2",
106
- "@typescript-eslint/parser": "^8.18.2",
107
- "@vitest/coverage-v8": "^2.1.8",
108
- eslint: "^9.18.0",
109
- "eslint-config-prettier": "^9.1.0",
110
- "eslint-plugin-prettier": "^5.2.1",
111
- prettier: "^3.4.2",
112
- tsup: "^8.3.5",
113
- typescript: "^5.7.2",
114
- vitest: "^2.1.8"
115
- }
116
- };
117
- }
118
- });
119
-
120
31
  // src/index.ts
121
32
  var index_exports = {};
122
33
  __export(index_exports, {
@@ -262,14 +173,6 @@ async function withRetry(fn, options) {
262
173
  }
263
174
 
264
175
  // src/client/HTTPClient.ts
265
- function getPackageVersion() {
266
- try {
267
- const packageJson = require_package();
268
- return packageJson.version || "0.1.0";
269
- } catch {
270
- return "0.1.0";
271
- }
272
- }
273
176
  var HTTPClient = class {
274
177
  get apiKey() {
275
178
  return this.config.apiKey;
@@ -281,7 +184,7 @@ var HTTPClient = class {
281
184
  timeout: config.timeout,
282
185
  headers: {
283
186
  "Content-Type": "application/json",
284
- "User-Agent": `instavm-js-sdk/${getPackageVersion()}`
187
+ "User-Agent": "instavm-js-sdk/0.1.0"
285
188
  }
286
189
  });
287
190
  this.setupInterceptors();
@@ -289,7 +192,7 @@ var HTTPClient = class {
289
192
  setupInterceptors() {
290
193
  this.client.interceptors.request.use(
291
194
  (config) => {
292
- if (!config.headers["X-API-Key"] && !config.url?.includes("/v1/sessions/session")) {
195
+ if (config.url?.includes("/browser/")) {
293
196
  config.headers["X-API-Key"] = this.config.apiKey;
294
197
  }
295
198
  return config;
@@ -378,14 +281,18 @@ var HTTPClient = class {
378
281
  });
379
282
  }
380
283
  /**
381
- * POST request for code execution (X-API-Key header is automatically added)
284
+ * POST request for code execution (uses X-API-Key header like Python client)
382
285
  */
383
286
  async postExecution(url, data, headers) {
287
+ const requestHeaders = {
288
+ "X-API-Key": this.config.apiKey,
289
+ ...headers
290
+ };
384
291
  return this.request({
385
292
  method: "POST",
386
293
  url,
387
294
  data,
388
- headers
295
+ headers: requestHeaders
389
296
  });
390
297
  }
391
298
  /**
@@ -424,6 +331,31 @@ var HTTPClient = class {
424
331
  headers
425
332
  });
426
333
  }
334
+ /**
335
+ * POST request that returns raw binary data (for file downloads)
336
+ */
337
+ async postRaw(url, data, headers) {
338
+ const requestHeaders = {
339
+ "X-API-Key": this.config.apiKey,
340
+ ...headers
341
+ };
342
+ const axiosConfig = {
343
+ method: "POST",
344
+ url,
345
+ data,
346
+ headers: requestHeaders,
347
+ responseType: "arraybuffer",
348
+ timeout: this.config.timeout
349
+ };
350
+ const makeRequest = async () => {
351
+ const response = await this.client.request(axiosConfig);
352
+ return response.data;
353
+ };
354
+ return withRetry(makeRequest, {
355
+ retries: this.config.maxRetries,
356
+ retryDelay: this.config.retryDelay
357
+ });
358
+ }
427
359
  };
428
360
 
429
361
  // src/client/BrowserSession.ts
@@ -650,6 +582,59 @@ var BrowserSession = class extends import_eventemitter3.EventEmitter {
650
582
  );
651
583
  }
652
584
  }
585
+ /**
586
+ * Extract LLM-friendly content from the current page
587
+ *
588
+ * Returns clean article content, interactive elements, and content anchors
589
+ * for intelligent browser automation with LLMs.
590
+ *
591
+ * @param options - Content extraction options
592
+ * @returns Extracted content with readable text, interactive elements, and content anchors
593
+ *
594
+ * @example
595
+ * ```typescript
596
+ * const content = await session.extractContent();
597
+ *
598
+ * // LLM reads clean content
599
+ * const article = content.readableContent.content;
600
+ *
601
+ * // LLM finds "Sign Up" in content and uses anchors to get selector
602
+ * const signUpAnchor = content.contentAnchors?.find(
603
+ * anchor => anchor.text.toLowerCase().includes('sign up')
604
+ * );
605
+ * if (signUpAnchor) {
606
+ * await session.click(signUpAnchor.selector);
607
+ * }
608
+ * ```
609
+ */
610
+ async extractContent(options = {}) {
611
+ this.ensureActive();
612
+ const requestData = {
613
+ session_id: this.sessionId,
614
+ include_interactive: options.includeInteractive !== false,
615
+ include_anchors: options.includeAnchors !== false,
616
+ max_anchors: options.maxAnchors ?? 50
617
+ };
618
+ try {
619
+ const response = await this.httpClient.post(
620
+ "/v1/browser/interactions/content",
621
+ requestData
622
+ );
623
+ return {
624
+ readableContent: {
625
+ ...response.readable_content || {},
626
+ content: response.readable_content?.content || ""
627
+ },
628
+ interactiveElements: response.interactive_elements,
629
+ contentAnchors: response.content_anchors
630
+ };
631
+ } catch (error) {
632
+ throw new BrowserInteractionError(
633
+ `Content extraction failed: ${getErrorMessage(error)}`,
634
+ { cause: error }
635
+ );
636
+ }
637
+ }
653
638
  /**
654
639
  * Wait for a condition
655
640
  */
@@ -1041,6 +1026,34 @@ var InstaVM = class {
1041
1026
  );
1042
1027
  }
1043
1028
  }
1029
+ /**
1030
+ * Download a file from the remote VM
1031
+ */
1032
+ async download(filename, options = {}) {
1033
+ const targetSessionId = options.sessionId || this._sessionId;
1034
+ if (!targetSessionId) {
1035
+ throw new SessionError("No active session to download from");
1036
+ }
1037
+ try {
1038
+ const response = await this.httpClient.postRaw("/download", {
1039
+ filename,
1040
+ session_id: targetSessionId
1041
+ });
1042
+ const content = Buffer.from(response);
1043
+ return {
1044
+ success: true,
1045
+ filename,
1046
+ content,
1047
+ size: content.length
1048
+ };
1049
+ } catch (error) {
1050
+ const errorMessage = error instanceof Error ? error.message : String(error);
1051
+ throw new SessionError(
1052
+ `File download failed: ${errorMessage}`,
1053
+ { cause: error }
1054
+ );
1055
+ }
1056
+ }
1044
1057
  /**
1045
1058
  * Get the current session ID
1046
1059
  */