@miosa/cli 1.0.2 → 1.0.3
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/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +3768 -300
- package/dist/commands/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/mcp.js
CHANGED
|
@@ -58,6 +58,15 @@ const TOOL_LIST = [
|
|
|
58
58
|
type: "string",
|
|
59
59
|
description: "Your internal project ID for attribution (optional)",
|
|
60
60
|
},
|
|
61
|
+
gpu_model: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "GPU model to attach (e.g. 'nvidia-a10g', 'nvidia-t4'). Omit for CPU-only.",
|
|
64
|
+
},
|
|
65
|
+
gpu_count: {
|
|
66
|
+
type: "integer",
|
|
67
|
+
description: "Number of GPUs to attach (default: 1 when gpu_model is set).",
|
|
68
|
+
default: 1,
|
|
69
|
+
},
|
|
61
70
|
},
|
|
62
71
|
required: ["name"],
|
|
63
72
|
},
|
|
@@ -81,6 +90,72 @@ const TOOL_LIST = [
|
|
|
81
90
|
required: ["computer_id"],
|
|
82
91
|
},
|
|
83
92
|
},
|
|
93
|
+
{
|
|
94
|
+
name: "computer_get",
|
|
95
|
+
description: "Get details and current status of a computer.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
computer_id: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "ID of the computer to fetch.",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
required: ["computer_id"],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "computer_start",
|
|
109
|
+
description: "Start a stopped computer.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
114
|
+
},
|
|
115
|
+
required: ["computer_id"],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "computer_stop",
|
|
120
|
+
description: "Stop a running computer.",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
125
|
+
},
|
|
126
|
+
required: ["computer_id"],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "computer_restart",
|
|
131
|
+
description: "Restart a computer.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
136
|
+
},
|
|
137
|
+
required: ["computer_id"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "computer_update",
|
|
142
|
+
description: "Rename a computer or update its metadata.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
147
|
+
name: {
|
|
148
|
+
type: "string",
|
|
149
|
+
description: "New human-readable name for the computer (optional).",
|
|
150
|
+
},
|
|
151
|
+
metadata: {
|
|
152
|
+
type: "object",
|
|
153
|
+
description: "Arbitrary key/value metadata to attach (optional).",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
required: ["computer_id"],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
84
159
|
// Screenshot
|
|
85
160
|
{
|
|
86
161
|
name: "computer_screenshot",
|
|
@@ -331,397 +406,3790 @@ const TOOL_LIST = [
|
|
|
331
406
|
required: ["computer_id", "path"],
|
|
332
407
|
},
|
|
333
408
|
},
|
|
334
|
-
//
|
|
409
|
+
// Desktop — extended pointer / keyboard / window / env
|
|
335
410
|
{
|
|
336
|
-
name: "
|
|
337
|
-
description: "
|
|
411
|
+
name: "computer_right_click",
|
|
412
|
+
description: "Right-click at the given screen coordinates.",
|
|
338
413
|
inputSchema: {
|
|
339
414
|
type: "object",
|
|
340
415
|
properties: {
|
|
341
|
-
|
|
416
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
417
|
+
x: { type: "integer", description: "X coordinate in pixels" },
|
|
418
|
+
y: { type: "integer", description: "Y coordinate in pixels" },
|
|
419
|
+
},
|
|
420
|
+
required: ["computer_id", "x", "y"],
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "computer_mouse_down",
|
|
425
|
+
description: "Press and hold a mouse button at (x, y). Pair with computer_mouse_up to release.",
|
|
426
|
+
inputSchema: {
|
|
427
|
+
type: "object",
|
|
428
|
+
properties: {
|
|
429
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
430
|
+
x: { type: "integer" },
|
|
431
|
+
y: { type: "integer" },
|
|
432
|
+
button: {
|
|
342
433
|
type: "string",
|
|
343
|
-
|
|
434
|
+
enum: ["left", "right", "middle"],
|
|
435
|
+
default: "left",
|
|
344
436
|
},
|
|
345
|
-
|
|
437
|
+
},
|
|
438
|
+
required: ["computer_id", "x", "y"],
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "computer_mouse_up",
|
|
443
|
+
description: "Release a held mouse button at (x, y).",
|
|
444
|
+
inputSchema: {
|
|
445
|
+
type: "object",
|
|
446
|
+
properties: {
|
|
447
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
448
|
+
x: { type: "integer" },
|
|
449
|
+
y: { type: "integer" },
|
|
450
|
+
button: {
|
|
346
451
|
type: "string",
|
|
347
|
-
|
|
452
|
+
enum: ["left", "right", "middle"],
|
|
453
|
+
default: "left",
|
|
348
454
|
},
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
455
|
+
},
|
|
456
|
+
required: ["computer_id", "x", "y"],
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: "computer_key_down",
|
|
461
|
+
description: "Press and hold a key without releasing it. Pair with computer_key_up.",
|
|
462
|
+
inputSchema: {
|
|
463
|
+
type: "object",
|
|
464
|
+
properties: {
|
|
465
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
466
|
+
key: { type: "string", description: "Key name to hold down" },
|
|
467
|
+
},
|
|
468
|
+
required: ["computer_id", "key"],
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "computer_key_up",
|
|
473
|
+
description: "Release a previously held key.",
|
|
474
|
+
inputSchema: {
|
|
475
|
+
type: "object",
|
|
476
|
+
properties: {
|
|
477
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
478
|
+
key: { type: "string", description: "Key name to release" },
|
|
479
|
+
},
|
|
480
|
+
required: ["computer_id", "key"],
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: "computer_wait",
|
|
485
|
+
description: "Pause execution inside the computer for N seconds.",
|
|
486
|
+
inputSchema: {
|
|
487
|
+
type: "object",
|
|
488
|
+
properties: {
|
|
489
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
490
|
+
seconds: {
|
|
491
|
+
type: "number",
|
|
492
|
+
description: "Seconds to wait (may be fractional, e.g. 0.5)",
|
|
354
493
|
},
|
|
355
494
|
},
|
|
495
|
+
required: ["computer_id", "seconds"],
|
|
356
496
|
},
|
|
357
497
|
},
|
|
358
498
|
{
|
|
359
|
-
name: "
|
|
360
|
-
description: "
|
|
499
|
+
name: "computer_focus_window",
|
|
500
|
+
description: "Bring a window to the foreground by its window ID.",
|
|
361
501
|
inputSchema: {
|
|
362
502
|
type: "object",
|
|
363
503
|
properties: {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
type: "integer",
|
|
369
|
-
description: "Timeout in seconds (optional)",
|
|
504
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
505
|
+
window_id: {
|
|
506
|
+
type: "string",
|
|
507
|
+
description: "Window ID from computer_windows",
|
|
370
508
|
},
|
|
371
509
|
},
|
|
372
|
-
required: ["
|
|
510
|
+
required: ["computer_id", "window_id"],
|
|
373
511
|
},
|
|
374
512
|
},
|
|
375
513
|
{
|
|
376
|
-
name: "
|
|
377
|
-
description: "
|
|
514
|
+
name: "computer_set_window_size",
|
|
515
|
+
description: "Resize a window to the given width and height in pixels.",
|
|
378
516
|
inputSchema: {
|
|
379
517
|
type: "object",
|
|
380
518
|
properties: {
|
|
381
|
-
|
|
519
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
520
|
+
window_id: {
|
|
521
|
+
type: "string",
|
|
522
|
+
description: "Window ID from computer_windows",
|
|
523
|
+
},
|
|
524
|
+
width: { type: "integer" },
|
|
525
|
+
height: { type: "integer" },
|
|
382
526
|
},
|
|
383
|
-
required: ["
|
|
527
|
+
required: ["computer_id", "window_id", "width", "height"],
|
|
384
528
|
},
|
|
385
529
|
},
|
|
386
|
-
// Deploy
|
|
387
530
|
{
|
|
388
|
-
name: "
|
|
389
|
-
description: "
|
|
531
|
+
name: "computer_set_window_position",
|
|
532
|
+
description: "Move a window to the given screen coordinates.",
|
|
390
533
|
inputSchema: {
|
|
391
534
|
type: "object",
|
|
392
535
|
properties: {
|
|
393
|
-
|
|
536
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
537
|
+
window_id: {
|
|
394
538
|
type: "string",
|
|
395
|
-
description: "
|
|
539
|
+
description: "Window ID from computer_windows",
|
|
396
540
|
},
|
|
541
|
+
x: { type: "integer" },
|
|
542
|
+
y: { type: "integer" },
|
|
397
543
|
},
|
|
398
|
-
required: ["
|
|
544
|
+
required: ["computer_id", "window_id", "x", "y"],
|
|
399
545
|
},
|
|
400
546
|
},
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
{
|
|
413
|
-
type: "image",
|
|
414
|
-
data: pngBytes.toString("base64"),
|
|
415
|
-
mimeType: "image/png",
|
|
547
|
+
{
|
|
548
|
+
name: "computer_maximize_window",
|
|
549
|
+
description: "Maximize a window.",
|
|
550
|
+
inputSchema: {
|
|
551
|
+
type: "object",
|
|
552
|
+
properties: {
|
|
553
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
554
|
+
window_id: {
|
|
555
|
+
type: "string",
|
|
556
|
+
description: "Window ID from computer_windows",
|
|
557
|
+
},
|
|
416
558
|
},
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
559
|
+
required: ["computer_id", "window_id"],
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "computer_minimize_window",
|
|
564
|
+
description: "Minimize (iconify) a window.",
|
|
565
|
+
inputSchema: {
|
|
566
|
+
type: "object",
|
|
567
|
+
properties: {
|
|
568
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
569
|
+
window_id: {
|
|
570
|
+
type: "string",
|
|
571
|
+
description: "Window ID from computer_windows",
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
required: ["computer_id", "window_id"],
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
name: "computer_close_window",
|
|
579
|
+
description: "Close a window.",
|
|
580
|
+
inputSchema: {
|
|
581
|
+
type: "object",
|
|
582
|
+
properties: {
|
|
583
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
584
|
+
window_id: {
|
|
585
|
+
type: "string",
|
|
586
|
+
description: "Window ID from computer_windows",
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
required: ["computer_id", "window_id"],
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: "computer_get_desktop_env",
|
|
594
|
+
description: "Get desktop environment info (name, resolution, session type).",
|
|
595
|
+
inputSchema: {
|
|
596
|
+
type: "object",
|
|
597
|
+
properties: {
|
|
598
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
599
|
+
},
|
|
600
|
+
required: ["computer_id"],
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
name: "computer_set_wallpaper",
|
|
605
|
+
description: "Set the desktop wallpaper. Accepts an absolute path inside the VM (e.g. '/home/ubuntu/bg.png') or an https:// URL.",
|
|
606
|
+
inputSchema: {
|
|
607
|
+
type: "object",
|
|
608
|
+
properties: {
|
|
609
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
610
|
+
path: {
|
|
611
|
+
type: "string",
|
|
612
|
+
description: "Absolute VM path or https:// URL for the wallpaper image",
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
required: ["computer_id", "path"],
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: "computer_accessibility_tree",
|
|
620
|
+
description: "Get the AT-SPI accessibility tree for the current desktop state. Returns a nested JSON structure describing all visible UI elements with roles, names, bounding boxes, and parent/child relationships.",
|
|
621
|
+
inputSchema: {
|
|
622
|
+
type: "object",
|
|
623
|
+
properties: {
|
|
624
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
625
|
+
},
|
|
626
|
+
required: ["computer_id"],
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
// Files — extended
|
|
630
|
+
{
|
|
631
|
+
name: "computer_list_files",
|
|
632
|
+
description: "List directory contents on the computer.",
|
|
633
|
+
inputSchema: {
|
|
634
|
+
type: "object",
|
|
635
|
+
properties: {
|
|
636
|
+
computer_id: { type: "string" },
|
|
637
|
+
path: {
|
|
638
|
+
type: "string",
|
|
639
|
+
description: "Directory path to list (default: /)",
|
|
640
|
+
},
|
|
641
|
+
},
|
|
642
|
+
required: ["computer_id"],
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: "computer_stat_file",
|
|
647
|
+
description: "Get file/directory metadata (size, type, permissions, mtime) inside the computer.",
|
|
648
|
+
inputSchema: {
|
|
649
|
+
type: "object",
|
|
650
|
+
properties: {
|
|
651
|
+
computer_id: { type: "string" },
|
|
652
|
+
path: { type: "string", description: "Absolute path inside the VM" },
|
|
653
|
+
},
|
|
654
|
+
required: ["computer_id", "path"],
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
name: "computer_mkdir",
|
|
659
|
+
description: "Create a directory (and any missing parents) inside the computer.",
|
|
660
|
+
inputSchema: {
|
|
661
|
+
type: "object",
|
|
662
|
+
properties: {
|
|
663
|
+
computer_id: { type: "string" },
|
|
664
|
+
path: {
|
|
665
|
+
type: "string",
|
|
666
|
+
description: "Absolute directory path to create",
|
|
667
|
+
},
|
|
668
|
+
recursive: {
|
|
669
|
+
type: "boolean",
|
|
670
|
+
description: "Create parent directories if missing (default: true)",
|
|
671
|
+
default: true,
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
required: ["computer_id", "path"],
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
name: "computer_rename_file",
|
|
679
|
+
description: "Rename or move a file/directory inside the computer.",
|
|
680
|
+
inputSchema: {
|
|
681
|
+
type: "object",
|
|
682
|
+
properties: {
|
|
683
|
+
computer_id: { type: "string" },
|
|
684
|
+
source: { type: "string", description: "Current absolute path" },
|
|
685
|
+
dest: { type: "string", description: "New absolute path" },
|
|
686
|
+
},
|
|
687
|
+
required: ["computer_id", "source", "dest"],
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
name: "computer_copy_file",
|
|
692
|
+
description: "Copy a file or directory tree inside the computer.",
|
|
693
|
+
inputSchema: {
|
|
694
|
+
type: "object",
|
|
695
|
+
properties: {
|
|
696
|
+
computer_id: { type: "string" },
|
|
697
|
+
source: { type: "string", description: "Source absolute path" },
|
|
698
|
+
dest: { type: "string", description: "Destination absolute path" },
|
|
699
|
+
recursive: {
|
|
700
|
+
type: "boolean",
|
|
701
|
+
description: "Copy directory trees recursively (default: false)",
|
|
702
|
+
default: false,
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
required: ["computer_id", "source", "dest"],
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
name: "computer_delete_file",
|
|
710
|
+
description: "Delete a file or directory inside the computer.",
|
|
711
|
+
inputSchema: {
|
|
712
|
+
type: "object",
|
|
713
|
+
properties: {
|
|
714
|
+
computer_id: { type: "string" },
|
|
715
|
+
path: { type: "string", description: "Absolute path to delete" },
|
|
716
|
+
},
|
|
717
|
+
required: ["computer_id", "path"],
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
name: "computer_upload_file",
|
|
722
|
+
description: "Upload a local file from the host machine into the computer at a given path.",
|
|
723
|
+
inputSchema: {
|
|
724
|
+
type: "object",
|
|
725
|
+
properties: {
|
|
726
|
+
computer_id: { type: "string" },
|
|
727
|
+
local_path: {
|
|
728
|
+
type: "string",
|
|
729
|
+
description: "Absolute path on the local host to upload",
|
|
730
|
+
},
|
|
731
|
+
remote_path: {
|
|
732
|
+
type: "string",
|
|
733
|
+
description: "Destination absolute path inside the VM",
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
required: ["computer_id", "local_path", "remote_path"],
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
// Checkpoints
|
|
740
|
+
{
|
|
741
|
+
name: "computer_checkpoint_create",
|
|
742
|
+
description: "Save the current state of the computer as a checkpoint (snapshot).",
|
|
743
|
+
inputSchema: {
|
|
744
|
+
type: "object",
|
|
745
|
+
properties: {
|
|
746
|
+
computer_id: { type: "string" },
|
|
747
|
+
comment: {
|
|
748
|
+
type: "string",
|
|
749
|
+
description: "Optional human-readable label for the checkpoint",
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
required: ["computer_id"],
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
name: "computer_checkpoint_list",
|
|
757
|
+
description: "List all saved checkpoints for the computer.",
|
|
758
|
+
inputSchema: {
|
|
759
|
+
type: "object",
|
|
760
|
+
properties: {
|
|
761
|
+
computer_id: { type: "string" },
|
|
762
|
+
},
|
|
763
|
+
required: ["computer_id"],
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
name: "computer_checkpoint_restore",
|
|
768
|
+
description: "Restore the computer to a previously saved checkpoint.",
|
|
769
|
+
inputSchema: {
|
|
770
|
+
type: "object",
|
|
771
|
+
properties: {
|
|
772
|
+
computer_id: { type: "string" },
|
|
773
|
+
checkpoint_id: {
|
|
774
|
+
type: "string",
|
|
775
|
+
description: "ID of the checkpoint to restore",
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
required: ["computer_id", "checkpoint_id"],
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
name: "computer_checkpoint_delete",
|
|
783
|
+
description: "Delete a saved checkpoint.",
|
|
784
|
+
inputSchema: {
|
|
785
|
+
type: "object",
|
|
786
|
+
properties: {
|
|
787
|
+
computer_id: { type: "string" },
|
|
788
|
+
checkpoint_id: {
|
|
789
|
+
type: "string",
|
|
790
|
+
description: "ID of the checkpoint to delete",
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
required: ["computer_id", "checkpoint_id"],
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
// Services
|
|
797
|
+
{
|
|
798
|
+
name: "computer_service_create",
|
|
799
|
+
description: "Create and start a long-running background service on the computer (like systemd — process manager for your VM).",
|
|
800
|
+
inputSchema: {
|
|
801
|
+
type: "object",
|
|
802
|
+
properties: {
|
|
803
|
+
computer_id: { type: "string" },
|
|
804
|
+
name: { type: "string", description: "Service name (must be unique)" },
|
|
805
|
+
command: { type: "string", description: "Shell command to run" },
|
|
806
|
+
working_dir: {
|
|
807
|
+
type: "string",
|
|
808
|
+
description: "Working directory for the service (optional)",
|
|
809
|
+
},
|
|
810
|
+
port: {
|
|
811
|
+
type: "integer",
|
|
812
|
+
description: "Port the service listens on (optional)",
|
|
813
|
+
},
|
|
814
|
+
restart_policy: {
|
|
815
|
+
type: "string",
|
|
816
|
+
enum: ["always", "on-failure", "never"],
|
|
817
|
+
description: "Restart behaviour (default: always)",
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
required: ["computer_id", "name", "command"],
|
|
821
|
+
},
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
name: "computer_service_list",
|
|
825
|
+
description: "List all background services registered on the computer.",
|
|
826
|
+
inputSchema: {
|
|
827
|
+
type: "object",
|
|
828
|
+
properties: {
|
|
829
|
+
computer_id: { type: "string" },
|
|
830
|
+
},
|
|
831
|
+
required: ["computer_id"],
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
name: "computer_service_start",
|
|
836
|
+
description: "Start a stopped background service.",
|
|
837
|
+
inputSchema: {
|
|
838
|
+
type: "object",
|
|
839
|
+
properties: {
|
|
840
|
+
computer_id: { type: "string" },
|
|
841
|
+
service_id: { type: "string", description: "Service ID to start" },
|
|
842
|
+
},
|
|
843
|
+
required: ["computer_id", "service_id"],
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
name: "computer_service_stop",
|
|
848
|
+
description: "Stop a running background service (sends SIGTERM).",
|
|
849
|
+
inputSchema: {
|
|
850
|
+
type: "object",
|
|
851
|
+
properties: {
|
|
852
|
+
computer_id: { type: "string" },
|
|
853
|
+
service_id: { type: "string", description: "Service ID to stop" },
|
|
854
|
+
},
|
|
855
|
+
required: ["computer_id", "service_id"],
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
name: "computer_service_restart",
|
|
860
|
+
description: "Restart a background service (stop then start).",
|
|
861
|
+
inputSchema: {
|
|
862
|
+
type: "object",
|
|
863
|
+
properties: {
|
|
864
|
+
computer_id: { type: "string" },
|
|
865
|
+
service_id: { type: "string", description: "Service ID to restart" },
|
|
866
|
+
},
|
|
867
|
+
required: ["computer_id", "service_id"],
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
name: "computer_service_logs",
|
|
872
|
+
description: "Retrieve recent log output from a background service (last 100 lines).",
|
|
873
|
+
inputSchema: {
|
|
874
|
+
type: "object",
|
|
875
|
+
properties: {
|
|
876
|
+
computer_id: { type: "string" },
|
|
877
|
+
service_id: { type: "string", description: "Service ID" },
|
|
878
|
+
},
|
|
879
|
+
required: ["computer_id", "service_id"],
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
name: "computer_service_delete",
|
|
884
|
+
description: "Delete a background service (stops it first if running).",
|
|
885
|
+
inputSchema: {
|
|
886
|
+
type: "object",
|
|
887
|
+
properties: {
|
|
888
|
+
computer_id: { type: "string" },
|
|
889
|
+
service_id: { type: "string", description: "Service ID to delete" },
|
|
890
|
+
},
|
|
891
|
+
required: ["computer_id", "service_id"],
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
// Env vars
|
|
895
|
+
{
|
|
896
|
+
name: "computer_env_list",
|
|
897
|
+
description: "List all environment variables set on the computer.",
|
|
898
|
+
inputSchema: {
|
|
899
|
+
type: "object",
|
|
900
|
+
properties: {
|
|
901
|
+
computer_id: { type: "string" },
|
|
902
|
+
},
|
|
903
|
+
required: ["computer_id"],
|
|
904
|
+
},
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
name: "computer_env_set",
|
|
908
|
+
description: "Set (create or update) an environment variable on the computer.",
|
|
909
|
+
inputSchema: {
|
|
910
|
+
type: "object",
|
|
911
|
+
properties: {
|
|
912
|
+
computer_id: { type: "string" },
|
|
913
|
+
name: { type: "string", description: "Variable name" },
|
|
914
|
+
value: { type: "string", description: "Variable value" },
|
|
915
|
+
},
|
|
916
|
+
required: ["computer_id", "name", "value"],
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
name: "computer_env_delete",
|
|
921
|
+
description: "Delete an environment variable from the computer.",
|
|
922
|
+
inputSchema: {
|
|
923
|
+
type: "object",
|
|
924
|
+
properties: {
|
|
925
|
+
computer_id: { type: "string" },
|
|
926
|
+
name: {
|
|
927
|
+
type: "string",
|
|
928
|
+
description: "Variable name to delete",
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
required: ["computer_id", "name"],
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
// Logs
|
|
935
|
+
{
|
|
936
|
+
name: "computer_logs",
|
|
937
|
+
description: "Get recent VM-level logs from the computer.",
|
|
938
|
+
inputSchema: {
|
|
939
|
+
type: "object",
|
|
940
|
+
properties: {
|
|
941
|
+
computer_id: { type: "string" },
|
|
942
|
+
lines: {
|
|
943
|
+
type: "integer",
|
|
944
|
+
description: "Number of recent log lines to return (optional)",
|
|
945
|
+
},
|
|
946
|
+
},
|
|
947
|
+
required: ["computer_id"],
|
|
948
|
+
},
|
|
949
|
+
},
|
|
950
|
+
// Domains
|
|
951
|
+
{
|
|
952
|
+
name: "computer_domain_add",
|
|
953
|
+
description: "Add a custom domain to the computer. Returns CNAME verification instructions to add to your DNS registrar.",
|
|
954
|
+
inputSchema: {
|
|
955
|
+
type: "object",
|
|
956
|
+
properties: {
|
|
957
|
+
computer_id: { type: "string" },
|
|
958
|
+
fqdn: {
|
|
959
|
+
type: "string",
|
|
960
|
+
description: "Fully-qualified domain name (e.g. app.example.com)",
|
|
961
|
+
},
|
|
962
|
+
},
|
|
963
|
+
required: ["computer_id", "fqdn"],
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
name: "computer_domain_list",
|
|
968
|
+
description: "List all custom domains registered for the computer.",
|
|
969
|
+
inputSchema: {
|
|
970
|
+
type: "object",
|
|
971
|
+
properties: {
|
|
972
|
+
computer_id: { type: "string" },
|
|
973
|
+
},
|
|
974
|
+
required: ["computer_id"],
|
|
975
|
+
},
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
name: "computer_domain_delete",
|
|
979
|
+
description: "Delete a custom domain mapping from the computer.",
|
|
980
|
+
inputSchema: {
|
|
981
|
+
type: "object",
|
|
982
|
+
properties: {
|
|
983
|
+
computer_id: { type: "string" },
|
|
984
|
+
domain_id: {
|
|
985
|
+
type: "string",
|
|
986
|
+
description: "Domain ID to delete",
|
|
987
|
+
},
|
|
988
|
+
},
|
|
989
|
+
required: ["computer_id", "domain_id"],
|
|
990
|
+
},
|
|
991
|
+
},
|
|
992
|
+
// Sandboxes
|
|
993
|
+
{
|
|
994
|
+
name: "sandbox_create",
|
|
995
|
+
description: "Create a new lightweight code sandbox (Firecracker microVM without desktop).",
|
|
996
|
+
inputSchema: {
|
|
997
|
+
type: "object",
|
|
998
|
+
properties: {
|
|
999
|
+
name: {
|
|
1000
|
+
type: "string",
|
|
1001
|
+
description: "Human-readable name for the sandbox",
|
|
1002
|
+
},
|
|
1003
|
+
template_id: {
|
|
1004
|
+
type: "string",
|
|
1005
|
+
description: "Template / image ID (default: miosa-sandbox)",
|
|
1006
|
+
},
|
|
1007
|
+
cpu_count: { type: "integer", description: "vCPU count" },
|
|
1008
|
+
memory_mb: { type: "integer", description: "Memory in MB" },
|
|
1009
|
+
timeout_sec: {
|
|
1010
|
+
type: "integer",
|
|
1011
|
+
description: "Idle timeout in seconds",
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
},
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
name: "sandbox_exec",
|
|
1018
|
+
description: "Run a bash command inside a sandbox and return the output.",
|
|
1019
|
+
inputSchema: {
|
|
1020
|
+
type: "object",
|
|
1021
|
+
properties: {
|
|
1022
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1023
|
+
command: { type: "string", description: "Command to run" },
|
|
1024
|
+
cwd: { type: "string", description: "Working directory (optional)" },
|
|
1025
|
+
timeout: {
|
|
1026
|
+
type: "integer",
|
|
1027
|
+
description: "Timeout in seconds (optional)",
|
|
1028
|
+
},
|
|
1029
|
+
},
|
|
1030
|
+
required: ["sandbox_id", "command"],
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
name: "sandbox_destroy",
|
|
1035
|
+
description: "Destroy a sandbox permanently.",
|
|
1036
|
+
inputSchema: {
|
|
1037
|
+
type: "object",
|
|
1038
|
+
properties: {
|
|
1039
|
+
sandbox_id: { type: "string", description: "Sandbox ID to destroy" },
|
|
1040
|
+
},
|
|
1041
|
+
required: ["sandbox_id"],
|
|
1042
|
+
},
|
|
1043
|
+
},
|
|
1044
|
+
// Sandbox lifecycle (list/get/pause/resume)
|
|
1045
|
+
{
|
|
1046
|
+
name: "sandbox_list",
|
|
1047
|
+
description: "List all sandboxes in the tenant.",
|
|
1048
|
+
inputSchema: {
|
|
1049
|
+
type: "object",
|
|
1050
|
+
properties: {
|
|
1051
|
+
state: {
|
|
1052
|
+
type: "string",
|
|
1053
|
+
enum: ["provisioning", "running", "paused", "destroyed", "error"],
|
|
1054
|
+
description: "Filter by state (optional)",
|
|
1055
|
+
},
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
name: "sandbox_get",
|
|
1061
|
+
description: "Get details of a specific sandbox.",
|
|
1062
|
+
inputSchema: {
|
|
1063
|
+
type: "object",
|
|
1064
|
+
properties: {
|
|
1065
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1066
|
+
},
|
|
1067
|
+
required: ["sandbox_id"],
|
|
1068
|
+
},
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
name: "sandbox_pause",
|
|
1072
|
+
description: "Pause a running sandbox (suspends it to save compute).",
|
|
1073
|
+
inputSchema: {
|
|
1074
|
+
type: "object",
|
|
1075
|
+
properties: {
|
|
1076
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1077
|
+
},
|
|
1078
|
+
required: ["sandbox_id"],
|
|
1079
|
+
},
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
name: "sandbox_resume",
|
|
1083
|
+
description: "Resume a paused sandbox.",
|
|
1084
|
+
inputSchema: {
|
|
1085
|
+
type: "object",
|
|
1086
|
+
properties: {
|
|
1087
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1088
|
+
},
|
|
1089
|
+
required: ["sandbox_id"],
|
|
1090
|
+
},
|
|
1091
|
+
},
|
|
1092
|
+
// Sandbox files
|
|
1093
|
+
{
|
|
1094
|
+
name: "sandbox_write_file",
|
|
1095
|
+
description: "Write text content to a file path inside a sandbox.",
|
|
1096
|
+
inputSchema: {
|
|
1097
|
+
type: "object",
|
|
1098
|
+
properties: {
|
|
1099
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1100
|
+
path: {
|
|
1101
|
+
type: "string",
|
|
1102
|
+
description: "Absolute path inside the sandbox",
|
|
1103
|
+
},
|
|
1104
|
+
content: { type: "string", description: "File content (text)" },
|
|
1105
|
+
},
|
|
1106
|
+
required: ["sandbox_id", "path", "content"],
|
|
1107
|
+
},
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
name: "sandbox_read_file",
|
|
1111
|
+
description: "Read a file from inside a sandbox and return its content as text.",
|
|
1112
|
+
inputSchema: {
|
|
1113
|
+
type: "object",
|
|
1114
|
+
properties: {
|
|
1115
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1116
|
+
path: {
|
|
1117
|
+
type: "string",
|
|
1118
|
+
description: "Absolute path inside the sandbox",
|
|
1119
|
+
},
|
|
1120
|
+
},
|
|
1121
|
+
required: ["sandbox_id", "path"],
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
name: "sandbox_list_files",
|
|
1126
|
+
description: "List files in a directory inside a sandbox.",
|
|
1127
|
+
inputSchema: {
|
|
1128
|
+
type: "object",
|
|
1129
|
+
properties: {
|
|
1130
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1131
|
+
path: {
|
|
1132
|
+
type: "string",
|
|
1133
|
+
description: "Directory path to list (default: /workspace)",
|
|
1134
|
+
default: "/workspace",
|
|
1135
|
+
},
|
|
1136
|
+
depth: { type: "integer", description: "Recursion depth (optional)" },
|
|
1137
|
+
},
|
|
1138
|
+
required: ["sandbox_id"],
|
|
1139
|
+
},
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
name: "sandbox_upload",
|
|
1143
|
+
description: "Upload a file (UTF-8 text or base64 binary) into a sandbox.",
|
|
1144
|
+
inputSchema: {
|
|
1145
|
+
type: "object",
|
|
1146
|
+
properties: {
|
|
1147
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1148
|
+
path: {
|
|
1149
|
+
type: "string",
|
|
1150
|
+
description: "Destination path inside the sandbox",
|
|
1151
|
+
},
|
|
1152
|
+
content: {
|
|
1153
|
+
type: "string",
|
|
1154
|
+
description: "File content as UTF-8 text or base64-encoded binary",
|
|
1155
|
+
},
|
|
1156
|
+
},
|
|
1157
|
+
required: ["sandbox_id", "path", "content"],
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
// Sandbox exec
|
|
1161
|
+
{
|
|
1162
|
+
name: "sandbox_python",
|
|
1163
|
+
description: "Run Python code inside a sandbox and return stdout, stderr, and exit code.",
|
|
1164
|
+
inputSchema: {
|
|
1165
|
+
type: "object",
|
|
1166
|
+
properties: {
|
|
1167
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1168
|
+
code: { type: "string", description: "Python code to execute" },
|
|
1169
|
+
timeout: {
|
|
1170
|
+
type: "integer",
|
|
1171
|
+
description: "Timeout in seconds (optional)",
|
|
1172
|
+
},
|
|
1173
|
+
},
|
|
1174
|
+
required: ["sandbox_id", "code"],
|
|
1175
|
+
},
|
|
1176
|
+
},
|
|
1177
|
+
// Sandbox snapshots
|
|
1178
|
+
{
|
|
1179
|
+
name: "sandbox_snapshot_create",
|
|
1180
|
+
description: "Create a snapshot of the current sandbox state.",
|
|
1181
|
+
inputSchema: {
|
|
1182
|
+
type: "object",
|
|
1183
|
+
properties: {
|
|
1184
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1185
|
+
comment: {
|
|
1186
|
+
type: "string",
|
|
1187
|
+
description: "Optional comment for the snapshot",
|
|
1188
|
+
},
|
|
1189
|
+
},
|
|
1190
|
+
required: ["sandbox_id"],
|
|
1191
|
+
},
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
name: "sandbox_snapshot_list",
|
|
1195
|
+
description: "List all snapshots for a sandbox.",
|
|
1196
|
+
inputSchema: {
|
|
1197
|
+
type: "object",
|
|
1198
|
+
properties: {
|
|
1199
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1200
|
+
},
|
|
1201
|
+
required: ["sandbox_id"],
|
|
1202
|
+
},
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
name: "sandbox_snapshot_restore",
|
|
1206
|
+
description: "Restore a sandbox from a specific snapshot.",
|
|
1207
|
+
inputSchema: {
|
|
1208
|
+
type: "object",
|
|
1209
|
+
properties: {
|
|
1210
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1211
|
+
snapshot_id: {
|
|
1212
|
+
type: "string",
|
|
1213
|
+
description: "Snapshot ID to restore from",
|
|
1214
|
+
},
|
|
1215
|
+
},
|
|
1216
|
+
required: ["sandbox_id", "snapshot_id"],
|
|
1217
|
+
},
|
|
1218
|
+
},
|
|
1219
|
+
// Sandbox logs
|
|
1220
|
+
{
|
|
1221
|
+
name: "sandbox_logs",
|
|
1222
|
+
description: "Get logs from a sandbox.",
|
|
1223
|
+
inputSchema: {
|
|
1224
|
+
type: "object",
|
|
1225
|
+
properties: {
|
|
1226
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1227
|
+
lines: {
|
|
1228
|
+
type: "integer",
|
|
1229
|
+
description: "Number of log lines to return (optional)",
|
|
1230
|
+
},
|
|
1231
|
+
},
|
|
1232
|
+
required: ["sandbox_id"],
|
|
1233
|
+
},
|
|
1234
|
+
},
|
|
1235
|
+
// Sandbox preview
|
|
1236
|
+
{
|
|
1237
|
+
name: "sandbox_expose",
|
|
1238
|
+
description: "Expose a port on a sandbox and return a publicly accessible preview URL.",
|
|
1239
|
+
inputSchema: {
|
|
1240
|
+
type: "object",
|
|
1241
|
+
properties: {
|
|
1242
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1243
|
+
port: {
|
|
1244
|
+
type: "integer",
|
|
1245
|
+
description: "Port number to expose (optional; exposes default port if omitted)",
|
|
1246
|
+
},
|
|
1247
|
+
},
|
|
1248
|
+
required: ["sandbox_id"],
|
|
1249
|
+
},
|
|
1250
|
+
},
|
|
1251
|
+
// Sandbox deploy
|
|
1252
|
+
{
|
|
1253
|
+
name: "sandbox_deploy",
|
|
1254
|
+
description: "Deploy sandbox contents to production.",
|
|
1255
|
+
inputSchema: {
|
|
1256
|
+
type: "object",
|
|
1257
|
+
properties: {
|
|
1258
|
+
sandbox_id: { type: "string", description: "Sandbox ID" },
|
|
1259
|
+
name: { type: "string", description: "Deployment name (optional)" },
|
|
1260
|
+
output_path: {
|
|
1261
|
+
type: "string",
|
|
1262
|
+
description: "Path inside the sandbox to deploy (optional)",
|
|
1263
|
+
},
|
|
1264
|
+
entrypoint: {
|
|
1265
|
+
type: "string",
|
|
1266
|
+
description: "Entrypoint command or file (optional)",
|
|
1267
|
+
},
|
|
1268
|
+
domain: { type: "string", description: "Custom domain (optional)" },
|
|
1269
|
+
},
|
|
1270
|
+
required: ["sandbox_id"],
|
|
1271
|
+
},
|
|
1272
|
+
},
|
|
1273
|
+
// Sandbox templates
|
|
1274
|
+
{
|
|
1275
|
+
name: "sandbox_template_list",
|
|
1276
|
+
description: "List available sandbox templates.",
|
|
1277
|
+
inputSchema: { type: "object", properties: {} },
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
name: "sandbox_template_create",
|
|
1281
|
+
description: "Create a custom sandbox template from a build spec.",
|
|
1282
|
+
inputSchema: {
|
|
1283
|
+
type: "object",
|
|
1284
|
+
properties: {
|
|
1285
|
+
name: { type: "string", description: "Template name" },
|
|
1286
|
+
build_spec: {
|
|
1287
|
+
type: "object",
|
|
1288
|
+
description: "Build specification (Dockerfile, packages, etc.)",
|
|
1289
|
+
},
|
|
1290
|
+
slug: {
|
|
1291
|
+
type: "string",
|
|
1292
|
+
description: "URL-friendly identifier (optional)",
|
|
1293
|
+
},
|
|
1294
|
+
description: {
|
|
1295
|
+
type: "string",
|
|
1296
|
+
description: "Human-readable description (optional)",
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
1299
|
+
required: ["name", "build_spec"],
|
|
1300
|
+
},
|
|
1301
|
+
},
|
|
1302
|
+
// Deploy
|
|
1303
|
+
{
|
|
1304
|
+
name: "deploy",
|
|
1305
|
+
description: "Trigger a redeploy for an existing MIOSA deployment.",
|
|
1306
|
+
inputSchema: {
|
|
1307
|
+
type: "object",
|
|
1308
|
+
properties: {
|
|
1309
|
+
deployment_id: {
|
|
1310
|
+
type: "string",
|
|
1311
|
+
description: "Deployment ID to redeploy",
|
|
1312
|
+
},
|
|
1313
|
+
},
|
|
1314
|
+
required: ["deployment_id"],
|
|
1315
|
+
},
|
|
1316
|
+
},
|
|
1317
|
+
// Deployments
|
|
1318
|
+
{
|
|
1319
|
+
name: "deployment_list",
|
|
1320
|
+
description: "List all deployments in the tenant.",
|
|
1321
|
+
inputSchema: { type: "object", properties: {} },
|
|
1322
|
+
},
|
|
1323
|
+
{
|
|
1324
|
+
name: "deployment_get",
|
|
1325
|
+
description: "Get details of a specific deployment.",
|
|
1326
|
+
inputSchema: {
|
|
1327
|
+
type: "object",
|
|
1328
|
+
properties: {
|
|
1329
|
+
deployment_id: { type: "string", description: "Deployment ID" },
|
|
1330
|
+
},
|
|
1331
|
+
required: ["deployment_id"],
|
|
1332
|
+
},
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
name: "deployment_create",
|
|
1336
|
+
description: "Create a new deployment.",
|
|
1337
|
+
inputSchema: {
|
|
1338
|
+
type: "object",
|
|
1339
|
+
properties: {
|
|
1340
|
+
name: { type: "string", description: "Deployment name" },
|
|
1341
|
+
type: {
|
|
1342
|
+
type: "string",
|
|
1343
|
+
description: "Deployment type (e.g. web, worker)",
|
|
1344
|
+
},
|
|
1345
|
+
source: { type: "object", description: "Source configuration" },
|
|
1346
|
+
env_vars: {
|
|
1347
|
+
type: "object",
|
|
1348
|
+
description: "Environment variables as key-value pairs",
|
|
1349
|
+
},
|
|
1350
|
+
region: { type: "string", description: "Deployment region (optional)" },
|
|
1351
|
+
},
|
|
1352
|
+
required: ["name"],
|
|
1353
|
+
},
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
name: "deployment_delete",
|
|
1357
|
+
description: "Delete a deployment permanently.",
|
|
1358
|
+
inputSchema: {
|
|
1359
|
+
type: "object",
|
|
1360
|
+
properties: {
|
|
1361
|
+
deployment_id: {
|
|
1362
|
+
type: "string",
|
|
1363
|
+
description: "Deployment ID to delete",
|
|
1364
|
+
},
|
|
1365
|
+
},
|
|
1366
|
+
required: ["deployment_id"],
|
|
1367
|
+
},
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
name: "deployment_publish",
|
|
1371
|
+
description: "Publish a new version of a deployment.",
|
|
1372
|
+
inputSchema: {
|
|
1373
|
+
type: "object",
|
|
1374
|
+
properties: {
|
|
1375
|
+
deployment_id: { type: "string", description: "Deployment ID" },
|
|
1376
|
+
source: {
|
|
1377
|
+
type: "object",
|
|
1378
|
+
description: "Source configuration for the new version",
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
required: ["deployment_id"],
|
|
1382
|
+
},
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
name: "deployment_rollback",
|
|
1386
|
+
description: "Rollback a deployment to a previous version.",
|
|
1387
|
+
inputSchema: {
|
|
1388
|
+
type: "object",
|
|
1389
|
+
properties: {
|
|
1390
|
+
deployment_id: { type: "string", description: "Deployment ID" },
|
|
1391
|
+
version_id: {
|
|
1392
|
+
type: "string",
|
|
1393
|
+
description: "Version ID to roll back to",
|
|
1394
|
+
},
|
|
1395
|
+
},
|
|
1396
|
+
required: ["deployment_id", "version_id"],
|
|
1397
|
+
},
|
|
1398
|
+
},
|
|
1399
|
+
{
|
|
1400
|
+
name: "deployment_env_list",
|
|
1401
|
+
description: "List environment variables for a deployment.",
|
|
1402
|
+
inputSchema: {
|
|
1403
|
+
type: "object",
|
|
1404
|
+
properties: {
|
|
1405
|
+
deployment_id: { type: "string", description: "Deployment ID" },
|
|
1406
|
+
},
|
|
1407
|
+
required: ["deployment_id"],
|
|
1408
|
+
},
|
|
1409
|
+
},
|
|
1410
|
+
{
|
|
1411
|
+
name: "deployment_env_set",
|
|
1412
|
+
description: "Set (create or update) an environment variable for a deployment.",
|
|
1413
|
+
inputSchema: {
|
|
1414
|
+
type: "object",
|
|
1415
|
+
properties: {
|
|
1416
|
+
deployment_id: { type: "string", description: "Deployment ID" },
|
|
1417
|
+
key: { type: "string", description: "Environment variable name" },
|
|
1418
|
+
value: { type: "string", description: "Environment variable value" },
|
|
1419
|
+
},
|
|
1420
|
+
required: ["deployment_id", "key", "value"],
|
|
1421
|
+
},
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
name: "deployment_logs",
|
|
1425
|
+
description: "Get logs for a deployment.",
|
|
1426
|
+
inputSchema: {
|
|
1427
|
+
type: "object",
|
|
1428
|
+
properties: {
|
|
1429
|
+
deployment_id: { type: "string", description: "Deployment ID" },
|
|
1430
|
+
lines: {
|
|
1431
|
+
type: "integer",
|
|
1432
|
+
description: "Number of log lines to return (default: 100)",
|
|
1433
|
+
default: 100,
|
|
1434
|
+
},
|
|
1435
|
+
since: {
|
|
1436
|
+
type: "string",
|
|
1437
|
+
description: "ISO 8601 timestamp to fetch logs from (optional)",
|
|
1438
|
+
},
|
|
1439
|
+
},
|
|
1440
|
+
required: ["deployment_id"],
|
|
1441
|
+
},
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
name: "deployment_version_list",
|
|
1445
|
+
description: "List all versions of a deployment.",
|
|
1446
|
+
inputSchema: {
|
|
1447
|
+
type: "object",
|
|
1448
|
+
properties: {
|
|
1449
|
+
deployment_id: { type: "string", description: "Deployment ID" },
|
|
1450
|
+
},
|
|
1451
|
+
required: ["deployment_id"],
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
name: "deployment_version_promote",
|
|
1456
|
+
description: "Promote a specific version to be the active deployment.",
|
|
1457
|
+
inputSchema: {
|
|
1458
|
+
type: "object",
|
|
1459
|
+
properties: {
|
|
1460
|
+
deployment_id: { type: "string", description: "Deployment ID" },
|
|
1461
|
+
version_id: { type: "string", description: "Version ID to promote" },
|
|
1462
|
+
},
|
|
1463
|
+
required: ["deployment_id", "version_id"],
|
|
1464
|
+
},
|
|
1465
|
+
},
|
|
1466
|
+
// Storage
|
|
1467
|
+
{
|
|
1468
|
+
name: "storage_bucket_list",
|
|
1469
|
+
description: "List all storage buckets in the tenant.",
|
|
1470
|
+
inputSchema: { type: "object", properties: {} },
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "storage_bucket_create",
|
|
1474
|
+
description: "Create a new storage bucket.",
|
|
1475
|
+
inputSchema: {
|
|
1476
|
+
type: "object",
|
|
1477
|
+
properties: {
|
|
1478
|
+
name: { type: "string", description: "Bucket name" },
|
|
1479
|
+
region: { type: "string", description: "Bucket region (optional)" },
|
|
1480
|
+
public: {
|
|
1481
|
+
type: "boolean",
|
|
1482
|
+
description: "Whether the bucket is publicly readable (default: false)",
|
|
1483
|
+
default: false,
|
|
1484
|
+
},
|
|
1485
|
+
},
|
|
1486
|
+
required: ["name"],
|
|
1487
|
+
},
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
name: "storage_bucket_delete",
|
|
1491
|
+
description: "Delete a storage bucket.",
|
|
1492
|
+
inputSchema: {
|
|
1493
|
+
type: "object",
|
|
1494
|
+
properties: {
|
|
1495
|
+
bucket_id: {
|
|
1496
|
+
type: "string",
|
|
1497
|
+
description: "Bucket ID or name to delete",
|
|
1498
|
+
},
|
|
1499
|
+
},
|
|
1500
|
+
required: ["bucket_id"],
|
|
1501
|
+
},
|
|
1502
|
+
},
|
|
1503
|
+
{
|
|
1504
|
+
name: "storage_object_list",
|
|
1505
|
+
description: "List objects in a storage bucket, optionally filtered by prefix.",
|
|
1506
|
+
inputSchema: {
|
|
1507
|
+
type: "object",
|
|
1508
|
+
properties: {
|
|
1509
|
+
bucket_id: { type: "string", description: "Bucket ID or name" },
|
|
1510
|
+
prefix: {
|
|
1511
|
+
type: "string",
|
|
1512
|
+
description: "Key prefix to filter by (optional)",
|
|
1513
|
+
},
|
|
1514
|
+
},
|
|
1515
|
+
required: ["bucket_id"],
|
|
1516
|
+
},
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
name: "storage_object_upload",
|
|
1520
|
+
description: "Upload an object to a storage bucket.",
|
|
1521
|
+
inputSchema: {
|
|
1522
|
+
type: "object",
|
|
1523
|
+
properties: {
|
|
1524
|
+
bucket_id: { type: "string", description: "Bucket ID or name" },
|
|
1525
|
+
key: { type: "string", description: "Object key (path within bucket)" },
|
|
1526
|
+
content: {
|
|
1527
|
+
type: "string",
|
|
1528
|
+
description: "Object content (text or base64-encoded binary)",
|
|
1529
|
+
},
|
|
1530
|
+
content_type: {
|
|
1531
|
+
type: "string",
|
|
1532
|
+
description: "MIME type of the object (optional)",
|
|
1533
|
+
},
|
|
1534
|
+
},
|
|
1535
|
+
required: ["bucket_id", "key", "content"],
|
|
1536
|
+
},
|
|
1537
|
+
},
|
|
1538
|
+
{
|
|
1539
|
+
name: "storage_object_download",
|
|
1540
|
+
description: "Download an object from a storage bucket.",
|
|
1541
|
+
inputSchema: {
|
|
1542
|
+
type: "object",
|
|
1543
|
+
properties: {
|
|
1544
|
+
bucket_id: { type: "string", description: "Bucket ID or name" },
|
|
1545
|
+
key: { type: "string", description: "Object key to download" },
|
|
1546
|
+
},
|
|
1547
|
+
required: ["bucket_id", "key"],
|
|
1548
|
+
},
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
name: "storage_object_delete",
|
|
1552
|
+
description: "Delete an object from a storage bucket.",
|
|
1553
|
+
inputSchema: {
|
|
1554
|
+
type: "object",
|
|
1555
|
+
properties: {
|
|
1556
|
+
bucket_id: { type: "string", description: "Bucket ID or name" },
|
|
1557
|
+
key: { type: "string", description: "Object key to delete" },
|
|
1558
|
+
},
|
|
1559
|
+
required: ["bucket_id", "key"],
|
|
1560
|
+
},
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
name: "storage_presign",
|
|
1564
|
+
description: "Get a presigned URL for temporary access to a storage object.",
|
|
1565
|
+
inputSchema: {
|
|
1566
|
+
type: "object",
|
|
1567
|
+
properties: {
|
|
1568
|
+
bucket_id: { type: "string", description: "Bucket ID or name" },
|
|
1569
|
+
key: { type: "string", description: "Object key" },
|
|
1570
|
+
expires_in: {
|
|
1571
|
+
type: "integer",
|
|
1572
|
+
description: "URL expiry in seconds (default: 3600)",
|
|
1573
|
+
default: 3600,
|
|
1574
|
+
},
|
|
1575
|
+
method: {
|
|
1576
|
+
type: "string",
|
|
1577
|
+
enum: ["GET", "PUT"],
|
|
1578
|
+
description: "HTTP method (default: GET)",
|
|
1579
|
+
default: "GET",
|
|
1580
|
+
},
|
|
1581
|
+
},
|
|
1582
|
+
required: ["bucket_id", "key"],
|
|
1583
|
+
},
|
|
1584
|
+
},
|
|
1585
|
+
// Databases
|
|
1586
|
+
{
|
|
1587
|
+
name: "database_list",
|
|
1588
|
+
description: "List all managed databases in the tenant.",
|
|
1589
|
+
inputSchema: { type: "object", properties: {} },
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
name: "database_create",
|
|
1593
|
+
description: "Create a new managed database.",
|
|
1594
|
+
inputSchema: {
|
|
1595
|
+
type: "object",
|
|
1596
|
+
properties: {
|
|
1597
|
+
name: { type: "string", description: "Database name" },
|
|
1598
|
+
engine: {
|
|
1599
|
+
type: "string",
|
|
1600
|
+
enum: ["postgres", "mysql", "redis"],
|
|
1601
|
+
description: "Database engine",
|
|
1602
|
+
},
|
|
1603
|
+
version: { type: "string", description: "Engine version (optional)" },
|
|
1604
|
+
size: { type: "string", description: "Database size/tier (optional)" },
|
|
1605
|
+
region: { type: "string", description: "Region (optional)" },
|
|
1606
|
+
},
|
|
1607
|
+
required: ["name", "engine"],
|
|
1608
|
+
},
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
name: "database_get",
|
|
1612
|
+
description: "Get details of a specific database.",
|
|
1613
|
+
inputSchema: {
|
|
1614
|
+
type: "object",
|
|
1615
|
+
properties: {
|
|
1616
|
+
database_id: { type: "string", description: "Database ID" },
|
|
1617
|
+
},
|
|
1618
|
+
required: ["database_id"],
|
|
1619
|
+
},
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
name: "database_delete",
|
|
1623
|
+
description: "Delete a managed database permanently.",
|
|
1624
|
+
inputSchema: {
|
|
1625
|
+
type: "object",
|
|
1626
|
+
properties: {
|
|
1627
|
+
database_id: { type: "string", description: "Database ID to delete" },
|
|
1628
|
+
},
|
|
1629
|
+
required: ["database_id"],
|
|
1630
|
+
},
|
|
1631
|
+
},
|
|
1632
|
+
{
|
|
1633
|
+
name: "database_credentials",
|
|
1634
|
+
description: "Get the connection string and credentials for a database.",
|
|
1635
|
+
inputSchema: {
|
|
1636
|
+
type: "object",
|
|
1637
|
+
properties: {
|
|
1638
|
+
database_id: { type: "string", description: "Database ID" },
|
|
1639
|
+
},
|
|
1640
|
+
required: ["database_id"],
|
|
1641
|
+
},
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
name: "database_logs",
|
|
1645
|
+
description: "Get logs for a managed database.",
|
|
1646
|
+
inputSchema: {
|
|
1647
|
+
type: "object",
|
|
1648
|
+
properties: {
|
|
1649
|
+
database_id: { type: "string", description: "Database ID" },
|
|
1650
|
+
lines: {
|
|
1651
|
+
type: "integer",
|
|
1652
|
+
description: "Number of log lines to return (default: 100)",
|
|
1653
|
+
default: 100,
|
|
1654
|
+
},
|
|
1655
|
+
since: {
|
|
1656
|
+
type: "string",
|
|
1657
|
+
description: "ISO 8601 timestamp to fetch logs from (optional)",
|
|
1658
|
+
},
|
|
1659
|
+
},
|
|
1660
|
+
required: ["database_id"],
|
|
1661
|
+
},
|
|
1662
|
+
},
|
|
1663
|
+
// Workspaces
|
|
1664
|
+
{
|
|
1665
|
+
name: "workspace_list",
|
|
1666
|
+
description: "List all workspaces in the tenant.",
|
|
1667
|
+
inputSchema: { type: "object", properties: {} },
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
name: "workspace_create",
|
|
1671
|
+
description: "Create a new workspace.",
|
|
1672
|
+
inputSchema: {
|
|
1673
|
+
type: "object",
|
|
1674
|
+
properties: {
|
|
1675
|
+
name: { type: "string", description: "Workspace name" },
|
|
1676
|
+
description: {
|
|
1677
|
+
type: "string",
|
|
1678
|
+
description: "Workspace description (optional)",
|
|
1679
|
+
},
|
|
1680
|
+
},
|
|
1681
|
+
required: ["name"],
|
|
1682
|
+
},
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
name: "workspace_get",
|
|
1686
|
+
description: "Get details of a specific workspace.",
|
|
1687
|
+
inputSchema: {
|
|
1688
|
+
type: "object",
|
|
1689
|
+
properties: {
|
|
1690
|
+
workspace_id: { type: "string", description: "Workspace ID" },
|
|
1691
|
+
},
|
|
1692
|
+
required: ["workspace_id"],
|
|
1693
|
+
},
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
name: "workspace_update",
|
|
1697
|
+
description: "Update a workspace's name or description.",
|
|
1698
|
+
inputSchema: {
|
|
1699
|
+
type: "object",
|
|
1700
|
+
properties: {
|
|
1701
|
+
workspace_id: { type: "string", description: "Workspace ID" },
|
|
1702
|
+
name: { type: "string", description: "New workspace name (optional)" },
|
|
1703
|
+
description: {
|
|
1704
|
+
type: "string",
|
|
1705
|
+
description: "New description (optional)",
|
|
1706
|
+
},
|
|
1707
|
+
},
|
|
1708
|
+
required: ["workspace_id"],
|
|
1709
|
+
},
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
name: "workspace_stats",
|
|
1713
|
+
description: "Get resource statistics for a workspace (computers, sandboxes, databases, etc.).",
|
|
1714
|
+
inputSchema: {
|
|
1715
|
+
type: "object",
|
|
1716
|
+
properties: {
|
|
1717
|
+
workspace_id: { type: "string", description: "Workspace ID" },
|
|
1718
|
+
},
|
|
1719
|
+
required: ["workspace_id"],
|
|
1720
|
+
},
|
|
1721
|
+
},
|
|
1722
|
+
{
|
|
1723
|
+
name: "workspace_usage",
|
|
1724
|
+
description: "Get usage data (compute hours, storage, bandwidth) for a workspace.",
|
|
1725
|
+
inputSchema: {
|
|
1726
|
+
type: "object",
|
|
1727
|
+
properties: {
|
|
1728
|
+
workspace_id: { type: "string", description: "Workspace ID" },
|
|
1729
|
+
period: {
|
|
1730
|
+
type: "string",
|
|
1731
|
+
description: "Billing period (e.g. '2026-05'). Defaults to current month.",
|
|
1732
|
+
},
|
|
1733
|
+
},
|
|
1734
|
+
required: ["workspace_id"],
|
|
1735
|
+
},
|
|
1736
|
+
},
|
|
1737
|
+
// Billing
|
|
1738
|
+
{
|
|
1739
|
+
name: "billing_usage",
|
|
1740
|
+
description: "Get current billing period usage for the tenant.",
|
|
1741
|
+
inputSchema: { type: "object", properties: {} },
|
|
1742
|
+
},
|
|
1743
|
+
{
|
|
1744
|
+
name: "billing_plan",
|
|
1745
|
+
description: "Get the current billing plan details for the tenant.",
|
|
1746
|
+
inputSchema: { type: "object", properties: {} },
|
|
1747
|
+
},
|
|
1748
|
+
// Tunnels / Port forwarding
|
|
1749
|
+
{
|
|
1750
|
+
name: "computer_expose_port",
|
|
1751
|
+
description: "Expose a port on the computer and return the public URL. The URL follows the pattern https://{port}-{slug}.computer.miosa.ai.",
|
|
1752
|
+
inputSchema: {
|
|
1753
|
+
type: "object",
|
|
1754
|
+
properties: {
|
|
1755
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
1756
|
+
port: { type: "integer", description: "Port number to expose" },
|
|
1757
|
+
protocol: {
|
|
1758
|
+
type: "string",
|
|
1759
|
+
enum: ["http", "https", "tcp"],
|
|
1760
|
+
description: "Protocol (default: http)",
|
|
1761
|
+
},
|
|
1762
|
+
},
|
|
1763
|
+
required: ["computer_id", "port"],
|
|
1764
|
+
},
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
name: "computer_list_ports",
|
|
1768
|
+
description: "List all currently exposed ports on the computer.",
|
|
1769
|
+
inputSchema: {
|
|
1770
|
+
type: "object",
|
|
1771
|
+
properties: {
|
|
1772
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
1773
|
+
},
|
|
1774
|
+
required: ["computer_id"],
|
|
1775
|
+
},
|
|
1776
|
+
},
|
|
1777
|
+
{
|
|
1778
|
+
name: "computer_preview_url",
|
|
1779
|
+
description: "Return the public preview URL for a given port on the computer. Format: https://{port}-{slug}.computer.miosa.ai",
|
|
1780
|
+
inputSchema: {
|
|
1781
|
+
type: "object",
|
|
1782
|
+
properties: {
|
|
1783
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
1784
|
+
port: { type: "integer", description: "Port number" },
|
|
1785
|
+
},
|
|
1786
|
+
required: ["computer_id", "port"],
|
|
1787
|
+
},
|
|
1788
|
+
},
|
|
1789
|
+
// Network policy
|
|
1790
|
+
{
|
|
1791
|
+
name: "computer_network_policy_get",
|
|
1792
|
+
description: "Get the current network policy (firewall rules) for the computer.",
|
|
1793
|
+
inputSchema: {
|
|
1794
|
+
type: "object",
|
|
1795
|
+
properties: {
|
|
1796
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
1797
|
+
},
|
|
1798
|
+
required: ["computer_id"],
|
|
1799
|
+
},
|
|
1800
|
+
},
|
|
1801
|
+
{
|
|
1802
|
+
name: "computer_network_policy_set",
|
|
1803
|
+
description: "Set the network policy (firewall rules) for the computer.",
|
|
1804
|
+
inputSchema: {
|
|
1805
|
+
type: "object",
|
|
1806
|
+
properties: {
|
|
1807
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
1808
|
+
rules: {
|
|
1809
|
+
type: "array",
|
|
1810
|
+
items: { type: "object" },
|
|
1811
|
+
description: "List of firewall rule objects (e.g. {direction, protocol, port, action})",
|
|
1812
|
+
},
|
|
1813
|
+
default_effect: {
|
|
1814
|
+
type: "string",
|
|
1815
|
+
enum: ["allow", "deny"],
|
|
1816
|
+
description: "Default action when no rule matches (default: allow)",
|
|
1817
|
+
},
|
|
1818
|
+
},
|
|
1819
|
+
required: ["computer_id", "rules"],
|
|
1820
|
+
},
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
name: "computer_network_policy_reset",
|
|
1824
|
+
description: "Reset the network policy for the computer to the platform default (allow all).",
|
|
1825
|
+
inputSchema: {
|
|
1826
|
+
type: "object",
|
|
1827
|
+
properties: {
|
|
1828
|
+
computer_id: { type: "string", description: "Computer ID." },
|
|
1829
|
+
},
|
|
1830
|
+
required: ["computer_id"],
|
|
1831
|
+
},
|
|
1832
|
+
},
|
|
1833
|
+
// Webhooks
|
|
1834
|
+
{
|
|
1835
|
+
name: "webhook_list",
|
|
1836
|
+
description: "List all webhooks registered in the tenant.",
|
|
1837
|
+
inputSchema: { type: "object", properties: {} },
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
name: "webhook_create",
|
|
1841
|
+
description: "Create a new webhook endpoint.",
|
|
1842
|
+
inputSchema: {
|
|
1843
|
+
type: "object",
|
|
1844
|
+
properties: {
|
|
1845
|
+
url: {
|
|
1846
|
+
type: "string",
|
|
1847
|
+
description: "HTTPS URL to deliver webhook events to",
|
|
1848
|
+
},
|
|
1849
|
+
events: {
|
|
1850
|
+
type: "array",
|
|
1851
|
+
items: { type: "string" },
|
|
1852
|
+
description: "List of event types to subscribe to (e.g. ['computer.started', 'computer.stopped'])",
|
|
1853
|
+
},
|
|
1854
|
+
},
|
|
1855
|
+
required: ["url", "events"],
|
|
1856
|
+
},
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
name: "webhook_delete",
|
|
1860
|
+
description: "Delete a webhook.",
|
|
1861
|
+
inputSchema: {
|
|
1862
|
+
type: "object",
|
|
1863
|
+
properties: {
|
|
1864
|
+
webhook_id: { type: "string", description: "Webhook ID to delete" },
|
|
1865
|
+
},
|
|
1866
|
+
required: ["webhook_id"],
|
|
1867
|
+
},
|
|
1868
|
+
},
|
|
1869
|
+
{
|
|
1870
|
+
name: "webhook_test",
|
|
1871
|
+
description: "Send a test event delivery to a webhook endpoint.",
|
|
1872
|
+
inputSchema: {
|
|
1873
|
+
type: "object",
|
|
1874
|
+
properties: {
|
|
1875
|
+
webhook_id: { type: "string", description: "Webhook ID to test" },
|
|
1876
|
+
},
|
|
1877
|
+
required: ["webhook_id"],
|
|
1878
|
+
},
|
|
1879
|
+
},
|
|
1880
|
+
// Functions
|
|
1881
|
+
{
|
|
1882
|
+
name: "function_list",
|
|
1883
|
+
description: "List all serverless functions in the tenant.",
|
|
1884
|
+
inputSchema: { type: "object", properties: {} },
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
name: "function_create",
|
|
1888
|
+
description: "Create a new serverless function.",
|
|
1889
|
+
inputSchema: {
|
|
1890
|
+
type: "object",
|
|
1891
|
+
properties: {
|
|
1892
|
+
name: { type: "string", description: "Function name" },
|
|
1893
|
+
runtime: {
|
|
1894
|
+
type: "string",
|
|
1895
|
+
description: "Runtime identifier (e.g. 'node20', 'python311', 'go122')",
|
|
1896
|
+
},
|
|
1897
|
+
code: {
|
|
1898
|
+
type: "string",
|
|
1899
|
+
description: "Inline function source code (optional)",
|
|
1900
|
+
},
|
|
1901
|
+
},
|
|
1902
|
+
required: ["name", "runtime"],
|
|
1903
|
+
},
|
|
1904
|
+
},
|
|
1905
|
+
{
|
|
1906
|
+
name: "function_invoke",
|
|
1907
|
+
description: "Invoke a serverless function and return its response.",
|
|
1908
|
+
inputSchema: {
|
|
1909
|
+
type: "object",
|
|
1910
|
+
properties: {
|
|
1911
|
+
function_id: { type: "string", description: "Function ID to invoke" },
|
|
1912
|
+
payload: {
|
|
1913
|
+
type: "object",
|
|
1914
|
+
description: "JSON payload to pass to the function (optional)",
|
|
1915
|
+
},
|
|
1916
|
+
},
|
|
1917
|
+
required: ["function_id"],
|
|
1918
|
+
},
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
name: "function_delete",
|
|
1922
|
+
description: "Delete a serverless function permanently.",
|
|
1923
|
+
inputSchema: {
|
|
1924
|
+
type: "object",
|
|
1925
|
+
properties: {
|
|
1926
|
+
function_id: { type: "string", description: "Function ID to delete" },
|
|
1927
|
+
},
|
|
1928
|
+
required: ["function_id"],
|
|
1929
|
+
},
|
|
1930
|
+
},
|
|
1931
|
+
// API Keys
|
|
1932
|
+
{
|
|
1933
|
+
name: "api_key_list",
|
|
1934
|
+
description: "List all API keys for the tenant.",
|
|
1935
|
+
inputSchema: { type: "object", properties: {} },
|
|
1936
|
+
},
|
|
1937
|
+
{
|
|
1938
|
+
name: "api_key_create",
|
|
1939
|
+
description: "Create a new API key.",
|
|
1940
|
+
inputSchema: {
|
|
1941
|
+
type: "object",
|
|
1942
|
+
properties: {
|
|
1943
|
+
name: {
|
|
1944
|
+
type: "string",
|
|
1945
|
+
description: "Human-readable label for the key",
|
|
1946
|
+
},
|
|
1947
|
+
scopes: {
|
|
1948
|
+
type: "array",
|
|
1949
|
+
items: { type: "string" },
|
|
1950
|
+
description: "Permission scopes for the key (optional; defaults to full access)",
|
|
1951
|
+
},
|
|
1952
|
+
},
|
|
1953
|
+
required: ["name"],
|
|
1954
|
+
},
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
name: "api_key_delete",
|
|
1958
|
+
description: "Revoke and delete an API key.",
|
|
1959
|
+
inputSchema: {
|
|
1960
|
+
type: "object",
|
|
1961
|
+
properties: {
|
|
1962
|
+
key_id: { type: "string", description: "API key ID to delete" },
|
|
1963
|
+
},
|
|
1964
|
+
required: ["key_id"],
|
|
1965
|
+
},
|
|
1966
|
+
},
|
|
1967
|
+
// Regions
|
|
1968
|
+
{
|
|
1969
|
+
name: "region_list",
|
|
1970
|
+
description: "List available regions and their GPU availability.",
|
|
1971
|
+
inputSchema: { type: "object", properties: {} },
|
|
1972
|
+
},
|
|
1973
|
+
{
|
|
1974
|
+
name: "computer_list_regions",
|
|
1975
|
+
description: "List available compute regions with GPU availability details.",
|
|
1976
|
+
inputSchema: { type: "object", properties: {} },
|
|
1977
|
+
},
|
|
1978
|
+
// Computer templates
|
|
1979
|
+
{
|
|
1980
|
+
name: "computer_template_list",
|
|
1981
|
+
description: "List computer templates available in a workspace.",
|
|
1982
|
+
inputSchema: {
|
|
1983
|
+
type: "object",
|
|
1984
|
+
properties: {
|
|
1985
|
+
workspace_id: {
|
|
1986
|
+
type: "string",
|
|
1987
|
+
description: "Workspace ID to list templates for",
|
|
1988
|
+
},
|
|
1989
|
+
},
|
|
1990
|
+
required: ["workspace_id"],
|
|
1991
|
+
},
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
name: "computer_template_create",
|
|
1995
|
+
description: "Create a new computer template in a workspace.",
|
|
1996
|
+
inputSchema: {
|
|
1997
|
+
type: "object",
|
|
1998
|
+
properties: {
|
|
1999
|
+
workspace_id: {
|
|
2000
|
+
type: "string",
|
|
2001
|
+
description: "Workspace ID to create the template in",
|
|
2002
|
+
},
|
|
2003
|
+
name: {
|
|
2004
|
+
type: "string",
|
|
2005
|
+
description: "Human-readable name for the template",
|
|
2006
|
+
},
|
|
2007
|
+
template_type: {
|
|
2008
|
+
type: "string",
|
|
2009
|
+
description: "Base template type (e.g. miosa-desktop)",
|
|
2010
|
+
},
|
|
2011
|
+
size: {
|
|
2012
|
+
type: "string",
|
|
2013
|
+
enum: ["small", "medium", "large", "xl"],
|
|
2014
|
+
description: "VM size for the template",
|
|
2015
|
+
},
|
|
2016
|
+
selected_apps: {
|
|
2017
|
+
type: "array",
|
|
2018
|
+
items: { type: "string" },
|
|
2019
|
+
description: "List of app identifiers to pre-install",
|
|
2020
|
+
},
|
|
2021
|
+
settings: {
|
|
2022
|
+
type: "object",
|
|
2023
|
+
description: "Additional template settings (key-value pairs)",
|
|
2024
|
+
},
|
|
2025
|
+
},
|
|
2026
|
+
required: ["workspace_id", "name"],
|
|
2027
|
+
},
|
|
2028
|
+
},
|
|
2029
|
+
// Settings
|
|
2030
|
+
{
|
|
2031
|
+
name: "settings_get",
|
|
2032
|
+
description: "Get all tenant-level settings.",
|
|
2033
|
+
inputSchema: { type: "object", properties: {} },
|
|
2034
|
+
},
|
|
2035
|
+
{
|
|
2036
|
+
name: "settings_get_branding",
|
|
2037
|
+
description: "Get tenant branding settings (logo, wallpaper, colours).",
|
|
2038
|
+
inputSchema: { type: "object", properties: {} },
|
|
2039
|
+
},
|
|
2040
|
+
{
|
|
2041
|
+
name: "settings_update_branding",
|
|
2042
|
+
description: "Update tenant branding settings.",
|
|
2043
|
+
inputSchema: {
|
|
2044
|
+
type: "object",
|
|
2045
|
+
properties: {
|
|
2046
|
+
desktop_wallpaper_url: {
|
|
2047
|
+
type: "string",
|
|
2048
|
+
description: "HTTPS URL for the default desktop wallpaper (optional)",
|
|
2049
|
+
},
|
|
2050
|
+
logo_url: {
|
|
2051
|
+
type: "string",
|
|
2052
|
+
description: "HTTPS URL for the tenant logo (optional)",
|
|
2053
|
+
},
|
|
2054
|
+
},
|
|
2055
|
+
},
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
name: "settings_compute_pricing",
|
|
2059
|
+
description: "Get compute pricing information for available sizes and GPU types.",
|
|
2060
|
+
inputSchema: { type: "object", properties: {} },
|
|
2061
|
+
},
|
|
2062
|
+
// Sandbox template extensions
|
|
2063
|
+
{
|
|
2064
|
+
name: "sandbox_template_get",
|
|
2065
|
+
description: "Get details of a specific sandbox template.",
|
|
2066
|
+
inputSchema: {
|
|
2067
|
+
type: "object",
|
|
2068
|
+
properties: {
|
|
2069
|
+
template_id: {
|
|
2070
|
+
type: "string",
|
|
2071
|
+
description: "Sandbox template ID or slug",
|
|
2072
|
+
},
|
|
2073
|
+
},
|
|
2074
|
+
required: ["template_id"],
|
|
2075
|
+
},
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
name: "sandbox_template_builds",
|
|
2079
|
+
description: "List builds for a sandbox template.",
|
|
2080
|
+
inputSchema: {
|
|
2081
|
+
type: "object",
|
|
2082
|
+
properties: {
|
|
2083
|
+
template_id: {
|
|
2084
|
+
type: "string",
|
|
2085
|
+
description: "Sandbox template ID or slug",
|
|
2086
|
+
},
|
|
2087
|
+
},
|
|
2088
|
+
required: ["template_id"],
|
|
2089
|
+
},
|
|
2090
|
+
},
|
|
2091
|
+
// Cron jobs
|
|
2092
|
+
{
|
|
2093
|
+
name: "cron_list",
|
|
2094
|
+
description: "List all cron jobs in the tenant, optionally filtered by computer.",
|
|
2095
|
+
inputSchema: {
|
|
2096
|
+
type: "object",
|
|
2097
|
+
properties: {
|
|
2098
|
+
computer_id: {
|
|
2099
|
+
type: "string",
|
|
2100
|
+
description: "Filter cron jobs by computer ID (optional)",
|
|
2101
|
+
},
|
|
2102
|
+
},
|
|
2103
|
+
},
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
name: "cron_create",
|
|
2107
|
+
description: "Create a new cron job that runs a command on a schedule.",
|
|
2108
|
+
inputSchema: {
|
|
2109
|
+
type: "object",
|
|
2110
|
+
properties: {
|
|
2111
|
+
computer_id: {
|
|
2112
|
+
type: "string",
|
|
2113
|
+
description: "ID of the computer to run the cron job on",
|
|
2114
|
+
},
|
|
2115
|
+
schedule: {
|
|
2116
|
+
type: "string",
|
|
2117
|
+
description: "Cron schedule expression (e.g. '0 * * * *' for hourly)",
|
|
2118
|
+
},
|
|
2119
|
+
command: {
|
|
2120
|
+
type: "string",
|
|
2121
|
+
description: "Shell command to execute",
|
|
2122
|
+
},
|
|
2123
|
+
name: {
|
|
2124
|
+
type: "string",
|
|
2125
|
+
description: "Human-readable name for the cron job (optional)",
|
|
2126
|
+
},
|
|
2127
|
+
},
|
|
2128
|
+
required: ["computer_id", "schedule", "command"],
|
|
2129
|
+
},
|
|
2130
|
+
},
|
|
2131
|
+
{
|
|
2132
|
+
name: "cron_get",
|
|
2133
|
+
description: "Get details of a specific cron job.",
|
|
2134
|
+
inputSchema: {
|
|
2135
|
+
type: "object",
|
|
2136
|
+
properties: {
|
|
2137
|
+
cron_id: { type: "string", description: "Cron job ID" },
|
|
2138
|
+
},
|
|
2139
|
+
required: ["cron_id"],
|
|
2140
|
+
},
|
|
2141
|
+
},
|
|
2142
|
+
{
|
|
2143
|
+
name: "cron_delete",
|
|
2144
|
+
description: "Delete a cron job permanently.",
|
|
2145
|
+
inputSchema: {
|
|
2146
|
+
type: "object",
|
|
2147
|
+
properties: {
|
|
2148
|
+
cron_id: { type: "string", description: "Cron job ID to delete" },
|
|
2149
|
+
},
|
|
2150
|
+
required: ["cron_id"],
|
|
2151
|
+
},
|
|
2152
|
+
},
|
|
2153
|
+
{
|
|
2154
|
+
name: "cron_pause",
|
|
2155
|
+
description: "Pause a cron job so it stops running on schedule.",
|
|
2156
|
+
inputSchema: {
|
|
2157
|
+
type: "object",
|
|
2158
|
+
properties: {
|
|
2159
|
+
cron_id: { type: "string", description: "Cron job ID to pause" },
|
|
2160
|
+
},
|
|
2161
|
+
required: ["cron_id"],
|
|
2162
|
+
},
|
|
2163
|
+
},
|
|
2164
|
+
{
|
|
2165
|
+
name: "cron_resume",
|
|
2166
|
+
description: "Resume a paused cron job.",
|
|
2167
|
+
inputSchema: {
|
|
2168
|
+
type: "object",
|
|
2169
|
+
properties: {
|
|
2170
|
+
cron_id: { type: "string", description: "Cron job ID to resume" },
|
|
2171
|
+
},
|
|
2172
|
+
required: ["cron_id"],
|
|
2173
|
+
},
|
|
2174
|
+
},
|
|
2175
|
+
{
|
|
2176
|
+
name: "cron_run_now",
|
|
2177
|
+
description: "Trigger an immediate one-off execution of a cron job outside its schedule.",
|
|
2178
|
+
inputSchema: {
|
|
2179
|
+
type: "object",
|
|
2180
|
+
properties: {
|
|
2181
|
+
cron_id: {
|
|
2182
|
+
type: "string",
|
|
2183
|
+
description: "Cron job ID to run immediately",
|
|
2184
|
+
},
|
|
2185
|
+
},
|
|
2186
|
+
required: ["cron_id"],
|
|
2187
|
+
},
|
|
2188
|
+
},
|
|
2189
|
+
{
|
|
2190
|
+
name: "cron_executions",
|
|
2191
|
+
description: "List recent execution history for a cron job.",
|
|
2192
|
+
inputSchema: {
|
|
2193
|
+
type: "object",
|
|
2194
|
+
properties: {
|
|
2195
|
+
cron_id: { type: "string", description: "Cron job ID" },
|
|
2196
|
+
},
|
|
2197
|
+
required: ["cron_id"],
|
|
2198
|
+
},
|
|
2199
|
+
},
|
|
2200
|
+
// Volumes
|
|
2201
|
+
{
|
|
2202
|
+
name: "volume_list",
|
|
2203
|
+
description: "List all volumes in the tenant.",
|
|
2204
|
+
inputSchema: { type: "object", properties: {} },
|
|
2205
|
+
},
|
|
2206
|
+
{
|
|
2207
|
+
name: "volume_create",
|
|
2208
|
+
description: "Create a new persistent volume.",
|
|
2209
|
+
inputSchema: {
|
|
2210
|
+
type: "object",
|
|
2211
|
+
properties: {
|
|
2212
|
+
name: {
|
|
2213
|
+
type: "string",
|
|
2214
|
+
description: "Human-readable name for the volume",
|
|
2215
|
+
},
|
|
2216
|
+
size_gb: {
|
|
2217
|
+
type: "integer",
|
|
2218
|
+
description: "Size of the volume in GB (optional)",
|
|
2219
|
+
},
|
|
2220
|
+
region: {
|
|
2221
|
+
type: "string",
|
|
2222
|
+
description: "Region to create the volume in (optional)",
|
|
2223
|
+
},
|
|
2224
|
+
},
|
|
2225
|
+
required: ["name"],
|
|
2226
|
+
},
|
|
2227
|
+
},
|
|
2228
|
+
{
|
|
2229
|
+
name: "volume_get",
|
|
2230
|
+
description: "Get details of a specific volume.",
|
|
2231
|
+
inputSchema: {
|
|
2232
|
+
type: "object",
|
|
2233
|
+
properties: {
|
|
2234
|
+
volume_id: { type: "string", description: "Volume ID" },
|
|
2235
|
+
},
|
|
2236
|
+
required: ["volume_id"],
|
|
2237
|
+
},
|
|
2238
|
+
},
|
|
2239
|
+
{
|
|
2240
|
+
name: "volume_delete",
|
|
2241
|
+
description: "Delete a volume permanently.",
|
|
2242
|
+
inputSchema: {
|
|
2243
|
+
type: "object",
|
|
2244
|
+
properties: {
|
|
2245
|
+
volume_id: { type: "string", description: "Volume ID to delete" },
|
|
2246
|
+
},
|
|
2247
|
+
required: ["volume_id"],
|
|
2248
|
+
},
|
|
2249
|
+
},
|
|
2250
|
+
{
|
|
2251
|
+
name: "volume_attach",
|
|
2252
|
+
description: "Attach a volume to a computer at a given mount path.",
|
|
2253
|
+
inputSchema: {
|
|
2254
|
+
type: "object",
|
|
2255
|
+
properties: {
|
|
2256
|
+
computer_id: {
|
|
2257
|
+
type: "string",
|
|
2258
|
+
description: "Computer ID to attach the volume to",
|
|
2259
|
+
},
|
|
2260
|
+
volume_id: { type: "string", description: "Volume ID to attach" },
|
|
2261
|
+
mount_path: {
|
|
2262
|
+
type: "string",
|
|
2263
|
+
description: "Path inside the VM to mount the volume (optional)",
|
|
2264
|
+
},
|
|
2265
|
+
},
|
|
2266
|
+
required: ["computer_id", "volume_id"],
|
|
2267
|
+
},
|
|
2268
|
+
},
|
|
2269
|
+
{
|
|
2270
|
+
name: "volume_detach",
|
|
2271
|
+
description: "Detach a volume attachment from a computer.",
|
|
2272
|
+
inputSchema: {
|
|
2273
|
+
type: "object",
|
|
2274
|
+
properties: {
|
|
2275
|
+
computer_id: { type: "string", description: "Computer ID" },
|
|
2276
|
+
attachment_id: {
|
|
2277
|
+
type: "string",
|
|
2278
|
+
description: "Attachment ID to remove",
|
|
2279
|
+
},
|
|
2280
|
+
},
|
|
2281
|
+
required: ["computer_id", "attachment_id"],
|
|
2282
|
+
},
|
|
2283
|
+
},
|
|
2284
|
+
];
|
|
2285
|
+
// ── Wire helpers ─────────────────────────────────────────────────────────────
|
|
2286
|
+
function ok(text) {
|
|
2287
|
+
return { content: [{ type: "text", text }] };
|
|
2288
|
+
}
|
|
2289
|
+
function err(msg) {
|
|
2290
|
+
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
2291
|
+
}
|
|
2292
|
+
function image(pngBytes) {
|
|
2293
|
+
return {
|
|
2294
|
+
content: [
|
|
2295
|
+
{
|
|
2296
|
+
type: "image",
|
|
2297
|
+
data: pngBytes.toString("base64"),
|
|
2298
|
+
mimeType: "image/png",
|
|
2299
|
+
},
|
|
2300
|
+
],
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
function send(response) {
|
|
2304
|
+
process.stdout.write(JSON.stringify(response) + "\n");
|
|
2305
|
+
}
|
|
2306
|
+
// ── Tool dispatch ────────────────────────────────────────────────────────────
|
|
2307
|
+
async function dispatchTool(client, name, args) {
|
|
2308
|
+
const cid = typeof args["computer_id"] === "string" ? args["computer_id"] : undefined;
|
|
2309
|
+
try {
|
|
2310
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
2311
|
+
if (name === "computer_create") {
|
|
2312
|
+
const body = { name: args["name"] };
|
|
2313
|
+
if (args["template_type"])
|
|
2314
|
+
body["template_type"] = args["template_type"];
|
|
2315
|
+
if (args["size"])
|
|
2316
|
+
body["size"] = args["size"];
|
|
2317
|
+
if (args["workspace_id"])
|
|
2318
|
+
body["workspace_id"] = args["workspace_id"];
|
|
2319
|
+
if (args["external_workspace_id"])
|
|
2320
|
+
body["external_workspace_id"] = args["external_workspace_id"];
|
|
2321
|
+
if (args["external_project_id"])
|
|
2322
|
+
body["external_project_id"] = args["external_project_id"];
|
|
2323
|
+
if (args["gpu_model"]) {
|
|
2324
|
+
body["gpu_model"] = args["gpu_model"];
|
|
2325
|
+
body["gpu_count"] = args["gpu_count"] ?? 1;
|
|
2326
|
+
}
|
|
2327
|
+
const computer = await client.apiPost("/api/v1/computers", body);
|
|
2328
|
+
const data = unwrapData(computer);
|
|
2329
|
+
const id = String(data["id"] ?? "");
|
|
2330
|
+
const compName = String(data["name"] ?? args["name"]);
|
|
2331
|
+
// Poll until the computer reaches "active" state (mirrors Python MCP behaviour)
|
|
2332
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
2333
|
+
const POLL_TIMEOUT_MS = 30_000;
|
|
2334
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
2335
|
+
let finalStatus = String(data["status"] ?? data["state"] ?? "created");
|
|
2336
|
+
while (finalStatus !== "active" && Date.now() < deadline) {
|
|
2337
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
2338
|
+
try {
|
|
2339
|
+
const poll = await client.apiGet(`/api/v1/computers/${encodeURIComponent(id)}`);
|
|
2340
|
+
const pollData = unwrapData(poll);
|
|
2341
|
+
finalStatus = String(pollData["status"] ?? pollData["state"] ?? finalStatus);
|
|
2342
|
+
}
|
|
2343
|
+
catch {
|
|
2344
|
+
// Transient error during poll — keep waiting
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return ok(`Created computer '${compName}' (id=${id}, status=${finalStatus}).`);
|
|
2348
|
+
}
|
|
2349
|
+
if (name === "computer_list") {
|
|
2350
|
+
const result = await client.apiGet("/api/v1/computers");
|
|
2351
|
+
const items = listOf(result);
|
|
2352
|
+
if (items.length === 0)
|
|
2353
|
+
return ok("No computers found.");
|
|
2354
|
+
const lines = ["Available computers:"];
|
|
2355
|
+
for (const c of items) {
|
|
2356
|
+
const r = c;
|
|
2357
|
+
lines.push(` ${r["id"]} ${r["name"]} ${r["status"] ?? r["state"]}`);
|
|
2358
|
+
}
|
|
2359
|
+
return ok(lines.join("\n"));
|
|
2360
|
+
}
|
|
2361
|
+
if (name === "computer_destroy") {
|
|
2362
|
+
if (!cid)
|
|
2363
|
+
return err("computer_id is required");
|
|
2364
|
+
await client.apiDelete(`/api/v1/computers/${encodeURIComponent(cid)}`);
|
|
2365
|
+
return ok(`Computer ${cid} destroyed.`);
|
|
2366
|
+
}
|
|
2367
|
+
if (name === "computer_get") {
|
|
2368
|
+
if (!cid)
|
|
2369
|
+
return err("computer_id is required");
|
|
2370
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}`);
|
|
2371
|
+
const data = unwrapData(result);
|
|
2372
|
+
return ok(`id=${data["id"]} name=${JSON.stringify(data["name"])} status=${data["status"] ?? data["state"]}`);
|
|
2373
|
+
}
|
|
2374
|
+
if (name === "computer_start") {
|
|
2375
|
+
if (!cid)
|
|
2376
|
+
return err("computer_id is required");
|
|
2377
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/start`, {});
|
|
2378
|
+
return ok(`Computer ${cid} start issued.`);
|
|
2379
|
+
}
|
|
2380
|
+
if (name === "computer_stop") {
|
|
2381
|
+
if (!cid)
|
|
2382
|
+
return err("computer_id is required");
|
|
2383
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/stop`, {});
|
|
2384
|
+
return ok(`Computer ${cid} stop issued.`);
|
|
2385
|
+
}
|
|
2386
|
+
if (name === "computer_restart") {
|
|
2387
|
+
if (!cid)
|
|
2388
|
+
return err("computer_id is required");
|
|
2389
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/restart`, {});
|
|
2390
|
+
return ok(`Computer ${cid} restart issued.`);
|
|
2391
|
+
}
|
|
2392
|
+
if (name === "computer_update") {
|
|
2393
|
+
if (!cid)
|
|
2394
|
+
return err("computer_id is required");
|
|
2395
|
+
const body = {};
|
|
2396
|
+
if (typeof args["name"] === "string")
|
|
2397
|
+
body["name"] = args["name"];
|
|
2398
|
+
if (args["metadata"] !== undefined)
|
|
2399
|
+
body["metadata"] = args["metadata"];
|
|
2400
|
+
const result = await client.apiPatch(`/api/v1/computers/${encodeURIComponent(cid)}`, body);
|
|
2401
|
+
const data = unwrapData(result);
|
|
2402
|
+
return ok(`Computer ${cid} updated: name=${JSON.stringify(data["name"] ?? args["name"])}.`);
|
|
2403
|
+
}
|
|
2404
|
+
// ── Screenshot ────────────────────────────────────────────────────────
|
|
2405
|
+
if (name === "computer_screenshot") {
|
|
2406
|
+
if (!cid)
|
|
2407
|
+
return err("computer_id is required");
|
|
2408
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/screenshot`);
|
|
2409
|
+
// The API returns { data: { image: "<base64>", format: "png" } }
|
|
2410
|
+
const data = unwrapData(result);
|
|
2411
|
+
const b64 = typeof data["image"] === "string" ? data["image"] : null;
|
|
2412
|
+
if (!b64)
|
|
2413
|
+
return err("Screenshot API returned no image data");
|
|
2414
|
+
return image(Buffer.from(b64, "base64"));
|
|
2415
|
+
}
|
|
2416
|
+
// ── Pointer ───────────────────────────────────────────────────────────
|
|
2417
|
+
if (name === "computer_click") {
|
|
2418
|
+
if (!cid)
|
|
2419
|
+
return err("computer_id is required");
|
|
2420
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/click`, {
|
|
2421
|
+
x: args["x"],
|
|
2422
|
+
y: args["y"],
|
|
2423
|
+
button: args["button"] ?? "left",
|
|
2424
|
+
});
|
|
2425
|
+
return ok(`Clicked (${args["x"]}, ${args["y"]}) button=${args["button"] ?? "left"}`);
|
|
2426
|
+
}
|
|
2427
|
+
if (name === "computer_double_click") {
|
|
2428
|
+
if (!cid)
|
|
2429
|
+
return err("computer_id is required");
|
|
2430
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/double-click`, {
|
|
2431
|
+
x: args["x"],
|
|
2432
|
+
y: args["y"],
|
|
2433
|
+
});
|
|
2434
|
+
return ok(`Double-clicked (${args["x"]}, ${args["y"]})`);
|
|
2435
|
+
}
|
|
2436
|
+
if (name === "computer_move_cursor") {
|
|
2437
|
+
if (!cid)
|
|
2438
|
+
return err("computer_id is required");
|
|
2439
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/move`, {
|
|
2440
|
+
x: args["x"],
|
|
2441
|
+
y: args["y"],
|
|
2442
|
+
});
|
|
2443
|
+
return ok(`Moved cursor to (${args["x"]}, ${args["y"]})`);
|
|
2444
|
+
}
|
|
2445
|
+
if (name === "computer_drag") {
|
|
2446
|
+
if (!cid)
|
|
2447
|
+
return err("computer_id is required");
|
|
2448
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/drag`, {
|
|
2449
|
+
from_x: args["from_x"],
|
|
2450
|
+
from_y: args["from_y"],
|
|
2451
|
+
to_x: args["to_x"],
|
|
2452
|
+
to_y: args["to_y"],
|
|
2453
|
+
});
|
|
2454
|
+
return ok(`Dragged from (${args["from_x"]}, ${args["from_y"]}) to (${args["to_x"]}, ${args["to_y"]})`);
|
|
2455
|
+
}
|
|
2456
|
+
if (name === "computer_scroll") {
|
|
2457
|
+
if (!cid)
|
|
2458
|
+
return err("computer_id is required");
|
|
2459
|
+
const body = {
|
|
2460
|
+
direction: args["direction"] ?? "down",
|
|
2461
|
+
clicks: args["clicks"] ?? 3,
|
|
2462
|
+
};
|
|
2463
|
+
if (args["x"] !== undefined)
|
|
2464
|
+
body["x"] = args["x"];
|
|
2465
|
+
if (args["y"] !== undefined)
|
|
2466
|
+
body["y"] = args["y"];
|
|
2467
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/scroll`, body);
|
|
2468
|
+
return ok(`Scrolled ${body["direction"]} by ${body["clicks"]} clicks`);
|
|
2469
|
+
}
|
|
2470
|
+
// ── Keyboard ──────────────────────────────────────────────────────────
|
|
2471
|
+
if (name === "computer_type") {
|
|
2472
|
+
if (!cid)
|
|
2473
|
+
return err("computer_id is required");
|
|
2474
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/type`, {
|
|
2475
|
+
text: args["text"],
|
|
2476
|
+
});
|
|
2477
|
+
const preview = typeof args["text"] === "string"
|
|
2478
|
+
? args["text"].slice(0, 40) + (args["text"].length > 40 ? "..." : "")
|
|
2479
|
+
: "";
|
|
2480
|
+
return ok(`Typed: ${JSON.stringify(preview)}`);
|
|
2481
|
+
}
|
|
2482
|
+
if (name === "computer_key") {
|
|
2483
|
+
if (!cid)
|
|
2484
|
+
return err("computer_id is required");
|
|
2485
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/key`, {
|
|
2486
|
+
key: args["key"],
|
|
2487
|
+
});
|
|
2488
|
+
return ok(`Pressed key: ${args["key"]}`);
|
|
2489
|
+
}
|
|
2490
|
+
if (name === "computer_hotkey") {
|
|
2491
|
+
if (!cid)
|
|
2492
|
+
return err("computer_id is required");
|
|
2493
|
+
const keys = args["keys"];
|
|
2494
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/hotkey`, {
|
|
2495
|
+
keys,
|
|
2496
|
+
});
|
|
2497
|
+
return ok(`Pressed hotkey: ${keys.join("+")}`);
|
|
2498
|
+
}
|
|
2499
|
+
// ── Display info ──────────────────────────────────────────────────────
|
|
2500
|
+
if (name === "computer_get_screen_size") {
|
|
2501
|
+
if (!cid)
|
|
2502
|
+
return err("computer_id is required");
|
|
2503
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/screen-size`);
|
|
2504
|
+
const data = unwrapData(result);
|
|
2505
|
+
return ok(`Screen size: ${data["width"]}x${data["height"]} px`);
|
|
2506
|
+
}
|
|
2507
|
+
if (name === "computer_get_cursor_position") {
|
|
2508
|
+
if (!cid)
|
|
2509
|
+
return err("computer_id is required");
|
|
2510
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/cursor`);
|
|
2511
|
+
const data = unwrapData(result);
|
|
2512
|
+
return ok(`Cursor position: x=${data["x"]}, y=${data["y"]}`);
|
|
2513
|
+
}
|
|
2514
|
+
// ── Clipboard ─────────────────────────────────────────────────────────
|
|
2515
|
+
if (name === "computer_get_clipboard") {
|
|
2516
|
+
if (!cid)
|
|
2517
|
+
return err("computer_id is required");
|
|
2518
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/clipboard`);
|
|
2519
|
+
const data = unwrapData(result);
|
|
2520
|
+
return ok(`Clipboard content:\n${data["text"] ?? ""}`);
|
|
2521
|
+
}
|
|
2522
|
+
if (name === "computer_set_clipboard") {
|
|
2523
|
+
if (!cid)
|
|
2524
|
+
return err("computer_id is required");
|
|
2525
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/clipboard`, {
|
|
2526
|
+
text: args["text"],
|
|
2527
|
+
});
|
|
2528
|
+
return ok("Clipboard updated.");
|
|
2529
|
+
}
|
|
2530
|
+
// ── Window management ─────────────────────────────────────────────────
|
|
2531
|
+
if (name === "computer_windows") {
|
|
2532
|
+
if (!cid)
|
|
2533
|
+
return err("computer_id is required");
|
|
2534
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/windows`);
|
|
2535
|
+
const windows = listOf(result);
|
|
2536
|
+
if (windows.length === 0)
|
|
2537
|
+
return ok("No open windows found.");
|
|
2538
|
+
const lines = ["Open windows:"];
|
|
2539
|
+
for (const w of windows) {
|
|
2540
|
+
const r = w;
|
|
2541
|
+
const focused = r["focused"] ? " [focused]" : "";
|
|
2542
|
+
lines.push(` id=${r["id"]} title=${JSON.stringify(r["title"])} app=${JSON.stringify(r["app"])}` +
|
|
2543
|
+
` pos=(${r["x"]},${r["y"]}) size=${r["width"]}x${r["height"]}${focused}`);
|
|
2544
|
+
}
|
|
2545
|
+
return ok(lines.join("\n"));
|
|
2546
|
+
}
|
|
2547
|
+
if (name === "computer_launch") {
|
|
2548
|
+
if (!cid)
|
|
2549
|
+
return err("computer_id is required");
|
|
2550
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/launch`, {
|
|
2551
|
+
app: args["app"],
|
|
2552
|
+
});
|
|
2553
|
+
return ok(`Launched: ${args["app"]}`);
|
|
2554
|
+
}
|
|
2555
|
+
// ── Shell & Files ─────────────────────────────────────────────────────
|
|
2556
|
+
if (name === "computer_bash") {
|
|
2557
|
+
if (!cid)
|
|
2558
|
+
return err("computer_id is required");
|
|
2559
|
+
const body = { command: args["command"] };
|
|
2560
|
+
if (args["timeout"] !== undefined)
|
|
2561
|
+
body["timeout"] = args["timeout"];
|
|
2562
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/exec`, body);
|
|
2563
|
+
const data = unwrapData(result);
|
|
2564
|
+
const parts = [];
|
|
2565
|
+
if (data["output"] ?? data["stdout"]) {
|
|
2566
|
+
parts.push(`stdout:\n${data["output"] ?? data["stdout"]}`);
|
|
2567
|
+
}
|
|
2568
|
+
if (data["stderr"])
|
|
2569
|
+
parts.push(`stderr:\n${data["stderr"]}`);
|
|
2570
|
+
parts.push(`exit_code: ${data["exit_code"] ?? 0}`);
|
|
2571
|
+
return ok(parts.join("\n"));
|
|
2572
|
+
}
|
|
2573
|
+
if (name === "computer_write_file") {
|
|
2574
|
+
if (!cid)
|
|
2575
|
+
return err("computer_id is required");
|
|
2576
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/files/write`, {
|
|
2577
|
+
path: args["path"],
|
|
2578
|
+
content: args["content"],
|
|
2579
|
+
encoding: "utf8",
|
|
2580
|
+
});
|
|
2581
|
+
const len = typeof args["content"] === "string" ? args["content"].length : 0;
|
|
2582
|
+
return ok(`Wrote ${len} bytes to ${args["path"]}`);
|
|
2583
|
+
}
|
|
2584
|
+
if (name === "computer_read_file") {
|
|
2585
|
+
if (!cid)
|
|
2586
|
+
return err("computer_id is required");
|
|
2587
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/files/download?path=${encodeURIComponent(String(args["path"] ?? ""))}`);
|
|
2588
|
+
const data = unwrapData(result);
|
|
2589
|
+
const content = typeof data["content"] === "string"
|
|
2590
|
+
? Buffer.from(data["content"], "base64").toString("utf8")
|
|
2591
|
+
: typeof data["text"] === "string"
|
|
2592
|
+
? data["text"]
|
|
2593
|
+
: JSON.stringify(data);
|
|
2594
|
+
return ok(content);
|
|
2595
|
+
}
|
|
2596
|
+
// ── Extended pointer ──────────────────────────────────────────────────
|
|
2597
|
+
if (name === "computer_right_click") {
|
|
2598
|
+
if (!cid)
|
|
2599
|
+
return err("computer_id is required");
|
|
2600
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/click`, { x: args["x"], y: args["y"], button: "right" });
|
|
2601
|
+
return ok(`Right-clicked (${args["x"]}, ${args["y"]})`);
|
|
2602
|
+
}
|
|
2603
|
+
if (name === "computer_mouse_down") {
|
|
2604
|
+
if (!cid)
|
|
2605
|
+
return err("computer_id is required");
|
|
2606
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/mouse-down`, {
|
|
2607
|
+
x: args["x"],
|
|
2608
|
+
y: args["y"],
|
|
2609
|
+
button: args["button"] ?? "left",
|
|
2610
|
+
});
|
|
2611
|
+
return ok(`Mouse down at (${args["x"]}, ${args["y"]}) button=${args["button"] ?? "left"}`);
|
|
2612
|
+
}
|
|
2613
|
+
if (name === "computer_mouse_up") {
|
|
2614
|
+
if (!cid)
|
|
2615
|
+
return err("computer_id is required");
|
|
2616
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/mouse-up`, {
|
|
2617
|
+
x: args["x"],
|
|
2618
|
+
y: args["y"],
|
|
499
2619
|
button: args["button"] ?? "left",
|
|
500
2620
|
});
|
|
501
|
-
return ok(`
|
|
2621
|
+
return ok(`Mouse up at (${args["x"]}, ${args["y"]}) button=${args["button"] ?? "left"}`);
|
|
2622
|
+
}
|
|
2623
|
+
// ── Extended keyboard ─────────────────────────────────────────────────
|
|
2624
|
+
if (name === "computer_key_down") {
|
|
2625
|
+
if (!cid)
|
|
2626
|
+
return err("computer_id is required");
|
|
2627
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/key-down`, { key: args["key"] });
|
|
2628
|
+
return ok(`Key down: ${args["key"]}`);
|
|
2629
|
+
}
|
|
2630
|
+
if (name === "computer_key_up") {
|
|
2631
|
+
if (!cid)
|
|
2632
|
+
return err("computer_id is required");
|
|
2633
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/key-up`, { key: args["key"] });
|
|
2634
|
+
return ok(`Key up: ${args["key"]}`);
|
|
2635
|
+
}
|
|
2636
|
+
// ── Wait ──────────────────────────────────────────────────────────────
|
|
2637
|
+
if (name === "computer_wait") {
|
|
2638
|
+
if (!cid)
|
|
2639
|
+
return err("computer_id is required");
|
|
2640
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/wait`, { seconds: args["seconds"] });
|
|
2641
|
+
return ok(`Waited ${args["seconds"]}s`);
|
|
2642
|
+
}
|
|
2643
|
+
// ── Window management (extended) ──────────────────────────────────────
|
|
2644
|
+
if (name === "computer_focus_window") {
|
|
2645
|
+
if (!cid)
|
|
2646
|
+
return err("computer_id is required");
|
|
2647
|
+
const wid = String(args["window_id"] ?? "");
|
|
2648
|
+
if (!wid)
|
|
2649
|
+
return err("window_id is required");
|
|
2650
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/window/focus`, { window_id: wid });
|
|
2651
|
+
return ok(`Focused window ${wid}`);
|
|
2652
|
+
}
|
|
2653
|
+
if (name === "computer_set_window_size") {
|
|
2654
|
+
if (!cid)
|
|
2655
|
+
return err("computer_id is required");
|
|
2656
|
+
const wid = String(args["window_id"] ?? "");
|
|
2657
|
+
if (!wid)
|
|
2658
|
+
return err("window_id is required");
|
|
2659
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/window/${encodeURIComponent(wid)}/resize`, { width: args["width"], height: args["height"] });
|
|
2660
|
+
return ok(`Resized window ${wid} to ${args["width"]}x${args["height"]}`);
|
|
2661
|
+
}
|
|
2662
|
+
if (name === "computer_set_window_position") {
|
|
2663
|
+
if (!cid)
|
|
2664
|
+
return err("computer_id is required");
|
|
2665
|
+
const wid = String(args["window_id"] ?? "");
|
|
2666
|
+
if (!wid)
|
|
2667
|
+
return err("window_id is required");
|
|
2668
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/window/${encodeURIComponent(wid)}/move`, { x: args["x"], y: args["y"] });
|
|
2669
|
+
return ok(`Moved window ${wid} to (${args["x"]}, ${args["y"]})`);
|
|
2670
|
+
}
|
|
2671
|
+
if (name === "computer_maximize_window") {
|
|
2672
|
+
if (!cid)
|
|
2673
|
+
return err("computer_id is required");
|
|
2674
|
+
const wid = String(args["window_id"] ?? "");
|
|
2675
|
+
if (!wid)
|
|
2676
|
+
return err("window_id is required");
|
|
2677
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/window/${encodeURIComponent(wid)}/maximize`, {});
|
|
2678
|
+
return ok(`Maximized window ${wid}`);
|
|
2679
|
+
}
|
|
2680
|
+
if (name === "computer_minimize_window") {
|
|
2681
|
+
if (!cid)
|
|
2682
|
+
return err("computer_id is required");
|
|
2683
|
+
const wid = String(args["window_id"] ?? "");
|
|
2684
|
+
if (!wid)
|
|
2685
|
+
return err("window_id is required");
|
|
2686
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/window/${encodeURIComponent(wid)}/minimize`, {});
|
|
2687
|
+
return ok(`Minimized window ${wid}`);
|
|
2688
|
+
}
|
|
2689
|
+
if (name === "computer_close_window") {
|
|
2690
|
+
if (!cid)
|
|
2691
|
+
return err("computer_id is required");
|
|
2692
|
+
const wid = String(args["window_id"] ?? "");
|
|
2693
|
+
if (!wid)
|
|
2694
|
+
return err("window_id is required");
|
|
2695
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/window/${encodeURIComponent(wid)}/close`, {});
|
|
2696
|
+
return ok(`Closed window ${wid}`);
|
|
2697
|
+
}
|
|
2698
|
+
// ── Desktop environment ───────────────────────────────────────────────
|
|
2699
|
+
if (name === "computer_get_desktop_env") {
|
|
2700
|
+
if (!cid)
|
|
2701
|
+
return err("computer_id is required");
|
|
2702
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/environment`);
|
|
2703
|
+
const data = unwrapData(result);
|
|
2704
|
+
return ok(`desktop_env: name=${JSON.stringify(data["name"])} resolution=${data["resolution"]} session_type=${JSON.stringify(data["session_type"])}`);
|
|
2705
|
+
}
|
|
2706
|
+
if (name === "computer_set_wallpaper") {
|
|
2707
|
+
if (!cid)
|
|
2708
|
+
return err("computer_id is required");
|
|
2709
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/wallpaper`, { path: args["path"] });
|
|
2710
|
+
return ok(`Wallpaper set to: ${args["path"]}`);
|
|
2711
|
+
}
|
|
2712
|
+
if (name === "computer_accessibility_tree") {
|
|
2713
|
+
if (!cid)
|
|
2714
|
+
return err("computer_id is required");
|
|
2715
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/desktop/accessibility-tree`);
|
|
2716
|
+
const tree = unwrapData(result);
|
|
2717
|
+
const json = JSON.stringify(tree, null, 2);
|
|
2718
|
+
return ok(json.length > 8000 ? json.slice(0, 8000) + "\n…(truncated)" : json);
|
|
2719
|
+
}
|
|
2720
|
+
// ── Files — extended ──────────────────────────────────────────────────
|
|
2721
|
+
if (name === "computer_list_files") {
|
|
2722
|
+
if (!cid)
|
|
2723
|
+
return err("computer_id is required");
|
|
2724
|
+
const path = typeof args["path"] === "string" ? args["path"] : undefined;
|
|
2725
|
+
const qs = path ? `?path=${encodeURIComponent(path)}` : "";
|
|
2726
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/files${qs}`);
|
|
2727
|
+
const items = listOf(result);
|
|
2728
|
+
if (items.length === 0)
|
|
2729
|
+
return ok(`No files found at ${path ?? "/"}.`);
|
|
2730
|
+
const lines = [`Files at ${path ?? "/"}:`];
|
|
2731
|
+
for (const e of items) {
|
|
2732
|
+
const r = e;
|
|
2733
|
+
lines.push(` ${r["name"]} ${r["type"] ?? r["kind"] ?? ""} ${r["size"] ?? ""}`);
|
|
2734
|
+
}
|
|
2735
|
+
return ok(lines.join("\n"));
|
|
2736
|
+
}
|
|
2737
|
+
if (name === "computer_stat_file") {
|
|
2738
|
+
if (!cid)
|
|
2739
|
+
return err("computer_id is required");
|
|
2740
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/files/stat`, { path: args["path"] });
|
|
2741
|
+
const data = unwrapData(result);
|
|
2742
|
+
const lines = [
|
|
2743
|
+
`path: ${args["path"]}`,
|
|
2744
|
+
`type: ${data["type"] ?? data["kind"] ?? ""}`,
|
|
2745
|
+
`size: ${data["size"] ?? ""}`,
|
|
2746
|
+
`mode: ${data["mode"] ?? ""}`,
|
|
2747
|
+
`mtime: ${data["mtime"] ?? data["modified_at"] ?? ""}`,
|
|
2748
|
+
];
|
|
2749
|
+
return ok(lines.join("\n"));
|
|
2750
|
+
}
|
|
2751
|
+
if (name === "computer_mkdir") {
|
|
2752
|
+
if (!cid)
|
|
2753
|
+
return err("computer_id is required");
|
|
2754
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/files/mkdir`, {
|
|
2755
|
+
path: args["path"],
|
|
2756
|
+
recursive: args["recursive"] !== false,
|
|
2757
|
+
mode: "0755",
|
|
2758
|
+
});
|
|
2759
|
+
return ok(`Created directory ${args["path"]}`);
|
|
2760
|
+
}
|
|
2761
|
+
if (name === "computer_rename_file") {
|
|
2762
|
+
if (!cid)
|
|
2763
|
+
return err("computer_id is required");
|
|
2764
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/files/rename`, { from: args["source"], to: args["dest"] });
|
|
2765
|
+
return ok(`Renamed ${args["source"]} -> ${args["dest"]}`);
|
|
2766
|
+
}
|
|
2767
|
+
if (name === "computer_copy_file") {
|
|
2768
|
+
if (!cid)
|
|
2769
|
+
return err("computer_id is required");
|
|
2770
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/files/copy`, {
|
|
2771
|
+
from: args["source"],
|
|
2772
|
+
to: args["dest"],
|
|
2773
|
+
recursive: args["recursive"] === true,
|
|
2774
|
+
});
|
|
2775
|
+
return ok(`Copied ${args["source"]} -> ${args["dest"]}`);
|
|
2776
|
+
}
|
|
2777
|
+
if (name === "computer_delete_file") {
|
|
2778
|
+
if (!cid)
|
|
2779
|
+
return err("computer_id is required");
|
|
2780
|
+
await client.apiDelete(`/api/v1/computers/${encodeURIComponent(cid)}/files?path=${encodeURIComponent(String(args["path"] ?? ""))}`);
|
|
2781
|
+
return ok(`Deleted ${args["path"]}`);
|
|
2782
|
+
}
|
|
2783
|
+
if (name === "computer_upload_file") {
|
|
2784
|
+
if (!cid)
|
|
2785
|
+
return err("computer_id is required");
|
|
2786
|
+
// Read the local file and POST it as base64 via the write endpoint
|
|
2787
|
+
const fs = await import("node:fs/promises");
|
|
2788
|
+
const localBytes = await fs.readFile(String(args["local_path"]));
|
|
2789
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/files/write`, {
|
|
2790
|
+
path: args["remote_path"],
|
|
2791
|
+
content: localBytes.toString("base64"),
|
|
2792
|
+
encoding: "base64",
|
|
2793
|
+
});
|
|
2794
|
+
return ok(`Uploaded ${args["local_path"]} -> ${args["remote_path"]}`);
|
|
2795
|
+
}
|
|
2796
|
+
// ── Checkpoints ───────────────────────────────────────────────────────
|
|
2797
|
+
if (name === "computer_checkpoint_create") {
|
|
2798
|
+
if (!cid)
|
|
2799
|
+
return err("computer_id is required");
|
|
2800
|
+
const body = {};
|
|
2801
|
+
if (args["comment"])
|
|
2802
|
+
body["comment"] = args["comment"];
|
|
2803
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/snapshots`, body);
|
|
2804
|
+
const data = unwrapData(result);
|
|
2805
|
+
const snap_id = String(data["id"] ?? "");
|
|
2806
|
+
const snap_status = String(data["status"] ?? "");
|
|
2807
|
+
const snap_comment = data["comment"]
|
|
2808
|
+
? ` comment=${JSON.stringify(data["comment"])}`
|
|
2809
|
+
: "";
|
|
2810
|
+
return ok(`Checkpoint created: id=${snap_id} status=${snap_status}${snap_comment}`);
|
|
2811
|
+
}
|
|
2812
|
+
if (name === "computer_checkpoint_list") {
|
|
2813
|
+
if (!cid)
|
|
2814
|
+
return err("computer_id is required");
|
|
2815
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/snapshots`);
|
|
2816
|
+
const items = listOf(result);
|
|
2817
|
+
if (items.length === 0)
|
|
2818
|
+
return ok("No checkpoints found.");
|
|
2819
|
+
const lines = ["Checkpoints:"];
|
|
2820
|
+
for (const s of items) {
|
|
2821
|
+
const r = s;
|
|
2822
|
+
const c = r["comment"] ? ` ${JSON.stringify(r["comment"])}` : "";
|
|
2823
|
+
lines.push(` ${r["id"]} ${r["status"]} ${r["created_at"] ?? ""}${c}`);
|
|
2824
|
+
}
|
|
2825
|
+
return ok(lines.join("\n"));
|
|
2826
|
+
}
|
|
2827
|
+
if (name === "computer_checkpoint_restore") {
|
|
2828
|
+
if (!cid)
|
|
2829
|
+
return err("computer_id is required");
|
|
2830
|
+
const snapId = String(args["checkpoint_id"] ?? "");
|
|
2831
|
+
if (!snapId)
|
|
2832
|
+
return err("checkpoint_id is required");
|
|
2833
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/restore/${encodeURIComponent(snapId)}`, {});
|
|
2834
|
+
const raw = unwrapData(result);
|
|
2835
|
+
const compData = raw["computer"] ?? raw;
|
|
2836
|
+
const newId = String(compData["id"] ?? "");
|
|
2837
|
+
const newStatus = String(compData["status"] ?? compData["state"] ?? "");
|
|
2838
|
+
return ok(`Restored checkpoint ${snapId} -> new computer id=${newId} status=${newStatus}.`);
|
|
2839
|
+
}
|
|
2840
|
+
if (name === "computer_checkpoint_delete") {
|
|
2841
|
+
if (!cid)
|
|
2842
|
+
return err("computer_id is required");
|
|
2843
|
+
const snapId = String(args["checkpoint_id"] ?? "");
|
|
2844
|
+
if (!snapId)
|
|
2845
|
+
return err("checkpoint_id is required");
|
|
2846
|
+
const result = await client.apiDelete(`/api/v1/computers/${encodeURIComponent(cid)}/snapshots/${encodeURIComponent(snapId)}`);
|
|
2847
|
+
const data = unwrapData(result);
|
|
2848
|
+
return ok(`Checkpoint ${data["id"] ?? snapId} deleted (status=${data["status"] ?? "deleted"}).`);
|
|
2849
|
+
}
|
|
2850
|
+
// ── Services ──────────────────────────────────────────────────────────
|
|
2851
|
+
if (name === "computer_service_create") {
|
|
2852
|
+
if (!cid)
|
|
2853
|
+
return err("computer_id is required");
|
|
2854
|
+
const body = {
|
|
2855
|
+
name: args["name"],
|
|
2856
|
+
command: args["command"],
|
|
2857
|
+
};
|
|
2858
|
+
if (args["working_dir"])
|
|
2859
|
+
body["working_dir"] = args["working_dir"];
|
|
2860
|
+
if (args["port"] !== undefined)
|
|
2861
|
+
body["port"] = args["port"];
|
|
2862
|
+
if (args["restart_policy"])
|
|
2863
|
+
body["restart_policy"] = args["restart_policy"];
|
|
2864
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/services`, body);
|
|
2865
|
+
const data = unwrapData(result);
|
|
2866
|
+
return ok(`Service created: id=${data["id"]} name=${JSON.stringify(data["name"])} status=${data["status"] ?? data["state"] ?? ""}`);
|
|
2867
|
+
}
|
|
2868
|
+
if (name === "computer_service_list") {
|
|
2869
|
+
if (!cid)
|
|
2870
|
+
return err("computer_id is required");
|
|
2871
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/services`);
|
|
2872
|
+
const items = listOf(result);
|
|
2873
|
+
if (items.length === 0)
|
|
2874
|
+
return ok("No services found.");
|
|
2875
|
+
const lines = ["Services:"];
|
|
2876
|
+
for (const s of items) {
|
|
2877
|
+
const r = s;
|
|
2878
|
+
lines.push(` ${r["id"]} ${JSON.stringify(r["name"])} ${r["status"] ?? r["state"] ?? ""}`);
|
|
2879
|
+
}
|
|
2880
|
+
return ok(lines.join("\n"));
|
|
2881
|
+
}
|
|
2882
|
+
if (name === "computer_service_start") {
|
|
2883
|
+
if (!cid)
|
|
2884
|
+
return err("computer_id is required");
|
|
2885
|
+
const sid = String(args["service_id"] ?? "");
|
|
2886
|
+
if (!sid)
|
|
2887
|
+
return err("service_id is required");
|
|
2888
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/services/${encodeURIComponent(sid)}/start`, {});
|
|
2889
|
+
const data = unwrapData(result);
|
|
2890
|
+
return ok(`Service ${data["id"] ?? sid} started (status=${data["status"] ?? ""}).`);
|
|
2891
|
+
}
|
|
2892
|
+
if (name === "computer_service_stop") {
|
|
2893
|
+
if (!cid)
|
|
2894
|
+
return err("computer_id is required");
|
|
2895
|
+
const sid = String(args["service_id"] ?? "");
|
|
2896
|
+
if (!sid)
|
|
2897
|
+
return err("service_id is required");
|
|
2898
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/services/${encodeURIComponent(sid)}/stop`, {});
|
|
2899
|
+
const data = unwrapData(result);
|
|
2900
|
+
return ok(`Service ${data["id"] ?? sid} stopped (status=${data["status"] ?? ""}).`);
|
|
2901
|
+
}
|
|
2902
|
+
if (name === "computer_service_restart") {
|
|
2903
|
+
if (!cid)
|
|
2904
|
+
return err("computer_id is required");
|
|
2905
|
+
const sid = String(args["service_id"] ?? "");
|
|
2906
|
+
if (!sid)
|
|
2907
|
+
return err("service_id is required");
|
|
2908
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/services/${encodeURIComponent(sid)}/restart`, {});
|
|
2909
|
+
const data = unwrapData(result);
|
|
2910
|
+
return ok(`Service ${data["id"] ?? sid} restarted (status=${data["status"] ?? ""}).`);
|
|
2911
|
+
}
|
|
2912
|
+
if (name === "computer_service_logs") {
|
|
2913
|
+
if (!cid)
|
|
2914
|
+
return err("computer_id is required");
|
|
2915
|
+
const sid = String(args["service_id"] ?? "");
|
|
2916
|
+
if (!sid)
|
|
2917
|
+
return err("service_id is required");
|
|
2918
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/services/${encodeURIComponent(sid)}/logs?follow=false`);
|
|
2919
|
+
const data = unwrapData(result);
|
|
2920
|
+
const logLines = data["lines"] ?? data["logs"] ?? data["data"];
|
|
2921
|
+
if (Array.isArray(logLines) && logLines.length > 0) {
|
|
2922
|
+
return ok(logLines.map((l) => String(l)).join("\n"));
|
|
2923
|
+
}
|
|
2924
|
+
if (typeof logLines === "string")
|
|
2925
|
+
return ok(logLines);
|
|
2926
|
+
return ok("No log output.");
|
|
2927
|
+
}
|
|
2928
|
+
if (name === "computer_service_delete") {
|
|
2929
|
+
if (!cid)
|
|
2930
|
+
return err("computer_id is required");
|
|
2931
|
+
const sid = String(args["service_id"] ?? "");
|
|
2932
|
+
if (!sid)
|
|
2933
|
+
return err("service_id is required");
|
|
2934
|
+
await client.apiDelete(`/api/v1/computers/${encodeURIComponent(cid)}/services/${encodeURIComponent(sid)}`);
|
|
2935
|
+
return ok(`Service ${sid} deleted.`);
|
|
2936
|
+
}
|
|
2937
|
+
// ── Env vars ──────────────────────────────────────────────────────────
|
|
2938
|
+
if (name === "computer_env_list") {
|
|
2939
|
+
if (!cid)
|
|
2940
|
+
return err("computer_id is required");
|
|
2941
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/env`);
|
|
2942
|
+
const items = Array.isArray(result)
|
|
2943
|
+
? result
|
|
2944
|
+
: (() => {
|
|
2945
|
+
const d = unwrapData(result);
|
|
2946
|
+
return ((Array.isArray(d["env"]) ? d["env"] : null) ??
|
|
2947
|
+
(Array.isArray(d["items"]) ? d["items"] : null) ??
|
|
2948
|
+
(Array.isArray(d["data"]) ? d["data"] : null) ??
|
|
2949
|
+
[]);
|
|
2950
|
+
})();
|
|
2951
|
+
if (items.length === 0)
|
|
2952
|
+
return ok("No environment variables set.");
|
|
2953
|
+
const lines = ["Environment variables:"];
|
|
2954
|
+
for (const e of items) {
|
|
2955
|
+
const r = e;
|
|
2956
|
+
const k = String(r["name"] ?? r["key"] ?? "");
|
|
2957
|
+
const v = String(r["value"] ?? "");
|
|
2958
|
+
lines.push(` ${k}=${v}`);
|
|
2959
|
+
}
|
|
2960
|
+
return ok(lines.join("\n"));
|
|
2961
|
+
}
|
|
2962
|
+
if (name === "computer_env_set") {
|
|
2963
|
+
if (!cid)
|
|
2964
|
+
return err("computer_id is required");
|
|
2965
|
+
await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/env`, {
|
|
2966
|
+
name: args["name"],
|
|
2967
|
+
value: args["value"],
|
|
2968
|
+
});
|
|
2969
|
+
return ok(`Set env var ${args["name"]}.`);
|
|
2970
|
+
}
|
|
2971
|
+
if (name === "computer_env_delete") {
|
|
2972
|
+
if (!cid)
|
|
2973
|
+
return err("computer_id is required");
|
|
2974
|
+
const varName = String(args["name"] ?? "");
|
|
2975
|
+
if (!varName)
|
|
2976
|
+
return err("name is required");
|
|
2977
|
+
await client.apiDelete(`/api/v1/computers/${encodeURIComponent(cid)}/env/${encodeURIComponent(varName)}`);
|
|
2978
|
+
return ok(`Deleted env var ${varName}.`);
|
|
2979
|
+
}
|
|
2980
|
+
// ── Logs ──────────────────────────────────────────────────────────────
|
|
2981
|
+
if (name === "computer_logs") {
|
|
2982
|
+
if (!cid)
|
|
2983
|
+
return err("computer_id is required");
|
|
2984
|
+
const qs = args["lines"] !== undefined
|
|
2985
|
+
? `?lines=${encodeURIComponent(String(args["lines"]))}`
|
|
2986
|
+
: "";
|
|
2987
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/logs${qs}`);
|
|
2988
|
+
const data = unwrapData(result);
|
|
2989
|
+
const logLines = data["lines"] ?? data["logs"] ?? data["data"];
|
|
2990
|
+
if (Array.isArray(logLines) && logLines.length > 0) {
|
|
2991
|
+
return ok(logLines.map((l) => String(l)).join("\n"));
|
|
2992
|
+
}
|
|
2993
|
+
if (typeof logLines === "string")
|
|
2994
|
+
return ok(logLines);
|
|
2995
|
+
if (typeof result === "string")
|
|
2996
|
+
return ok(result);
|
|
2997
|
+
return ok("No logs.");
|
|
2998
|
+
}
|
|
2999
|
+
// ── Domains ───────────────────────────────────────────────────────────
|
|
3000
|
+
if (name === "computer_domain_add") {
|
|
3001
|
+
if (!cid)
|
|
3002
|
+
return err("computer_id is required");
|
|
3003
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/domains`, { fqdn: args["fqdn"] });
|
|
3004
|
+
const data = unwrapData(result);
|
|
3005
|
+
const lines = [
|
|
3006
|
+
`Domain registered: id=${data["id"]} fqdn=${data["fqdn"]} status=${data["status"] ?? ""}`,
|
|
3007
|
+
];
|
|
3008
|
+
if (data["verification_target"])
|
|
3009
|
+
lines.push(` CNAME target: ${data["verification_target"]}`);
|
|
3010
|
+
if (data["instructions"])
|
|
3011
|
+
lines.push(` Instructions: ${data["instructions"]}`);
|
|
3012
|
+
return ok(lines.join("\n"));
|
|
3013
|
+
}
|
|
3014
|
+
if (name === "computer_domain_list") {
|
|
3015
|
+
if (!cid)
|
|
3016
|
+
return err("computer_id is required");
|
|
3017
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/domains`);
|
|
3018
|
+
const items = listOf(result);
|
|
3019
|
+
if (items.length === 0)
|
|
3020
|
+
return ok("No custom domains registered.");
|
|
3021
|
+
const lines = ["Custom domains:"];
|
|
3022
|
+
for (const d of items) {
|
|
3023
|
+
const r = d;
|
|
3024
|
+
lines.push(` ${r["id"]} ${r["fqdn"]} ${r["status"] ?? ""}`);
|
|
3025
|
+
}
|
|
3026
|
+
return ok(lines.join("\n"));
|
|
3027
|
+
}
|
|
3028
|
+
if (name === "computer_domain_delete") {
|
|
3029
|
+
if (!cid)
|
|
3030
|
+
return err("computer_id is required");
|
|
3031
|
+
const domainId = String(args["domain_id"] ?? "");
|
|
3032
|
+
if (!domainId)
|
|
3033
|
+
return err("domain_id is required");
|
|
3034
|
+
await client.apiDelete(`/api/v1/computers/${encodeURIComponent(cid)}/domains/${encodeURIComponent(domainId)}`);
|
|
3035
|
+
return ok(`Domain ${domainId} deleted.`);
|
|
3036
|
+
}
|
|
3037
|
+
// ── Sandboxes ─────────────────────────────────────────────────────────
|
|
3038
|
+
if (name === "sandbox_create") {
|
|
3039
|
+
const body = {};
|
|
3040
|
+
if (args["name"])
|
|
3041
|
+
body["name"] = args["name"];
|
|
3042
|
+
if (args["template_id"])
|
|
3043
|
+
body["template_id"] = args["template_id"];
|
|
3044
|
+
if (args["cpu_count"] !== undefined)
|
|
3045
|
+
body["cpu_count"] = args["cpu_count"];
|
|
3046
|
+
if (args["memory_mb"] !== undefined)
|
|
3047
|
+
body["memory_mb"] = args["memory_mb"];
|
|
3048
|
+
if (args["timeout_sec"] !== undefined)
|
|
3049
|
+
body["timeout_sec"] = args["timeout_sec"];
|
|
3050
|
+
const result = await client.apiPost("/api/v1/sandboxes", body);
|
|
3051
|
+
const data = unwrapData(result);
|
|
3052
|
+
const sid = String(data["id"] ?? "");
|
|
3053
|
+
return ok(`Created sandbox '${data["name"] ?? sid}' (id=${sid}).`);
|
|
3054
|
+
}
|
|
3055
|
+
if (name === "sandbox_exec") {
|
|
3056
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3057
|
+
if (!sid)
|
|
3058
|
+
return err("sandbox_id is required");
|
|
3059
|
+
const body = { command: args["command"] };
|
|
3060
|
+
if (args["cwd"])
|
|
3061
|
+
body["cwd"] = args["cwd"];
|
|
3062
|
+
if (args["timeout"] !== undefined)
|
|
3063
|
+
body["timeout"] = args["timeout"];
|
|
3064
|
+
const result = await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/exec`, body);
|
|
3065
|
+
const data = unwrapData(result);
|
|
3066
|
+
const parts = [];
|
|
3067
|
+
if (data["output"] ?? data["stdout"]) {
|
|
3068
|
+
parts.push(`stdout:\n${data["output"] ?? data["stdout"]}`);
|
|
3069
|
+
}
|
|
3070
|
+
if (data["stderr"])
|
|
3071
|
+
parts.push(`stderr:\n${data["stderr"]}`);
|
|
3072
|
+
parts.push(`exit_code: ${data["exit_code"] ?? 0}`);
|
|
3073
|
+
return ok(parts.join("\n"));
|
|
3074
|
+
}
|
|
3075
|
+
if (name === "sandbox_destroy") {
|
|
3076
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3077
|
+
if (!sid)
|
|
3078
|
+
return err("sandbox_id is required");
|
|
3079
|
+
await client.apiDelete(`/api/v1/sandboxes/${encodeURIComponent(sid)}`);
|
|
3080
|
+
return ok(`Sandbox ${sid} destroyed.`);
|
|
3081
|
+
}
|
|
3082
|
+
// ── Sandbox list/get/pause/resume ─────────────────────────────────────
|
|
3083
|
+
if (name === "sandbox_list") {
|
|
3084
|
+
const params = new URLSearchParams();
|
|
3085
|
+
if (args["state"])
|
|
3086
|
+
params.set("state", String(args["state"]));
|
|
3087
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
3088
|
+
const result = await client.apiGet(`/api/v1/sandboxes${qs}`);
|
|
3089
|
+
const items = listOf(result);
|
|
3090
|
+
if (items.length === 0)
|
|
3091
|
+
return ok("No sandboxes found.");
|
|
3092
|
+
const lines = ["Sandboxes:"];
|
|
3093
|
+
for (const s of items) {
|
|
3094
|
+
const r = s;
|
|
3095
|
+
lines.push(` ${r["id"]} ${r["name"] ?? ""} ${r["state"] ?? r["status"] ?? ""} template=${r["template_id"] ?? ""}`);
|
|
3096
|
+
}
|
|
3097
|
+
return ok(lines.join("\n"));
|
|
3098
|
+
}
|
|
3099
|
+
if (name === "sandbox_get") {
|
|
3100
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3101
|
+
if (!sid)
|
|
3102
|
+
return err("sandbox_id is required");
|
|
3103
|
+
const result = await client.apiGet(`/api/v1/sandboxes/${encodeURIComponent(sid)}`);
|
|
3104
|
+
const data = unwrapData(result);
|
|
3105
|
+
const lines = [
|
|
3106
|
+
`id: ${data["id"]}`,
|
|
3107
|
+
`state: ${data["state"] ?? data["status"] ?? ""}`,
|
|
3108
|
+
`template_id: ${data["template_id"] ?? ""}`,
|
|
3109
|
+
`ready: ${data["ready"] ?? ""}`,
|
|
3110
|
+
];
|
|
3111
|
+
if (data["name"])
|
|
3112
|
+
lines.splice(1, 0, `name: ${data["name"]}`);
|
|
3113
|
+
if (data["preview_url"])
|
|
3114
|
+
lines.push(`preview_url: ${data["preview_url"]}`);
|
|
3115
|
+
if (data["boot_ms"] !== undefined)
|
|
3116
|
+
lines.push(`boot_ms: ${data["boot_ms"]}`);
|
|
3117
|
+
return ok(lines.join("\n"));
|
|
3118
|
+
}
|
|
3119
|
+
if (name === "sandbox_pause") {
|
|
3120
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3121
|
+
if (!sid)
|
|
3122
|
+
return err("sandbox_id is required");
|
|
3123
|
+
await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/pause`, {});
|
|
3124
|
+
return ok(`Sandbox ${sid} paused.`);
|
|
3125
|
+
}
|
|
3126
|
+
if (name === "sandbox_resume") {
|
|
3127
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3128
|
+
if (!sid)
|
|
3129
|
+
return err("sandbox_id is required");
|
|
3130
|
+
await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/resume`, {});
|
|
3131
|
+
return ok(`Sandbox ${sid} resumed.`);
|
|
3132
|
+
}
|
|
3133
|
+
// ── Sandbox files ─────────────────────────────────────────────────────
|
|
3134
|
+
if (name === "sandbox_write_file") {
|
|
3135
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3136
|
+
if (!sid)
|
|
3137
|
+
return err("sandbox_id is required");
|
|
3138
|
+
await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/files`, {
|
|
3139
|
+
path: args["path"],
|
|
3140
|
+
content: Buffer.from(typeof args["content"] === "string" ? args["content"] : "", "utf8").toString("base64"),
|
|
3141
|
+
});
|
|
3142
|
+
const len = typeof args["content"] === "string" ? args["content"].length : 0;
|
|
3143
|
+
return ok(`Wrote ${len} bytes to ${args["path"]}`);
|
|
502
3144
|
}
|
|
503
|
-
if (name === "
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
3145
|
+
if (name === "sandbox_read_file") {
|
|
3146
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3147
|
+
if (!sid)
|
|
3148
|
+
return err("sandbox_id is required");
|
|
3149
|
+
const path = String(args["path"] ?? "").replace(/^\//, "");
|
|
3150
|
+
const result = await client.apiGet(`/api/v1/sandboxes/${encodeURIComponent(sid)}/files/${encodeURIComponent(path)}`);
|
|
3151
|
+
const data = unwrapData(result);
|
|
3152
|
+
if (typeof data["content"] === "string") {
|
|
3153
|
+
try {
|
|
3154
|
+
return ok(Buffer.from(data["content"], "base64").toString("utf8"));
|
|
3155
|
+
}
|
|
3156
|
+
catch {
|
|
3157
|
+
return ok(data["content"]);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
if (typeof data["text"] === "string")
|
|
3161
|
+
return ok(data["text"]);
|
|
3162
|
+
return ok(JSON.stringify(data));
|
|
3163
|
+
}
|
|
3164
|
+
if (name === "sandbox_list_files") {
|
|
3165
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3166
|
+
if (!sid)
|
|
3167
|
+
return err("sandbox_id is required");
|
|
3168
|
+
const path = String(args["path"] ?? "/workspace");
|
|
3169
|
+
const params = new URLSearchParams({ path });
|
|
3170
|
+
if (args["depth"] !== undefined)
|
|
3171
|
+
params.set("depth", String(args["depth"]));
|
|
3172
|
+
const result = await client.apiGet(`/api/v1/sandboxes/${encodeURIComponent(sid)}/files?${params.toString()}`);
|
|
3173
|
+
const data = unwrapData(result);
|
|
3174
|
+
return ok(JSON.stringify(data, null, 2));
|
|
3175
|
+
}
|
|
3176
|
+
if (name === "sandbox_upload") {
|
|
3177
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3178
|
+
if (!sid)
|
|
3179
|
+
return err("sandbox_id is required");
|
|
3180
|
+
await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/files`, {
|
|
3181
|
+
path: args["path"],
|
|
3182
|
+
content: Buffer.from(typeof args["content"] === "string" ? args["content"] : "", "utf8").toString("base64"),
|
|
509
3183
|
});
|
|
510
|
-
return ok(`
|
|
3184
|
+
return ok(`Uploaded ${args["path"]} to sandbox ${sid}.`);
|
|
511
3185
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
3186
|
+
// ── Sandbox python ────────────────────────────────────────────────────
|
|
3187
|
+
if (name === "sandbox_python") {
|
|
3188
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3189
|
+
if (!sid)
|
|
3190
|
+
return err("sandbox_id is required");
|
|
3191
|
+
const code = String(args["code"] ?? "");
|
|
3192
|
+
const tmpPath = "/tmp/_mcp_run.py";
|
|
3193
|
+
// Write code file
|
|
3194
|
+
await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/files`, {
|
|
3195
|
+
path: tmpPath,
|
|
3196
|
+
content: Buffer.from(code, "utf8").toString("base64"),
|
|
518
3197
|
});
|
|
519
|
-
|
|
3198
|
+
// Execute it
|
|
3199
|
+
const execBody = {
|
|
3200
|
+
command: `python3 ${tmpPath}`,
|
|
3201
|
+
};
|
|
3202
|
+
if (args["timeout"] !== undefined)
|
|
3203
|
+
execBody["timeout"] = args["timeout"];
|
|
3204
|
+
const result = await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/exec`, execBody);
|
|
3205
|
+
const data = unwrapData(result);
|
|
3206
|
+
const parts = [];
|
|
3207
|
+
if (data["stdout"] ?? data["output"])
|
|
3208
|
+
parts.push(`stdout:\n${data["stdout"] ?? data["output"]}`);
|
|
3209
|
+
if (data["stderr"])
|
|
3210
|
+
parts.push(`stderr:\n${data["stderr"]}`);
|
|
3211
|
+
parts.push(`exit_code: ${data["exit_code"] ?? 0}`);
|
|
3212
|
+
return ok(parts.join("\n"));
|
|
520
3213
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
3214
|
+
// ── Sandbox snapshots ─────────────────────────────────────────────────
|
|
3215
|
+
if (name === "sandbox_snapshot_create") {
|
|
3216
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3217
|
+
if (!sid)
|
|
3218
|
+
return err("sandbox_id is required");
|
|
3219
|
+
const body = {};
|
|
3220
|
+
if (args["comment"])
|
|
3221
|
+
body["comment"] = args["comment"];
|
|
3222
|
+
const result = await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/snapshots`, body);
|
|
3223
|
+
const data = unwrapData(result);
|
|
3224
|
+
const snapId = data["id"] ?? data["snapshot_id"] ?? "unknown";
|
|
3225
|
+
return ok(`Snapshot created: id=${snapId}`);
|
|
3226
|
+
}
|
|
3227
|
+
if (name === "sandbox_snapshot_list") {
|
|
3228
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3229
|
+
if (!sid)
|
|
3230
|
+
return err("sandbox_id is required");
|
|
3231
|
+
const result = await client.apiGet(`/api/v1/sandboxes/${encodeURIComponent(sid)}/snapshots`);
|
|
3232
|
+
const items = listOf(result);
|
|
3233
|
+
if (items.length === 0)
|
|
3234
|
+
return ok("No snapshots found.");
|
|
3235
|
+
const lines = ["Snapshots:"];
|
|
3236
|
+
for (const s of items) {
|
|
3237
|
+
const r = s;
|
|
3238
|
+
lines.push(` ${r["id"]} ${r["created_at"] ?? ""} ${r["comment"] ?? ""}`);
|
|
3239
|
+
}
|
|
3240
|
+
return ok(lines.join("\n"));
|
|
3241
|
+
}
|
|
3242
|
+
if (name === "sandbox_snapshot_restore") {
|
|
3243
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3244
|
+
if (!sid)
|
|
3245
|
+
return err("sandbox_id is required");
|
|
3246
|
+
const snapId = String(args["snapshot_id"] ?? "");
|
|
3247
|
+
if (!snapId)
|
|
3248
|
+
return err("snapshot_id is required");
|
|
3249
|
+
const result = await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/restore/${encodeURIComponent(snapId)}`, {});
|
|
3250
|
+
const data = unwrapData(result);
|
|
3251
|
+
const state = data["state"] ?? data["status"] ?? "unknown";
|
|
3252
|
+
return ok(`Sandbox ${sid} restored from snapshot ${snapId} (state=${state}).`);
|
|
3253
|
+
}
|
|
3254
|
+
// ── Sandbox logs ──────────────────────────────────────────────────────
|
|
3255
|
+
if (name === "sandbox_logs") {
|
|
3256
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3257
|
+
if (!sid)
|
|
3258
|
+
return err("sandbox_id is required");
|
|
3259
|
+
const params = new URLSearchParams();
|
|
3260
|
+
if (args["lines"] !== undefined)
|
|
3261
|
+
params.set("lines", String(args["lines"]));
|
|
3262
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
3263
|
+
const result = await client.apiGet(`/api/v1/sandboxes/${encodeURIComponent(sid)}/logs${qs}`);
|
|
3264
|
+
const data = unwrapData(result);
|
|
3265
|
+
if (Array.isArray(data)) {
|
|
3266
|
+
return ok(data.length > 0 ? data.join("\n") : "No logs.");
|
|
3267
|
+
}
|
|
3268
|
+
return ok(JSON.stringify(data, null, 2));
|
|
3269
|
+
}
|
|
3270
|
+
// ── Sandbox expose ────────────────────────────────────────────────────
|
|
3271
|
+
if (name === "sandbox_expose") {
|
|
3272
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3273
|
+
if (!sid)
|
|
3274
|
+
return err("sandbox_id is required");
|
|
3275
|
+
const body = {};
|
|
3276
|
+
if (args["port"] !== undefined)
|
|
3277
|
+
body["port"] = args["port"];
|
|
3278
|
+
const result = await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/expose`, body);
|
|
3279
|
+
const data = unwrapData(result);
|
|
3280
|
+
const url = data["url"] ?? data["preview_url"] ?? "";
|
|
3281
|
+
return ok(`Preview URL: ${url}`);
|
|
3282
|
+
}
|
|
3283
|
+
// ── Sandbox deploy ────────────────────────────────────────────────────
|
|
3284
|
+
if (name === "sandbox_deploy") {
|
|
3285
|
+
const sid = String(args["sandbox_id"] ?? "");
|
|
3286
|
+
if (!sid)
|
|
3287
|
+
return err("sandbox_id is required");
|
|
3288
|
+
const body = {};
|
|
3289
|
+
if (args["name"])
|
|
3290
|
+
body["name"] = args["name"];
|
|
3291
|
+
if (args["output_path"])
|
|
3292
|
+
body["output_path"] = args["output_path"];
|
|
3293
|
+
if (args["entrypoint"])
|
|
3294
|
+
body["entrypoint"] = args["entrypoint"];
|
|
3295
|
+
if (args["domain"])
|
|
3296
|
+
body["domain"] = args["domain"];
|
|
3297
|
+
const result = await client.apiPost(`/api/v1/sandboxes/${encodeURIComponent(sid)}/deploy`, body);
|
|
3298
|
+
const data = unwrapData(result);
|
|
3299
|
+
const deployId = data["id"] ?? data["deployment_id"] ?? "unknown";
|
|
3300
|
+
const url = data["url"] ?? data["preview_url"] ?? "";
|
|
3301
|
+
return ok(`Deployed sandbox ${sid} (deployment id=${deployId}, url=${url}).`);
|
|
3302
|
+
}
|
|
3303
|
+
// ── Sandbox templates ─────────────────────────────────────────────────
|
|
3304
|
+
if (name === "sandbox_template_list") {
|
|
3305
|
+
const result = await client.apiGet("/api/v1/sandbox-templates");
|
|
3306
|
+
const data = unwrapData(result);
|
|
3307
|
+
const items = Array.isArray(data)
|
|
3308
|
+
? data
|
|
3309
|
+
: (() => {
|
|
3310
|
+
const r = data;
|
|
3311
|
+
for (const key of ["templates", "data", "items"]) {
|
|
3312
|
+
if (Array.isArray(r[key]))
|
|
3313
|
+
return r[key];
|
|
3314
|
+
}
|
|
3315
|
+
return Object.values(r);
|
|
3316
|
+
})();
|
|
3317
|
+
if (items.length === 0)
|
|
3318
|
+
return ok("No templates found.");
|
|
3319
|
+
const lines = ["Templates:"];
|
|
3320
|
+
for (const t of items) {
|
|
3321
|
+
const r = t;
|
|
3322
|
+
lines.push(` ${r["id"] ?? r["slug"]} ${r["name"]} ${r["description"] ?? ""}`);
|
|
3323
|
+
}
|
|
3324
|
+
return ok(lines.join("\n"));
|
|
3325
|
+
}
|
|
3326
|
+
if (name === "sandbox_template_create") {
|
|
3327
|
+
const body = {
|
|
3328
|
+
name: args["name"],
|
|
3329
|
+
build_spec: args["build_spec"],
|
|
3330
|
+
};
|
|
3331
|
+
if (args["slug"])
|
|
3332
|
+
body["slug"] = args["slug"];
|
|
3333
|
+
if (args["description"])
|
|
3334
|
+
body["description"] = args["description"];
|
|
3335
|
+
const result = await client.apiPost("/api/v1/sandbox-templates", body);
|
|
3336
|
+
const data = unwrapData(result);
|
|
3337
|
+
return ok(`Created template '${data["name"] ?? args["name"]}' (id=${data["id"]}).`);
|
|
3338
|
+
}
|
|
3339
|
+
// ── Deploy ────────────────────────────────────────────────────────────
|
|
3340
|
+
if (name === "deploy") {
|
|
3341
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3342
|
+
if (!did)
|
|
3343
|
+
return err("deployment_id is required");
|
|
3344
|
+
const result = await client.apiPost(`/api/v1/deployments/${encodeURIComponent(did)}/redeploy`, {});
|
|
3345
|
+
const data = unwrapData(result);
|
|
3346
|
+
return ok(`Redeploy queued (build id: ${data["id"] ?? "unknown"})`);
|
|
3347
|
+
}
|
|
3348
|
+
// ── Deployments ──────────────────────────────────────────────────────
|
|
3349
|
+
if (name === "deployment_list") {
|
|
3350
|
+
const result = await client.apiGet("/api/v1/deployments");
|
|
3351
|
+
const items = listOf(result);
|
|
3352
|
+
if (items.length === 0)
|
|
3353
|
+
return ok("No deployments found.");
|
|
3354
|
+
const lines = ["Deployments:"];
|
|
3355
|
+
for (const d of items) {
|
|
3356
|
+
const r = d;
|
|
3357
|
+
lines.push(" " +
|
|
3358
|
+
r["id"] +
|
|
3359
|
+
" " +
|
|
3360
|
+
r["name"] +
|
|
3361
|
+
" " +
|
|
3362
|
+
(r["status"] ?? r["state"] ?? ""));
|
|
3363
|
+
}
|
|
3364
|
+
return ok(lines.join("\n"));
|
|
3365
|
+
}
|
|
3366
|
+
if (name === "deployment_get") {
|
|
3367
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3368
|
+
if (!did)
|
|
3369
|
+
return err("deployment_id is required");
|
|
3370
|
+
const result = await client.apiGet("/api/v1/deployments/" + encodeURIComponent(did));
|
|
3371
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
3372
|
+
}
|
|
3373
|
+
if (name === "deployment_create") {
|
|
3374
|
+
const body = { name: args["name"] };
|
|
3375
|
+
if (args["type"])
|
|
3376
|
+
body["type"] = args["type"];
|
|
3377
|
+
if (args["source"])
|
|
3378
|
+
body["source"] = args["source"];
|
|
3379
|
+
if (args["env_vars"])
|
|
3380
|
+
body["env_vars"] = args["env_vars"];
|
|
3381
|
+
if (args["region"])
|
|
3382
|
+
body["region"] = args["region"];
|
|
3383
|
+
const result = await client.apiPost("/api/v1/deployments", body);
|
|
3384
|
+
const data = unwrapData(result);
|
|
3385
|
+
return ok("Created deployment '" +
|
|
3386
|
+
String(data["name"] ?? args["name"]) +
|
|
3387
|
+
"' (id=" +
|
|
3388
|
+
data["id"] +
|
|
3389
|
+
")");
|
|
3390
|
+
}
|
|
3391
|
+
if (name === "deployment_delete") {
|
|
3392
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3393
|
+
if (!did)
|
|
3394
|
+
return err("deployment_id is required");
|
|
3395
|
+
await client.apiDelete("/api/v1/deployments/" + encodeURIComponent(did));
|
|
3396
|
+
return ok("Deployment " + did + " deleted.");
|
|
3397
|
+
}
|
|
3398
|
+
if (name === "deployment_publish") {
|
|
3399
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3400
|
+
if (!did)
|
|
3401
|
+
return err("deployment_id is required");
|
|
3402
|
+
const body = {};
|
|
3403
|
+
if (args["source"])
|
|
3404
|
+
body["source"] = args["source"];
|
|
3405
|
+
const result = await client.apiPost("/api/v1/deployments/" + encodeURIComponent(did) + "/publish", body);
|
|
3406
|
+
const data = unwrapData(result);
|
|
3407
|
+
return ok("Published deployment " +
|
|
3408
|
+
did +
|
|
3409
|
+
" (version id=" +
|
|
3410
|
+
(data["id"] ?? "unknown") +
|
|
3411
|
+
")");
|
|
3412
|
+
}
|
|
3413
|
+
if (name === "deployment_rollback") {
|
|
3414
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3415
|
+
const vid = String(args["version_id"] ?? "");
|
|
3416
|
+
if (!did)
|
|
3417
|
+
return err("deployment_id is required");
|
|
3418
|
+
if (!vid)
|
|
3419
|
+
return err("version_id is required");
|
|
3420
|
+
await client.apiPost("/api/v1/deployments/" + encodeURIComponent(did) + "/rollback", { version_id: vid });
|
|
3421
|
+
return ok("Deployment " + did + " rolled back to version " + vid + ".");
|
|
3422
|
+
}
|
|
3423
|
+
if (name === "deployment_env_list") {
|
|
3424
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3425
|
+
if (!did)
|
|
3426
|
+
return err("deployment_id is required");
|
|
3427
|
+
const result = await client.apiGet("/api/v1/deployments/" + encodeURIComponent(did) + "/env");
|
|
3428
|
+
const envVars = unwrapData(result);
|
|
3429
|
+
if (!envVars ||
|
|
3430
|
+
(Array.isArray(envVars) && envVars.length === 0))
|
|
3431
|
+
return ok("No environment variables set.");
|
|
3432
|
+
const lines = ["Environment variables:"];
|
|
3433
|
+
if (typeof envVars === "object" && !Array.isArray(envVars)) {
|
|
3434
|
+
for (const [k, v] of Object.entries(envVars)) {
|
|
3435
|
+
lines.push(" " + k + "=" + String(v));
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
else if (Array.isArray(envVars)) {
|
|
3439
|
+
for (const e of envVars) {
|
|
3440
|
+
lines.push(" " + e["key"] + "=" + e["value"]);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
return ok(lines.join("\n"));
|
|
3444
|
+
}
|
|
3445
|
+
if (name === "deployment_env_set") {
|
|
3446
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3447
|
+
if (!did)
|
|
3448
|
+
return err("deployment_id is required");
|
|
3449
|
+
await client.apiPost("/api/v1/deployments/" + encodeURIComponent(did) + "/env", { key: args["key"], value: args["value"] });
|
|
3450
|
+
return ok("Set " + String(args["key"]) + " on deployment " + did + ".");
|
|
3451
|
+
}
|
|
3452
|
+
if (name === "deployment_logs") {
|
|
3453
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3454
|
+
if (!did)
|
|
3455
|
+
return err("deployment_id is required");
|
|
3456
|
+
const params = new URLSearchParams({
|
|
3457
|
+
lines: String(args["lines"] ?? 100),
|
|
529
3458
|
});
|
|
530
|
-
|
|
3459
|
+
if (args["since"])
|
|
3460
|
+
params.set("since", String(args["since"]));
|
|
3461
|
+
const result = await client.apiGet("/api/v1/deployments/" +
|
|
3462
|
+
encodeURIComponent(did) +
|
|
3463
|
+
"/logs?" +
|
|
3464
|
+
params.toString());
|
|
3465
|
+
const logs = unwrapData(result);
|
|
3466
|
+
if (Array.isArray(logs))
|
|
3467
|
+
return ok(logs.length
|
|
3468
|
+
? logs.map(String).join("\n")
|
|
3469
|
+
: "No logs.");
|
|
3470
|
+
return ok(String(logs));
|
|
3471
|
+
}
|
|
3472
|
+
if (name === "deployment_version_list") {
|
|
3473
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3474
|
+
if (!did)
|
|
3475
|
+
return err("deployment_id is required");
|
|
3476
|
+
const result = await client.apiGet("/api/v1/deployments/" + encodeURIComponent(did) + "/versions");
|
|
3477
|
+
const versions = listOf(result);
|
|
3478
|
+
if (versions.length === 0)
|
|
3479
|
+
return ok("No versions found.");
|
|
3480
|
+
const lines = ["Versions:"];
|
|
3481
|
+
for (const v of versions) {
|
|
3482
|
+
const r = v;
|
|
3483
|
+
lines.push(" " +
|
|
3484
|
+
r["id"] +
|
|
3485
|
+
" " +
|
|
3486
|
+
(r["created_at"] ?? "") +
|
|
3487
|
+
" " +
|
|
3488
|
+
(r["status"] ?? ""));
|
|
3489
|
+
}
|
|
3490
|
+
return ok(lines.join("\n"));
|
|
3491
|
+
}
|
|
3492
|
+
if (name === "deployment_version_promote") {
|
|
3493
|
+
const did = String(args["deployment_id"] ?? "");
|
|
3494
|
+
const vid = String(args["version_id"] ?? "");
|
|
3495
|
+
if (!did)
|
|
3496
|
+
return err("deployment_id is required");
|
|
3497
|
+
if (!vid)
|
|
3498
|
+
return err("version_id is required");
|
|
3499
|
+
await client.apiPost("/api/v1/deployments/" +
|
|
3500
|
+
encodeURIComponent(did) +
|
|
3501
|
+
"/versions/" +
|
|
3502
|
+
encodeURIComponent(vid) +
|
|
3503
|
+
"/promote", {});
|
|
3504
|
+
return ok("Version " + vid + " promoted on deployment " + did + ".");
|
|
3505
|
+
}
|
|
3506
|
+
// ── Storage ───────────────────────────────────────────────────────────
|
|
3507
|
+
if (name === "storage_bucket_list") {
|
|
3508
|
+
const result = await client.apiGet("/api/v1/storage/buckets");
|
|
3509
|
+
const items = listOf(result);
|
|
3510
|
+
if (items.length === 0)
|
|
3511
|
+
return ok("No buckets found.");
|
|
3512
|
+
const lines = ["Buckets:"];
|
|
3513
|
+
for (const b of items) {
|
|
3514
|
+
const r = b;
|
|
3515
|
+
lines.push(" " + r["id"] + " " + r["name"] + " " + (r["region"] ?? ""));
|
|
3516
|
+
}
|
|
3517
|
+
return ok(lines.join("\n"));
|
|
3518
|
+
}
|
|
3519
|
+
if (name === "storage_bucket_create") {
|
|
3520
|
+
const body = { name: args["name"] };
|
|
3521
|
+
if (args["region"])
|
|
3522
|
+
body["region"] = args["region"];
|
|
3523
|
+
if (args["public"] !== undefined)
|
|
3524
|
+
body["public"] = args["public"];
|
|
3525
|
+
const result = await client.apiPost("/api/v1/storage/buckets", body);
|
|
3526
|
+
const data = unwrapData(result);
|
|
3527
|
+
return ok("Created bucket '" +
|
|
3528
|
+
String(data["name"] ?? args["name"]) +
|
|
3529
|
+
"' (id=" +
|
|
3530
|
+
data["id"] +
|
|
3531
|
+
")");
|
|
3532
|
+
}
|
|
3533
|
+
if (name === "storage_bucket_delete") {
|
|
3534
|
+
const bid = String(args["bucket_id"] ?? "");
|
|
3535
|
+
if (!bid)
|
|
3536
|
+
return err("bucket_id is required");
|
|
3537
|
+
await client.apiDelete("/api/v1/storage/buckets/" + encodeURIComponent(bid));
|
|
3538
|
+
return ok("Bucket " + bid + " deleted.");
|
|
3539
|
+
}
|
|
3540
|
+
if (name === "storage_object_list") {
|
|
3541
|
+
const bid = String(args["bucket_id"] ?? "");
|
|
3542
|
+
if (!bid)
|
|
3543
|
+
return err("bucket_id is required");
|
|
3544
|
+
const params = new URLSearchParams();
|
|
3545
|
+
if (args["prefix"])
|
|
3546
|
+
params.set("prefix", String(args["prefix"]));
|
|
3547
|
+
const qs = params.toString() ? "?" + params.toString() : "";
|
|
3548
|
+
const result = await client.apiGet("/api/v1/storage/buckets/" + encodeURIComponent(bid) + "/objects" + qs);
|
|
3549
|
+
const items = listOf(result);
|
|
3550
|
+
if (items.length === 0)
|
|
3551
|
+
return ok("No objects found.");
|
|
3552
|
+
const lines = ["Objects:"];
|
|
3553
|
+
for (const o of items) {
|
|
3554
|
+
const r = o;
|
|
3555
|
+
lines.push(" " +
|
|
3556
|
+
r["key"] +
|
|
3557
|
+
" " +
|
|
3558
|
+
(r["size"] ?? "") +
|
|
3559
|
+
" " +
|
|
3560
|
+
(r["last_modified"] ?? ""));
|
|
3561
|
+
}
|
|
3562
|
+
return ok(lines.join("\n"));
|
|
3563
|
+
}
|
|
3564
|
+
if (name === "storage_object_upload") {
|
|
3565
|
+
const bid = String(args["bucket_id"] ?? "");
|
|
3566
|
+
if (!bid)
|
|
3567
|
+
return err("bucket_id is required");
|
|
3568
|
+
await client.apiPost("/api/v1/storage/buckets/" + encodeURIComponent(bid) + "/objects", {
|
|
3569
|
+
key: args["key"],
|
|
3570
|
+
content: args["content"],
|
|
3571
|
+
content_type: args["content_type"] ?? "application/octet-stream",
|
|
3572
|
+
});
|
|
3573
|
+
return ok("Uploaded " + String(args["key"]) + " to bucket " + bid + ".");
|
|
3574
|
+
}
|
|
3575
|
+
if (name === "storage_object_download") {
|
|
3576
|
+
const bid = String(args["bucket_id"] ?? "");
|
|
3577
|
+
if (!bid)
|
|
3578
|
+
return err("bucket_id is required");
|
|
3579
|
+
const result = await client.apiGet("/api/v1/storage/buckets/" +
|
|
3580
|
+
encodeURIComponent(bid) +
|
|
3581
|
+
"/objects/" +
|
|
3582
|
+
encodeURIComponent(String(args["key"] ?? "")));
|
|
3583
|
+
const data = unwrapData(result);
|
|
3584
|
+
const content = typeof data["content"] === "string"
|
|
3585
|
+
? Buffer.from(data["content"], "base64").toString("utf8")
|
|
3586
|
+
: typeof data["text"] === "string"
|
|
3587
|
+
? data["text"]
|
|
3588
|
+
: JSON.stringify(data);
|
|
3589
|
+
return ok(content);
|
|
3590
|
+
}
|
|
3591
|
+
if (name === "storage_object_delete") {
|
|
3592
|
+
const bid = String(args["bucket_id"] ?? "");
|
|
3593
|
+
if (!bid)
|
|
3594
|
+
return err("bucket_id is required");
|
|
3595
|
+
await client.apiDelete("/api/v1/storage/buckets/" +
|
|
3596
|
+
encodeURIComponent(bid) +
|
|
3597
|
+
"/objects/" +
|
|
3598
|
+
encodeURIComponent(String(args["key"] ?? "")));
|
|
3599
|
+
return ok("Deleted " + String(args["key"]) + " from bucket " + bid + ".");
|
|
3600
|
+
}
|
|
3601
|
+
if (name === "storage_presign") {
|
|
3602
|
+
const bid = String(args["bucket_id"] ?? "");
|
|
3603
|
+
if (!bid)
|
|
3604
|
+
return err("bucket_id is required");
|
|
3605
|
+
const result = await client.apiPost("/api/v1/storage/buckets/" + encodeURIComponent(bid) + "/presign", {
|
|
3606
|
+
key: args["key"],
|
|
3607
|
+
expires_in: args["expires_in"] ?? 3600,
|
|
3608
|
+
method: args["method"] ?? "GET",
|
|
3609
|
+
});
|
|
3610
|
+
const data = unwrapData(result);
|
|
3611
|
+
return ok("Presigned URL: " + (data["url"] ?? JSON.stringify(data)));
|
|
3612
|
+
}
|
|
3613
|
+
// ── Databases ─────────────────────────────────────────────────────────
|
|
3614
|
+
if (name === "database_list") {
|
|
3615
|
+
const result = await client.apiGet("/api/v1/databases");
|
|
3616
|
+
const items = listOf(result);
|
|
3617
|
+
if (items.length === 0)
|
|
3618
|
+
return ok("No databases found.");
|
|
3619
|
+
const lines = ["Databases:"];
|
|
3620
|
+
for (const d of items) {
|
|
3621
|
+
const r = d;
|
|
3622
|
+
lines.push(" " +
|
|
3623
|
+
r["id"] +
|
|
3624
|
+
" " +
|
|
3625
|
+
r["name"] +
|
|
3626
|
+
" " +
|
|
3627
|
+
(r["engine"] ?? "") +
|
|
3628
|
+
" " +
|
|
3629
|
+
(r["status"] ?? ""));
|
|
3630
|
+
}
|
|
3631
|
+
return ok(lines.join("\n"));
|
|
531
3632
|
}
|
|
532
|
-
if (name === "
|
|
533
|
-
if (!cid)
|
|
534
|
-
return err("computer_id is required");
|
|
3633
|
+
if (name === "database_create") {
|
|
535
3634
|
const body = {
|
|
536
|
-
|
|
537
|
-
|
|
3635
|
+
name: args["name"],
|
|
3636
|
+
engine: args["engine"],
|
|
538
3637
|
};
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
await client.apiPost(
|
|
544
|
-
|
|
3638
|
+
for (const key of ["version", "size", "region"]) {
|
|
3639
|
+
if (args[key])
|
|
3640
|
+
body[key] = args[key];
|
|
3641
|
+
}
|
|
3642
|
+
const result = await client.apiPost("/api/v1/databases", body);
|
|
3643
|
+
const data = unwrapData(result);
|
|
3644
|
+
return ok("Created database '" +
|
|
3645
|
+
String(data["name"] ?? args["name"]) +
|
|
3646
|
+
"' (id=" +
|
|
3647
|
+
data["id"] +
|
|
3648
|
+
")");
|
|
545
3649
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (!
|
|
549
|
-
return err("
|
|
550
|
-
await client.
|
|
551
|
-
|
|
552
|
-
});
|
|
553
|
-
const preview = typeof args["text"] === "string"
|
|
554
|
-
? args["text"].slice(0, 40) + (args["text"].length > 40 ? "..." : "")
|
|
555
|
-
: "";
|
|
556
|
-
return ok(`Typed: ${JSON.stringify(preview)}`);
|
|
3650
|
+
if (name === "database_get") {
|
|
3651
|
+
const dbid = String(args["database_id"] ?? "");
|
|
3652
|
+
if (!dbid)
|
|
3653
|
+
return err("database_id is required");
|
|
3654
|
+
const result = await client.apiGet("/api/v1/databases/" + encodeURIComponent(dbid));
|
|
3655
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
557
3656
|
}
|
|
558
|
-
if (name === "
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
3657
|
+
if (name === "database_delete") {
|
|
3658
|
+
const dbid = String(args["database_id"] ?? "");
|
|
3659
|
+
if (!dbid)
|
|
3660
|
+
return err("database_id is required");
|
|
3661
|
+
await client.apiDelete("/api/v1/databases/" + encodeURIComponent(dbid));
|
|
3662
|
+
return ok("Database " + dbid + " deleted.");
|
|
3663
|
+
}
|
|
3664
|
+
if (name === "database_credentials") {
|
|
3665
|
+
const dbid = String(args["database_id"] ?? "");
|
|
3666
|
+
if (!dbid)
|
|
3667
|
+
return err("database_id is required");
|
|
3668
|
+
const result = await client.apiGet("/api/v1/databases/" + encodeURIComponent(dbid) + "/credentials");
|
|
3669
|
+
const data = unwrapData(result);
|
|
3670
|
+
const lines = ["Database credentials:"];
|
|
3671
|
+
for (const field of [
|
|
3672
|
+
"connection_string",
|
|
3673
|
+
"host",
|
|
3674
|
+
"port",
|
|
3675
|
+
"database",
|
|
3676
|
+
"username",
|
|
3677
|
+
"password",
|
|
3678
|
+
]) {
|
|
3679
|
+
if (data[field])
|
|
3680
|
+
lines.push(" " + field + ": " + data[field]);
|
|
3681
|
+
}
|
|
3682
|
+
return ok(lines.join("\n"));
|
|
3683
|
+
}
|
|
3684
|
+
if (name === "database_logs") {
|
|
3685
|
+
const dbid = String(args["database_id"] ?? "");
|
|
3686
|
+
if (!dbid)
|
|
3687
|
+
return err("database_id is required");
|
|
3688
|
+
const params = new URLSearchParams({
|
|
3689
|
+
lines: String(args["lines"] ?? 100),
|
|
563
3690
|
});
|
|
564
|
-
|
|
3691
|
+
if (args["since"])
|
|
3692
|
+
params.set("since", String(args["since"]));
|
|
3693
|
+
const result = await client.apiGet("/api/v1/databases/" +
|
|
3694
|
+
encodeURIComponent(dbid) +
|
|
3695
|
+
"/logs?" +
|
|
3696
|
+
params.toString());
|
|
3697
|
+
const logs = unwrapData(result);
|
|
3698
|
+
if (Array.isArray(logs))
|
|
3699
|
+
return ok(logs.length
|
|
3700
|
+
? logs.map(String).join("\n")
|
|
3701
|
+
: "No logs.");
|
|
3702
|
+
return ok(String(logs));
|
|
565
3703
|
}
|
|
566
|
-
|
|
3704
|
+
// ── Workspaces ────────────────────────────────────────────────────────
|
|
3705
|
+
if (name === "workspace_list") {
|
|
3706
|
+
const result = await client.apiGet("/api/v1/workspaces");
|
|
3707
|
+
const items = listOf(result);
|
|
3708
|
+
if (items.length === 0)
|
|
3709
|
+
return ok("No workspaces found.");
|
|
3710
|
+
const lines = ["Workspaces:"];
|
|
3711
|
+
for (const w of items) {
|
|
3712
|
+
const r = w;
|
|
3713
|
+
lines.push(" " + r["id"] + " " + r["name"]);
|
|
3714
|
+
}
|
|
3715
|
+
return ok(lines.join("\n"));
|
|
3716
|
+
}
|
|
3717
|
+
if (name === "workspace_create") {
|
|
3718
|
+
const body = { name: args["name"] };
|
|
3719
|
+
if (args["description"])
|
|
3720
|
+
body["description"] = args["description"];
|
|
3721
|
+
const result = await client.apiPost("/api/v1/workspaces", body);
|
|
3722
|
+
const data = unwrapData(result);
|
|
3723
|
+
return ok("Created workspace '" +
|
|
3724
|
+
String(data["name"] ?? args["name"]) +
|
|
3725
|
+
"' (id=" +
|
|
3726
|
+
data["id"] +
|
|
3727
|
+
")");
|
|
3728
|
+
}
|
|
3729
|
+
if (name === "workspace_get") {
|
|
3730
|
+
const wid = String(args["workspace_id"] ?? "");
|
|
3731
|
+
if (!wid)
|
|
3732
|
+
return err("workspace_id is required");
|
|
3733
|
+
const result = await client.apiGet("/api/v1/workspaces/" + encodeURIComponent(wid));
|
|
3734
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
3735
|
+
}
|
|
3736
|
+
if (name === "workspace_update") {
|
|
3737
|
+
const wid = String(args["workspace_id"] ?? "");
|
|
3738
|
+
if (!wid)
|
|
3739
|
+
return err("workspace_id is required");
|
|
3740
|
+
const body = {};
|
|
3741
|
+
if (args["name"])
|
|
3742
|
+
body["name"] = args["name"];
|
|
3743
|
+
if (args["description"])
|
|
3744
|
+
body["description"] = args["description"];
|
|
3745
|
+
await client.apiPost("/api/v1/workspaces/" + encodeURIComponent(wid), body);
|
|
3746
|
+
return ok("Workspace " + wid + " updated.");
|
|
3747
|
+
}
|
|
3748
|
+
if (name === "workspace_stats") {
|
|
3749
|
+
const wid = String(args["workspace_id"] ?? "");
|
|
3750
|
+
if (!wid)
|
|
3751
|
+
return err("workspace_id is required");
|
|
3752
|
+
const result = await client.apiGet("/api/v1/workspaces/" + encodeURIComponent(wid) + "/stats");
|
|
3753
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
3754
|
+
}
|
|
3755
|
+
if (name === "workspace_usage") {
|
|
3756
|
+
const wid = String(args["workspace_id"] ?? "");
|
|
3757
|
+
if (!wid)
|
|
3758
|
+
return err("workspace_id is required");
|
|
3759
|
+
const params = new URLSearchParams();
|
|
3760
|
+
if (args["period"])
|
|
3761
|
+
params.set("period", String(args["period"]));
|
|
3762
|
+
const qs = params.toString() ? "?" + params.toString() : "";
|
|
3763
|
+
const result = await client.apiGet("/api/v1/workspaces/" + encodeURIComponent(wid) + "/usage" + qs);
|
|
3764
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
3765
|
+
}
|
|
3766
|
+
// ── Billing ───────────────────────────────────────────────────────────
|
|
3767
|
+
if (name === "billing_usage") {
|
|
3768
|
+
const result = await client.apiGet("/api/v1/billing/usage");
|
|
3769
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
3770
|
+
}
|
|
3771
|
+
if (name === "billing_plan") {
|
|
3772
|
+
const result = await client.apiGet("/api/v1/billing/plan");
|
|
3773
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
3774
|
+
}
|
|
3775
|
+
// ── Tunnels / Port forwarding ─────────────────────────────────────────
|
|
3776
|
+
if (name === "computer_expose_port") {
|
|
567
3777
|
if (!cid)
|
|
568
3778
|
return err("computer_id is required");
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
});
|
|
573
|
-
|
|
3779
|
+
const body = { port: args["port"] };
|
|
3780
|
+
if (args["protocol"])
|
|
3781
|
+
body["protocol"] = args["protocol"];
|
|
3782
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/ports`, body);
|
|
3783
|
+
const data = unwrapData(result);
|
|
3784
|
+
const url = String(data["url"] ?? data["public_url"] ?? "");
|
|
3785
|
+
return ok(`Port ${args["port"]} exposed. URL: ${url}`);
|
|
574
3786
|
}
|
|
575
|
-
|
|
576
|
-
if (name === "computer_get_screen_size") {
|
|
3787
|
+
if (name === "computer_list_ports") {
|
|
577
3788
|
if (!cid)
|
|
578
3789
|
return err("computer_id is required");
|
|
579
|
-
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/
|
|
580
|
-
const
|
|
581
|
-
|
|
3790
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/ports`);
|
|
3791
|
+
const items = listOf(result);
|
|
3792
|
+
if (items.length === 0)
|
|
3793
|
+
return ok("No ports currently exposed.");
|
|
3794
|
+
const lines = ["Exposed ports:"];
|
|
3795
|
+
for (const p of items) {
|
|
3796
|
+
const r = p;
|
|
3797
|
+
lines.push(` port=${r["port"]} protocol=${r["protocol"] ?? "http"} url=${r["url"] ?? r["public_url"] ?? ""}`);
|
|
3798
|
+
}
|
|
3799
|
+
return ok(lines.join("\n"));
|
|
582
3800
|
}
|
|
583
|
-
if (name === "
|
|
3801
|
+
if (name === "computer_preview_url") {
|
|
584
3802
|
if (!cid)
|
|
585
3803
|
return err("computer_id is required");
|
|
586
|
-
const
|
|
3804
|
+
const port = args["port"];
|
|
3805
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/ports/${encodeURIComponent(String(port))}/url`);
|
|
587
3806
|
const data = unwrapData(result);
|
|
588
|
-
|
|
3807
|
+
const url = String(data["url"] ?? data["public_url"] ?? "") ||
|
|
3808
|
+
`https://${port}-${cid}.computer.miosa.ai`;
|
|
3809
|
+
return ok(`Preview URL: ${url}`);
|
|
589
3810
|
}
|
|
590
|
-
// ──
|
|
591
|
-
if (name === "
|
|
3811
|
+
// ── Network policy ────────────────────────────────────────────────────
|
|
3812
|
+
if (name === "computer_network_policy_get") {
|
|
592
3813
|
if (!cid)
|
|
593
3814
|
return err("computer_id is required");
|
|
594
|
-
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/
|
|
595
|
-
|
|
596
|
-
return ok(`Clipboard content:\n${data["text"] ?? ""}`);
|
|
3815
|
+
const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/network-policy`);
|
|
3816
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
597
3817
|
}
|
|
598
|
-
if (name === "
|
|
3818
|
+
if (name === "computer_network_policy_set") {
|
|
599
3819
|
if (!cid)
|
|
600
3820
|
return err("computer_id is required");
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
3821
|
+
const body = { rules: args["rules"] };
|
|
3822
|
+
if (args["default_effect"])
|
|
3823
|
+
body["default_effect"] = args["default_effect"];
|
|
3824
|
+
await client.apiPut(`/api/v1/computers/${encodeURIComponent(cid)}/network-policy`, body);
|
|
3825
|
+
return ok(`Network policy updated for computer ${cid}.`);
|
|
605
3826
|
}
|
|
606
|
-
|
|
607
|
-
if (name === "computer_windows") {
|
|
3827
|
+
if (name === "computer_network_policy_reset") {
|
|
608
3828
|
if (!cid)
|
|
609
3829
|
return err("computer_id is required");
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
3830
|
+
await client.apiDelete(`/api/v1/computers/${encodeURIComponent(cid)}/network-policy`);
|
|
3831
|
+
return ok(`Network policy reset to default for computer ${cid}.`);
|
|
3832
|
+
}
|
|
3833
|
+
// ── Webhooks ──────────────────────────────────────────────────────────
|
|
3834
|
+
if (name === "webhook_list") {
|
|
3835
|
+
const result = await client.apiGet("/api/v1/webhooks");
|
|
3836
|
+
const items = listOf(result);
|
|
3837
|
+
if (items.length === 0)
|
|
3838
|
+
return ok("No webhooks found.");
|
|
3839
|
+
const lines = ["Webhooks:"];
|
|
3840
|
+
for (const w of items) {
|
|
616
3841
|
const r = w;
|
|
617
|
-
|
|
618
|
-
lines.push(` id=${r["id"]} title=${JSON.stringify(r["title"])} app=${JSON.stringify(r["app"])}` +
|
|
619
|
-
` pos=(${r["x"]},${r["y"]}) size=${r["width"]}x${r["height"]}${focused}`);
|
|
3842
|
+
lines.push(` ${r["id"]} ${r["url"]} events=${JSON.stringify(r["events"] ?? [])}`);
|
|
620
3843
|
}
|
|
621
3844
|
return ok(lines.join("\n"));
|
|
622
3845
|
}
|
|
623
|
-
if (name === "
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
app: args["app"],
|
|
3846
|
+
if (name === "webhook_create") {
|
|
3847
|
+
const result = await client.apiPost("/api/v1/webhooks", {
|
|
3848
|
+
url: args["url"],
|
|
3849
|
+
events: args["events"],
|
|
628
3850
|
});
|
|
629
|
-
|
|
3851
|
+
const data = unwrapData(result);
|
|
3852
|
+
return ok(`Created webhook (id=${data["id"] ?? "?"}).`);
|
|
630
3853
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (!
|
|
634
|
-
return err("
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
3854
|
+
if (name === "webhook_delete") {
|
|
3855
|
+
const whId = String(args["webhook_id"] ?? "");
|
|
3856
|
+
if (!whId)
|
|
3857
|
+
return err("webhook_id is required");
|
|
3858
|
+
await client.apiDelete(`/api/v1/webhooks/${encodeURIComponent(whId)}`);
|
|
3859
|
+
return ok(`Webhook ${whId} deleted.`);
|
|
3860
|
+
}
|
|
3861
|
+
if (name === "webhook_test") {
|
|
3862
|
+
const whId = String(args["webhook_id"] ?? "");
|
|
3863
|
+
if (!whId)
|
|
3864
|
+
return err("webhook_id is required");
|
|
3865
|
+
const result = await client.apiPost(`/api/v1/webhooks/${encodeURIComponent(whId)}/test`, {});
|
|
639
3866
|
const data = unwrapData(result);
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
parts.push(`stdout:\n${data["output"] ?? data["stdout"]}`);
|
|
643
|
-
}
|
|
644
|
-
if (data["stderr"])
|
|
645
|
-
parts.push(`stderr:\n${data["stderr"]}`);
|
|
646
|
-
parts.push(`exit_code: ${data["exit_code"] ?? 0}`);
|
|
647
|
-
return ok(parts.join("\n"));
|
|
3867
|
+
const status = String(data["status"] ?? "delivered");
|
|
3868
|
+
return ok(`Test event sent to webhook ${whId} (status=${status}).`);
|
|
648
3869
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
3870
|
+
// ── Functions ─────────────────────────────────────────────────────────
|
|
3871
|
+
if (name === "function_list") {
|
|
3872
|
+
const result = await client.apiGet("/api/v1/functions");
|
|
3873
|
+
const items = listOf(result);
|
|
3874
|
+
if (items.length === 0)
|
|
3875
|
+
return ok("No functions found.");
|
|
3876
|
+
const lines = ["Functions:"];
|
|
3877
|
+
for (const f of items) {
|
|
3878
|
+
const r = f;
|
|
3879
|
+
lines.push(` ${r["id"]} ${r["name"]} runtime=${r["runtime"] ?? ""}`);
|
|
3880
|
+
}
|
|
3881
|
+
return ok(lines.join("\n"));
|
|
659
3882
|
}
|
|
660
|
-
if (name === "
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
3883
|
+
if (name === "function_create") {
|
|
3884
|
+
const body = {
|
|
3885
|
+
name: args["name"],
|
|
3886
|
+
runtime: args["runtime"],
|
|
3887
|
+
};
|
|
3888
|
+
if (args["code"])
|
|
3889
|
+
body["code"] = args["code"];
|
|
3890
|
+
const result = await client.apiPost("/api/v1/functions", body);
|
|
664
3891
|
const data = unwrapData(result);
|
|
665
|
-
|
|
666
|
-
? Buffer.from(data["content"], "base64").toString("utf8")
|
|
667
|
-
: typeof data["text"] === "string"
|
|
668
|
-
? data["text"]
|
|
669
|
-
: JSON.stringify(data);
|
|
670
|
-
return ok(content);
|
|
3892
|
+
return ok(`Created function '${data["name"] ?? args["name"]}' (id=${data["id"]}).`);
|
|
671
3893
|
}
|
|
672
|
-
|
|
673
|
-
|
|
3894
|
+
if (name === "function_invoke") {
|
|
3895
|
+
const fnId = String(args["function_id"] ?? "");
|
|
3896
|
+
if (!fnId)
|
|
3897
|
+
return err("function_id is required");
|
|
674
3898
|
const body = {};
|
|
3899
|
+
if (args["payload"] !== undefined)
|
|
3900
|
+
body["payload"] = args["payload"];
|
|
3901
|
+
const result = await client.apiPost(`/api/v1/functions/${encodeURIComponent(fnId)}/invoke`, body);
|
|
3902
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
3903
|
+
}
|
|
3904
|
+
if (name === "function_delete") {
|
|
3905
|
+
const fnId = String(args["function_id"] ?? "");
|
|
3906
|
+
if (!fnId)
|
|
3907
|
+
return err("function_id is required");
|
|
3908
|
+
await client.apiDelete(`/api/v1/functions/${encodeURIComponent(fnId)}`);
|
|
3909
|
+
return ok(`Function ${fnId} deleted.`);
|
|
3910
|
+
}
|
|
3911
|
+
// ── API Keys ──────────────────────────────────────────────────────────
|
|
3912
|
+
if (name === "api_key_list") {
|
|
3913
|
+
const result = await client.apiGet("/api/v1/api-keys");
|
|
3914
|
+
const items = listOf(result);
|
|
3915
|
+
if (items.length === 0)
|
|
3916
|
+
return ok("No API keys found.");
|
|
3917
|
+
const lines = ["API keys:"];
|
|
3918
|
+
for (const k of items) {
|
|
3919
|
+
const r = k;
|
|
3920
|
+
const scopes = Array.isArray(r["scopes"])
|
|
3921
|
+
? r["scopes"].join(", ")
|
|
3922
|
+
: String(r["scopes"] ?? "");
|
|
3923
|
+
lines.push(` ${r["id"]} ${r["name"]} scopes=[${scopes}]`);
|
|
3924
|
+
}
|
|
3925
|
+
return ok(lines.join("\n"));
|
|
3926
|
+
}
|
|
3927
|
+
if (name === "api_key_create") {
|
|
3928
|
+
const body = { name: args["name"] };
|
|
3929
|
+
if (args["scopes"])
|
|
3930
|
+
body["scopes"] = args["scopes"];
|
|
3931
|
+
const result = await client.apiPost("/api/v1/api-keys", body);
|
|
3932
|
+
const data = unwrapData(result);
|
|
3933
|
+
const keyId = String(data["id"] ?? "?");
|
|
3934
|
+
const keyValue = String(data["key"] ?? data["token"] ?? data["secret"] ?? "");
|
|
3935
|
+
let msg = `Created API key '${args["name"]}' (id=${keyId}).`;
|
|
3936
|
+
if (keyValue)
|
|
3937
|
+
msg += `\nKey value (shown once): ${keyValue}`;
|
|
3938
|
+
return ok(msg);
|
|
3939
|
+
}
|
|
3940
|
+
if (name === "api_key_delete") {
|
|
3941
|
+
const keyId = String(args["key_id"] ?? "");
|
|
3942
|
+
if (!keyId)
|
|
3943
|
+
return err("key_id is required");
|
|
3944
|
+
await client.apiDelete(`/api/v1/api-keys/${encodeURIComponent(keyId)}`);
|
|
3945
|
+
return ok(`API key ${keyId} deleted.`);
|
|
3946
|
+
}
|
|
3947
|
+
// ── Cron jobs ─────────────────────────────────────────────────────────
|
|
3948
|
+
if (name === "cron_list") {
|
|
3949
|
+
const params = new URLSearchParams();
|
|
3950
|
+
if (args["computer_id"])
|
|
3951
|
+
params.set("computer_id", String(args["computer_id"]));
|
|
3952
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
3953
|
+
const result = await client.apiGet(`/api/v1/cron-jobs${qs}`);
|
|
3954
|
+
const items = listOf(result);
|
|
3955
|
+
if (items.length === 0)
|
|
3956
|
+
return ok("No cron jobs found.");
|
|
3957
|
+
const lines = ["Cron jobs:"];
|
|
3958
|
+
for (const j of items) {
|
|
3959
|
+
const r = j;
|
|
3960
|
+
lines.push(` ${r["id"]} ${r["name"] ?? ""} ${r["schedule"] ?? ""} status=${r["status"] ?? r["state"] ?? ""}`);
|
|
3961
|
+
}
|
|
3962
|
+
return ok(lines.join("\n"));
|
|
3963
|
+
}
|
|
3964
|
+
if (name === "cron_create") {
|
|
3965
|
+
const body = {
|
|
3966
|
+
computer_id: args["computer_id"],
|
|
3967
|
+
schedule: args["schedule"],
|
|
3968
|
+
command: args["command"],
|
|
3969
|
+
};
|
|
675
3970
|
if (args["name"])
|
|
676
3971
|
body["name"] = args["name"];
|
|
677
|
-
|
|
678
|
-
body["template_id"] = args["template_id"];
|
|
679
|
-
if (args["cpu_count"] !== undefined)
|
|
680
|
-
body["cpu_count"] = args["cpu_count"];
|
|
681
|
-
if (args["memory_mb"] !== undefined)
|
|
682
|
-
body["memory_mb"] = args["memory_mb"];
|
|
683
|
-
if (args["timeout_sec"] !== undefined)
|
|
684
|
-
body["timeout_sec"] = args["timeout_sec"];
|
|
685
|
-
const result = await client.apiPost("/api/v1/sandboxes", body);
|
|
3972
|
+
const result = await client.apiPost("/api/v1/cron-jobs", body);
|
|
686
3973
|
const data = unwrapData(result);
|
|
687
|
-
|
|
688
|
-
return ok(`Created sandbox '${data["name"] ?? sid}' (id=${sid}).`);
|
|
3974
|
+
return ok(`Created cron job '${data["name"] ?? args["name"] ?? ""}' (id=${data["id"]}).`);
|
|
689
3975
|
}
|
|
690
|
-
if (name === "
|
|
691
|
-
const
|
|
692
|
-
if (!
|
|
693
|
-
return err("
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
3976
|
+
if (name === "cron_get") {
|
|
3977
|
+
const cronId = String(args["cron_id"] ?? "");
|
|
3978
|
+
if (!cronId)
|
|
3979
|
+
return err("cron_id is required");
|
|
3980
|
+
const result = await client.apiGet(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}`);
|
|
3981
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
3982
|
+
}
|
|
3983
|
+
if (name === "cron_delete") {
|
|
3984
|
+
const cronId = String(args["cron_id"] ?? "");
|
|
3985
|
+
if (!cronId)
|
|
3986
|
+
return err("cron_id is required");
|
|
3987
|
+
await client.apiDelete(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}`);
|
|
3988
|
+
return ok(`Cron job ${cronId} deleted.`);
|
|
3989
|
+
}
|
|
3990
|
+
if (name === "cron_pause") {
|
|
3991
|
+
const cronId = String(args["cron_id"] ?? "");
|
|
3992
|
+
if (!cronId)
|
|
3993
|
+
return err("cron_id is required");
|
|
3994
|
+
await client.apiPost(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}/pause`, {});
|
|
3995
|
+
return ok(`Cron job ${cronId} paused.`);
|
|
3996
|
+
}
|
|
3997
|
+
if (name === "cron_resume") {
|
|
3998
|
+
const cronId = String(args["cron_id"] ?? "");
|
|
3999
|
+
if (!cronId)
|
|
4000
|
+
return err("cron_id is required");
|
|
4001
|
+
await client.apiPost(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}/resume`, {});
|
|
4002
|
+
return ok(`Cron job ${cronId} resumed.`);
|
|
4003
|
+
}
|
|
4004
|
+
if (name === "cron_run_now") {
|
|
4005
|
+
const cronId = String(args["cron_id"] ?? "");
|
|
4006
|
+
if (!cronId)
|
|
4007
|
+
return err("cron_id is required");
|
|
4008
|
+
const result = await client.apiPost(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}/run-now`, {});
|
|
700
4009
|
const data = unwrapData(result);
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
4010
|
+
const execId = data["id"] ?? data["execution_id"];
|
|
4011
|
+
return ok(`Cron job ${cronId} triggered.${execId ? ` Execution id=${execId}.` : ""}`);
|
|
4012
|
+
}
|
|
4013
|
+
if (name === "cron_executions") {
|
|
4014
|
+
const cronId = String(args["cron_id"] ?? "");
|
|
4015
|
+
if (!cronId)
|
|
4016
|
+
return err("cron_id is required");
|
|
4017
|
+
const result = await client.apiGet(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}/executions`);
|
|
4018
|
+
const items = listOf(result);
|
|
4019
|
+
if (items.length === 0)
|
|
4020
|
+
return ok("No executions found.");
|
|
4021
|
+
const lines = ["Executions:"];
|
|
4022
|
+
for (const e of items) {
|
|
4023
|
+
const r = e;
|
|
4024
|
+
lines.push(` ${r["id"]} ${r["started_at"] ?? r["created_at"] ?? ""} status=${r["status"] ?? ""} exit_code=${r["exit_code"] ?? ""}`);
|
|
704
4025
|
}
|
|
705
|
-
|
|
706
|
-
parts.push(`stderr:\n${data["stderr"]}`);
|
|
707
|
-
parts.push(`exit_code: ${data["exit_code"] ?? 0}`);
|
|
708
|
-
return ok(parts.join("\n"));
|
|
4026
|
+
return ok(lines.join("\n"));
|
|
709
4027
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
4028
|
+
// ── Regions ────────────────────────────────────────────────────────────
|
|
4029
|
+
if (name === "region_list" || name === "computer_list_regions") {
|
|
4030
|
+
const result = await client.apiGet("/api/v1/regions");
|
|
4031
|
+
const regions = (() => {
|
|
4032
|
+
const d = unwrapData(result);
|
|
4033
|
+
if (Array.isArray(d))
|
|
4034
|
+
return d;
|
|
4035
|
+
const r = d;
|
|
4036
|
+
for (const key of ["regions", "data", "items"]) {
|
|
4037
|
+
if (Array.isArray(r[key]))
|
|
4038
|
+
return r[key];
|
|
4039
|
+
}
|
|
4040
|
+
return Object.values(r);
|
|
4041
|
+
})();
|
|
4042
|
+
if (regions.length === 0)
|
|
4043
|
+
return ok("No regions found.");
|
|
4044
|
+
const lines = ["Regions:"];
|
|
4045
|
+
for (const region of regions) {
|
|
4046
|
+
const r = region;
|
|
4047
|
+
const gpuTypes = r["gpu_types"] ?? r["gpus"] ?? [];
|
|
4048
|
+
const gpuInfo = Array.isArray(gpuTypes) && gpuTypes.length > 0
|
|
4049
|
+
? ` gpus=${JSON.stringify(gpuTypes)}`
|
|
4050
|
+
: "";
|
|
4051
|
+
lines.push(` ${r["id"] ?? r["slug"] ?? ""} ${r["name"] ?? ""} status=${r["status"] ?? "available"}${gpuInfo}`);
|
|
4052
|
+
}
|
|
4053
|
+
return ok(lines.join("\n"));
|
|
716
4054
|
}
|
|
717
|
-
// ──
|
|
718
|
-
if (name === "
|
|
719
|
-
const
|
|
720
|
-
if (!
|
|
721
|
-
return err("
|
|
722
|
-
const result = await client.
|
|
4055
|
+
// ── Computer templates ─────────────────────────────────────────────────
|
|
4056
|
+
if (name === "computer_template_list") {
|
|
4057
|
+
const wid = String(args["workspace_id"] ?? "");
|
|
4058
|
+
if (!wid)
|
|
4059
|
+
return err("workspace_id is required");
|
|
4060
|
+
const result = await client.apiGet(`/api/v1/workspaces/${encodeURIComponent(wid)}/computer-templates`);
|
|
4061
|
+
const items = listOf(result);
|
|
4062
|
+
if (items.length === 0)
|
|
4063
|
+
return ok("No computer templates found.");
|
|
4064
|
+
const lines = ["Computer templates:"];
|
|
4065
|
+
for (const t of items) {
|
|
4066
|
+
const r = t;
|
|
4067
|
+
lines.push(` ${r["id"]} ${r["name"]} type=${r["template_type"] ?? ""} size=${r["size"] ?? ""}`);
|
|
4068
|
+
}
|
|
4069
|
+
return ok(lines.join("\n"));
|
|
4070
|
+
}
|
|
4071
|
+
if (name === "computer_template_create") {
|
|
4072
|
+
const wid = String(args["workspace_id"] ?? "");
|
|
4073
|
+
if (!wid)
|
|
4074
|
+
return err("workspace_id is required");
|
|
4075
|
+
const body = { name: args["name"] };
|
|
4076
|
+
if (args["template_type"])
|
|
4077
|
+
body["template_type"] = args["template_type"];
|
|
4078
|
+
if (args["size"])
|
|
4079
|
+
body["size"] = args["size"];
|
|
4080
|
+
if (args["selected_apps"])
|
|
4081
|
+
body["selected_apps"] = args["selected_apps"];
|
|
4082
|
+
if (args["settings"])
|
|
4083
|
+
body["settings"] = args["settings"];
|
|
4084
|
+
const result = await client.apiPost(`/api/v1/workspaces/${encodeURIComponent(wid)}/computer-templates`, body);
|
|
723
4085
|
const data = unwrapData(result);
|
|
724
|
-
return ok(`
|
|
4086
|
+
return ok(`Created computer template '${data["name"] ?? args["name"]}' (id=${data["id"]}).`);
|
|
4087
|
+
}
|
|
4088
|
+
// ── Settings ───────────────────────────────────────────────────────────
|
|
4089
|
+
if (name === "settings_get") {
|
|
4090
|
+
const result = await client.apiGet("/api/v1/settings");
|
|
4091
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
4092
|
+
}
|
|
4093
|
+
if (name === "settings_get_branding") {
|
|
4094
|
+
const result = await client.apiGet("/api/v1/settings/branding");
|
|
4095
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
4096
|
+
}
|
|
4097
|
+
if (name === "settings_update_branding") {
|
|
4098
|
+
const body = {};
|
|
4099
|
+
if (args["desktop_wallpaper_url"])
|
|
4100
|
+
body["desktop_wallpaper_url"] = args["desktop_wallpaper_url"];
|
|
4101
|
+
if (args["logo_url"])
|
|
4102
|
+
body["logo_url"] = args["logo_url"];
|
|
4103
|
+
const result = await client.apiPut("/api/v1/settings/branding", body);
|
|
4104
|
+
return ok(`Branding updated: ${JSON.stringify(unwrapData(result), null, 2)}`);
|
|
4105
|
+
}
|
|
4106
|
+
if (name === "settings_compute_pricing") {
|
|
4107
|
+
const result = await client.apiGet("/api/v1/settings/compute-pricing");
|
|
4108
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
4109
|
+
}
|
|
4110
|
+
// ── Sandbox template extensions ────────────────────────────────────────
|
|
4111
|
+
if (name === "sandbox_template_get") {
|
|
4112
|
+
const tid = String(args["template_id"] ?? "");
|
|
4113
|
+
if (!tid)
|
|
4114
|
+
return err("template_id is required");
|
|
4115
|
+
const result = await client.apiGet(`/api/v1/sandbox-templates/${encodeURIComponent(tid)}`);
|
|
4116
|
+
return ok(JSON.stringify(unwrapData(result), null, 2));
|
|
4117
|
+
}
|
|
4118
|
+
if (name === "sandbox_template_builds") {
|
|
4119
|
+
const tid = String(args["template_id"] ?? "");
|
|
4120
|
+
if (!tid)
|
|
4121
|
+
return err("template_id is required");
|
|
4122
|
+
const result = await client.apiGet(`/api/v1/sandbox-templates/${encodeURIComponent(tid)}/builds`);
|
|
4123
|
+
const items = listOf(result);
|
|
4124
|
+
if (items.length === 0)
|
|
4125
|
+
return ok("No builds found.");
|
|
4126
|
+
const lines = ["Builds:"];
|
|
4127
|
+
for (const b of items) {
|
|
4128
|
+
const r = b;
|
|
4129
|
+
lines.push(` ${r["id"]} ${r["status"] ?? ""} created_at=${r["created_at"] ?? ""}`);
|
|
4130
|
+
}
|
|
4131
|
+
return ok(lines.join("\n"));
|
|
4132
|
+
}
|
|
4133
|
+
// ── Volumes ───────────────────────────────────────────────────────────
|
|
4134
|
+
if (name === "volume_list") {
|
|
4135
|
+
const result = await client.apiGet("/api/v1/volumes");
|
|
4136
|
+
const items = listOf(result);
|
|
4137
|
+
if (items.length === 0)
|
|
4138
|
+
return ok("No volumes found.");
|
|
4139
|
+
const lines = ["Volumes:"];
|
|
4140
|
+
for (const v of items) {
|
|
4141
|
+
const r = v;
|
|
4142
|
+
lines.push(` ${r["id"]} ${r["name"]} size_gb=${r["size_gb"] ?? ""} region=${r["region"] ?? ""} status=${r["status"] ?? ""}`);
|
|
4143
|
+
}
|
|
4144
|
+
return ok(lines.join("\n"));
|
|
4145
|
+
}
|
|
4146
|
+
if (name === "volume_create") {
|
|
4147
|
+
const body = { name: args["name"] };
|
|
4148
|
+
if (args["size_gb"] !== undefined)
|
|
4149
|
+
body["size_gb"] = args["size_gb"];
|
|
4150
|
+
if (args["region"])
|
|
4151
|
+
body["region"] = args["region"];
|
|
4152
|
+
const result = await client.apiPost("/api/v1/volumes", body);
|
|
4153
|
+
const data = unwrapData(result);
|
|
4154
|
+
return ok(`Created volume '${data["name"] ?? args["name"]}' (id=${data["id"]}).`);
|
|
4155
|
+
}
|
|
4156
|
+
if (name === "volume_get") {
|
|
4157
|
+
const vid = String(args["volume_id"] ?? "");
|
|
4158
|
+
if (!vid)
|
|
4159
|
+
return err("volume_id is required");
|
|
4160
|
+
const result = await client.apiGet(`/api/v1/volumes/${encodeURIComponent(vid)}`);
|
|
4161
|
+
const data = unwrapData(result);
|
|
4162
|
+
return ok(`id=${data["id"]} name=${JSON.stringify(data["name"])} size_gb=${data["size_gb"] ?? ""} region=${data["region"] ?? ""} status=${data["status"] ?? ""}`);
|
|
4163
|
+
}
|
|
4164
|
+
if (name === "volume_delete") {
|
|
4165
|
+
const vid = String(args["volume_id"] ?? "");
|
|
4166
|
+
if (!vid)
|
|
4167
|
+
return err("volume_id is required");
|
|
4168
|
+
await client.apiDelete(`/api/v1/volumes/${encodeURIComponent(vid)}`);
|
|
4169
|
+
return ok(`Volume ${vid} deleted.`);
|
|
4170
|
+
}
|
|
4171
|
+
if (name === "volume_attach") {
|
|
4172
|
+
const attachCid = String(args["computer_id"] ?? "");
|
|
4173
|
+
if (!attachCid)
|
|
4174
|
+
return err("computer_id is required");
|
|
4175
|
+
const attachBody = {
|
|
4176
|
+
volume_id: args["volume_id"],
|
|
4177
|
+
};
|
|
4178
|
+
if (args["mount_path"])
|
|
4179
|
+
attachBody["mount_path"] = args["mount_path"];
|
|
4180
|
+
const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(attachCid)}/volumes`, attachBody);
|
|
4181
|
+
const data = unwrapData(result);
|
|
4182
|
+
return ok(`Volume ${args["volume_id"]} attached to computer ${attachCid} (attachment id=${data["id"] ?? "?"}).`);
|
|
4183
|
+
}
|
|
4184
|
+
if (name === "volume_detach") {
|
|
4185
|
+
const detachCid = String(args["computer_id"] ?? "");
|
|
4186
|
+
if (!detachCid)
|
|
4187
|
+
return err("computer_id is required");
|
|
4188
|
+
const attId = String(args["attachment_id"] ?? "");
|
|
4189
|
+
if (!attId)
|
|
4190
|
+
return err("attachment_id is required");
|
|
4191
|
+
await client.apiDelete(`/api/v1/computers/${encodeURIComponent(detachCid)}/volumes/${encodeURIComponent(attId)}`);
|
|
4192
|
+
return ok(`Attachment ${attId} removed from computer ${detachCid}.`);
|
|
725
4193
|
}
|
|
726
4194
|
return err(`Unknown tool: ${name}`);
|
|
727
4195
|
}
|