@labring/devbox-sdk 1.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/dist/index.mjs ADDED
@@ -0,0 +1,2665 @@
1
+ import yaml from 'js-yaml';
2
+
3
+ // src/core/constants.ts
4
+ var DEFAULT_CONFIG = {
5
+ /** Default base URL for Devbox API */
6
+ BASE_URL: "https://devbox.usw.sealos.io/v1",
7
+ /** Default HTTP server port for containers */
8
+ CONTAINER_HTTP_PORT: 3e3,
9
+ /** Default mock server configuration */
10
+ MOCK_SERVER: {
11
+ DEFAULT_URL: "http://localhost:9757",
12
+ ENV_VAR: "MOCK_SERVER_URL"
13
+ },
14
+ /** Default HTTP client settings */
15
+ HTTP_CLIENT: {
16
+ TIMEOUT: 3e4,
17
+ // 30 seconds
18
+ RETRIES: 3
19
+ },
20
+ /** File operation limits */
21
+ FILE_LIMITS: {
22
+ MAX_FILE_SIZE: 100 * 1024 * 1024,
23
+ // 100MB
24
+ MAX_BATCH_SIZE: 50,
25
+ // maximum files per batch
26
+ CHUNK_SIZE: 1024 * 1024
27
+ // 1MB chunks for streaming
28
+ },
29
+ /** Performance targets */
30
+ PERFORMANCE: {
31
+ SMALL_FILE_LATENCY_MS: 50,
32
+ // <50ms for files <1MB
33
+ LARGE_FILE_THROUGHPUT_MBPS: 15,
34
+ // >15MB/s for large files
35
+ CONNECTION_REUSE_RATE: 0.98,
36
+ // >98% connection reuse
37
+ STARTUP_TIME_MS: 100
38
+ // <100ms Bun server startup
39
+ }
40
+ };
41
+ var API_ENDPOINTS = {
42
+ /** Devbox management endpoints */
43
+ DEVBOX: {
44
+ LIST: "/api/v1/devbox",
45
+ CREATE: "/api/v1/devbox",
46
+ GET: "/api/v1/devbox/{name}",
47
+ UPDATE: "/api/v1/devbox/{name}",
48
+ DELETE: "/api/v1/devbox/{name}/delete",
49
+ START: "/api/v1/devbox/{name}/start",
50
+ PAUSE: "/api/v1/devbox/{name}/pause",
51
+ RESTART: "/api/v1/devbox/{name}/restart",
52
+ SHUTDOWN: "/api/v1/devbox/{name}/shutdown",
53
+ MONITOR: "/api/v1/devbox/{name}/monitor",
54
+ TEMPLATES: "/api/v1/devbox/templates",
55
+ PORTS: "/api/v1/devbox/{name}/ports",
56
+ AUTOSTART: "/api/v1/devbox/{name}/autostart",
57
+ RELEASE: {
58
+ LIST: "/api/v1/devbox/{name}/release",
59
+ CREATE: "/api/v1/devbox/{name}/release",
60
+ DELETE: "/api/v1/devbox/{name}/release/{tag}",
61
+ DEPLOY: "/api/v1/devbox/{name}/release/{tag}/deploy"
62
+ }
63
+ },
64
+ /** Container server endpoints */
65
+ CONTAINER: {
66
+ HEALTH: "/health",
67
+ FILES: {
68
+ WRITE: "/api/v1/files/write",
69
+ READ: "/api/v1/files/read",
70
+ LIST: "/api/v1/files/list",
71
+ DELETE: "/api/v1/files/delete",
72
+ MOVE: "/api/v1/files/move",
73
+ RENAME: "/api/v1/files/rename",
74
+ DOWNLOAD: "/api/v1/files/download",
75
+ BATCH_UPLOAD: "/api/v1/files/batch-upload",
76
+ BATCH_DOWNLOAD: "/api/v1/files/batch-download",
77
+ SEARCH: "/api/v1/files/search",
78
+ FIND: "/api/v1/files/find",
79
+ REPLACE: "/api/v1/files/replace"
80
+ },
81
+ PROCESS: {
82
+ EXEC: "/api/v1/process/exec",
83
+ EXEC_SYNC: "/api/v1/process/exec-sync",
84
+ EXEC_SYNC_STREAM: "/api/v1/process/sync-stream",
85
+ LIST: "/api/v1/process/list",
86
+ STATUS: "/api/v1/process/{process_id}/status",
87
+ KILL: "/api/v1/process/{process_id}/kill",
88
+ LOGS: "/api/v1/process/{process_id}/logs"
89
+ },
90
+ PORTS: "/api/v1/ports"
91
+ // Temporarily disabled - ws module removed
92
+ // WEBSOCKET: '/ws',
93
+ }
94
+ };
95
+ var ERROR_CODES = {
96
+ /** Authentication errors */
97
+ AUTHENTICATION_FAILED: "AUTHENTICATION_FAILED",
98
+ INVALID_KUBECONFIG: "INVALID_KUBECONFIG",
99
+ UNAUTHORIZED: "UNAUTHORIZED",
100
+ INVALID_TOKEN: "INVALID_TOKEN",
101
+ TOKEN_EXPIRED: "TOKEN_EXPIRED",
102
+ INSUFFICIENT_PERMISSIONS: "INSUFFICIENT_PERMISSIONS",
103
+ /** Connection errors */
104
+ CONNECTION_FAILED: "CONNECTION_FAILED",
105
+ CONNECTION_TIMEOUT: "CONNECTION_TIMEOUT",
106
+ /** Devbox errors */
107
+ DEVBOX_NOT_FOUND: "DEVBOX_NOT_FOUND",
108
+ DEVBOX_NOT_READY: "DEVBOX_NOT_READY",
109
+ DEVBOX_CREATION_FAILED: "DEVBOX_CREATION_FAILED",
110
+ DEVBOX_OPERATION_FAILED: "DEVBOX_OPERATION_FAILED",
111
+ /** Validation errors */
112
+ INVALID_REQUEST: "INVALID_REQUEST",
113
+ MISSING_REQUIRED_FIELD: "MISSING_REQUIRED_FIELD",
114
+ INVALID_FIELD_VALUE: "INVALID_FIELD_VALUE",
115
+ INVALID_JSON_FORMAT: "INVALID_JSON_FORMAT",
116
+ INVALID_PATH: "INVALID_PATH",
117
+ VALIDATION_ERROR: "VALIDATION_ERROR",
118
+ /** Resource errors */
119
+ NOT_FOUND: "NOT_FOUND",
120
+ PROCESS_NOT_FOUND: "PROCESS_NOT_FOUND",
121
+ SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
122
+ FILE_NOT_FOUND: "FILE_NOT_FOUND",
123
+ DIRECTORY_NOT_FOUND: "DIRECTORY_NOT_FOUND",
124
+ /** State errors */
125
+ CONFLICT: "CONFLICT",
126
+ PROCESS_ALREADY_RUNNING: "PROCESS_ALREADY_RUNNING",
127
+ PROCESS_NOT_RUNNING: "PROCESS_NOT_RUNNING",
128
+ SESSION_INACTIVE: "SESSION_INACTIVE",
129
+ RESOURCE_LOCKED: "RESOURCE_LOCKED",
130
+ PROCESS_ALREADY_TERMINATED: "PROCESS_ALREADY_TERMINATED",
131
+ /** Operation errors */
132
+ OPERATION_TIMEOUT: "OPERATION_TIMEOUT",
133
+ OPERATION_FAILED: "OPERATION_FAILED",
134
+ EXECUTION_FAILED: "EXECUTION_FAILED",
135
+ SIGNAL_FAILED: "SIGNAL_FAILED",
136
+ /** File operation errors */
137
+ FILE_OPERATION_ERROR: "FILE_OPERATION_ERROR",
138
+ FILE_TOO_LARGE: "FILE_TOO_LARGE",
139
+ FILE_TRANSFER_FAILED: "FILE_TRANSFER_FAILED",
140
+ PATH_TRAVERSAL_DETECTED: "PATH_TRAVERSAL_DETECTED",
141
+ DIRECTORY_NOT_EMPTY: "DIRECTORY_NOT_EMPTY",
142
+ DISK_FULL: "DISK_FULL",
143
+ FILE_LOCKED: "FILE_LOCKED",
144
+ /** Process errors */
145
+ PROCESS_START_FAILED: "PROCESS_START_FAILED",
146
+ INVALID_SIGNAL: "INVALID_SIGNAL",
147
+ PROCESS_LIMIT_EXCEEDED: "PROCESS_LIMIT_EXCEEDED",
148
+ /** Session errors */
149
+ SESSION_CREATION_FAILED: "SESSION_CREATION_FAILED",
150
+ SESSION_LIMIT_EXCEEDED: "SESSION_LIMIT_EXCEEDED",
151
+ SESSION_TIMEOUT: "SESSION_TIMEOUT",
152
+ SHELL_NOT_FOUND: "SHELL_NOT_FOUND",
153
+ /** WebSocket errors */
154
+ // Temporarily disabled - ws module removed
155
+ // WEBSOCKET_CONNECTION_FAILED: 'WEBSOCKET_CONNECTION_FAILED',
156
+ // INVALID_SUBSCRIPTION: 'INVALID_SUBSCRIPTION',
157
+ // TARGET_NOT_SUBSCRIBABLE: 'TARGET_NOT_SUBSCRIBABLE',
158
+ /** Server errors */
159
+ SERVER_UNAVAILABLE: "SERVER_UNAVAILABLE",
160
+ HEALTH_CHECK_FAILED: "HEALTH_CHECK_FAILED",
161
+ INTERNAL_ERROR: "INTERNAL_ERROR",
162
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
163
+ MAINTENANCE_MODE: "MAINTENANCE_MODE"
164
+ };
165
+ var HTTP_STATUS = {
166
+ OK: 200,
167
+ CREATED: 201,
168
+ ACCEPTED: 202,
169
+ NO_CONTENT: 204,
170
+ BAD_REQUEST: 400,
171
+ UNAUTHORIZED: 401,
172
+ FORBIDDEN: 403,
173
+ NOT_FOUND: 404,
174
+ METHOD_NOT_ALLOWED: 405,
175
+ TIMEOUT: 408,
176
+ CONFLICT: 409,
177
+ GONE: 410,
178
+ TOO_MANY_REQUESTS: 429,
179
+ INTERNAL_SERVER_ERROR: 500,
180
+ BAD_GATEWAY: 502,
181
+ SERVICE_UNAVAILABLE: 503,
182
+ GATEWAY_TIMEOUT: 504
183
+ };
184
+
185
+ // src/utils/error.ts
186
+ var DevboxSDKError = class extends Error {
187
+ constructor(message, code, context) {
188
+ super(message);
189
+ this.code = code;
190
+ this.context = context;
191
+ this.name = "DevboxSDKError";
192
+ }
193
+ };
194
+ var AuthenticationError = class extends DevboxSDKError {
195
+ constructor(message, context) {
196
+ super(message, "AUTHENTICATION_FAILED", context);
197
+ this.name = "AuthenticationError";
198
+ }
199
+ };
200
+ var ConnectionError = class extends DevboxSDKError {
201
+ constructor(message, context) {
202
+ super(message, "CONNECTION_FAILED", context);
203
+ this.name = "ConnectionError";
204
+ }
205
+ };
206
+ var FileOperationError = class extends DevboxSDKError {
207
+ constructor(message, context, code = ERROR_CODES.FILE_TRANSFER_FAILED) {
208
+ super(message, code, context);
209
+ this.name = "FileOperationError";
210
+ }
211
+ };
212
+ var DevboxNotFoundError = class extends DevboxSDKError {
213
+ constructor(devboxName, context) {
214
+ super(`Devbox '${devboxName}' not found`, "DEVBOX_NOT_FOUND", context);
215
+ this.name = "DevboxNotFoundError";
216
+ }
217
+ };
218
+ var DevboxNotReadyError = class extends DevboxSDKError {
219
+ constructor(devboxName, currentStatus, context) {
220
+ const statusInfo = currentStatus ? ` (current status: ${currentStatus})` : "";
221
+ super(
222
+ `Devbox '${devboxName}' is not ready yet${statusInfo}. The devbox may still be starting. Please wait a moment and try again, or use 'await devbox.waitForReady()' to wait until it's fully initialized.`,
223
+ "DEVBOX_NOT_READY",
224
+ context
225
+ );
226
+ this.name = "DevboxNotReadyError";
227
+ }
228
+ };
229
+ var ValidationError = class extends DevboxSDKError {
230
+ constructor(message, context) {
231
+ super(message, "VALIDATION_ERROR", context);
232
+ this.name = "ValidationError";
233
+ }
234
+ };
235
+ function mapServerStatusToErrorCode(status) {
236
+ switch (status) {
237
+ case 1404:
238
+ return ERROR_CODES.FILE_NOT_FOUND;
239
+ case 1400:
240
+ return ERROR_CODES.VALIDATION_ERROR;
241
+ case 1401:
242
+ return ERROR_CODES.UNAUTHORIZED;
243
+ case 1403:
244
+ return ERROR_CODES.INSUFFICIENT_PERMISSIONS;
245
+ case 1422:
246
+ return ERROR_CODES.INVALID_REQUEST;
247
+ case 1500:
248
+ return ERROR_CODES.INTERNAL_ERROR;
249
+ case 1409:
250
+ return ERROR_CODES.CONFLICT;
251
+ case 1600:
252
+ return ERROR_CODES.OPERATION_FAILED;
253
+ case 500:
254
+ return ERROR_CODES.INTERNAL_ERROR;
255
+ default:
256
+ return ERROR_CODES.OPERATION_FAILED;
257
+ }
258
+ }
259
+ function parseServerResponse(jsonData) {
260
+ if (jsonData.status !== void 0 && jsonData.status !== 0) {
261
+ const errorCode = mapServerStatusToErrorCode(jsonData.status);
262
+ const errorMessage = jsonData.message || "Unknown server error";
263
+ throw createErrorFromServerResponse(errorMessage, errorCode, void 0);
264
+ }
265
+ return jsonData.Data !== void 0 ? jsonData.Data : jsonData;
266
+ }
267
+ function createErrorFromServerResponse(error, code, timestamp) {
268
+ const errorContext = { timestamp, serverErrorCode: code };
269
+ switch (code) {
270
+ case ERROR_CODES.UNAUTHORIZED:
271
+ case ERROR_CODES.INVALID_TOKEN:
272
+ case ERROR_CODES.TOKEN_EXPIRED:
273
+ case ERROR_CODES.INSUFFICIENT_PERMISSIONS:
274
+ return new AuthenticationError(error, errorContext);
275
+ case ERROR_CODES.FILE_NOT_FOUND:
276
+ case ERROR_CODES.DIRECTORY_NOT_FOUND:
277
+ case ERROR_CODES.FILE_OPERATION_ERROR:
278
+ case ERROR_CODES.FILE_TOO_LARGE:
279
+ case ERROR_CODES.FILE_LOCKED:
280
+ case ERROR_CODES.DIRECTORY_NOT_EMPTY:
281
+ case ERROR_CODES.DISK_FULL:
282
+ return new FileOperationError(error, errorContext, code);
283
+ case ERROR_CODES.INVALID_REQUEST:
284
+ case ERROR_CODES.MISSING_REQUIRED_FIELD:
285
+ case ERROR_CODES.INVALID_FIELD_VALUE:
286
+ case ERROR_CODES.INVALID_JSON_FORMAT:
287
+ case ERROR_CODES.INVALID_PATH:
288
+ case ERROR_CODES.INVALID_SIGNAL:
289
+ return new ValidationError(error, errorContext);
290
+ case ERROR_CODES.DEVBOX_NOT_FOUND:
291
+ case ERROR_CODES.PROCESS_NOT_FOUND:
292
+ case ERROR_CODES.SESSION_NOT_FOUND:
293
+ case ERROR_CODES.NOT_FOUND:
294
+ if (code === ERROR_CODES.DEVBOX_NOT_FOUND) {
295
+ const devboxNameMatch = error.match(/Devbox '([^']+)'/i) || error.match(/devbox[:\s]+([^\s]+)/i);
296
+ const devboxName = devboxNameMatch?.[1] ?? "unknown";
297
+ return new DevboxNotFoundError(devboxName, errorContext);
298
+ }
299
+ return new DevboxSDKError(error, code || ERROR_CODES.INTERNAL_ERROR, errorContext);
300
+ default:
301
+ return new DevboxSDKError(error, code, errorContext);
302
+ }
303
+ }
304
+
305
+ // src/utils/logger.ts
306
+ var LOG_LEVEL_PRIORITY = {
307
+ ["INFO" /* INFO */]: 1,
308
+ ["WARN" /* WARN */]: 2,
309
+ ["ERROR" /* ERROR */]: 3,
310
+ ["SILENT" /* SILENT */]: Number.POSITIVE_INFINITY
311
+ // Highest priority, suppresses all logs
312
+ };
313
+ function getLogLevelFromEnv() {
314
+ const envLevel = process.env.LOG_LEVEL?.toUpperCase();
315
+ if (envLevel === "INFO") {
316
+ return "INFO" /* INFO */;
317
+ }
318
+ if (envLevel === "WARN" || envLevel === "WARNING") {
319
+ return "WARN" /* WARN */;
320
+ }
321
+ if (envLevel === "ERROR") {
322
+ return "ERROR" /* ERROR */;
323
+ }
324
+ return "SILENT" /* SILENT */;
325
+ }
326
+ var Logger = class {
327
+ currentLevel;
328
+ constructor() {
329
+ this.currentLevel = getLogLevelFromEnv();
330
+ }
331
+ /**
332
+ * Check if a log level should be output
333
+ */
334
+ shouldLog(level) {
335
+ return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.currentLevel];
336
+ }
337
+ /**
338
+ * Log info message
339
+ */
340
+ info(message, ...args) {
341
+ if (this.shouldLog("INFO" /* INFO */)) {
342
+ console.log(`[INFO] ${message}`, ...args);
343
+ }
344
+ }
345
+ /**
346
+ * Log warning message
347
+ */
348
+ warn(message, ...args) {
349
+ if (this.shouldLog("WARN" /* WARN */)) {
350
+ console.warn(`[WARN] ${message}`, ...args);
351
+ }
352
+ }
353
+ /**
354
+ * Log error message
355
+ */
356
+ error(message, ...args) {
357
+ if (this.shouldLog("ERROR" /* ERROR */)) {
358
+ console.error(`[ERROR] ${message}`, ...args);
359
+ }
360
+ }
361
+ };
362
+ var logger = new Logger();
363
+ function parseKubeconfigServerUrl(kubeconfig) {
364
+ if (!kubeconfig || typeof kubeconfig !== "string") {
365
+ return null;
366
+ }
367
+ try {
368
+ const config = yaml.load(kubeconfig);
369
+ if (!config) {
370
+ return null;
371
+ }
372
+ const currentContextName = config["current-context"] || config.currentContext;
373
+ if (!currentContextName) {
374
+ return null;
375
+ }
376
+ const contexts = config.contexts || [];
377
+ const currentContext = contexts.find(
378
+ (ctx) => ctx.name === currentContextName
379
+ );
380
+ if (!currentContext || !currentContext.context) {
381
+ return null;
382
+ }
383
+ const clusterName = currentContext.context.cluster;
384
+ if (!clusterName) {
385
+ return null;
386
+ }
387
+ const clusters = config.clusters || [];
388
+ const cluster = clusters.find((cl) => cl.name === clusterName);
389
+ if (!cluster || !cluster.cluster) {
390
+ return null;
391
+ }
392
+ const serverUrl = cluster.cluster.server;
393
+ if (!serverUrl || typeof serverUrl !== "string") {
394
+ return null;
395
+ }
396
+ try {
397
+ const url = new URL(serverUrl);
398
+ url.hostname = `devbox.${url.hostname}`;
399
+ url.port = "";
400
+ url.pathname = "";
401
+ let result = url.toString();
402
+ if (result.endsWith("/")) {
403
+ result = result.slice(0, -1);
404
+ }
405
+ return result;
406
+ } catch (_urlError) {
407
+ return serverUrl;
408
+ }
409
+ } catch (_error) {
410
+ return null;
411
+ }
412
+ }
413
+
414
+ // src/api/auth.ts
415
+ var KubeconfigAuthenticator = class {
416
+ token;
417
+ constructor(kubeconfig) {
418
+ if (!kubeconfig || typeof kubeconfig !== "string") {
419
+ throw new DevboxSDKError(
420
+ "kubeconfig is required and must be a string",
421
+ ERROR_CODES.INVALID_KUBECONFIG
422
+ );
423
+ }
424
+ this.token = encodeURIComponent(kubeconfig);
425
+ }
426
+ getAuthHeaders() {
427
+ return {
428
+ Authorization: this.token
429
+ };
430
+ }
431
+ };
432
+
433
+ // src/api/endpoints.ts
434
+ var APIEndpoints = class {
435
+ baseUrl;
436
+ constructor(baseUrl = "https://devbox.usw.sealos.io/v1") {
437
+ this.baseUrl = baseUrl;
438
+ }
439
+ /**
440
+ * Get the base URL
441
+ */
442
+ getBaseUrl() {
443
+ return this.baseUrl;
444
+ }
445
+ /**
446
+ * Construct URL with parameters
447
+ */
448
+ constructUrl(template, params = {}) {
449
+ let url = template;
450
+ for (const [key, value] of Object.entries(params)) {
451
+ url = url.replace(`{${key}}`, encodeURIComponent(value));
452
+ }
453
+ const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl.slice(0, -1) : this.baseUrl;
454
+ return `${baseUrl}${url}`;
455
+ }
456
+ // Devbox management endpoints
457
+ devboxList() {
458
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.LIST);
459
+ }
460
+ devboxCreate() {
461
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.CREATE);
462
+ }
463
+ devboxGet(name) {
464
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.GET, { name });
465
+ }
466
+ devboxUpdate(name) {
467
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.UPDATE, { name });
468
+ }
469
+ devboxDelete(name) {
470
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.DELETE, { name });
471
+ }
472
+ devboxStart(name) {
473
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.START, { name });
474
+ }
475
+ devboxPause(name) {
476
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.PAUSE, { name });
477
+ }
478
+ devboxRestart(name) {
479
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RESTART, { name });
480
+ }
481
+ devboxShutdown(name) {
482
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.SHUTDOWN, { name });
483
+ }
484
+ devboxMonitor(name) {
485
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.MONITOR, { name });
486
+ }
487
+ devboxTemplates() {
488
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.TEMPLATES);
489
+ }
490
+ devboxPorts(name) {
491
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.PORTS, { name });
492
+ }
493
+ devboxAutostart(name) {
494
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.AUTOSTART, { name });
495
+ }
496
+ // Release endpoints
497
+ releaseList(name) {
498
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.LIST, { name });
499
+ }
500
+ releaseCreate(name) {
501
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.CREATE, { name });
502
+ }
503
+ releaseDelete(name, tag) {
504
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.DELETE, { name, tag });
505
+ }
506
+ releaseDeploy(name, tag) {
507
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.DEPLOY, { name, tag });
508
+ }
509
+ containerHealth(baseUrl) {
510
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.HEALTH}`;
511
+ }
512
+ filesWrite(baseUrl) {
513
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.WRITE}`;
514
+ }
515
+ filesRead(baseUrl) {
516
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.READ}`;
517
+ }
518
+ filesList(baseUrl) {
519
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.LIST}`;
520
+ }
521
+ filesDelete(baseUrl) {
522
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.DELETE}`;
523
+ }
524
+ filesBatchUpload(baseUrl) {
525
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.BATCH_UPLOAD}`;
526
+ }
527
+ filesBatchDownload(baseUrl) {
528
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.BATCH_DOWNLOAD}`;
529
+ }
530
+ filesSearch(baseUrl) {
531
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.SEARCH}`;
532
+ }
533
+ filesFind(baseUrl) {
534
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.FIND}`;
535
+ }
536
+ filesReplace(baseUrl) {
537
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.REPLACE}`;
538
+ }
539
+ processExec(baseUrl) {
540
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.PROCESS.EXEC}`;
541
+ }
542
+ processStatus(baseUrl, pid) {
543
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.PROCESS.STATUS.replace("{pid}", pid.toString())}`;
544
+ }
545
+ // Temporarily disabled - ws module removed
546
+ // websocket(baseUrl: string): string {
547
+ // return `${baseUrl}${API_ENDPOINTS.CONTAINER.WEBSOCKET}`
548
+ // }
549
+ };
550
+
551
+ // src/api/types.ts
552
+ var DevboxRuntime = /* @__PURE__ */ ((DevboxRuntime2) => {
553
+ DevboxRuntime2["NUXT3"] = "nuxt3";
554
+ DevboxRuntime2["ANGULAR"] = "angular";
555
+ DevboxRuntime2["QUARKUS"] = "quarkus";
556
+ DevboxRuntime2["UBUNTU"] = "ubuntu";
557
+ DevboxRuntime2["FLASK"] = "flask";
558
+ DevboxRuntime2["JAVA"] = "java";
559
+ DevboxRuntime2["CHI"] = "chi";
560
+ DevboxRuntime2["NET"] = "net";
561
+ DevboxRuntime2["IRIS"] = "iris";
562
+ DevboxRuntime2["HEXO"] = "hexo";
563
+ DevboxRuntime2["PYTHON"] = "python";
564
+ DevboxRuntime2["DOCUSAURUS"] = "docusaurus";
565
+ DevboxRuntime2["VITEPRESS"] = "vitepress";
566
+ DevboxRuntime2["CPP"] = "cpp";
567
+ DevboxRuntime2["VUE"] = "vue";
568
+ DevboxRuntime2["NGINX"] = "nginx";
569
+ DevboxRuntime2["ROCKET"] = "rocket";
570
+ DevboxRuntime2["DEBIAN_SSH"] = "debian-ssh";
571
+ DevboxRuntime2["VERT_X"] = "vert.x";
572
+ DevboxRuntime2["EXPRESS_JS"] = "express.js";
573
+ DevboxRuntime2["DJANGO"] = "django";
574
+ DevboxRuntime2["NEXT_JS"] = "next.js";
575
+ DevboxRuntime2["SEALAF"] = "sealaf";
576
+ DevboxRuntime2["GO"] = "go";
577
+ DevboxRuntime2["REACT"] = "react";
578
+ DevboxRuntime2["PHP"] = "php";
579
+ DevboxRuntime2["SVELTE"] = "svelte";
580
+ DevboxRuntime2["C"] = "c";
581
+ DevboxRuntime2["ASTRO"] = "astro";
582
+ DevboxRuntime2["UMI"] = "umi";
583
+ DevboxRuntime2["GIN"] = "gin";
584
+ DevboxRuntime2["NODE_JS"] = "node.js";
585
+ DevboxRuntime2["ECHO"] = "echo";
586
+ DevboxRuntime2["RUST"] = "rust";
587
+ DevboxRuntime2["TEST_AGENT"] = "node-expt-agent";
588
+ return DevboxRuntime2;
589
+ })(DevboxRuntime || {});
590
+
591
+ // src/api/client.ts
592
+ var SealosAPIClient = class {
593
+ baseUrl;
594
+ timeout;
595
+ retries;
596
+ rejectUnauthorized;
597
+ getAuthHeaders;
598
+ constructor(config) {
599
+ this.baseUrl = config.baseUrl || "https://devbox.usw.sealos.io";
600
+ this.timeout = config.timeout || 3e4;
601
+ this.retries = config.retries || 3;
602
+ this.rejectUnauthorized = config.rejectUnauthorized ?? process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0";
603
+ this.getAuthHeaders = config.getAuthHeaders;
604
+ if (!this.rejectUnauthorized) {
605
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
606
+ }
607
+ }
608
+ async request(method, path, options = {}) {
609
+ const url = new URL(path, this.baseUrl);
610
+ if (options.params) {
611
+ for (const [key, value] of Object.entries(options.params)) {
612
+ if (value !== void 0 && value !== null) {
613
+ url.searchParams.append(key, String(value));
614
+ }
615
+ }
616
+ }
617
+ const fetchOptions = {
618
+ method,
619
+ headers: {
620
+ "Content-Type": "application/json",
621
+ ...this.getAuthHeaders ? this.getAuthHeaders() : {},
622
+ ...options.headers
623
+ }
624
+ };
625
+ if (options.data) {
626
+ fetchOptions.body = JSON.stringify(options.data);
627
+ }
628
+ let lastError = new Error("Unknown error");
629
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
630
+ try {
631
+ const controller = new AbortController();
632
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
633
+ const response = await fetch(url.toString(), {
634
+ ...fetchOptions,
635
+ signal: controller.signal
636
+ });
637
+ clearTimeout(timeoutId);
638
+ if (!response.ok) {
639
+ let errorData = {};
640
+ try {
641
+ const contentType2 = response.headers.get("content-type") || "";
642
+ if (contentType2.includes("application/json")) {
643
+ errorData = await response.json();
644
+ } else {
645
+ const errorText = await response.text().catch(() => "Unable to read error response");
646
+ try {
647
+ errorData = JSON.parse(errorText);
648
+ } catch {
649
+ errorData = { error: errorText };
650
+ }
651
+ }
652
+ } catch (e) {
653
+ }
654
+ const errorMessage = errorData.error || response.statusText;
655
+ const errorCode = errorData.code || this.getErrorCodeFromStatus(response.status);
656
+ throw new DevboxSDKError(errorMessage, errorCode, {
657
+ status: response.status,
658
+ statusText: response.statusText,
659
+ timestamp: errorData.timestamp,
660
+ serverErrorCode: errorData.code
661
+ });
662
+ }
663
+ const contentType = response.headers.get("content-type");
664
+ const data = contentType?.includes("application/json") ? await response.json() : await response.text();
665
+ logger.info("Response data:", url.toString(), data);
666
+ return {
667
+ data,
668
+ status: response.status,
669
+ statusText: response.statusText,
670
+ headers: Object.fromEntries(response.headers.entries())
671
+ };
672
+ } catch (error) {
673
+ lastError = error;
674
+ if (attempt === this.retries || !this.shouldRetry(error)) {
675
+ break;
676
+ }
677
+ await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1e3));
678
+ }
679
+ }
680
+ throw lastError;
681
+ }
682
+ shouldRetry(error) {
683
+ if (error instanceof DevboxSDKError) {
684
+ const nonRetryable4xxCodes = [
685
+ ERROR_CODES.UNAUTHORIZED,
686
+ ERROR_CODES.INVALID_TOKEN,
687
+ ERROR_CODES.TOKEN_EXPIRED,
688
+ ERROR_CODES.INVALID_REQUEST,
689
+ ERROR_CODES.MISSING_REQUIRED_FIELD,
690
+ ERROR_CODES.INVALID_FIELD_VALUE,
691
+ ERROR_CODES.NOT_FOUND,
692
+ ERROR_CODES.FILE_NOT_FOUND,
693
+ ERROR_CODES.PROCESS_NOT_FOUND,
694
+ ERROR_CODES.SESSION_NOT_FOUND,
695
+ ERROR_CODES.CONFLICT,
696
+ ERROR_CODES.VALIDATION_ERROR,
697
+ ERROR_CODES.AUTHENTICATION_FAILED
698
+ ];
699
+ if (nonRetryable4xxCodes.includes(error.code)) {
700
+ return false;
701
+ }
702
+ return [
703
+ ERROR_CODES.CONNECTION_TIMEOUT,
704
+ ERROR_CODES.CONNECTION_FAILED,
705
+ ERROR_CODES.SERVER_UNAVAILABLE,
706
+ ERROR_CODES.SERVICE_UNAVAILABLE,
707
+ ERROR_CODES.OPERATION_TIMEOUT,
708
+ ERROR_CODES.SESSION_TIMEOUT,
709
+ ERROR_CODES.INTERNAL_ERROR
710
+ ].includes(error.code);
711
+ }
712
+ return error.name === "AbortError" || error.message.includes("fetch");
713
+ }
714
+ getErrorCodeFromStatus(status) {
715
+ switch (status) {
716
+ case 401:
717
+ return ERROR_CODES.AUTHENTICATION_FAILED;
718
+ case 403:
719
+ return ERROR_CODES.AUTHENTICATION_FAILED;
720
+ case 404:
721
+ return ERROR_CODES.DEVBOX_NOT_FOUND;
722
+ case 408:
723
+ return ERROR_CODES.CONNECTION_TIMEOUT;
724
+ case 429:
725
+ return "TOO_MANY_REQUESTS";
726
+ case 500:
727
+ return ERROR_CODES.INTERNAL_ERROR;
728
+ case 502:
729
+ return ERROR_CODES.SERVER_UNAVAILABLE;
730
+ case 503:
731
+ return ERROR_CODES.SERVICE_UNAVAILABLE;
732
+ case 504:
733
+ return ERROR_CODES.CONNECTION_TIMEOUT;
734
+ default:
735
+ return ERROR_CODES.INTERNAL_ERROR;
736
+ }
737
+ }
738
+ get(url, options) {
739
+ return this.request("GET", url, options);
740
+ }
741
+ post(url, options) {
742
+ return this.request("POST", url, options);
743
+ }
744
+ put(url, options) {
745
+ return this.request("PUT", url, options);
746
+ }
747
+ delete(url, options) {
748
+ return this.request("DELETE", url, options);
749
+ }
750
+ };
751
+ var DevboxAPI = class {
752
+ httpClient;
753
+ authenticator;
754
+ endpoints;
755
+ constructor(config) {
756
+ this.authenticator = new KubeconfigAuthenticator(config.kubeconfig);
757
+ const kubeconfigUrl = parseKubeconfigServerUrl(config.kubeconfig);
758
+ const baseUrl = config.baseUrl || kubeconfigUrl || "https://devbox.usw.sealos.io";
759
+ this.httpClient = new SealosAPIClient({
760
+ baseUrl,
761
+ timeout: config.timeout,
762
+ retries: config.retries,
763
+ rejectUnauthorized: config.rejectUnauthorized,
764
+ getAuthHeaders: () => this.authenticator.getAuthHeaders()
765
+ });
766
+ this.endpoints = new APIEndpoints(baseUrl);
767
+ }
768
+ /**
769
+ * Create a new Devbox instance
770
+ */
771
+ async createDevbox(config) {
772
+ const request = {
773
+ name: config.name,
774
+ runtime: config.runtime,
775
+ resource: config.resource,
776
+ ports: config.ports?.map((p) => ({ number: p.number, protocol: p.protocol })),
777
+ env: config.env
778
+ };
779
+ try {
780
+ const response = await this.httpClient.post(this.endpoints.devboxCreate(), {
781
+ data: request
782
+ });
783
+ if (!response || !response.data) {
784
+ throw new Error(
785
+ `Invalid API response: response or response.data is undefined. Response: ${JSON.stringify(response)}`
786
+ );
787
+ }
788
+ let createResponse;
789
+ if (typeof response.data === "object" && "data" in response.data) {
790
+ const responseData = response.data;
791
+ if (!responseData.data) {
792
+ throw new Error(
793
+ `Invalid API response structure: expected { data: DevboxCreateResponse }, but data field is undefined. Full response: ${JSON.stringify(response.data)}`
794
+ );
795
+ }
796
+ createResponse = responseData.data;
797
+ } else {
798
+ createResponse = response.data;
799
+ }
800
+ if (!createResponse || !createResponse.name) {
801
+ throw new Error(
802
+ `Invalid DevboxCreateResponse: missing 'name' field. Response data: ${JSON.stringify(createResponse)}`
803
+ );
804
+ }
805
+ return this.transformCreateResponseToDevboxInfo(
806
+ createResponse,
807
+ config.runtime,
808
+ config.resource
809
+ );
810
+ } catch (error) {
811
+ throw this.handleAPIError(error, "Failed to create Devbox");
812
+ }
813
+ }
814
+ /**
815
+ * Get an existing Devbox instance
816
+ */
817
+ async getDevbox(name) {
818
+ try {
819
+ const response = await this.httpClient.get(this.endpoints.devboxGet(name));
820
+ const responseData = response.data;
821
+ return this.transformDetailToDevboxInfo(responseData.data);
822
+ } catch (error) {
823
+ throw this.handleAPIError(error, `Failed to get Devbox '${name}'`);
824
+ }
825
+ }
826
+ /**
827
+ * List all Devbox instances
828
+ */
829
+ async listDevboxes() {
830
+ try {
831
+ const response = await this.httpClient.get(this.endpoints.devboxList());
832
+ const listResponse = response.data;
833
+ return listResponse.data.map(this.transformListItemToDevboxInfo);
834
+ } catch (error) {
835
+ throw this.handleAPIError(error, "Failed to list Devboxes");
836
+ }
837
+ }
838
+ /**
839
+ * Start a Devbox instance
840
+ */
841
+ async startDevbox(name) {
842
+ try {
843
+ await this.httpClient.post(this.endpoints.devboxStart(name), {
844
+ data: {}
845
+ });
846
+ } catch (error) {
847
+ throw this.handleAPIError(error, `Failed to start Devbox '${name}'`);
848
+ }
849
+ }
850
+ /**
851
+ * Pause a Devbox instance
852
+ */
853
+ async pauseDevbox(name) {
854
+ try {
855
+ await this.httpClient.post(this.endpoints.devboxPause(name), {
856
+ data: {}
857
+ });
858
+ } catch (error) {
859
+ throw this.handleAPIError(error, `Failed to pause Devbox '${name}'`);
860
+ }
861
+ }
862
+ /**
863
+ * Restart a Devbox instance
864
+ */
865
+ async restartDevbox(name) {
866
+ try {
867
+ await this.httpClient.post(this.endpoints.devboxRestart(name), {
868
+ data: {}
869
+ });
870
+ } catch (error) {
871
+ throw this.handleAPIError(error, `Failed to restart Devbox '${name}'`);
872
+ }
873
+ }
874
+ /**
875
+ * Delete a Devbox instance
876
+ */
877
+ async deleteDevbox(name) {
878
+ try {
879
+ await this.httpClient.delete(this.endpoints.devboxDelete(name));
880
+ } catch (error) {
881
+ throw this.handleAPIError(error, `Failed to delete Devbox '${name}'`);
882
+ }
883
+ }
884
+ /**
885
+ * Update a Devbox instance configuration
886
+ */
887
+ async updateDevbox(name, config) {
888
+ try {
889
+ await this.httpClient.request("PATCH", this.endpoints.devboxUpdate(name), {
890
+ data: config
891
+ });
892
+ } catch (error) {
893
+ throw this.handleAPIError(error, `Failed to update Devbox '${name}'`);
894
+ }
895
+ }
896
+ /**
897
+ * Shutdown a Devbox instance
898
+ */
899
+ async shutdownDevbox(name) {
900
+ try {
901
+ await this.httpClient.post(this.endpoints.devboxShutdown(name), {
902
+ data: {}
903
+ });
904
+ } catch (error) {
905
+ throw this.handleAPIError(error, `Failed to shutdown Devbox '${name}'`);
906
+ }
907
+ }
908
+ /**
909
+ * Get available runtime templates
910
+ */
911
+ async getTemplates() {
912
+ try {
913
+ const response = await this.httpClient.get(
914
+ this.endpoints.devboxTemplates()
915
+ );
916
+ return response.data.data;
917
+ } catch (error) {
918
+ throw this.handleAPIError(error, "Failed to get templates");
919
+ }
920
+ }
921
+ /**
922
+ * Update port configuration for a Devbox
923
+ */
924
+ async updatePorts(name, ports) {
925
+ try {
926
+ await this.httpClient.put(this.endpoints.devboxPorts(name), {
927
+ data: { ports }
928
+ });
929
+ } catch (error) {
930
+ throw this.handleAPIError(error, `Failed to update ports for '${name}'`);
931
+ }
932
+ }
933
+ /**
934
+ * Configure autostart for a Devbox
935
+ */
936
+ async configureAutostart(name, config) {
937
+ try {
938
+ await this.httpClient.post(this.endpoints.devboxAutostart(name), {
939
+ data: config || {}
940
+ });
941
+ } catch (error) {
942
+ throw this.handleAPIError(error, `Failed to configure autostart for '${name}'`);
943
+ }
944
+ }
945
+ /**
946
+ * List releases for a Devbox
947
+ */
948
+ async listReleases(name) {
949
+ try {
950
+ const response = await this.httpClient.get(this.endpoints.releaseList(name));
951
+ const responseData = response.data;
952
+ return responseData.data || [];
953
+ } catch (error) {
954
+ throw this.handleAPIError(error, `Failed to list releases for '${name}'`);
955
+ }
956
+ }
957
+ /**
958
+ * Create a release for a Devbox
959
+ */
960
+ async createRelease(name, config) {
961
+ try {
962
+ await this.httpClient.post(this.endpoints.releaseCreate(name), {
963
+ data: config
964
+ });
965
+ } catch (error) {
966
+ throw this.handleAPIError(error, `Failed to create release for '${name}'`);
967
+ }
968
+ }
969
+ /**
970
+ * Delete a release
971
+ */
972
+ async deleteRelease(name, tag) {
973
+ try {
974
+ await this.httpClient.delete(this.endpoints.releaseDelete(name, tag));
975
+ } catch (error) {
976
+ throw this.handleAPIError(error, `Failed to delete release '${tag}' for '${name}'`);
977
+ }
978
+ }
979
+ /**
980
+ * Deploy a release
981
+ */
982
+ async deployRelease(name, tag) {
983
+ try {
984
+ await this.httpClient.post(this.endpoints.releaseDeploy(name, tag), {
985
+ data: {}
986
+ });
987
+ } catch (error) {
988
+ throw this.handleAPIError(error, `Failed to deploy release '${tag}' for '${name}'`);
989
+ }
990
+ }
991
+ /**
992
+ * Get monitoring data for a Devbox instance
993
+ */
994
+ async getMonitorData(name, timeRange) {
995
+ try {
996
+ const params = {
997
+ start: timeRange?.start || Date.now() - 36e5,
998
+ // Default 1 hour ago
999
+ end: timeRange?.end || Date.now(),
1000
+ step: timeRange?.step
1001
+ };
1002
+ const response = await this.httpClient.get(this.endpoints.devboxMonitor(name), {
1003
+ params
1004
+ });
1005
+ const dataPoints = response.data;
1006
+ return dataPoints.map(this.transformMonitorData);
1007
+ } catch (error) {
1008
+ throw this.handleAPIError(error, `Failed to get monitor data for '${name}'`);
1009
+ }
1010
+ }
1011
+ /**
1012
+ * Test authentication
1013
+ */
1014
+ async testAuth() {
1015
+ try {
1016
+ await this.httpClient.get(this.endpoints.devboxList());
1017
+ return true;
1018
+ } catch (error) {
1019
+ return false;
1020
+ }
1021
+ }
1022
+ transformSSHInfoToDevboxInfo(sshInfo) {
1023
+ return {
1024
+ name: sshInfo.name,
1025
+ status: sshInfo.status,
1026
+ runtime: sshInfo.runtime,
1027
+ resources: sshInfo.resources,
1028
+ podIP: sshInfo.podIP,
1029
+ ssh: sshInfo.ssh ? {
1030
+ host: sshInfo.ssh.host,
1031
+ port: sshInfo.ssh.port,
1032
+ user: sshInfo.ssh.user,
1033
+ privateKey: sshInfo.ssh.privateKey
1034
+ } : void 0
1035
+ };
1036
+ }
1037
+ transformListItemToDevboxInfo(listItem) {
1038
+ return {
1039
+ name: listItem.name,
1040
+ status: listItem.status,
1041
+ runtime: listItem.runtime,
1042
+ resources: listItem.resources
1043
+ };
1044
+ }
1045
+ /**
1046
+ * Safely convert a string to DevboxRuntime enum
1047
+ * Returns the enum value if valid, otherwise returns a default value
1048
+ */
1049
+ stringToRuntime(value) {
1050
+ if (!value) {
1051
+ return "node-expt-agent" /* TEST_AGENT */;
1052
+ }
1053
+ const runtimeValues = Object.values(DevboxRuntime);
1054
+ if (runtimeValues.includes(value)) {
1055
+ return value;
1056
+ }
1057
+ return "node-expt-agent" /* TEST_AGENT */;
1058
+ }
1059
+ transformCreateResponseToDevboxInfo(createResponse, runtime, resource) {
1060
+ return {
1061
+ name: createResponse.name,
1062
+ status: "Pending",
1063
+ // New devboxes start in Pending state
1064
+ runtime,
1065
+ // Use the runtime from the create request
1066
+ resources: {
1067
+ cpu: resource.cpu,
1068
+ // Use the resource from the create request
1069
+ memory: resource.memory
1070
+ // Use the resource from the create request
1071
+ },
1072
+ ssh: {
1073
+ host: createResponse.domain,
1074
+ port: createResponse.sshPort,
1075
+ user: createResponse.userName,
1076
+ privateKey: createResponse.base64PrivateKey
1077
+ }
1078
+ };
1079
+ }
1080
+ /**
1081
+ * Transform DevboxDetail (actual API response) to DevboxInfo
1082
+ */
1083
+ transformDetailToDevboxInfo(detail) {
1084
+ const runtime = typeof detail.runtime === "string" ? this.stringToRuntime(detail.runtime) : detail.runtime;
1085
+ const ssh = detail.ssh?.privateKey ? {
1086
+ host: detail.ssh.host,
1087
+ port: detail.ssh.port,
1088
+ user: detail.ssh.user,
1089
+ privateKey: detail.ssh.privateKey
1090
+ } : void 0;
1091
+ let podIP;
1092
+ if (detail.pods && detail.pods.length > 0) ;
1093
+ return {
1094
+ name: detail.name,
1095
+ status: detail.status,
1096
+ runtime,
1097
+ resources: detail.resources,
1098
+ podIP,
1099
+ ssh,
1100
+ ports: detail.ports,
1101
+ agentServer: detail.agentServer
1102
+ };
1103
+ }
1104
+ /**
1105
+ * Transform DevboxGetResponse to DevboxInfo (legacy method, kept for backward compatibility)
1106
+ */
1107
+ transformGetResponseToDevboxInfo(getResponse) {
1108
+ const status = typeof getResponse.status === "string" ? getResponse.status : getResponse.status.value;
1109
+ const resources = getResponse.resources || {
1110
+ cpu: getResponse.cpu || 0,
1111
+ memory: getResponse.memory || 0
1112
+ };
1113
+ const runtime = getResponse.runtime ? this.stringToRuntime(getResponse.runtime) : getResponse.iconId ? this.stringToRuntime(getResponse.iconId) : "node-expt-agent" /* TEST_AGENT */;
1114
+ return {
1115
+ name: getResponse.name,
1116
+ status,
1117
+ runtime,
1118
+ resources
1119
+ };
1120
+ }
1121
+ transformMonitorData(dataPoint) {
1122
+ return {
1123
+ cpu: dataPoint.cpu,
1124
+ memory: dataPoint.memory,
1125
+ network: dataPoint.network,
1126
+ disk: dataPoint.disk,
1127
+ timestamp: dataPoint.timestamp
1128
+ };
1129
+ }
1130
+ handleAPIError(error, context) {
1131
+ if (error instanceof DevboxSDKError) {
1132
+ return error;
1133
+ }
1134
+ const message = error instanceof Error ? error.message : String(error);
1135
+ return new DevboxSDKError(`${context}: ${message}`, ERROR_CODES.INTERNAL_ERROR, {
1136
+ originalError: error
1137
+ });
1138
+ }
1139
+ };
1140
+
1141
+ // src/http/client.ts
1142
+ var DevboxContainerClient = class {
1143
+ baseUrl;
1144
+ timeout;
1145
+ token;
1146
+ constructor(baseUrl, timeout, token) {
1147
+ this.baseUrl = baseUrl;
1148
+ this.timeout = timeout;
1149
+ this.token = token;
1150
+ }
1151
+ async get(path, options) {
1152
+ return this.request("GET", path, options);
1153
+ }
1154
+ async post(path, options) {
1155
+ return this.request("POST", path, options);
1156
+ }
1157
+ async put(path, options) {
1158
+ return this.request("PUT", path, options);
1159
+ }
1160
+ async delete(path, options) {
1161
+ return this.request("DELETE", path, options);
1162
+ }
1163
+ async request(method, path, options) {
1164
+ const url = new URL(path, this.baseUrl);
1165
+ if (options?.params) {
1166
+ for (const [key, value] of Object.entries(options.params)) {
1167
+ if (value !== void 0 && value !== null) {
1168
+ url.searchParams.append(key, String(value));
1169
+ }
1170
+ }
1171
+ }
1172
+ const isFormData = options?.body !== void 0 && options.body instanceof FormData;
1173
+ const fetchOptions = {
1174
+ method,
1175
+ headers: {
1176
+ ...isFormData ? {} : { "Content-Type": "application/json" },
1177
+ ...options?.headers,
1178
+ // Decode base64 token and use as Bearer token
1179
+ Authorization: `Bearer ${Buffer.from(this.token, "base64").toString("utf-8")}`
1180
+ },
1181
+ signal: options?.signal
1182
+ };
1183
+ if (options?.body !== void 0) {
1184
+ if (isFormData) {
1185
+ fetchOptions.body = options.body;
1186
+ } else if (typeof options.body === "string") {
1187
+ fetchOptions.body = options.body;
1188
+ } else if (Buffer.isBuffer(options.body) || options.body instanceof ArrayBuffer || options.body instanceof Uint8Array) {
1189
+ fetchOptions.body = options.body;
1190
+ } else {
1191
+ fetchOptions.body = JSON.stringify(options.body);
1192
+ }
1193
+ }
1194
+ const controller = new AbortController();
1195
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1196
+ try {
1197
+ const response = await fetch(url.toString(), {
1198
+ ...fetchOptions,
1199
+ signal: options?.signal || controller.signal
1200
+ });
1201
+ logger.info("Request URL:", url.toString());
1202
+ clearTimeout(timeoutId);
1203
+ if (!response.ok) {
1204
+ let errorData = {};
1205
+ try {
1206
+ const contentType2 = response.headers.get("content-type") || "";
1207
+ if (contentType2.includes("application/json")) {
1208
+ errorData = await response.json();
1209
+ }
1210
+ } catch {
1211
+ }
1212
+ const errorMessage = errorData.error || response.statusText;
1213
+ const errorCode = errorData.code || ERROR_CODES.CONNECTION_FAILED;
1214
+ throw new DevboxSDKError(errorMessage, errorCode, {
1215
+ status: response.status,
1216
+ statusText: response.statusText,
1217
+ timestamp: errorData.timestamp,
1218
+ serverErrorCode: errorData.code
1219
+ });
1220
+ }
1221
+ const contentType = response.headers.get("content-type") || "";
1222
+ let data;
1223
+ if (contentType.includes("application/json")) {
1224
+ const jsonData = await response.json();
1225
+ data = parseServerResponse(jsonData);
1226
+ } else if (contentType.includes("application/octet-stream") || contentType.includes("application/gzip") || contentType.includes("application/x-tar") || contentType.includes("multipart/") || contentType.includes("image/") || contentType.includes("video/") || contentType.includes("audio/")) {
1227
+ const arrayBuffer = await response.arrayBuffer();
1228
+ data = Buffer.from(arrayBuffer);
1229
+ } else {
1230
+ const arrayBuffer = await response.arrayBuffer();
1231
+ data = Buffer.from(arrayBuffer);
1232
+ }
1233
+ return {
1234
+ data,
1235
+ status: response.status,
1236
+ headers: Object.fromEntries(response.headers.entries()),
1237
+ url: response.url
1238
+ };
1239
+ } catch (error) {
1240
+ logger.error("Request failed:", error);
1241
+ clearTimeout(timeoutId);
1242
+ if (error instanceof DevboxSDKError) {
1243
+ throw error;
1244
+ }
1245
+ throw new DevboxSDKError(
1246
+ `Request failed: ${error.message}`,
1247
+ ERROR_CODES.CONNECTION_FAILED,
1248
+ { originalError: error.message }
1249
+ );
1250
+ }
1251
+ }
1252
+ };
1253
+
1254
+ // src/http/manager.ts
1255
+ var ContainerUrlResolver = class {
1256
+ apiClient;
1257
+ cache = /* @__PURE__ */ new Map();
1258
+ CACHE_TTL = 6e4;
1259
+ mockServerUrl;
1260
+ baseUrl;
1261
+ timeout;
1262
+ constructor(config) {
1263
+ this.mockServerUrl = config.mockServerUrl || process.env.MOCK_SERVER_URL;
1264
+ const kubeconfigUrl = config.kubeconfig ? parseKubeconfigServerUrl(config.kubeconfig) : null;
1265
+ this.baseUrl = config.baseUrl || kubeconfigUrl || "https://devbox.usw.sealos.io";
1266
+ this.timeout = config.http?.timeout || 3e4;
1267
+ }
1268
+ setAPIClient(apiClient) {
1269
+ this.apiClient = apiClient;
1270
+ }
1271
+ async executeWithConnection(devboxName, operation) {
1272
+ const devboxInfo = await this.getDevboxInfo(devboxName);
1273
+ const serverUrl = this.extractUrlFromDevboxInfo(devboxInfo);
1274
+ const token = devboxInfo?.agentServer?.token;
1275
+ if (!serverUrl || !token) {
1276
+ throw new DevboxNotReadyError(devboxName, devboxInfo?.status, {
1277
+ hasServerUrl: !!serverUrl,
1278
+ hasToken: !!token,
1279
+ currentStatus: devboxInfo?.status
1280
+ });
1281
+ }
1282
+ const client = new DevboxContainerClient(serverUrl, this.timeout, token);
1283
+ return await operation(client);
1284
+ }
1285
+ async getServerUrl(devboxName) {
1286
+ const configuredUrl = this.getConfiguredServerUrl();
1287
+ if (configuredUrl) {
1288
+ return configuredUrl;
1289
+ }
1290
+ if (!this.apiClient) {
1291
+ throw new DevboxSDKError(
1292
+ "API client not set. Call setAPIClient() first.",
1293
+ ERROR_CODES.INTERNAL_ERROR
1294
+ );
1295
+ }
1296
+ const cached = this.getFromCache(`url:${devboxName}`);
1297
+ if (cached && typeof cached === "string") {
1298
+ return cached;
1299
+ }
1300
+ try {
1301
+ const url = await this.resolveServerUrlFromAPI(devboxName);
1302
+ this.setCache(`url:${devboxName}`, url);
1303
+ return url;
1304
+ } catch (error) {
1305
+ if (error instanceof DevboxSDKError) {
1306
+ throw error;
1307
+ }
1308
+ throw new DevboxSDKError(
1309
+ `Failed to get server URL for '${devboxName}': ${error.message}`,
1310
+ ERROR_CODES.CONNECTION_FAILED,
1311
+ { originalError: error.message }
1312
+ );
1313
+ }
1314
+ }
1315
+ getConfiguredServerUrl() {
1316
+ if (this.mockServerUrl) {
1317
+ return this.mockServerUrl;
1318
+ }
1319
+ return null;
1320
+ }
1321
+ async resolveServerUrlFromAPI(devboxName) {
1322
+ const devboxInfo = await this.getDevboxInfo(devboxName);
1323
+ if (!devboxInfo) {
1324
+ throw new DevboxSDKError(`Devbox '${devboxName}' not found`, ERROR_CODES.DEVBOX_NOT_FOUND);
1325
+ }
1326
+ const url = this.extractUrlFromDevboxInfo(devboxInfo);
1327
+ if (!url) {
1328
+ throw new DevboxSDKError(
1329
+ `Devbox '${devboxName}' does not have an accessible URL`,
1330
+ ERROR_CODES.CONNECTION_FAILED
1331
+ );
1332
+ }
1333
+ return url;
1334
+ }
1335
+ extractUrlFromDevboxInfo(devboxInfo) {
1336
+ if (devboxInfo.agentServer?.url) {
1337
+ const serviceName = devboxInfo.agentServer.url;
1338
+ const urlObj = new URL(this.baseUrl);
1339
+ const domain = urlObj.hostname.replace(/^devbox\./, "");
1340
+ return `${urlObj.protocol}//devbox-${serviceName}-agent.${domain}`;
1341
+ }
1342
+ if (devboxInfo.ports && devboxInfo.ports.length > 0) {
1343
+ const port = devboxInfo.ports[0];
1344
+ if (port?.publicAddress) {
1345
+ return port.publicAddress;
1346
+ }
1347
+ if (port?.privateAddress) {
1348
+ return port.privateAddress;
1349
+ }
1350
+ }
1351
+ if (devboxInfo.podIP) {
1352
+ return `http://${devboxInfo.podIP}:3000`;
1353
+ }
1354
+ return null;
1355
+ }
1356
+ async getDevboxInfo(devboxName) {
1357
+ const cached = this.getFromCache(`devbox:${devboxName}`);
1358
+ if (cached) {
1359
+ return cached;
1360
+ }
1361
+ try {
1362
+ if (!this.apiClient) {
1363
+ throw new Error("API client not set");
1364
+ }
1365
+ const devboxInfo = await this.apiClient.getDevbox(devboxName);
1366
+ this.setCache(`devbox:${devboxName}`, devboxInfo);
1367
+ return devboxInfo;
1368
+ } catch (error) {
1369
+ return null;
1370
+ }
1371
+ }
1372
+ getFromCache(key) {
1373
+ const entry = this.cache.get(key);
1374
+ if (!entry) return null;
1375
+ if (Date.now() - entry.timestamp > this.CACHE_TTL) {
1376
+ this.cache.delete(key);
1377
+ return null;
1378
+ }
1379
+ return entry.data;
1380
+ }
1381
+ setCache(key, data) {
1382
+ this.cache.set(key, {
1383
+ data,
1384
+ timestamp: Date.now()
1385
+ });
1386
+ }
1387
+ clearCache() {
1388
+ this.cache.clear();
1389
+ }
1390
+ async closeAllConnections() {
1391
+ this.clearCache();
1392
+ }
1393
+ async checkDevboxHealth(devboxName) {
1394
+ try {
1395
+ const devboxInfo = await this.getDevboxInfo(devboxName);
1396
+ const serverUrl = this.extractUrlFromDevboxInfo(devboxInfo);
1397
+ if (!serverUrl) return false;
1398
+ const token = devboxInfo?.agentServer?.token;
1399
+ if (!token) return false;
1400
+ const client = new DevboxContainerClient(serverUrl, this.timeout, token);
1401
+ const response = await client.get("/health");
1402
+ return response.data?.healthStatus === "ok";
1403
+ } catch (error) {
1404
+ return false;
1405
+ }
1406
+ }
1407
+ };
1408
+
1409
+ // src/core/git/git.ts
1410
+ var Git = class {
1411
+ constructor(deps) {
1412
+ this.deps = deps;
1413
+ }
1414
+ /**
1415
+ * Build Git URL with authentication
1416
+ */
1417
+ buildAuthUrl(url, auth) {
1418
+ if (!auth) return url;
1419
+ if (auth.token) {
1420
+ const urlMatch = url.match(/^(https?:\/\/)([^@]+@)?([^\/]+)(\/.+)?$/);
1421
+ if (urlMatch) {
1422
+ const [, protocol, , host, path] = urlMatch;
1423
+ return `${protocol}${auth.token}@${host}${path || ""}`;
1424
+ }
1425
+ }
1426
+ if (auth.username && (auth.password || auth.token)) {
1427
+ const urlMatch = url.match(/^(https?:\/\/)([^\/]+)(\/.+)?$/);
1428
+ if (urlMatch) {
1429
+ const [, protocol, host, path] = urlMatch;
1430
+ const password = auth.password || auth.token || "";
1431
+ return `${protocol}${auth.username}:${password}@${host}${path || ""}`;
1432
+ }
1433
+ }
1434
+ return url;
1435
+ }
1436
+ /**
1437
+ * Setup Git authentication environment variables
1438
+ */
1439
+ setupGitAuth(env = {}, auth) {
1440
+ const gitEnv = { ...env };
1441
+ if (auth?.username) {
1442
+ gitEnv.GIT_USERNAME = auth.username;
1443
+ }
1444
+ if (auth?.password) {
1445
+ gitEnv.GIT_PASSWORD = auth.password;
1446
+ } else if (auth?.token) {
1447
+ gitEnv.GIT_PASSWORD = auth.token;
1448
+ }
1449
+ return gitEnv;
1450
+ }
1451
+ /**
1452
+ * Parse Git branch list output
1453
+ */
1454
+ parseGitBranches(stdout, currentBranch) {
1455
+ const lines = stdout.split("\n").filter(Boolean);
1456
+ const branches = [];
1457
+ for (const line of lines) {
1458
+ const trimmed = line.trim();
1459
+ if (!trimmed) continue;
1460
+ const isCurrent = trimmed.startsWith("*");
1461
+ const isRemote = trimmed.includes("remotes/");
1462
+ let name = trimmed.replace(/^\*\s*/, "").trim();
1463
+ if (isRemote) {
1464
+ const match = name.match(/^remotes\/[^/]+\/(.+)$/);
1465
+ if (match?.[1]) {
1466
+ name = match[1];
1467
+ } else {
1468
+ continue;
1469
+ }
1470
+ }
1471
+ branches.push({
1472
+ name,
1473
+ isCurrent: name === currentBranch || isCurrent,
1474
+ isRemote,
1475
+ commit: ""
1476
+ // Will be filled by additional git command if needed
1477
+ });
1478
+ }
1479
+ return branches;
1480
+ }
1481
+ /**
1482
+ * Parse Git status output
1483
+ */
1484
+ parseGitStatus(stdout, branchLine) {
1485
+ const lines = stdout.split("\n").filter(Boolean);
1486
+ const staged = [];
1487
+ const modified = [];
1488
+ const untracked = [];
1489
+ const deleted = [];
1490
+ for (const line of lines) {
1491
+ if (line.length < 3) continue;
1492
+ const status = line.substring(0, 2);
1493
+ const file = line.substring(3).trim();
1494
+ if (status[0] === "A" || status[0] === "M" || status[0] === "R" || status[0] === "C") {
1495
+ staged.push(file);
1496
+ }
1497
+ if (status[1] === "M" || status[1] === "D") {
1498
+ modified.push(file);
1499
+ }
1500
+ if (status === "??") {
1501
+ untracked.push(file);
1502
+ }
1503
+ if (status[0] === "D" || status[1] === "D") {
1504
+ deleted.push(file);
1505
+ }
1506
+ }
1507
+ let currentBranch = "main";
1508
+ let ahead = 0;
1509
+ let behind = 0;
1510
+ if (branchLine) {
1511
+ const branchMatch = branchLine.match(/^##\s+([^.]+)/);
1512
+ if (branchMatch?.[1]) {
1513
+ currentBranch = branchMatch[1];
1514
+ }
1515
+ const aheadMatch = branchLine.match(/ahead\s+(\d+)/);
1516
+ if (aheadMatch?.[1]) {
1517
+ ahead = Number.parseInt(aheadMatch[1], 10);
1518
+ }
1519
+ const behindMatch = branchLine.match(/behind\s+(\d+)/);
1520
+ if (behindMatch?.[1]) {
1521
+ behind = Number.parseInt(behindMatch[1], 10);
1522
+ }
1523
+ }
1524
+ const isClean = staged.length === 0 && modified.length === 0 && untracked.length === 0 && deleted.length === 0;
1525
+ return {
1526
+ currentBranch,
1527
+ isClean,
1528
+ ahead,
1529
+ behind,
1530
+ staged,
1531
+ modified,
1532
+ untracked,
1533
+ deleted
1534
+ };
1535
+ }
1536
+ /**
1537
+ * Clone a Git repository
1538
+ */
1539
+ async clone(options) {
1540
+ const args = ["clone"];
1541
+ if (options.branch) {
1542
+ args.push("-b", options.branch);
1543
+ }
1544
+ if (options.depth) {
1545
+ args.push("--depth", String(options.depth));
1546
+ }
1547
+ if (options.commit) {
1548
+ args.push("--single-branch");
1549
+ }
1550
+ const authUrl = this.buildAuthUrl(options.url, options.auth);
1551
+ args.push(authUrl);
1552
+ if (options.targetDir) {
1553
+ args.push(options.targetDir);
1554
+ }
1555
+ const env = this.setupGitAuth({}, options.auth);
1556
+ const result = await this.deps.execSync({
1557
+ command: "git",
1558
+ args,
1559
+ env,
1560
+ timeout: 300
1561
+ // 5 minutes timeout for clone
1562
+ });
1563
+ if (result.exitCode !== 0) {
1564
+ throw new Error(`Git clone failed: ${result.stderr || result.stdout}`);
1565
+ }
1566
+ if (options.commit && options.targetDir) {
1567
+ await this.deps.execSync({
1568
+ command: "git",
1569
+ args: ["checkout", options.commit],
1570
+ cwd: options.targetDir
1571
+ });
1572
+ }
1573
+ }
1574
+ /**
1575
+ * Pull changes from remote repository
1576
+ */
1577
+ async pull(repoPath, options) {
1578
+ const remote = options?.remote || "origin";
1579
+ if (options?.auth) {
1580
+ const urlResult = await this.deps.execSync({
1581
+ command: "git",
1582
+ args: ["remote", "get-url", remote],
1583
+ cwd: repoPath
1584
+ });
1585
+ if (urlResult.exitCode === 0) {
1586
+ const currentUrl = urlResult.stdout.trim();
1587
+ const authUrl = this.buildAuthUrl(currentUrl, options.auth);
1588
+ await this.deps.execSync({
1589
+ command: "git",
1590
+ args: ["remote", "set-url", remote, authUrl],
1591
+ cwd: repoPath
1592
+ });
1593
+ }
1594
+ }
1595
+ const args = ["pull"];
1596
+ if (options?.branch) {
1597
+ args.push(remote, options.branch);
1598
+ }
1599
+ const result = await this.deps.execSync({
1600
+ command: "git",
1601
+ args,
1602
+ cwd: repoPath,
1603
+ timeout: 120
1604
+ // 2 minutes timeout
1605
+ });
1606
+ if (result.exitCode !== 0) {
1607
+ throw new Error(`Git pull failed: ${result.stderr || result.stdout}`);
1608
+ }
1609
+ }
1610
+ /**
1611
+ * Push changes to remote repository
1612
+ */
1613
+ async push(repoPath, options) {
1614
+ const remote = options?.remote || "origin";
1615
+ if (options?.auth) {
1616
+ const urlResult = await this.deps.execSync({
1617
+ command: "git",
1618
+ args: ["remote", "get-url", remote],
1619
+ cwd: repoPath
1620
+ });
1621
+ if (urlResult.exitCode === 0) {
1622
+ const currentUrl = urlResult.stdout.trim();
1623
+ const authUrl = this.buildAuthUrl(currentUrl, options.auth);
1624
+ await this.deps.execSync({
1625
+ command: "git",
1626
+ args: ["remote", "set-url", remote, authUrl],
1627
+ cwd: repoPath
1628
+ });
1629
+ }
1630
+ }
1631
+ const args = ["push"];
1632
+ if (options?.force) {
1633
+ args.push("--force");
1634
+ }
1635
+ if (options?.branch) {
1636
+ args.push(remote, options.branch);
1637
+ } else {
1638
+ args.push(remote);
1639
+ }
1640
+ const result = await this.deps.execSync({
1641
+ command: "git",
1642
+ args,
1643
+ cwd: repoPath,
1644
+ timeout: 120
1645
+ // 2 minutes timeout
1646
+ });
1647
+ if (result.exitCode !== 0) {
1648
+ throw new Error(`Git push failed: ${result.stderr || result.stdout}`);
1649
+ }
1650
+ }
1651
+ /**
1652
+ * List all branches
1653
+ */
1654
+ async branches(repoPath) {
1655
+ const currentBranchResult = await this.deps.execSync({
1656
+ command: "git",
1657
+ args: ["rev-parse", "--abbrev-ref", "HEAD"],
1658
+ cwd: repoPath
1659
+ });
1660
+ const currentBranch = currentBranchResult.stdout.trim();
1661
+ const branchesResult = await this.deps.execSync({
1662
+ command: "git",
1663
+ args: ["branch", "-a"],
1664
+ cwd: repoPath
1665
+ });
1666
+ if (branchesResult.exitCode !== 0) {
1667
+ throw new Error(`Git branches failed: ${branchesResult.stderr || branchesResult.stdout}`);
1668
+ }
1669
+ const branches = this.parseGitBranches(branchesResult.stdout, currentBranch);
1670
+ for (const branch of branches) {
1671
+ try {
1672
+ const commitResult = await this.deps.execSync({
1673
+ command: "git",
1674
+ args: ["rev-parse", branch.isRemote ? `origin/${branch.name}` : branch.name],
1675
+ cwd: repoPath
1676
+ });
1677
+ if (commitResult.exitCode === 0) {
1678
+ branch.commit = commitResult.stdout.trim();
1679
+ }
1680
+ } catch {
1681
+ }
1682
+ }
1683
+ return branches;
1684
+ }
1685
+ /**
1686
+ * Create a new branch
1687
+ */
1688
+ async createBranch(repoPath, branchName, checkout = false) {
1689
+ const args = checkout ? ["checkout", "-b", branchName] : ["branch", branchName];
1690
+ const result = await this.deps.execSync({
1691
+ command: "git",
1692
+ args,
1693
+ cwd: repoPath
1694
+ });
1695
+ if (result.exitCode !== 0) {
1696
+ throw new Error(`Git create branch failed: ${result.stderr || result.stdout}`);
1697
+ }
1698
+ }
1699
+ /**
1700
+ * Delete a branch
1701
+ */
1702
+ async deleteBranch(repoPath, branchName, force = false, remote = false) {
1703
+ if (remote) {
1704
+ const result = await this.deps.execSync({
1705
+ command: "git",
1706
+ args: ["push", "origin", "--delete", branchName],
1707
+ cwd: repoPath
1708
+ });
1709
+ if (result.exitCode !== 0) {
1710
+ throw new Error(`Git delete remote branch failed: ${result.stderr || result.stdout}`);
1711
+ }
1712
+ } else {
1713
+ const args = force ? ["branch", "-D", branchName] : ["branch", "-d", branchName];
1714
+ const result = await this.deps.execSync({
1715
+ command: "git",
1716
+ args,
1717
+ cwd: repoPath
1718
+ });
1719
+ if (result.exitCode !== 0) {
1720
+ throw new Error(`Git delete branch failed: ${result.stderr || result.stdout}`);
1721
+ }
1722
+ }
1723
+ }
1724
+ /**
1725
+ * Checkout a branch
1726
+ */
1727
+ async checkoutBranch(repoPath, branchName, create = false) {
1728
+ const args = create ? ["checkout", "-b", branchName] : ["checkout", branchName];
1729
+ const result = await this.deps.execSync({
1730
+ command: "git",
1731
+ args,
1732
+ cwd: repoPath
1733
+ });
1734
+ if (result.exitCode !== 0) {
1735
+ throw new Error(`Git checkout failed: ${result.stderr || result.stdout}`);
1736
+ }
1737
+ }
1738
+ normalizePath(repoPath, filePath) {
1739
+ const normalize = (p) => {
1740
+ let normalized = p.trim();
1741
+ if (normalized.startsWith("./")) {
1742
+ normalized = normalized.substring(2);
1743
+ }
1744
+ normalized = normalized.replace(/\/$/, "");
1745
+ return normalized;
1746
+ };
1747
+ const normRepo = normalize(repoPath);
1748
+ const normFile = normalize(filePath);
1749
+ if (normFile.startsWith(`${normRepo}/`)) {
1750
+ return normFile.substring(normRepo.length + 1);
1751
+ }
1752
+ if (normFile === normRepo) {
1753
+ return ".";
1754
+ }
1755
+ if (filePath.startsWith("/")) {
1756
+ const repoIndex = filePath.indexOf(normRepo);
1757
+ if (repoIndex !== -1) {
1758
+ const afterRepo = filePath.substring(repoIndex + normRepo.length);
1759
+ if (afterRepo.startsWith("/")) {
1760
+ return afterRepo.substring(1) || ".";
1761
+ }
1762
+ }
1763
+ }
1764
+ return normFile;
1765
+ }
1766
+ /**
1767
+ * Stage files for commit
1768
+ */
1769
+ async add(repoPath, files) {
1770
+ const args = ["add"];
1771
+ if (!files || Array.isArray(files) && files.length === 0) {
1772
+ args.push(".");
1773
+ } else if (typeof files === "string") {
1774
+ args.push(this.normalizePath(repoPath, files));
1775
+ } else {
1776
+ args.push(...files.map((file) => this.normalizePath(repoPath, file)));
1777
+ }
1778
+ const result = await this.deps.execSync({
1779
+ command: "git",
1780
+ args,
1781
+ cwd: repoPath
1782
+ });
1783
+ if (result.exitCode !== 0) {
1784
+ throw new Error(`Git add failed: ${result.stderr || result.stdout}`);
1785
+ }
1786
+ }
1787
+ /**
1788
+ * Commit changes
1789
+ */
1790
+ async commit(repoPath, message, author, email, allowEmpty) {
1791
+ const args = ["commit"];
1792
+ if (allowEmpty) {
1793
+ args.push("--allow-empty");
1794
+ }
1795
+ args.push("--author", `${author} <${email}>`);
1796
+ args.push("-m", message);
1797
+ const result = await this.deps.execSync({
1798
+ command: "git",
1799
+ args,
1800
+ cwd: repoPath
1801
+ });
1802
+ if (result.exitCode !== 0) {
1803
+ throw new Error(`Git commit failed: ${result.stderr || result.stdout}`);
1804
+ }
1805
+ }
1806
+ /**
1807
+ * Get repository status
1808
+ */
1809
+ async status(repoPath) {
1810
+ const porcelainResult = await this.deps.execSync({
1811
+ command: "git",
1812
+ args: ["status", "--porcelain"],
1813
+ cwd: repoPath
1814
+ });
1815
+ const branchResult = await this.deps.execSync({
1816
+ command: "git",
1817
+ args: ["status", "-sb"],
1818
+ cwd: repoPath
1819
+ });
1820
+ if (porcelainResult.exitCode !== 0 || branchResult.exitCode !== 0) {
1821
+ throw new Error(`Git status failed: ${branchResult.stderr || branchResult.stdout}`);
1822
+ }
1823
+ const branchLine = branchResult.stdout.split("\n")[0] || "";
1824
+ return this.parseGitStatus(porcelainResult.stdout, branchLine);
1825
+ }
1826
+ };
1827
+
1828
+ // src/core/devbox-instance.ts
1829
+ var DevboxInstance = class {
1830
+ info;
1831
+ sdk;
1832
+ git;
1833
+ constructor(info, sdk) {
1834
+ this.info = info;
1835
+ this.sdk = sdk;
1836
+ this.git = new Git({
1837
+ execSync: (options) => this.execSync(options)
1838
+ });
1839
+ }
1840
+ // Properties
1841
+ get name() {
1842
+ return this.info.name;
1843
+ }
1844
+ get status() {
1845
+ return this.info.status;
1846
+ }
1847
+ get runtime() {
1848
+ return this.info.runtime;
1849
+ }
1850
+ get resources() {
1851
+ return this.info.resources;
1852
+ }
1853
+ get serverUrl() {
1854
+ if (!this.info.podIP) {
1855
+ throw new Error(`Devbox '${this.name}' does not have a pod IP address`);
1856
+ }
1857
+ return `http://${this.info.podIP}:3000`;
1858
+ }
1859
+ // Lifecycle operations
1860
+ async start() {
1861
+ const apiClient = this.sdk.getAPIClient();
1862
+ await apiClient.startDevbox(this.name);
1863
+ await this.refreshInfo();
1864
+ }
1865
+ async pause() {
1866
+ const apiClient = this.sdk.getAPIClient();
1867
+ await apiClient.pauseDevbox(this.name);
1868
+ await this.refreshInfo();
1869
+ }
1870
+ async restart() {
1871
+ const apiClient = this.sdk.getAPIClient();
1872
+ await apiClient.restartDevbox(this.name);
1873
+ await this.refreshInfo();
1874
+ }
1875
+ async shutdown() {
1876
+ const apiClient = this.sdk.getAPIClient();
1877
+ await apiClient.shutdownDevbox(this.name);
1878
+ await this.refreshInfo();
1879
+ }
1880
+ async delete() {
1881
+ const apiClient = this.sdk.getAPIClient();
1882
+ await apiClient.deleteDevbox(this.name);
1883
+ }
1884
+ /**
1885
+ * Refresh the instance information from the API
1886
+ */
1887
+ async refreshInfo() {
1888
+ const apiClient = this.sdk.getAPIClient();
1889
+ this.info = await apiClient.getDevbox(this.name);
1890
+ }
1891
+ async writeFile(path, content, options) {
1892
+ this.validatePath(path);
1893
+ const urlResolver = this.sdk.getUrlResolver();
1894
+ await urlResolver.executeWithConnection(this.name, async (client) => {
1895
+ const contentSize = Buffer.isBuffer(content) ? content.length : Buffer.byteLength(content, "utf-8");
1896
+ const LARGE_FILE_THRESHOLD = 1 * 1024 * 1024;
1897
+ const useBinaryMode = contentSize > LARGE_FILE_THRESHOLD;
1898
+ if (Buffer.isBuffer(content)) {
1899
+ if (options?.encoding === "base64") {
1900
+ const base64Content = content.toString("base64");
1901
+ await client.post("/api/v1/files/write", {
1902
+ body: {
1903
+ path,
1904
+ content: base64Content,
1905
+ encoding: "base64"
1906
+ }
1907
+ });
1908
+ } else {
1909
+ await client.post("/api/v1/files/write", {
1910
+ params: { path },
1911
+ headers: {
1912
+ "Content-Type": "application/octet-stream"
1913
+ },
1914
+ body: content
1915
+ // Direct binary data
1916
+ });
1917
+ }
1918
+ } else {
1919
+ if (useBinaryMode && !options?.encoding) {
1920
+ const buffer = Buffer.from(content, "utf-8");
1921
+ await client.post("/api/v1/files/write", {
1922
+ params: { path },
1923
+ headers: {
1924
+ "Content-Type": "application/octet-stream"
1925
+ },
1926
+ body: buffer
1927
+ });
1928
+ } else if (options?.encoding === "base64") {
1929
+ const base64Content = Buffer.from(content, "utf-8").toString("base64");
1930
+ await client.post("/api/v1/files/write", {
1931
+ body: {
1932
+ path,
1933
+ content: base64Content,
1934
+ encoding: "base64"
1935
+ }
1936
+ });
1937
+ } else {
1938
+ await client.post("/api/v1/files/write", {
1939
+ body: {
1940
+ path,
1941
+ content
1942
+ }
1943
+ });
1944
+ }
1945
+ }
1946
+ });
1947
+ }
1948
+ async readFile(path, options) {
1949
+ this.validatePath(path);
1950
+ const urlResolver = this.sdk.getUrlResolver();
1951
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
1952
+ const response = await client.get("/api/v1/files/read", {
1953
+ params: { path, ...options }
1954
+ });
1955
+ if (Buffer.isBuffer(response.data)) {
1956
+ return response.data;
1957
+ }
1958
+ if (typeof response.data === "string") {
1959
+ return Buffer.from(response.data, "utf-8");
1960
+ }
1961
+ if (response.data instanceof ArrayBuffer) {
1962
+ return Buffer.from(new Uint8Array(response.data));
1963
+ }
1964
+ if (response.data instanceof Uint8Array) {
1965
+ return Buffer.from(response.data);
1966
+ }
1967
+ const dataType = typeof response.data;
1968
+ const dataConstructor = response.data?.constructor?.name || "unknown";
1969
+ throw new Error(
1970
+ `Failed to read file: unexpected response format (type: ${dataType}, constructor: ${dataConstructor})`
1971
+ );
1972
+ });
1973
+ }
1974
+ /**
1975
+ * Validate file path to prevent directory traversal attacks
1976
+ */
1977
+ validatePath(path) {
1978
+ if (!path || path.length === 0) {
1979
+ throw new Error("Path cannot be empty");
1980
+ }
1981
+ if (path.endsWith("/") || path.endsWith("\\")) {
1982
+ throw new Error("Path cannot end with a directory separator");
1983
+ }
1984
+ const normalized = path.replace(/\\/g, "/");
1985
+ if (normalized.includes("../") || normalized.includes("..\\")) {
1986
+ throw new Error(`Path traversal detected: ${path}`);
1987
+ }
1988
+ if (normalized.startsWith("/") && (normalized.startsWith("/../") || normalized === "/..")) {
1989
+ throw new Error(`Invalid absolute path: ${path}`);
1990
+ }
1991
+ }
1992
+ async deleteFile(path) {
1993
+ this.validatePath(path);
1994
+ const urlResolver = this.sdk.getUrlResolver();
1995
+ await urlResolver.executeWithConnection(this.name, async (client) => {
1996
+ await client.post("/api/v1/files/delete", {
1997
+ body: { path }
1998
+ });
1999
+ });
2000
+ }
2001
+ async listFiles(path) {
2002
+ this.validatePath(path);
2003
+ const urlResolver = this.sdk.getUrlResolver();
2004
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2005
+ const response = await client.get("/api/v1/files/list", {
2006
+ params: { path }
2007
+ });
2008
+ return response.data;
2009
+ });
2010
+ }
2011
+ async uploadFiles(files, options) {
2012
+ const urlResolver = this.sdk.getUrlResolver();
2013
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2014
+ const formData = new FormData();
2015
+ let targetDir;
2016
+ const relativePaths = [];
2017
+ const filePaths = Object.keys(files);
2018
+ if (options?.targetDir) {
2019
+ targetDir = options.targetDir.replace(/\/+$/, "") || ".";
2020
+ for (const filePath of filePaths) {
2021
+ if (filePath.startsWith(`${targetDir}/`)) {
2022
+ relativePaths.push(filePath.slice(targetDir.length + 1));
2023
+ } else if (filePath === targetDir) {
2024
+ relativePaths.push("");
2025
+ } else {
2026
+ relativePaths.push(filePath);
2027
+ }
2028
+ }
2029
+ } else {
2030
+ if (filePaths.length === 0) {
2031
+ targetDir = ".";
2032
+ } else {
2033
+ const dirParts = filePaths.map((path) => {
2034
+ const parts = path.split("/");
2035
+ return parts.slice(0, -1);
2036
+ });
2037
+ if (dirParts.length > 0 && dirParts[0] && dirParts[0].length > 0) {
2038
+ const commonPrefix = [];
2039
+ const minLength = Math.min(...dirParts.map((p) => p.length));
2040
+ const firstDirParts = dirParts[0];
2041
+ for (let i = 0; i < minLength; i++) {
2042
+ const segment = firstDirParts[i];
2043
+ if (segment && dirParts.every((p) => p[i] === segment)) {
2044
+ commonPrefix.push(segment);
2045
+ } else {
2046
+ break;
2047
+ }
2048
+ }
2049
+ targetDir = commonPrefix.length > 0 ? commonPrefix.join("/") : ".";
2050
+ } else {
2051
+ targetDir = ".";
2052
+ }
2053
+ const normalizedTargetDir = targetDir === "." ? "" : targetDir;
2054
+ for (const filePath of filePaths) {
2055
+ if (normalizedTargetDir && filePath.startsWith(`${normalizedTargetDir}/`)) {
2056
+ relativePaths.push(filePath.slice(normalizedTargetDir.length + 1));
2057
+ } else {
2058
+ relativePaths.push(filePath);
2059
+ }
2060
+ }
2061
+ }
2062
+ }
2063
+ formData.append("targetDir", targetDir);
2064
+ let index = 0;
2065
+ for (const [filePath, content] of Object.entries(files)) {
2066
+ const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
2067
+ const relativePath = relativePaths[index++] || filePath.split("/").pop() || "file";
2068
+ const fullPath = targetDir === "." ? relativePath : `${targetDir}/${relativePath}`;
2069
+ const uint8Array = new Uint8Array(buffer);
2070
+ const file = new File([uint8Array], fullPath);
2071
+ formData.append("files", file);
2072
+ }
2073
+ const response = await client.post("/api/v1/files/batch-upload", {
2074
+ body: formData
2075
+ });
2076
+ return response.data;
2077
+ });
2078
+ }
2079
+ async moveFile(source, destination, overwrite = false) {
2080
+ this.validatePath(source);
2081
+ this.validatePath(destination);
2082
+ const urlResolver = this.sdk.getUrlResolver();
2083
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2084
+ const response = await client.post(API_ENDPOINTS.CONTAINER.FILES.MOVE, {
2085
+ body: {
2086
+ source,
2087
+ destination,
2088
+ overwrite
2089
+ }
2090
+ });
2091
+ return response.data;
2092
+ });
2093
+ }
2094
+ /**
2095
+ * Rename a file or directory
2096
+ * @param oldPath Current file or directory path
2097
+ * @param newPath New file or directory path
2098
+ * @returns Rename operation response
2099
+ */
2100
+ async renameFile(oldPath, newPath) {
2101
+ this.validatePath(oldPath);
2102
+ this.validatePath(newPath);
2103
+ const urlResolver = this.sdk.getUrlResolver();
2104
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2105
+ const response = await client.post(API_ENDPOINTS.CONTAINER.FILES.RENAME, {
2106
+ body: {
2107
+ oldPath,
2108
+ newPath
2109
+ }
2110
+ });
2111
+ return response.data;
2112
+ });
2113
+ }
2114
+ /**
2115
+ * Search for files by filename pattern (case-insensitive substring match)
2116
+ * @param options Search options including directory and pattern
2117
+ * @returns List of matching file paths
2118
+ */
2119
+ async searchFiles(options) {
2120
+ if (!options.pattern || options.pattern.trim().length === 0) {
2121
+ throw new Error("Pattern cannot be empty");
2122
+ }
2123
+ const urlResolver = this.sdk.getUrlResolver();
2124
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2125
+ const response = await client.post(
2126
+ API_ENDPOINTS.CONTAINER.FILES.SEARCH,
2127
+ {
2128
+ body: {
2129
+ dir: options.dir || ".",
2130
+ pattern: options.pattern
2131
+ }
2132
+ }
2133
+ );
2134
+ return response.data;
2135
+ });
2136
+ }
2137
+ /**
2138
+ * Find files by content keyword (searches inside text files)
2139
+ * @param options Find options including directory and keyword
2140
+ * @returns List of file paths containing the keyword
2141
+ */
2142
+ async findInFiles(options) {
2143
+ if (!options.keyword || options.keyword.trim().length === 0) {
2144
+ throw new Error("Keyword cannot be empty");
2145
+ }
2146
+ const urlResolver = this.sdk.getUrlResolver();
2147
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2148
+ const response = await client.post(
2149
+ API_ENDPOINTS.CONTAINER.FILES.FIND,
2150
+ {
2151
+ body: {
2152
+ dir: options.dir || ".",
2153
+ keyword: options.keyword
2154
+ }
2155
+ }
2156
+ );
2157
+ return response.data;
2158
+ });
2159
+ }
2160
+ /**
2161
+ * Replace text in multiple files
2162
+ * @param options Replace options including file paths, from text, and to text
2163
+ * @returns Replacement results for each file
2164
+ */
2165
+ async replaceInFiles(options) {
2166
+ if (!options.from || options.from.trim().length === 0) {
2167
+ throw new Error("'from' string cannot be empty");
2168
+ }
2169
+ if (!options.files || options.files.length === 0) {
2170
+ throw new Error("At least one file path is required");
2171
+ }
2172
+ for (const filePath of options.files) {
2173
+ this.validatePath(filePath);
2174
+ }
2175
+ const urlResolver = this.sdk.getUrlResolver();
2176
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2177
+ const response = await client.post(
2178
+ API_ENDPOINTS.CONTAINER.FILES.REPLACE,
2179
+ {
2180
+ body: {
2181
+ files: options.files,
2182
+ from: options.from,
2183
+ to: options.to
2184
+ }
2185
+ }
2186
+ );
2187
+ return response.data;
2188
+ });
2189
+ }
2190
+ /**
2191
+ * Download a single file
2192
+ * @param path File path to download
2193
+ * @returns Buffer containing file content
2194
+ */
2195
+ async downloadFile(path) {
2196
+ this.validatePath(path);
2197
+ const urlResolver = this.sdk.getUrlResolver();
2198
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2199
+ const response = await client.get(
2200
+ `${API_ENDPOINTS.CONTAINER.FILES.DOWNLOAD}?path=${encodeURIComponent(path)}`
2201
+ );
2202
+ return response.data;
2203
+ });
2204
+ }
2205
+ /**
2206
+ * Download multiple files with format options
2207
+ * @param paths Array of file paths to download
2208
+ * @param options Download options including format
2209
+ * @returns Buffer containing downloaded files (tar.gz, tar, or multipart format)
2210
+ */
2211
+ async downloadFiles(paths, options) {
2212
+ if (!paths || paths.length === 0) {
2213
+ throw new Error("At least one file path is required");
2214
+ }
2215
+ for (const path of paths) {
2216
+ this.validatePath(path);
2217
+ }
2218
+ const urlResolver = this.sdk.getUrlResolver();
2219
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2220
+ const headers = {};
2221
+ if (options?.format) {
2222
+ switch (options.format) {
2223
+ case "tar.gz":
2224
+ headers.Accept = "application/gzip";
2225
+ break;
2226
+ case "tar":
2227
+ headers.Accept = "application/x-tar";
2228
+ break;
2229
+ case "multipart":
2230
+ headers.Accept = "multipart/mixed";
2231
+ break;
2232
+ }
2233
+ }
2234
+ const response = await client.post(API_ENDPOINTS.CONTAINER.FILES.BATCH_DOWNLOAD, {
2235
+ body: { paths, format: options?.format },
2236
+ headers: Object.keys(headers).length > 0 ? headers : void 0
2237
+ });
2238
+ return response.data;
2239
+ });
2240
+ }
2241
+ /**
2242
+ * Get listening ports on the system
2243
+ * @returns Ports response with list of listening ports (3000-9999 range)
2244
+ */
2245
+ async getPorts() {
2246
+ const urlResolver = this.sdk.getUrlResolver();
2247
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2248
+ const response = await client.get(API_ENDPOINTS.CONTAINER.PORTS);
2249
+ return response.data;
2250
+ });
2251
+ }
2252
+ /**
2253
+ * Get preview link for a specific port
2254
+ * @param port Port number to get preview link for
2255
+ * @returns Preview URL information
2256
+ */
2257
+ async getPreviewLink(port) {
2258
+ await this.refreshInfo();
2259
+ if (!this.info.agentServer?.url) {
2260
+ throw new Error(
2261
+ `No agentServer URL available for Devbox '${this.name}'. Cannot generate preview link.`
2262
+ );
2263
+ }
2264
+ const serviceName = this.info.agentServer.url;
2265
+ const urlResolver = this.sdk.getUrlResolver();
2266
+ const baseUrl = urlResolver.baseUrl;
2267
+ const urlObj = new URL(baseUrl);
2268
+ const domain = urlObj.hostname.replace(/^devbox\./, "");
2269
+ const url = `${urlObj.protocol}//devbox-${serviceName}-${port}.${domain}`;
2270
+ const protocol = urlObj.protocol.replace(":", "");
2271
+ return {
2272
+ url,
2273
+ port,
2274
+ protocol
2275
+ };
2276
+ }
2277
+ // Temporarily disabled - ws module removed
2278
+ // File watching (instance method)
2279
+ // async watchFiles(
2280
+ // path: string,
2281
+ // callback: (event: FileChangeEvent) => void
2282
+ // ): Promise<FileWatchWebSocket> {
2283
+ // const urlResolver = this.sdk.getUrlResolver()
2284
+ // const serverUrl = await urlResolver.getServerUrl(this.name)
2285
+ // const { default: WebSocket } = await import('ws')
2286
+ // const ws = new WebSocket(`ws://${serverUrl.replace('http://', '')}/ws`) as unknown as FileWatchWebSocket
2287
+ // ws.onopen = () => {
2288
+ // const watchRequest: WatchRequest = { type: 'watch', path }
2289
+ // ws.send(JSON.stringify(watchRequest))
2290
+ // }
2291
+ // ws.onmessage = (event: any) => {
2292
+ // try {
2293
+ // const data = typeof event.data === 'string' ? event.data : event.data?.toString() || ''
2294
+ // const fileEvent = JSON.parse(data) as FileChangeEvent
2295
+ // callback(fileEvent)
2296
+ // } catch (error) {
2297
+ // console.error('Failed to parse file watch event:', error)
2298
+ // }
2299
+ // }
2300
+ // return ws
2301
+ // }
2302
+ // Process execution
2303
+ /**
2304
+ * Execute a process asynchronously
2305
+ * All commands are automatically executed through shell (sh -c) for consistent behavior
2306
+ * This ensures environment variables, pipes, redirections, etc. work as expected
2307
+ * @param options Process execution options
2308
+ * @returns Process execution response with process_id and pid
2309
+ */
2310
+ async executeCommand(options) {
2311
+ const urlResolver = this.sdk.getUrlResolver();
2312
+ let fullCommand = options.command;
2313
+ if (options.args && options.args.length > 0) {
2314
+ fullCommand = `${options.command} ${options.args.join(" ")}`;
2315
+ }
2316
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2317
+ const response = await client.post(
2318
+ API_ENDPOINTS.CONTAINER.PROCESS.EXEC,
2319
+ {
2320
+ body: {
2321
+ command: "sh",
2322
+ args: ["-c", fullCommand],
2323
+ cwd: options.cwd,
2324
+ env: options.env,
2325
+ timeout: options.timeout
2326
+ }
2327
+ }
2328
+ );
2329
+ return response.data;
2330
+ });
2331
+ }
2332
+ /**
2333
+ * Execute a process synchronously and wait for completion
2334
+ * All commands are automatically executed through shell (sh -c) for consistent behavior
2335
+ * This ensures environment variables, pipes, redirections, etc. work as expected
2336
+ * @param options Process execution options
2337
+ * @returns Synchronous execution response with stdout, stderr, and exit code
2338
+ */
2339
+ async execSync(options) {
2340
+ const urlResolver = this.sdk.getUrlResolver();
2341
+ let fullCommand = options.command;
2342
+ if (options.args && options.args.length > 0) {
2343
+ fullCommand = `${options.command} ${options.args.join(" ")}`;
2344
+ }
2345
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2346
+ const response = await client.post(
2347
+ API_ENDPOINTS.CONTAINER.PROCESS.EXEC_SYNC,
2348
+ {
2349
+ body: {
2350
+ command: "sh",
2351
+ args: ["-c", fullCommand],
2352
+ cwd: options.cwd,
2353
+ env: options.env,
2354
+ timeout: options.timeout
2355
+ }
2356
+ }
2357
+ );
2358
+ return response.data;
2359
+ });
2360
+ }
2361
+ /**
2362
+ * Execute code directly (Node.js or Python)
2363
+ * @param code Code string to execute
2364
+ * @param options Code execution options
2365
+ * @returns Synchronous execution response with stdout, stderr, and exit code
2366
+ */
2367
+ async codeRun(code, options) {
2368
+ const language = options?.language || this.detectLanguage(code);
2369
+ const command = this.buildCodeCommand(code, language, options?.argv);
2370
+ return this.execSync({
2371
+ command,
2372
+ cwd: options?.cwd,
2373
+ env: options?.env,
2374
+ timeout: options?.timeout
2375
+ });
2376
+ }
2377
+ /**
2378
+ * Detect programming language from code string
2379
+ * @param code Code string to analyze
2380
+ * @returns Detected language ('node' or 'python')
2381
+ */
2382
+ detectLanguage(code) {
2383
+ if (/\bdef\s+\w+\(|^\s*import\s+\w+|print\s*\(|:\s*$/.test(code)) {
2384
+ return "python";
2385
+ }
2386
+ if (/\brequire\s*\(|module\.exports|console\.log/.test(code)) {
2387
+ return "node";
2388
+ }
2389
+ return "node";
2390
+ }
2391
+ /**
2392
+ * Build shell command to execute code
2393
+ * Note: sh -c wrapper is now handled by wrapCommandWithShell
2394
+ * @param code Code string to execute
2395
+ * @param language Programming language ('node' or 'python')
2396
+ * @param argv Command line arguments
2397
+ * @returns Shell command string (without sh -c wrapper)
2398
+ */
2399
+ buildCodeCommand(code, language, argv) {
2400
+ const base64Code = Buffer.from(code).toString("base64");
2401
+ const argvStr = argv && argv.length > 0 ? ` ${argv.join(" ")}` : "";
2402
+ if (language === "python") {
2403
+ return `python3 -u -c "exec(__import__(\\"base64\\").b64decode(\\"${base64Code}\\").decode())"${argvStr}`;
2404
+ }
2405
+ return `echo ${base64Code} | base64 --decode | node -e "$(cat)"${argvStr}`;
2406
+ }
2407
+ /**
2408
+ * Execute a process synchronously with streaming output (SSE)
2409
+ * @param options Process execution options
2410
+ * @returns ReadableStream for Server-Sent Events
2411
+ */
2412
+ async execSyncStream(options) {
2413
+ const urlResolver = this.sdk.getUrlResolver();
2414
+ const serverUrl = await urlResolver.getServerUrl(this.name);
2415
+ const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.EXEC_SYNC_STREAM;
2416
+ const url = `${serverUrl}${endpoint}`;
2417
+ const response = await fetch(url, {
2418
+ method: "POST",
2419
+ headers: {
2420
+ "Content-Type": "application/json",
2421
+ Accept: "text/event-stream",
2422
+ Authorization: "Bearer 1234"
2423
+ // TODO: remove this
2424
+ },
2425
+ body: JSON.stringify({
2426
+ command: options.command,
2427
+ args: options.args,
2428
+ cwd: options.cwd,
2429
+ env: options.env,
2430
+ shell: options.shell,
2431
+ timeout: options.timeout
2432
+ })
2433
+ });
2434
+ if (!response.ok) {
2435
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2436
+ }
2437
+ if (!response.body) {
2438
+ throw new Error("Response body is null");
2439
+ }
2440
+ return response.body;
2441
+ }
2442
+ /**
2443
+ * List all processes
2444
+ * @returns List of all processes with their metadata
2445
+ */
2446
+ async listProcesses() {
2447
+ const urlResolver = this.sdk.getUrlResolver();
2448
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2449
+ const response = await client.get(API_ENDPOINTS.CONTAINER.PROCESS.LIST);
2450
+ return response.data;
2451
+ });
2452
+ }
2453
+ /**
2454
+ * Get process status by process_id
2455
+ * @param processId Process ID (string)
2456
+ * @returns Process status response
2457
+ */
2458
+ async getProcessStatus(processId) {
2459
+ const urlResolver = this.sdk.getUrlResolver();
2460
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2461
+ const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.STATUS.replace("{process_id}", processId);
2462
+ const response = await client.get(endpoint);
2463
+ return response.data;
2464
+ });
2465
+ }
2466
+ /**
2467
+ * Kill a process by process_id
2468
+ * @param processId Process ID (string)
2469
+ * @param options Optional kill options (signal)
2470
+ */
2471
+ async killProcess(processId, options) {
2472
+ const urlResolver = this.sdk.getUrlResolver();
2473
+ await urlResolver.executeWithConnection(this.name, async (client) => {
2474
+ const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.KILL.replace("{process_id}", processId);
2475
+ await client.post(endpoint, {
2476
+ params: options?.signal ? { signal: options.signal } : void 0
2477
+ });
2478
+ });
2479
+ }
2480
+ /**
2481
+ * Get process logs by process_id
2482
+ * @param processId Process ID (string)
2483
+ * @param stream Enable log streaming (default: false)
2484
+ * @returns Process logs response
2485
+ */
2486
+ async getProcessLogs(processId, stream = false) {
2487
+ const urlResolver = this.sdk.getUrlResolver();
2488
+ return await urlResolver.executeWithConnection(this.name, async (client) => {
2489
+ const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.LOGS.replace("{process_id}", processId);
2490
+ const response = await client.get(endpoint, {
2491
+ params: { stream }
2492
+ });
2493
+ return response.data;
2494
+ });
2495
+ }
2496
+ // Monitoring
2497
+ async getMonitorData(timeRange) {
2498
+ return await this.sdk.getMonitorData(this.name, timeRange);
2499
+ }
2500
+ // Health check
2501
+ async isHealthy() {
2502
+ try {
2503
+ const urlResolver = this.sdk.getUrlResolver();
2504
+ return await urlResolver.checkDevboxHealth(this.name);
2505
+ } catch {
2506
+ return false;
2507
+ }
2508
+ }
2509
+ /**
2510
+ * Wait for the Devbox to be ready and healthy
2511
+ * @param timeoutOrOptions Timeout in milliseconds (for backward compatibility) or options object
2512
+ * @param checkInterval Check interval in milliseconds (for backward compatibility, ignored if first param is options)
2513
+ */
2514
+ async waitForReady(timeoutOrOptions, checkInterval = 2e3) {
2515
+ const options = typeof timeoutOrOptions === "number" ? {
2516
+ timeout: timeoutOrOptions,
2517
+ checkInterval,
2518
+ // For backward compatibility: if explicitly called with numbers, use fixed interval
2519
+ useExponentialBackoff: false
2520
+ } : timeoutOrOptions ?? {
2521
+ // Default: use exponential backoff when called without params
2522
+ useExponentialBackoff: true
2523
+ };
2524
+ const {
2525
+ timeout = 3e5,
2526
+ // 5 minutes
2527
+ checkInterval: fixedInterval,
2528
+ useExponentialBackoff = fixedInterval === void 0,
2529
+ initialCheckInterval = 200,
2530
+ // 0.2 seconds - faster initial checks
2531
+ maxCheckInterval = 5e3,
2532
+ // 5 seconds
2533
+ backoffMultiplier = 1.5
2534
+ } = options;
2535
+ const startTime = Date.now();
2536
+ let currentInterval = useExponentialBackoff ? initialCheckInterval : fixedInterval ?? 2e3;
2537
+ let checkCount = 0;
2538
+ let lastStatus = this.status;
2539
+ while (Date.now() - startTime < timeout) {
2540
+ try {
2541
+ await this.refreshInfo();
2542
+ if (this.status === "Running") {
2543
+ const healthy = await this.isHealthy();
2544
+ if (healthy) {
2545
+ return;
2546
+ }
2547
+ if (lastStatus !== "Running") {
2548
+ currentInterval = Math.min(initialCheckInterval * 2, 1e3);
2549
+ checkCount = 1;
2550
+ }
2551
+ } else if (lastStatus !== this.status) {
2552
+ currentInterval = initialCheckInterval;
2553
+ checkCount = 0;
2554
+ }
2555
+ lastStatus = this.status;
2556
+ } catch {
2557
+ }
2558
+ if (useExponentialBackoff) {
2559
+ currentInterval = Math.min(
2560
+ initialCheckInterval * backoffMultiplier ** checkCount,
2561
+ maxCheckInterval
2562
+ );
2563
+ checkCount++;
2564
+ }
2565
+ await new Promise((resolve) => setTimeout(resolve, currentInterval));
2566
+ }
2567
+ throw new Error(`Devbox '${this.name}' did not become ready within ${timeout}ms`);
2568
+ }
2569
+ /**
2570
+ * Get detailed information about the instance
2571
+ */
2572
+ async getDetailedInfo() {
2573
+ await this.refreshInfo();
2574
+ return { ...this.info };
2575
+ }
2576
+ };
2577
+
2578
+ // src/core/devbox-sdk.ts
2579
+ var DevboxSDK = class {
2580
+ apiClient;
2581
+ urlResolver;
2582
+ constructor(config) {
2583
+ this.apiClient = new DevboxAPI({
2584
+ kubeconfig: config.kubeconfig,
2585
+ baseUrl: config.baseUrl,
2586
+ timeout: config.http?.timeout,
2587
+ retries: config.http?.retries,
2588
+ rejectUnauthorized: config.http?.rejectUnauthorized
2589
+ });
2590
+ this.urlResolver = new ContainerUrlResolver(config);
2591
+ this.urlResolver.setAPIClient(this.apiClient);
2592
+ }
2593
+ /**
2594
+ * Create a new Devbox instance (async, returns immediately without waiting)
2595
+ * @param config Devbox creation configuration
2596
+ * @returns DevboxInstance (may not be ready immediately - use waitForReady() if needed)
2597
+ * @description This method returns immediately after creating the Devbox without waiting for it to be ready.
2598
+ * The returned instance may not be ready for file operations or commands.
2599
+ * Use `createDevbox()` (default behavior) or call `waitForReady()` on the instance if you need to wait.
2600
+ */
2601
+ async createDevboxAsync(config) {
2602
+ const devboxInfo = await this.apiClient.createDevbox(config);
2603
+ return new DevboxInstance(devboxInfo, this);
2604
+ }
2605
+ /**
2606
+ * Create a new Devbox instance
2607
+ * @param config Devbox creation configuration
2608
+ * @param options Creation options (waitUntilReady defaults to true)
2609
+ * @returns DevboxInstance (ready for use if waitUntilReady is true)
2610
+ * @description By default, this method waits for the Devbox to be fully ready before returning.
2611
+ * Set `options.waitUntilReady = false` to return immediately without waiting.
2612
+ */
2613
+ async createDevbox(config, options = {}) {
2614
+ const {
2615
+ waitUntilReady = true,
2616
+ timeout = 18e4,
2617
+ // 3 minutes
2618
+ checkInterval,
2619
+ useExponentialBackoff = true,
2620
+ initialCheckInterval = 200,
2621
+ // 0.2 seconds - faster initial checks
2622
+ maxCheckInterval = 5e3,
2623
+ // 5 seconds
2624
+ backoffMultiplier = 1.5
2625
+ } = options;
2626
+ const instance = await this.createDevboxAsync(config);
2627
+ if (waitUntilReady) {
2628
+ await instance.waitForReady({
2629
+ timeout,
2630
+ checkInterval,
2631
+ useExponentialBackoff,
2632
+ initialCheckInterval,
2633
+ maxCheckInterval,
2634
+ backoffMultiplier
2635
+ });
2636
+ }
2637
+ return instance;
2638
+ }
2639
+ async getDevbox(name) {
2640
+ const devboxInfo = await this.apiClient.getDevbox(name);
2641
+ return new DevboxInstance(devboxInfo, this);
2642
+ }
2643
+ async listDevboxes() {
2644
+ const devboxes = await this.apiClient.listDevboxes();
2645
+ return devboxes.map((info) => new DevboxInstance(info, this));
2646
+ }
2647
+ async getMonitorData(devboxName, timeRange) {
2648
+ return await this.apiClient.getMonitorData(devboxName, timeRange);
2649
+ }
2650
+ async close() {
2651
+ await this.urlResolver.closeAllConnections();
2652
+ }
2653
+ getAPIClient() {
2654
+ return this.apiClient;
2655
+ }
2656
+ getUrlResolver() {
2657
+ return this.urlResolver;
2658
+ }
2659
+ };
2660
+
2661
+ // src/index.ts
2662
+ var VERSION = "1.0.0";
2663
+ var index_default = DevboxSDK;
2664
+
2665
+ export { API_ENDPOINTS, AuthenticationError, ConnectionError, ContainerUrlResolver, DEFAULT_CONFIG, DevboxAPI, DevboxContainerClient, DevboxInstance, DevboxNotFoundError, DevboxNotReadyError, DevboxRuntime, DevboxSDK, DevboxSDKError, ERROR_CODES, FileOperationError, HTTP_STATUS, VERSION, ValidationError, index_default as default };