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