tmex-cli 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/server.js +466 -232
- package/package.json +1 -1
- package/resources/fe-dist/assets/DevicePage-iSkEDEpS.js +4570 -0
- package/resources/fe-dist/assets/DevicePage-iSkEDEpS.js.map +1 -0
- package/resources/fe-dist/assets/DevicesPage-CtNzaW_c.js +2143 -0
- package/resources/fe-dist/assets/{DevicesPage-BTB6mlBW.js.map → DevicesPage-CtNzaW_c.js.map} +1 -1
- package/resources/fe-dist/assets/SettingsPage-D25_d6j9.js +1144 -0
- package/resources/fe-dist/assets/{SettingsPage-BDVq_Akt.js.map → SettingsPage-D25_d6j9.js.map} +1 -1
- package/resources/fe-dist/assets/index-CJaX5rlK.css +4527 -0
- package/resources/fe-dist/assets/index-dsVN7rgz.js +200 -0
- package/resources/fe-dist/assets/index-dsVN7rgz.js.map +1 -0
- package/resources/fe-dist/assets/select-BNsiC9zT.js +2805 -0
- package/resources/fe-dist/assets/{select-7tfuDWw3.js.map → select-BNsiC9zT.js.map} +1 -1
- package/resources/fe-dist/assets/switch-CIU4AisU.js +234 -0
- package/resources/fe-dist/assets/{switch-CNHRGb7-.js.map → switch-CIU4AisU.js.map} +1 -1
- package/resources/fe-dist/assets/useValueChanged-V23H0VpC.js +351 -0
- package/resources/fe-dist/assets/{useValueChanged-Bi_NzPTr.js.map → useValueChanged-V23H0VpC.js.map} +1 -1
- package/resources/fe-dist/index.html +2 -2
- package/resources/gateway-drizzle/meta/0000_snapshot.json +6 -17
- package/resources/gateway-drizzle/meta/0001_snapshot.json +6 -17
- package/resources/gateway-drizzle/meta/_journal.json +1 -1
- package/resources/fe-dist/assets/DevicePage-BE5Hlzny.js +0 -26
- package/resources/fe-dist/assets/DevicePage-BE5Hlzny.js.map +0 -1
- package/resources/fe-dist/assets/DevicesPage-BTB6mlBW.js +0 -17
- package/resources/fe-dist/assets/SettingsPage-BDVq_Akt.js +0 -17
- package/resources/fe-dist/assets/index-Chps0Pui.js +0 -448
- package/resources/fe-dist/assets/index-Chps0Pui.js.map +0 -1
- package/resources/fe-dist/assets/index-CyKyNcdz.css +0 -1
- package/resources/fe-dist/assets/select-7tfuDWw3.js +0 -17
- package/resources/fe-dist/assets/switch-CNHRGb7-.js +0 -12
- package/resources/fe-dist/assets/useValueChanged-Bi_NzPTr.js +0 -7
package/dist/runtime/server.js
CHANGED
|
@@ -20281,8 +20281,8 @@ var require_lib3 = __commonJS((exports, module) => {
|
|
|
20281
20281
|
});
|
|
20282
20282
|
|
|
20283
20283
|
// src/runtime/server.ts
|
|
20284
|
-
import { existsSync as
|
|
20285
|
-
import { extname, join as
|
|
20284
|
+
import { existsSync as existsSync4 } from "fs";
|
|
20285
|
+
import { extname, join as join5, normalize, resolve as resolve2, sep } from "path";
|
|
20286
20286
|
|
|
20287
20287
|
// ../../apps/gateway/src/crypto/errors.ts
|
|
20288
20288
|
function contextLabel(context) {
|
|
@@ -21687,6 +21687,7 @@ var TermHistorySchema = import_zorsh.b.struct({
|
|
|
21687
21687
|
paneId: import_zorsh.b.string(),
|
|
21688
21688
|
selectToken: import_zorsh.b.bytes(16),
|
|
21689
21689
|
encoding: import_zorsh.b.u8(),
|
|
21690
|
+
alternateScreen: import_zorsh.b.bool(),
|
|
21690
21691
|
data: import_zorsh.b.bytes()
|
|
21691
21692
|
});
|
|
21692
21693
|
var SwitchAckSchema = import_zorsh.b.struct({
|
|
@@ -52151,7 +52152,9 @@ function encoderFromString(value) {
|
|
|
52151
52152
|
}
|
|
52152
52153
|
|
|
52153
52154
|
// ../../apps/gateway/src/tmux-client/local-external-connection.ts
|
|
52154
|
-
|
|
52155
|
+
function hasRenderableTerminalContent(value) {
|
|
52156
|
+
return value.trim().length > 0;
|
|
52157
|
+
}
|
|
52155
52158
|
var BELL_DEDUP_WINDOW_MS = 200;
|
|
52156
52159
|
function shouldIgnoreReaderAbortError(error) {
|
|
52157
52160
|
if (!error || typeof error !== "object") {
|
|
@@ -52307,7 +52310,7 @@ class LocalExternalTmuxConnection {
|
|
|
52307
52310
|
if (!this.connected) {
|
|
52308
52311
|
return;
|
|
52309
52312
|
}
|
|
52310
|
-
const argv = name ? ["new-window", "-n", name] : ["new-window"];
|
|
52313
|
+
const argv = name ? ["new-window", "-t", this.sessionName, "-n", name] : ["new-window", "-t", this.sessionName];
|
|
52311
52314
|
this.runAndRefresh(argv).catch((error) => {
|
|
52312
52315
|
this.callbacks.onError(error);
|
|
52313
52316
|
});
|
|
@@ -52475,11 +52478,12 @@ class LocalExternalTmuxConnection {
|
|
|
52475
52478
|
}
|
|
52476
52479
|
async capturePaneHistory(paneId) {
|
|
52477
52480
|
const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
|
|
52478
|
-
const
|
|
52479
|
-
const
|
|
52480
|
-
const
|
|
52481
|
+
const alternateScreen = mode === "1";
|
|
52482
|
+
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-N", "-p"], true)).stdout;
|
|
52483
|
+
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-N", "-p", "-q"], true)).stdout;
|
|
52484
|
+
const history = alternateScreen ? hasRenderableTerminalContent(normal) ? normal : alternate : normal || alternate;
|
|
52481
52485
|
if (history) {
|
|
52482
|
-
this.callbacks.onTerminalHistory(paneId, history);
|
|
52486
|
+
this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
|
|
52483
52487
|
}
|
|
52484
52488
|
}
|
|
52485
52489
|
async requestSnapshotInternal() {
|
|
@@ -52729,6 +52733,10 @@ class LocalExternalTmuxConnection {
|
|
|
52729
52733
|
// ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
|
|
52730
52734
|
var import_ssh2 = __toESM(require_lib3(), 1);
|
|
52731
52735
|
|
|
52736
|
+
// ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
|
|
52737
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
52738
|
+
import { join as join4 } from "path";
|
|
52739
|
+
|
|
52732
52740
|
// ../../apps/gateway/src/tmux/ssh-auth.ts
|
|
52733
52741
|
function normalizeEnvValue(value) {
|
|
52734
52742
|
const trimmed = value?.trim();
|
|
@@ -52761,6 +52769,219 @@ function resolveSshAgentSocket(authMode, env = process.env) {
|
|
|
52761
52769
|
return;
|
|
52762
52770
|
}
|
|
52763
52771
|
|
|
52772
|
+
// ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
|
|
52773
|
+
function defaultRunSync2(cmd) {
|
|
52774
|
+
const result = Bun.spawnSync(cmd, {
|
|
52775
|
+
env: process.env,
|
|
52776
|
+
stdout: "pipe",
|
|
52777
|
+
stderr: "pipe"
|
|
52778
|
+
});
|
|
52779
|
+
return {
|
|
52780
|
+
exitCode: result.exitCode,
|
|
52781
|
+
stdout: Buffer.from(result.stdout).toString("utf8"),
|
|
52782
|
+
stderr: Buffer.from(result.stderr).toString("utf8")
|
|
52783
|
+
};
|
|
52784
|
+
}
|
|
52785
|
+
function expandHomePath(value, env) {
|
|
52786
|
+
const trimmed = value.trim();
|
|
52787
|
+
if (trimmed === "~") {
|
|
52788
|
+
return env.HOME?.trim() || trimmed;
|
|
52789
|
+
}
|
|
52790
|
+
if (trimmed.startsWith("~/") && env.HOME?.trim()) {
|
|
52791
|
+
return join4(env.HOME.trim(), trimmed.slice(2));
|
|
52792
|
+
}
|
|
52793
|
+
return trimmed;
|
|
52794
|
+
}
|
|
52795
|
+
function parseSshConfigOutput(stdout, env) {
|
|
52796
|
+
let host = "";
|
|
52797
|
+
let port;
|
|
52798
|
+
let username;
|
|
52799
|
+
let identityAgent;
|
|
52800
|
+
const identityFiles = [];
|
|
52801
|
+
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
52802
|
+
const line = rawLine.trim();
|
|
52803
|
+
if (!line) {
|
|
52804
|
+
continue;
|
|
52805
|
+
}
|
|
52806
|
+
const firstSpace = line.indexOf(" ");
|
|
52807
|
+
if (firstSpace <= 0) {
|
|
52808
|
+
continue;
|
|
52809
|
+
}
|
|
52810
|
+
const key = line.slice(0, firstSpace).trim().toLowerCase();
|
|
52811
|
+
const value = line.slice(firstSpace + 1).trim();
|
|
52812
|
+
if (!value) {
|
|
52813
|
+
continue;
|
|
52814
|
+
}
|
|
52815
|
+
switch (key) {
|
|
52816
|
+
case "hostname":
|
|
52817
|
+
host = value;
|
|
52818
|
+
break;
|
|
52819
|
+
case "port": {
|
|
52820
|
+
const parsedPort = Number.parseInt(value, 10);
|
|
52821
|
+
port = Number.isNaN(parsedPort) ? undefined : parsedPort;
|
|
52822
|
+
break;
|
|
52823
|
+
}
|
|
52824
|
+
case "user":
|
|
52825
|
+
username = value;
|
|
52826
|
+
break;
|
|
52827
|
+
case "identityagent":
|
|
52828
|
+
identityAgent = value;
|
|
52829
|
+
break;
|
|
52830
|
+
case "identityfile":
|
|
52831
|
+
identityFiles.push(expandHomePath(value, env));
|
|
52832
|
+
break;
|
|
52833
|
+
}
|
|
52834
|
+
}
|
|
52835
|
+
if (!host) {
|
|
52836
|
+
throw new Error("ssh_config_ref_invalid: SSH Config \u5F15\u7528\u672A\u89E3\u6790\u5230 hostname");
|
|
52837
|
+
}
|
|
52838
|
+
return {
|
|
52839
|
+
host,
|
|
52840
|
+
port,
|
|
52841
|
+
username,
|
|
52842
|
+
identityAgent,
|
|
52843
|
+
identityFiles
|
|
52844
|
+
};
|
|
52845
|
+
}
|
|
52846
|
+
function toSshAuthEnv(env) {
|
|
52847
|
+
return {
|
|
52848
|
+
SSH_AUTH_SOCK: env.SSH_AUTH_SOCK,
|
|
52849
|
+
USER: env.USER,
|
|
52850
|
+
LOGNAME: env.LOGNAME
|
|
52851
|
+
};
|
|
52852
|
+
}
|
|
52853
|
+
function resolveAgentFromConfig(identityAgent, deps) {
|
|
52854
|
+
const trimmed = identityAgent?.trim();
|
|
52855
|
+
if (!trimmed || trimmed.toLowerCase() === "none") {
|
|
52856
|
+
return;
|
|
52857
|
+
}
|
|
52858
|
+
if (trimmed === "SSH_AUTH_SOCK" || trimmed === "$SSH_AUTH_SOCK") {
|
|
52859
|
+
return resolveSshAgentSocket("auto", toSshAuthEnv(deps.env));
|
|
52860
|
+
}
|
|
52861
|
+
const expanded = expandHomePath(trimmed, deps.env);
|
|
52862
|
+
return deps.fileExists(expanded) ? expanded : undefined;
|
|
52863
|
+
}
|
|
52864
|
+
function resolvePrivateKeyFromConfig(identityFiles, deps) {
|
|
52865
|
+
for (const identityFile of identityFiles) {
|
|
52866
|
+
if (!deps.fileExists(identityFile)) {
|
|
52867
|
+
continue;
|
|
52868
|
+
}
|
|
52869
|
+
return deps.readTextFile(identityFile);
|
|
52870
|
+
}
|
|
52871
|
+
return;
|
|
52872
|
+
}
|
|
52873
|
+
function resolveSshConfigRef(device, deps) {
|
|
52874
|
+
const ref = device.sshConfigRef?.trim();
|
|
52875
|
+
if (!ref) {
|
|
52876
|
+
return null;
|
|
52877
|
+
}
|
|
52878
|
+
const result = deps.runSync(["ssh", "-G", ref]);
|
|
52879
|
+
if (result.exitCode !== 0) {
|
|
52880
|
+
const detail = result.stderr.trim() || result.stdout.trim() || ref;
|
|
52881
|
+
throw new Error(`ssh_config_ref_resolve_failed: ${detail}`);
|
|
52882
|
+
}
|
|
52883
|
+
return parseSshConfigOutput(result.stdout, deps.env);
|
|
52884
|
+
}
|
|
52885
|
+
async function resolveSshConnectConfig(device, decrypt2, inputDeps = {}) {
|
|
52886
|
+
const deps = {
|
|
52887
|
+
env: inputDeps.env ?? process.env,
|
|
52888
|
+
runSync: inputDeps.runSync ?? defaultRunSync2,
|
|
52889
|
+
fileExists: inputDeps.fileExists ?? existsSync2,
|
|
52890
|
+
readTextFile: inputDeps.readTextFile ?? ((path) => readFileSync(path, "utf8"))
|
|
52891
|
+
};
|
|
52892
|
+
const sshEnv = toSshAuthEnv(deps.env);
|
|
52893
|
+
const resolvedConfig = resolveSshConfigRef(device, deps);
|
|
52894
|
+
const host = resolvedConfig?.host ?? device.host;
|
|
52895
|
+
const port = resolvedConfig?.port ?? device.port ?? 22;
|
|
52896
|
+
const username = resolvedConfig?.username ?? resolveSshUsername(device.username, device.authMode, sshEnv);
|
|
52897
|
+
if (!host) {
|
|
52898
|
+
throw new Error("SSH device missing host");
|
|
52899
|
+
}
|
|
52900
|
+
const authConfig = {
|
|
52901
|
+
host,
|
|
52902
|
+
port,
|
|
52903
|
+
username
|
|
52904
|
+
};
|
|
52905
|
+
const configAgent = resolveAgentFromConfig(resolvedConfig?.identityAgent, deps);
|
|
52906
|
+
const envAgent = resolveSshAgentSocket("auto", sshEnv);
|
|
52907
|
+
const configPrivateKey = resolvePrivateKeyFromConfig(resolvedConfig?.identityFiles ?? [], deps);
|
|
52908
|
+
switch (device.authMode) {
|
|
52909
|
+
case "password": {
|
|
52910
|
+
if (!device.passwordEnc) {
|
|
52911
|
+
throw new Error("auth_password_missing: \u5BC6\u7801\u8BA4\u8BC1\u672A\u63D0\u4F9B\u5BC6\u7801");
|
|
52912
|
+
}
|
|
52913
|
+
authConfig.password = await decrypt2(device.passwordEnc, {
|
|
52914
|
+
scope: "device",
|
|
52915
|
+
entityId: device.id,
|
|
52916
|
+
field: "password_enc"
|
|
52917
|
+
});
|
|
52918
|
+
break;
|
|
52919
|
+
}
|
|
52920
|
+
case "key": {
|
|
52921
|
+
if (!device.privateKeyEnc) {
|
|
52922
|
+
throw new Error("auth_key_missing: \u79C1\u94A5\u8BA4\u8BC1\u672A\u63D0\u4F9B\u79C1\u94A5");
|
|
52923
|
+
}
|
|
52924
|
+
authConfig.privateKey = await decrypt2(device.privateKeyEnc, {
|
|
52925
|
+
scope: "device",
|
|
52926
|
+
entityId: device.id,
|
|
52927
|
+
field: "private_key_enc"
|
|
52928
|
+
});
|
|
52929
|
+
if (device.privateKeyPassphraseEnc) {
|
|
52930
|
+
authConfig.passphrase = await decrypt2(device.privateKeyPassphraseEnc, {
|
|
52931
|
+
scope: "device",
|
|
52932
|
+
entityId: device.id,
|
|
52933
|
+
field: "private_key_passphrase_enc"
|
|
52934
|
+
});
|
|
52935
|
+
}
|
|
52936
|
+
break;
|
|
52937
|
+
}
|
|
52938
|
+
case "agent": {
|
|
52939
|
+
authConfig.agent = configAgent ?? resolveSshAgentSocket("agent", sshEnv);
|
|
52940
|
+
break;
|
|
52941
|
+
}
|
|
52942
|
+
case "configRef": {
|
|
52943
|
+
if (!resolvedConfig) {
|
|
52944
|
+
throw new Error("ssh_config_ref_missing: SSH Config \u5F15\u7528\u4E0D\u80FD\u4E3A\u7A7A");
|
|
52945
|
+
}
|
|
52946
|
+
if (configAgent ?? envAgent) {
|
|
52947
|
+
authConfig.agent = configAgent ?? envAgent;
|
|
52948
|
+
}
|
|
52949
|
+
if (configPrivateKey) {
|
|
52950
|
+
authConfig.privateKey = configPrivateKey;
|
|
52951
|
+
}
|
|
52952
|
+
if (!authConfig.agent && !authConfig.privateKey) {
|
|
52953
|
+
throw new Error("ssh_config_ref_auth_missing: SSH Config \u5F15\u7528\u672A\u89E3\u6790\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08IdentityAgent / IdentityFile / SSH_AUTH_SOCK\uFF09");
|
|
52954
|
+
}
|
|
52955
|
+
break;
|
|
52956
|
+
}
|
|
52957
|
+
case "auto": {
|
|
52958
|
+
if (configAgent ?? envAgent) {
|
|
52959
|
+
authConfig.agent = configAgent ?? envAgent;
|
|
52960
|
+
}
|
|
52961
|
+
if (device.privateKeyEnc) {
|
|
52962
|
+
authConfig.privateKey = await decrypt2(device.privateKeyEnc, {
|
|
52963
|
+
scope: "device",
|
|
52964
|
+
entityId: device.id,
|
|
52965
|
+
field: "private_key_enc"
|
|
52966
|
+
});
|
|
52967
|
+
} else if (configPrivateKey) {
|
|
52968
|
+
authConfig.privateKey = configPrivateKey;
|
|
52969
|
+
} else if (device.passwordEnc) {
|
|
52970
|
+
authConfig.password = await decrypt2(device.passwordEnc, {
|
|
52971
|
+
scope: "device",
|
|
52972
|
+
entityId: device.id,
|
|
52973
|
+
field: "password_enc"
|
|
52974
|
+
});
|
|
52975
|
+
}
|
|
52976
|
+
break;
|
|
52977
|
+
}
|
|
52978
|
+
}
|
|
52979
|
+
if (device.authMode === "auto" && !authConfig.agent && !authConfig.privateKey && !authConfig.password) {
|
|
52980
|
+
throw new Error("auth_auto_missing: auto \u6A21\u5F0F\u4E0B\u672A\u627E\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08SSH_AUTH_SOCK / \u79C1\u94A5 / \u5BC6\u7801\uFF09");
|
|
52981
|
+
}
|
|
52982
|
+
return authConfig;
|
|
52983
|
+
}
|
|
52984
|
+
|
|
52764
52985
|
// ../../apps/gateway/src/tmux-client/ssh-bootstrap.ts
|
|
52765
52986
|
function buildSshBootstrapScript() {
|
|
52766
52987
|
return [
|
|
@@ -52801,9 +53022,36 @@ function parseSshBootstrapOutput(output) {
|
|
|
52801
53022
|
}
|
|
52802
53023
|
|
|
52803
53024
|
// ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
|
|
52804
|
-
|
|
53025
|
+
function hasRenderableTerminalContent2(value) {
|
|
53026
|
+
return value.trim().length > 0;
|
|
53027
|
+
}
|
|
52805
53028
|
var BELL_DEDUP_WINDOW_MS2 = 200;
|
|
52806
53029
|
var COMMAND_SENTINEL = "\x1ETMEX_END ";
|
|
53030
|
+
var SNAPSHOT_FIELD_SEPARATOR = "|";
|
|
53031
|
+
function splitSnapshotFields(line, fieldCount) {
|
|
53032
|
+
const parts = line.split(SNAPSHOT_FIELD_SEPARATOR);
|
|
53033
|
+
if (parts.length <= fieldCount) {
|
|
53034
|
+
return parts;
|
|
53035
|
+
}
|
|
53036
|
+
if (fieldCount === 2) {
|
|
53037
|
+
return [parts[0] ?? "", parts.slice(1).join(SNAPSHOT_FIELD_SEPARATOR)];
|
|
53038
|
+
}
|
|
53039
|
+
if (fieldCount === 4) {
|
|
53040
|
+
return [parts[0] ?? "", parts[1] ?? "", parts.slice(2, -1).join(SNAPSHOT_FIELD_SEPARATOR), parts.at(-1) ?? ""];
|
|
53041
|
+
}
|
|
53042
|
+
if (fieldCount === 7) {
|
|
53043
|
+
return [
|
|
53044
|
+
parts[0] ?? "",
|
|
53045
|
+
parts[1] ?? "",
|
|
53046
|
+
parts[2] ?? "",
|
|
53047
|
+
parts.slice(3, -3).join(SNAPSHOT_FIELD_SEPARATOR),
|
|
53048
|
+
parts.at(-3) ?? "",
|
|
53049
|
+
parts.at(-2) ?? "",
|
|
53050
|
+
parts.at(-1) ?? ""
|
|
53051
|
+
];
|
|
53052
|
+
}
|
|
53053
|
+
return parts;
|
|
53054
|
+
}
|
|
52807
53055
|
|
|
52808
53056
|
class SshExternalTmuxConnection {
|
|
52809
53057
|
deviceId;
|
|
@@ -52932,7 +53180,7 @@ class SshExternalTmuxConnection {
|
|
|
52932
53180
|
if (!this.connected) {
|
|
52933
53181
|
return;
|
|
52934
53182
|
}
|
|
52935
|
-
const argv = name ? ["new-window", "-n", name] : ["new-window"];
|
|
53183
|
+
const argv = name ? ["new-window", "-t", this.sessionName, "-n", name] : ["new-window", "-t", this.sessionName];
|
|
52936
53184
|
this.runAndRefresh(argv).catch((error) => {
|
|
52937
53185
|
this.callbacks.onError(error);
|
|
52938
53186
|
});
|
|
@@ -52965,80 +53213,7 @@ class SshExternalTmuxConnection {
|
|
|
52965
53213
|
if (!this.device) {
|
|
52966
53214
|
throw new Error("SSH device not loaded");
|
|
52967
53215
|
}
|
|
52968
|
-
const
|
|
52969
|
-
const port = this.device.port ?? 22;
|
|
52970
|
-
const username = resolveSshUsername(this.device.username, this.device.authMode);
|
|
52971
|
-
if (this.device.authMode === "configRef" || !host && this.device.sshConfigRef) {
|
|
52972
|
-
throw new Error("ssh_config_ref_not_supported: \u5F53\u524D\u7248\u672C\u6682\u4E0D\u652F\u6301 SSH Config \u5F15\u7528\uFF0C\u8BF7\u6539\u4E3A\u586B\u5199 host + username\uFF0C\u5E76\u9009\u62E9 Agent/\u79C1\u94A5/\u5BC6\u7801\u8BA4\u8BC1");
|
|
52973
|
-
}
|
|
52974
|
-
if (!host) {
|
|
52975
|
-
throw new Error("SSH device missing host");
|
|
52976
|
-
}
|
|
52977
|
-
const authConfig = {
|
|
52978
|
-
host,
|
|
52979
|
-
port,
|
|
52980
|
-
username
|
|
52981
|
-
};
|
|
52982
|
-
switch (this.device.authMode) {
|
|
52983
|
-
case "password": {
|
|
52984
|
-
if (!this.device.passwordEnc) {
|
|
52985
|
-
throw new Error("auth_password_missing: \u5BC6\u7801\u8BA4\u8BC1\u672A\u63D0\u4F9B\u5BC6\u7801");
|
|
52986
|
-
}
|
|
52987
|
-
authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
|
|
52988
|
-
scope: "device",
|
|
52989
|
-
entityId: this.device.id,
|
|
52990
|
-
field: "password_enc"
|
|
52991
|
-
});
|
|
52992
|
-
break;
|
|
52993
|
-
}
|
|
52994
|
-
case "key": {
|
|
52995
|
-
if (!this.device.privateKeyEnc) {
|
|
52996
|
-
throw new Error("auth_key_missing: \u79C1\u94A5\u8BA4\u8BC1\u672A\u63D0\u4F9B\u79C1\u94A5");
|
|
52997
|
-
}
|
|
52998
|
-
authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
|
|
52999
|
-
scope: "device",
|
|
53000
|
-
entityId: this.device.id,
|
|
53001
|
-
field: "private_key_enc"
|
|
53002
|
-
});
|
|
53003
|
-
if (this.device.privateKeyPassphraseEnc) {
|
|
53004
|
-
authConfig.passphrase = await this.deps.decrypt(this.device.privateKeyPassphraseEnc, {
|
|
53005
|
-
scope: "device",
|
|
53006
|
-
entityId: this.device.id,
|
|
53007
|
-
field: "private_key_passphrase_enc"
|
|
53008
|
-
});
|
|
53009
|
-
}
|
|
53010
|
-
break;
|
|
53011
|
-
}
|
|
53012
|
-
case "agent": {
|
|
53013
|
-
authConfig.agent = resolveSshAgentSocket("agent");
|
|
53014
|
-
break;
|
|
53015
|
-
}
|
|
53016
|
-
case "auto": {
|
|
53017
|
-
const agentSocket = resolveSshAgentSocket("auto");
|
|
53018
|
-
if (agentSocket) {
|
|
53019
|
-
authConfig.agent = agentSocket;
|
|
53020
|
-
}
|
|
53021
|
-
if (this.device.privateKeyEnc) {
|
|
53022
|
-
authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
|
|
53023
|
-
scope: "device",
|
|
53024
|
-
entityId: this.device.id,
|
|
53025
|
-
field: "private_key_enc"
|
|
53026
|
-
});
|
|
53027
|
-
} else if (this.device.passwordEnc) {
|
|
53028
|
-
authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
|
|
53029
|
-
scope: "device",
|
|
53030
|
-
entityId: this.device.id,
|
|
53031
|
-
field: "password_enc"
|
|
53032
|
-
});
|
|
53033
|
-
}
|
|
53034
|
-
break;
|
|
53035
|
-
}
|
|
53036
|
-
case "configRef":
|
|
53037
|
-
break;
|
|
53038
|
-
}
|
|
53039
|
-
if (this.device.authMode === "auto" && !authConfig.agent && !authConfig.privateKey && !authConfig.password) {
|
|
53040
|
-
throw new Error("auth_auto_missing: auto \u6A21\u5F0F\u4E0B\u672A\u627E\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08SSH_AUTH_SOCK / \u79C1\u94A5 / \u5BC6\u7801\uFF09");
|
|
53041
|
-
}
|
|
53216
|
+
const authConfig = await resolveSshConnectConfig(this.device, this.deps.decrypt);
|
|
53042
53217
|
const client = this.deps.createClient();
|
|
53043
53218
|
this.sshClient = client;
|
|
53044
53219
|
await new Promise((resolve, reject) => {
|
|
@@ -53263,11 +53438,12 @@ class SshExternalTmuxConnection {
|
|
|
53263
53438
|
}
|
|
53264
53439
|
async capturePaneHistory(paneId) {
|
|
53265
53440
|
const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
|
|
53266
|
-
const
|
|
53267
|
-
const
|
|
53268
|
-
const
|
|
53441
|
+
const alternateScreen = mode === "1";
|
|
53442
|
+
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-N", "-p"], true, 30000)).stdout;
|
|
53443
|
+
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-N", "-p", "-q"], true, 30000)).stdout;
|
|
53444
|
+
const history = alternateScreen ? hasRenderableTerminalContent2(normal) ? normal : alternate : normal || alternate;
|
|
53269
53445
|
if (history) {
|
|
53270
|
-
this.callbacks.onTerminalHistory(paneId, history);
|
|
53446
|
+
this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
|
|
53271
53447
|
}
|
|
53272
53448
|
}
|
|
53273
53449
|
async requestSnapshotInternal() {
|
|
@@ -53280,21 +53456,21 @@ class SshExternalTmuxConnection {
|
|
|
53280
53456
|
"-p",
|
|
53281
53457
|
"-t",
|
|
53282
53458
|
this.sessionName,
|
|
53283
|
-
"#{session_id}
|
|
53459
|
+
"#{session_id}|#{session_name}"
|
|
53284
53460
|
]),
|
|
53285
53461
|
this.runTmuxAllowFailure([
|
|
53286
53462
|
"list-windows",
|
|
53287
53463
|
"-t",
|
|
53288
53464
|
this.sessionName,
|
|
53289
53465
|
"-F",
|
|
53290
|
-
"#{window_id}
|
|
53466
|
+
"#{window_id}|#{window_index}|#{window_name}|#{window_active}"
|
|
53291
53467
|
]),
|
|
53292
53468
|
this.runTmuxAllowFailure([
|
|
53293
53469
|
"list-panes",
|
|
53294
53470
|
"-t",
|
|
53295
53471
|
this.sessionName,
|
|
53296
53472
|
"-F",
|
|
53297
|
-
"#{pane_id}
|
|
53473
|
+
"#{pane_id}|#{window_id}|#{pane_index}|#{pane_title}|#{pane_active}|#{pane_width}|#{pane_height}"
|
|
53298
53474
|
])
|
|
53299
53475
|
]);
|
|
53300
53476
|
if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
|
|
@@ -53312,7 +53488,7 @@ class SshExternalTmuxConnection {
|
|
|
53312
53488
|
if (!line.trim()) {
|
|
53313
53489
|
continue;
|
|
53314
53490
|
}
|
|
53315
|
-
const [id, name] = line
|
|
53491
|
+
const [id, name] = splitSnapshotFields(line, 2);
|
|
53316
53492
|
if (id) {
|
|
53317
53493
|
this.snapshotSession = { id, name: name ?? "" };
|
|
53318
53494
|
}
|
|
@@ -53325,7 +53501,7 @@ class SshExternalTmuxConnection {
|
|
|
53325
53501
|
if (!line.trim()) {
|
|
53326
53502
|
continue;
|
|
53327
53503
|
}
|
|
53328
|
-
const [id, indexRaw, name, activeRaw] = line
|
|
53504
|
+
const [id, indexRaw, name, activeRaw] = splitSnapshotFields(line, 4);
|
|
53329
53505
|
if (!id) {
|
|
53330
53506
|
continue;
|
|
53331
53507
|
}
|
|
@@ -53351,7 +53527,7 @@ class SshExternalTmuxConnection {
|
|
|
53351
53527
|
if (!line.trim()) {
|
|
53352
53528
|
continue;
|
|
53353
53529
|
}
|
|
53354
|
-
const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = line
|
|
53530
|
+
const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = splitSnapshotFields(line, 7);
|
|
53355
53531
|
if (!paneId || !windowId) {
|
|
53356
53532
|
continue;
|
|
53357
53533
|
}
|
|
@@ -53680,8 +53856,8 @@ class DeviceSessionRuntime {
|
|
|
53680
53856
|
onTerminalOutput: (paneId, data) => {
|
|
53681
53857
|
this.broadcast((listener) => listener.onTerminalOutput?.(paneId, data));
|
|
53682
53858
|
},
|
|
53683
|
-
onTerminalHistory: (paneId, data) => {
|
|
53684
|
-
this.broadcast((listener) => listener.onTerminalHistory?.(paneId, data));
|
|
53859
|
+
onTerminalHistory: (paneId, data, alternateScreen) => {
|
|
53860
|
+
this.broadcast((listener) => listener.onTerminalHistory?.(paneId, data, alternateScreen));
|
|
53685
53861
|
},
|
|
53686
53862
|
onSnapshot: (payload) => {
|
|
53687
53863
|
this.broadcast((listener) => listener.onSnapshot?.(payload));
|
|
@@ -53695,6 +53871,7 @@ class DeviceSessionRuntime {
|
|
|
53695
53871
|
}
|
|
53696
53872
|
this.closeEmitted = true;
|
|
53697
53873
|
this.terminated = true;
|
|
53874
|
+
this.connectPromise = null;
|
|
53698
53875
|
this.broadcast((listener) => listener.onClose?.());
|
|
53699
53876
|
}
|
|
53700
53877
|
});
|
|
@@ -53706,7 +53883,7 @@ class DeviceSessionRuntime {
|
|
|
53706
53883
|
};
|
|
53707
53884
|
}
|
|
53708
53885
|
async connect() {
|
|
53709
|
-
if (this.terminated
|
|
53886
|
+
if (this.terminated) {
|
|
53710
53887
|
return Promise.reject(new Error(`Device session runtime already terminated: ${this.deviceId}`));
|
|
53711
53888
|
}
|
|
53712
53889
|
if (this.connectPromise) {
|
|
@@ -53714,6 +53891,7 @@ class DeviceSessionRuntime {
|
|
|
53714
53891
|
}
|
|
53715
53892
|
this.connectPromise = this.connection.connect().catch((error) => {
|
|
53716
53893
|
this.terminated = true;
|
|
53894
|
+
this.connectPromise = null;
|
|
53717
53895
|
throw error;
|
|
53718
53896
|
});
|
|
53719
53897
|
return this.connectPromise;
|
|
@@ -53724,6 +53902,7 @@ class DeviceSessionRuntime {
|
|
|
53724
53902
|
}
|
|
53725
53903
|
this.terminated = true;
|
|
53726
53904
|
this.manualDisconnect = true;
|
|
53905
|
+
this.connectPromise = null;
|
|
53727
53906
|
this.connection.disconnect();
|
|
53728
53907
|
}
|
|
53729
53908
|
async shutdown() {
|
|
@@ -54138,6 +54317,144 @@ class PushSupervisor {
|
|
|
54138
54317
|
}
|
|
54139
54318
|
var pushSupervisor = new PushSupervisor;
|
|
54140
54319
|
|
|
54320
|
+
// ../../apps/gateway/src/ws/error-classify.ts
|
|
54321
|
+
function classifySshError(error) {
|
|
54322
|
+
const msg = error.message.toLowerCase();
|
|
54323
|
+
if (msg.includes("ssh_config_ref_not_supported")) {
|
|
54324
|
+
return {
|
|
54325
|
+
type: "ssh_config_ref_not_supported",
|
|
54326
|
+
messageKey: "sshError.configRefNotSupported"
|
|
54327
|
+
};
|
|
54328
|
+
}
|
|
54329
|
+
if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
|
|
54330
|
+
return {
|
|
54331
|
+
type: "agent_unavailable",
|
|
54332
|
+
messageKey: "sshError.agentUnavailable"
|
|
54333
|
+
};
|
|
54334
|
+
}
|
|
54335
|
+
if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
|
|
54336
|
+
return {
|
|
54337
|
+
type: "agent_no_identity",
|
|
54338
|
+
messageKey: "sshError.agentNoIdentities"
|
|
54339
|
+
};
|
|
54340
|
+
}
|
|
54341
|
+
if (msg.includes("permission denied")) {
|
|
54342
|
+
return {
|
|
54343
|
+
type: "auth_failed",
|
|
54344
|
+
messageKey: "sshError.authFailed"
|
|
54345
|
+
};
|
|
54346
|
+
}
|
|
54347
|
+
if (msg.includes("all configured authentication methods failed")) {
|
|
54348
|
+
return {
|
|
54349
|
+
type: "auth_failed",
|
|
54350
|
+
messageKey: "sshError.authFailedGeneric"
|
|
54351
|
+
};
|
|
54352
|
+
}
|
|
54353
|
+
if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
|
|
54354
|
+
return {
|
|
54355
|
+
type: "network_unreachable",
|
|
54356
|
+
messageKey: "sshError.networkUnreachable"
|
|
54357
|
+
};
|
|
54358
|
+
}
|
|
54359
|
+
if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
|
|
54360
|
+
return {
|
|
54361
|
+
type: "connection_refused",
|
|
54362
|
+
messageKey: "sshError.connectionRefused"
|
|
54363
|
+
};
|
|
54364
|
+
}
|
|
54365
|
+
if (msg.includes("timeout") || msg.includes("etimedout")) {
|
|
54366
|
+
return {
|
|
54367
|
+
type: "timeout",
|
|
54368
|
+
messageKey: "sshError.connectionTimeout"
|
|
54369
|
+
};
|
|
54370
|
+
}
|
|
54371
|
+
if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
|
|
54372
|
+
return {
|
|
54373
|
+
type: "host_not_found",
|
|
54374
|
+
messageKey: "sshError.hostNotFound"
|
|
54375
|
+
};
|
|
54376
|
+
}
|
|
54377
|
+
if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
|
|
54378
|
+
return {
|
|
54379
|
+
type: "handshake_failed",
|
|
54380
|
+
messageKey: "sshError.handshakeFailed"
|
|
54381
|
+
};
|
|
54382
|
+
}
|
|
54383
|
+
if (msg.includes("remote tmux unavailable") || msg.includes("tmux_not_found") || msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
|
|
54384
|
+
return {
|
|
54385
|
+
type: "tmux_unavailable",
|
|
54386
|
+
messageKey: "sshError.tmuxUnavailable"
|
|
54387
|
+
};
|
|
54388
|
+
}
|
|
54389
|
+
return {
|
|
54390
|
+
type: "unknown",
|
|
54391
|
+
messageKey: "sshError.unknown",
|
|
54392
|
+
messageParams: { message: error.message }
|
|
54393
|
+
};
|
|
54394
|
+
}
|
|
54395
|
+
|
|
54396
|
+
// ../../apps/gateway/src/api/test-connection.ts
|
|
54397
|
+
function inferFailurePhase(errorType) {
|
|
54398
|
+
if (errorType === "tmux_unavailable") {
|
|
54399
|
+
return "bootstrap";
|
|
54400
|
+
}
|
|
54401
|
+
return "connect";
|
|
54402
|
+
}
|
|
54403
|
+
function json(data, status = 200) {
|
|
54404
|
+
return new Response(JSON.stringify(data), {
|
|
54405
|
+
status,
|
|
54406
|
+
headers: {
|
|
54407
|
+
"Content-Type": "application/json"
|
|
54408
|
+
}
|
|
54409
|
+
});
|
|
54410
|
+
}
|
|
54411
|
+
async function handleDeviceTestConnection(deviceId, inputDeps = {}) {
|
|
54412
|
+
const deps = {
|
|
54413
|
+
getDevice: inputDeps.getDevice ?? ((currentDeviceId) => getDeviceById(currentDeviceId)),
|
|
54414
|
+
acquireRuntime: inputDeps.acquireRuntime ?? ((currentDeviceId) => tmuxRuntimeRegistry.acquire(currentDeviceId)),
|
|
54415
|
+
releaseRuntime: inputDeps.releaseRuntime ?? (async (currentDeviceId) => {
|
|
54416
|
+
await tmuxRuntimeRegistry.release(currentDeviceId);
|
|
54417
|
+
}),
|
|
54418
|
+
translate: inputDeps.translate ?? t2
|
|
54419
|
+
};
|
|
54420
|
+
const device = deps.getDevice(deviceId);
|
|
54421
|
+
if (!device) {
|
|
54422
|
+
return json({ error: deps.translate("apiError.deviceNotFound") }, 404);
|
|
54423
|
+
}
|
|
54424
|
+
const classifyErrorResponse = (error) => {
|
|
54425
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
54426
|
+
const classified = classifySshError(new Error(rawMessage));
|
|
54427
|
+
const payload = {
|
|
54428
|
+
success: false,
|
|
54429
|
+
tmuxAvailable: false,
|
|
54430
|
+
phase: inferFailurePhase(classified.type),
|
|
54431
|
+
errorType: classified.type,
|
|
54432
|
+
message: deps.translate(classified.messageKey, classified.messageParams),
|
|
54433
|
+
rawMessage
|
|
54434
|
+
};
|
|
54435
|
+
return json(payload);
|
|
54436
|
+
};
|
|
54437
|
+
let runtime = null;
|
|
54438
|
+
try {
|
|
54439
|
+
runtime = await deps.acquireRuntime(deviceId);
|
|
54440
|
+
await runtime.connect();
|
|
54441
|
+
runtime.requestSnapshot();
|
|
54442
|
+
const payload = {
|
|
54443
|
+
success: true,
|
|
54444
|
+
tmuxAvailable: true,
|
|
54445
|
+
phase: "ready",
|
|
54446
|
+
message: deps.translate("common.success")
|
|
54447
|
+
};
|
|
54448
|
+
return json(payload);
|
|
54449
|
+
} catch (error) {
|
|
54450
|
+
return classifyErrorResponse(error);
|
|
54451
|
+
} finally {
|
|
54452
|
+
if (runtime) {
|
|
54453
|
+
await deps.releaseRuntime(deviceId, runtime);
|
|
54454
|
+
}
|
|
54455
|
+
}
|
|
54456
|
+
}
|
|
54457
|
+
|
|
54141
54458
|
// ../../apps/gateway/src/api/index.ts
|
|
54142
54459
|
function shouldReconnectPushSupervisor(existing, updates) {
|
|
54143
54460
|
if (updates.type !== undefined && updates.type !== existing.type)
|
|
@@ -54286,28 +54603,28 @@ function handleApiRequest(req, _server) {
|
|
|
54286
54603
|
return handleGetManifest(req.method);
|
|
54287
54604
|
}
|
|
54288
54605
|
if (path === "/healthz" && req.method === "GET") {
|
|
54289
|
-
return
|
|
54606
|
+
return json2({ status: "ok", restarting: runtimeController.isRestarting() });
|
|
54290
54607
|
}
|
|
54291
|
-
return
|
|
54608
|
+
return json2({ error: t2("apiError.notFound") }, 404);
|
|
54292
54609
|
}
|
|
54293
54610
|
async function handleGetDevices() {
|
|
54294
54611
|
const devices2 = getAllDevices();
|
|
54295
|
-
return
|
|
54612
|
+
return json2({ devices: devices2 });
|
|
54296
54613
|
}
|
|
54297
54614
|
async function handleGetDevice(id) {
|
|
54298
54615
|
const device = getDeviceById(id);
|
|
54299
54616
|
if (!device) {
|
|
54300
|
-
return
|
|
54617
|
+
return json2({ error: t2("apiError.deviceNotFound") }, 404);
|
|
54301
54618
|
}
|
|
54302
|
-
return
|
|
54619
|
+
return json2({ device });
|
|
54303
54620
|
}
|
|
54304
54621
|
async function handleCreateDevice(req) {
|
|
54305
54622
|
const body = await req.json();
|
|
54306
54623
|
if (!body.name || !body.type || !body.authMode) {
|
|
54307
|
-
return
|
|
54624
|
+
return json2({ error: t2("apiError.missingFields") }, 400);
|
|
54308
54625
|
}
|
|
54309
54626
|
if (body.type === "ssh" && !body.host && !body.sshConfigRef) {
|
|
54310
|
-
return
|
|
54627
|
+
return json2({ error: t2("apiError.sshRequiresHost") }, 400);
|
|
54311
54628
|
}
|
|
54312
54629
|
const now = new Date().toISOString();
|
|
54313
54630
|
const device = {
|
|
@@ -54328,12 +54645,12 @@ async function handleCreateDevice(req) {
|
|
|
54328
54645
|
};
|
|
54329
54646
|
createDevice(device);
|
|
54330
54647
|
await pushSupervisor.upsert(device.id);
|
|
54331
|
-
return
|
|
54648
|
+
return json2({ device }, 201);
|
|
54332
54649
|
}
|
|
54333
54650
|
async function handleUpdateDevice(req, id) {
|
|
54334
54651
|
const existing = getDeviceById(id);
|
|
54335
54652
|
if (!existing) {
|
|
54336
|
-
return
|
|
54653
|
+
return json2({ error: t2("apiError.deviceNotFound") }, 404);
|
|
54337
54654
|
}
|
|
54338
54655
|
const body = await req.json();
|
|
54339
54656
|
const updates = {};
|
|
@@ -54363,61 +54680,53 @@ async function handleUpdateDevice(req, id) {
|
|
|
54363
54680
|
await pushSupervisor.reconnect(id);
|
|
54364
54681
|
}
|
|
54365
54682
|
const device = getDeviceById(id);
|
|
54366
|
-
return
|
|
54683
|
+
return json2({ device });
|
|
54367
54684
|
}
|
|
54368
54685
|
async function handleDeleteDevice(id) {
|
|
54369
54686
|
const existing = getDeviceById(id);
|
|
54370
54687
|
if (!existing) {
|
|
54371
|
-
return
|
|
54688
|
+
return json2({ error: t2("apiError.deviceNotFound") }, 404);
|
|
54372
54689
|
}
|
|
54373
54690
|
deleteDevice(id);
|
|
54374
54691
|
pushSupervisor.remove(id);
|
|
54375
|
-
return
|
|
54692
|
+
return json2({ success: true });
|
|
54376
54693
|
}
|
|
54377
54694
|
async function handleTestConnection(id) {
|
|
54378
|
-
|
|
54379
|
-
if (!device) {
|
|
54380
|
-
return json({ error: t2("apiError.deviceNotFound") }, 404);
|
|
54381
|
-
}
|
|
54382
|
-
return json({
|
|
54383
|
-
success: true,
|
|
54384
|
-
tmuxAvailable: false,
|
|
54385
|
-
message: "Connection test not fully implemented yet"
|
|
54386
|
-
});
|
|
54695
|
+
return handleDeviceTestConnection(id);
|
|
54387
54696
|
}
|
|
54388
54697
|
async function handleGetSiteSettings() {
|
|
54389
|
-
return
|
|
54698
|
+
return json2({ settings: getSiteSettings() });
|
|
54390
54699
|
}
|
|
54391
54700
|
async function handleUpdateSiteSettings(req) {
|
|
54392
54701
|
try {
|
|
54393
54702
|
const body = await req.json();
|
|
54394
54703
|
const updates = normalizeSiteSettingsInput(body);
|
|
54395
54704
|
const settings = updateSiteSettings(updates);
|
|
54396
|
-
return
|
|
54705
|
+
return json2({ settings });
|
|
54397
54706
|
} catch (err) {
|
|
54398
|
-
return
|
|
54707
|
+
return json2({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
|
|
54399
54708
|
}
|
|
54400
54709
|
}
|
|
54401
54710
|
async function handleRestartGateway() {
|
|
54402
54711
|
setTimeout(() => {
|
|
54403
54712
|
runtimeController.requestRestart();
|
|
54404
54713
|
}, 50);
|
|
54405
|
-
return
|
|
54714
|
+
return json2({
|
|
54406
54715
|
success: true,
|
|
54407
54716
|
message: t2("settings.restartScheduled")
|
|
54408
54717
|
});
|
|
54409
54718
|
}
|
|
54410
54719
|
async function handleGetTelegramBots() {
|
|
54411
54720
|
const bots = getTelegramBotsWithStats();
|
|
54412
|
-
return
|
|
54721
|
+
return json2({ bots });
|
|
54413
54722
|
}
|
|
54414
54723
|
async function handleCreateTelegramBot(req) {
|
|
54415
54724
|
const body = await req.json();
|
|
54416
54725
|
if (!body.name?.trim()) {
|
|
54417
|
-
return
|
|
54726
|
+
return json2({ error: t2("apiError.botNameRequired") }, 400);
|
|
54418
54727
|
}
|
|
54419
54728
|
if (!body.token?.trim()) {
|
|
54420
|
-
return
|
|
54729
|
+
return json2({ error: t2("apiError.botTokenRequired") }, 400);
|
|
54421
54730
|
}
|
|
54422
54731
|
const now = new Date().toISOString();
|
|
54423
54732
|
createTelegramBot({
|
|
@@ -54431,26 +54740,26 @@ async function handleCreateTelegramBot(req) {
|
|
|
54431
54740
|
updatedAt: now
|
|
54432
54741
|
});
|
|
54433
54742
|
await telegramService.refresh();
|
|
54434
|
-
return
|
|
54743
|
+
return json2({ success: true }, 201);
|
|
54435
54744
|
}
|
|
54436
54745
|
async function handleUpdateTelegramBot(req, botId) {
|
|
54437
54746
|
const existing = getTelegramBotById(botId);
|
|
54438
54747
|
if (!existing) {
|
|
54439
|
-
return
|
|
54748
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54440
54749
|
}
|
|
54441
54750
|
const body = await req.json();
|
|
54442
54751
|
const updates = {};
|
|
54443
54752
|
if (body.name !== undefined) {
|
|
54444
54753
|
const value = body.name.trim();
|
|
54445
54754
|
if (!value) {
|
|
54446
|
-
return
|
|
54755
|
+
return json2({ error: t2("apiError.botNameRequired") }, 400);
|
|
54447
54756
|
}
|
|
54448
54757
|
updates.name = value;
|
|
54449
54758
|
}
|
|
54450
54759
|
if (body.token !== undefined) {
|
|
54451
54760
|
const token = body.token.trim();
|
|
54452
54761
|
if (!token) {
|
|
54453
|
-
return
|
|
54762
|
+
return json2({ error: t2("apiError.botTokenRequired") }, 400);
|
|
54454
54763
|
}
|
|
54455
54764
|
updates.tokenEnc = await encrypt(token);
|
|
54456
54765
|
}
|
|
@@ -54462,69 +54771,69 @@ async function handleUpdateTelegramBot(req, botId) {
|
|
|
54462
54771
|
}
|
|
54463
54772
|
updateTelegramBot(botId, updates);
|
|
54464
54773
|
await telegramService.refresh();
|
|
54465
|
-
return
|
|
54774
|
+
return json2({ success: true });
|
|
54466
54775
|
}
|
|
54467
54776
|
async function handleDeleteTelegramBot(botId) {
|
|
54468
54777
|
const existing = getTelegramBotById(botId);
|
|
54469
54778
|
if (!existing) {
|
|
54470
|
-
return
|
|
54779
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54471
54780
|
}
|
|
54472
54781
|
deleteTelegramBot(botId);
|
|
54473
54782
|
await telegramService.refresh();
|
|
54474
|
-
return
|
|
54783
|
+
return json2({ success: true });
|
|
54475
54784
|
}
|
|
54476
54785
|
async function handleListTelegramChats(botId) {
|
|
54477
54786
|
const existing = getTelegramBotById(botId);
|
|
54478
54787
|
if (!existing) {
|
|
54479
|
-
return
|
|
54788
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54480
54789
|
}
|
|
54481
54790
|
const chats = listTelegramChatsByBot(botId);
|
|
54482
|
-
return
|
|
54791
|
+
return json2({ chats });
|
|
54483
54792
|
}
|
|
54484
54793
|
async function handleApproveTelegramChat(botId, chatId) {
|
|
54485
54794
|
const existing = getTelegramBotById(botId);
|
|
54486
54795
|
if (!existing) {
|
|
54487
|
-
return
|
|
54796
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54488
54797
|
}
|
|
54489
54798
|
const chat = approveTelegramChat(botId, chatId);
|
|
54490
54799
|
if (!chat) {
|
|
54491
|
-
return
|
|
54800
|
+
return json2({ error: t2("apiError.chatNotFound") }, 404);
|
|
54492
54801
|
}
|
|
54493
54802
|
const settings = getSiteSettings();
|
|
54494
54803
|
await telegramService.sendTestMessage(botId, chatId, t2("telegram.approveMessageTemplate", {
|
|
54495
54804
|
botName: existing.name,
|
|
54496
54805
|
time: new Date().toLocaleString(toBCP47(settings.language))
|
|
54497
54806
|
}));
|
|
54498
|
-
return
|
|
54807
|
+
return json2({ chat });
|
|
54499
54808
|
}
|
|
54500
54809
|
async function handleDeleteTelegramChat(botId, chatId) {
|
|
54501
54810
|
const existing = getTelegramBotById(botId);
|
|
54502
54811
|
if (!existing) {
|
|
54503
|
-
return
|
|
54812
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54504
54813
|
}
|
|
54505
54814
|
deleteTelegramChat(botId, chatId);
|
|
54506
|
-
return
|
|
54815
|
+
return json2({ success: true });
|
|
54507
54816
|
}
|
|
54508
54817
|
async function handleTestTelegramChat(botId, chatId) {
|
|
54509
54818
|
const bot = getTelegramBotById(botId);
|
|
54510
54819
|
if (!bot) {
|
|
54511
|
-
return
|
|
54820
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54512
54821
|
}
|
|
54513
54822
|
const settings = getSiteSettings();
|
|
54514
54823
|
await telegramService.sendTestMessage(botId, chatId, t2("telegram.testMessageTemplate", {
|
|
54515
54824
|
siteName: settings.siteName,
|
|
54516
54825
|
time: new Date().toLocaleString(toBCP47(settings.language))
|
|
54517
54826
|
}));
|
|
54518
|
-
return
|
|
54827
|
+
return json2({ success: true });
|
|
54519
54828
|
}
|
|
54520
54829
|
async function handleGetWebhooks() {
|
|
54521
54830
|
const webhooks = getAllWebhookEndpoints();
|
|
54522
|
-
return
|
|
54831
|
+
return json2({ webhooks });
|
|
54523
54832
|
}
|
|
54524
54833
|
async function handleCreateWebhook(req) {
|
|
54525
54834
|
const body = await req.json();
|
|
54526
54835
|
if (!body.url || !body.secret) {
|
|
54527
|
-
return
|
|
54836
|
+
return json2({ error: t2("apiError.urlAndSecretRequired") }, 400);
|
|
54528
54837
|
}
|
|
54529
54838
|
const now = new Date().toISOString();
|
|
54530
54839
|
const endpoint = {
|
|
@@ -54537,11 +54846,11 @@ async function handleCreateWebhook(req) {
|
|
|
54537
54846
|
updatedAt: now
|
|
54538
54847
|
};
|
|
54539
54848
|
createWebhookEndpoint(endpoint);
|
|
54540
|
-
return
|
|
54849
|
+
return json2({ webhook: endpoint }, 201);
|
|
54541
54850
|
}
|
|
54542
54851
|
async function handleDeleteWebhook(id) {
|
|
54543
54852
|
deleteWebhookEndpoint(id);
|
|
54544
|
-
return
|
|
54853
|
+
return json2({ success: true });
|
|
54545
54854
|
}
|
|
54546
54855
|
async function handleGetManifest(method) {
|
|
54547
54856
|
const settings = getSiteSettings();
|
|
@@ -54574,7 +54883,7 @@ function manifestJson(data, method) {
|
|
|
54574
54883
|
}
|
|
54575
54884
|
});
|
|
54576
54885
|
}
|
|
54577
|
-
function
|
|
54886
|
+
function json2(data, status = 200, headers = {}) {
|
|
54578
54887
|
return new Response(JSON.stringify(data), {
|
|
54579
54888
|
status,
|
|
54580
54889
|
headers: {
|
|
@@ -54585,7 +54894,7 @@ function json(data, status = 200, headers = {}) {
|
|
|
54585
54894
|
}
|
|
54586
54895
|
|
|
54587
54896
|
// ../../apps/gateway/src/db/migrate.ts
|
|
54588
|
-
import { existsSync as
|
|
54897
|
+
import { existsSync as existsSync3 } from "fs";
|
|
54589
54898
|
import { resolve } from "path";
|
|
54590
54899
|
|
|
54591
54900
|
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/migrator.js
|
|
@@ -54632,7 +54941,7 @@ function resolveMigrationsFolder() {
|
|
|
54632
54941
|
if (fromEnv)
|
|
54633
54942
|
return fromEnv;
|
|
54634
54943
|
const byCwd = resolve(process.cwd(), "drizzle");
|
|
54635
|
-
if (
|
|
54944
|
+
if (existsSync3(byCwd))
|
|
54636
54945
|
return byCwd;
|
|
54637
54946
|
return resolve(import.meta.dir, "../../drizzle");
|
|
54638
54947
|
}
|
|
@@ -55053,7 +55362,7 @@ class SwitchBarrier {
|
|
|
55053
55362
|
}
|
|
55054
55363
|
pending.callbacks.onAckSent?.();
|
|
55055
55364
|
}
|
|
55056
|
-
sendTermHistory(ws, deviceId, paneId, historyData) {
|
|
55365
|
+
sendTermHistory(ws, deviceId, paneId, historyData, alternateScreen) {
|
|
55057
55366
|
const pending = this.getPending(ws, deviceId);
|
|
55058
55367
|
if (!pending)
|
|
55059
55368
|
return;
|
|
@@ -55079,6 +55388,7 @@ class SwitchBarrier {
|
|
|
55079
55388
|
paneId: context.paneId,
|
|
55080
55389
|
selectToken: context.selectToken,
|
|
55081
55390
|
encoding: 2,
|
|
55391
|
+
alternateScreen,
|
|
55082
55392
|
data: historyData
|
|
55083
55393
|
}, borshState.seqGen, borshState.maxFrameBytes);
|
|
55084
55394
|
sendToClient(ws, historyMessages);
|
|
@@ -55200,82 +55510,6 @@ class SwitchBarrier {
|
|
|
55200
55510
|
}
|
|
55201
55511
|
var switchBarrier = new SwitchBarrier;
|
|
55202
55512
|
|
|
55203
|
-
// ../../apps/gateway/src/ws/error-classify.ts
|
|
55204
|
-
function classifySshError(error) {
|
|
55205
|
-
const msg = error.message.toLowerCase();
|
|
55206
|
-
if (msg.includes("ssh_config_ref_not_supported")) {
|
|
55207
|
-
return {
|
|
55208
|
-
type: "ssh_config_ref_not_supported",
|
|
55209
|
-
messageKey: "sshError.configRefNotSupported"
|
|
55210
|
-
};
|
|
55211
|
-
}
|
|
55212
|
-
if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
|
|
55213
|
-
return {
|
|
55214
|
-
type: "agent_unavailable",
|
|
55215
|
-
messageKey: "sshError.agentUnavailable"
|
|
55216
|
-
};
|
|
55217
|
-
}
|
|
55218
|
-
if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
|
|
55219
|
-
return {
|
|
55220
|
-
type: "agent_no_identity",
|
|
55221
|
-
messageKey: "sshError.agentNoIdentities"
|
|
55222
|
-
};
|
|
55223
|
-
}
|
|
55224
|
-
if (msg.includes("permission denied")) {
|
|
55225
|
-
return {
|
|
55226
|
-
type: "auth_failed",
|
|
55227
|
-
messageKey: "sshError.authFailed"
|
|
55228
|
-
};
|
|
55229
|
-
}
|
|
55230
|
-
if (msg.includes("all configured authentication methods failed")) {
|
|
55231
|
-
return {
|
|
55232
|
-
type: "auth_failed",
|
|
55233
|
-
messageKey: "sshError.authFailedGeneric"
|
|
55234
|
-
};
|
|
55235
|
-
}
|
|
55236
|
-
if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
|
|
55237
|
-
return {
|
|
55238
|
-
type: "network_unreachable",
|
|
55239
|
-
messageKey: "sshError.networkUnreachable"
|
|
55240
|
-
};
|
|
55241
|
-
}
|
|
55242
|
-
if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
|
|
55243
|
-
return {
|
|
55244
|
-
type: "connection_refused",
|
|
55245
|
-
messageKey: "sshError.connectionRefused"
|
|
55246
|
-
};
|
|
55247
|
-
}
|
|
55248
|
-
if (msg.includes("timeout") || msg.includes("etimedout")) {
|
|
55249
|
-
return {
|
|
55250
|
-
type: "timeout",
|
|
55251
|
-
messageKey: "sshError.connectionTimeout"
|
|
55252
|
-
};
|
|
55253
|
-
}
|
|
55254
|
-
if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
|
|
55255
|
-
return {
|
|
55256
|
-
type: "host_not_found",
|
|
55257
|
-
messageKey: "sshError.hostNotFound"
|
|
55258
|
-
};
|
|
55259
|
-
}
|
|
55260
|
-
if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
|
|
55261
|
-
return {
|
|
55262
|
-
type: "handshake_failed",
|
|
55263
|
-
messageKey: "sshError.handshakeFailed"
|
|
55264
|
-
};
|
|
55265
|
-
}
|
|
55266
|
-
if (msg.includes("remote tmux unavailable") || msg.includes("tmux_not_found") || msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
|
|
55267
|
-
return {
|
|
55268
|
-
type: "tmux_unavailable",
|
|
55269
|
-
messageKey: "sshError.tmuxUnavailable"
|
|
55270
|
-
};
|
|
55271
|
-
}
|
|
55272
|
-
return {
|
|
55273
|
-
type: "unknown",
|
|
55274
|
-
messageKey: "sshError.unknown",
|
|
55275
|
-
messageParams: { message: error.message }
|
|
55276
|
-
};
|
|
55277
|
-
}
|
|
55278
|
-
|
|
55279
55513
|
// ../../apps/gateway/src/ws/index.ts
|
|
55280
55514
|
var defaultDeps2 = {
|
|
55281
55515
|
acquireRuntime: async (deviceId) => tmuxRuntimeRegistry.acquire(deviceId),
|
|
@@ -55328,8 +55562,8 @@ class WebSocketServer {
|
|
|
55328
55562
|
onTerminalOutput: (paneId, data) => {
|
|
55329
55563
|
this.broadcastTerminalOutput(deviceId, paneId, data);
|
|
55330
55564
|
},
|
|
55331
|
-
onTerminalHistory: (paneId, data) => {
|
|
55332
|
-
this.broadcastTerminalHistory(deviceId, paneId, data);
|
|
55565
|
+
onTerminalHistory: (paneId, data, alternateScreen) => {
|
|
55566
|
+
this.broadcastTerminalHistory(deviceId, paneId, data, alternateScreen);
|
|
55333
55567
|
},
|
|
55334
55568
|
onSnapshot: (payload) => {
|
|
55335
55569
|
this.broadcastStateSnapshot(deviceId, payload);
|
|
@@ -55863,7 +56097,7 @@ class WebSocketServer {
|
|
|
55863
56097
|
this.sendChunked(client, exports_ws_borsh.KIND_TERM_OUTPUT, payloadBytes);
|
|
55864
56098
|
}
|
|
55865
56099
|
}
|
|
55866
|
-
broadcastTerminalHistory(deviceId, paneId, data) {
|
|
56100
|
+
broadcastTerminalHistory(deviceId, paneId, data, alternateScreen) {
|
|
55867
56101
|
const entry = this.connections.get(deviceId);
|
|
55868
56102
|
if (!entry)
|
|
55869
56103
|
return;
|
|
@@ -55872,7 +56106,7 @@ class WebSocketServer {
|
|
|
55872
56106
|
if (client.data.borshState.selectedPanes[deviceId] !== paneId) {
|
|
55873
56107
|
continue;
|
|
55874
56108
|
}
|
|
55875
|
-
switchBarrier.sendTermHistory(client, deviceId, paneId, historyBytes);
|
|
56109
|
+
switchBarrier.sendTermHistory(client, deviceId, paneId, historyBytes, alternateScreen);
|
|
55876
56110
|
}
|
|
55877
56111
|
}
|
|
55878
56112
|
broadcastError(deviceId, err) {
|
|
@@ -56294,9 +56528,9 @@ async function serveFrontend(req, staticRoot) {
|
|
|
56294
56528
|
if (!requestedPath) {
|
|
56295
56529
|
return new Response(t3("runtime.forbidden"), { status: 403 });
|
|
56296
56530
|
}
|
|
56297
|
-
const indexPath =
|
|
56298
|
-
const targetPath =
|
|
56299
|
-
if (!
|
|
56531
|
+
const indexPath = join5(staticRoot, "index.html");
|
|
56532
|
+
const targetPath = existsSync4(requestedPath) ? requestedPath : indexPath;
|
|
56533
|
+
if (!existsSync4(targetPath)) {
|
|
56300
56534
|
return new Response(t3("runtime.frontendMissing"), { status: 500 });
|
|
56301
56535
|
}
|
|
56302
56536
|
const headers = new Headers;
|