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.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, {
@@ -135,7 +46,8 @@ __export(index_exports, {
135
46
  NetworkError: () => NetworkError,
136
47
  QuotaExceededError: () => QuotaExceededError,
137
48
  RateLimitError: () => RateLimitError,
138
- SessionError: () => SessionError
49
+ SessionError: () => SessionError,
50
+ UnsupportedOperationError: () => UnsupportedOperationError
139
51
  });
140
52
  module.exports = __toCommonJS(index_exports);
141
53
 
@@ -220,6 +132,11 @@ var ElementNotFoundError = class extends BrowserError {
220
132
  this.selector = selector;
221
133
  }
222
134
  };
135
+ var UnsupportedOperationError = class extends InstaVMError {
136
+ constructor(message = "Operation not supported", options) {
137
+ super(message, options);
138
+ }
139
+ };
223
140
 
224
141
  // src/utils/retry.ts
225
142
  function defaultRetryCondition(error) {
@@ -262,14 +179,6 @@ async function withRetry(fn, options) {
262
179
  }
263
180
 
264
181
  // 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
182
  var HTTPClient = class {
274
183
  get apiKey() {
275
184
  return this.config.apiKey;
@@ -281,7 +190,7 @@ var HTTPClient = class {
281
190
  timeout: config.timeout,
282
191
  headers: {
283
192
  "Content-Type": "application/json",
284
- "User-Agent": `instavm-js-sdk/${getPackageVersion()}`
193
+ "User-Agent": "instavm-js-sdk/0.1.0"
285
194
  }
286
195
  });
287
196
  this.setupInterceptors();
@@ -289,7 +198,7 @@ var HTTPClient = class {
289
198
  setupInterceptors() {
290
199
  this.client.interceptors.request.use(
291
200
  (config) => {
292
- if (!config.headers["X-API-Key"] && !config.url?.includes("/v1/sessions/session")) {
201
+ if (config.url?.includes("/browser/")) {
293
202
  config.headers["X-API-Key"] = this.config.apiKey;
294
203
  }
295
204
  return config;
@@ -378,14 +287,18 @@ var HTTPClient = class {
378
287
  });
379
288
  }
380
289
  /**
381
- * POST request for code execution (X-API-Key header is automatically added)
290
+ * POST request for code execution (uses X-API-Key header like Python client)
382
291
  */
383
292
  async postExecution(url, data, headers) {
293
+ const requestHeaders = {
294
+ "X-API-Key": this.config.apiKey,
295
+ ...headers
296
+ };
384
297
  return this.request({
385
298
  method: "POST",
386
299
  url,
387
300
  data,
388
- headers
301
+ headers: requestHeaders
389
302
  });
390
303
  }
391
304
  /**
@@ -424,6 +337,31 @@ var HTTPClient = class {
424
337
  headers
425
338
  });
426
339
  }
340
+ /**
341
+ * POST request that returns raw binary data (for file downloads)
342
+ */
343
+ async postRaw(url, data, headers) {
344
+ const requestHeaders = {
345
+ "X-API-Key": this.config.apiKey,
346
+ ...headers
347
+ };
348
+ const axiosConfig = {
349
+ method: "POST",
350
+ url,
351
+ data,
352
+ headers: requestHeaders,
353
+ responseType: "arraybuffer",
354
+ timeout: this.config.timeout
355
+ };
356
+ const makeRequest = async () => {
357
+ const response = await this.client.request(axiosConfig);
358
+ return response.data;
359
+ };
360
+ return withRetry(makeRequest, {
361
+ retries: this.config.maxRetries,
362
+ retryDelay: this.config.retryDelay
363
+ });
364
+ }
427
365
  };
428
366
 
429
367
  // src/client/BrowserSession.ts
@@ -650,6 +588,59 @@ var BrowserSession = class extends import_eventemitter3.EventEmitter {
650
588
  );
651
589
  }
652
590
  }
591
+ /**
592
+ * Extract LLM-friendly content from the current page
593
+ *
594
+ * Returns clean article content, interactive elements, and content anchors
595
+ * for intelligent browser automation with LLMs.
596
+ *
597
+ * @param options - Content extraction options
598
+ * @returns Extracted content with readable text, interactive elements, and content anchors
599
+ *
600
+ * @example
601
+ * ```typescript
602
+ * const content = await session.extractContent();
603
+ *
604
+ * // LLM reads clean content
605
+ * const article = content.readableContent.content;
606
+ *
607
+ * // LLM finds "Sign Up" in content and uses anchors to get selector
608
+ * const signUpAnchor = content.contentAnchors?.find(
609
+ * anchor => anchor.text.toLowerCase().includes('sign up')
610
+ * );
611
+ * if (signUpAnchor) {
612
+ * await session.click(signUpAnchor.selector);
613
+ * }
614
+ * ```
615
+ */
616
+ async extractContent(options = {}) {
617
+ this.ensureActive();
618
+ const requestData = {
619
+ session_id: this.sessionId,
620
+ include_interactive: options.includeInteractive !== false,
621
+ include_anchors: options.includeAnchors !== false,
622
+ max_anchors: options.maxAnchors ?? 50
623
+ };
624
+ try {
625
+ const response = await this.httpClient.post(
626
+ "/v1/browser/interactions/content",
627
+ requestData
628
+ );
629
+ return {
630
+ readableContent: {
631
+ ...response.readable_content || {},
632
+ content: response.readable_content?.content || ""
633
+ },
634
+ interactiveElements: response.interactive_elements,
635
+ contentAnchors: response.content_anchors
636
+ };
637
+ } catch (error) {
638
+ throw new BrowserInteractionError(
639
+ `Content extraction failed: ${getErrorMessage(error)}`,
640
+ { cause: error }
641
+ );
642
+ }
643
+ }
653
644
  /**
654
645
  * Wait for a condition
655
646
  */
@@ -721,14 +712,20 @@ var BrowserSession = class extends import_eventemitter3.EventEmitter {
721
712
 
722
713
  // src/client/BrowserManager.ts
723
714
  var BrowserManager = class {
724
- constructor(httpClient) {
715
+ constructor(httpClient, local = false) {
725
716
  this.activeSessions = /* @__PURE__ */ new Map();
726
717
  this.httpClient = httpClient;
718
+ this.local = local;
727
719
  }
728
720
  /**
729
721
  * Create a new browser session
730
722
  */
731
723
  async createSession(options = {}) {
724
+ if (this.local) {
725
+ throw new UnsupportedOperationError(
726
+ "Browser session management is not supported in local mode. Use navigate() or extractContent() with URL directly."
727
+ );
728
+ }
732
729
  const requestData = {
733
730
  viewport_width: options.viewportWidth || 1920,
734
731
  viewport_height: options.viewportHeight || 1080,
@@ -827,6 +824,77 @@ var BrowserManager = class {
827
824
  );
828
825
  this.activeSessions.clear();
829
826
  }
827
+ /**
828
+ * Navigate to a URL (local mode support - no session required)
829
+ */
830
+ async navigate(url, options = {}) {
831
+ if (!this.local) {
832
+ throw new UnsupportedOperationError(
833
+ "navigate() without session is only supported in local mode. In cloud mode, create a session first."
834
+ );
835
+ }
836
+ const requestData = {
837
+ url,
838
+ wait_timeout: options.waitTimeout || 3e4
839
+ };
840
+ try {
841
+ const response = await this.httpClient.post(
842
+ "/v1/browser/interactions/navigate",
843
+ requestData
844
+ );
845
+ return {
846
+ success: response.success !== false,
847
+ url: response.url || url,
848
+ title: response.title,
849
+ status: response.status
850
+ };
851
+ } catch (error) {
852
+ const errorMessage = error instanceof Error ? error.message : String(error);
853
+ throw new BrowserNavigationError(
854
+ `Navigation failed: ${errorMessage}`,
855
+ { cause: error }
856
+ );
857
+ }
858
+ }
859
+ /**
860
+ * Extract LLM-friendly content (local mode support - no session required)
861
+ */
862
+ async extractContent(options = {}) {
863
+ if (!this.local) {
864
+ throw new UnsupportedOperationError(
865
+ "extractContent() without session is only supported in local mode. In cloud mode, create a session first."
866
+ );
867
+ }
868
+ if (!options.url) {
869
+ throw new BrowserInteractionError("url is required in local mode");
870
+ }
871
+ const requestData = {
872
+ url: options.url,
873
+ include_interactive: options.includeInteractive !== false,
874
+ include_anchors: options.includeAnchors !== false,
875
+ max_anchors: options.maxAnchors ?? 50
876
+ };
877
+ try {
878
+ const response = await this.httpClient.post(
879
+ "/v1/browser/interactions/content",
880
+ requestData
881
+ );
882
+ return {
883
+ readableContent: {
884
+ ...response.readable_content || {},
885
+ content: response.readable_content?.content || ""
886
+ },
887
+ interactiveElements: response.interactive_elements,
888
+ contentAnchors: response.content_anchors
889
+ };
890
+ } catch (error) {
891
+ const errorMessage = error instanceof Error ? error.message : String(error);
892
+ throw new BrowserInteractionError(
893
+ `Content extraction failed: ${errorMessage}`,
894
+ { cause: error }
895
+ );
896
+ }
897
+ }
830
898
  /**
831
899
  * Clean up resources
832
900
  */
@@ -840,34 +908,47 @@ var import_form_data = __toESM(require("form-data"));
840
908
  var InstaVM = class {
841
909
  constructor(apiKey, options = {}) {
842
910
  this._sessionId = null;
843
- if (!apiKey) {
844
- throw new Error("API key is required");
911
+ this.local = options.local || false;
912
+ if (!this.local && !apiKey) {
913
+ throw new AuthenticationError("API key is required for cloud mode");
845
914
  }
846
915
  const config = {
847
- baseURL: options.baseURL || "https://api.instavm.io",
916
+ baseURL: this.local ? options.localURL || "http://coderunner.local:8222" : options.baseURL || "https://api.instavm.io",
848
917
  timeout: options.timeout || 3e5,
849
918
  // 5 minutes
850
919
  maxRetries: options.maxRetries || 3,
851
920
  retryDelay: options.retryDelay || 1e3,
852
- apiKey
921
+ apiKey: apiKey || ""
853
922
  };
854
923
  this.httpClient = new HTTPClient(config);
855
- this.browser = new BrowserManager(this.httpClient);
924
+ this.browser = new BrowserManager(this.httpClient, this.local);
925
+ }
926
+ /**
927
+ * Ensure operation is not called in local mode
928
+ */
929
+ ensureNotLocal(operationName) {
930
+ if (this.local) {
931
+ throw new UnsupportedOperationError(
932
+ `${operationName} is not supported in local mode. This operation is only available when using the cloud API.`
933
+ );
934
+ }
856
935
  }
857
936
  /**
858
937
  * Execute code synchronously
859
938
  */
860
939
  async execute(command, options = {}) {
861
940
  let sessionId = options.sessionId || this._sessionId;
862
- if (!sessionId) {
941
+ if (!this.local && !sessionId) {
863
942
  sessionId = await this.createSession();
864
943
  }
865
944
  const requestData = {
866
945
  command,
867
946
  language: options.language || "python",
868
- timeout: options.timeout || 15,
869
- session_id: sessionId
947
+ timeout: options.timeout || 15
870
948
  };
949
+ if (!this.local && sessionId) {
950
+ requestData.session_id = sessionId;
951
+ }
871
952
  try {
872
953
  const response = await this.httpClient.postExecution(
873
954
  "/execute",
@@ -897,6 +978,7 @@ var InstaVM = class {
897
978
  * Execute code asynchronously
898
979
  */
899
980
  async executeAsync(command, options = {}) {
981
+ this.ensureNotLocal("Async execution");
900
982
  let sessionId = options.sessionId || this._sessionId;
901
983
  if (!sessionId) {
902
984
  sessionId = await this.createSession();
@@ -938,6 +1020,7 @@ var InstaVM = class {
938
1020
  * Upload files to the execution environment
939
1021
  */
940
1022
  async upload(files, options = {}) {
1023
+ this.ensureNotLocal("File upload");
941
1024
  const formData = new import_form_data.default();
942
1025
  for (const file of files) {
943
1026
  if (Buffer.isBuffer(file.content)) {
@@ -980,6 +1063,7 @@ var InstaVM = class {
980
1063
  * Create a new execution session
981
1064
  */
982
1065
  async createSession() {
1066
+ this.ensureNotLocal("Session management");
983
1067
  try {
984
1068
  const response = await this.httpClient.post("/v1/sessions/session", {
985
1069
  api_key: this.httpClient.apiKey
@@ -1019,6 +1103,7 @@ var InstaVM = class {
1019
1103
  * Get usage statistics for a session
1020
1104
  */
1021
1105
  async getUsage(sessionId) {
1106
+ this.ensureNotLocal("Usage tracking");
1022
1107
  const targetSessionId = sessionId || this._sessionId;
1023
1108
  if (!targetSessionId) {
1024
1109
  throw new SessionError("No active session to get usage for");
@@ -1041,6 +1126,35 @@ var InstaVM = class {
1041
1126
  );
1042
1127
  }
1043
1128
  }
1129
+ /**
1130
+ * Download a file from the remote VM
1131
+ */
1132
+ async download(filename, options = {}) {
1133
+ this.ensureNotLocal("File download");
1134
+ const targetSessionId = options.sessionId || this._sessionId;
1135
+ if (!targetSessionId) {
1136
+ throw new SessionError("No active session to download from");
1137
+ }
1138
+ try {
1139
+ const response = await this.httpClient.postRaw("/download", {
1140
+ filename,
1141
+ session_id: targetSessionId
1142
+ });
1143
+ const content = Buffer.from(response);
1144
+ return {
1145
+ success: true,
1146
+ filename,
1147
+ content,
1148
+ size: content.length
1149
+ };
1150
+ } catch (error) {
1151
+ const errorMessage = error instanceof Error ? error.message : String(error);
1152
+ throw new SessionError(
1153
+ `File download failed: ${errorMessage}`,
1154
+ { cause: error }
1155
+ );
1156
+ }
1157
+ }
1044
1158
  /**
1045
1159
  * Get the current session ID
1046
1160
  */
@@ -1074,6 +1188,7 @@ var InstaVM = class {
1074
1188
  NetworkError,
1075
1189
  QuotaExceededError,
1076
1190
  RateLimitError,
1077
- SessionError
1191
+ SessionError,
1192
+ UnsupportedOperationError
1078
1193
  });
1079
1194
  //# sourceMappingURL=index.js.map