instavm 0.2.2 → 0.4.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/dist/index.mjs CHANGED
@@ -1,94 +1,4 @@
1
1
  "use strict";
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __commonJS = (cb, mod) => function __require() {
4
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5
- };
6
-
7
- // package.json
8
- var require_package = __commonJS({
9
- "package.json"(exports, module) {
10
- module.exports = {
11
- name: "instavm",
12
- version: "0.2.2",
13
- description: "Official JavaScript SDK for InstaVM API",
14
- main: "dist/index.js",
15
- module: "dist/index.mjs",
16
- types: "dist/index.d.ts",
17
- exports: {
18
- ".": {
19
- types: "./dist/index.d.ts",
20
- import: "./dist/index.mjs",
21
- require: "./dist/index.js"
22
- },
23
- "./integrations/openai": {
24
- types: "./dist/integrations/openai.d.ts",
25
- import: "./dist/integrations/openai.mjs",
26
- require: "./dist/integrations/openai.js"
27
- }
28
- },
29
- files: [
30
- "dist"
31
- ],
32
- scripts: {
33
- dev: "tsup --watch",
34
- build: "tsup",
35
- "type-check": "tsc --noEmit",
36
- test: "vitest",
37
- "test:unit": "vitest run tests/unit",
38
- "test:integration": "vitest run tests/integration",
39
- "test:watch": "vitest --watch",
40
- "test:coverage": "vitest --coverage",
41
- lint: "eslint src --ext .ts,.tsx",
42
- "lint:fix": "eslint src --ext .ts,.tsx --fix",
43
- format: "prettier --write src/**/*.ts",
44
- prepublishOnly: "npm run build && npm run test:unit",
45
- clean: "rm -rf dist"
46
- },
47
- keywords: [
48
- "instavm",
49
- "api",
50
- "sdk",
51
- "browser-automation",
52
- "code-execution",
53
- "microvm",
54
- "cloud"
55
- ],
56
- author: "InstaVM",
57
- license: "UNLICENSED",
58
- repository: {
59
- type: "git",
60
- url: "https://github.com/instavm/js.git"
61
- },
62
- bugs: {
63
- url: "https://github.com/instavm/js/issues"
64
- },
65
- homepage: "https://github.com/instavm/js#readme",
66
- engines: {
67
- node: ">=16.0.0"
68
- },
69
- dependencies: {
70
- axios: "^1.7.9",
71
- eventemitter3: "^5.0.1",
72
- "form-data": "^4.0.1",
73
- ws: "^8.18.0"
74
- },
75
- devDependencies: {
76
- "@types/node": "^22.10.5",
77
- "@types/ws": "^8.5.13",
78
- "@typescript-eslint/eslint-plugin": "^8.18.2",
79
- "@typescript-eslint/parser": "^8.18.2",
80
- "@vitest/coverage-v8": "^2.1.8",
81
- eslint: "^9.18.0",
82
- "eslint-config-prettier": "^9.1.0",
83
- "eslint-plugin-prettier": "^5.2.1",
84
- prettier: "^3.4.2",
85
- tsup: "^8.3.5",
86
- typescript: "^5.7.2",
87
- vitest: "^2.1.8"
88
- }
89
- };
90
- }
91
- });
92
2
 
93
3
  // src/client/HTTPClient.ts
94
4
  import axios from "axios";
@@ -171,6 +81,11 @@ var ElementNotFoundError = class extends BrowserError {
171
81
  this.selector = selector;
172
82
  }
173
83
  };
84
+ var UnsupportedOperationError = class extends InstaVMError {
85
+ constructor(message = "Operation not supported", options) {
86
+ super(message, options);
87
+ }
88
+ };
174
89
 
175
90
  // src/utils/retry.ts
176
91
  function defaultRetryCondition(error) {
@@ -213,14 +128,6 @@ async function withRetry(fn, options) {
213
128
  }
214
129
 
215
130
  // src/client/HTTPClient.ts
216
- function getPackageVersion() {
217
- try {
218
- const packageJson = require_package();
219
- return packageJson.version || "0.1.0";
220
- } catch {
221
- return "0.1.0";
222
- }
223
- }
224
131
  var HTTPClient = class {
225
132
  get apiKey() {
226
133
  return this.config.apiKey;
@@ -232,7 +139,7 @@ var HTTPClient = class {
232
139
  timeout: config.timeout,
233
140
  headers: {
234
141
  "Content-Type": "application/json",
235
- "User-Agent": `instavm-js-sdk/${getPackageVersion()}`
142
+ "User-Agent": "instavm-js-sdk/0.1.0"
236
143
  }
237
144
  });
238
145
  this.setupInterceptors();
@@ -240,7 +147,7 @@ var HTTPClient = class {
240
147
  setupInterceptors() {
241
148
  this.client.interceptors.request.use(
242
149
  (config) => {
243
- if (!config.headers["X-API-Key"] && !config.url?.includes("/v1/sessions/session")) {
150
+ if (config.url?.includes("/browser/")) {
244
151
  config.headers["X-API-Key"] = this.config.apiKey;
245
152
  }
246
153
  return config;
@@ -329,14 +236,18 @@ var HTTPClient = class {
329
236
  });
330
237
  }
331
238
  /**
332
- * POST request for code execution (X-API-Key header is automatically added)
239
+ * POST request for code execution (uses X-API-Key header like Python client)
333
240
  */
334
241
  async postExecution(url, data, headers) {
242
+ const requestHeaders = {
243
+ "X-API-Key": this.config.apiKey,
244
+ ...headers
245
+ };
335
246
  return this.request({
336
247
  method: "POST",
337
248
  url,
338
249
  data,
339
- headers
250
+ headers: requestHeaders
340
251
  });
341
252
  }
342
253
  /**
@@ -375,6 +286,31 @@ var HTTPClient = class {
375
286
  headers
376
287
  });
377
288
  }
289
+ /**
290
+ * POST request that returns raw binary data (for file downloads)
291
+ */
292
+ async postRaw(url, data, headers) {
293
+ const requestHeaders = {
294
+ "X-API-Key": this.config.apiKey,
295
+ ...headers
296
+ };
297
+ const axiosConfig = {
298
+ method: "POST",
299
+ url,
300
+ data,
301
+ headers: requestHeaders,
302
+ responseType: "arraybuffer",
303
+ timeout: this.config.timeout
304
+ };
305
+ const makeRequest = async () => {
306
+ const response = await this.client.request(axiosConfig);
307
+ return response.data;
308
+ };
309
+ return withRetry(makeRequest, {
310
+ retries: this.config.maxRetries,
311
+ retryDelay: this.config.retryDelay
312
+ });
313
+ }
378
314
  };
379
315
 
380
316
  // src/client/BrowserSession.ts
@@ -601,6 +537,59 @@ var BrowserSession = class extends EventEmitter {
601
537
  );
602
538
  }
603
539
  }
540
+ /**
541
+ * Extract LLM-friendly content from the current page
542
+ *
543
+ * Returns clean article content, interactive elements, and content anchors
544
+ * for intelligent browser automation with LLMs.
545
+ *
546
+ * @param options - Content extraction options
547
+ * @returns Extracted content with readable text, interactive elements, and content anchors
548
+ *
549
+ * @example
550
+ * ```typescript
551
+ * const content = await session.extractContent();
552
+ *
553
+ * // LLM reads clean content
554
+ * const article = content.readableContent.content;
555
+ *
556
+ * // LLM finds "Sign Up" in content and uses anchors to get selector
557
+ * const signUpAnchor = content.contentAnchors?.find(
558
+ * anchor => anchor.text.toLowerCase().includes('sign up')
559
+ * );
560
+ * if (signUpAnchor) {
561
+ * await session.click(signUpAnchor.selector);
562
+ * }
563
+ * ```
564
+ */
565
+ async extractContent(options = {}) {
566
+ this.ensureActive();
567
+ const requestData = {
568
+ session_id: this.sessionId,
569
+ include_interactive: options.includeInteractive !== false,
570
+ include_anchors: options.includeAnchors !== false,
571
+ max_anchors: options.maxAnchors ?? 50
572
+ };
573
+ try {
574
+ const response = await this.httpClient.post(
575
+ "/v1/browser/interactions/content",
576
+ requestData
577
+ );
578
+ return {
579
+ readableContent: {
580
+ ...response.readable_content || {},
581
+ content: response.readable_content?.content || ""
582
+ },
583
+ interactiveElements: response.interactive_elements,
584
+ contentAnchors: response.content_anchors
585
+ };
586
+ } catch (error) {
587
+ throw new BrowserInteractionError(
588
+ `Content extraction failed: ${getErrorMessage(error)}`,
589
+ { cause: error }
590
+ );
591
+ }
592
+ }
604
593
  /**
605
594
  * Wait for a condition
606
595
  */
@@ -672,14 +661,20 @@ var BrowserSession = class extends EventEmitter {
672
661
 
673
662
  // src/client/BrowserManager.ts
674
663
  var BrowserManager = class {
675
- constructor(httpClient) {
664
+ constructor(httpClient, local = false) {
676
665
  this.activeSessions = /* @__PURE__ */ new Map();
677
666
  this.httpClient = httpClient;
667
+ this.local = local;
678
668
  }
679
669
  /**
680
670
  * Create a new browser session
681
671
  */
682
672
  async createSession(options = {}) {
673
+ if (this.local) {
674
+ throw new UnsupportedOperationError(
675
+ "Browser session management is not supported in local mode. Use navigate() or extractContent() with URL directly."
676
+ );
677
+ }
683
678
  const requestData = {
684
679
  viewport_width: options.viewportWidth || 1920,
685
680
  viewport_height: options.viewportHeight || 1080,
@@ -778,6 +773,77 @@ var BrowserManager = class {
778
773
  );
779
774
  this.activeSessions.clear();
780
775
  }
776
+ /**
777
+ * Navigate to a URL (local mode support - no session required)
778
+ */
779
+ async navigate(url, options = {}) {
780
+ if (!this.local) {
781
+ throw new UnsupportedOperationError(
782
+ "navigate() without session is only supported in local mode. In cloud mode, create a session first."
783
+ );
784
+ }
785
+ const requestData = {
786
+ url,
787
+ wait_timeout: options.waitTimeout || 3e4
788
+ };
789
+ try {
790
+ const response = await this.httpClient.post(
791
+ "/v1/browser/interactions/navigate",
792
+ requestData
793
+ );
794
+ return {
795
+ success: response.success !== false,
796
+ url: response.url || url,
797
+ title: response.title,
798
+ status: response.status
799
+ };
800
+ } catch (error) {
801
+ const errorMessage = error instanceof Error ? error.message : String(error);
802
+ throw new BrowserNavigationError(
803
+ `Navigation failed: ${errorMessage}`,
804
+ { cause: error }
805
+ );
806
+ }
807
+ }
808
+ /**
809
+ * Extract LLM-friendly content (local mode support - no session required)
810
+ */
811
+ async extractContent(options = {}) {
812
+ if (!this.local) {
813
+ throw new UnsupportedOperationError(
814
+ "extractContent() without session is only supported in local mode. In cloud mode, create a session first."
815
+ );
816
+ }
817
+ if (!options.url) {
818
+ throw new BrowserInteractionError("url is required in local mode");
819
+ }
820
+ const requestData = {
821
+ url: options.url,
822
+ include_interactive: options.includeInteractive !== false,
823
+ include_anchors: options.includeAnchors !== false,
824
+ max_anchors: options.maxAnchors ?? 50
825
+ };
826
+ try {
827
+ const response = await this.httpClient.post(
828
+ "/v1/browser/interactions/content",
829
+ requestData
830
+ );
831
+ return {
832
+ readableContent: {
833
+ ...response.readable_content || {},
834
+ content: response.readable_content?.content || ""
835
+ },
836
+ interactiveElements: response.interactive_elements,
837
+ contentAnchors: response.content_anchors
838
+ };
839
+ } catch (error) {
840
+ const errorMessage = error instanceof Error ? error.message : String(error);
841
+ throw new BrowserInteractionError(
842
+ `Content extraction failed: ${errorMessage}`,
843
+ { cause: error }
844
+ );
845
+ }
846
+ }
781
847
  /**
782
848
  * Clean up resources
783
849
  */
@@ -791,34 +857,47 @@ import FormData from "form-data";
791
857
  var InstaVM = class {
792
858
  constructor(apiKey, options = {}) {
793
859
  this._sessionId = null;
794
- if (!apiKey) {
795
- throw new Error("API key is required");
860
+ this.local = options.local || false;
861
+ if (!this.local && !apiKey) {
862
+ throw new AuthenticationError("API key is required for cloud mode");
796
863
  }
797
864
  const config = {
798
- baseURL: options.baseURL || "https://api.instavm.io",
865
+ baseURL: this.local ? options.localURL || "http://coderunner.local:8222" : options.baseURL || "https://api.instavm.io",
799
866
  timeout: options.timeout || 3e5,
800
867
  // 5 minutes
801
868
  maxRetries: options.maxRetries || 3,
802
869
  retryDelay: options.retryDelay || 1e3,
803
- apiKey
870
+ apiKey: apiKey || ""
804
871
  };
805
872
  this.httpClient = new HTTPClient(config);
806
- this.browser = new BrowserManager(this.httpClient);
873
+ this.browser = new BrowserManager(this.httpClient, this.local);
874
+ }
875
+ /**
876
+ * Ensure operation is not called in local mode
877
+ */
878
+ ensureNotLocal(operationName) {
879
+ if (this.local) {
880
+ throw new UnsupportedOperationError(
881
+ `${operationName} is not supported in local mode. This operation is only available when using the cloud API.`
882
+ );
883
+ }
807
884
  }
808
885
  /**
809
886
  * Execute code synchronously
810
887
  */
811
888
  async execute(command, options = {}) {
812
889
  let sessionId = options.sessionId || this._sessionId;
813
- if (!sessionId) {
890
+ if (!this.local && !sessionId) {
814
891
  sessionId = await this.createSession();
815
892
  }
816
893
  const requestData = {
817
894
  command,
818
895
  language: options.language || "python",
819
- timeout: options.timeout || 15,
820
- session_id: sessionId
896
+ timeout: options.timeout || 15
821
897
  };
898
+ if (!this.local && sessionId) {
899
+ requestData.session_id = sessionId;
900
+ }
822
901
  try {
823
902
  const response = await this.httpClient.postExecution(
824
903
  "/execute",
@@ -848,6 +927,7 @@ var InstaVM = class {
848
927
  * Execute code asynchronously
849
928
  */
850
929
  async executeAsync(command, options = {}) {
930
+ this.ensureNotLocal("Async execution");
851
931
  let sessionId = options.sessionId || this._sessionId;
852
932
  if (!sessionId) {
853
933
  sessionId = await this.createSession();
@@ -889,6 +969,7 @@ var InstaVM = class {
889
969
  * Upload files to the execution environment
890
970
  */
891
971
  async upload(files, options = {}) {
972
+ this.ensureNotLocal("File upload");
892
973
  const formData = new FormData();
893
974
  for (const file of files) {
894
975
  if (Buffer.isBuffer(file.content)) {
@@ -931,6 +1012,7 @@ var InstaVM = class {
931
1012
  * Create a new execution session
932
1013
  */
933
1014
  async createSession() {
1015
+ this.ensureNotLocal("Session management");
934
1016
  try {
935
1017
  const response = await this.httpClient.post("/v1/sessions/session", {
936
1018
  api_key: this.httpClient.apiKey
@@ -970,6 +1052,7 @@ var InstaVM = class {
970
1052
  * Get usage statistics for a session
971
1053
  */
972
1054
  async getUsage(sessionId) {
1055
+ this.ensureNotLocal("Usage tracking");
973
1056
  const targetSessionId = sessionId || this._sessionId;
974
1057
  if (!targetSessionId) {
975
1058
  throw new SessionError("No active session to get usage for");
@@ -992,6 +1075,35 @@ var InstaVM = class {
992
1075
  );
993
1076
  }
994
1077
  }
1078
+ /**
1079
+ * Download a file from the remote VM
1080
+ */
1081
+ async download(filename, options = {}) {
1082
+ this.ensureNotLocal("File download");
1083
+ const targetSessionId = options.sessionId || this._sessionId;
1084
+ if (!targetSessionId) {
1085
+ throw new SessionError("No active session to download from");
1086
+ }
1087
+ try {
1088
+ const response = await this.httpClient.postRaw("/download", {
1089
+ filename,
1090
+ session_id: targetSessionId
1091
+ });
1092
+ const content = Buffer.from(response);
1093
+ return {
1094
+ success: true,
1095
+ filename,
1096
+ content,
1097
+ size: content.length
1098
+ };
1099
+ } catch (error) {
1100
+ const errorMessage = error instanceof Error ? error.message : String(error);
1101
+ throw new SessionError(
1102
+ `File download failed: ${errorMessage}`,
1103
+ { cause: error }
1104
+ );
1105
+ }
1106
+ }
995
1107
  /**
996
1108
  * Get the current session ID
997
1109
  */
@@ -1024,6 +1136,7 @@ export {
1024
1136
  NetworkError,
1025
1137
  QuotaExceededError,
1026
1138
  RateLimitError,
1027
- SessionError
1139
+ SessionError,
1140
+ UnsupportedOperationError
1028
1141
  };
1029
1142
  //# sourceMappingURL=index.mjs.map