@boxes-dev/dvb 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/dvb.cjs +28232 -0
- package/dist/bin/dvb.d.ts +2 -0
- package/dist/bin/dvb.d.ts.map +1 -0
- package/dist/bin/dvb.js +71 -0
- package/dist/bin/dvb.js.map +1 -0
- package/dist/bin/dvbd.cjs +11816 -0
- package/dist/bin/dvbd.d.ts +2 -0
- package/dist/bin/dvbd.d.ts.map +1 -0
- package/dist/bin/dvbd.js +20 -0
- package/dist/bin/dvbd.js.map +1 -0
- package/dist/codex/services-schema.json +59 -0
- package/dist/codex/setup-schema.json +176 -0
- package/dist/devbox/auth.d.ts +17 -0
- package/dist/devbox/auth.d.ts.map +1 -0
- package/dist/devbox/auth.js +302 -0
- package/dist/devbox/auth.js.map +1 -0
- package/dist/devbox/cli.d.ts +2 -0
- package/dist/devbox/cli.d.ts.map +1 -0
- package/dist/devbox/cli.js +142 -0
- package/dist/devbox/cli.js.map +1 -0
- package/dist/devbox/commands/agent.d.ts +21 -0
- package/dist/devbox/commands/agent.d.ts.map +1 -0
- package/dist/devbox/commands/agent.js +257 -0
- package/dist/devbox/commands/agent.js.map +1 -0
- package/dist/devbox/commands/boxSelect.d.ts +14 -0
- package/dist/devbox/commands/boxSelect.d.ts.map +1 -0
- package/dist/devbox/commands/boxSelect.js +82 -0
- package/dist/devbox/commands/boxSelect.js.map +1 -0
- package/dist/devbox/commands/connect.d.ts +8 -0
- package/dist/devbox/commands/connect.d.ts.map +1 -0
- package/dist/devbox/commands/connect.js +1369 -0
- package/dist/devbox/commands/connect.js.map +1 -0
- package/dist/devbox/commands/daemon.d.ts +4 -0
- package/dist/devbox/commands/daemon.d.ts.map +1 -0
- package/dist/devbox/commands/daemon.js +36 -0
- package/dist/devbox/commands/daemon.js.map +1 -0
- package/dist/devbox/commands/destroy.d.ts +2 -0
- package/dist/devbox/commands/destroy.d.ts.map +1 -0
- package/dist/devbox/commands/destroy.js +131 -0
- package/dist/devbox/commands/destroy.js.map +1 -0
- package/dist/devbox/commands/init/args.d.ts +12 -0
- package/dist/devbox/commands/init/args.d.ts.map +1 -0
- package/dist/devbox/commands/init/args.js +42 -0
- package/dist/devbox/commands/init/args.js.map +1 -0
- package/dist/devbox/commands/init/codex/artifacts.d.ts +26 -0
- package/dist/devbox/commands/init/codex/artifacts.d.ts.map +1 -0
- package/dist/devbox/commands/init/codex/artifacts.js +108 -0
- package/dist/devbox/commands/init/codex/artifacts.js.map +1 -0
- package/dist/devbox/commands/init/codex/index.d.ts +35 -0
- package/dist/devbox/commands/init/codex/index.d.ts.map +1 -0
- package/dist/devbox/commands/init/codex/index.js +147 -0
- package/dist/devbox/commands/init/codex/index.js.map +1 -0
- package/dist/devbox/commands/init/codex/local.d.ts +19 -0
- package/dist/devbox/commands/init/codex/local.d.ts.map +1 -0
- package/dist/devbox/commands/init/codex/local.js +189 -0
- package/dist/devbox/commands/init/codex/local.js.map +1 -0
- package/dist/devbox/commands/init/codex/plan.d.ts +61 -0
- package/dist/devbox/commands/init/codex/plan.d.ts.map +1 -0
- package/dist/devbox/commands/init/codex/plan.js +39 -0
- package/dist/devbox/commands/init/codex/plan.js.map +1 -0
- package/dist/devbox/commands/init/codex/prompts.d.ts +6 -0
- package/dist/devbox/commands/init/codex/prompts.d.ts.map +1 -0
- package/dist/devbox/commands/init/codex/prompts.js +26 -0
- package/dist/devbox/commands/init/codex/prompts.js.map +1 -0
- package/dist/devbox/commands/init/codex/remote.d.ts +56 -0
- package/dist/devbox/commands/init/codex/remote.d.ts.map +1 -0
- package/dist/devbox/commands/init/codex/remote.js +395 -0
- package/dist/devbox/commands/init/codex/remote.js.map +1 -0
- package/dist/devbox/commands/init/codex/template.d.ts +3 -0
- package/dist/devbox/commands/init/codex/template.d.ts.map +1 -0
- package/dist/devbox/commands/init/codex/template.js +3 -0
- package/dist/devbox/commands/init/codex/template.js.map +1 -0
- package/dist/devbox/commands/init/index.d.ts +2 -0
- package/dist/devbox/commands/init/index.d.ts.map +1 -0
- package/dist/devbox/commands/init/index.js +1117 -0
- package/dist/devbox/commands/init/index.js.map +1 -0
- package/dist/devbox/commands/init/packaging.d.ts +5 -0
- package/dist/devbox/commands/init/packaging.d.ts.map +1 -0
- package/dist/devbox/commands/init/packaging.js +86 -0
- package/dist/devbox/commands/init/packaging.js.map +1 -0
- package/dist/devbox/commands/init/registry.d.ts +14 -0
- package/dist/devbox/commands/init/registry.d.ts.map +1 -0
- package/dist/devbox/commands/init/registry.js +61 -0
- package/dist/devbox/commands/init/registry.js.map +1 -0
- package/dist/devbox/commands/init/remote.d.ts +38 -0
- package/dist/devbox/commands/init/remote.d.ts.map +1 -0
- package/dist/devbox/commands/init/remote.js +554 -0
- package/dist/devbox/commands/init/remote.js.map +1 -0
- package/dist/devbox/commands/init/repo.d.ts +31 -0
- package/dist/devbox/commands/init/repo.d.ts.map +1 -0
- package/dist/devbox/commands/init/repo.js +164 -0
- package/dist/devbox/commands/init/repo.js.map +1 -0
- package/dist/devbox/commands/init/scripts.d.ts +3 -0
- package/dist/devbox/commands/init/scripts.d.ts.map +1 -0
- package/dist/devbox/commands/init/scripts.js +15 -0
- package/dist/devbox/commands/init/scripts.js.map +1 -0
- package/dist/devbox/commands/init/ssh.d.ts +19 -0
- package/dist/devbox/commands/init/ssh.d.ts.map +1 -0
- package/dist/devbox/commands/init/ssh.js +270 -0
- package/dist/devbox/commands/init/ssh.js.map +1 -0
- package/dist/devbox/commands/init/state.d.ts +31 -0
- package/dist/devbox/commands/init/state.d.ts.map +1 -0
- package/dist/devbox/commands/init/state.js +25 -0
- package/dist/devbox/commands/init/state.js.map +1 -0
- package/dist/devbox/commands/list.d.ts +2 -0
- package/dist/devbox/commands/list.d.ts.map +1 -0
- package/dist/devbox/commands/list.js +213 -0
- package/dist/devbox/commands/list.js.map +1 -0
- package/dist/devbox/commands/listFormatting.d.ts +27 -0
- package/dist/devbox/commands/listFormatting.d.ts.map +1 -0
- package/dist/devbox/commands/listFormatting.js +155 -0
- package/dist/devbox/commands/listFormatting.js.map +1 -0
- package/dist/devbox/commands/logout.d.ts +2 -0
- package/dist/devbox/commands/logout.d.ts.map +1 -0
- package/dist/devbox/commands/logout.js +45 -0
- package/dist/devbox/commands/logout.js.map +1 -0
- package/dist/devbox/commands/mount.d.ts +3 -0
- package/dist/devbox/commands/mount.d.ts.map +1 -0
- package/dist/devbox/commands/mount.js +144 -0
- package/dist/devbox/commands/mount.js.map +1 -0
- package/dist/devbox/commands/mountSsh.d.ts +12 -0
- package/dist/devbox/commands/mountSsh.d.ts.map +1 -0
- package/dist/devbox/commands/mountSsh.js +137 -0
- package/dist/devbox/commands/mountSsh.js.map +1 -0
- package/dist/devbox/commands/ports.d.ts +2 -0
- package/dist/devbox/commands/ports.d.ts.map +1 -0
- package/dist/devbox/commands/ports.js +145 -0
- package/dist/devbox/commands/ports.js.map +1 -0
- package/dist/devbox/commands/services.d.ts +2 -0
- package/dist/devbox/commands/services.d.ts.map +1 -0
- package/dist/devbox/commands/services.js +552 -0
- package/dist/devbox/commands/services.js.map +1 -0
- package/dist/devbox/commands/servicesToml.d.ts +13 -0
- package/dist/devbox/commands/servicesToml.d.ts.map +1 -0
- package/dist/devbox/commands/servicesToml.js +198 -0
- package/dist/devbox/commands/servicesToml.js.map +1 -0
- package/dist/devbox/commands/sessionUtils.d.ts +10 -0
- package/dist/devbox/commands/sessionUtils.d.ts.map +1 -0
- package/dist/devbox/commands/sessionUtils.js +48 -0
- package/dist/devbox/commands/sessionUtils.js.map +1 -0
- package/dist/devbox/commands/sessions.d.ts +2 -0
- package/dist/devbox/commands/sessions.d.ts.map +1 -0
- package/dist/devbox/commands/sessions.js +425 -0
- package/dist/devbox/commands/sessions.js.map +1 -0
- package/dist/devbox/commands/setup.d.ts +2 -0
- package/dist/devbox/commands/setup.d.ts.map +1 -0
- package/dist/devbox/commands/setup.js +183 -0
- package/dist/devbox/commands/setup.js.map +1 -0
- package/dist/devbox/commands/wezterm.d.ts +3 -0
- package/dist/devbox/commands/wezterm.d.ts.map +1 -0
- package/dist/devbox/commands/wezterm.js +878 -0
- package/dist/devbox/commands/wezterm.js.map +1 -0
- package/dist/devbox/completions/cache.d.ts +59 -0
- package/dist/devbox/completions/cache.d.ts.map +1 -0
- package/dist/devbox/completions/cache.js +122 -0
- package/dist/devbox/completions/cache.js.map +1 -0
- package/dist/devbox/completions/index.d.ts +14 -0
- package/dist/devbox/completions/index.d.ts.map +1 -0
- package/dist/devbox/completions/index.js +796 -0
- package/dist/devbox/completions/index.js.map +1 -0
- package/dist/devbox/controlPlane.d.ts +44 -0
- package/dist/devbox/controlPlane.d.ts.map +1 -0
- package/dist/devbox/controlPlane.js +310 -0
- package/dist/devbox/controlPlane.js.map +1 -0
- package/dist/devbox/daemonClient.d.ts +28 -0
- package/dist/devbox/daemonClient.d.ts.map +1 -0
- package/dist/devbox/daemonClient.js +276 -0
- package/dist/devbox/daemonClient.js.map +1 -0
- package/dist/devbox/latency.d.ts +25 -0
- package/dist/devbox/latency.d.ts.map +1 -0
- package/dist/devbox/latency.js +186 -0
- package/dist/devbox/latency.js.map +1 -0
- package/dist/devbox/logger.d.ts +2 -0
- package/dist/devbox/logger.d.ts.map +1 -0
- package/dist/devbox/logger.js +3 -0
- package/dist/devbox/logger.js.map +1 -0
- package/dist/devbox/terminal/osc.d.ts +10 -0
- package/dist/devbox/terminal/osc.d.ts.map +1 -0
- package/dist/devbox/terminal/osc.js +88 -0
- package/dist/devbox/terminal/osc.js.map +1 -0
- package/dist/devbox/types.d.ts +4 -0
- package/dist/devbox/types.d.ts.map +1 -0
- package/dist/devbox/types.js +2 -0
- package/dist/devbox/types.js.map +1 -0
- package/dist/devbox/ui/colors.d.ts +19 -0
- package/dist/devbox/ui/colors.d.ts.map +1 -0
- package/dist/devbox/ui/colors.js +22 -0
- package/dist/devbox/ui/colors.js.map +1 -0
- package/dist/devbox/ui/statusLine.d.ts +14 -0
- package/dist/devbox/ui/statusLine.d.ts.map +1 -0
- package/dist/devbox/ui/statusLine.js +90 -0
- package/dist/devbox/ui/statusLine.js.map +1 -0
- package/dist/devbox/weztermMux.d.ts +11 -0
- package/dist/devbox/weztermMux.d.ts.map +1 -0
- package/dist/devbox/weztermMux.js +11 -0
- package/dist/devbox/weztermMux.js.map +1 -0
- package/dist/prompts/local-scan.md +32 -0
- package/dist/prompts/local-services-scan.md +17 -0
- package/dist/prompts/remote-apply.md +370 -0
- package/dist/scripts/fix-bashrc.sh +48 -0
- package/package.json +38 -0
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import readline from "node:readline/promises";
|
|
7
|
+
import { createLogger, createSecretStore, createSpritesClient, loadConfig, resolveSocketInfo, resolveSpritesApiUrl, } from "@boxes-dev/core";
|
|
8
|
+
import { DAEMON_TIMEOUT_MS, ensureDaemonRunning, requestJson, requireDaemonFeatures, } from "../daemonClient.js";
|
|
9
|
+
import { ensureSpritesToken } from "../auth.js";
|
|
10
|
+
import { selectDevbox } from "./boxSelect.js";
|
|
11
|
+
import { WEZTERM_MUX_SERVICE_NAME, WEZTERM_MUX_TCP_PORT, } from "../weztermMux.js";
|
|
12
|
+
import { ensureWeztermMuxService, installWeztermMux, isWeztermMuxHealthy, } from "./init/remote.js";
|
|
13
|
+
const logger = createLogger("devbox-wezterm");
|
|
14
|
+
const WEZTERM_APP_PATH = "/Applications/WezTerm.app";
|
|
15
|
+
const WEZTERM_BIN_PATH = "/Applications/WezTerm.app/Contents/MacOS/wezterm";
|
|
16
|
+
const WEZTERM_BIN_DIR = "/Applications/WezTerm.app/Contents/MacOS";
|
|
17
|
+
const WEZTERM_INSTALL_URL = "https://wezfurlong.org/wezterm/install.html";
|
|
18
|
+
const WEZTERM_PATH_EXPORT = 'export PATH="$PATH:/Applications/WezTerm.app/Contents/MacOS"';
|
|
19
|
+
const WEZTERM_HEALTH_POLL_INTERVAL_MS = 200;
|
|
20
|
+
const WEZTERM_HEALTH_POLL_TIMEOUT_MS = 6_000;
|
|
21
|
+
const WEZTERM_PROXY_ENV = [
|
|
22
|
+
"DEVBOX_LOG_LEVEL=silent",
|
|
23
|
+
"DEVBOX_WEZTERM_PROXY=1",
|
|
24
|
+
"NODE_NO_WARNINGS=1",
|
|
25
|
+
];
|
|
26
|
+
const WEZTERM_PROXY_LOG_NAME = "proxy.log";
|
|
27
|
+
const WEZTERM_USAGE = "Usage: dvb wezterm [<box>] [--port <port>] [--domain <name>] [--path <path>]\n" +
|
|
28
|
+
" dvb wezterm proxy [<box>] [--port <port>] [--version <ver>] [--asset-url <url>]\n" +
|
|
29
|
+
" dvb wezterm setup [<box>] [--port <port>] [--domain <name>] [--path <path>]";
|
|
30
|
+
const parseWeztermArgs = (args) => {
|
|
31
|
+
const parsed = {};
|
|
32
|
+
if (!args.length) {
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
const subcommand = args[0];
|
|
36
|
+
if (!subcommand) {
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
const rest = args.slice(1);
|
|
40
|
+
let argList = rest;
|
|
41
|
+
if (subcommand === "proxy" || subcommand === "setup") {
|
|
42
|
+
parsed.subcommand = subcommand;
|
|
43
|
+
}
|
|
44
|
+
else if (subcommand.startsWith("-")) {
|
|
45
|
+
// `dvb wezterm --domain ...` should behave like `dvb wezterm <box> --domain ...`
|
|
46
|
+
// and prompt for a box when multiple exist.
|
|
47
|
+
parsed.subcommand = "launch";
|
|
48
|
+
argList = args;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
parsed.subcommand = "launch";
|
|
52
|
+
parsed.box = subcommand;
|
|
53
|
+
argList = rest;
|
|
54
|
+
}
|
|
55
|
+
for (let i = 0; i < argList.length; i += 1) {
|
|
56
|
+
const arg = argList[i];
|
|
57
|
+
if (!arg)
|
|
58
|
+
continue;
|
|
59
|
+
if (arg === "--help" || arg === "-h") {
|
|
60
|
+
throw new Error(WEZTERM_USAGE);
|
|
61
|
+
}
|
|
62
|
+
if (arg === "--port") {
|
|
63
|
+
const value = argList[i + 1];
|
|
64
|
+
if (!value)
|
|
65
|
+
throw new Error("Missing value for --port.");
|
|
66
|
+
const parsedPort = Number.parseInt(value, 10);
|
|
67
|
+
if (!Number.isFinite(parsedPort) || parsedPort <= 0) {
|
|
68
|
+
throw new Error(`Invalid port: ${value}`);
|
|
69
|
+
}
|
|
70
|
+
parsed.port = parsedPort;
|
|
71
|
+
i += 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (arg === "--domain") {
|
|
75
|
+
const value = argList[i + 1];
|
|
76
|
+
if (!value)
|
|
77
|
+
throw new Error("Missing value for --domain.");
|
|
78
|
+
parsed.domainName = value;
|
|
79
|
+
i += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg === "--path") {
|
|
83
|
+
const value = argList[i + 1];
|
|
84
|
+
if (!value)
|
|
85
|
+
throw new Error("Missing value for --path.");
|
|
86
|
+
parsed.configPath = value;
|
|
87
|
+
i += 1;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (arg === "--version") {
|
|
91
|
+
const value = argList[i + 1];
|
|
92
|
+
if (!value)
|
|
93
|
+
throw new Error("Missing value for --version.");
|
|
94
|
+
parsed.version = value;
|
|
95
|
+
i += 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (arg === "--asset-url") {
|
|
99
|
+
const value = argList[i + 1];
|
|
100
|
+
if (!value)
|
|
101
|
+
throw new Error("Missing value for --asset-url.");
|
|
102
|
+
parsed.assetUrl = value;
|
|
103
|
+
i += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (arg.startsWith("-")) {
|
|
107
|
+
throw new Error(`Unknown wezterm flag: ${arg}`);
|
|
108
|
+
}
|
|
109
|
+
if (!parsed.box) {
|
|
110
|
+
parsed.box = arg;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
114
|
+
}
|
|
115
|
+
return parsed;
|
|
116
|
+
};
|
|
117
|
+
const resolveCanonical = async (box) => {
|
|
118
|
+
const socketInfo = resolveSocketInfo();
|
|
119
|
+
await ensureDaemonRunning(socketInfo.socketPath);
|
|
120
|
+
await requireDaemonFeatures(socketInfo.socketPath, ["registry"]);
|
|
121
|
+
const response = await requestJson(socketInfo.socketPath, "GET", `/registry/alias?alias=${encodeURIComponent(box)}`, DAEMON_TIMEOUT_MS.registry);
|
|
122
|
+
return response.body.canonical ?? box;
|
|
123
|
+
};
|
|
124
|
+
const initWeztermClient = async (box) => {
|
|
125
|
+
const canonical = await resolveCanonical(box);
|
|
126
|
+
const config = await loadConfig(process.env.HOME ? { homeDir: process.env.HOME } : undefined);
|
|
127
|
+
const store = await createSecretStore(config?.tokenStore, process.env.HOME ? { homeDir: process.env.HOME } : undefined);
|
|
128
|
+
const { token } = await ensureSpritesToken(store, () => { });
|
|
129
|
+
const apiBaseUrl = resolveSpritesApiUrl(config);
|
|
130
|
+
const client = createSpritesClient({ apiBaseUrl, token });
|
|
131
|
+
const sprite = await client.getSprite(canonical);
|
|
132
|
+
if (!sprite) {
|
|
133
|
+
throw new Error(`No devbox found named "${box}". Run "dvb init" first.`);
|
|
134
|
+
}
|
|
135
|
+
return { canonical, client };
|
|
136
|
+
};
|
|
137
|
+
const waitForWeztermMuxHealthy = async (client, canonical) => {
|
|
138
|
+
const start = Date.now();
|
|
139
|
+
while (Date.now() - start < WEZTERM_HEALTH_POLL_TIMEOUT_MS) {
|
|
140
|
+
const healthy = await isWeztermMuxHealthy({ client, spriteName: canonical });
|
|
141
|
+
if (healthy)
|
|
142
|
+
return true;
|
|
143
|
+
await new Promise((resolve) => setTimeout(resolve, WEZTERM_HEALTH_POLL_INTERVAL_MS));
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
};
|
|
147
|
+
const ensureWeztermMuxReady = async (box, options) => {
|
|
148
|
+
const { canonical, client } = await initWeztermClient(box);
|
|
149
|
+
const installOptions = {
|
|
150
|
+
client,
|
|
151
|
+
spriteName: canonical,
|
|
152
|
+
...(options?.assetUrl !== undefined ? { assetUrl: options.assetUrl } : {}),
|
|
153
|
+
...(options?.assetName !== undefined ? { assetName: options.assetName } : {}),
|
|
154
|
+
...(options?.version !== undefined ? { version: options.version } : {}),
|
|
155
|
+
};
|
|
156
|
+
const updated = await installWeztermMux(installOptions);
|
|
157
|
+
if (updated) {
|
|
158
|
+
try {
|
|
159
|
+
await client.stopService(canonical, WEZTERM_MUX_SERVICE_NAME);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
logger.warn("wezterm_mux_restart_failed", {
|
|
163
|
+
error: error instanceof Error ? error.message : String(error),
|
|
164
|
+
box: canonical,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
await ensureWeztermMuxService({ client, spriteName: canonical });
|
|
169
|
+
let healthy = await waitForWeztermMuxHealthy(client, canonical);
|
|
170
|
+
if (!healthy) {
|
|
171
|
+
logger.warn("wezterm_mux_unhealthy", { box: canonical });
|
|
172
|
+
try {
|
|
173
|
+
await client.stopService(canonical, WEZTERM_MUX_SERVICE_NAME);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
logger.warn("wezterm_mux_restart_failed", {
|
|
177
|
+
error: error instanceof Error ? error.message : String(error),
|
|
178
|
+
box: canonical,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
await ensureWeztermMuxService({ client, spriteName: canonical });
|
|
182
|
+
healthy = await waitForWeztermMuxHealthy(client, canonical);
|
|
183
|
+
if (!healthy) {
|
|
184
|
+
throw new Error("WezTerm mux service failed health check.");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { canonical, client };
|
|
188
|
+
};
|
|
189
|
+
const runWeztermProxy = async (box, port, options) => {
|
|
190
|
+
const writeProxyLog = async (message) => {
|
|
191
|
+
if (process.env.DEVBOX_WEZTERM_PROXY !== "1")
|
|
192
|
+
return;
|
|
193
|
+
const homeDir = os.homedir();
|
|
194
|
+
if (!homeDir)
|
|
195
|
+
return;
|
|
196
|
+
try {
|
|
197
|
+
const logDir = path.join(homeDir, ".devbox", "wezterm");
|
|
198
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
199
|
+
const line = `${new Date().toISOString()} ${message}\n`;
|
|
200
|
+
await fs.appendFile(path.join(logDir, WEZTERM_PROXY_LOG_NAME), line, "utf8");
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// ignore proxy log failures
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
await writeProxyLog(`proxy_begin box=${box} port=${port}`);
|
|
207
|
+
const skipSetup = process.env.DEVBOX_WEZTERM_PROXY === "1";
|
|
208
|
+
const { canonical, client } = skipSetup
|
|
209
|
+
? await initWeztermClient(box)
|
|
210
|
+
: await ensureWeztermMuxReady(box, options);
|
|
211
|
+
await writeProxyLog(`proxy_ready box=${canonical} setup=${skipSetup ? "skip" : "full"}`);
|
|
212
|
+
const requestId = randomUUID();
|
|
213
|
+
const proxyPath = `/v1/sprites/${canonical}/proxy`;
|
|
214
|
+
logger.info("sprites_request", {
|
|
215
|
+
requestId,
|
|
216
|
+
method: "proxy",
|
|
217
|
+
path: proxyPath,
|
|
218
|
+
box: canonical,
|
|
219
|
+
port,
|
|
220
|
+
});
|
|
221
|
+
await writeProxyLog(`proxy_start box=${canonical} port=${port}`);
|
|
222
|
+
const ws = client.openProxySocket(canonical);
|
|
223
|
+
ws.binaryType = "arraybuffer";
|
|
224
|
+
let wsOpen = false;
|
|
225
|
+
const pending = [];
|
|
226
|
+
let closed = false;
|
|
227
|
+
const flushPending = () => {
|
|
228
|
+
if (!wsOpen)
|
|
229
|
+
return;
|
|
230
|
+
for (const chunk of pending) {
|
|
231
|
+
ws.send(chunk);
|
|
232
|
+
}
|
|
233
|
+
pending.length = 0;
|
|
234
|
+
};
|
|
235
|
+
const closeAll = () => {
|
|
236
|
+
if (closed)
|
|
237
|
+
return;
|
|
238
|
+
closed = true;
|
|
239
|
+
try {
|
|
240
|
+
ws.close();
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// ignore
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
process.stdin.pause();
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// ignore
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
ws.addEventListener("open", () => {
|
|
253
|
+
wsOpen = true;
|
|
254
|
+
ws.send(JSON.stringify({ host: "localhost", port }));
|
|
255
|
+
flushPending();
|
|
256
|
+
logger.info("sprites_response", {
|
|
257
|
+
requestId,
|
|
258
|
+
method: "proxy",
|
|
259
|
+
path: proxyPath,
|
|
260
|
+
status: "ok",
|
|
261
|
+
box: canonical,
|
|
262
|
+
port,
|
|
263
|
+
});
|
|
264
|
+
void writeProxyLog(`proxy_ws_open box=${canonical}`);
|
|
265
|
+
});
|
|
266
|
+
ws.addEventListener("message", (event) => {
|
|
267
|
+
const data = event.data;
|
|
268
|
+
if (!data || closed)
|
|
269
|
+
return;
|
|
270
|
+
if (typeof data === "string") {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (Buffer.isBuffer(data)) {
|
|
274
|
+
process.stdout.write(data);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (data instanceof ArrayBuffer) {
|
|
278
|
+
process.stdout.write(Buffer.from(new Uint8Array(data)));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (data instanceof Uint8Array) {
|
|
282
|
+
process.stdout.write(Buffer.from(data));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
286
|
+
data
|
|
287
|
+
.arrayBuffer()
|
|
288
|
+
.then((buffer) => {
|
|
289
|
+
process.stdout.write(Buffer.from(new Uint8Array(buffer)));
|
|
290
|
+
})
|
|
291
|
+
.catch(() => {
|
|
292
|
+
// ignore blob decode failures
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
ws.addEventListener("close", (event) => {
|
|
297
|
+
logger.info("sprites_response", {
|
|
298
|
+
requestId,
|
|
299
|
+
method: "proxy",
|
|
300
|
+
path: proxyPath,
|
|
301
|
+
status: "closed",
|
|
302
|
+
box: canonical,
|
|
303
|
+
port,
|
|
304
|
+
});
|
|
305
|
+
let detail = "";
|
|
306
|
+
if (event && typeof event === "object") {
|
|
307
|
+
if ("code" in event && typeof event.code === "number") {
|
|
308
|
+
detail += ` code=${event.code}`;
|
|
309
|
+
}
|
|
310
|
+
if ("reason" in event && typeof event.reason === "string" && event.reason) {
|
|
311
|
+
detail += ` reason=${event.reason}`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
void writeProxyLog(`proxy_ws_close box=${canonical}${detail}`);
|
|
315
|
+
closeAll();
|
|
316
|
+
});
|
|
317
|
+
const wsAny = ws;
|
|
318
|
+
if (wsAny.on) {
|
|
319
|
+
wsAny.on("unexpected-response", (_request, response) => {
|
|
320
|
+
const status = typeof response === "object" && response && "statusCode" in response
|
|
321
|
+
? Number(response.statusCode ?? 0)
|
|
322
|
+
: 0;
|
|
323
|
+
logger.warn("sprites_response", {
|
|
324
|
+
requestId,
|
|
325
|
+
method: "proxy",
|
|
326
|
+
path: proxyPath,
|
|
327
|
+
status: "error",
|
|
328
|
+
box: canonical,
|
|
329
|
+
port,
|
|
330
|
+
});
|
|
331
|
+
void writeProxyLog(`proxy_ws_unexpected box=${canonical} status=${status}`);
|
|
332
|
+
closeAll();
|
|
333
|
+
});
|
|
334
|
+
wsAny.on("error", (error) => {
|
|
335
|
+
logger.warn("sprites_response", {
|
|
336
|
+
requestId,
|
|
337
|
+
method: "proxy",
|
|
338
|
+
path: proxyPath,
|
|
339
|
+
status: "error",
|
|
340
|
+
box: canonical,
|
|
341
|
+
port,
|
|
342
|
+
});
|
|
343
|
+
let detail = error instanceof Error ? error.message : String(error);
|
|
344
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
345
|
+
const code = error.code;
|
|
346
|
+
if (code !== undefined) {
|
|
347
|
+
detail = `${detail} code=${code}`;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
void writeProxyLog(`proxy_ws_error box=${canonical} error=${detail}`);
|
|
351
|
+
closeAll();
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
ws.addEventListener("error", () => {
|
|
356
|
+
logger.warn("sprites_response", {
|
|
357
|
+
requestId,
|
|
358
|
+
method: "proxy",
|
|
359
|
+
path: proxyPath,
|
|
360
|
+
status: "error",
|
|
361
|
+
box: canonical,
|
|
362
|
+
port,
|
|
363
|
+
});
|
|
364
|
+
void writeProxyLog(`proxy_ws_error box=${canonical}`);
|
|
365
|
+
closeAll();
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
process.stdin.on("data", (chunk) => {
|
|
369
|
+
if (closed)
|
|
370
|
+
return;
|
|
371
|
+
if (!wsOpen) {
|
|
372
|
+
pending.push(Buffer.from(chunk));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
ws.send(Buffer.from(chunk));
|
|
376
|
+
});
|
|
377
|
+
process.stdin.on("error", closeAll);
|
|
378
|
+
process.stdin.on("end", closeAll);
|
|
379
|
+
};
|
|
380
|
+
const runCommand = async (command, args) => new Promise((resolve) => {
|
|
381
|
+
let settled = false;
|
|
382
|
+
const finish = (result) => {
|
|
383
|
+
if (settled)
|
|
384
|
+
return;
|
|
385
|
+
settled = true;
|
|
386
|
+
resolve(result);
|
|
387
|
+
};
|
|
388
|
+
let child;
|
|
389
|
+
try {
|
|
390
|
+
child = spawn(command, args, {
|
|
391
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
finish({
|
|
396
|
+
ok: false,
|
|
397
|
+
stdout: "",
|
|
398
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
399
|
+
});
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
let stdout = "";
|
|
403
|
+
let stderr = "";
|
|
404
|
+
child.stdout.on("data", (chunk) => {
|
|
405
|
+
stdout += chunk.toString();
|
|
406
|
+
});
|
|
407
|
+
child.stderr.on("data", (chunk) => {
|
|
408
|
+
stderr += chunk.toString();
|
|
409
|
+
});
|
|
410
|
+
child.on("error", (error) => {
|
|
411
|
+
finish({
|
|
412
|
+
ok: false,
|
|
413
|
+
stdout: stdout.trim(),
|
|
414
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
child.on("close", (code) => {
|
|
418
|
+
finish({
|
|
419
|
+
ok: code === 0,
|
|
420
|
+
stdout: stdout.trim(),
|
|
421
|
+
stderr: stderr.trim(),
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
const promptYesNo = async (question, defaultYes = false) => {
|
|
426
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
427
|
+
throw new Error("This command requires a TTY to confirm changes.");
|
|
428
|
+
}
|
|
429
|
+
const rl = readline.createInterface({
|
|
430
|
+
input: process.stdin,
|
|
431
|
+
output: process.stdout,
|
|
432
|
+
});
|
|
433
|
+
const suffix = defaultYes ? " (Y/n): " : " (y/N): ";
|
|
434
|
+
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
|
435
|
+
rl.close();
|
|
436
|
+
if (!answer)
|
|
437
|
+
return defaultYes;
|
|
438
|
+
return ["y", "yes"].includes(answer);
|
|
439
|
+
};
|
|
440
|
+
const fileExists = async (value) => {
|
|
441
|
+
try {
|
|
442
|
+
await fs.access(value);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
const parseWeztermVersion = (output) => {
|
|
450
|
+
const match = /wezterm\s+([0-9][^\s]+)/i.exec(output.trim());
|
|
451
|
+
return match ? match[1] : null;
|
|
452
|
+
};
|
|
453
|
+
const hasWeztermOnPath = async () => {
|
|
454
|
+
const result = await runCommand("wezterm", ["--version"]);
|
|
455
|
+
return result.ok;
|
|
456
|
+
};
|
|
457
|
+
const resolveShellProfilePath = async () => {
|
|
458
|
+
const homeDir = process.env.HOME ?? "";
|
|
459
|
+
if (!homeDir) {
|
|
460
|
+
throw new Error("HOME is not set; cannot update shell profile.");
|
|
461
|
+
}
|
|
462
|
+
const shell = process.env.SHELL ?? "";
|
|
463
|
+
if (shell.includes("zsh")) {
|
|
464
|
+
return path.join(homeDir, ".zshrc");
|
|
465
|
+
}
|
|
466
|
+
if (shell.includes("bash")) {
|
|
467
|
+
const profile = path.join(homeDir, ".bash_profile");
|
|
468
|
+
if (await fileExists(profile)) {
|
|
469
|
+
return profile;
|
|
470
|
+
}
|
|
471
|
+
return path.join(homeDir, ".bashrc");
|
|
472
|
+
}
|
|
473
|
+
return path.join(homeDir, ".profile");
|
|
474
|
+
};
|
|
475
|
+
const ensureWeztermOnPath = async () => {
|
|
476
|
+
if (await hasWeztermOnPath()) {
|
|
477
|
+
return { executable: "wezterm", updatedProfile: false };
|
|
478
|
+
}
|
|
479
|
+
if (process.platform !== "darwin" || !(await fileExists(WEZTERM_BIN_PATH))) {
|
|
480
|
+
throw new Error("WezTerm is not available on your PATH. Install it and try again.");
|
|
481
|
+
}
|
|
482
|
+
const profilePath = await resolveShellProfilePath();
|
|
483
|
+
const shouldUpdate = await promptYesNo(`WezTerm is installed but not on your PATH. Add it to ${profilePath}?`, true);
|
|
484
|
+
let updatedProfile = false;
|
|
485
|
+
if (shouldUpdate) {
|
|
486
|
+
let content = "";
|
|
487
|
+
try {
|
|
488
|
+
content = await fs.readFile(profilePath, "utf8");
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
content = "";
|
|
492
|
+
}
|
|
493
|
+
if (!content.includes(WEZTERM_BIN_DIR)) {
|
|
494
|
+
const next = `${content.trimEnd()}\n${content ? "\n" : ""}${WEZTERM_PATH_EXPORT}\n`;
|
|
495
|
+
await fs.writeFile(profilePath, next, "utf8");
|
|
496
|
+
updatedProfile = true;
|
|
497
|
+
}
|
|
498
|
+
const currentPath = process.env.PATH ?? "";
|
|
499
|
+
if (!currentPath.split(":").includes(WEZTERM_BIN_DIR)) {
|
|
500
|
+
process.env.PATH = `${currentPath}${currentPath ? ":" : ""}${WEZTERM_BIN_DIR}`;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (await hasWeztermOnPath()) {
|
|
504
|
+
return { executable: "wezterm", updatedProfile };
|
|
505
|
+
}
|
|
506
|
+
return { executable: WEZTERM_BIN_PATH, updatedProfile };
|
|
507
|
+
};
|
|
508
|
+
const ensureWeztermInstalled = async () => {
|
|
509
|
+
if (await hasWeztermOnPath()) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (process.platform === "darwin") {
|
|
513
|
+
if (await fileExists(WEZTERM_APP_PATH)) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
console.log("WezTerm is not installed.");
|
|
518
|
+
console.log(`Install it here: ${WEZTERM_INSTALL_URL}`);
|
|
519
|
+
throw new Error("WezTerm is required to continue.");
|
|
520
|
+
};
|
|
521
|
+
const resolveLocalWeztermVersion = async () => {
|
|
522
|
+
const candidates = [];
|
|
523
|
+
if (process.env.WEZTERM_EXECUTABLE) {
|
|
524
|
+
candidates.push(process.env.WEZTERM_EXECUTABLE);
|
|
525
|
+
}
|
|
526
|
+
candidates.push("wezterm");
|
|
527
|
+
if (process.platform === "darwin") {
|
|
528
|
+
candidates.push("/Applications/WezTerm.app/Contents/MacOS/wezterm");
|
|
529
|
+
}
|
|
530
|
+
for (const command of candidates) {
|
|
531
|
+
if (!command)
|
|
532
|
+
continue;
|
|
533
|
+
if (command.includes("/")) {
|
|
534
|
+
try {
|
|
535
|
+
await fs.access(command);
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const result = await runCommand(command, ["--version"]);
|
|
542
|
+
if (!result.ok) {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
const parsed = parseWeztermVersion(result.stdout) ?? parseWeztermVersion(result.stderr);
|
|
546
|
+
if (parsed)
|
|
547
|
+
return parsed;
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
};
|
|
551
|
+
const resolveWeztermAssetUrl = async (version) => {
|
|
552
|
+
const response = await fetch(`https://api.github.com/repos/wez/wezterm/releases/tags/${encodeURIComponent(version)}`, {
|
|
553
|
+
headers: {
|
|
554
|
+
"User-Agent": "devbox-cli",
|
|
555
|
+
Accept: "application/vnd.github+json",
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
if (!response.ok) {
|
|
559
|
+
throw new Error(`Failed to resolve WezTerm release ${version} (HTTP ${response.status}).`);
|
|
560
|
+
}
|
|
561
|
+
const payload = (await response.json());
|
|
562
|
+
const assets = Array.isArray(payload.assets) ? payload.assets : [];
|
|
563
|
+
const matches = (asset, pattern) => asset.name && pattern.test(asset.name);
|
|
564
|
+
const byName = (pattern) => assets.filter((asset) => matches(asset, pattern));
|
|
565
|
+
const isNonLinux = (name) => /(windows|win32|msvc|mingw|macos|darwin|osx|apple|dmg)\b/i.test(name);
|
|
566
|
+
const byLinuxArchive = (pattern) => assets.filter((asset) => asset.name && pattern.test(asset.name) && !isNonLinux(asset.name));
|
|
567
|
+
const appImageCandidates = byLinuxArchive(/\.AppImage$/i);
|
|
568
|
+
const archiveCandidates = [
|
|
569
|
+
...byLinuxArchive(/\.tar\.xz$/i),
|
|
570
|
+
...byLinuxArchive(/\.tar\.gz$/i),
|
|
571
|
+
...byLinuxArchive(/\.tgz$/i),
|
|
572
|
+
...byLinuxArchive(/\.zip$/i),
|
|
573
|
+
];
|
|
574
|
+
const candidates = appImageCandidates.length > 0 ? appImageCandidates : archiveCandidates;
|
|
575
|
+
const selected = candidates.find((asset) => asset.browser_download_url);
|
|
576
|
+
if (!selected?.browser_download_url || !selected.name) {
|
|
577
|
+
throw new Error(`Unable to find a Linux WezTerm asset for version ${version}.`);
|
|
578
|
+
}
|
|
579
|
+
return { url: selected.browser_download_url, name: selected.name };
|
|
580
|
+
};
|
|
581
|
+
const resolveWeztermInstallOptions = async ({ version, assetUrl, quiet = false, }) => {
|
|
582
|
+
let resolvedVersion = version;
|
|
583
|
+
if (!resolvedVersion) {
|
|
584
|
+
const localVersion = await resolveLocalWeztermVersion();
|
|
585
|
+
if (localVersion) {
|
|
586
|
+
resolvedVersion = localVersion;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
let resolvedAssetUrl = assetUrl;
|
|
590
|
+
let assetName;
|
|
591
|
+
if (!resolvedAssetUrl && resolvedVersion) {
|
|
592
|
+
try {
|
|
593
|
+
const resolved = await resolveWeztermAssetUrl(resolvedVersion);
|
|
594
|
+
resolvedAssetUrl = resolved.url;
|
|
595
|
+
assetName = resolved.name;
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
logger.warn("wezterm_asset_lookup_failed", {
|
|
599
|
+
error: error instanceof Error ? error.message : String(error),
|
|
600
|
+
version: resolvedVersion,
|
|
601
|
+
});
|
|
602
|
+
if (!quiet) {
|
|
603
|
+
console.warn(`Warning: unable to resolve a Linux WezTerm build for ${resolvedVersion}. ` +
|
|
604
|
+
"The Sprite may use a different version, which can break mux connections. " +
|
|
605
|
+
"Pass --asset-url to override.");
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const result = {};
|
|
610
|
+
if (resolvedAssetUrl !== undefined)
|
|
611
|
+
result.assetUrl = resolvedAssetUrl;
|
|
612
|
+
if (assetName !== undefined)
|
|
613
|
+
result.assetName = assetName;
|
|
614
|
+
if (resolvedVersion !== undefined)
|
|
615
|
+
result.version = resolvedVersion;
|
|
616
|
+
return result;
|
|
617
|
+
};
|
|
618
|
+
const expandPath = (value) => {
|
|
619
|
+
if (value.startsWith("~/")) {
|
|
620
|
+
const homeDir = process.env.HOME ?? "";
|
|
621
|
+
return homeDir ? path.join(homeDir, value.slice(2)) : value;
|
|
622
|
+
}
|
|
623
|
+
return value;
|
|
624
|
+
};
|
|
625
|
+
const resolveDefaultConfigPath = async () => {
|
|
626
|
+
const homeDir = process.env.HOME ?? "";
|
|
627
|
+
if (!homeDir) {
|
|
628
|
+
throw new Error("HOME is not set; pass --path to wezterm setup.");
|
|
629
|
+
}
|
|
630
|
+
const candidates = [
|
|
631
|
+
path.join(homeDir, ".wezterm.lua"),
|
|
632
|
+
path.join(homeDir, ".config", "wezterm", "wezterm.lua"),
|
|
633
|
+
];
|
|
634
|
+
for (const candidate of candidates) {
|
|
635
|
+
try {
|
|
636
|
+
await fs.access(candidate);
|
|
637
|
+
return candidate;
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
// ignore missing
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return candidates[0];
|
|
644
|
+
};
|
|
645
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
646
|
+
const resolveDvbCommand = async () => {
|
|
647
|
+
const scriptPath = process.argv[1];
|
|
648
|
+
if (scriptPath) {
|
|
649
|
+
const candidate = path.isAbsolute(scriptPath)
|
|
650
|
+
? scriptPath
|
|
651
|
+
: path.resolve(process.cwd(), scriptPath);
|
|
652
|
+
try {
|
|
653
|
+
await fs.access(candidate);
|
|
654
|
+
return [process.execPath, candidate];
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
// fall through
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return ["dvb"];
|
|
661
|
+
};
|
|
662
|
+
const formatLuaArgs = (args) => `{ ${args.map((arg) => JSON.stringify(arg)).join(", ")} }`;
|
|
663
|
+
const buildProxyCommand = async (box, port) => {
|
|
664
|
+
const command = await resolveDvbCommand();
|
|
665
|
+
const args = ["env", ...WEZTERM_PROXY_ENV, ...command, "wezterm", "proxy", box];
|
|
666
|
+
if (port) {
|
|
667
|
+
args.push("--port", String(port));
|
|
668
|
+
}
|
|
669
|
+
return formatLuaArgs(args);
|
|
670
|
+
};
|
|
671
|
+
const buildDomainBlock = (domainName, proxyCommand) => {
|
|
672
|
+
const start = `-- devbox: wezterm domain begin (${domainName})`;
|
|
673
|
+
const end = `-- devbox: wezterm domain end (${domainName})`;
|
|
674
|
+
const lines = [
|
|
675
|
+
start,
|
|
676
|
+
"do",
|
|
677
|
+
" local devbox_domain = {",
|
|
678
|
+
` name = "${domainName}",`,
|
|
679
|
+
` proxy_command = ${proxyCommand},`,
|
|
680
|
+
" }",
|
|
681
|
+
" config.unix_domains = config.unix_domains or {}",
|
|
682
|
+
" local exists = false",
|
|
683
|
+
" for _, domain in ipairs(config.unix_domains) do",
|
|
684
|
+
" if domain.name == devbox_domain.name then",
|
|
685
|
+
" domain.proxy_command = devbox_domain.proxy_command",
|
|
686
|
+
" exists = true",
|
|
687
|
+
" break",
|
|
688
|
+
" end",
|
|
689
|
+
" end",
|
|
690
|
+
" if not exists then",
|
|
691
|
+
" table.insert(config.unix_domains, devbox_domain)",
|
|
692
|
+
" end",
|
|
693
|
+
"end",
|
|
694
|
+
end,
|
|
695
|
+
"",
|
|
696
|
+
];
|
|
697
|
+
return { start, end, block: lines.join("\n") };
|
|
698
|
+
};
|
|
699
|
+
const canAppendToConfig = (content) => {
|
|
700
|
+
const hasConfig = /\blocal\s+config\b/.test(content) || /\bconfig\s*=/.test(content);
|
|
701
|
+
const hasReturnConfig = /\breturn\s+config\b/.test(content);
|
|
702
|
+
return hasConfig || hasReturnConfig;
|
|
703
|
+
};
|
|
704
|
+
const countReturns = (content) => (content.match(/\breturn\b/g) ?? []).length;
|
|
705
|
+
const applyReturnTableTransform = (content) => {
|
|
706
|
+
if (!/\breturn\s*{/.test(content))
|
|
707
|
+
return null;
|
|
708
|
+
if (countReturns(content) !== 1)
|
|
709
|
+
return null;
|
|
710
|
+
const replaced = content.replace(/\breturn\s*{/, "local config = {");
|
|
711
|
+
return `${replaced.trimEnd()}\n\nreturn config\n`;
|
|
712
|
+
};
|
|
713
|
+
const insertBlock = (content, block) => {
|
|
714
|
+
const match = /\breturn\s+config\b/.exec(content);
|
|
715
|
+
if (match && match.index > 0) {
|
|
716
|
+
return `${content.slice(0, match.index).trimEnd()}\n\n${block}${content.slice(match.index)}`;
|
|
717
|
+
}
|
|
718
|
+
return `${content.trimEnd()}\n\n${block}`;
|
|
719
|
+
};
|
|
720
|
+
const runWeztermSetup = async (box, options) => {
|
|
721
|
+
const resolvedPath = options.configPath ?? (await resolveDefaultConfigPath());
|
|
722
|
+
if (!resolvedPath) {
|
|
723
|
+
throw new Error("WezTerm config path unavailable. Pass --path to override.");
|
|
724
|
+
}
|
|
725
|
+
const configPath = expandPath(resolvedPath);
|
|
726
|
+
const domainName = options.domainName ?? `sprite:${box}`;
|
|
727
|
+
const proxyCommand = await buildProxyCommand(box, options.port);
|
|
728
|
+
const { block, start, end } = buildDomainBlock(domainName, proxyCommand);
|
|
729
|
+
let existing = "";
|
|
730
|
+
let hasExisting = false;
|
|
731
|
+
try {
|
|
732
|
+
existing = await fs.readFile(configPath, "utf8");
|
|
733
|
+
hasExisting = true;
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
hasExisting = false;
|
|
737
|
+
}
|
|
738
|
+
let nextContent = "";
|
|
739
|
+
let updated = true;
|
|
740
|
+
if (!hasExisting) {
|
|
741
|
+
const header = [
|
|
742
|
+
'local wezterm = require "wezterm"',
|
|
743
|
+
"local config = wezterm.config_builder and wezterm.config_builder() or {}",
|
|
744
|
+
"",
|
|
745
|
+
].join("\n");
|
|
746
|
+
nextContent = `${header}${block}return config\n`;
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
const marker = new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`);
|
|
750
|
+
if (marker.test(existing)) {
|
|
751
|
+
nextContent = existing.replace(marker, block);
|
|
752
|
+
updated = nextContent !== existing;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
let candidate = existing;
|
|
756
|
+
if (!canAppendToConfig(candidate)) {
|
|
757
|
+
const transformed = applyReturnTableTransform(candidate);
|
|
758
|
+
if (transformed) {
|
|
759
|
+
candidate = transformed;
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
throw new Error(`Unable to safely modify ${configPath}. ` +
|
|
763
|
+
"Add the domain snippet manually or use --path to target a different file.");
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
nextContent = insertBlock(candidate, block);
|
|
767
|
+
updated = nextContent !== existing;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (updated) {
|
|
771
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
772
|
+
await fs.writeFile(configPath, nextContent, "utf8");
|
|
773
|
+
console.log(`Updated WezTerm config: ${configPath}`);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
console.log(`WezTerm config already up to date: ${configPath}`);
|
|
777
|
+
}
|
|
778
|
+
console.log(`Domain "${domainName}" now targets ${box}.`);
|
|
779
|
+
return { configPath, domainName, updated };
|
|
780
|
+
};
|
|
781
|
+
const startWeztermDomain = async (executable, domainName) => {
|
|
782
|
+
try {
|
|
783
|
+
const child = spawn(executable, ["connect", domainName], {
|
|
784
|
+
stdio: "ignore",
|
|
785
|
+
detached: true,
|
|
786
|
+
});
|
|
787
|
+
child.unref();
|
|
788
|
+
if (process.platform === "darwin") {
|
|
789
|
+
const activator = spawn("osascript", [
|
|
790
|
+
"-e",
|
|
791
|
+
'tell application "WezTerm" to activate',
|
|
792
|
+
]);
|
|
793
|
+
activator.unref();
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
throw new Error(`Failed to launch WezTerm: ${error instanceof Error ? error.message : String(error)}`);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
const runWezterm = async (args) => {
|
|
801
|
+
const parsed = parseWeztermArgs(args);
|
|
802
|
+
const subcommand = parsed.subcommand ?? "launch";
|
|
803
|
+
let targetBox = parsed.box;
|
|
804
|
+
if (!targetBox) {
|
|
805
|
+
const reminder = subcommand === "proxy"
|
|
806
|
+
? "To target a specific box, use `dvb wezterm proxy <box>`."
|
|
807
|
+
: subcommand === "setup"
|
|
808
|
+
? "To target a specific box, use `dvb wezterm setup <box>`."
|
|
809
|
+
: "To target a specific box, use `dvb wezterm <box>`.";
|
|
810
|
+
const selected = await selectDevbox(reminder);
|
|
811
|
+
if (!selected)
|
|
812
|
+
return;
|
|
813
|
+
targetBox = selected.box;
|
|
814
|
+
}
|
|
815
|
+
if (subcommand === "setup") {
|
|
816
|
+
const options = {};
|
|
817
|
+
if (parsed.port !== undefined)
|
|
818
|
+
options.port = parsed.port;
|
|
819
|
+
if (parsed.configPath !== undefined)
|
|
820
|
+
options.configPath = parsed.configPath;
|
|
821
|
+
if (parsed.domainName !== undefined)
|
|
822
|
+
options.domainName = parsed.domainName;
|
|
823
|
+
await runWeztermSetup(targetBox, options);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (subcommand === "launch") {
|
|
827
|
+
await ensureWeztermInstalled();
|
|
828
|
+
const domainName = parsed.domainName ?? `sprite:${targetBox}`;
|
|
829
|
+
const { executable, updatedProfile } = await ensureWeztermOnPath();
|
|
830
|
+
if (updatedProfile) {
|
|
831
|
+
console.log("Updated your shell profile for WezTerm PATH.");
|
|
832
|
+
}
|
|
833
|
+
const setupOptions = { domainName };
|
|
834
|
+
if (parsed.port !== undefined)
|
|
835
|
+
setupOptions.port = parsed.port;
|
|
836
|
+
if (parsed.configPath !== undefined) {
|
|
837
|
+
setupOptions.configPath = parsed.configPath;
|
|
838
|
+
}
|
|
839
|
+
await runWeztermSetup(targetBox, setupOptions);
|
|
840
|
+
const installInput = { quiet: false };
|
|
841
|
+
if (parsed.version !== undefined)
|
|
842
|
+
installInput.version = parsed.version;
|
|
843
|
+
if (parsed.assetUrl !== undefined)
|
|
844
|
+
installInput.assetUrl = parsed.assetUrl;
|
|
845
|
+
const installOptions = await resolveWeztermInstallOptions(installInput);
|
|
846
|
+
await ensureWeztermMuxReady(targetBox, installOptions);
|
|
847
|
+
await startWeztermDomain(executable, domainName);
|
|
848
|
+
console.log(`Opened WezTerm domain "${domainName}".`);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
const port = parsed.port ?? WEZTERM_MUX_TCP_PORT;
|
|
852
|
+
if (subcommand === "proxy") {
|
|
853
|
+
const quietProxy = process.env.DEVBOX_WEZTERM_PROXY === "1";
|
|
854
|
+
let resolvedOptions = {};
|
|
855
|
+
if (!quietProxy) {
|
|
856
|
+
const proxyInput = { quiet: quietProxy };
|
|
857
|
+
if (parsed.version !== undefined)
|
|
858
|
+
proxyInput.version = parsed.version;
|
|
859
|
+
if (parsed.assetUrl !== undefined)
|
|
860
|
+
proxyInput.assetUrl = parsed.assetUrl;
|
|
861
|
+
resolvedOptions = await resolveWeztermInstallOptions(proxyInput);
|
|
862
|
+
}
|
|
863
|
+
const proxyOptions = {};
|
|
864
|
+
if (resolvedOptions.assetUrl !== undefined) {
|
|
865
|
+
proxyOptions.assetUrl = resolvedOptions.assetUrl;
|
|
866
|
+
}
|
|
867
|
+
if (resolvedOptions.assetName !== undefined) {
|
|
868
|
+
proxyOptions.assetName = resolvedOptions.assetName;
|
|
869
|
+
}
|
|
870
|
+
if (resolvedOptions.version !== undefined) {
|
|
871
|
+
proxyOptions.version = resolvedOptions.version;
|
|
872
|
+
}
|
|
873
|
+
await runWeztermProxy(targetBox, port, proxyOptions);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
export { runWezterm };
|
|
878
|
+
//# sourceMappingURL=wezterm.js.map
|