@miosa/cli 1.0.7 → 1.0.8

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.
@@ -22,6 +22,7 @@ import { request } from "undici";
22
22
  import chalk from "chalk";
23
23
  import { loadConfig } from "../config.js";
24
24
  import { MiosaClient } from "../client.js";
25
+ import { banner, errorEnvelope, hintBlock, icon, kvPanel, printElapsed, formatDuration, } from "../ui/render.js";
25
26
  // ── Tool definitions — mirror the Python MCP server exactly ─────────────────
26
27
  const TOOL_LIST = [
27
28
  // Lifecycle
@@ -58,6 +59,15 @@ const TOOL_LIST = [
58
59
  type: "string",
59
60
  description: "Your internal project ID for attribution (optional)",
60
61
  },
62
+ gpu_model: {
63
+ type: "string",
64
+ description: "GPU model to attach (e.g. 'nvidia-a10g', 'nvidia-t4'). Omit for CPU-only.",
65
+ },
66
+ gpu_count: {
67
+ type: "integer",
68
+ description: "Number of GPUs to attach (default: 1 when gpu_model is set).",
69
+ default: 1,
70
+ },
61
71
  },
62
72
  required: ["name"],
63
73
  },
@@ -1307,368 +1317,971 @@ const TOOL_LIST = [
1307
1317
  },
1308
1318
  // Deployments
1309
1319
  {
1310
- name: 'deployment_list',
1311
- description: 'List all deployments in the tenant.',
1312
- inputSchema: { type: 'object', properties: {} },
1320
+ name: "deployment_list",
1321
+ description: "List all deployments in the tenant.",
1322
+ inputSchema: { type: "object", properties: {} },
1323
+ },
1324
+ {
1325
+ name: "deployment_get",
1326
+ description: "Get details of a specific deployment.",
1327
+ inputSchema: {
1328
+ type: "object",
1329
+ properties: {
1330
+ deployment_id: { type: "string", description: "Deployment ID" },
1331
+ },
1332
+ required: ["deployment_id"],
1333
+ },
1313
1334
  },
1314
1335
  {
1315
- name: 'deployment_get',
1316
- description: 'Get details of a specific deployment.',
1336
+ name: "deployment_create",
1337
+ description: "Create a new deployment.",
1317
1338
  inputSchema: {
1318
- type: 'object',
1339
+ type: "object",
1319
1340
  properties: {
1320
- deployment_id: { type: 'string', description: 'Deployment ID' },
1341
+ name: { type: "string", description: "Deployment name" },
1342
+ type: {
1343
+ type: "string",
1344
+ description: "Deployment type (e.g. web, worker)",
1345
+ },
1346
+ source: { type: "object", description: "Source configuration" },
1347
+ env_vars: {
1348
+ type: "object",
1349
+ description: "Environment variables as key-value pairs",
1350
+ },
1351
+ region: { type: "string", description: "Deployment region (optional)" },
1321
1352
  },
1322
- required: ['deployment_id'],
1353
+ required: ["name"],
1323
1354
  },
1324
1355
  },
1325
1356
  {
1326
- name: 'deployment_create',
1327
- description: 'Create a new deployment.',
1357
+ name: "deployment_delete",
1358
+ description: "Delete a deployment permanently.",
1328
1359
  inputSchema: {
1329
- type: 'object',
1360
+ type: "object",
1330
1361
  properties: {
1331
- name: { type: 'string', description: 'Deployment name' },
1332
- type: { type: 'string', description: 'Deployment type (e.g. web, worker)' },
1333
- source: { type: 'object', description: 'Source configuration' },
1334
- env_vars: { type: 'object', description: 'Environment variables as key-value pairs' },
1335
- region: { type: 'string', description: 'Deployment region (optional)' },
1362
+ deployment_id: {
1363
+ type: "string",
1364
+ description: "Deployment ID to delete",
1365
+ },
1336
1366
  },
1337
- required: ['name'],
1367
+ required: ["deployment_id"],
1338
1368
  },
1339
1369
  },
1340
1370
  {
1341
- name: 'deployment_delete',
1342
- description: 'Delete a deployment permanently.',
1371
+ name: "deployment_publish",
1372
+ description: "Publish a new version of a deployment.",
1343
1373
  inputSchema: {
1344
- type: 'object',
1374
+ type: "object",
1345
1375
  properties: {
1346
- deployment_id: { type: 'string', description: 'Deployment ID to delete' },
1376
+ deployment_id: { type: "string", description: "Deployment ID" },
1377
+ source: {
1378
+ type: "object",
1379
+ description: "Source configuration for the new version",
1380
+ },
1347
1381
  },
1348
- required: ['deployment_id'],
1382
+ required: ["deployment_id"],
1349
1383
  },
1350
1384
  },
1351
1385
  {
1352
- name: 'deployment_publish',
1353
- description: 'Publish a new version of a deployment.',
1386
+ name: "deployment_rollback",
1387
+ description: "Rollback a deployment to a previous version.",
1354
1388
  inputSchema: {
1355
- type: 'object',
1389
+ type: "object",
1356
1390
  properties: {
1357
- deployment_id: { type: 'string', description: 'Deployment ID' },
1358
- source: { type: 'object', description: 'Source configuration for the new version' },
1391
+ deployment_id: { type: "string", description: "Deployment ID" },
1392
+ version_id: {
1393
+ type: "string",
1394
+ description: "Version ID to roll back to",
1395
+ },
1359
1396
  },
1360
- required: ['deployment_id'],
1397
+ required: ["deployment_id", "version_id"],
1361
1398
  },
1362
1399
  },
1363
1400
  {
1364
- name: 'deployment_rollback',
1365
- description: 'Rollback a deployment to a previous version.',
1401
+ name: "deployment_env_list",
1402
+ description: "List environment variables for a deployment.",
1366
1403
  inputSchema: {
1367
- type: 'object',
1404
+ type: "object",
1368
1405
  properties: {
1369
- deployment_id: { type: 'string', description: 'Deployment ID' },
1370
- version_id: { type: 'string', description: 'Version ID to roll back to' },
1406
+ deployment_id: { type: "string", description: "Deployment ID" },
1371
1407
  },
1372
- required: ['deployment_id', 'version_id'],
1408
+ required: ["deployment_id"],
1373
1409
  },
1374
1410
  },
1375
1411
  {
1376
- name: 'deployment_env_list',
1377
- description: 'List environment variables for a deployment.',
1412
+ name: "deployment_env_set",
1413
+ description: "Set (create or update) an environment variable for a deployment.",
1378
1414
  inputSchema: {
1379
- type: 'object',
1415
+ type: "object",
1380
1416
  properties: {
1381
- deployment_id: { type: 'string', description: 'Deployment ID' },
1417
+ deployment_id: { type: "string", description: "Deployment ID" },
1418
+ key: { type: "string", description: "Environment variable name" },
1419
+ value: { type: "string", description: "Environment variable value" },
1382
1420
  },
1383
- required: ['deployment_id'],
1421
+ required: ["deployment_id", "key", "value"],
1384
1422
  },
1385
1423
  },
1386
1424
  {
1387
- name: 'deployment_env_set',
1388
- description: 'Set (create or update) an environment variable for a deployment.',
1425
+ name: "deployment_logs",
1426
+ description: "Get logs for a deployment.",
1389
1427
  inputSchema: {
1390
- type: 'object',
1428
+ type: "object",
1391
1429
  properties: {
1392
- deployment_id: { type: 'string', description: 'Deployment ID' },
1393
- key: { type: 'string', description: 'Environment variable name' },
1394
- value: { type: 'string', description: 'Environment variable value' },
1430
+ deployment_id: { type: "string", description: "Deployment ID" },
1431
+ lines: {
1432
+ type: "integer",
1433
+ description: "Number of log lines to return (default: 100)",
1434
+ default: 100,
1435
+ },
1436
+ since: {
1437
+ type: "string",
1438
+ description: "ISO 8601 timestamp to fetch logs from (optional)",
1439
+ },
1395
1440
  },
1396
- required: ['deployment_id', 'key', 'value'],
1441
+ required: ["deployment_id"],
1397
1442
  },
1398
1443
  },
1399
1444
  {
1400
- name: 'deployment_logs',
1401
- description: 'Get logs for a deployment.',
1445
+ name: "deployment_version_list",
1446
+ description: "List all versions of a deployment.",
1402
1447
  inputSchema: {
1403
- type: 'object',
1448
+ type: "object",
1404
1449
  properties: {
1405
- deployment_id: { type: 'string', description: 'Deployment ID' },
1406
- lines: { type: 'integer', description: 'Number of log lines to return (default: 100)', default: 100 },
1407
- since: { type: 'string', description: 'ISO 8601 timestamp to fetch logs from (optional)' },
1450
+ deployment_id: { type: "string", description: "Deployment ID" },
1451
+ },
1452
+ required: ["deployment_id"],
1453
+ },
1454
+ },
1455
+ {
1456
+ name: "deployment_version_promote",
1457
+ description: "Promote a specific version to be the active deployment.",
1458
+ inputSchema: {
1459
+ type: "object",
1460
+ properties: {
1461
+ deployment_id: { type: "string", description: "Deployment ID" },
1462
+ version_id: { type: "string", description: "Version ID to promote" },
1463
+ },
1464
+ required: ["deployment_id", "version_id"],
1465
+ },
1466
+ },
1467
+ // Storage
1468
+ {
1469
+ name: "storage_bucket_list",
1470
+ description: "List all storage buckets in the tenant.",
1471
+ inputSchema: { type: "object", properties: {} },
1472
+ },
1473
+ {
1474
+ name: "storage_bucket_create",
1475
+ description: "Create a new storage bucket.",
1476
+ inputSchema: {
1477
+ type: "object",
1478
+ properties: {
1479
+ name: { type: "string", description: "Bucket name" },
1480
+ region: { type: "string", description: "Bucket region (optional)" },
1481
+ public: {
1482
+ type: "boolean",
1483
+ description: "Whether the bucket is publicly readable (default: false)",
1484
+ default: false,
1485
+ },
1486
+ },
1487
+ required: ["name"],
1488
+ },
1489
+ },
1490
+ {
1491
+ name: "storage_bucket_delete",
1492
+ description: "Delete a storage bucket.",
1493
+ inputSchema: {
1494
+ type: "object",
1495
+ properties: {
1496
+ bucket_id: {
1497
+ type: "string",
1498
+ description: "Bucket ID or name to delete",
1499
+ },
1500
+ },
1501
+ required: ["bucket_id"],
1502
+ },
1503
+ },
1504
+ {
1505
+ name: "storage_object_list",
1506
+ description: "List objects in a storage bucket, optionally filtered by prefix.",
1507
+ inputSchema: {
1508
+ type: "object",
1509
+ properties: {
1510
+ bucket_id: { type: "string", description: "Bucket ID or name" },
1511
+ prefix: {
1512
+ type: "string",
1513
+ description: "Key prefix to filter by (optional)",
1514
+ },
1515
+ },
1516
+ required: ["bucket_id"],
1517
+ },
1518
+ },
1519
+ {
1520
+ name: "storage_object_upload",
1521
+ description: "Upload an object to a storage bucket.",
1522
+ inputSchema: {
1523
+ type: "object",
1524
+ properties: {
1525
+ bucket_id: { type: "string", description: "Bucket ID or name" },
1526
+ key: { type: "string", description: "Object key (path within bucket)" },
1527
+ content: {
1528
+ type: "string",
1529
+ description: "Object content (text or base64-encoded binary)",
1530
+ },
1531
+ content_type: {
1532
+ type: "string",
1533
+ description: "MIME type of the object (optional)",
1534
+ },
1535
+ },
1536
+ required: ["bucket_id", "key", "content"],
1537
+ },
1538
+ },
1539
+ {
1540
+ name: "storage_object_download",
1541
+ description: "Download an object from a storage bucket.",
1542
+ inputSchema: {
1543
+ type: "object",
1544
+ properties: {
1545
+ bucket_id: { type: "string", description: "Bucket ID or name" },
1546
+ key: { type: "string", description: "Object key to download" },
1547
+ },
1548
+ required: ["bucket_id", "key"],
1549
+ },
1550
+ },
1551
+ {
1552
+ name: "storage_object_delete",
1553
+ description: "Delete an object from a storage bucket.",
1554
+ inputSchema: {
1555
+ type: "object",
1556
+ properties: {
1557
+ bucket_id: { type: "string", description: "Bucket ID or name" },
1558
+ key: { type: "string", description: "Object key to delete" },
1559
+ },
1560
+ required: ["bucket_id", "key"],
1561
+ },
1562
+ },
1563
+ {
1564
+ name: "storage_presign",
1565
+ description: "Get a presigned URL for temporary access to a storage object.",
1566
+ inputSchema: {
1567
+ type: "object",
1568
+ properties: {
1569
+ bucket_id: { type: "string", description: "Bucket ID or name" },
1570
+ key: { type: "string", description: "Object key" },
1571
+ expires_in: {
1572
+ type: "integer",
1573
+ description: "URL expiry in seconds (default: 3600)",
1574
+ default: 3600,
1575
+ },
1576
+ method: {
1577
+ type: "string",
1578
+ enum: ["GET", "PUT"],
1579
+ description: "HTTP method (default: GET)",
1580
+ default: "GET",
1581
+ },
1582
+ },
1583
+ required: ["bucket_id", "key"],
1584
+ },
1585
+ },
1586
+ // Databases
1587
+ {
1588
+ name: "database_list",
1589
+ description: "List all managed databases in the tenant.",
1590
+ inputSchema: { type: "object", properties: {} },
1591
+ },
1592
+ {
1593
+ name: "database_create",
1594
+ description: "Create a new managed database.",
1595
+ inputSchema: {
1596
+ type: "object",
1597
+ properties: {
1598
+ name: { type: "string", description: "Database name" },
1599
+ engine: {
1600
+ type: "string",
1601
+ enum: ["postgres", "mysql", "redis"],
1602
+ description: "Database engine",
1603
+ },
1604
+ version: { type: "string", description: "Engine version (optional)" },
1605
+ size: { type: "string", description: "Database size/tier (optional)" },
1606
+ region: { type: "string", description: "Region (optional)" },
1607
+ },
1608
+ required: ["name", "engine"],
1609
+ },
1610
+ },
1611
+ {
1612
+ name: "database_get",
1613
+ description: "Get details of a specific database.",
1614
+ inputSchema: {
1615
+ type: "object",
1616
+ properties: {
1617
+ database_id: { type: "string", description: "Database ID" },
1618
+ },
1619
+ required: ["database_id"],
1620
+ },
1621
+ },
1622
+ {
1623
+ name: "database_delete",
1624
+ description: "Delete a managed database permanently.",
1625
+ inputSchema: {
1626
+ type: "object",
1627
+ properties: {
1628
+ database_id: { type: "string", description: "Database ID to delete" },
1629
+ },
1630
+ required: ["database_id"],
1631
+ },
1632
+ },
1633
+ {
1634
+ name: "database_credentials",
1635
+ description: "Get the connection string and credentials for a database.",
1636
+ inputSchema: {
1637
+ type: "object",
1638
+ properties: {
1639
+ database_id: { type: "string", description: "Database ID" },
1640
+ },
1641
+ required: ["database_id"],
1642
+ },
1643
+ },
1644
+ {
1645
+ name: "database_logs",
1646
+ description: "Get logs for a managed database.",
1647
+ inputSchema: {
1648
+ type: "object",
1649
+ properties: {
1650
+ database_id: { type: "string", description: "Database ID" },
1651
+ lines: {
1652
+ type: "integer",
1653
+ description: "Number of log lines to return (default: 100)",
1654
+ default: 100,
1655
+ },
1656
+ since: {
1657
+ type: "string",
1658
+ description: "ISO 8601 timestamp to fetch logs from (optional)",
1659
+ },
1660
+ },
1661
+ required: ["database_id"],
1662
+ },
1663
+ },
1664
+ // Workspaces
1665
+ {
1666
+ name: "workspace_list",
1667
+ description: "List all workspaces in the tenant.",
1668
+ inputSchema: { type: "object", properties: {} },
1669
+ },
1670
+ {
1671
+ name: "workspace_create",
1672
+ description: "Create a new workspace.",
1673
+ inputSchema: {
1674
+ type: "object",
1675
+ properties: {
1676
+ name: { type: "string", description: "Workspace name" },
1677
+ description: {
1678
+ type: "string",
1679
+ description: "Workspace description (optional)",
1680
+ },
1681
+ },
1682
+ required: ["name"],
1683
+ },
1684
+ },
1685
+ {
1686
+ name: "workspace_get",
1687
+ description: "Get details of a specific workspace.",
1688
+ inputSchema: {
1689
+ type: "object",
1690
+ properties: {
1691
+ workspace_id: { type: "string", description: "Workspace ID" },
1692
+ },
1693
+ required: ["workspace_id"],
1694
+ },
1695
+ },
1696
+ {
1697
+ name: "workspace_update",
1698
+ description: "Update a workspace's name or description.",
1699
+ inputSchema: {
1700
+ type: "object",
1701
+ properties: {
1702
+ workspace_id: { type: "string", description: "Workspace ID" },
1703
+ name: { type: "string", description: "New workspace name (optional)" },
1704
+ description: {
1705
+ type: "string",
1706
+ description: "New description (optional)",
1707
+ },
1708
+ },
1709
+ required: ["workspace_id"],
1710
+ },
1711
+ },
1712
+ {
1713
+ name: "workspace_stats",
1714
+ description: "Get resource statistics for a workspace (computers, sandboxes, databases, etc.).",
1715
+ inputSchema: {
1716
+ type: "object",
1717
+ properties: {
1718
+ workspace_id: { type: "string", description: "Workspace ID" },
1719
+ },
1720
+ required: ["workspace_id"],
1721
+ },
1722
+ },
1723
+ {
1724
+ name: "workspace_usage",
1725
+ description: "Get usage data (compute hours, storage, bandwidth) for a workspace.",
1726
+ inputSchema: {
1727
+ type: "object",
1728
+ properties: {
1729
+ workspace_id: { type: "string", description: "Workspace ID" },
1730
+ period: {
1731
+ type: "string",
1732
+ description: "Billing period (e.g. '2026-05'). Defaults to current month.",
1733
+ },
1734
+ },
1735
+ required: ["workspace_id"],
1736
+ },
1737
+ },
1738
+ // Billing
1739
+ {
1740
+ name: "billing_usage",
1741
+ description: "Get current billing period usage for the tenant.",
1742
+ inputSchema: { type: "object", properties: {} },
1743
+ },
1744
+ {
1745
+ name: "billing_plan",
1746
+ description: "Get the current billing plan details for the tenant.",
1747
+ inputSchema: { type: "object", properties: {} },
1748
+ },
1749
+ // Tunnels / Port forwarding
1750
+ {
1751
+ name: "computer_expose_port",
1752
+ description: "Expose a port on the computer and return the public URL. The URL follows the pattern https://{port}-{slug}.computer.miosa.ai.",
1753
+ inputSchema: {
1754
+ type: "object",
1755
+ properties: {
1756
+ computer_id: { type: "string", description: "Computer ID." },
1757
+ port: { type: "integer", description: "Port number to expose" },
1758
+ protocol: {
1759
+ type: "string",
1760
+ enum: ["http", "https", "tcp"],
1761
+ description: "Protocol (default: http)",
1762
+ },
1763
+ },
1764
+ required: ["computer_id", "port"],
1765
+ },
1766
+ },
1767
+ {
1768
+ name: "computer_list_ports",
1769
+ description: "List all currently exposed ports on the computer.",
1770
+ inputSchema: {
1771
+ type: "object",
1772
+ properties: {
1773
+ computer_id: { type: "string", description: "Computer ID." },
1774
+ },
1775
+ required: ["computer_id"],
1776
+ },
1777
+ },
1778
+ {
1779
+ name: "computer_preview_url",
1780
+ description: "Return the public preview URL for a given port on the computer. Format: https://{port}-{slug}.computer.miosa.ai",
1781
+ inputSchema: {
1782
+ type: "object",
1783
+ properties: {
1784
+ computer_id: { type: "string", description: "Computer ID." },
1785
+ port: { type: "integer", description: "Port number" },
1786
+ },
1787
+ required: ["computer_id", "port"],
1788
+ },
1789
+ },
1790
+ // Network policy
1791
+ {
1792
+ name: "computer_network_policy_get",
1793
+ description: "Get the current network policy (firewall rules) for the computer.",
1794
+ inputSchema: {
1795
+ type: "object",
1796
+ properties: {
1797
+ computer_id: { type: "string", description: "Computer ID." },
1798
+ },
1799
+ required: ["computer_id"],
1800
+ },
1801
+ },
1802
+ {
1803
+ name: "computer_network_policy_set",
1804
+ description: "Set the network policy (firewall rules) for the computer.",
1805
+ inputSchema: {
1806
+ type: "object",
1807
+ properties: {
1808
+ computer_id: { type: "string", description: "Computer ID." },
1809
+ rules: {
1810
+ type: "array",
1811
+ items: { type: "object" },
1812
+ description: "List of firewall rule objects (e.g. {direction, protocol, port, action})",
1813
+ },
1814
+ default_effect: {
1815
+ type: "string",
1816
+ enum: ["allow", "deny"],
1817
+ description: "Default action when no rule matches (default: allow)",
1818
+ },
1819
+ },
1820
+ required: ["computer_id", "rules"],
1821
+ },
1822
+ },
1823
+ {
1824
+ name: "computer_network_policy_reset",
1825
+ description: "Reset the network policy for the computer to the platform default (allow all).",
1826
+ inputSchema: {
1827
+ type: "object",
1828
+ properties: {
1829
+ computer_id: { type: "string", description: "Computer ID." },
1830
+ },
1831
+ required: ["computer_id"],
1832
+ },
1833
+ },
1834
+ // Webhooks
1835
+ {
1836
+ name: "webhook_list",
1837
+ description: "List all webhooks registered in the tenant.",
1838
+ inputSchema: { type: "object", properties: {} },
1839
+ },
1840
+ {
1841
+ name: "webhook_create",
1842
+ description: "Create a new webhook endpoint.",
1843
+ inputSchema: {
1844
+ type: "object",
1845
+ properties: {
1846
+ url: {
1847
+ type: "string",
1848
+ description: "HTTPS URL to deliver webhook events to",
1849
+ },
1850
+ events: {
1851
+ type: "array",
1852
+ items: { type: "string" },
1853
+ description: "List of event types to subscribe to (e.g. ['computer.started', 'computer.stopped'])",
1854
+ },
1855
+ },
1856
+ required: ["url", "events"],
1857
+ },
1858
+ },
1859
+ {
1860
+ name: "webhook_delete",
1861
+ description: "Delete a webhook.",
1862
+ inputSchema: {
1863
+ type: "object",
1864
+ properties: {
1865
+ webhook_id: { type: "string", description: "Webhook ID to delete" },
1866
+ },
1867
+ required: ["webhook_id"],
1868
+ },
1869
+ },
1870
+ {
1871
+ name: "webhook_test",
1872
+ description: "Send a test event delivery to a webhook endpoint.",
1873
+ inputSchema: {
1874
+ type: "object",
1875
+ properties: {
1876
+ webhook_id: { type: "string", description: "Webhook ID to test" },
1877
+ },
1878
+ required: ["webhook_id"],
1879
+ },
1880
+ },
1881
+ // Functions
1882
+ {
1883
+ name: "function_list",
1884
+ description: "List all serverless functions in the tenant.",
1885
+ inputSchema: { type: "object", properties: {} },
1886
+ },
1887
+ {
1888
+ name: "function_create",
1889
+ description: "Create a new serverless function.",
1890
+ inputSchema: {
1891
+ type: "object",
1892
+ properties: {
1893
+ name: { type: "string", description: "Function name" },
1894
+ runtime: {
1895
+ type: "string",
1896
+ description: "Runtime identifier (e.g. 'node20', 'python311', 'go122')",
1897
+ },
1898
+ code: {
1899
+ type: "string",
1900
+ description: "Inline function source code (optional)",
1901
+ },
1902
+ },
1903
+ required: ["name", "runtime"],
1904
+ },
1905
+ },
1906
+ {
1907
+ name: "function_invoke",
1908
+ description: "Invoke a serverless function and return its response.",
1909
+ inputSchema: {
1910
+ type: "object",
1911
+ properties: {
1912
+ function_id: { type: "string", description: "Function ID to invoke" },
1913
+ payload: {
1914
+ type: "object",
1915
+ description: "JSON payload to pass to the function (optional)",
1916
+ },
1917
+ },
1918
+ required: ["function_id"],
1919
+ },
1920
+ },
1921
+ {
1922
+ name: "function_delete",
1923
+ description: "Delete a serverless function permanently.",
1924
+ inputSchema: {
1925
+ type: "object",
1926
+ properties: {
1927
+ function_id: { type: "string", description: "Function ID to delete" },
1928
+ },
1929
+ required: ["function_id"],
1930
+ },
1931
+ },
1932
+ // API Keys
1933
+ {
1934
+ name: "api_key_list",
1935
+ description: "List all API keys for the tenant.",
1936
+ inputSchema: { type: "object", properties: {} },
1937
+ },
1938
+ {
1939
+ name: "api_key_create",
1940
+ description: "Create a new API key.",
1941
+ inputSchema: {
1942
+ type: "object",
1943
+ properties: {
1944
+ name: {
1945
+ type: "string",
1946
+ description: "Human-readable label for the key",
1947
+ },
1948
+ scopes: {
1949
+ type: "array",
1950
+ items: { type: "string" },
1951
+ description: "Permission scopes for the key (optional; defaults to full access)",
1952
+ },
1953
+ },
1954
+ required: ["name"],
1955
+ },
1956
+ },
1957
+ {
1958
+ name: "api_key_delete",
1959
+ description: "Revoke and delete an API key.",
1960
+ inputSchema: {
1961
+ type: "object",
1962
+ properties: {
1963
+ key_id: { type: "string", description: "API key ID to delete" },
1964
+ },
1965
+ required: ["key_id"],
1966
+ },
1967
+ },
1968
+ // Regions
1969
+ {
1970
+ name: "region_list",
1971
+ description: "List available regions and their GPU availability.",
1972
+ inputSchema: { type: "object", properties: {} },
1973
+ },
1974
+ {
1975
+ name: "computer_list_regions",
1976
+ description: "List available compute regions with GPU availability details.",
1977
+ inputSchema: { type: "object", properties: {} },
1978
+ },
1979
+ // Computer templates
1980
+ {
1981
+ name: "computer_template_list",
1982
+ description: "List computer templates available in a workspace.",
1983
+ inputSchema: {
1984
+ type: "object",
1985
+ properties: {
1986
+ workspace_id: {
1987
+ type: "string",
1988
+ description: "Workspace ID to list templates for",
1989
+ },
1408
1990
  },
1409
- required: ['deployment_id'],
1991
+ required: ["workspace_id"],
1410
1992
  },
1411
1993
  },
1412
1994
  {
1413
- name: 'deployment_version_list',
1414
- description: 'List all versions of a deployment.',
1995
+ name: "computer_template_create",
1996
+ description: "Create a new computer template in a workspace.",
1415
1997
  inputSchema: {
1416
- type: 'object',
1998
+ type: "object",
1417
1999
  properties: {
1418
- deployment_id: { type: 'string', description: 'Deployment ID' },
2000
+ workspace_id: {
2001
+ type: "string",
2002
+ description: "Workspace ID to create the template in",
2003
+ },
2004
+ name: {
2005
+ type: "string",
2006
+ description: "Human-readable name for the template",
2007
+ },
2008
+ template_type: {
2009
+ type: "string",
2010
+ description: "Base template type (e.g. miosa-desktop)",
2011
+ },
2012
+ size: {
2013
+ type: "string",
2014
+ enum: ["small", "medium", "large", "xl"],
2015
+ description: "VM size for the template",
2016
+ },
2017
+ selected_apps: {
2018
+ type: "array",
2019
+ items: { type: "string" },
2020
+ description: "List of app identifiers to pre-install",
2021
+ },
2022
+ settings: {
2023
+ type: "object",
2024
+ description: "Additional template settings (key-value pairs)",
2025
+ },
1419
2026
  },
1420
- required: ['deployment_id'],
2027
+ required: ["workspace_id", "name"],
1421
2028
  },
1422
2029
  },
2030
+ // Settings
1423
2031
  {
1424
- name: 'deployment_version_promote',
1425
- description: 'Promote a specific version to be the active deployment.',
1426
- inputSchema: {
1427
- type: 'object',
1428
- properties: {
1429
- deployment_id: { type: 'string', description: 'Deployment ID' },
1430
- version_id: { type: 'string', description: 'Version ID to promote' },
1431
- },
1432
- required: ['deployment_id', 'version_id'],
1433
- },
2032
+ name: "settings_get",
2033
+ description: "Get all tenant-level settings.",
2034
+ inputSchema: { type: "object", properties: {} },
1434
2035
  },
1435
- // Storage
1436
2036
  {
1437
- name: 'storage_bucket_list',
1438
- description: 'List all storage buckets in the tenant.',
1439
- inputSchema: { type: 'object', properties: {} },
2037
+ name: "settings_get_branding",
2038
+ description: "Get tenant branding settings (logo, wallpaper, colours).",
2039
+ inputSchema: { type: "object", properties: {} },
1440
2040
  },
1441
2041
  {
1442
- name: 'storage_bucket_create',
1443
- description: 'Create a new storage bucket.',
2042
+ name: "settings_update_branding",
2043
+ description: "Update tenant branding settings.",
1444
2044
  inputSchema: {
1445
- type: 'object',
2045
+ type: "object",
1446
2046
  properties: {
1447
- name: { type: 'string', description: 'Bucket name' },
1448
- region: { type: 'string', description: 'Bucket region (optional)' },
1449
- public: { type: 'boolean', description: 'Whether the bucket is publicly readable (default: false)', default: false },
2047
+ desktop_wallpaper_url: {
2048
+ type: "string",
2049
+ description: "HTTPS URL for the default desktop wallpaper (optional)",
2050
+ },
2051
+ logo_url: {
2052
+ type: "string",
2053
+ description: "HTTPS URL for the tenant logo (optional)",
2054
+ },
1450
2055
  },
1451
- required: ['name'],
1452
2056
  },
1453
2057
  },
1454
2058
  {
1455
- name: 'storage_bucket_delete',
1456
- description: 'Delete a storage bucket.',
1457
- inputSchema: {
1458
- type: 'object',
1459
- properties: {
1460
- bucket_id: { type: 'string', description: 'Bucket ID or name to delete' },
1461
- },
1462
- required: ['bucket_id'],
1463
- },
2059
+ name: "settings_compute_pricing",
2060
+ description: "Get compute pricing information for available sizes and GPU types.",
2061
+ inputSchema: { type: "object", properties: {} },
1464
2062
  },
2063
+ // Sandbox template extensions
1465
2064
  {
1466
- name: 'storage_object_list',
1467
- description: 'List objects in a storage bucket, optionally filtered by prefix.',
2065
+ name: "sandbox_template_get",
2066
+ description: "Get details of a specific sandbox template.",
1468
2067
  inputSchema: {
1469
- type: 'object',
2068
+ type: "object",
1470
2069
  properties: {
1471
- bucket_id: { type: 'string', description: 'Bucket ID or name' },
1472
- prefix: { type: 'string', description: 'Key prefix to filter by (optional)' },
2070
+ template_id: {
2071
+ type: "string",
2072
+ description: "Sandbox template ID or slug",
2073
+ },
1473
2074
  },
1474
- required: ['bucket_id'],
2075
+ required: ["template_id"],
1475
2076
  },
1476
2077
  },
1477
2078
  {
1478
- name: 'storage_object_upload',
1479
- description: 'Upload an object to a storage bucket.',
2079
+ name: "sandbox_template_builds",
2080
+ description: "List builds for a sandbox template.",
1480
2081
  inputSchema: {
1481
- type: 'object',
2082
+ type: "object",
1482
2083
  properties: {
1483
- bucket_id: { type: 'string', description: 'Bucket ID or name' },
1484
- key: { type: 'string', description: 'Object key (path within bucket)' },
1485
- content: { type: 'string', description: 'Object content (text or base64-encoded binary)' },
1486
- content_type: { type: 'string', description: 'MIME type of the object (optional)' },
2084
+ template_id: {
2085
+ type: "string",
2086
+ description: "Sandbox template ID or slug",
2087
+ },
1487
2088
  },
1488
- required: ['bucket_id', 'key', 'content'],
2089
+ required: ["template_id"],
1489
2090
  },
1490
2091
  },
2092
+ // Cron jobs
1491
2093
  {
1492
- name: 'storage_object_download',
1493
- description: 'Download an object from a storage bucket.',
2094
+ name: "cron_list",
2095
+ description: "List all cron jobs in the tenant, optionally filtered by computer.",
1494
2096
  inputSchema: {
1495
- type: 'object',
2097
+ type: "object",
1496
2098
  properties: {
1497
- bucket_id: { type: 'string', description: 'Bucket ID or name' },
1498
- key: { type: 'string', description: 'Object key to download' },
2099
+ computer_id: {
2100
+ type: "string",
2101
+ description: "Filter cron jobs by computer ID (optional)",
2102
+ },
1499
2103
  },
1500
- required: ['bucket_id', 'key'],
1501
2104
  },
1502
2105
  },
1503
2106
  {
1504
- name: 'storage_object_delete',
1505
- description: 'Delete an object from a storage bucket.',
2107
+ name: "cron_create",
2108
+ description: "Create a new cron job that runs a command on a schedule.",
1506
2109
  inputSchema: {
1507
- type: 'object',
2110
+ type: "object",
1508
2111
  properties: {
1509
- bucket_id: { type: 'string', description: 'Bucket ID or name' },
1510
- key: { type: 'string', description: 'Object key to delete' },
2112
+ computer_id: {
2113
+ type: "string",
2114
+ description: "ID of the computer to run the cron job on",
2115
+ },
2116
+ schedule: {
2117
+ type: "string",
2118
+ description: "Cron schedule expression (e.g. '0 * * * *' for hourly)",
2119
+ },
2120
+ command: {
2121
+ type: "string",
2122
+ description: "Shell command to execute",
2123
+ },
2124
+ name: {
2125
+ type: "string",
2126
+ description: "Human-readable name for the cron job (optional)",
2127
+ },
1511
2128
  },
1512
- required: ['bucket_id', 'key'],
2129
+ required: ["computer_id", "schedule", "command"],
1513
2130
  },
1514
2131
  },
1515
2132
  {
1516
- name: 'storage_presign',
1517
- description: 'Get a presigned URL for temporary access to a storage object.',
2133
+ name: "cron_get",
2134
+ description: "Get details of a specific cron job.",
1518
2135
  inputSchema: {
1519
- type: 'object',
2136
+ type: "object",
1520
2137
  properties: {
1521
- bucket_id: { type: 'string', description: 'Bucket ID or name' },
1522
- key: { type: 'string', description: 'Object key' },
1523
- expires_in: { type: 'integer', description: 'URL expiry in seconds (default: 3600)', default: 3600 },
1524
- method: { type: 'string', enum: ['GET', 'PUT'], description: 'HTTP method (default: GET)', default: 'GET' },
2138
+ cron_id: { type: "string", description: "Cron job ID" },
1525
2139
  },
1526
- required: ['bucket_id', 'key'],
2140
+ required: ["cron_id"],
1527
2141
  },
1528
2142
  },
1529
- // Databases
1530
- {
1531
- name: 'database_list',
1532
- description: 'List all managed databases in the tenant.',
1533
- inputSchema: { type: 'object', properties: {} },
1534
- },
1535
2143
  {
1536
- name: 'database_create',
1537
- description: 'Create a new managed database.',
2144
+ name: "cron_delete",
2145
+ description: "Delete a cron job permanently.",
1538
2146
  inputSchema: {
1539
- type: 'object',
2147
+ type: "object",
1540
2148
  properties: {
1541
- name: { type: 'string', description: 'Database name' },
1542
- engine: { type: 'string', enum: ['postgres', 'mysql', 'redis'], description: 'Database engine' },
1543
- version: { type: 'string', description: 'Engine version (optional)' },
1544
- size: { type: 'string', description: 'Database size/tier (optional)' },
1545
- region: { type: 'string', description: 'Region (optional)' },
2149
+ cron_id: { type: "string", description: "Cron job ID to delete" },
1546
2150
  },
1547
- required: ['name', 'engine'],
2151
+ required: ["cron_id"],
1548
2152
  },
1549
2153
  },
1550
2154
  {
1551
- name: 'database_get',
1552
- description: 'Get details of a specific database.',
2155
+ name: "cron_pause",
2156
+ description: "Pause a cron job so it stops running on schedule.",
1553
2157
  inputSchema: {
1554
- type: 'object',
2158
+ type: "object",
1555
2159
  properties: {
1556
- database_id: { type: 'string', description: 'Database ID' },
2160
+ cron_id: { type: "string", description: "Cron job ID to pause" },
1557
2161
  },
1558
- required: ['database_id'],
2162
+ required: ["cron_id"],
1559
2163
  },
1560
2164
  },
1561
2165
  {
1562
- name: 'database_delete',
1563
- description: 'Delete a managed database permanently.',
2166
+ name: "cron_resume",
2167
+ description: "Resume a paused cron job.",
1564
2168
  inputSchema: {
1565
- type: 'object',
2169
+ type: "object",
1566
2170
  properties: {
1567
- database_id: { type: 'string', description: 'Database ID to delete' },
2171
+ cron_id: { type: "string", description: "Cron job ID to resume" },
1568
2172
  },
1569
- required: ['database_id'],
2173
+ required: ["cron_id"],
1570
2174
  },
1571
2175
  },
1572
2176
  {
1573
- name: 'database_credentials',
1574
- description: 'Get the connection string and credentials for a database.',
2177
+ name: "cron_run_now",
2178
+ description: "Trigger an immediate one-off execution of a cron job outside its schedule.",
1575
2179
  inputSchema: {
1576
- type: 'object',
2180
+ type: "object",
1577
2181
  properties: {
1578
- database_id: { type: 'string', description: 'Database ID' },
2182
+ cron_id: {
2183
+ type: "string",
2184
+ description: "Cron job ID to run immediately",
2185
+ },
1579
2186
  },
1580
- required: ['database_id'],
2187
+ required: ["cron_id"],
1581
2188
  },
1582
2189
  },
1583
2190
  {
1584
- name: 'database_logs',
1585
- description: 'Get logs for a managed database.',
2191
+ name: "cron_executions",
2192
+ description: "List recent execution history for a cron job.",
1586
2193
  inputSchema: {
1587
- type: 'object',
2194
+ type: "object",
1588
2195
  properties: {
1589
- database_id: { type: 'string', description: 'Database ID' },
1590
- lines: { type: 'integer', description: 'Number of log lines to return (default: 100)', default: 100 },
1591
- since: { type: 'string', description: 'ISO 8601 timestamp to fetch logs from (optional)' },
2196
+ cron_id: { type: "string", description: "Cron job ID" },
1592
2197
  },
1593
- required: ['database_id'],
2198
+ required: ["cron_id"],
1594
2199
  },
1595
2200
  },
1596
- // Workspaces
2201
+ // Volumes
1597
2202
  {
1598
- name: 'workspace_list',
1599
- description: 'List all workspaces in the tenant.',
1600
- inputSchema: { type: 'object', properties: {} },
2203
+ name: "volume_list",
2204
+ description: "List all volumes in the tenant.",
2205
+ inputSchema: { type: "object", properties: {} },
1601
2206
  },
1602
2207
  {
1603
- name: 'workspace_create',
1604
- description: 'Create a new workspace.',
2208
+ name: "volume_create",
2209
+ description: "Create a new persistent volume.",
1605
2210
  inputSchema: {
1606
- type: 'object',
2211
+ type: "object",
1607
2212
  properties: {
1608
- name: { type: 'string', description: 'Workspace name' },
1609
- description: { type: 'string', description: 'Workspace description (optional)' },
2213
+ name: {
2214
+ type: "string",
2215
+ description: "Human-readable name for the volume",
2216
+ },
2217
+ size_gb: {
2218
+ type: "integer",
2219
+ description: "Size of the volume in GB (optional)",
2220
+ },
2221
+ region: {
2222
+ type: "string",
2223
+ description: "Region to create the volume in (optional)",
2224
+ },
1610
2225
  },
1611
- required: ['name'],
2226
+ required: ["name"],
1612
2227
  },
1613
2228
  },
1614
2229
  {
1615
- name: 'workspace_get',
1616
- description: 'Get details of a specific workspace.',
2230
+ name: "volume_get",
2231
+ description: "Get details of a specific volume.",
1617
2232
  inputSchema: {
1618
- type: 'object',
2233
+ type: "object",
1619
2234
  properties: {
1620
- workspace_id: { type: 'string', description: 'Workspace ID' },
2235
+ volume_id: { type: "string", description: "Volume ID" },
1621
2236
  },
1622
- required: ['workspace_id'],
2237
+ required: ["volume_id"],
1623
2238
  },
1624
2239
  },
1625
2240
  {
1626
- name: 'workspace_update',
1627
- description: "Update a workspace's name or description.",
2241
+ name: "volume_delete",
2242
+ description: "Delete a volume permanently.",
1628
2243
  inputSchema: {
1629
- type: 'object',
2244
+ type: "object",
1630
2245
  properties: {
1631
- workspace_id: { type: 'string', description: 'Workspace ID' },
1632
- name: { type: 'string', description: 'New workspace name (optional)' },
1633
- description: { type: 'string', description: 'New description (optional)' },
2246
+ volume_id: { type: "string", description: "Volume ID to delete" },
1634
2247
  },
1635
- required: ['workspace_id'],
2248
+ required: ["volume_id"],
1636
2249
  },
1637
2250
  },
1638
2251
  {
1639
- name: 'workspace_stats',
1640
- description: 'Get resource statistics for a workspace (computers, sandboxes, databases, etc.).',
2252
+ name: "volume_attach",
2253
+ description: "Attach a volume to a computer at a given mount path.",
1641
2254
  inputSchema: {
1642
- type: 'object',
2255
+ type: "object",
1643
2256
  properties: {
1644
- workspace_id: { type: 'string', description: 'Workspace ID' },
2257
+ computer_id: {
2258
+ type: "string",
2259
+ description: "Computer ID to attach the volume to",
2260
+ },
2261
+ volume_id: { type: "string", description: "Volume ID to attach" },
2262
+ mount_path: {
2263
+ type: "string",
2264
+ description: "Path inside the VM to mount the volume (optional)",
2265
+ },
1645
2266
  },
1646
- required: ['workspace_id'],
2267
+ required: ["computer_id", "volume_id"],
1647
2268
  },
1648
2269
  },
1649
2270
  {
1650
- name: 'workspace_usage',
1651
- description: 'Get usage data (compute hours, storage, bandwidth) for a workspace.',
2271
+ name: "volume_detach",
2272
+ description: "Detach a volume attachment from a computer.",
1652
2273
  inputSchema: {
1653
- type: 'object',
2274
+ type: "object",
1654
2275
  properties: {
1655
- workspace_id: { type: 'string', description: 'Workspace ID' },
1656
- period: { type: 'string', description: "Billing period (e.g. '2026-05'). Defaults to current month." },
2276
+ computer_id: { type: "string", description: "Computer ID" },
2277
+ attachment_id: {
2278
+ type: "string",
2279
+ description: "Attachment ID to remove",
2280
+ },
1657
2281
  },
1658
- required: ['workspace_id'],
2282
+ required: ["computer_id", "attachment_id"],
1659
2283
  },
1660
2284
  },
1661
- // Billing
1662
- {
1663
- name: 'billing_usage',
1664
- description: 'Get current billing period usage for the tenant.',
1665
- inputSchema: { type: 'object', properties: {} },
1666
- },
1667
- {
1668
- name: 'billing_plan',
1669
- description: 'Get the current billing plan details for the tenant.',
1670
- inputSchema: { type: 'object', properties: {} },
1671
- },
1672
2285
  ];
1673
2286
  // ── Wire helpers ─────────────────────────────────────────────────────────────
1674
2287
  function ok(text) {
@@ -1708,6 +2321,10 @@ async function dispatchTool(client, name, args) {
1708
2321
  body["external_workspace_id"] = args["external_workspace_id"];
1709
2322
  if (args["external_project_id"])
1710
2323
  body["external_project_id"] = args["external_project_id"];
2324
+ if (args["gpu_model"]) {
2325
+ body["gpu_model"] = args["gpu_model"];
2326
+ body["gpu_count"] = args["gpu_count"] ?? 1;
2327
+ }
1711
2328
  const computer = await client.apiPost("/api/v1/computers", body);
1712
2329
  const data = unwrapData(computer);
1713
2330
  const id = String(data["id"] ?? "");
@@ -2738,7 +3355,12 @@ async function dispatchTool(client, name, args) {
2738
3355
  const lines = ["Deployments:"];
2739
3356
  for (const d of items) {
2740
3357
  const r = d;
2741
- lines.push(" " + r["id"] + " " + r["name"] + " " + (r["status"] ?? r["state"] ?? ""));
3358
+ lines.push(" " +
3359
+ r["id"] +
3360
+ " " +
3361
+ r["name"] +
3362
+ " " +
3363
+ (r["status"] ?? r["state"] ?? ""));
2742
3364
  }
2743
3365
  return ok(lines.join("\n"));
2744
3366
  }
@@ -2761,7 +3383,11 @@ async function dispatchTool(client, name, args) {
2761
3383
  body["region"] = args["region"];
2762
3384
  const result = await client.apiPost("/api/v1/deployments", body);
2763
3385
  const data = unwrapData(result);
2764
- return ok("Created deployment '" + String(data["name"] ?? args["name"]) + "' (id=" + data["id"] + ")");
3386
+ return ok("Created deployment '" +
3387
+ String(data["name"] ?? args["name"]) +
3388
+ "' (id=" +
3389
+ data["id"] +
3390
+ ")");
2765
3391
  }
2766
3392
  if (name === "deployment_delete") {
2767
3393
  const did = String(args["deployment_id"] ?? "");
@@ -2779,7 +3405,11 @@ async function dispatchTool(client, name, args) {
2779
3405
  body["source"] = args["source"];
2780
3406
  const result = await client.apiPost("/api/v1/deployments/" + encodeURIComponent(did) + "/publish", body);
2781
3407
  const data = unwrapData(result);
2782
- return ok("Published deployment " + did + " (version id=" + (data["id"] ?? "unknown") + ")");
3408
+ return ok("Published deployment " +
3409
+ did +
3410
+ " (version id=" +
3411
+ (data["id"] ?? "unknown") +
3412
+ ")");
2783
3413
  }
2784
3414
  if (name === "deployment_rollback") {
2785
3415
  const did = String(args["deployment_id"] ?? "");
@@ -2797,7 +3427,8 @@ async function dispatchTool(client, name, args) {
2797
3427
  return err("deployment_id is required");
2798
3428
  const result = await client.apiGet("/api/v1/deployments/" + encodeURIComponent(did) + "/env");
2799
3429
  const envVars = unwrapData(result);
2800
- if (!envVars || (Array.isArray(envVars) && envVars.length === 0))
3430
+ if (!envVars ||
3431
+ (Array.isArray(envVars) && envVars.length === 0))
2801
3432
  return ok("No environment variables set.");
2802
3433
  const lines = ["Environment variables:"];
2803
3434
  if (typeof envVars === "object" && !Array.isArray(envVars)) {
@@ -2823,13 +3454,20 @@ async function dispatchTool(client, name, args) {
2823
3454
  const did = String(args["deployment_id"] ?? "");
2824
3455
  if (!did)
2825
3456
  return err("deployment_id is required");
2826
- const params = new URLSearchParams({ lines: String(args["lines"] ?? 100) });
3457
+ const params = new URLSearchParams({
3458
+ lines: String(args["lines"] ?? 100),
3459
+ });
2827
3460
  if (args["since"])
2828
3461
  params.set("since", String(args["since"]));
2829
- const result = await client.apiGet("/api/v1/deployments/" + encodeURIComponent(did) + "/logs?" + params.toString());
3462
+ const result = await client.apiGet("/api/v1/deployments/" +
3463
+ encodeURIComponent(did) +
3464
+ "/logs?" +
3465
+ params.toString());
2830
3466
  const logs = unwrapData(result);
2831
3467
  if (Array.isArray(logs))
2832
- return ok(logs.length ? logs.map(String).join("\n") : "No logs.");
3468
+ return ok(logs.length
3469
+ ? logs.map(String).join("\n")
3470
+ : "No logs.");
2833
3471
  return ok(String(logs));
2834
3472
  }
2835
3473
  if (name === "deployment_version_list") {
@@ -2843,7 +3481,12 @@ async function dispatchTool(client, name, args) {
2843
3481
  const lines = ["Versions:"];
2844
3482
  for (const v of versions) {
2845
3483
  const r = v;
2846
- lines.push(" " + r["id"] + " " + (r["created_at"] ?? "") + " " + (r["status"] ?? ""));
3484
+ lines.push(" " +
3485
+ r["id"] +
3486
+ " " +
3487
+ (r["created_at"] ?? "") +
3488
+ " " +
3489
+ (r["status"] ?? ""));
2847
3490
  }
2848
3491
  return ok(lines.join("\n"));
2849
3492
  }
@@ -2854,7 +3497,11 @@ async function dispatchTool(client, name, args) {
2854
3497
  return err("deployment_id is required");
2855
3498
  if (!vid)
2856
3499
  return err("version_id is required");
2857
- await client.apiPost("/api/v1/deployments/" + encodeURIComponent(did) + "/versions/" + encodeURIComponent(vid) + "/promote", {});
3500
+ await client.apiPost("/api/v1/deployments/" +
3501
+ encodeURIComponent(did) +
3502
+ "/versions/" +
3503
+ encodeURIComponent(vid) +
3504
+ "/promote", {});
2858
3505
  return ok("Version " + vid + " promoted on deployment " + did + ".");
2859
3506
  }
2860
3507
  // ── Storage ───────────────────────────────────────────────────────────
@@ -2878,7 +3525,11 @@ async function dispatchTool(client, name, args) {
2878
3525
  body["public"] = args["public"];
2879
3526
  const result = await client.apiPost("/api/v1/storage/buckets", body);
2880
3527
  const data = unwrapData(result);
2881
- return ok("Created bucket '" + String(data["name"] ?? args["name"]) + "' (id=" + data["id"] + ")");
3528
+ return ok("Created bucket '" +
3529
+ String(data["name"] ?? args["name"]) +
3530
+ "' (id=" +
3531
+ data["id"] +
3532
+ ")");
2882
3533
  }
2883
3534
  if (name === "storage_bucket_delete") {
2884
3535
  const bid = String(args["bucket_id"] ?? "");
@@ -2902,7 +3553,12 @@ async function dispatchTool(client, name, args) {
2902
3553
  const lines = ["Objects:"];
2903
3554
  for (const o of items) {
2904
3555
  const r = o;
2905
- lines.push(" " + r["key"] + " " + (r["size"] ?? "") + " " + (r["last_modified"] ?? ""));
3556
+ lines.push(" " +
3557
+ r["key"] +
3558
+ " " +
3559
+ (r["size"] ?? "") +
3560
+ " " +
3561
+ (r["last_modified"] ?? ""));
2906
3562
  }
2907
3563
  return ok(lines.join("\n"));
2908
3564
  }
@@ -2921,7 +3577,10 @@ async function dispatchTool(client, name, args) {
2921
3577
  const bid = String(args["bucket_id"] ?? "");
2922
3578
  if (!bid)
2923
3579
  return err("bucket_id is required");
2924
- const result = await client.apiGet("/api/v1/storage/buckets/" + encodeURIComponent(bid) + "/objects/" + encodeURIComponent(String(args["key"] ?? "")));
3580
+ const result = await client.apiGet("/api/v1/storage/buckets/" +
3581
+ encodeURIComponent(bid) +
3582
+ "/objects/" +
3583
+ encodeURIComponent(String(args["key"] ?? "")));
2925
3584
  const data = unwrapData(result);
2926
3585
  const content = typeof data["content"] === "string"
2927
3586
  ? Buffer.from(data["content"], "base64").toString("utf8")
@@ -2934,7 +3593,10 @@ async function dispatchTool(client, name, args) {
2934
3593
  const bid = String(args["bucket_id"] ?? "");
2935
3594
  if (!bid)
2936
3595
  return err("bucket_id is required");
2937
- await client.apiDelete("/api/v1/storage/buckets/" + encodeURIComponent(bid) + "/objects/" + encodeURIComponent(String(args["key"] ?? "")));
3596
+ await client.apiDelete("/api/v1/storage/buckets/" +
3597
+ encodeURIComponent(bid) +
3598
+ "/objects/" +
3599
+ encodeURIComponent(String(args["key"] ?? "")));
2938
3600
  return ok("Deleted " + String(args["key"]) + " from bucket " + bid + ".");
2939
3601
  }
2940
3602
  if (name === "storage_presign") {
@@ -2958,19 +3620,33 @@ async function dispatchTool(client, name, args) {
2958
3620
  const lines = ["Databases:"];
2959
3621
  for (const d of items) {
2960
3622
  const r = d;
2961
- lines.push(" " + r["id"] + " " + r["name"] + " " + (r["engine"] ?? "") + " " + (r["status"] ?? ""));
3623
+ lines.push(" " +
3624
+ r["id"] +
3625
+ " " +
3626
+ r["name"] +
3627
+ " " +
3628
+ (r["engine"] ?? "") +
3629
+ " " +
3630
+ (r["status"] ?? ""));
2962
3631
  }
2963
3632
  return ok(lines.join("\n"));
2964
3633
  }
2965
3634
  if (name === "database_create") {
2966
- const body = { name: args["name"], engine: args["engine"] };
3635
+ const body = {
3636
+ name: args["name"],
3637
+ engine: args["engine"],
3638
+ };
2967
3639
  for (const key of ["version", "size", "region"]) {
2968
3640
  if (args[key])
2969
3641
  body[key] = args[key];
2970
3642
  }
2971
3643
  const result = await client.apiPost("/api/v1/databases", body);
2972
3644
  const data = unwrapData(result);
2973
- return ok("Created database '" + String(data["name"] ?? args["name"]) + "' (id=" + data["id"] + ")");
3645
+ return ok("Created database '" +
3646
+ String(data["name"] ?? args["name"]) +
3647
+ "' (id=" +
3648
+ data["id"] +
3649
+ ")");
2974
3650
  }
2975
3651
  if (name === "database_get") {
2976
3652
  const dbid = String(args["database_id"] ?? "");
@@ -2993,7 +3669,14 @@ async function dispatchTool(client, name, args) {
2993
3669
  const result = await client.apiGet("/api/v1/databases/" + encodeURIComponent(dbid) + "/credentials");
2994
3670
  const data = unwrapData(result);
2995
3671
  const lines = ["Database credentials:"];
2996
- for (const field of ["connection_string", "host", "port", "database", "username", "password"]) {
3672
+ for (const field of [
3673
+ "connection_string",
3674
+ "host",
3675
+ "port",
3676
+ "database",
3677
+ "username",
3678
+ "password",
3679
+ ]) {
2997
3680
  if (data[field])
2998
3681
  lines.push(" " + field + ": " + data[field]);
2999
3682
  }
@@ -3003,13 +3686,20 @@ async function dispatchTool(client, name, args) {
3003
3686
  const dbid = String(args["database_id"] ?? "");
3004
3687
  if (!dbid)
3005
3688
  return err("database_id is required");
3006
- const params = new URLSearchParams({ lines: String(args["lines"] ?? 100) });
3689
+ const params = new URLSearchParams({
3690
+ lines: String(args["lines"] ?? 100),
3691
+ });
3007
3692
  if (args["since"])
3008
3693
  params.set("since", String(args["since"]));
3009
- const result = await client.apiGet("/api/v1/databases/" + encodeURIComponent(dbid) + "/logs?" + params.toString());
3694
+ const result = await client.apiGet("/api/v1/databases/" +
3695
+ encodeURIComponent(dbid) +
3696
+ "/logs?" +
3697
+ params.toString());
3010
3698
  const logs = unwrapData(result);
3011
3699
  if (Array.isArray(logs))
3012
- return ok(logs.length ? logs.map(String).join("\n") : "No logs.");
3700
+ return ok(logs.length
3701
+ ? logs.map(String).join("\n")
3702
+ : "No logs.");
3013
3703
  return ok(String(logs));
3014
3704
  }
3015
3705
  // ── Workspaces ────────────────────────────────────────────────────────
@@ -3031,7 +3721,11 @@ async function dispatchTool(client, name, args) {
3031
3721
  body["description"] = args["description"];
3032
3722
  const result = await client.apiPost("/api/v1/workspaces", body);
3033
3723
  const data = unwrapData(result);
3034
- return ok("Created workspace '" + String(data["name"] ?? args["name"]) + "' (id=" + data["id"] + ")");
3724
+ return ok("Created workspace '" +
3725
+ String(data["name"] ?? args["name"]) +
3726
+ "' (id=" +
3727
+ data["id"] +
3728
+ ")");
3035
3729
  }
3036
3730
  if (name === "workspace_get") {
3037
3731
  const wid = String(args["workspace_id"] ?? "");
@@ -3079,6 +3773,425 @@ async function dispatchTool(client, name, args) {
3079
3773
  const result = await client.apiGet("/api/v1/billing/plan");
3080
3774
  return ok(JSON.stringify(unwrapData(result), null, 2));
3081
3775
  }
3776
+ // ── Tunnels / Port forwarding ─────────────────────────────────────────
3777
+ if (name === "computer_expose_port") {
3778
+ if (!cid)
3779
+ return err("computer_id is required");
3780
+ const body = { port: args["port"] };
3781
+ if (args["protocol"])
3782
+ body["protocol"] = args["protocol"];
3783
+ const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(cid)}/ports`, body);
3784
+ const data = unwrapData(result);
3785
+ const url = String(data["url"] ?? data["public_url"] ?? "");
3786
+ return ok(`Port ${args["port"]} exposed. URL: ${url}`);
3787
+ }
3788
+ if (name === "computer_list_ports") {
3789
+ if (!cid)
3790
+ return err("computer_id is required");
3791
+ const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/ports`);
3792
+ const items = listOf(result);
3793
+ if (items.length === 0)
3794
+ return ok("No ports currently exposed.");
3795
+ const lines = ["Exposed ports:"];
3796
+ for (const p of items) {
3797
+ const r = p;
3798
+ lines.push(` port=${r["port"]} protocol=${r["protocol"] ?? "http"} url=${r["url"] ?? r["public_url"] ?? ""}`);
3799
+ }
3800
+ return ok(lines.join("\n"));
3801
+ }
3802
+ if (name === "computer_preview_url") {
3803
+ if (!cid)
3804
+ return err("computer_id is required");
3805
+ const port = args["port"];
3806
+ const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/ports/${encodeURIComponent(String(port))}/url`);
3807
+ const data = unwrapData(result);
3808
+ const url = String(data["url"] ?? data["public_url"] ?? "") ||
3809
+ `https://${port}-${cid}.computer.miosa.ai`;
3810
+ return ok(`Preview URL: ${url}`);
3811
+ }
3812
+ // ── Network policy ────────────────────────────────────────────────────
3813
+ if (name === "computer_network_policy_get") {
3814
+ if (!cid)
3815
+ return err("computer_id is required");
3816
+ const result = await client.apiGet(`/api/v1/computers/${encodeURIComponent(cid)}/network-policy`);
3817
+ return ok(JSON.stringify(unwrapData(result), null, 2));
3818
+ }
3819
+ if (name === "computer_network_policy_set") {
3820
+ if (!cid)
3821
+ return err("computer_id is required");
3822
+ const body = { rules: args["rules"] };
3823
+ if (args["default_effect"])
3824
+ body["default_effect"] = args["default_effect"];
3825
+ await client.apiPut(`/api/v1/computers/${encodeURIComponent(cid)}/network-policy`, body);
3826
+ return ok(`Network policy updated for computer ${cid}.`);
3827
+ }
3828
+ if (name === "computer_network_policy_reset") {
3829
+ if (!cid)
3830
+ return err("computer_id is required");
3831
+ await client.apiDelete(`/api/v1/computers/${encodeURIComponent(cid)}/network-policy`);
3832
+ return ok(`Network policy reset to default for computer ${cid}.`);
3833
+ }
3834
+ // ── Webhooks ──────────────────────────────────────────────────────────
3835
+ if (name === "webhook_list") {
3836
+ const result = await client.apiGet("/api/v1/webhooks");
3837
+ const items = listOf(result);
3838
+ if (items.length === 0)
3839
+ return ok("No webhooks found.");
3840
+ const lines = ["Webhooks:"];
3841
+ for (const w of items) {
3842
+ const r = w;
3843
+ lines.push(` ${r["id"]} ${r["url"]} events=${JSON.stringify(r["events"] ?? [])}`);
3844
+ }
3845
+ return ok(lines.join("\n"));
3846
+ }
3847
+ if (name === "webhook_create") {
3848
+ const result = await client.apiPost("/api/v1/webhooks", {
3849
+ url: args["url"],
3850
+ events: args["events"],
3851
+ });
3852
+ const data = unwrapData(result);
3853
+ return ok(`Created webhook (id=${data["id"] ?? "?"}).`);
3854
+ }
3855
+ if (name === "webhook_delete") {
3856
+ const whId = String(args["webhook_id"] ?? "");
3857
+ if (!whId)
3858
+ return err("webhook_id is required");
3859
+ await client.apiDelete(`/api/v1/webhooks/${encodeURIComponent(whId)}`);
3860
+ return ok(`Webhook ${whId} deleted.`);
3861
+ }
3862
+ if (name === "webhook_test") {
3863
+ const whId = String(args["webhook_id"] ?? "");
3864
+ if (!whId)
3865
+ return err("webhook_id is required");
3866
+ const result = await client.apiPost(`/api/v1/webhooks/${encodeURIComponent(whId)}/test`, {});
3867
+ const data = unwrapData(result);
3868
+ const status = String(data["status"] ?? "delivered");
3869
+ return ok(`Test event sent to webhook ${whId} (status=${status}).`);
3870
+ }
3871
+ // ── Functions ─────────────────────────────────────────────────────────
3872
+ if (name === "function_list") {
3873
+ const result = await client.apiGet("/api/v1/functions");
3874
+ const items = listOf(result);
3875
+ if (items.length === 0)
3876
+ return ok("No functions found.");
3877
+ const lines = ["Functions:"];
3878
+ for (const f of items) {
3879
+ const r = f;
3880
+ lines.push(` ${r["id"]} ${r["name"]} runtime=${r["runtime"] ?? ""}`);
3881
+ }
3882
+ return ok(lines.join("\n"));
3883
+ }
3884
+ if (name === "function_create") {
3885
+ const body = {
3886
+ name: args["name"],
3887
+ runtime: args["runtime"],
3888
+ };
3889
+ if (args["code"])
3890
+ body["code"] = args["code"];
3891
+ const result = await client.apiPost("/api/v1/functions", body);
3892
+ const data = unwrapData(result);
3893
+ return ok(`Created function '${data["name"] ?? args["name"]}' (id=${data["id"]}).`);
3894
+ }
3895
+ if (name === "function_invoke") {
3896
+ const fnId = String(args["function_id"] ?? "");
3897
+ if (!fnId)
3898
+ return err("function_id is required");
3899
+ const body = {};
3900
+ if (args["payload"] !== undefined)
3901
+ body["payload"] = args["payload"];
3902
+ const result = await client.apiPost(`/api/v1/functions/${encodeURIComponent(fnId)}/invoke`, body);
3903
+ return ok(JSON.stringify(unwrapData(result), null, 2));
3904
+ }
3905
+ if (name === "function_delete") {
3906
+ const fnId = String(args["function_id"] ?? "");
3907
+ if (!fnId)
3908
+ return err("function_id is required");
3909
+ await client.apiDelete(`/api/v1/functions/${encodeURIComponent(fnId)}`);
3910
+ return ok(`Function ${fnId} deleted.`);
3911
+ }
3912
+ // ── API Keys ──────────────────────────────────────────────────────────
3913
+ if (name === "api_key_list") {
3914
+ const result = await client.apiGet("/api/v1/api-keys");
3915
+ const items = listOf(result);
3916
+ if (items.length === 0)
3917
+ return ok("No API keys found.");
3918
+ const lines = ["API keys:"];
3919
+ for (const k of items) {
3920
+ const r = k;
3921
+ const scopes = Array.isArray(r["scopes"])
3922
+ ? r["scopes"].join(", ")
3923
+ : String(r["scopes"] ?? "");
3924
+ lines.push(` ${r["id"]} ${r["name"]} scopes=[${scopes}]`);
3925
+ }
3926
+ return ok(lines.join("\n"));
3927
+ }
3928
+ if (name === "api_key_create") {
3929
+ const body = { name: args["name"] };
3930
+ if (args["scopes"])
3931
+ body["scopes"] = args["scopes"];
3932
+ const result = await client.apiPost("/api/v1/api-keys", body);
3933
+ const data = unwrapData(result);
3934
+ const keyId = String(data["id"] ?? "?");
3935
+ const keyValue = String(data["key"] ?? data["token"] ?? data["secret"] ?? "");
3936
+ let msg = `Created API key '${args["name"]}' (id=${keyId}).`;
3937
+ if (keyValue)
3938
+ msg += `\nKey value (shown once): ${keyValue}`;
3939
+ return ok(msg);
3940
+ }
3941
+ if (name === "api_key_delete") {
3942
+ const keyId = String(args["key_id"] ?? "");
3943
+ if (!keyId)
3944
+ return err("key_id is required");
3945
+ await client.apiDelete(`/api/v1/api-keys/${encodeURIComponent(keyId)}`);
3946
+ return ok(`API key ${keyId} deleted.`);
3947
+ }
3948
+ // ── Cron jobs ─────────────────────────────────────────────────────────
3949
+ if (name === "cron_list") {
3950
+ const params = new URLSearchParams();
3951
+ if (args["computer_id"])
3952
+ params.set("computer_id", String(args["computer_id"]));
3953
+ const qs = params.toString() ? `?${params.toString()}` : "";
3954
+ const result = await client.apiGet(`/api/v1/cron-jobs${qs}`);
3955
+ const items = listOf(result);
3956
+ if (items.length === 0)
3957
+ return ok("No cron jobs found.");
3958
+ const lines = ["Cron jobs:"];
3959
+ for (const j of items) {
3960
+ const r = j;
3961
+ lines.push(` ${r["id"]} ${r["name"] ?? ""} ${r["schedule"] ?? ""} status=${r["status"] ?? r["state"] ?? ""}`);
3962
+ }
3963
+ return ok(lines.join("\n"));
3964
+ }
3965
+ if (name === "cron_create") {
3966
+ const body = {
3967
+ computer_id: args["computer_id"],
3968
+ schedule: args["schedule"],
3969
+ command: args["command"],
3970
+ };
3971
+ if (args["name"])
3972
+ body["name"] = args["name"];
3973
+ const result = await client.apiPost("/api/v1/cron-jobs", body);
3974
+ const data = unwrapData(result);
3975
+ return ok(`Created cron job '${data["name"] ?? args["name"] ?? ""}' (id=${data["id"]}).`);
3976
+ }
3977
+ if (name === "cron_get") {
3978
+ const cronId = String(args["cron_id"] ?? "");
3979
+ if (!cronId)
3980
+ return err("cron_id is required");
3981
+ const result = await client.apiGet(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}`);
3982
+ return ok(JSON.stringify(unwrapData(result), null, 2));
3983
+ }
3984
+ if (name === "cron_delete") {
3985
+ const cronId = String(args["cron_id"] ?? "");
3986
+ if (!cronId)
3987
+ return err("cron_id is required");
3988
+ await client.apiDelete(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}`);
3989
+ return ok(`Cron job ${cronId} deleted.`);
3990
+ }
3991
+ if (name === "cron_pause") {
3992
+ const cronId = String(args["cron_id"] ?? "");
3993
+ if (!cronId)
3994
+ return err("cron_id is required");
3995
+ await client.apiPost(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}/pause`, {});
3996
+ return ok(`Cron job ${cronId} paused.`);
3997
+ }
3998
+ if (name === "cron_resume") {
3999
+ const cronId = String(args["cron_id"] ?? "");
4000
+ if (!cronId)
4001
+ return err("cron_id is required");
4002
+ await client.apiPost(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}/resume`, {});
4003
+ return ok(`Cron job ${cronId} resumed.`);
4004
+ }
4005
+ if (name === "cron_run_now") {
4006
+ const cronId = String(args["cron_id"] ?? "");
4007
+ if (!cronId)
4008
+ return err("cron_id is required");
4009
+ const result = await client.apiPost(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}/run-now`, {});
4010
+ const data = unwrapData(result);
4011
+ const execId = data["id"] ?? data["execution_id"];
4012
+ return ok(`Cron job ${cronId} triggered.${execId ? ` Execution id=${execId}.` : ""}`);
4013
+ }
4014
+ if (name === "cron_executions") {
4015
+ const cronId = String(args["cron_id"] ?? "");
4016
+ if (!cronId)
4017
+ return err("cron_id is required");
4018
+ const result = await client.apiGet(`/api/v1/cron-jobs/${encodeURIComponent(cronId)}/executions`);
4019
+ const items = listOf(result);
4020
+ if (items.length === 0)
4021
+ return ok("No executions found.");
4022
+ const lines = ["Executions:"];
4023
+ for (const e of items) {
4024
+ const r = e;
4025
+ lines.push(` ${r["id"]} ${r["started_at"] ?? r["created_at"] ?? ""} status=${r["status"] ?? ""} exit_code=${r["exit_code"] ?? ""}`);
4026
+ }
4027
+ return ok(lines.join("\n"));
4028
+ }
4029
+ // ── Regions ────────────────────────────────────────────────────────────
4030
+ if (name === "region_list" || name === "computer_list_regions") {
4031
+ const result = await client.apiGet("/api/v1/regions");
4032
+ const regions = (() => {
4033
+ const d = unwrapData(result);
4034
+ if (Array.isArray(d))
4035
+ return d;
4036
+ const r = d;
4037
+ for (const key of ["regions", "data", "items"]) {
4038
+ if (Array.isArray(r[key]))
4039
+ return r[key];
4040
+ }
4041
+ return Object.values(r);
4042
+ })();
4043
+ if (regions.length === 0)
4044
+ return ok("No regions found.");
4045
+ const lines = ["Regions:"];
4046
+ for (const region of regions) {
4047
+ const r = region;
4048
+ const gpuTypes = r["gpu_types"] ?? r["gpus"] ?? [];
4049
+ const gpuInfo = Array.isArray(gpuTypes) && gpuTypes.length > 0
4050
+ ? ` gpus=${JSON.stringify(gpuTypes)}`
4051
+ : "";
4052
+ lines.push(` ${r["id"] ?? r["slug"] ?? ""} ${r["name"] ?? ""} status=${r["status"] ?? "available"}${gpuInfo}`);
4053
+ }
4054
+ return ok(lines.join("\n"));
4055
+ }
4056
+ // ── Computer templates ─────────────────────────────────────────────────
4057
+ if (name === "computer_template_list") {
4058
+ const wid = String(args["workspace_id"] ?? "");
4059
+ if (!wid)
4060
+ return err("workspace_id is required");
4061
+ const result = await client.apiGet(`/api/v1/workspaces/${encodeURIComponent(wid)}/computer-templates`);
4062
+ const items = listOf(result);
4063
+ if (items.length === 0)
4064
+ return ok("No computer templates found.");
4065
+ const lines = ["Computer templates:"];
4066
+ for (const t of items) {
4067
+ const r = t;
4068
+ lines.push(` ${r["id"]} ${r["name"]} type=${r["template_type"] ?? ""} size=${r["size"] ?? ""}`);
4069
+ }
4070
+ return ok(lines.join("\n"));
4071
+ }
4072
+ if (name === "computer_template_create") {
4073
+ const wid = String(args["workspace_id"] ?? "");
4074
+ if (!wid)
4075
+ return err("workspace_id is required");
4076
+ const body = { name: args["name"] };
4077
+ if (args["template_type"])
4078
+ body["template_type"] = args["template_type"];
4079
+ if (args["size"])
4080
+ body["size"] = args["size"];
4081
+ if (args["selected_apps"])
4082
+ body["selected_apps"] = args["selected_apps"];
4083
+ if (args["settings"])
4084
+ body["settings"] = args["settings"];
4085
+ const result = await client.apiPost(`/api/v1/workspaces/${encodeURIComponent(wid)}/computer-templates`, body);
4086
+ const data = unwrapData(result);
4087
+ return ok(`Created computer template '${data["name"] ?? args["name"]}' (id=${data["id"]}).`);
4088
+ }
4089
+ // ── Settings ───────────────────────────────────────────────────────────
4090
+ if (name === "settings_get") {
4091
+ const result = await client.apiGet("/api/v1/settings");
4092
+ return ok(JSON.stringify(unwrapData(result), null, 2));
4093
+ }
4094
+ if (name === "settings_get_branding") {
4095
+ const result = await client.apiGet("/api/v1/settings/branding");
4096
+ return ok(JSON.stringify(unwrapData(result), null, 2));
4097
+ }
4098
+ if (name === "settings_update_branding") {
4099
+ const body = {};
4100
+ if (args["desktop_wallpaper_url"])
4101
+ body["desktop_wallpaper_url"] = args["desktop_wallpaper_url"];
4102
+ if (args["logo_url"])
4103
+ body["logo_url"] = args["logo_url"];
4104
+ const result = await client.apiPut("/api/v1/settings/branding", body);
4105
+ return ok(`Branding updated: ${JSON.stringify(unwrapData(result), null, 2)}`);
4106
+ }
4107
+ if (name === "settings_compute_pricing") {
4108
+ const result = await client.apiGet("/api/v1/settings/compute-pricing");
4109
+ return ok(JSON.stringify(unwrapData(result), null, 2));
4110
+ }
4111
+ // ── Sandbox template extensions ────────────────────────────────────────
4112
+ if (name === "sandbox_template_get") {
4113
+ const tid = String(args["template_id"] ?? "");
4114
+ if (!tid)
4115
+ return err("template_id is required");
4116
+ const result = await client.apiGet(`/api/v1/sandbox-templates/${encodeURIComponent(tid)}`);
4117
+ return ok(JSON.stringify(unwrapData(result), null, 2));
4118
+ }
4119
+ if (name === "sandbox_template_builds") {
4120
+ const tid = String(args["template_id"] ?? "");
4121
+ if (!tid)
4122
+ return err("template_id is required");
4123
+ const result = await client.apiGet(`/api/v1/sandbox-templates/${encodeURIComponent(tid)}/builds`);
4124
+ const items = listOf(result);
4125
+ if (items.length === 0)
4126
+ return ok("No builds found.");
4127
+ const lines = ["Builds:"];
4128
+ for (const b of items) {
4129
+ const r = b;
4130
+ lines.push(` ${r["id"]} ${r["status"] ?? ""} created_at=${r["created_at"] ?? ""}`);
4131
+ }
4132
+ return ok(lines.join("\n"));
4133
+ }
4134
+ // ── Volumes ───────────────────────────────────────────────────────────
4135
+ if (name === "volume_list") {
4136
+ const result = await client.apiGet("/api/v1/volumes");
4137
+ const items = listOf(result);
4138
+ if (items.length === 0)
4139
+ return ok("No volumes found.");
4140
+ const lines = ["Volumes:"];
4141
+ for (const v of items) {
4142
+ const r = v;
4143
+ lines.push(` ${r["id"]} ${r["name"]} size_gb=${r["size_gb"] ?? ""} region=${r["region"] ?? ""} status=${r["status"] ?? ""}`);
4144
+ }
4145
+ return ok(lines.join("\n"));
4146
+ }
4147
+ if (name === "volume_create") {
4148
+ const body = { name: args["name"] };
4149
+ if (args["size_gb"] !== undefined)
4150
+ body["size_gb"] = args["size_gb"];
4151
+ if (args["region"])
4152
+ body["region"] = args["region"];
4153
+ const result = await client.apiPost("/api/v1/volumes", body);
4154
+ const data = unwrapData(result);
4155
+ return ok(`Created volume '${data["name"] ?? args["name"]}' (id=${data["id"]}).`);
4156
+ }
4157
+ if (name === "volume_get") {
4158
+ const vid = String(args["volume_id"] ?? "");
4159
+ if (!vid)
4160
+ return err("volume_id is required");
4161
+ const result = await client.apiGet(`/api/v1/volumes/${encodeURIComponent(vid)}`);
4162
+ const data = unwrapData(result);
4163
+ return ok(`id=${data["id"]} name=${JSON.stringify(data["name"])} size_gb=${data["size_gb"] ?? ""} region=${data["region"] ?? ""} status=${data["status"] ?? ""}`);
4164
+ }
4165
+ if (name === "volume_delete") {
4166
+ const vid = String(args["volume_id"] ?? "");
4167
+ if (!vid)
4168
+ return err("volume_id is required");
4169
+ await client.apiDelete(`/api/v1/volumes/${encodeURIComponent(vid)}`);
4170
+ return ok(`Volume ${vid} deleted.`);
4171
+ }
4172
+ if (name === "volume_attach") {
4173
+ const attachCid = String(args["computer_id"] ?? "");
4174
+ if (!attachCid)
4175
+ return err("computer_id is required");
4176
+ const attachBody = {
4177
+ volume_id: args["volume_id"],
4178
+ };
4179
+ if (args["mount_path"])
4180
+ attachBody["mount_path"] = args["mount_path"];
4181
+ const result = await client.apiPost(`/api/v1/computers/${encodeURIComponent(attachCid)}/volumes`, attachBody);
4182
+ const data = unwrapData(result);
4183
+ return ok(`Volume ${args["volume_id"]} attached to computer ${attachCid} (attachment id=${data["id"] ?? "?"}).`);
4184
+ }
4185
+ if (name === "volume_detach") {
4186
+ const detachCid = String(args["computer_id"] ?? "");
4187
+ if (!detachCid)
4188
+ return err("computer_id is required");
4189
+ const attId = String(args["attachment_id"] ?? "");
4190
+ if (!attId)
4191
+ return err("attachment_id is required");
4192
+ await client.apiDelete(`/api/v1/computers/${encodeURIComponent(detachCid)}/volumes/${encodeURIComponent(attId)}`);
4193
+ return ok(`Attachment ${attId} removed from computer ${detachCid}.`);
4194
+ }
3082
4195
  return err(`Unknown tool: ${name}`);
3083
4196
  }
3084
4197
  catch (e) {
@@ -3143,7 +4256,47 @@ async function runServer() {
3143
4256
  result: {
3144
4257
  protocolVersion: "2024-11-05",
3145
4258
  capabilities: { tools: {} },
3146
- serverInfo: { name: "miosa-mcp", version: "0.1.1" },
4259
+ serverInfo: { name: "miosa-mcp", version: "0.1.2" },
4260
+ instructions: [
4261
+ "MIOSA cloud infrastructure — Firecracker microVMs you control via API.",
4262
+ "",
4263
+ "## Concepts",
4264
+ "- **Computer**: full Linux desktop VM (GUI, browser, apps). Use for visual tasks, browser automation, desktop control.",
4265
+ "- **Sandbox**: headless Linux VM. Use for code execution, builds, CI, scripts. No desktop.",
4266
+ "- **Deployment**: git-based app hosting with builds, releases, domains.",
4267
+ "- **Storage**: S3-compatible object storage (buckets + objects).",
4268
+ "- **Database**: managed Postgres, MySQL, or Redis.",
4269
+ "- **Volume**: persistent block storage, attachable to computers.",
4270
+ "",
4271
+ "## Core workflows",
4272
+ "",
4273
+ "### Run code (sandbox)",
4274
+ "create_sandbox → exec (or exec_python) → read output → destroy_sandbox",
4275
+ "",
4276
+ "### Desktop automation (computer)",
4277
+ "computer_create → computer_screenshot → computer_click/type/key → computer_screenshot → repeat",
4278
+ "",
4279
+ "### Deploy an app",
4280
+ "deployment_create(repo_url) → deployment_publish → custom_domain_add (optional)",
4281
+ "",
4282
+ "### Store files",
4283
+ "storage_bucket_create → storage_object_upload → storage_object_presign (for public URL)",
4284
+ "",
4285
+ "### Provision a database",
4286
+ 'database_create(engine="postgres") → database_credentials → use connection string',
4287
+ "",
4288
+ "## Conventions",
4289
+ "- IDs: pass computer_id or sandbox_id to every tool that operates on a resource.",
4290
+ "- Sizes: xs (1cpu/2GB), small (2cpu/4GB), medium (4cpu/8GB), large (8cpu/16GB), xl (16cpu/32GB).",
4291
+ '- Status: "running" = ready. Poll with get/list until status is "running".',
4292
+ "- File paths inside VMs: /workspace is the default working directory. /home/user also writable.",
4293
+ "- exec timeout: default 30s. For installs (npm/pip), set timeout_ms=120000.",
4294
+ "- Screenshots: PNG bytes, 1024x768 default. Coordinates are absolute pixels from top-left (0,0).",
4295
+ "",
4296
+ "## Tool naming",
4297
+ "{resource}_{action} — e.g. computer_create, sandbox_exec, storage_bucket_list.",
4298
+ "Desktop tools: computer_screenshot, computer_click, computer_type, computer_key, computer_scroll.",
4299
+ ].join("\n"),
3147
4300
  },
3148
4301
  });
3149
4302
  break;
@@ -3215,17 +4368,19 @@ async function runDeviceFlow(endpoint, clientName) {
3215
4368
  }
3216
4369
  const flow = start.body;
3217
4370
  console.log();
3218
- console.log(chalk.bold("Authorize MIOSA MCP for this device"));
4371
+ console.log(` ${banner({ subtitle: "MCP install" })}`);
3219
4372
  console.log();
3220
- console.log(` Open: ${chalk.cyan(flow.verification_uri_complete)}`);
3221
- console.log(` Code: ${chalk.bold(flow.user_code)}`);
4373
+ console.log(kvPanel([
4374
+ { label: "Open", value: chalk.cyan(flow.verification_uri_complete) },
4375
+ { label: "Code", value: chalk.bold(flow.user_code) },
4376
+ ]));
3222
4377
  console.log();
3223
4378
  try {
3224
4379
  openUrl(flow.verification_uri_complete);
3225
- console.log(chalk.dim(" Browser opened. Waiting for approval..."));
4380
+ console.log(` ${icon.pending} ${chalk.dim("Browser opened. Waiting for approval")}`);
3226
4381
  }
3227
4382
  catch {
3228
- console.log(chalk.dim(" Could not open a browser automatically."));
4383
+ console.log(` ${icon.warn} ${chalk.dim("Could not open a browser automatically.")}`);
3229
4384
  }
3230
4385
  const deadline = Date.now() + flow.expires_in * 1000;
3231
4386
  const intervalMs = Math.max(flow.interval || 3, 1) * 1000;
@@ -3312,26 +4467,50 @@ function printManualSnippet(client, apiKey, remoteUrl) {
3312
4467
  console.log(` ${chalk.cyan(`--header "Authorization: Bearer ${apiKey}"`)}`);
3313
4468
  }
3314
4469
  async function runInstall(opts) {
4470
+ const startTime = Date.now();
3315
4471
  const config = loadConfig();
3316
4472
  const clientName = `MIOSA MCP (${opts.client === "manual" ? "manual" : opts.client})`;
3317
- console.log(chalk.bold("MIOSA MCP installer"), chalk.dim(`— wiring ${opts.client} → ${opts.remoteUrl}`));
3318
4473
  const apiKey = await runDeviceFlow(config.endpoint, clientName);
3319
4474
  if (opts.client === "claude") {
3320
4475
  const wired = wireClaudeCode(apiKey, opts.remoteUrl, opts.scope);
3321
4476
  if (wired.ok) {
3322
4477
  console.log();
3323
- console.log(chalk.green("✓"), `MCP server '${MCP_SERVER_NAME}' added to Claude Code (${opts.scope} scope).`);
3324
- console.log();
3325
- console.log(chalk.dim("Verify:"));
3326
- console.log(` ${chalk.cyan("claude mcp list")}`);
4478
+ console.log(kvPanel([
4479
+ {
4480
+ icon: icon.ok,
4481
+ label: "Server",
4482
+ value: chalk.bold(MCP_SERVER_NAME),
4483
+ },
4484
+ {
4485
+ icon: icon.ok,
4486
+ label: "Client",
4487
+ value: chalk.bold("Claude Code") + chalk.dim(` · ${opts.scope} scope`),
4488
+ },
4489
+ {
4490
+ icon: icon.ok,
4491
+ label: "Endpoint",
4492
+ value: chalk.dim(opts.remoteUrl),
4493
+ },
4494
+ ]));
3327
4495
  console.log();
3328
- console.log(chalk.dim("Try in a fresh Claude Code session:"));
3329
- console.log(chalk.dim(` "Create a MIOSA sandbox, run \`python -c 'print(2+2)'\`, then destroy it."`));
4496
+ console.log(hintBlock("Next", [
4497
+ "claude mcp list",
4498
+ "Try in Claude Code: 'Create a MIOSA sandbox and run python -c print(2+2)'",
4499
+ ]));
4500
+ printElapsed(formatDuration(Date.now() - startTime));
3330
4501
  return;
3331
4502
  }
3332
4503
  console.log();
3333
- console.log(chalk.yellow("!"), `Could not auto-wire Claude Code: ${wired.reason}`);
3334
- console.log(chalk.yellow(" Falling back to manual snippet:"));
4504
+ console.log(errorEnvelope({
4505
+ title: "Could not auto-wire Claude Code",
4506
+ body: wired.reason,
4507
+ suggest: [
4508
+ "miosa mcp install # try again",
4509
+ "miosa mcp install --client manual # get the snippet instead",
4510
+ ],
4511
+ withDebugHint: true,
4512
+ }));
4513
+ console.log();
3335
4514
  printManualSnippet("claude", apiKey, opts.remoteUrl);
3336
4515
  return;
3337
4516
  }
@@ -3369,7 +4548,16 @@ export function register(program) {
3369
4548
  }
3370
4549
  catch (e) {
3371
4550
  const msg = e instanceof Error ? e.message : String(e);
3372
- console.error(chalk.red(`Error: ${msg}`));
4551
+ console.log();
4552
+ console.log(errorEnvelope({
4553
+ title: "MCP install failed",
4554
+ body: msg,
4555
+ suggest: [
4556
+ "miosa mcp install # try again",
4557
+ "miosa login # re-authenticate first",
4558
+ ],
4559
+ withDebugHint: true,
4560
+ }));
3373
4561
  process.exit(3);
3374
4562
  }
3375
4563
  });