tmex-cli 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/runtime/server.js +414 -88
  2. package/package.json +1 -1
  3. package/resources/fe-dist/assets/DevicePage-B9rZioAr.js +26 -0
  4. package/resources/fe-dist/assets/{DevicePage-n4JoyDed.js.map → DevicePage-B9rZioAr.js.map} +1 -1
  5. package/resources/fe-dist/assets/DevicesPage-Bco831ry.js +17 -0
  6. package/resources/fe-dist/assets/DevicesPage-Bco831ry.js.map +1 -0
  7. package/resources/fe-dist/assets/SettingsPage-BRs8Unfx.js +17 -0
  8. package/resources/fe-dist/assets/{SettingsPage-hS99lHcp.js.map → SettingsPage-BRs8Unfx.js.map} +1 -1
  9. package/resources/fe-dist/assets/index-40zyi_9K.css +1 -0
  10. package/resources/fe-dist/assets/index-BhBqXsZI.js +215 -0
  11. package/resources/fe-dist/assets/index-BhBqXsZI.js.map +1 -0
  12. package/resources/fe-dist/assets/select-D70hG6p7.js +17 -0
  13. package/resources/fe-dist/assets/{select-DGBwxGiK.js.map → select-D70hG6p7.js.map} +1 -1
  14. package/resources/fe-dist/assets/switch-DsyIGzyC.js +12 -0
  15. package/resources/fe-dist/assets/{switch-CWUBjs7N.js.map → switch-DsyIGzyC.js.map} +1 -1
  16. package/resources/fe-dist/assets/useValueChanged-CBb-JR7o.js +7 -0
  17. package/resources/fe-dist/assets/{useValueChanged-DwJ_SDCu.js.map → useValueChanged-CBb-JR7o.js.map} +1 -1
  18. package/resources/fe-dist/index.html +2 -2
  19. package/resources/gateway-drizzle/0003_glamorous_lizard.sql +1 -0
  20. package/resources/gateway-drizzle/meta/0003_snapshot.json +542 -0
  21. package/resources/gateway-drizzle/meta/_journal.json +7 -0
  22. package/resources/fe-dist/assets/DevicePage-n4JoyDed.js +0 -26
  23. package/resources/fe-dist/assets/DevicesPage-BwLKaiUR.js +0 -17
  24. package/resources/fe-dist/assets/DevicesPage-BwLKaiUR.js.map +0 -1
  25. package/resources/fe-dist/assets/SettingsPage-hS99lHcp.js +0 -17
  26. package/resources/fe-dist/assets/index-CJaX5rlK.css +0 -1
  27. package/resources/fe-dist/assets/index-CJyFlAt8.js +0 -449
  28. package/resources/fe-dist/assets/index-CJyFlAt8.js.map +0 -1
  29. package/resources/fe-dist/assets/select-DGBwxGiK.js +0 -17
  30. package/resources/fe-dist/assets/switch-CWUBjs7N.js +0 -12
  31. package/resources/fe-dist/assets/useValueChanged-DwJ_SDCu.js +0 -7
@@ -20363,8 +20363,13 @@ var I18N_RESOURCES = {
20363
20363
  title: "Device Management",
20364
20364
  devices: "Devices",
20365
20365
  addDevice: "Add Device",
20366
+ addDeviceDescription: "Fill in device details and choose a connection method",
20366
20367
  addFirstDevice: "Add First Device",
20367
20368
  editDevice: "Edit Device",
20369
+ editDeviceDescription: "Update device configuration",
20370
+ sectionBasic: "Basic Info",
20371
+ sectionConnection: "Connection",
20372
+ sectionAuth: "Authentication",
20368
20373
  noDevices: "No Devices",
20369
20374
  noDevicesDescription: "Add a local or SSH device to get started",
20370
20375
  name: "Device Name",
@@ -20397,6 +20402,7 @@ var I18N_RESOURCES = {
20397
20402
  disconnected: "Disconnected",
20398
20403
  connecting: "Connecting...",
20399
20404
  deleteConfirm: "Delete this device?",
20405
+ deleteDescription: 'Device "{{name}}" will be permanently removed. This action cannot be undone.',
20400
20406
  deleteSuccess: "Device deleted",
20401
20407
  createSuccess: "Device created",
20402
20408
  updateSuccess: "Device updated",
@@ -20518,6 +20524,8 @@ var I18N_RESOURCES = {
20518
20524
  chatId: "Chat ID",
20519
20525
  applyTime: "Application Time",
20520
20526
  gatewayOnline: "\uD83D\uDFE2 Gateway online @ {{siteName}}",
20527
+ deviceConnectionError: `\uD83D\uDD34 {{siteName}}: Connection error on device "{{deviceName}}" ({{host}}) [{{category}}]
20528
+ {{error}}`,
20521
20529
  authSuccess: "\u2705 Authorized. You will now receive notifications.",
20522
20530
  authPending: "\u23F3 Authorization request received. Please approve in tmex settings.",
20523
20531
  authFailed: "\u274C Authorization request failed. Please contact administrator.",
@@ -20558,11 +20566,30 @@ Time: {{time}}`,
20558
20566
  hostNotFound: "Host not found: Unable to resolve hostname. Please check DNS or hostname configuration.",
20559
20567
  handshakeFailed: "Handshake failed: Unable to establish secure connection. Possibly incompatible key exchange algorithm.",
20560
20568
  tmuxUnavailable: "Remote tmux unavailable or failed to start. Please ensure tmux is installed and available in the remote shell PATH.",
20569
+ connectionClosed: "Connection closed, attempting to reconnect",
20561
20570
  unknown: "Connection failed: {{message}}",
20562
20571
  reconnecting: "Connection interrupted, reconnecting in {{delay}} seconds ({{attempt}}/{{maxRetries}})",
20563
20572
  reconnectFailed: "Auto-reconnect failed, please retry manually",
20564
20573
  reconnected: "Device reconnected automatically"
20565
20574
  },
20575
+ deviceStatus: {
20576
+ reconnecting: "Reconnecting {{delay}}s",
20577
+ offline: "Offline",
20578
+ errorBadge: {
20579
+ authFailed: "Auth failed",
20580
+ agentUnavailable: "Agent unavailable",
20581
+ agentNoIdentity: "Agent has no keys",
20582
+ configRefNotSupported: "SSH Config unsupported",
20583
+ networkUnreachable: "Network unreachable",
20584
+ connectionRefused: "Refused",
20585
+ timeout: "Timeout",
20586
+ hostNotFound: "Host not found",
20587
+ handshakeFailed: "Handshake failed",
20588
+ tmuxUnavailable: "Tmux unavailable",
20589
+ connectionClosed: "Disconnected",
20590
+ unknown: "Connection error"
20591
+ }
20592
+ },
20566
20593
  websocket: {
20567
20594
  error: "WebSocket connection error",
20568
20595
  checkGateway: "Please check Gateway status",
@@ -20695,8 +20722,13 @@ Time: {{time}}`,
20695
20722
  title: "\u8BBE\u5907\u7BA1\u7406",
20696
20723
  devices: "\u8BBE\u5907",
20697
20724
  addDevice: "\u6DFB\u52A0\u8BBE\u5907",
20725
+ addDeviceDescription: "\u586B\u5199\u8BBE\u5907\u4FE1\u606F\u5E76\u9009\u62E9\u8FDE\u63A5\u65B9\u5F0F",
20698
20726
  addFirstDevice: "\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u8BBE\u5907",
20699
20727
  editDevice: "\u4FEE\u6539\u8BBE\u5907",
20728
+ editDeviceDescription: "\u66F4\u65B0\u8BBE\u5907\u914D\u7F6E",
20729
+ sectionBasic: "\u57FA\u672C\u4FE1\u606F",
20730
+ sectionConnection: "\u8FDE\u63A5\u4FE1\u606F",
20731
+ sectionAuth: "\u8BA4\u8BC1\u4FE1\u606F",
20700
20732
  noDevices: "\u6682\u65E0\u8BBE\u5907",
20701
20733
  noDevicesDescription: "\u6DFB\u52A0\u672C\u5730\u6216 SSH \u8BBE\u5907\u5F00\u59CB\u4F7F\u7528",
20702
20734
  name: "\u8BBE\u5907\u540D\u79F0",
@@ -20729,6 +20761,7 @@ Time: {{time}}`,
20729
20761
  disconnected: "\u5DF2\u65AD\u5F00",
20730
20762
  connecting: "\u8FDE\u63A5\u4E2D...",
20731
20763
  deleteConfirm: "\u5220\u9664\u6B64\u8BBE\u5907\uFF1F",
20764
+ deleteDescription: '\u8BBE\u5907 "{{name}}" \u5C06\u88AB\u6C38\u4E45\u79FB\u9664\uFF0C\u6B64\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500\u3002',
20732
20765
  deleteSuccess: "\u8BBE\u5907\u5DF2\u5220\u9664",
20733
20766
  createSuccess: "\u8BBE\u5907\u5DF2\u521B\u5EFA",
20734
20767
  updateSuccess: "\u8BBE\u5907\u5DF2\u66F4\u65B0",
@@ -20850,6 +20883,8 @@ Time: {{time}}`,
20850
20883
  chatId: "chatId",
20851
20884
  applyTime: "\u7533\u8BF7\u65F6\u95F4",
20852
20885
  gatewayOnline: "\uD83D\uDFE2 Gateway online @ {{siteName}}",
20886
+ deviceConnectionError: `\uD83D\uDD34 {{siteName}}\uFF1A\u8BBE\u5907\u300C{{deviceName}}\u300D({{host}}) \u8FDE\u63A5\u5F02\u5E38 [{{category}}]
20887
+ {{error}}`,
20853
20888
  authSuccess: "\u2705 \u5DF2\u6388\u6743\uFF0C\u53EF\u63A5\u6536\u901A\u77E5\u3002",
20854
20889
  authPending: "\u23F3 \u5DF2\u6536\u5230\u6388\u6743\u7533\u8BF7\uFF0C\u8BF7\u5728 tmex \u8BBE\u7F6E\u9875\u5BA1\u6279\u3002",
20855
20890
  authFailed: "\u274C \u6388\u6743\u7533\u8BF7\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002",
@@ -20890,11 +20925,30 @@ Bot\uFF1A{{botName}}
20890
20925
  hostNotFound: "\u4E3B\u673A\u672A\u627E\u5230\uFF1A\u65E0\u6CD5\u89E3\u6790\u4E3B\u673A\u5730\u5740\uFF0C\u8BF7\u68C0\u67E5 DNS \u6216\u4E3B\u673A\u540D\u662F\u5426\u6B63\u786E",
20891
20926
  handshakeFailed: "\u63E1\u624B\u5931\u8D25\uFF1A\u65E0\u6CD5\u5EFA\u7ACB\u5B89\u5168\u8FDE\u63A5\uFF0C\u53EF\u80FD\u662F\u5BC6\u94A5\u4EA4\u6362\u7B97\u6CD5\u4E0D\u517C\u5BB9",
20892
20927
  tmuxUnavailable: "\u8FDC\u7AEF tmux \u4E0D\u53EF\u7528\u6216\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u662F\u5426\u5DF2\u5B89\u88C5 tmux\uFF0C\u4E14\u8FDC\u7AEF shell PATH \u53EF\u627E\u5230 tmux",
20928
+ connectionClosed: "\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u5C1D\u8BD5\u91CD\u8FDE\u4E2D",
20893
20929
  unknown: "\u8FDE\u63A5\u5931\u8D25\uFF1A{{message}}",
20894
20930
  reconnecting: "\u8FDE\u63A5\u4E2D\u65AD\uFF0C{{delay}} \u79D2\u540E\u81EA\u52A8\u91CD\u8FDE\uFF08{{attempt}}/{{maxRetries}}\uFF09",
20895
20931
  reconnectFailed: "\u81EA\u52A8\u91CD\u8FDE\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u91CD\u8BD5",
20896
20932
  reconnected: "\u8BBE\u5907\u5DF2\u81EA\u52A8\u91CD\u8FDE"
20897
20933
  },
20934
+ deviceStatus: {
20935
+ reconnecting: "\u91CD\u8FDE\u4E2D {{delay}}s",
20936
+ offline: "\u79BB\u7EBF",
20937
+ errorBadge: {
20938
+ authFailed: "\u8BA4\u8BC1\u5931\u8D25",
20939
+ agentUnavailable: "Agent \u4E0D\u53EF\u7528",
20940
+ agentNoIdentity: "Agent \u65E0\u5BC6\u94A5",
20941
+ configRefNotSupported: "\u4E0D\u652F\u6301 SSH Config",
20942
+ networkUnreachable: "\u7F51\u7EDC\u4E0D\u53EF\u8FBE",
20943
+ connectionRefused: "\u8FDE\u63A5\u88AB\u62D2",
20944
+ timeout: "\u8FDE\u63A5\u8D85\u65F6",
20945
+ hostNotFound: "\u4E3B\u673A\u672A\u627E\u5230",
20946
+ handshakeFailed: "\u63E1\u624B\u5931\u8D25",
20947
+ tmuxUnavailable: "tmux \u4E0D\u53EF\u7528",
20948
+ connectionClosed: "\u8FDE\u63A5\u5DF2\u65AD\u5F00",
20949
+ unknown: "\u8FDE\u63A5\u5F02\u5E38"
20950
+ }
20951
+ },
20898
20952
  websocket: {
20899
20953
  error: "WebSocket \u8FDE\u63A5\u9519\u8BEF",
20900
20954
  checkGateway: "\u8BF7\u68C0\u67E5 Gateway \u72B6\u6001",
@@ -21027,8 +21081,13 @@ Bot\uFF1A{{botName}}
21027
21081
  title: "\u30C7\u30D0\u30A4\u30B9\u7BA1\u7406",
21028
21082
  devices: "\u30C7\u30D0\u30A4\u30B9",
21029
21083
  addDevice: "\u30C7\u30D0\u30A4\u30B9\u3092\u8FFD\u52A0",
21084
+ addDeviceDescription: "\u30C7\u30D0\u30A4\u30B9\u60C5\u5831\u3092\u5165\u529B\u3057\u3001\u63A5\u7D9A\u65B9\u6CD5\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
21030
21085
  addFirstDevice: "\u6700\u521D\u306E\u30C7\u30D0\u30A4\u30B9\u3092\u8FFD\u52A0",
21031
21086
  editDevice: "\u30C7\u30D0\u30A4\u30B9\u3092\u7DE8\u96C6",
21087
+ editDeviceDescription: "\u30C7\u30D0\u30A4\u30B9\u8A2D\u5B9A\u3092\u66F4\u65B0",
21088
+ sectionBasic: "\u57FA\u672C\u60C5\u5831",
21089
+ sectionConnection: "\u63A5\u7D9A\u60C5\u5831",
21090
+ sectionAuth: "\u8A8D\u8A3C\u60C5\u5831",
21032
21091
  noDevices: "\u30C7\u30D0\u30A4\u30B9\u304C\u3042\u308A\u307E\u305B\u3093",
21033
21092
  noDevicesDescription: "\u30ED\u30FC\u30AB\u30EB\u307E\u305F\u306F SSH \u30C7\u30D0\u30A4\u30B9\u3092\u8FFD\u52A0\u3057\u3066\u958B\u59CB",
21034
21093
  name: "\u30C7\u30D0\u30A4\u30B9\u540D",
@@ -21061,6 +21120,7 @@ Bot\uFF1A{{botName}}
21061
21120
  disconnected: "\u5207\u65AD\u6E08\u307F",
21062
21121
  connecting: "\u63A5\u7D9A\u4E2D...",
21063
21122
  deleteConfirm: "\u3053\u306E\u30C7\u30D0\u30A4\u30B9\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F",
21123
+ deleteDescription: "\u30C7\u30D0\u30A4\u30B9\u300C{{name}}\u300D\u306F\u5B8C\u5168\u306B\u524A\u9664\u3055\u308C\u307E\u3059\u3002\u3053\u306E\u64CD\u4F5C\u306F\u53D6\u308A\u6D88\u305B\u307E\u305B\u3093\u3002",
21064
21124
  deleteSuccess: "\u30C7\u30D0\u30A4\u30B9\u3092\u524A\u9664\u3057\u307E\u3057\u305F",
21065
21125
  createSuccess: "\u30C7\u30D0\u30A4\u30B9\u3092\u4F5C\u6210\u3057\u307E\u3057\u305F",
21066
21126
  updateSuccess: "\u30C7\u30D0\u30A4\u30B9\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F",
@@ -21182,6 +21242,8 @@ Bot\uFF1A{{botName}}
21182
21242
  chatId: "Chat ID",
21183
21243
  applyTime: "\u7533\u8ACB\u6642\u9593",
21184
21244
  gatewayOnline: "\uD83D\uDFE2 Gateway online @ {{siteName}}",
21245
+ deviceConnectionError: `\uD83D\uDD34 {{siteName}}\uFF1A\u30C7\u30D0\u30A4\u30B9\u300C{{deviceName}}\u300D({{host}}) \u3067\u63A5\u7D9A\u30A8\u30E9\u30FC [{{category}}]
21246
+ {{error}}`,
21185
21247
  authSuccess: "\u2705 \u627F\u8A8D\u3055\u308C\u307E\u3057\u305F\u3002\u901A\u77E5\u3092\u53D7\u4FE1\u3067\u304D\u307E\u3059\u3002",
21186
21248
  authPending: "\u23F3 \u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F\u3002tmex \u8A2D\u5B9A\u30DA\u30FC\u30B8\u3067\u627F\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
21187
21249
  authFailed: "\u274C \u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u7BA1\u7406\u8005\u306B\u9023\u7D61\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
@@ -21222,11 +21284,30 @@ Bot\uFF1A{{botName}}
21222
21284
  hostNotFound: "\u30DB\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF1A\u30DB\u30B9\u30C8\u540D\u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002DNS \u307E\u305F\u306F\u30DB\u30B9\u30C8\u540D\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
21223
21285
  handshakeFailed: "\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A\u5B89\u5168\u306A\u63A5\u7D9A\u3092\u78BA\u7ACB\u3067\u304D\u307E\u305B\u3093\u3002\u30AD\u30FC\u4EA4\u63DB\u30A2\u30EB\u30B4\u30EA\u30BA\u30E0\u304C\u4E92\u63DB\u6027\u304C\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
21224
21286
  tmuxUnavailable: "\u30EA\u30E2\u30FC\u30C8 tmux \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u304B\u8D77\u52D5\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002tmux \u304C\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3001\u30EA\u30E2\u30FC\u30C8 shell \u306E PATH \u304B\u3089\u53C2\u7167\u3067\u304D\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
21287
+ connectionClosed: "\u63A5\u7D9A\u304C\u5207\u65AD\u3055\u308C\u307E\u3057\u305F\u3002\u518D\u63A5\u7D9A\u3092\u8A66\u307F\u3066\u3044\u307E\u3059",
21225
21288
  unknown: "\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{{message}}",
21226
21289
  reconnecting: "\u63A5\u7D9A\u304C\u4E2D\u65AD\u3055\u308C\u307E\u3057\u305F\u3002{{delay}} \u79D2\u5F8C\u306B\u518D\u63A5\u7D9A\u3057\u307E\u3059\uFF08{{attempt}}/{{maxRetries}}\uFF09",
21227
21290
  reconnectFailed: "\u81EA\u52D5\u518D\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u624B\u52D5\u3067\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
21228
21291
  reconnected: "\u30C7\u30D0\u30A4\u30B9\u304C\u81EA\u52D5\u7684\u306B\u518D\u63A5\u7D9A\u3055\u308C\u307E\u3057\u305F"
21229
21292
  },
21293
+ deviceStatus: {
21294
+ reconnecting: "\u518D\u63A5\u7D9A\u4E2D {{delay}}s",
21295
+ offline: "\u30AA\u30D5\u30E9\u30A4\u30F3",
21296
+ errorBadge: {
21297
+ authFailed: "\u8A8D\u8A3C\u5931\u6557",
21298
+ agentUnavailable: "Agent \u5229\u7528\u4E0D\u53EF",
21299
+ agentNoIdentity: "Agent \u306B\u9375\u306A\u3057",
21300
+ configRefNotSupported: "SSH Config \u975E\u5BFE\u5FDC",
21301
+ networkUnreachable: "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u4E0D\u53EF\u9054",
21302
+ connectionRefused: "\u63A5\u7D9A\u62D2\u5426",
21303
+ timeout: "\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8",
21304
+ hostNotFound: "\u30DB\u30B9\u30C8\u672A\u691C\u51FA",
21305
+ handshakeFailed: "\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF\u5931\u6557",
21306
+ tmuxUnavailable: "tmux \u5229\u7528\u4E0D\u53EF",
21307
+ connectionClosed: "\u5207\u65AD",
21308
+ unknown: "\u63A5\u7D9A\u30A8\u30E9\u30FC"
21309
+ }
21310
+ },
21230
21311
  websocket: {
21231
21312
  error: "WebSocket \u63A5\u7D9A\u30A8\u30E9\u30FC",
21232
21313
  checkGateway: "Gateway \u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044",
@@ -28866,7 +28947,8 @@ var deviceRuntimeStatus = sqliteTable("device_runtime_status", {
28866
28947
  deviceId: text("device_id").primaryKey().references(() => devices.id, { onDelete: "cascade" }),
28867
28948
  lastSeenAt: text("last_seen_at"),
28868
28949
  tmuxAvailable: integer("tmux_available", { mode: "boolean" }).notNull().default(false),
28869
- lastError: text("last_error")
28950
+ lastError: text("last_error"),
28951
+ lastErrorType: text("last_error_type")
28870
28952
  });
28871
28953
  var webhookEndpoints = sqliteTable("webhook_endpoints", {
28872
28954
  id: text("id").primaryKey(),
@@ -29039,7 +29121,8 @@ function createDevice(device) {
29039
29121
  deviceId: device.id,
29040
29122
  lastSeenAt: null,
29041
29123
  tmuxAvailable: false,
29042
- lastError: null
29124
+ lastError: null,
29125
+ lastErrorType: null
29043
29126
  }).onConflictDoNothing({ target: deviceRuntimeStatus.deviceId }).run();
29044
29127
  });
29045
29128
  }
@@ -29096,6 +29179,26 @@ function deleteDevice(id) {
29096
29179
  const orm = getDb();
29097
29180
  orm.delete(devices).where(eq(devices.id, id)).run();
29098
29181
  }
29182
+ function getDeviceRuntimeStatus(deviceId) {
29183
+ const orm = getDb();
29184
+ const row = orm.select().from(deviceRuntimeStatus).where(eq(deviceRuntimeStatus.deviceId, deviceId)).get();
29185
+ if (!row) {
29186
+ return {
29187
+ deviceId,
29188
+ lastSeenAt: null,
29189
+ tmuxAvailable: false,
29190
+ lastError: null,
29191
+ lastErrorType: null
29192
+ };
29193
+ }
29194
+ return {
29195
+ deviceId: row.deviceId,
29196
+ lastSeenAt: row.lastSeenAt,
29197
+ tmuxAvailable: row.tmuxAvailable,
29198
+ lastError: row.lastError,
29199
+ lastErrorType: row.lastErrorType
29200
+ };
29201
+ }
29099
29202
  function updateDeviceRuntimeStatus(deviceId, status) {
29100
29203
  const orm = getDb();
29101
29204
  const setValues = {};
@@ -29108,6 +29211,9 @@ function updateDeviceRuntimeStatus(deviceId, status) {
29108
29211
  if (status.lastError !== undefined) {
29109
29212
  setValues.lastError = status.lastError;
29110
29213
  }
29214
+ if (status.lastErrorType !== undefined) {
29215
+ setValues.lastErrorType = status.lastErrorType;
29216
+ }
29111
29217
  if (Object.keys(setValues).length === 0) {
29112
29218
  return;
29113
29219
  }
@@ -52015,6 +52121,241 @@ var eventNotifier = new EventNotifier;
52015
52121
  import { mkdirSync, rmSync } from "fs";
52016
52122
  import { homedir } from "os";
52017
52123
 
52124
+ // ../../apps/gateway/src/ws/error-classify.ts
52125
+ function classifySshError(error) {
52126
+ const msg = error.message.toLowerCase();
52127
+ if (msg.includes("ssh_config_ref_not_supported")) {
52128
+ return {
52129
+ type: "ssh_config_ref_not_supported",
52130
+ messageKey: "sshError.configRefNotSupported"
52131
+ };
52132
+ }
52133
+ if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
52134
+ return {
52135
+ type: "agent_unavailable",
52136
+ messageKey: "sshError.agentUnavailable"
52137
+ };
52138
+ }
52139
+ if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
52140
+ return {
52141
+ type: "agent_no_identity",
52142
+ messageKey: "sshError.agentNoIdentities"
52143
+ };
52144
+ }
52145
+ if (msg.includes("permission denied")) {
52146
+ return {
52147
+ type: "auth_failed",
52148
+ messageKey: "sshError.authFailed"
52149
+ };
52150
+ }
52151
+ if (msg.includes("all configured authentication methods failed")) {
52152
+ return {
52153
+ type: "auth_failed",
52154
+ messageKey: "sshError.authFailedGeneric"
52155
+ };
52156
+ }
52157
+ if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
52158
+ return {
52159
+ type: "network_unreachable",
52160
+ messageKey: "sshError.networkUnreachable"
52161
+ };
52162
+ }
52163
+ if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
52164
+ return {
52165
+ type: "connection_refused",
52166
+ messageKey: "sshError.connectionRefused"
52167
+ };
52168
+ }
52169
+ if (msg.includes("timeout") || msg.includes("etimedout")) {
52170
+ return {
52171
+ type: "timeout",
52172
+ messageKey: "sshError.connectionTimeout"
52173
+ };
52174
+ }
52175
+ if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
52176
+ return {
52177
+ type: "host_not_found",
52178
+ messageKey: "sshError.hostNotFound"
52179
+ };
52180
+ }
52181
+ if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
52182
+ return {
52183
+ type: "handshake_failed",
52184
+ messageKey: "sshError.handshakeFailed"
52185
+ };
52186
+ }
52187
+ 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")) {
52188
+ return {
52189
+ type: "tmux_unavailable",
52190
+ messageKey: "sshError.tmuxUnavailable"
52191
+ };
52192
+ }
52193
+ if (msg.includes("ssh_connection_closed") || msg.includes("connection closed") || msg.includes("ssh command channel not ready") || msg.includes("ssh connection not ready") || msg.includes("channel closed")) {
52194
+ return {
52195
+ type: "connection_closed",
52196
+ messageKey: "sshError.connectionClosed"
52197
+ };
52198
+ }
52199
+ return {
52200
+ type: "unknown",
52201
+ messageKey: "sshError.unknown",
52202
+ messageParams: { message: error.message }
52203
+ };
52204
+ }
52205
+
52206
+ // ../../apps/gateway/src/push/connection-alerts.ts
52207
+ var NOTIFY_THROTTLE_MS = 5 * 60 * 1000;
52208
+ function toErrorObject(err) {
52209
+ if (err instanceof Error) {
52210
+ return err;
52211
+ }
52212
+ if (typeof err === "string") {
52213
+ return new Error(err);
52214
+ }
52215
+ try {
52216
+ return new Error(JSON.stringify(err));
52217
+ } catch {
52218
+ return new Error(String(err));
52219
+ }
52220
+ }
52221
+
52222
+ class ConnectionAlertNotifier {
52223
+ throttleMap = new Map;
52224
+ broadcaster = null;
52225
+ settingsProvider = () => getSiteSettings();
52226
+ persister = (deviceId, friendlyMessage, errorType) => {
52227
+ updateDeviceRuntimeStatus(deviceId, {
52228
+ lastSeenAt: new Date().toISOString(),
52229
+ lastError: friendlyMessage,
52230
+ lastErrorType: errorType
52231
+ });
52232
+ };
52233
+ telegramSender = (text2) => telegramService.sendToAuthorizedChats({ text: text2 });
52234
+ setBroadcaster(broadcaster) {
52235
+ this.broadcaster = broadcaster;
52236
+ }
52237
+ setSettingsProvider(provider) {
52238
+ this.settingsProvider = provider;
52239
+ }
52240
+ setPersister(persister) {
52241
+ this.persister = persister;
52242
+ }
52243
+ setTelegramSender(sender) {
52244
+ this.telegramSender = sender;
52245
+ }
52246
+ async notify(alert) {
52247
+ const { device, error, source, silentTelegram = false, persist = true } = alert;
52248
+ const errObj = toErrorObject(error);
52249
+ const classified = classifySshError(errObj);
52250
+ const friendlyMessage = t2(classified.messageKey, { ...classified.messageParams });
52251
+ const rawMessage = errObj.message;
52252
+ console.error(`[conn-alert] device ${device.id} (${device.name}) source=${source} type=${classified.type}: ${rawMessage}`);
52253
+ if (persist) {
52254
+ try {
52255
+ this.persister(device.id, friendlyMessage, classified.type);
52256
+ } catch (dbErr) {
52257
+ console.error("[conn-alert] failed to persist runtime status:", dbErr);
52258
+ }
52259
+ }
52260
+ if (this.broadcaster) {
52261
+ try {
52262
+ this.broadcaster(device.id, {
52263
+ deviceId: device.id,
52264
+ type: "error",
52265
+ errorType: classified.type,
52266
+ message: friendlyMessage,
52267
+ rawMessage
52268
+ });
52269
+ } catch (broadcastErr) {
52270
+ console.error("[conn-alert] failed to broadcast:", broadcastErr);
52271
+ }
52272
+ }
52273
+ if (!silentTelegram && this.shouldSendTelegram(device.id, classified.type)) {
52274
+ await this.sendTelegram(device, classified.type, friendlyMessage, rawMessage);
52275
+ }
52276
+ return {
52277
+ errorType: classified.type,
52278
+ messageKey: classified.messageKey,
52279
+ message: friendlyMessage,
52280
+ rawMessage
52281
+ };
52282
+ }
52283
+ clear(deviceId) {
52284
+ for (const key of this.throttleMap.keys()) {
52285
+ if (key.startsWith(`${deviceId}:`)) {
52286
+ this.throttleMap.delete(key);
52287
+ }
52288
+ }
52289
+ }
52290
+ shouldSendTelegram(deviceId, errorType) {
52291
+ const key = `${deviceId}:${errorType}`;
52292
+ const now = Date.now();
52293
+ const last = this.throttleMap.get(key) ?? 0;
52294
+ if (now - last < NOTIFY_THROTTLE_MS) {
52295
+ return false;
52296
+ }
52297
+ this.throttleMap.set(key, now);
52298
+ for (const [otherKey, ts] of this.throttleMap) {
52299
+ if (otherKey !== key && otherKey.startsWith(`${deviceId}:`) && now - ts >= NOTIFY_THROTTLE_MS) {
52300
+ this.throttleMap.delete(otherKey);
52301
+ }
52302
+ }
52303
+ return true;
52304
+ }
52305
+ async sendTelegram(device, errorType, friendlyMessage, rawMessage) {
52306
+ let settings;
52307
+ try {
52308
+ settings = this.settingsProvider();
52309
+ } catch (err) {
52310
+ console.error("[conn-alert] failed to read site settings:", err);
52311
+ return;
52312
+ }
52313
+ const categoryKey = `deviceStatus.errorBadge.${toBadgeKey(errorType)}`;
52314
+ const translatedCategory = t2(categoryKey, { defaultValue: errorType });
52315
+ const text2 = t2("telegram.deviceConnectionError", {
52316
+ siteName: settings.siteName,
52317
+ deviceName: device.name,
52318
+ host: device.host ?? "-",
52319
+ category: translatedCategory,
52320
+ error: friendlyMessage || rawMessage
52321
+ });
52322
+ try {
52323
+ await this.telegramSender(text2);
52324
+ } catch (notifyErr) {
52325
+ console.error("[conn-alert] telegram send failed:", notifyErr);
52326
+ }
52327
+ }
52328
+ }
52329
+ function toBadgeKey(errorType) {
52330
+ switch (errorType) {
52331
+ case "auth_failed":
52332
+ return "authFailed";
52333
+ case "agent_unavailable":
52334
+ return "agentUnavailable";
52335
+ case "agent_no_identity":
52336
+ return "agentNoIdentity";
52337
+ case "ssh_config_ref_not_supported":
52338
+ return "configRefNotSupported";
52339
+ case "network_unreachable":
52340
+ return "networkUnreachable";
52341
+ case "connection_refused":
52342
+ return "connectionRefused";
52343
+ case "timeout":
52344
+ return "timeout";
52345
+ case "host_not_found":
52346
+ return "hostNotFound";
52347
+ case "handshake_failed":
52348
+ return "handshakeFailed";
52349
+ case "tmux_unavailable":
52350
+ return "tmuxUnavailable";
52351
+ case "connection_closed":
52352
+ return "connectionClosed";
52353
+ default:
52354
+ return "unknown";
52355
+ }
52356
+ }
52357
+ var connectionAlertNotifier = new ConnectionAlertNotifier;
52358
+
52018
52359
  // ../../apps/gateway/src/tmux/local-shell-path.ts
52019
52360
  import { existsSync } from "fs";
52020
52361
  import { delimiter, join as join2 } from "path";
@@ -53040,13 +53381,26 @@ class LocalExternalTmuxConnection {
53040
53381
  this.recoverFromTargetMissingError(message);
53041
53382
  return result;
53042
53383
  }
53043
- updateDeviceRuntimeStatus(this.deviceId, {
53044
- lastSeenAt: new Date().toISOString(),
53045
- tmuxAvailable: false,
53046
- lastError: message
53047
- });
53384
+ this.notifyRuntimeError(message);
53048
53385
  throw new Error(message);
53049
53386
  }
53387
+ async notifyRuntimeError(message) {
53388
+ const device = getDeviceById(this.deviceId);
53389
+ if (!device) {
53390
+ updateDeviceRuntimeStatus(this.deviceId, {
53391
+ lastSeenAt: new Date().toISOString(),
53392
+ tmuxAvailable: false,
53393
+ lastError: message
53394
+ });
53395
+ return;
53396
+ }
53397
+ await connectionAlertNotifier.notify({
53398
+ device,
53399
+ error: new Error(message),
53400
+ source: "runtime",
53401
+ silentTelegram: true
53402
+ });
53403
+ }
53050
53404
  async runTmuxAllowFailure(argv) {
53051
53405
  return this.deps.run(["tmux", ...argv]);
53052
53406
  }
@@ -54035,7 +54389,9 @@ class SshExternalTmuxConnection {
54035
54389
  const next = this.pipeTransition.catch(() => {
54036
54390
  return;
54037
54391
  }).then(task);
54038
- this.pipeTransition = next;
54392
+ this.pipeTransition = next.catch(() => {
54393
+ return;
54394
+ });
54039
54395
  return next;
54040
54396
  }
54041
54397
  async runTmux(argv, allowTargetMissing = false, timeoutMs = 1e4) {
@@ -54078,6 +54434,8 @@ class SshExternalTmuxConnection {
54078
54434
  }).then(() => this.executeShellCommand(command, timeoutMs));
54079
54435
  this.commandQueue = next.then(() => {
54080
54436
  return;
54437
+ }, () => {
54438
+ return;
54081
54439
  });
54082
54440
  return next;
54083
54441
  }
@@ -54639,9 +54997,14 @@ class PushSupervisor {
54639
54997
  },
54640
54998
  onError: (error) => {
54641
54999
  console.error(`[push] tmux error on device ${entry.deviceId}:`, error);
55000
+ connectionAlertNotifier.notify({
55001
+ device,
55002
+ error,
55003
+ source: "runtime"
55004
+ });
54642
55005
  },
54643
55006
  onClose: () => {
54644
- this.handleClose(entry.deviceId, generation, runtime);
55007
+ this.handleClose(entry.deviceId, generation, runtime, device);
54645
55008
  }
54646
55009
  });
54647
55010
  entry.runtime = runtime;
@@ -54665,6 +55028,11 @@ class PushSupervisor {
54665
55028
  return;
54666
55029
  }
54667
55030
  console.error(`[push] failed connecting device ${entry.deviceId}:`, err);
55031
+ await connectionAlertNotifier.notify({
55032
+ device,
55033
+ error: err,
55034
+ source: "connect"
55035
+ });
54668
55036
  detachRuntime();
54669
55037
  entry.detachRuntime = null;
54670
55038
  entry.runtime = null;
@@ -54702,11 +55070,16 @@ class PushSupervisor {
54702
55070
  this.connectEntry(entry);
54703
55071
  }, delayMs);
54704
55072
  }
54705
- async handleClose(deviceId, generation, runtime) {
55073
+ async handleClose(deviceId, generation, runtime, device) {
54706
55074
  const entry = this.entries.get(deviceId);
54707
55075
  if (!entry || entry.generation !== generation || entry.runtime !== runtime) {
54708
55076
  return;
54709
55077
  }
55078
+ await connectionAlertNotifier.notify({
55079
+ device,
55080
+ error: new Error("ssh_connection_closed"),
55081
+ source: "close"
55082
+ });
54710
55083
  entry.detachRuntime?.();
54711
55084
  entry.detachRuntime = null;
54712
55085
  entry.runtime = null;
@@ -54767,82 +55140,6 @@ class PushSupervisor {
54767
55140
  }
54768
55141
  var pushSupervisor = new PushSupervisor;
54769
55142
 
54770
- // ../../apps/gateway/src/ws/error-classify.ts
54771
- function classifySshError(error) {
54772
- const msg = error.message.toLowerCase();
54773
- if (msg.includes("ssh_config_ref_not_supported")) {
54774
- return {
54775
- type: "ssh_config_ref_not_supported",
54776
- messageKey: "sshError.configRefNotSupported"
54777
- };
54778
- }
54779
- if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
54780
- return {
54781
- type: "agent_unavailable",
54782
- messageKey: "sshError.agentUnavailable"
54783
- };
54784
- }
54785
- if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
54786
- return {
54787
- type: "agent_no_identity",
54788
- messageKey: "sshError.agentNoIdentities"
54789
- };
54790
- }
54791
- if (msg.includes("permission denied")) {
54792
- return {
54793
- type: "auth_failed",
54794
- messageKey: "sshError.authFailed"
54795
- };
54796
- }
54797
- if (msg.includes("all configured authentication methods failed")) {
54798
- return {
54799
- type: "auth_failed",
54800
- messageKey: "sshError.authFailedGeneric"
54801
- };
54802
- }
54803
- if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
54804
- return {
54805
- type: "network_unreachable",
54806
- messageKey: "sshError.networkUnreachable"
54807
- };
54808
- }
54809
- if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
54810
- return {
54811
- type: "connection_refused",
54812
- messageKey: "sshError.connectionRefused"
54813
- };
54814
- }
54815
- if (msg.includes("timeout") || msg.includes("etimedout")) {
54816
- return {
54817
- type: "timeout",
54818
- messageKey: "sshError.connectionTimeout"
54819
- };
54820
- }
54821
- if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
54822
- return {
54823
- type: "host_not_found",
54824
- messageKey: "sshError.hostNotFound"
54825
- };
54826
- }
54827
- if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
54828
- return {
54829
- type: "handshake_failed",
54830
- messageKey: "sshError.handshakeFailed"
54831
- };
54832
- }
54833
- 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")) {
54834
- return {
54835
- type: "tmux_unavailable",
54836
- messageKey: "sshError.tmuxUnavailable"
54837
- };
54838
- }
54839
- return {
54840
- type: "unknown",
54841
- messageKey: "sshError.unknown",
54842
- messageParams: { message: error.message }
54843
- };
54844
- }
54845
-
54846
55143
  // ../../apps/gateway/src/api/test-connection.ts
54847
55144
  function inferFailurePhase(errorType) {
54848
55145
  if (errorType === "tmux_unavailable") {
@@ -55076,8 +55373,18 @@ function handleApiRequest(req, _server) {
55076
55373
  }
55077
55374
  return json2({ error: t2("apiError.notFound") }, 404);
55078
55375
  }
55376
+ function enrichDeviceWithRuntime(device) {
55377
+ const status = getDeviceRuntimeStatus(device.id);
55378
+ return {
55379
+ ...device,
55380
+ lastSeenAt: status.lastSeenAt,
55381
+ lastError: status.lastError,
55382
+ lastErrorType: status.lastErrorType,
55383
+ tmuxAvailable: status.tmuxAvailable
55384
+ };
55385
+ }
55079
55386
  async function handleGetDevices() {
55080
- const devices2 = getAllDevices();
55387
+ const devices2 = getAllDevices().map(enrichDeviceWithRuntime);
55081
55388
  return json2({ devices: devices2 });
55082
55389
  }
55083
55390
  async function handleGetDevice(id) {
@@ -55085,7 +55392,7 @@ async function handleGetDevice(id) {
55085
55392
  if (!device) {
55086
55393
  return json2({ error: t2("apiError.deviceNotFound") }, 404);
55087
55394
  }
55088
- return json2({ device });
55395
+ return json2({ device: enrichDeviceWithRuntime(device) });
55089
55396
  }
55090
55397
  async function handleCreateDevice(req) {
55091
55398
  const body = await req.json();
@@ -56657,6 +56964,15 @@ class WebSocketServer {
56657
56964
  this.sendEnvelope(client, exports_ws_borsh.KIND_DEVICE_EVENT, payloadBytes);
56658
56965
  }
56659
56966
  }
56967
+ broadcastDeviceError(deviceId, payload) {
56968
+ const entry = this.connections.get(deviceId);
56969
+ if (!entry)
56970
+ return;
56971
+ const payloadBytes = exports_ws_borsh.encodeDeviceEventPayload(payload);
56972
+ for (const client of entry.clients) {
56973
+ this.sendEnvelope(client, exports_ws_borsh.KIND_DEVICE_EVENT, payloadBytes);
56974
+ }
56975
+ }
56660
56976
  broadcastDeviceEvent(entry, payload) {
56661
56977
  const payloadBytes = exports_ws_borsh.encodeDeviceEventPayload(payload);
56662
56978
  for (const client of entry.clients) {
@@ -56749,6 +57065,9 @@ async function createGatewayRuntime(options = {}) {
56749
57065
  runtimeController.reset();
56750
57066
  primeLocalShellPath();
56751
57067
  const wsServer = new WebSocketServer;
57068
+ connectionAlertNotifier.setBroadcaster((deviceId, payload) => {
57069
+ wsServer.broadcastDeviceError(deviceId, payload);
57070
+ });
56752
57071
  await telegramService.refresh();
56753
57072
  await pushSupervisor.start();
56754
57073
  try {
@@ -56791,6 +57110,7 @@ async function createGatewayRuntime(options = {}) {
56791
57110
  runtimeController.onRestart(listener);
56792
57111
  },
56793
57112
  async stop() {
57113
+ connectionAlertNotifier.setBroadcaster(null);
56794
57114
  wsServer.closeAll();
56795
57115
  await pushSupervisor.stopAll();
56796
57116
  await tmuxRuntimeRegistry.shutdownAll();
@@ -57097,6 +57417,12 @@ async function main() {
57097
57417
  });
57098
57418
  console.log(`[tmex] ${t3("runtime.started", { url: `http://${host}:${port}` })}`);
57099
57419
  }
57420
+ process.on("unhandledRejection", (reason) => {
57421
+ console.error("[tmex][unhandledRejection]", reason);
57422
+ });
57423
+ process.on("uncaughtException", (error) => {
57424
+ console.error("[tmex][uncaughtException]", error);
57425
+ });
57100
57426
  try {
57101
57427
  await main();
57102
57428
  } catch (error) {