@miosa/cli 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +327 -0
- package/dist/bin/miosa.d.ts +3 -0
- package/dist/bin/miosa.d.ts.map +1 -0
- package/dist/bin/miosa.js +139 -0
- package/dist/bin/miosa.js.map +1 -0
- package/dist/client.d.ts +74 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +523 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/agent.d.ts +18 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +468 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/alerts.d.ts +3 -0
- package/dist/commands/alerts.d.ts.map +1 -0
- package/dist/commands/alerts.js +41 -0
- package/dist/commands/alerts.js.map +1 -0
- package/dist/commands/api-keys.d.ts +3 -0
- package/dist/commands/api-keys.d.ts.map +1 -0
- package/dist/commands/api-keys.js +119 -0
- package/dist/commands/api-keys.js.map +1 -0
- package/dist/commands/api-resource.d.ts +20 -0
- package/dist/commands/api-resource.d.ts.map +1 -0
- package/dist/commands/api-resource.js +120 -0
- package/dist/commands/api-resource.js.map +1 -0
- package/dist/commands/apps.d.ts +3 -0
- package/dist/commands/apps.d.ts.map +1 -0
- package/dist/commands/apps.js +218 -0
- package/dist/commands/apps.js.map +1 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +25 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +363 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/backups.d.ts +3 -0
- package/dist/commands/backups.d.ts.map +1 -0
- package/dist/commands/backups.js +23 -0
- package/dist/commands/backups.js.map +1 -0
- package/dist/commands/checkpoints.d.ts +3 -0
- package/dist/commands/checkpoints.d.ts.map +1 -0
- package/dist/commands/checkpoints.js +33 -0
- package/dist/commands/checkpoints.js.map +1 -0
- package/dist/commands/computers.d.ts +3 -0
- package/dist/commands/computers.d.ts.map +1 -0
- package/dist/commands/computers.js +118 -0
- package/dist/commands/computers.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +114 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/connect.d.ts +3 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +96 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/containers.d.ts +3 -0
- package/dist/commands/containers.d.ts.map +1 -0
- package/dist/commands/containers.js +20 -0
- package/dist/commands/containers.js.map +1 -0
- package/dist/commands/cp.d.ts +3 -0
- package/dist/commands/cp.d.ts.map +1 -0
- package/dist/commands/cp.js +102 -0
- package/dist/commands/cp.js.map +1 -0
- package/dist/commands/cron.d.ts +3 -0
- package/dist/commands/cron.d.ts.map +1 -0
- package/dist/commands/cron.js +65 -0
- package/dist/commands/cron.js.map +1 -0
- package/dist/commands/databases.d.ts +3 -0
- package/dist/commands/databases.d.ts.map +1 -0
- package/dist/commands/databases.js +222 -0
- package/dist/commands/databases.js.map +1 -0
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +174 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/deploy.d.ts +3 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +579 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/desktop.d.ts +3 -0
- package/dist/commands/desktop.d.ts.map +1 -0
- package/dist/commands/desktop.js +276 -0
- package/dist/commands/desktop.js.map +1 -0
- package/dist/commands/dev.d.ts +3 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +246 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +241 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/domains.d.ts +3 -0
- package/dist/commands/domains.d.ts.map +1 -0
- package/dist/commands/domains.js +31 -0
- package/dist/commands/domains.js.map +1 -0
- package/dist/commands/enterprise-util.d.ts +37 -0
- package/dist/commands/enterprise-util.d.ts.map +1 -0
- package/dist/commands/enterprise-util.js +185 -0
- package/dist/commands/enterprise-util.js.map +1 -0
- package/dist/commands/exec.d.ts +3 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/exec.js +68 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/functions.d.ts +3 -0
- package/dist/commands/functions.d.ts.map +1 -0
- package/dist/commands/functions.js +47 -0
- package/dist/commands/functions.js.map +1 -0
- package/dist/commands/gha-runners.d.ts +3 -0
- package/dist/commands/gha-runners.d.ts.map +1 -0
- package/dist/commands/gha-runners.js +33 -0
- package/dist/commands/gha-runners.js.map +1 -0
- package/dist/commands/groups.d.ts +3 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +38 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/host.d.ts +3 -0
- package/dist/commands/host.d.ts.map +1 -0
- package/dist/commands/host.js +74 -0
- package/dist/commands/host.js.map +1 -0
- package/dist/commands/hosts.d.ts +3 -0
- package/dist/commands/hosts.d.ts.map +1 -0
- package/dist/commands/hosts.js +90 -0
- package/dist/commands/hosts.js.map +1 -0
- package/dist/commands/link.d.ts +8 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +124 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +172 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/logs.d.ts +3 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +94 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/ls.d.ts +3 -0
- package/dist/commands/ls.d.ts.map +1 -0
- package/dist/commands/ls.js +67 -0
- package/dist/commands/ls.js.map +1 -0
- package/dist/commands/machines.d.ts +3 -0
- package/dist/commands/machines.d.ts.map +1 -0
- package/dist/commands/machines.js +29 -0
- package/dist/commands/machines.js.map +1 -0
- package/dist/commands/mcp.d.ts +21 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +1021 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/meshes.d.ts +3 -0
- package/dist/commands/meshes.d.ts.map +1 -0
- package/dist/commands/meshes.js +27 -0
- package/dist/commands/meshes.js.map +1 -0
- package/dist/commands/network-policy.d.ts +3 -0
- package/dist/commands/network-policy.d.ts.map +1 -0
- package/dist/commands/network-policy.js +40 -0
- package/dist/commands/network-policy.js.map +1 -0
- package/dist/commands/project.d.ts +4 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +25 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/pull.d.ts +3 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +155 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/regions.d.ts +3 -0
- package/dist/commands/regions.d.ts.map +1 -0
- package/dist/commands/regions.js +67 -0
- package/dist/commands/regions.js.map +1 -0
- package/dist/commands/releases.d.ts +3 -0
- package/dist/commands/releases.d.ts.map +1 -0
- package/dist/commands/releases.js +176 -0
- package/dist/commands/releases.js.map +1 -0
- package/dist/commands/rm.d.ts +3 -0
- package/dist/commands/rm.d.ts.map +1 -0
- package/dist/commands/rm.js +42 -0
- package/dist/commands/rm.js.map +1 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +131 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/sandbox.d.ts +3 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +352 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/schedules.d.ts +3 -0
- package/dist/commands/schedules.d.ts.map +1 -0
- package/dist/commands/schedules.js +37 -0
- package/dist/commands/schedules.js.map +1 -0
- package/dist/commands/secrets.d.ts +3 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +194 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/services.d.ts +3 -0
- package/dist/commands/services.d.ts.map +1 -0
- package/dist/commands/services.js +70 -0
- package/dist/commands/services.js.map +1 -0
- package/dist/commands/shell.d.ts +16 -0
- package/dist/commands/shell.d.ts.map +1 -0
- package/dist/commands/shell.js +527 -0
- package/dist/commands/shell.js.map +1 -0
- package/dist/commands/snapshot.d.ts +10 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +181 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/ssh.d.ts +3 -0
- package/dist/commands/ssh.d.ts.map +1 -0
- package/dist/commands/ssh.js +37 -0
- package/dist/commands/ssh.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +300 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/storage.d.ts +3 -0
- package/dist/commands/storage.d.ts.map +1 -0
- package/dist/commands/storage.js +180 -0
- package/dist/commands/storage.js.map +1 -0
- package/dist/commands/tenant.d.ts +3 -0
- package/dist/commands/tenant.d.ts.map +1 -0
- package/dist/commands/tenant.js +87 -0
- package/dist/commands/tenant.js.map +1 -0
- package/dist/commands/tunnel.d.ts +3 -0
- package/dist/commands/tunnel.d.ts.map +1 -0
- package/dist/commands/tunnel.js +418 -0
- package/dist/commands/tunnel.js.map +1 -0
- package/dist/commands/up.d.ts +14 -0
- package/dist/commands/up.d.ts.map +1 -0
- package/dist/commands/up.js +703 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/commands/util.d.ts +19 -0
- package/dist/commands/util.d.ts.map +1 -0
- package/dist/commands/util.js +116 -0
- package/dist/commands/util.js.map +1 -0
- package/dist/commands/volumes.d.ts +3 -0
- package/dist/commands/volumes.d.ts.map +1 -0
- package/dist/commands/volumes.js +196 -0
- package/dist/commands/volumes.js.map +1 -0
- package/dist/commands/watch.d.ts +3 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +398 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/commands/webhooks.d.ts +3 -0
- package/dist/commands/webhooks.d.ts.map +1 -0
- package/dist/commands/webhooks.js +23 -0
- package/dist/commands/webhooks.js.map +1 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +84 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/commands/workspaces.d.ts +3 -0
- package/dist/commands/workspaces.d.ts.map +1 -0
- package/dist/commands/workspaces.js +87 -0
- package/dist/commands/workspaces.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +129 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +62 -0
- package/dist/errors.js.map +1 -0
- package/dist/framework-detector.d.ts +22 -0
- package/dist/framework-detector.d.ts.map +1 -0
- package/dist/framework-detector.js +373 -0
- package/dist/framework-detector.js.map +1 -0
- package/dist/pty/raw-mode.d.ts +7 -0
- package/dist/pty/raw-mode.d.ts.map +1 -0
- package/dist/pty/raw-mode.js +22 -0
- package/dist/pty/raw-mode.js.map +1 -0
- package/dist/pty/ws-pty-client.d.ts +12 -0
- package/dist/pty/ws-pty-client.d.ts.map +1 -0
- package/dist/pty/ws-pty-client.js +69 -0
- package/dist/pty/ws-pty-client.js.map +1 -0
- package/dist/types.d.ts +326 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/progress.d.ts +10 -0
- package/dist/ui/progress.d.ts.map +1 -0
- package/dist/ui/progress.js +36 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/spinner.d.ts +4 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +7 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/table.d.ts +8 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +46 -0
- package/dist/ui/table.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../src/commands/services.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYzC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6H/C"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { addDataOption, deleteAndPrint, enc, getAndPrint, postAndPrint, runAction, } from "./enterprise-util.js";
|
|
2
|
+
export function register(program) {
|
|
3
|
+
const services = program
|
|
4
|
+
.command("services")
|
|
5
|
+
.description("Manage long-running services on Computers");
|
|
6
|
+
// services list <computer-id>
|
|
7
|
+
services
|
|
8
|
+
.command("list <computer-id>")
|
|
9
|
+
.description("List all services on a Computer")
|
|
10
|
+
.option("--json", "Output as JSON")
|
|
11
|
+
.action((id, opts) => runAction(() => getAndPrint(`/computers/${enc(id)}/services`, opts)));
|
|
12
|
+
// services create <computer-id> --name X --command "..."
|
|
13
|
+
addDataOption(services
|
|
14
|
+
.command("create <computer-id>")
|
|
15
|
+
.description("Create a service on a Computer")
|
|
16
|
+
.option("--name <name>", "Service name")
|
|
17
|
+
.option("--command <cmd>", "Command to run")
|
|
18
|
+
.option("--working-dir <dir>", "Working directory")
|
|
19
|
+
.option("--port <port>", "Port the service listens on"))
|
|
20
|
+
.option("--json", "Output as JSON")
|
|
21
|
+
.action((id, opts) => runAction(() => {
|
|
22
|
+
const flagBody = {};
|
|
23
|
+
if (opts.name)
|
|
24
|
+
flagBody["name"] = opts.name;
|
|
25
|
+
if (opts.command)
|
|
26
|
+
flagBody["command"] = opts.command;
|
|
27
|
+
if (opts.workingDir)
|
|
28
|
+
flagBody["working_dir"] = opts.workingDir;
|
|
29
|
+
if (opts.port)
|
|
30
|
+
flagBody["port"] = Number(opts.port);
|
|
31
|
+
return postAndPrint(`/computers/${enc(id)}/services`, opts, flagBody);
|
|
32
|
+
}));
|
|
33
|
+
// services show <computer-id> <service-id>
|
|
34
|
+
services
|
|
35
|
+
.command("show <computer-id> <service-id>")
|
|
36
|
+
.description("Show a service")
|
|
37
|
+
.option("--json", "Output as JSON")
|
|
38
|
+
.action((id, serviceId, opts) => runAction(() => getAndPrint(`/computers/${enc(id)}/services/${enc(serviceId)}`, opts)));
|
|
39
|
+
// services start <computer-id> <service-id>
|
|
40
|
+
services
|
|
41
|
+
.command("start <computer-id> <service-id>")
|
|
42
|
+
.description("Start a stopped service")
|
|
43
|
+
.option("--json", "Output as JSON")
|
|
44
|
+
.action((id, serviceId, opts) => runAction(() => postAndPrint(`/computers/${enc(id)}/services/${enc(serviceId)}/start`, opts)));
|
|
45
|
+
// services stop <computer-id> <service-id>
|
|
46
|
+
services
|
|
47
|
+
.command("stop <computer-id> <service-id>")
|
|
48
|
+
.description("Stop a running service")
|
|
49
|
+
.option("--json", "Output as JSON")
|
|
50
|
+
.action((id, serviceId, opts) => runAction(() => postAndPrint(`/computers/${enc(id)}/services/${enc(serviceId)}/stop`, opts)));
|
|
51
|
+
// services restart <computer-id> <service-id>
|
|
52
|
+
services
|
|
53
|
+
.command("restart <computer-id> <service-id>")
|
|
54
|
+
.description("Restart a service")
|
|
55
|
+
.option("--json", "Output as JSON")
|
|
56
|
+
.action((id, serviceId, opts) => runAction(() => postAndPrint(`/computers/${enc(id)}/services/${enc(serviceId)}/restart`, opts)));
|
|
57
|
+
// services logs <computer-id> <service-id>
|
|
58
|
+
services
|
|
59
|
+
.command("logs <computer-id> <service-id>")
|
|
60
|
+
.description("Show service logs")
|
|
61
|
+
.option("--json", "Output as JSON")
|
|
62
|
+
.action((id, serviceId, opts) => runAction(() => getAndPrint(`/computers/${enc(id)}/services/${enc(serviceId)}/logs`, opts)));
|
|
63
|
+
// services delete <computer-id> <service-id>
|
|
64
|
+
services
|
|
65
|
+
.command("delete <computer-id> <service-id>")
|
|
66
|
+
.description("Delete a service (stops it first if running)")
|
|
67
|
+
.option("--json", "Output as JSON")
|
|
68
|
+
.action((id, serviceId, opts) => runAction(() => deleteAndPrint(`/computers/${enc(id)}/services/${enc(serviceId)}`, opts)));
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=services.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.js","sourceRoot":"","sources":["../../src/commands/services.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* miosa shell <computer-id>
|
|
3
|
+
*
|
|
4
|
+
* Hybrid interactive session: PTY for shell commands + desktop API for
|
|
5
|
+
* `desktop <action>` lines. Unique to MIOSA — SSH and desktop control in one.
|
|
6
|
+
*
|
|
7
|
+
* PTY lifecycle:
|
|
8
|
+
* POST /api/v1/computers/{id}/terminal → { id, ws_url }
|
|
9
|
+
* WebSocket ws_url → raw terminal I/O
|
|
10
|
+
*
|
|
11
|
+
* Desktop commands are intercepted client-side and dispatched to:
|
|
12
|
+
* /api/v1/computers/{id}/desktop/{action}
|
|
13
|
+
*/
|
|
14
|
+
import type { Command } from "commander";
|
|
15
|
+
export declare function register(program: Command): void;
|
|
16
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/commands/shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAynBzC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuF/C"}
|
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* miosa shell <computer-id>
|
|
3
|
+
*
|
|
4
|
+
* Hybrid interactive session: PTY for shell commands + desktop API for
|
|
5
|
+
* `desktop <action>` lines. Unique to MIOSA — SSH and desktop control in one.
|
|
6
|
+
*
|
|
7
|
+
* PTY lifecycle:
|
|
8
|
+
* POST /api/v1/computers/{id}/terminal → { id, ws_url }
|
|
9
|
+
* WebSocket ws_url → raw terminal I/O
|
|
10
|
+
*
|
|
11
|
+
* Desktop commands are intercepted client-side and dispatched to:
|
|
12
|
+
* /api/v1/computers/{id}/desktop/{action}
|
|
13
|
+
*/
|
|
14
|
+
import { writeFileSync } from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import process from "node:process";
|
|
18
|
+
import WebSocket from "ws";
|
|
19
|
+
import chalk from "chalk";
|
|
20
|
+
import { request } from "undici";
|
|
21
|
+
import { loadConfig } from "../config.js";
|
|
22
|
+
import { MiosaClient } from "../client.js";
|
|
23
|
+
import { handleError } from "./util.js";
|
|
24
|
+
import { spin } from "../ui/spinner.js";
|
|
25
|
+
import { getTerminalSize } from "../pty/raw-mode.js";
|
|
26
|
+
import { NetworkError, mapHttpError } from "../errors.js";
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// PTY creation
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
async function createPty(endpoint, apiKey, computerId) {
|
|
31
|
+
let res;
|
|
32
|
+
try {
|
|
33
|
+
res = await request(`${endpoint}/api/v1/computers/${encodeURIComponent(computerId)}/terminal`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${apiKey}`,
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"User-Agent": "@miosa/cli/0.1.0",
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
cmd: "/bin/bash",
|
|
42
|
+
env: { TERM: "xterm-256color" },
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
throw new NetworkError(`Network error creating PTY: ${err instanceof Error ? err.message : String(err)}`, "Check your connection and endpoint: miosa status");
|
|
48
|
+
}
|
|
49
|
+
if (res.statusCode >= 400) {
|
|
50
|
+
const raw = await res.body.text();
|
|
51
|
+
let body = {};
|
|
52
|
+
try {
|
|
53
|
+
body = JSON.parse(raw);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
body = { message: raw || `HTTP ${res.statusCode}` };
|
|
57
|
+
}
|
|
58
|
+
throw mapHttpError(res.statusCode, body, raw);
|
|
59
|
+
}
|
|
60
|
+
const payload = (await res.body.json());
|
|
61
|
+
// Support both wrapped {data: {...}} and flat responses
|
|
62
|
+
const ticket = payload.data ??
|
|
63
|
+
(payload.id && payload.ws_url
|
|
64
|
+
? { id: payload.id, ws_url: payload.ws_url }
|
|
65
|
+
: (() => {
|
|
66
|
+
throw new Error("Invalid PTY response: missing id or ws_url");
|
|
67
|
+
})());
|
|
68
|
+
return ticket;
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Desktop API dispatch
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
async function desktopRequest(endpoint, apiKey, computerId, sub, body) {
|
|
74
|
+
const method = body !== undefined ? "POST" : "GET";
|
|
75
|
+
let res;
|
|
76
|
+
try {
|
|
77
|
+
res = await request(`${endpoint}/api/v1/computers/${encodeURIComponent(computerId)}/desktop/${sub}`, {
|
|
78
|
+
method,
|
|
79
|
+
headers: {
|
|
80
|
+
Authorization: `Bearer ${apiKey}`,
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
"User-Agent": "@miosa/cli/0.1.0",
|
|
83
|
+
},
|
|
84
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
throw new NetworkError(`Network error calling desktop/${sub}: ${err instanceof Error ? err.message : String(err)}`);
|
|
89
|
+
}
|
|
90
|
+
if (res.statusCode >= 400) {
|
|
91
|
+
const raw = await res.body.text();
|
|
92
|
+
let errBody = {};
|
|
93
|
+
try {
|
|
94
|
+
errBody = JSON.parse(raw);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
errBody = { message: raw || `HTTP ${res.statusCode}` };
|
|
98
|
+
}
|
|
99
|
+
throw mapHttpError(res.statusCode, errBody, raw);
|
|
100
|
+
}
|
|
101
|
+
// Screenshot returns binary — we return the raw buffer for that path
|
|
102
|
+
if (sub === "screenshot") {
|
|
103
|
+
const chunks = [];
|
|
104
|
+
for await (const chunk of res.body) {
|
|
105
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
106
|
+
}
|
|
107
|
+
return Buffer.concat(chunks);
|
|
108
|
+
}
|
|
109
|
+
return res.body.json();
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Desktop command parser
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
function parseDesktopCommand(line) {
|
|
115
|
+
// line is already stripped of leading "desktop "
|
|
116
|
+
const parts = line.trim().split(/\s+/);
|
|
117
|
+
const sub = parts[0] ?? "";
|
|
118
|
+
switch (sub) {
|
|
119
|
+
case "screenshot":
|
|
120
|
+
return { action: "screenshot" };
|
|
121
|
+
case "click": {
|
|
122
|
+
const x = Number(parts[1]);
|
|
123
|
+
const y = Number(parts[2]);
|
|
124
|
+
const button = parts[3] ?? "left";
|
|
125
|
+
if (isNaN(x) || isNaN(y))
|
|
126
|
+
return { action: "unknown", raw: line };
|
|
127
|
+
return { action: "click", x, y, button };
|
|
128
|
+
}
|
|
129
|
+
case "type": {
|
|
130
|
+
// Reconstruct text: everything after "type ", strip optional outer quotes
|
|
131
|
+
const raw = line.slice(sub.length).trim();
|
|
132
|
+
const text = (raw.startsWith('"') && raw.endsWith('"')) ||
|
|
133
|
+
(raw.startsWith("'") && raw.endsWith("'"))
|
|
134
|
+
? raw.slice(1, -1)
|
|
135
|
+
: raw;
|
|
136
|
+
return { action: "type", text };
|
|
137
|
+
}
|
|
138
|
+
case "open": {
|
|
139
|
+
const app = parts.slice(1).join(" ");
|
|
140
|
+
if (!app)
|
|
141
|
+
return { action: "unknown", raw: line };
|
|
142
|
+
return { action: "open", app };
|
|
143
|
+
}
|
|
144
|
+
case "windows":
|
|
145
|
+
return { action: "windows" };
|
|
146
|
+
default:
|
|
147
|
+
return { action: "unknown", raw: line };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Desktop command handler — called from the interactive REPL
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
async function handleDesktopCommand(cmd, endpoint, apiKey, computerId, json) {
|
|
154
|
+
switch (cmd.action) {
|
|
155
|
+
case "screenshot": {
|
|
156
|
+
const png = (await desktopRequest(endpoint, apiKey, computerId, "screenshot"));
|
|
157
|
+
const ts = Date.now();
|
|
158
|
+
const outPath = join(tmpdir(), `miosa-screenshot-${ts}.png`);
|
|
159
|
+
writeFileSync(outPath, png);
|
|
160
|
+
if (json) {
|
|
161
|
+
process.stdout.write(JSON.stringify({
|
|
162
|
+
saved: outPath,
|
|
163
|
+
bytes: png.length,
|
|
164
|
+
timestamp: ts,
|
|
165
|
+
}) + "\n");
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
process.stdout.write(chalk.green(`Screenshot saved to ${outPath}\n`));
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "click": {
|
|
173
|
+
const result = await desktopRequest(endpoint, apiKey, computerId, "click", {
|
|
174
|
+
x: cmd.x,
|
|
175
|
+
y: cmd.y,
|
|
176
|
+
button: cmd.button,
|
|
177
|
+
});
|
|
178
|
+
if (json) {
|
|
179
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
process.stdout.write(chalk.green(`Clicked at (${cmd.x}, ${cmd.y}) [${cmd.button}]\n`));
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "type": {
|
|
187
|
+
const result = await desktopRequest(endpoint, apiKey, computerId, "type", {
|
|
188
|
+
text: cmd.text,
|
|
189
|
+
});
|
|
190
|
+
if (json) {
|
|
191
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
process.stdout.write(chalk.green(`Typed: ${cmd.text}\n`));
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "open": {
|
|
199
|
+
const result = await desktopRequest(endpoint, apiKey, computerId, "launch", { app: cmd.app });
|
|
200
|
+
if (json) {
|
|
201
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
process.stdout.write(chalk.green(`Launched ${cmd.app}\n`));
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case "windows": {
|
|
209
|
+
const result = await desktopRequest(endpoint, apiKey, computerId, "windows");
|
|
210
|
+
if (json) {
|
|
211
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// Pretty-print: result is likely an array of window objects
|
|
215
|
+
if (Array.isArray(result)) {
|
|
216
|
+
if (result.length === 0) {
|
|
217
|
+
process.stdout.write(chalk.dim("No open windows.\n"));
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
for (const w of result) {
|
|
221
|
+
const title = String(w["title"] ?? w["name"] ?? "(untitled)");
|
|
222
|
+
const id = w["id"] !== undefined ? ` [${String(w["id"])}]` : "";
|
|
223
|
+
process.stdout.write(` ${title}${id}\n`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "unknown":
|
|
234
|
+
process.stdout.write(chalk.yellow(`Unknown desktop command: ${cmd.raw}\n` +
|
|
235
|
+
`Available: screenshot | click X Y [button] | type "text" | open APP | windows\n`));
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Help text for the interactive session
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
function printSessionHelp(computerName) {
|
|
243
|
+
process.stdout.write(chalk.dim(`Connected to ${computerName}. Type commands normally. Special commands:\n` +
|
|
244
|
+
` desktop screenshot - take screenshot (saved to /tmp/)\n` +
|
|
245
|
+
` desktop click X Y - click at coordinates\n` +
|
|
246
|
+
` desktop click X Y right - right-click at coordinates\n` +
|
|
247
|
+
` desktop type "text" - type text on desktop\n` +
|
|
248
|
+
` desktop open APP - launch an application\n` +
|
|
249
|
+
` desktop windows - list open windows\n` +
|
|
250
|
+
` exit - disconnect\n\n`));
|
|
251
|
+
}
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// Connection banner
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
function formatBanner(computer) {
|
|
256
|
+
const name = computer.name || computer.id;
|
|
257
|
+
const image = computer.image ?? "unknown";
|
|
258
|
+
const cpuCount = computer.cpu_count ?? computer.vcpu ?? computer.cpu ?? null;
|
|
259
|
+
const ramMb = computer.ram_mb ?? computer.memory_mb ?? null;
|
|
260
|
+
const ram = ramMb !== null ? `${Math.round(ramMb / 1024)}GB RAM` : null;
|
|
261
|
+
const cpu = cpuCount !== null ? `${cpuCount} CPU` : null;
|
|
262
|
+
const specs = [cpu, ram].filter(Boolean).join(", ");
|
|
263
|
+
const specStr = specs ? `, ${specs}` : "";
|
|
264
|
+
return (chalk.bold.green(`Connected to ${name}`) +
|
|
265
|
+
chalk.dim(` (${image}${specStr})`));
|
|
266
|
+
}
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// JSON mode (non-interactive) — run a single desktop or shell command
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
async function runJsonMode(opts) {
|
|
271
|
+
if (opts.desktopCmd) {
|
|
272
|
+
const parsed = parseDesktopCommand(opts.desktopCmd);
|
|
273
|
+
await handleDesktopCommand(parsed, opts.endpoint, opts.apiKey, opts.computerId, true);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (opts.shellCmd) {
|
|
277
|
+
// Use computerExec SSE stream for non-interactive shell command
|
|
278
|
+
const client = new MiosaClient(loadConfig());
|
|
279
|
+
const res = await client.computerExec(opts.computerId, opts.shellCmd);
|
|
280
|
+
const { parseSse } = await import("../client.js");
|
|
281
|
+
for await (const event of parseSse(res.body)) {
|
|
282
|
+
switch (event.type) {
|
|
283
|
+
case "stdout":
|
|
284
|
+
process.stdout.write(event.data);
|
|
285
|
+
break;
|
|
286
|
+
case "stderr":
|
|
287
|
+
process.stderr.write(event.data);
|
|
288
|
+
break;
|
|
289
|
+
case "exit":
|
|
290
|
+
process.exit(event.exit_code);
|
|
291
|
+
break;
|
|
292
|
+
case "error":
|
|
293
|
+
process.stderr.write(`Error: ${event.message}\n`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
break;
|
|
296
|
+
default:
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Interactive shell session
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
async function runInteractiveSession(opts) {
|
|
306
|
+
const { computer, ticket, endpoint, apiKey } = opts;
|
|
307
|
+
const computerId = computer.id;
|
|
308
|
+
const computerName = computer.name || computerId;
|
|
309
|
+
return new Promise((resolve) => {
|
|
310
|
+
const ws = new WebSocket(ticket.ws_url, {
|
|
311
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
312
|
+
});
|
|
313
|
+
// Track whether we're in the middle of a desktop call so we can suppress
|
|
314
|
+
// echoing that line to the PTY.
|
|
315
|
+
let pendingDesktop = false;
|
|
316
|
+
// Buffer for intercepting typed lines — we use readline in "line mode" but
|
|
317
|
+
// the PTY expects raw chars. We switch strategy based on whether the user
|
|
318
|
+
// is typing a desktop command (line-buffered check) vs normal (raw).
|
|
319
|
+
let lineBuffer = "";
|
|
320
|
+
let inDesktopCapture = false;
|
|
321
|
+
let cleanedUp = false;
|
|
322
|
+
function cleanup(code) {
|
|
323
|
+
if (cleanedUp)
|
|
324
|
+
return;
|
|
325
|
+
cleanedUp = true;
|
|
326
|
+
// Restore terminal
|
|
327
|
+
if (process.stdin.isTTY) {
|
|
328
|
+
process.stdin.setRawMode(false);
|
|
329
|
+
process.stdin.pause();
|
|
330
|
+
}
|
|
331
|
+
process.stdin.removeAllListeners("data");
|
|
332
|
+
process.removeAllListeners("SIGWINCH");
|
|
333
|
+
if (ws.readyState === WebSocket.OPEN)
|
|
334
|
+
ws.close();
|
|
335
|
+
resolve(code);
|
|
336
|
+
}
|
|
337
|
+
function sendResize() {
|
|
338
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
339
|
+
return;
|
|
340
|
+
const { cols, rows } = getTerminalSize();
|
|
341
|
+
ws.send(JSON.stringify({ type: "resize", cols, rows }));
|
|
342
|
+
}
|
|
343
|
+
ws.on("open", () => {
|
|
344
|
+
// Put terminal in raw mode so the PTY gets key-by-key input
|
|
345
|
+
if (process.stdin.isTTY) {
|
|
346
|
+
process.stdin.setRawMode(true);
|
|
347
|
+
process.stdin.resume();
|
|
348
|
+
}
|
|
349
|
+
sendResize();
|
|
350
|
+
// Print the connection banner after the WS is open
|
|
351
|
+
process.stdout.write(formatBanner(computer) + "\n");
|
|
352
|
+
printSessionHelp(computerName);
|
|
353
|
+
process.on("SIGWINCH", sendResize);
|
|
354
|
+
// stdin data handler — intercept "desktop " lines; forward everything
|
|
355
|
+
// else raw to the PTY.
|
|
356
|
+
process.stdin.on("data", (chunk) => {
|
|
357
|
+
const str = chunk.toString("utf8");
|
|
358
|
+
for (const char of str) {
|
|
359
|
+
const code = char.charCodeAt(0);
|
|
360
|
+
// Ctrl+C (0x03) — forward to PTY (interrupt running process), do NOT exit CLI
|
|
361
|
+
if (code === 0x03) {
|
|
362
|
+
if (!pendingDesktop && ws.readyState === WebSocket.OPEN) {
|
|
363
|
+
ws.send(chunk);
|
|
364
|
+
}
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
// Ctrl+D (0x04) — disconnect
|
|
368
|
+
if (code === 0x04) {
|
|
369
|
+
cleanup(0);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
// Accumulate characters to detect "desktop " prefix
|
|
373
|
+
if (inDesktopCapture) {
|
|
374
|
+
if (char === "\r" || char === "\n") {
|
|
375
|
+
// End of desktop command line — process it
|
|
376
|
+
const captured = lineBuffer;
|
|
377
|
+
lineBuffer = "";
|
|
378
|
+
inDesktopCapture = false;
|
|
379
|
+
pendingDesktop = true;
|
|
380
|
+
// Echo a newline back to user (raw mode won't auto-echo)
|
|
381
|
+
process.stdout.write("\r\n");
|
|
382
|
+
const parsed = parseDesktopCommand(captured);
|
|
383
|
+
void handleDesktopCommand(parsed, endpoint, apiKey, computerId, false).finally(() => {
|
|
384
|
+
pendingDesktop = false;
|
|
385
|
+
// Re-emit the shell prompt hint
|
|
386
|
+
process.stdout.write(chalk.dim(`${computerName}> `));
|
|
387
|
+
});
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (char === "\x7f" || char === "\x08") {
|
|
391
|
+
// Backspace
|
|
392
|
+
if (lineBuffer.length > 0) {
|
|
393
|
+
lineBuffer = lineBuffer.slice(0, -1);
|
|
394
|
+
// Check if we've deleted back past "desktop "
|
|
395
|
+
if (!lineBuffer.startsWith("desktop ") &&
|
|
396
|
+
lineBuffer !== "desktop") {
|
|
397
|
+
inDesktopCapture = false;
|
|
398
|
+
// Forward the accumulated buffer so far to the PTY
|
|
399
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
400
|
+
ws.send(Buffer.from("desktop " + lineBuffer + "\x08"));
|
|
401
|
+
}
|
|
402
|
+
lineBuffer = "";
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
process.stdout.write("\x08 \x08");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
// Regular character — accumulate and echo
|
|
411
|
+
lineBuffer += char;
|
|
412
|
+
process.stdout.write(char);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
// Not in desktop capture yet — check if this starts a desktop command
|
|
416
|
+
lineBuffer += char;
|
|
417
|
+
if ("desktop ".startsWith(lineBuffer) &&
|
|
418
|
+
lineBuffer.length <= "desktop ".length) {
|
|
419
|
+
// Could be the start of a desktop command — keep buffering silently
|
|
420
|
+
// but echo to user so they see what they type
|
|
421
|
+
process.stdout.write(char);
|
|
422
|
+
if (lineBuffer === "desktop ") {
|
|
423
|
+
inDesktopCapture = true;
|
|
424
|
+
// Keep lineBuffer empty for the subcommand part
|
|
425
|
+
lineBuffer = "";
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// Not a desktop command — flush buffer + current char to PTY
|
|
430
|
+
if (lineBuffer.length > 0) {
|
|
431
|
+
const flush = lineBuffer; // includes current char
|
|
432
|
+
lineBuffer = "";
|
|
433
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
434
|
+
ws.send(Buffer.from(flush));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
// PTY output → local stdout
|
|
441
|
+
ws.on("message", (data) => {
|
|
442
|
+
if (!pendingDesktop) {
|
|
443
|
+
process.stdout.write(typeof data === "string" ? data : data);
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
ws.on("close", (code) => {
|
|
447
|
+
cleanup(code === 1000 ? 0 : 1);
|
|
448
|
+
});
|
|
449
|
+
ws.on("error", (err) => {
|
|
450
|
+
process.stderr.write(`\r\nWebSocket error: ${err.message}\r\n`);
|
|
451
|
+
cleanup(2);
|
|
452
|
+
});
|
|
453
|
+
process.stdin.on("end", () => {
|
|
454
|
+
ws.close();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
// ---------------------------------------------------------------------------
|
|
459
|
+
// Commander registration
|
|
460
|
+
// ---------------------------------------------------------------------------
|
|
461
|
+
export function register(program) {
|
|
462
|
+
program
|
|
463
|
+
.command("shell <computer-id>")
|
|
464
|
+
.description("Open an interactive shell + desktop control session on a Computer (SSH and desktop in one)")
|
|
465
|
+
.option("--json", "Non-interactive mode — print structured JSON output")
|
|
466
|
+
.option("--desktop <cmd>", 'Run a single desktop command (e.g. "screenshot")')
|
|
467
|
+
.option("--cmd <command>", "Run a single shell command and exit")
|
|
468
|
+
.action(async (computerIdArg, opts) => {
|
|
469
|
+
try {
|
|
470
|
+
const config = loadConfig();
|
|
471
|
+
if (!config.api_key) {
|
|
472
|
+
process.stderr.write(chalk.red("You are not logged in. Run: miosa auth login\n"));
|
|
473
|
+
process.exit(3);
|
|
474
|
+
}
|
|
475
|
+
const endpoint = config.endpoint.replace(/\/$/, "");
|
|
476
|
+
const apiKey = config.api_key;
|
|
477
|
+
// --- Non-interactive JSON / single-command mode ---
|
|
478
|
+
if (opts.json || opts.desktop || opts.cmd) {
|
|
479
|
+
await runJsonMode({
|
|
480
|
+
computerId: computerIdArg,
|
|
481
|
+
desktopCmd: opts.desktop,
|
|
482
|
+
shellCmd: opts.cmd,
|
|
483
|
+
endpoint,
|
|
484
|
+
apiKey,
|
|
485
|
+
});
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
// --- Interactive mode ---
|
|
489
|
+
const spinner = spin(`Connecting to ${computerIdArg}...`);
|
|
490
|
+
const client = new MiosaClient(config);
|
|
491
|
+
// Fetch computer details for the banner (best-effort — don't block on 404)
|
|
492
|
+
let computer;
|
|
493
|
+
try {
|
|
494
|
+
computer = await client
|
|
495
|
+
.apiGet(`/api/v1/computers/${encodeURIComponent(computerIdArg)}`)
|
|
496
|
+
.then((r) => r.data ?? r);
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
// Fall back to a minimal stub so we can still connect
|
|
500
|
+
computer = {
|
|
501
|
+
id: computerIdArg,
|
|
502
|
+
name: computerIdArg,
|
|
503
|
+
state: "unknown",
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
if (computer.state !== "running" && computer.state !== "unknown") {
|
|
507
|
+
spinner.warn(`Computer "${computer.name || computerIdArg}" is ${computer.state}. Connection may fail.`);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
spinner.text = `Opening PTY on ${computer.name || computerIdArg}...`;
|
|
511
|
+
}
|
|
512
|
+
const ticket = await createPty(endpoint, apiKey, computerIdArg);
|
|
513
|
+
spinner.stop();
|
|
514
|
+
const exitCode = await runInteractiveSession({
|
|
515
|
+
computer,
|
|
516
|
+
ticket,
|
|
517
|
+
endpoint,
|
|
518
|
+
apiKey,
|
|
519
|
+
});
|
|
520
|
+
process.exit(exitCode);
|
|
521
|
+
}
|
|
522
|
+
catch (err) {
|
|
523
|
+
handleError(err);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
//# sourceMappingURL=shell.js.map
|