tmex-cli 0.10.0 → 0.12.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/runtime/server.js +1339 -135
  3. package/package.json +4 -3
  4. package/resources/fe-dist/assets/DevicePage-B2xsa-ys.js +21 -0
  5. package/resources/fe-dist/assets/DevicesPage-Bm7E9QAf.js +2 -0
  6. package/resources/fe-dist/assets/FilePage-DtXS6Krk.js +16 -0
  7. package/resources/fe-dist/assets/SettingsPage-Bxat-d_V.js +32 -0
  8. package/resources/fe-dist/assets/{arc-Wv219fq_.js → arc-CWkT1O24.js} +2 -2
  9. package/resources/fe-dist/assets/{architectureDiagram-3BPJPVTR-DkPPo2hY.js → architectureDiagram-3BPJPVTR-nu1LX68y.js} +2 -2
  10. package/resources/fe-dist/assets/{blockDiagram-GPEHLZMM-DPAThozG.js → blockDiagram-GPEHLZMM-DLFLNzT_.js} +2 -2
  11. package/resources/fe-dist/assets/{c4Diagram-AAUBKEIU-DBs_N-_p.js → c4Diagram-AAUBKEIU-BE8aWxc1.js} +2 -2
  12. package/resources/fe-dist/assets/{card-Dl0HC88A.js → card-Duis2VHj.js} +2 -2
  13. package/resources/fe-dist/assets/channel-zKL2OH3S.js +2 -0
  14. package/resources/fe-dist/assets/{chunk-2J33WTMH-D3mrUPCp.js → chunk-2J33WTMH-Dlc3MV0y.js} +2 -2
  15. package/resources/fe-dist/assets/{chunk-4BX2VUAB-C0DRv58j.js → chunk-4BX2VUAB-E9XGobks.js} +2 -2
  16. package/resources/fe-dist/assets/{chunk-55IACEB6-DheUp7iQ.js → chunk-55IACEB6-Bp7ueFZf.js} +2 -2
  17. package/resources/fe-dist/assets/{chunk-727SXJPM-Cvew2yIJ.js → chunk-727SXJPM-CQrvyHfQ.js} +2 -2
  18. package/resources/fe-dist/assets/{chunk-AQP2D5EJ-DUoa4Wdg.js → chunk-AQP2D5EJ-c0B1XNZr.js} +2 -2
  19. package/resources/fe-dist/assets/{chunk-FMBD7UC4-Bt9Oi52P.js → chunk-FMBD7UC4-BWQBkKWs.js} +2 -2
  20. package/resources/fe-dist/assets/{chunk-ND2GUHAM-DlWpyDaS.js → chunk-ND2GUHAM-D9TROogS.js} +2 -2
  21. package/resources/fe-dist/assets/{chunk-QZHKN3VN-BZtXwgRW.js → chunk-QZHKN3VN-DRXr6zVP.js} +2 -2
  22. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-Drw216KI.js +2 -0
  23. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-Drw216KI.js +2 -0
  24. package/resources/fe-dist/assets/{cose-bilkent-S5V4N54A-yKy6sTY_.js → cose-bilkent-S5V4N54A-CBgJRn7g.js} +2 -2
  25. package/resources/fe-dist/assets/{dagre-BM42HDAG-DL4fpno6.js → dagre-BM42HDAG-ClYznWvX.js} +2 -2
  26. package/resources/fe-dist/assets/{diagram-2AECGRRQ-BueYws-9.js → diagram-2AECGRRQ-Br_41uIc.js} +2 -2
  27. package/resources/fe-dist/assets/{diagram-5GNKFQAL-GQEQSUeb.js → diagram-5GNKFQAL-BD9o1BrS.js} +2 -2
  28. package/resources/fe-dist/assets/{diagram-KO2AKTUF-LE3yI4ed.js → diagram-KO2AKTUF-Diq2zS9Y.js} +2 -2
  29. package/resources/fe-dist/assets/{diagram-LMA3HP47-BkHjYrU4.js → diagram-LMA3HP47-CLaOalPy.js} +2 -2
  30. package/resources/fe-dist/assets/{diagram-OG6HWLK6-DrnHic-P.js → diagram-OG6HWLK6-BcSlvdPy.js} +2 -2
  31. package/resources/fe-dist/assets/{erDiagram-TEJ5UH35-BCwWv-rO.js → erDiagram-TEJ5UH35-BNlad0c4.js} +2 -2
  32. package/resources/fe-dist/assets/{flowDiagram-I6XJVG4X-DEC2Z_fh.js → flowDiagram-I6XJVG4X-Zb1FLY00.js} +2 -2
  33. package/resources/fe-dist/assets/{ganttDiagram-6RSMTGT7-H1T5bCsY.js → ganttDiagram-6RSMTGT7-DPRPR5Is.js} +2 -2
  34. package/resources/fe-dist/assets/{gitGraphDiagram-PVQCEYII-DmZUfKfl.js → gitGraphDiagram-PVQCEYII-BENU2uEN.js} +2 -2
  35. package/resources/fe-dist/assets/index-CRCD4gPw.css +1 -0
  36. package/resources/fe-dist/assets/index-Den36xcn.js +488 -0
  37. package/resources/fe-dist/assets/{infoDiagram-5YYISTIA-fDOg9Qr5.js → infoDiagram-5YYISTIA-NfMlH21e.js} +2 -2
  38. package/resources/fe-dist/assets/{ishikawaDiagram-YF4QCWOH-QIvSJI3q.js → ishikawaDiagram-YF4QCWOH-CzK9-7uY.js} +2 -2
  39. package/resources/fe-dist/assets/{journeyDiagram-JHISSGLW-Iz7u4iH8.js → journeyDiagram-JHISSGLW-CIX5Ahuw.js} +2 -2
  40. package/resources/fe-dist/assets/{kanban-definition-UN3LZRKU-BYiFSFgn.js → kanban-definition-UN3LZRKU-C5Ccl5Cw.js} +2 -2
  41. package/resources/fe-dist/assets/{linear-DNHZFKop.js → linear-CBC08yeS.js} +2 -2
  42. package/resources/fe-dist/assets/markdown-preview-Bf_6UH9R.css +1 -0
  43. package/resources/fe-dist/assets/markdown-preview-Drd503bU.js +269 -0
  44. package/resources/fe-dist/assets/{mermaid.core-DUipsjFg.js → mermaid.core-DYl6mxS0.js} +6 -6
  45. package/resources/fe-dist/assets/{mindmap-definition-RKZ34NQL-tPbrKC3m.js → mindmap-definition-RKZ34NQL-ACR2SUDm.js} +2 -2
  46. package/resources/fe-dist/assets/{pieDiagram-4H26LBE5-26o8qBzb.js → pieDiagram-4H26LBE5-BuDDdwwy.js} +2 -2
  47. package/resources/fe-dist/assets/{quadrantDiagram-W4KKPZXB-BgpGo9Ak.js → quadrantDiagram-W4KKPZXB-CzM2Ydhy.js} +2 -2
  48. package/resources/fe-dist/assets/{refresh-cw-DdcimDhs.js → refresh-cw-DhzvbVDA.js} +2 -2
  49. package/resources/fe-dist/assets/{requirementDiagram-4Y6WPE33-BEfrxsDZ.js → requirementDiagram-4Y6WPE33-BveKh_kW.js} +2 -2
  50. package/resources/fe-dist/assets/{sankeyDiagram-5OEKKPKP-BuIeHyjh.js → sankeyDiagram-5OEKKPKP-BGQ6jtyE.js} +2 -2
  51. package/resources/fe-dist/assets/{sequenceDiagram-3UESZ5HK-CL0sinOn.js → sequenceDiagram-3UESZ5HK-D2fdqire.js} +2 -2
  52. package/resources/fe-dist/assets/{stateDiagram-AJRCARHV-BCq5X6hO.js → stateDiagram-AJRCARHV-DtuVBIq9.js} +2 -2
  53. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-gun__PvU.js +2 -0
  54. package/resources/fe-dist/assets/{timeline-definition-PNZ67QCA-CnqqA91O.js → timeline-definition-PNZ67QCA-Dslo0JFS.js} +2 -2
  55. package/resources/fe-dist/assets/{vennDiagram-CIIHVFJN-BqFG3JXB.js → vennDiagram-CIIHVFJN-BLcoN4xU.js} +2 -2
  56. package/resources/fe-dist/assets/{wardley-L42UT6IY-DvfZKNwT.js → wardley-L42UT6IY-OsbD5r1L.js} +2 -2
  57. package/resources/fe-dist/assets/{wardleyDiagram-YWT4CUSO-DlABpMGt.js → wardleyDiagram-YWT4CUSO-B_33zIif.js} +2 -2
  58. package/resources/fe-dist/assets/{xychartDiagram-2RQKCTM6-BDiyUuus.js → xychartDiagram-2RQKCTM6-DcB27Ap1.js} +2 -2
  59. package/resources/fe-dist/fonts/GeistMonoNerdFontMono-Bold.woff2 +0 -0
  60. package/resources/fe-dist/fonts/GeistMonoNerdFontMono-Regular.woff2 +0 -0
  61. package/resources/fe-dist/index.html +2 -2
  62. package/resources/fe-dist/tmex-maskable.png +0 -0
  63. package/resources/fe-dist/tmex.png +0 -0
  64. package/resources/fe-dist/assets/DevicePage-5vkaNOjl.js +0 -26
  65. package/resources/fe-dist/assets/DevicesPage-CKnRUGQm.js +0 -2
  66. package/resources/fe-dist/assets/FilePage-Dj0cszD4.css +0 -1
  67. package/resources/fe-dist/assets/FilePage-Uj-tzGW7.js +0 -283
  68. package/resources/fe-dist/assets/SettingsPage-Cfzr_Phh.js +0 -22
  69. package/resources/fe-dist/assets/channel-DyLhDM_y.js +0 -2
  70. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-DA8DiM06.js +0 -2
  71. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-DA8DiM06.js +0 -2
  72. package/resources/fe-dist/assets/index-OpBFduyh.js +0 -466
  73. package/resources/fe-dist/assets/index-yMgNjHUH.css +0 -1
  74. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-DH2ctCND.js +0 -2
  75. package/resources/fe-dist/fonts/GeistMono-Bold.woff2 +0 -0
  76. package/resources/fe-dist/fonts/GeistMono-Regular.woff2 +0 -0
@@ -22549,8 +22549,8 @@ function applyRepoEnv(name, env, options, log) {
22549
22549
  loadEnv();
22550
22550
 
22551
22551
  // src/runtime/server.ts
22552
- import { existsSync as existsSync5 } from "fs";
22553
- import { extname as extname2, join as join6, normalize, resolve as resolve4, sep } from "path";
22552
+ import { existsSync as existsSync8 } from "fs";
22553
+ import { extname as extname2, join as join8, normalize, resolve as resolve6, sep } from "path";
22554
22554
 
22555
22555
  // ../../apps/gateway/src/crypto/errors.ts
22556
22556
  function contextLabel(context) {
@@ -22659,6 +22659,8 @@ var I18N_RESOURCES = {
22659
22659
  authKey: "Private Key",
22660
22660
  authAgent: "SSH Agent",
22661
22661
  authConfigRef: "SSH Config",
22662
+ sshConfigRefPlaceholder: "Host alias, e.g. my-server",
22663
+ sshConfigRefHint: "A Host alias from your ssh config, resolved via `ssh -G`. Not the path to the config file.",
22662
22664
  password: "Password",
22663
22665
  privateKey: "Private Key",
22664
22666
  privateKeyPlaceholder: "-----BEGIN OPENSSH PRIVATE KEY-----",
@@ -22740,11 +22742,22 @@ var I18N_RESOURCES = {
22740
22742
  language_en_US: "English",
22741
22743
  language_zh_CN: "\u7B80\u4F53\u4E2D\u6587",
22742
22744
  language_ja_JP: "\u65E5\u672C\u8A9E",
22743
- theme: "Theme",
22745
+ theme: "Dark Mode",
22744
22746
  themeLight: "Light",
22745
22747
  themeDark: "Dark",
22746
22748
  siteTab: "Site",
22747
22749
  notificationsTab: "Notifications",
22750
+ tabGroup: {
22751
+ general: "General",
22752
+ devicesAndFiles: "Devices & Files",
22753
+ notifications: "Notifications",
22754
+ ai: "AI"
22755
+ },
22756
+ deviceManagement: {
22757
+ title: "Device Management",
22758
+ description: "Add, edit, and connect to your local and SSH devices.",
22759
+ openButton: "Open Device Management"
22760
+ },
22748
22761
  refreshToApply: "Refresh to apply language change",
22749
22762
  saveSettings: "Save Settings",
22750
22763
  settingsSaved: "Settings saved",
@@ -22798,6 +22811,7 @@ var I18N_RESOURCES = {
22798
22811
  search: {
22799
22812
  title: "Search",
22800
22813
  provider: "Search Provider",
22814
+ responsesApiHint: "If your LLM provider uses the OpenAI Responses API protocol, it can use that API's built-in hosted search tool; the third-party search below is an optional supplement.",
22801
22815
  providerNone: "Disabled",
22802
22816
  tavilyApiKey: "Tavily API Key",
22803
22817
  braveApiKey: "Brave API Key",
@@ -22830,6 +22844,35 @@ var I18N_RESOURCES = {
22830
22844
  toggleFailed: "Failed to update directory",
22831
22845
  deleteTitle: "Remove directory?",
22832
22846
  deleteDesc: 'Remove "{{path}}" from the whitelist? Files under it will no longer be accessible.'
22847
+ },
22848
+ version: {
22849
+ title: "Version & Updates",
22850
+ currentVersion: "Current version",
22851
+ installMethod: "Install method",
22852
+ installMethodCli: "Installed via CLI",
22853
+ installMethodNonCli: "Not installed via CLI",
22854
+ deployment: "Deployment",
22855
+ deploymentLaunchd: "launchd (macOS)",
22856
+ deploymentSystemd: "systemd (Linux)",
22857
+ deploymentNone: "None",
22858
+ checkUpdate: "Check for updates",
22859
+ checking: "Checking\u2026",
22860
+ upToDate: "You are on the latest version.",
22861
+ updateAvailable: "Update available: {{version}}",
22862
+ changelog: "Changelog",
22863
+ changelogUnavailable: "No changelog available for this version.",
22864
+ publishedAt: "Published {{date}}",
22865
+ upgrade: "Upgrade now",
22866
+ upgradeDisabledDev: "In-app update is disabled outside the production environment.",
22867
+ upgradeDisabledNonCli: "In-app update is only available for CLI installations.",
22868
+ upgradeWarningTitle: "Confirm upgrade",
22869
+ upgradeWarningBody: "Upgrading restarts the service and will interrupt your current session. It may also affect the survival of tmux processes managed by the service. Continue?",
22870
+ upgradeStarted: "Upgrade started",
22871
+ stateDownloading: "Downloading the new version\u2026",
22872
+ stateExecuting: "Applying the upgrade, the service is restarting\u2026",
22873
+ interruptNotice: "The connection will drop while the service restarts; it will recover shortly.",
22874
+ checkFailed: "Failed to check for updates",
22875
+ terminalHint: "Or upgrade from a terminal: npx tmex-cli@<version> upgrade"
22833
22876
  }
22834
22877
  },
22835
22878
  telegram: {
@@ -22839,6 +22882,7 @@ var I18N_RESOURCES = {
22839
22882
  botToken: "Bot Token",
22840
22883
  botTokenPlaceholder: "123456:AA...",
22841
22884
  addBot: "Add Bot",
22885
+ editBot: "Edit Bot",
22842
22886
  enableBot: "Enable Bot",
22843
22887
  allowAuthRequests: "Allow Authorization Requests",
22844
22888
  pendingChats: "Pending Authorization",
@@ -23015,7 +23059,11 @@ Time: {{time}}`,
23015
23059
  fileTooLarge: "File is too large",
23016
23060
  fileBinary: "Binary files are not supported",
23017
23061
  fileRootDeviceInvalid: "Invalid or unknown device",
23018
- fileRootDuplicate: "This directory is already added for the device"
23062
+ fileRootDuplicate: "This directory is already added for the device",
23063
+ upgradeNotAllowed: "In-app update is not available for this installation.",
23064
+ upgradeInProgress: "An upgrade is already in progress.",
23065
+ upgradeVersionRequired: "Target version is required.",
23066
+ updateCheckFailed: "Failed to query the npm registry."
23019
23067
  },
23020
23068
  notification: {
23021
23069
  clickToJump: "Click to jump to corresponding pane",
@@ -23304,7 +23352,10 @@ Time: {{time}}`,
23304
23352
  },
23305
23353
  validation: {
23306
23354
  deviceNameRequired: "Device name is required",
23307
- hostRequired: "Host is required for SSH devices"
23355
+ hostRequired: "Host is required for SSH devices",
23356
+ portRequired: "A valid port is required for SSH devices",
23357
+ usernameRequired: "Username is required for SSH devices",
23358
+ sshConfigRequired: "SSH config path is required for SSH devices"
23308
23359
  },
23309
23360
  files: {
23310
23361
  title: "Files",
@@ -23333,12 +23384,45 @@ Time: {{time}}`,
23333
23384
  unknown: "Failed to load"
23334
23385
  },
23335
23386
  retry: "Retry (collapse then expand)",
23387
+ menu: {
23388
+ copyAbsolute: "Copy absolute path",
23389
+ copyRelative: "Copy relative path",
23390
+ sendToAgent: "Send to Agent",
23391
+ expand: "Expand",
23392
+ collapse: "Collapse",
23393
+ upload: "Upload files here",
23394
+ open: "Open"
23395
+ },
23396
+ copied: "Copied to clipboard",
23397
+ copyFailed: "Copy failed",
23398
+ sendToAgent: {
23399
+ prompt: "Please work with this path: `{{path}}`"
23400
+ },
23401
+ upload: {
23402
+ uploading: "Uploading {{name}}\u2026",
23403
+ success: "Uploaded {{name}}",
23404
+ fail: "Failed to upload {{name}}"
23405
+ },
23406
+ transfer: {
23407
+ legUserToTmex: "Browser \u2192 tmex",
23408
+ legTmexToServer: "tmex \u2192 Server",
23409
+ legServerToTmex: "Server \u2192 tmex",
23410
+ legTmexToUser: "tmex \u2192 Browser",
23411
+ cancel: "Cancel",
23412
+ canceled: "Canceled {{name}}",
23413
+ downloaded: "Downloaded {{name}}",
23414
+ downloadFailed: "Failed to download {{name}}",
23415
+ dragDownloadStarted: "Started downloading {{name}} (handled by browser)",
23416
+ tooLarge: "{{name}} exceeds the size limit ({{max}})"
23417
+ },
23418
+ agentLaunch: {
23419
+ connectFailed: "Failed to connect to the device",
23420
+ windowFailed: "Failed to create a window"
23421
+ },
23336
23422
  install: {
23337
23423
  button: "Install rsync",
23338
23424
  scopeLocal: "server",
23339
23425
  scopeRemote: "remote",
23340
- connectFailed: "Failed to connect to the device",
23341
- windowFailed: "Failed to create a window",
23342
23426
  prompt: "rsync is not installed on {{device}} ({{scope}}). Please install rsync using the appropriate command for this system (e.g. `brew install rsync`, `sudo apt-get install -y rsync`, or `sudo yum install -y rsync`), then reply once it is done."
23343
23427
  }
23344
23428
  },
@@ -23438,6 +23522,8 @@ Time: {{time}}`,
23438
23522
  authKey: "\u79C1\u94A5",
23439
23523
  authAgent: "SSH Agent",
23440
23524
  authConfigRef: "SSH Config",
23525
+ sshConfigRefPlaceholder: "Host \u522B\u540D\uFF0C\u5982 my-server",
23526
+ sshConfigRefHint: "\u586B ssh config \u91CC\u7684 Host \u522B\u540D\uFF0C\u7ECF `ssh -G` \u89E3\u6790\uFF1B\u4E0D\u662F config \u6587\u4EF6\u8DEF\u5F84\u3002",
23441
23527
  password: "\u5BC6\u7801",
23442
23528
  privateKey: "\u79C1\u94A5",
23443
23529
  privateKeyPlaceholder: "-----BEGIN OPENSSH PRIVATE KEY-----",
@@ -23519,11 +23605,22 @@ Time: {{time}}`,
23519
23605
  language_en_US: "English",
23520
23606
  language_zh_CN: "\u7B80\u4F53\u4E2D\u6587",
23521
23607
  language_ja_JP: "\u65E5\u672C\u8A9E",
23522
- theme: "\u4E3B\u9898",
23608
+ theme: "\u6DF1\u8272\u6A21\u5F0F",
23523
23609
  themeLight: "\u6D45\u8272",
23524
23610
  themeDark: "\u6DF1\u8272",
23525
23611
  siteTab: "\u7AD9\u70B9",
23526
23612
  notificationsTab: "\u901A\u77E5",
23613
+ tabGroup: {
23614
+ general: "\u901A\u7528\u8BBE\u7F6E",
23615
+ devicesAndFiles: "\u8BBE\u5907\u4E0E\u6587\u4EF6",
23616
+ notifications: "\u901A\u77E5\u8BBE\u7F6E",
23617
+ ai: "AI \u8BBE\u7F6E"
23618
+ },
23619
+ deviceManagement: {
23620
+ title: "\u8BBE\u5907\u7BA1\u7406",
23621
+ description: "\u6DFB\u52A0\u3001\u7F16\u8F91\u5E76\u8FDE\u63A5\u4F60\u7684\u672C\u5730\u4E0E SSH \u8BBE\u5907\u3002",
23622
+ openButton: "\u6253\u5F00\u8BBE\u5907\u7BA1\u7406"
23623
+ },
23527
23624
  refreshToApply: "\u5237\u65B0\u540E\u751F\u6548",
23528
23625
  saveSettings: "\u4FDD\u5B58\u8BBE\u7F6E",
23529
23626
  settingsSaved: "\u7AD9\u70B9\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
@@ -23577,6 +23674,7 @@ Time: {{time}}`,
23577
23674
  search: {
23578
23675
  title: "\u641C\u7D22",
23579
23676
  provider: "\u641C\u7D22\u63D0\u4F9B\u5546",
23677
+ responsesApiHint: "\u82E5 LLM Provider \u4F7F\u7528 OpenAI Responses API \u534F\u8BAE\uFF0C\u53EF\u76F4\u63A5\u4F7F\u7528\u5176\u5185\u7F6E\u7684 hosted \u641C\u7D22\u5DE5\u5177\uFF1B\u4E0B\u65B9\u7B2C\u4E09\u65B9\u641C\u7D22\u4E3A\u53EF\u9009\u8865\u5145\u3002",
23580
23678
  providerNone: "\u4E0D\u542F\u7528",
23581
23679
  tavilyApiKey: "Tavily API Key",
23582
23680
  braveApiKey: "Brave API Key",
@@ -23609,6 +23707,35 @@ Time: {{time}}`,
23609
23707
  toggleFailed: "\u66F4\u65B0\u76EE\u5F55\u5931\u8D25",
23610
23708
  deleteTitle: "\u79FB\u9664\u76EE\u5F55\uFF1F",
23611
23709
  deleteDesc: "\u5C06\u300C{{path}}\u300D\u79FB\u51FA\u767D\u540D\u5355\uFF1F\u8BE5\u76EE\u5F55\u4E0B\u7684\u6587\u4EF6\u5C06\u4E0D\u518D\u53EF\u8BBF\u95EE\u3002"
23710
+ },
23711
+ version: {
23712
+ title: "\u7248\u672C\u4E0E\u66F4\u65B0",
23713
+ currentVersion: "\u5F53\u524D\u7248\u672C",
23714
+ installMethod: "\u5B89\u88C5\u65B9\u5F0F",
23715
+ installMethodCli: "\u901A\u8FC7 CLI \u5B89\u88C5",
23716
+ installMethodNonCli: "\u975E CLI \u5B89\u88C5",
23717
+ deployment: "\u90E8\u7F72\u65B9\u5F0F",
23718
+ deploymentLaunchd: "launchd\uFF08macOS\uFF09",
23719
+ deploymentSystemd: "systemd\uFF08Linux\uFF09",
23720
+ deploymentNone: "\u65E0",
23721
+ checkUpdate: "\u68C0\u67E5\u66F4\u65B0",
23722
+ checking: "\u68C0\u67E5\u4E2D\u2026",
23723
+ upToDate: "\u5DF2\u662F\u6700\u65B0\u7248\u672C\u3002",
23724
+ updateAvailable: "\u6709\u53EF\u7528\u66F4\u65B0\uFF1A{{version}}",
23725
+ changelog: "\u66F4\u65B0\u65E5\u5FD7",
23726
+ changelogUnavailable: "\u8BE5\u7248\u672C\u6682\u65E0\u66F4\u65B0\u65E5\u5FD7\u3002",
23727
+ publishedAt: "\u53D1\u5E03\u4E8E {{date}}",
23728
+ upgrade: "\u7ACB\u5373\u5347\u7EA7",
23729
+ upgradeDisabledDev: "\u975E production \u73AF\u5883\u5DF2\u7981\u7528\u7A0B\u5E8F\u5185\u66F4\u65B0\u3002",
23730
+ upgradeDisabledNonCli: "\u7A0B\u5E8F\u5185\u66F4\u65B0\u4EC5\u5728\u901A\u8FC7 CLI \u5B89\u88C5\u65F6\u53EF\u7528\u3002",
23731
+ upgradeWarningTitle: "\u786E\u8BA4\u5347\u7EA7",
23732
+ upgradeWarningBody: "\u5347\u7EA7\u4F1A\u91CD\u542F\u670D\u52A1\u5E76\u4E2D\u65AD\u5F53\u524D\u8BBF\u95EE\uFF0C\u8FD8\u53EF\u80FD\u5F71\u54CD\u8BE5\u670D\u52A1\u6258\u7BA1\u7684 tmux \u8FDB\u7A0B\u7684\u5B58\u6D3B\u3002\u662F\u5426\u7EE7\u7EED\uFF1F",
23733
+ upgradeStarted: "\u5347\u7EA7\u5DF2\u5F00\u59CB",
23734
+ stateDownloading: "\u6B63\u5728\u4E0B\u8F7D\u65B0\u7248\u672C\u2026",
23735
+ stateExecuting: "\u6B63\u5728\u6267\u884C\u5347\u7EA7\uFF0C\u670D\u52A1\u5373\u5C06\u91CD\u542F\u2026",
23736
+ interruptNotice: "\u670D\u52A1\u91CD\u542F\u671F\u95F4\u8FDE\u63A5\u4F1A\u65AD\u5F00\uFF0C\u7A0D\u5019\u5373\u53EF\u6062\u590D\u3002",
23737
+ checkFailed: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25",
23738
+ terminalHint: "\u6216\u901A\u8FC7\u7EC8\u7AEF\u5347\u7EA7\uFF1Anpx tmex-cli@<version> upgrade"
23612
23739
  }
23613
23740
  },
23614
23741
  telegram: {
@@ -23618,6 +23745,7 @@ Time: {{time}}`,
23618
23745
  botToken: "Bot Token",
23619
23746
  botTokenPlaceholder: "123456:AA...",
23620
23747
  addBot: "\u65B0\u589E Bot",
23748
+ editBot: "\u7F16\u8F91 Bot",
23621
23749
  enableBot: "\u542F\u7528 Bot",
23622
23750
  allowAuthRequests: "\u5141\u8BB8\u7533\u8BF7\u6388\u6743",
23623
23751
  pendingChats: "\u5F85\u6388\u6743",
@@ -23794,7 +23922,11 @@ Bot\uFF1A{{botName}}
23794
23922
  fileTooLarge: "\u6587\u4EF6\u8FC7\u5927",
23795
23923
  fileBinary: "\u4E0D\u652F\u6301\u4E8C\u8FDB\u5236\u6587\u4EF6",
23796
23924
  fileRootDeviceInvalid: "\u65E0\u6548\u6216\u672A\u77E5\u7684\u8BBE\u5907",
23797
- fileRootDuplicate: "\u8BE5\u8BBE\u5907\u4E0B\u5DF2\u6DFB\u52A0\u8FC7\u6B64\u76EE\u5F55"
23925
+ fileRootDuplicate: "\u8BE5\u8BBE\u5907\u4E0B\u5DF2\u6DFB\u52A0\u8FC7\u6B64\u76EE\u5F55",
23926
+ upgradeNotAllowed: "\u5F53\u524D\u5B89\u88C5\u65B9\u5F0F\u4E0D\u652F\u6301\u7A0B\u5E8F\u5185\u66F4\u65B0\u3002",
23927
+ upgradeInProgress: "\u5DF2\u6709\u5347\u7EA7\u4EFB\u52A1\u6B63\u5728\u8FDB\u884C\u3002",
23928
+ upgradeVersionRequired: "\u7F3A\u5C11\u76EE\u6807\u7248\u672C\u53F7\u3002",
23929
+ updateCheckFailed: "\u67E5\u8BE2 npm registry \u5931\u8D25\u3002"
23798
23930
  },
23799
23931
  notification: {
23800
23932
  clickToJump: "\u70B9\u51FB\u8DF3\u8F6C\u5230\u5BF9\u5E94 Pane",
@@ -24083,7 +24215,10 @@ Bot\uFF1A{{botName}}
24083
24215
  },
24084
24216
  validation: {
24085
24217
  deviceNameRequired: "\u8BBE\u5907\u540D\u79F0\u4E3A\u5FC5\u586B\u9879",
24086
- hostRequired: "SSH \u8BBE\u5907\u9700\u8981\u586B\u5199\u4E3B\u673A\u5730\u5740"
24218
+ hostRequired: "SSH \u8BBE\u5907\u9700\u8981\u586B\u5199\u4E3B\u673A\u5730\u5740",
24219
+ portRequired: "SSH \u8BBE\u5907\u9700\u8981\u586B\u5199\u6709\u6548\u7AEF\u53E3",
24220
+ usernameRequired: "SSH \u8BBE\u5907\u9700\u8981\u586B\u5199\u7528\u6237\u540D",
24221
+ sshConfigRequired: "SSH \u8BBE\u5907\u9700\u8981\u586B\u5199 ssh config \u8DEF\u5F84"
24087
24222
  },
24088
24223
  files: {
24089
24224
  title: "\u6587\u4EF6",
@@ -24112,12 +24247,45 @@ Bot\uFF1A{{botName}}
24112
24247
  unknown: "\u52A0\u8F7D\u5931\u8D25"
24113
24248
  },
24114
24249
  retry: "\u91CD\u8BD5\uFF08\u6536\u8D77\u540E\u518D\u5C55\u5F00\uFF09",
24250
+ menu: {
24251
+ copyAbsolute: "\u590D\u5236\u7EDD\u5BF9\u4F4D\u7F6E",
24252
+ copyRelative: "\u590D\u5236\u76F8\u5BF9\u4F4D\u7F6E",
24253
+ sendToAgent: "\u53D1\u9001\u5230 Agent",
24254
+ expand: "\u5C55\u5F00",
24255
+ collapse: "\u6536\u8D77",
24256
+ upload: "\u4E0A\u4F20\u6587\u4EF6\u5230\u8FD9\u4E2A\u6587\u4EF6\u5939",
24257
+ open: "\u6253\u5F00"
24258
+ },
24259
+ copied: "\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
24260
+ copyFailed: "\u590D\u5236\u5931\u8D25",
24261
+ sendToAgent: {
24262
+ prompt: "\u8BF7\u5904\u7406\u8FD9\u4E2A\u8DEF\u5F84\uFF1A`{{path}}`"
24263
+ },
24264
+ upload: {
24265
+ uploading: "\u6B63\u5728\u4E0A\u4F20 {{name}}\u2026",
24266
+ success: "\u5DF2\u4E0A\u4F20 {{name}}",
24267
+ fail: "\u4E0A\u4F20 {{name}} \u5931\u8D25"
24268
+ },
24269
+ transfer: {
24270
+ legUserToTmex: "\u7528\u6237 \u2192 tmex",
24271
+ legTmexToServer: "tmex \u2192 \u670D\u52A1\u5668",
24272
+ legServerToTmex: "\u670D\u52A1\u5668 \u2192 tmex",
24273
+ legTmexToUser: "tmex \u2192 \u7528\u6237",
24274
+ cancel: "\u53D6\u6D88",
24275
+ canceled: "\u5DF2\u53D6\u6D88 {{name}}",
24276
+ downloaded: "\u5DF2\u4E0B\u8F7D {{name}}",
24277
+ downloadFailed: "\u4E0B\u8F7D {{name}} \u5931\u8D25",
24278
+ dragDownloadStarted: "\u5DF2\u5F00\u59CB\u4E0B\u8F7D {{name}}\uFF08\u7531\u6D4F\u89C8\u5668\u63A5\u7BA1\uFF09",
24279
+ tooLarge: "{{name}} \u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650\uFF08{{max}}\uFF09"
24280
+ },
24281
+ agentLaunch: {
24282
+ connectFailed: "\u8FDE\u63A5\u8BBE\u5907\u5931\u8D25",
24283
+ windowFailed: "\u521B\u5EFA\u7A97\u53E3\u5931\u8D25"
24284
+ },
24115
24285
  install: {
24116
24286
  button: "\u5B89\u88C5 rsync",
24117
24287
  scopeLocal: "\u670D\u52A1\u5668",
24118
24288
  scopeRemote: "\u8FDC\u7A0B",
24119
- connectFailed: "\u8FDE\u63A5\u8BBE\u5907\u5931\u8D25",
24120
- windowFailed: "\u521B\u5EFA\u7A97\u53E3\u5931\u8D25",
24121
24289
  prompt: "\u8BBE\u5907 {{device}}\uFF08{{scope}}\uFF09\u4E0A\u672A\u68C0\u6D4B\u5230 rsync\u3002\u8BF7\u6839\u636E\u7CFB\u7EDF\u9009\u62E9\u5408\u9002\u7684\u547D\u4EE4\u5B89\u88C5 rsync\uFF08\u4F8B\u5982 `brew install rsync`\u3001`sudo apt-get install -y rsync` \u6216 `sudo yum install -y rsync`\uFF09\uFF0C\u5B89\u88C5\u5B8C\u6210\u540E\u56DE\u590D\u6211\u3002"
24122
24290
  }
24123
24291
  },
@@ -24217,6 +24385,8 @@ Bot\uFF1A{{botName}}
24217
24385
  authKey: "\u79D8\u5BC6\u9375",
24218
24386
  authAgent: "SSH Agent",
24219
24387
  authConfigRef: "SSH Config",
24388
+ sshConfigRefPlaceholder: "Host \u30A8\u30A4\u30EA\u30A2\u30B9\uFF08\u4F8B: my-server\uFF09",
24389
+ sshConfigRefHint: "ssh config \u306E Host \u30A8\u30A4\u30EA\u30A2\u30B9\u3092\u5165\u529B\u3057\u307E\u3059\u3002`ssh -G` \u3067\u89E3\u6C7A\u3055\u308C\u307E\u3059\u3002config \u30D5\u30A1\u30A4\u30EB\u306E\u30D1\u30B9\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
24220
24390
  password: "\u30D1\u30B9\u30EF\u30FC\u30C9",
24221
24391
  privateKey: "\u79D8\u5BC6\u9375",
24222
24392
  privateKeyPlaceholder: "-----BEGIN OPENSSH PRIVATE KEY-----",
@@ -24298,11 +24468,22 @@ Bot\uFF1A{{botName}}
24298
24468
  language_en_US: "English",
24299
24469
  language_zh_CN: "\u7B80\u4F53\u4E2D\u6587",
24300
24470
  language_ja_JP: "\u65E5\u672C\u8A9E",
24301
- theme: "\u30C6\u30FC\u30DE",
24471
+ theme: "\u30C0\u30FC\u30AF\u30E2\u30FC\u30C9",
24302
24472
  themeLight: "\u30E9\u30A4\u30C8",
24303
24473
  themeDark: "\u30C0\u30FC\u30AF",
24304
24474
  siteTab: "\u30B5\u30A4\u30C8",
24305
24475
  notificationsTab: "\u901A\u77E5",
24476
+ tabGroup: {
24477
+ general: "\u4E00\u822C\u8A2D\u5B9A",
24478
+ devicesAndFiles: "\u30C7\u30D0\u30A4\u30B9\u3068\u30D5\u30A1\u30A4\u30EB",
24479
+ notifications: "\u901A\u77E5\u8A2D\u5B9A",
24480
+ ai: "AI \u8A2D\u5B9A"
24481
+ },
24482
+ deviceManagement: {
24483
+ title: "\u30C7\u30D0\u30A4\u30B9\u7BA1\u7406",
24484
+ description: "\u30ED\u30FC\u30AB\u30EB\u304A\u3088\u3073 SSH \u30C7\u30D0\u30A4\u30B9\u306E\u8FFD\u52A0\u30FB\u7DE8\u96C6\u30FB\u63A5\u7D9A\u3092\u884C\u3044\u307E\u3059\u3002",
24485
+ openButton: "\u30C7\u30D0\u30A4\u30B9\u7BA1\u7406\u3092\u958B\u304F"
24486
+ },
24306
24487
  refreshToApply: "\u8A00\u8A9E\u5909\u66F4\u3092\u9069\u7528\u3059\u308B\u306B\u306F\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044",
24307
24488
  saveSettings: "\u8A2D\u5B9A\u3092\u4FDD\u5B58",
24308
24489
  settingsSaved: "\u8A2D\u5B9A\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F",
@@ -24356,6 +24537,7 @@ Bot\uFF1A{{botName}}
24356
24537
  search: {
24357
24538
  title: "\u691C\u7D22",
24358
24539
  provider: "\u691C\u7D22\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC",
24540
+ responsesApiHint: "LLM \u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u304C OpenAI Responses API \u30D7\u30ED\u30C8\u30B3\u30EB\u3092\u4F7F\u7528\u3057\u3066\u3044\u308B\u5834\u5408\u3001\u305D\u306E\u5185\u8535 hosted \u691C\u7D22\u30C4\u30FC\u30EB\u3092\u5229\u7528\u3067\u304D\u307E\u3059\u3002\u4E0B\u8A18\u306E\u30B5\u30FC\u30C9\u30D1\u30FC\u30C6\u30A3\u691C\u7D22\u306F\u4EFB\u610F\u306E\u88DC\u5B8C\u3067\u3059\u3002",
24359
24541
  providerNone: "\u7121\u52B9",
24360
24542
  tavilyApiKey: "Tavily API \u30AD\u30FC",
24361
24543
  braveApiKey: "Brave API \u30AD\u30FC",
@@ -24388,6 +24570,35 @@ Bot\uFF1A{{botName}}
24388
24570
  toggleFailed: "\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
24389
24571
  deleteTitle: "\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F",
24390
24572
  deleteDesc: "\u300C{{path}}\u300D\u3092\u30DB\u30EF\u30A4\u30C8\u30EA\u30B9\u30C8\u304B\u3089\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F\u914D\u4E0B\u306E\u30D5\u30A1\u30A4\u30EB\u306B\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u306A\u304F\u306A\u308A\u307E\u3059\u3002"
24573
+ },
24574
+ version: {
24575
+ title: "\u30D0\u30FC\u30B8\u30E7\u30F3\u3068\u66F4\u65B0",
24576
+ currentVersion: "\u73FE\u5728\u306E\u30D0\u30FC\u30B8\u30E7\u30F3",
24577
+ installMethod: "\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u65B9\u6CD5",
24578
+ installMethodCli: "CLI \u3067\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB",
24579
+ installMethodNonCli: "CLI \u4EE5\u5916\u3067\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB",
24580
+ deployment: "\u30C7\u30D7\u30ED\u30A4\u65B9\u5F0F",
24581
+ deploymentLaunchd: "launchd\uFF08macOS\uFF09",
24582
+ deploymentSystemd: "systemd\uFF08Linux\uFF09",
24583
+ deploymentNone: "\u306A\u3057",
24584
+ checkUpdate: "\u66F4\u65B0\u3092\u78BA\u8A8D",
24585
+ checking: "\u78BA\u8A8D\u4E2D\u2026",
24586
+ upToDate: "\u6700\u65B0\u30D0\u30FC\u30B8\u30E7\u30F3\u3067\u3059\u3002",
24587
+ updateAvailable: "\u66F4\u65B0\u304C\u3042\u308A\u307E\u3059\uFF1A{{version}}",
24588
+ changelog: "\u5909\u66F4\u5C65\u6B74",
24589
+ changelogUnavailable: "\u3053\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u306E\u5909\u66F4\u5C65\u6B74\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
24590
+ publishedAt: "\u516C\u958B\u65E5 {{date}}",
24591
+ upgrade: "\u4ECA\u3059\u3050\u66F4\u65B0",
24592
+ upgradeDisabledDev: "production \u4EE5\u5916\u306E\u74B0\u5883\u3067\u306F\u30A2\u30D7\u30EA\u5185\u66F4\u65B0\u306F\u7121\u52B9\u3067\u3059\u3002",
24593
+ upgradeDisabledNonCli: "\u30A2\u30D7\u30EA\u5185\u66F4\u65B0\u306F CLI \u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u6642\u306E\u307F\u5229\u7528\u3067\u304D\u307E\u3059\u3002",
24594
+ upgradeWarningTitle: "\u66F4\u65B0\u306E\u78BA\u8A8D",
24595
+ upgradeWarningBody: "\u66F4\u65B0\u3059\u308B\u3068\u30B5\u30FC\u30D3\u30B9\u304C\u518D\u8D77\u52D5\u3057\u3001\u73FE\u5728\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u4E2D\u65AD\u3055\u308C\u307E\u3059\u3002\u30B5\u30FC\u30D3\u30B9\u304C\u7BA1\u7406\u3059\u308B tmux \u30D7\u30ED\u30BB\u30B9\u306E\u5B58\u7D9A\u306B\u3082\u5F71\u97FF\u3059\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u7D9A\u884C\u3057\u307E\u3059\u304B\uFF1F",
24596
+ upgradeStarted: "\u66F4\u65B0\u3092\u958B\u59CB\u3057\u307E\u3057\u305F",
24597
+ stateDownloading: "\u65B0\u3057\u3044\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u4E2D\u2026",
24598
+ stateExecuting: "\u66F4\u65B0\u3092\u9069\u7528\u4E2D\u3001\u30B5\u30FC\u30D3\u30B9\u3092\u518D\u8D77\u52D5\u3057\u3066\u3044\u307E\u3059\u2026",
24599
+ interruptNotice: "\u30B5\u30FC\u30D3\u30B9\u518D\u8D77\u52D5\u4E2D\u306F\u63A5\u7D9A\u304C\u5207\u65AD\u3055\u308C\u307E\u3059\u304C\u3001\u307E\u3082\u306A\u304F\u5FA9\u65E7\u3057\u307E\u3059\u3002",
24600
+ checkFailed: "\u66F4\u65B0\u306E\u78BA\u8A8D\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
24601
+ terminalHint: "\u307E\u305F\u306F\u30BF\u30FC\u30DF\u30CA\u30EB\u304B\u3089\u66F4\u65B0\uFF1Anpx tmex-cli@<version> upgrade"
24391
24602
  }
24392
24603
  },
24393
24604
  telegram: {
@@ -24397,6 +24608,7 @@ Bot\uFF1A{{botName}}
24397
24608
  botToken: "Bot Token",
24398
24609
  botTokenPlaceholder: "123456:AA...",
24399
24610
  addBot: "Bot \u3092\u8FFD\u52A0",
24611
+ editBot: "Bot \u3092\u7DE8\u96C6",
24400
24612
  enableBot: "Bot \u3092\u6709\u52B9\u306B\u3059\u308B",
24401
24613
  allowAuthRequests: "\u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u8A31\u53EF",
24402
24614
  pendingChats: "\u627F\u8A8D\u5F85\u3061",
@@ -24573,7 +24785,11 @@ Bot\uFF1A{{botName}}
24573
24785
  fileTooLarge: "\u30D5\u30A1\u30A4\u30EB\u304C\u5927\u304D\u3059\u304E\u307E\u3059",
24574
24786
  fileBinary: "\u30D0\u30A4\u30CA\u30EA\u30D5\u30A1\u30A4\u30EB\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
24575
24787
  fileRootDeviceInvalid: "\u7121\u52B9\u307E\u305F\u306F\u4E0D\u660E\u306A\u30C7\u30D0\u30A4\u30B9",
24576
- fileRootDuplicate: "\u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306F\u3053\u306E\u30C7\u30D0\u30A4\u30B9\u306B\u65E2\u306B\u8FFD\u52A0\u3055\u308C\u3066\u3044\u307E\u3059"
24788
+ fileRootDuplicate: "\u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306F\u3053\u306E\u30C7\u30D0\u30A4\u30B9\u306B\u65E2\u306B\u8FFD\u52A0\u3055\u308C\u3066\u3044\u307E\u3059",
24789
+ upgradeNotAllowed: "\u3053\u306E\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3067\u306F\u30A2\u30D7\u30EA\u5185\u66F4\u65B0\u3092\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002",
24790
+ upgradeInProgress: "\u3059\u3067\u306B\u66F4\u65B0\u304C\u9032\u884C\u4E2D\u3067\u3059\u3002",
24791
+ upgradeVersionRequired: "\u5BFE\u8C61\u30D0\u30FC\u30B8\u30E7\u30F3\u304C\u5FC5\u8981\u3067\u3059\u3002",
24792
+ updateCheckFailed: "npm registry \u306E\u7167\u4F1A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002"
24577
24793
  },
24578
24794
  notification: {
24579
24795
  clickToJump: "\u5BFE\u5FDC\u3059\u308B\u30DA\u30A4\u30F3\u306B\u30B8\u30E3\u30F3\u30D7",
@@ -24862,7 +25078,10 @@ Bot\uFF1A{{botName}}
24862
25078
  },
24863
25079
  validation: {
24864
25080
  deviceNameRequired: "\u30C7\u30D0\u30A4\u30B9\u540D\u306F\u5FC5\u9808\u3067\u3059",
24865
- hostRequired: "SSH \u30C7\u30D0\u30A4\u30B9\u306B\u306F\u30DB\u30B9\u30C8\u30A2\u30C9\u30EC\u30B9\u304C\u5FC5\u8981\u3067\u3059"
25081
+ hostRequired: "SSH \u30C7\u30D0\u30A4\u30B9\u306B\u306F\u30DB\u30B9\u30C8\u30A2\u30C9\u30EC\u30B9\u304C\u5FC5\u8981\u3067\u3059",
25082
+ portRequired: "SSH \u30C7\u30D0\u30A4\u30B9\u306B\u306F\u6709\u52B9\u306A\u30DD\u30FC\u30C8\u304C\u5FC5\u8981\u3067\u3059",
25083
+ usernameRequired: "SSH \u30C7\u30D0\u30A4\u30B9\u306B\u306F\u30E6\u30FC\u30B6\u30FC\u540D\u304C\u5FC5\u8981\u3067\u3059",
25084
+ sshConfigRequired: "SSH \u30C7\u30D0\u30A4\u30B9\u306B\u306F ssh config \u306E\u30D1\u30B9\u304C\u5FC5\u8981\u3067\u3059"
24866
25085
  },
24867
25086
  files: {
24868
25087
  title: "\u30D5\u30A1\u30A4\u30EB",
@@ -24891,12 +25110,45 @@ Bot\uFF1A{{botName}}
24891
25110
  unknown: "\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
24892
25111
  },
24893
25112
  retry: "\u518D\u8A66\u884C\uFF08\u6298\u308A\u305F\u305F\u3093\u3067\u518D\u5C55\u958B\uFF09",
25113
+ menu: {
25114
+ copyAbsolute: "\u7D76\u5BFE\u30D1\u30B9\u3092\u30B3\u30D4\u30FC",
25115
+ copyRelative: "\u76F8\u5BFE\u30D1\u30B9\u3092\u30B3\u30D4\u30FC",
25116
+ sendToAgent: "Agent \u306B\u9001\u308B",
25117
+ expand: "\u5C55\u958B",
25118
+ collapse: "\u6298\u308A\u305F\u305F\u3080",
25119
+ upload: "\u3053\u306E\u30D5\u30A9\u30EB\u30C0\u306B\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9",
25120
+ open: "\u958B\u304F"
25121
+ },
25122
+ copied: "\u30AF\u30EA\u30C3\u30D7\u30DC\u30FC\u30C9\u306B\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F",
25123
+ copyFailed: "\u30B3\u30D4\u30FC\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25124
+ sendToAgent: {
25125
+ prompt: "\u3053\u306E\u30D1\u30B9\u3092\u51E6\u7406\u3057\u3066\u304F\u3060\u3055\u3044\uFF1A`{{path}}`"
25126
+ },
25127
+ upload: {
25128
+ uploading: "{{name}} \u3092\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u4E2D\u2026",
25129
+ success: "{{name}} \u3092\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3057\u307E\u3057\u305F",
25130
+ fail: "{{name}} \u306E\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
25131
+ },
25132
+ transfer: {
25133
+ legUserToTmex: "\u30D6\u30E9\u30A6\u30B6 \u2192 tmex",
25134
+ legTmexToServer: "tmex \u2192 \u30B5\u30FC\u30D0\u30FC",
25135
+ legServerToTmex: "\u30B5\u30FC\u30D0\u30FC \u2192 tmex",
25136
+ legTmexToUser: "tmex \u2192 \u30D6\u30E9\u30A6\u30B6",
25137
+ cancel: "\u30AD\u30E3\u30F3\u30BB\u30EB",
25138
+ canceled: "{{name}} \u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F",
25139
+ downloaded: "{{name}} \u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3057\u307E\u3057\u305F",
25140
+ downloadFailed: "{{name}} \u306E\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25141
+ dragDownloadStarted: "{{name}} \u306E\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3092\u958B\u59CB\uFF08\u30D6\u30E9\u30A6\u30B6\u304C\u51E6\u7406\uFF09",
25142
+ tooLarge: "{{name}} \u306F\u30B5\u30A4\u30BA\u4E0A\u9650\uFF08{{max}}\uFF09\u3092\u8D85\u3048\u3066\u3044\u307E\u3059"
25143
+ },
25144
+ agentLaunch: {
25145
+ connectFailed: "\u30C7\u30D0\u30A4\u30B9\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25146
+ windowFailed: "\u30A6\u30A3\u30F3\u30C9\u30A6\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
25147
+ },
24894
25148
  install: {
24895
25149
  button: "rsync \u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB",
24896
25150
  scopeLocal: "\u30B5\u30FC\u30D0\u30FC",
24897
25151
  scopeRemote: "\u30EA\u30E2\u30FC\u30C8",
24898
- connectFailed: "\u30C7\u30D0\u30A4\u30B9\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
24899
- windowFailed: "\u30A6\u30A3\u30F3\u30C9\u30A6\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
24900
25152
  prompt: "{{device}}\uFF08{{scope}}\uFF09\u306B rsync \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u30B7\u30B9\u30C6\u30E0\u306B\u5408\u308F\u305B\u305F\u65B9\u6CD5\u3067 rsync \u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\uFF08\u4F8B: `brew install rsync`\u3001`sudo apt-get install -y rsync`\u3001`sudo yum install -y rsync`\uFF09\u3001\u5B8C\u4E86\u3057\u305F\u3089\u8FD4\u4FE1\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
24901
25153
  }
24902
25154
  },
@@ -24920,6 +25172,11 @@ function toBCP47(locale) {
24920
25172
  return locale.replace("_", "-");
24921
25173
  }
24922
25174
  var AVAILABLE_LOCALES = ["en_US", "zh_CN", "ja_JP"];
25175
+ // ../shared/src/version.ts
25176
+ function formatDisplayVersion(baseVersion, isProd) {
25177
+ const base = baseVersion?.trim() || "unknown";
25178
+ return isProd ? base : `${base}_dev`;
25179
+ }
24923
25180
  // ../shared/src/ws-borsh/index.ts
24924
25181
  var exports_ws_borsh = {};
24925
25182
  __export(exports_ws_borsh, {
@@ -27431,11 +27688,13 @@ var config = {
27431
27688
  baseUrl: getEnv("TMEX_BASE_URL", "http://127.0.0.1:8085"),
27432
27689
  siteNameDefault: getEnv("TMEX_SITE_NAME", "tmex"),
27433
27690
  databaseUrl: getEnv("DATABASE_URL", "./tmex.db"),
27691
+ transferMaxBytes: Number.parseInt(getEnv("TMEX_TRANSFER_MAX_BYTES", "2147483648"), 10),
27434
27692
  bellThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_BELL_THROTTLE_SECONDS", "6"), 10),
27435
27693
  notificationThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_NOTIFICATION_THROTTLE_SECONDS", "3"), 10),
27436
27694
  tmuxAllowPassthrough: getBooleanEnv("TMEX_TMUX_ALLOW_PASSTHROUGH", false),
27437
27695
  tmuxTermProgram: getEnv("TMEX_TMUX_TERM_PROGRAM", "ghostty"),
27438
27696
  tmuxWindowStyle: getEnv("TMEX_TMUX_WINDOW_STYLE", "fg=#d0d0d0,bg=#262626"),
27697
+ tmuxSocket: getEnv("TMEX_TMUX_SOCKET", ""),
27439
27698
  sshReconnectMaxRetriesDefault: Number.parseInt(getEnv("TMEX_SSH_RECONNECT_MAX_RETRIES", "2"), 10),
27440
27699
  sshReconnectDelaySecondsDefault: Number.parseInt(getEnv("TMEX_SSH_RECONNECT_DELAY_SECONDS", "10"), 10),
27441
27700
  languageDefault: getEnv("TMEX_DEFAULT_LANGUAGE", "en_US"),
@@ -86600,9 +86859,6 @@ function normalizeHttpUrl(input) {
86600
86859
  return null;
86601
86860
  }
86602
86861
  }
86603
- function encodePercentForTelegramUrl(url2) {
86604
- return url2.replace(/%/g, "%25");
86605
- }
86606
86862
 
86607
86863
  class EventNotifier {
86608
86864
  webhooks = [];
@@ -86746,8 +87002,7 @@ class EventNotifier {
86746
87002
  lines.push(...this.buildPaneMetaLines(event));
86747
87003
  const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
86748
87004
  if (paneUrl) {
86749
- const tgSafePaneUrl = encodePercentForTelegramUrl(paneUrl);
86750
- lines.push("", `<a href="${escapeTelegramHtmlAttribute(tgSafePaneUrl)}">${escapeTelegramHtmlText(t2("notification.telegramBell.viewLink"))}</a>`);
87005
+ lines.push("", `<a href="${escapeTelegramHtmlAttribute(paneUrl)}">${escapeTelegramHtmlText(t2("notification.telegramBell.viewLink"))}</a>`);
86751
87006
  }
86752
87007
  return lines.join(`
86753
87008
  `);
@@ -86767,8 +87022,7 @@ class EventNotifier {
86767
87022
  const topbarLabel = this.buildTerminalTopbarLabel(event);
86768
87023
  const footer = `from ${event.site.name}: ${topbarLabel}`;
86769
87024
  if (paneUrl) {
86770
- const tgSafePaneUrl = encodePercentForTelegramUrl(paneUrl);
86771
- lines.push("", `<a href="${escapeTelegramHtmlAttribute(tgSafePaneUrl)}">${escapeTelegramHtmlText(footer)}</a>`);
87025
+ lines.push("", `<a href="${escapeTelegramHtmlAttribute(paneUrl)}">${escapeTelegramHtmlText(footer)}</a>`);
86772
87026
  } else {
86773
87027
  lines.push("", escapeTelegramHtmlText(footer));
86774
87028
  }
@@ -86808,8 +87062,7 @@ class EventNotifier {
86808
87062
  }
86809
87063
  const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
86810
87064
  if (paneUrl) {
86811
- const tgSafePaneUrl = encodePercentForTelegramUrl(paneUrl);
86812
- lines.push("", `<a href="${escapeTelegramHtmlAttribute(tgSafePaneUrl)}">${esc2(t2("notification.directLink"))}</a>`);
87065
+ lines.push("", `<a href="${escapeTelegramHtmlAttribute(paneUrl)}">${esc2(t2("notification.directLink"))}</a>`);
86813
87066
  }
86814
87067
  return lines.join(`
86815
87068
  `);
@@ -95730,10 +95983,12 @@ class ConnectionAlertNotifier {
95730
95983
  const classified = classifySshError(errObj);
95731
95984
  const friendlyMessage = t2(classified.messageKey, { ...classified.messageParams });
95732
95985
  const rawMessage = errObj.message;
95986
+ const persistedMessage = rawMessage && !friendlyMessage.includes(rawMessage) ? `${friendlyMessage}
95987
+ ${rawMessage}` : friendlyMessage;
95733
95988
  console.error(`[conn-alert] device ${device.id} (${device.name}) source=${source} type=${classified.type}: ${rawMessage}`);
95734
95989
  if (persist) {
95735
95990
  try {
95736
- this.persister(device.id, friendlyMessage, classified.type);
95991
+ this.persister(device.id, persistedMessage, classified.type);
95737
95992
  } catch (dbErr) {
95738
95993
  console.error("[conn-alert] failed to persist runtime status:", dbErr);
95739
95994
  }
@@ -97013,6 +97268,19 @@ function shouldIgnoreReaderAbortError(error51) {
97013
97268
  const maybeError = error51;
97014
97269
  return maybeError.name === "AbortError" && maybeError.code === "ERR_STREAM_RELEASE_LOCK" && typeof maybeError.message === "string" && maybeError.message.includes("releaseLock");
97015
97270
  }
97271
+ var TRANSIENT_SPAWN_ERROR_CODES = new Set(["EAGAIN", "EMFILE", "ENFILE", "ENOMEM"]);
97272
+ var TMUX_SPAWN_UNAVAILABLE_EXIT = -2;
97273
+ function isTransientSpawnError(error51) {
97274
+ if (!error51 || typeof error51 !== "object") {
97275
+ return false;
97276
+ }
97277
+ const code2 = error51.code;
97278
+ if (typeof code2 === "string" && TRANSIENT_SPAWN_ERROR_CODES.has(code2)) {
97279
+ return true;
97280
+ }
97281
+ const message = error51.message;
97282
+ return typeof message === "string" && (message.includes("EAGAIN") || message.includes("posix_spawn") || message.includes("resource temporarily unavailable") || message.includes("Too many open files"));
97283
+ }
97016
97284
  function defaultRun(argv) {
97017
97285
  return new Promise((resolve3, reject) => {
97018
97286
  const subprocess = Bun.spawn(argv, {
@@ -97072,6 +97340,7 @@ class LocalExternalTmuxConnection {
97072
97340
  controlStartedAt = 0;
97073
97341
  controlRestartCount = 0;
97074
97342
  controlStderrTail = "";
97343
+ spawnUnavailableNotified = false;
97075
97344
  constructor(options, inputDeps = {}) {
97076
97345
  this.deviceId = options.deviceId;
97077
97346
  this.callbacks = options;
@@ -97123,7 +97392,13 @@ class LocalExternalTmuxConnection {
97123
97392
  this.stopControlClient();
97124
97393
  }
97125
97394
  requestSnapshot() {
97126
- this.requestSnapshotInternal();
97395
+ this.requestSnapshotInternal().catch((error51) => {
97396
+ if (isTransientSpawnError(error51)) {
97397
+ this.handleSpawnUnavailable(error51 instanceof Error ? error51.message : String(error51));
97398
+ return;
97399
+ }
97400
+ this.callbacks.onError(error51 instanceof Error ? error51 : new Error(String(error51)));
97401
+ });
97127
97402
  }
97128
97403
  sendInput(paneId, data) {
97129
97404
  if (!this.connected) {
@@ -97423,6 +97698,7 @@ class LocalExternalTmuxConnection {
97423
97698
  });
97424
97699
  const proc = this.deps.spawnControlClient([
97425
97700
  "tmux",
97701
+ ...config.tmuxSocket ? ["-L", config.tmuxSocket] : [],
97426
97702
  "-C",
97427
97703
  "attach-session",
97428
97704
  "-t",
@@ -97511,6 +97787,16 @@ class LocalExternalTmuxConnection {
97511
97787
  return;
97512
97788
  }
97513
97789
  const probe = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
97790
+ if (probe.exitCode === TMUX_SPAWN_UNAVAILABLE_EXIT) {
97791
+ this.handleSpawnUnavailable(probe.stderr);
97792
+ this.controlRestartCount = Math.max(0, this.controlRestartCount - 1);
97793
+ if (this.connected && !this.manualDisconnect) {
97794
+ setTimeout(() => {
97795
+ this.reconnectControlClient(exitCode);
97796
+ }, CONTROL_RESTART_DELAY_MS * 4);
97797
+ }
97798
+ return;
97799
+ }
97514
97800
  if (probe.exitCode !== 0) {
97515
97801
  const message = probe.stderr.trim() || probe.stdout.trim() || "tmux session gone";
97516
97802
  console.warn(`[local] tmux session gone on ${this.deviceId}: ${message}`);
@@ -97622,6 +97908,11 @@ class LocalExternalTmuxConnection {
97622
97908
  ].join(SNAPSHOT_FIELD_SEPARATOR)
97623
97909
  ])
97624
97910
  ]);
97911
+ const transientResult = [sessionRes, windowsRes, panesRes].find((res) => res.exitCode === TMUX_SPAWN_UNAVAILABLE_EXIT);
97912
+ if (transientResult) {
97913
+ this.handleSpawnUnavailable(transientResult.stderr);
97914
+ return;
97915
+ }
97625
97916
  if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
97626
97917
  const stderrBlob = `${sessionRes.stderr}
97627
97918
  ${windowsRes.stderr}
@@ -97645,6 +97936,7 @@ ${panesRes.stderr}`;
97645
97936
  this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
97646
97937
  this.discardInvalidSnapshot();
97647
97938
  this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
97939
+ this.markSpawnRecovered();
97648
97940
  this.emitSnapshot();
97649
97941
  }
97650
97942
  parseSnapshotSession(lines) {
@@ -97830,8 +98122,29 @@ ${panesRes.stderr}`;
97830
98122
  silentTelegram: true
97831
98123
  });
97832
98124
  }
98125
+ handleSpawnUnavailable(message) {
98126
+ if (this.spawnUnavailableNotified) {
98127
+ return;
98128
+ }
98129
+ this.spawnUnavailableNotified = true;
98130
+ const detail = (message || "tmux spawn unavailable (process table exhausted)").trim();
98131
+ console.warn(`[local] tmux spawn unavailable on ${this.deviceId} (process pressure), degrading without shutdown: ${detail}`);
98132
+ this.notifyRuntimeError(detail);
98133
+ }
98134
+ markSpawnRecovered() {
98135
+ this.spawnUnavailableNotified = false;
98136
+ }
97833
98137
  async runTmuxAllowFailure(argv) {
97834
- return this.deps.run(["tmux", ...argv]);
98138
+ const socketArgs = config.tmuxSocket ? ["-L", config.tmuxSocket] : [];
98139
+ try {
98140
+ return await this.deps.run(["tmux", ...socketArgs, ...argv]);
98141
+ } catch (error51) {
98142
+ if (isTransientSpawnError(error51)) {
98143
+ const message = error51 instanceof Error ? error51.message : String(error51);
98144
+ return { exitCode: TMUX_SPAWN_UNAVAILABLE_EXIT, stdout: "", stderr: message };
98145
+ }
98146
+ throw error51;
98147
+ }
97835
98148
  }
97836
98149
  isTmuxServerGoneMessage(message) {
97837
98150
  const normalized = message.toLowerCase();
@@ -98040,7 +98353,7 @@ function resolveSshConfigRef(device, deps) {
98040
98353
  return parseSshConfigOutput(result.stdout, deps.env);
98041
98354
  }
98042
98355
  function resolveImplicitIdentityFilesForAgentAuth(device, host, port, username, deps) {
98043
- if (device.authMode !== "agent" || device.sshConfigRef?.trim()) {
98356
+ if (device.authMode !== "agent") {
98044
98357
  return [];
98045
98358
  }
98046
98359
  const target = username ? `${username}@${host}` : host;
@@ -98062,7 +98375,7 @@ async function resolveSshConnectConfig(device, decrypt2, inputDeps = {}) {
98062
98375
  readTextFile: inputDeps.readTextFile ?? ((path) => readFileSync2(path, "utf8"))
98063
98376
  };
98064
98377
  const sshEnv = toSshAuthEnv(deps.env);
98065
- const resolvedConfig = resolveSshConfigRef(device, deps);
98378
+ const resolvedConfig = device.authMode === "configRef" ? resolveSshConfigRef(device, deps) : null;
98066
98379
  const host = resolvedConfig?.host ?? device.host;
98067
98380
  const port = resolvedConfig?.port ?? device.port ?? 22;
98068
98381
  const username = resolvedConfig?.username ?? resolveSshUsername(device.username, device.authMode, sshEnv);
@@ -102660,7 +102973,7 @@ function deleteFileRoot(id) {
102660
102973
  }
102661
102974
 
102662
102975
  // ../../apps/gateway/src/files/device-storage.ts
102663
- import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, realpathSync, rmSync as rmSync2 } from "fs";
102976
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, realpathSync, rmSync as rmSync2, statSync } from "fs";
102664
102977
  import { tmpdir as tmpdir2 } from "os";
102665
102978
  import { join as join5 } from "path";
102666
102979
 
@@ -102895,6 +103208,18 @@ function enqueueDeviceJob(deviceId, job) {
102895
103208
  }
102896
103209
 
102897
103210
  // ../../apps/gateway/src/files/rsync.ts
103211
+ var PROGRESS_RE = /^\s*([\d,]+)\s+(\d+)%\s+([\d.]+[KMGT]?B\/s)/;
103212
+ function parseRsyncProgress(line) {
103213
+ const m = PROGRESS_RE.exec(line);
103214
+ if (!m)
103215
+ return null;
103216
+ const transferred = Number.parseInt(m[1].replace(/,/g, ""), 10);
103217
+ const pct = Number.parseInt(m[2], 10);
103218
+ if (Number.isNaN(transferred) || Number.isNaN(pct))
103219
+ return null;
103220
+ return { transferred, pct, rate: m[3] };
103221
+ }
103222
+
102898
103223
  class RsyncMissingLocalError extends Error {
102899
103224
  constructor(message) {
102900
103225
  super(message);
@@ -102916,8 +103241,36 @@ function baseSubprocessEnv() {
102916
103241
  out.LC_ALL = "C";
102917
103242
  return out;
102918
103243
  }
103244
+ async function readStdoutWithProgress(stream, onProgress, resetIdle) {
103245
+ const reader = stream.getReader();
103246
+ const decoder4 = new TextDecoder;
103247
+ let full = "";
103248
+ let buf = "";
103249
+ for (;; ) {
103250
+ const { done, value } = await reader.read();
103251
+ if (done)
103252
+ break;
103253
+ resetIdle();
103254
+ const text3 = decoder4.decode(value, { stream: true });
103255
+ full += text3;
103256
+ buf += text3;
103257
+ const parts = buf.split(/[\r\n]/);
103258
+ buf = parts.pop() ?? "";
103259
+ for (const line of parts) {
103260
+ const p = parseRsyncProgress(line);
103261
+ if (p)
103262
+ onProgress(p);
103263
+ }
103264
+ }
103265
+ const tail = parseRsyncProgress(buf);
103266
+ if (tail)
103267
+ onProgress(tail);
103268
+ return full;
103269
+ }
102919
103270
  async function runRsync(argv, opts = {}) {
103271
+ const streaming = typeof opts.onProgress === "function";
102920
103272
  const timeoutMs = opts.timeoutMs ?? 20000;
103273
+ const idleTimeoutMs = opts.idleTimeoutMs ?? 120000;
102921
103274
  let proc;
102922
103275
  try {
102923
103276
  proc = Bun.spawn(["rsync", ...argv], {
@@ -102930,21 +103283,36 @@ async function runRsync(argv, opts = {}) {
102930
103283
  throw new RsyncMissingLocalError(error51 instanceof Error ? error51.message : String(error51));
102931
103284
  }
102932
103285
  let timedOut = false;
102933
- const killTimer = setTimeout(() => {
102934
- timedOut = true;
102935
- try {
102936
- proc.kill();
102937
- } catch {}
102938
- }, timeoutMs);
102939
- const onAbort = () => {
103286
+ const killProc = () => {
102940
103287
  try {
102941
103288
  proc.kill();
102942
103289
  } catch {}
102943
103290
  };
103291
+ let killTimer;
103292
+ const armFixed = () => {
103293
+ killTimer = setTimeout(() => {
103294
+ timedOut = true;
103295
+ killProc();
103296
+ }, timeoutMs);
103297
+ };
103298
+ const resetIdle = () => {
103299
+ if (killTimer)
103300
+ clearTimeout(killTimer);
103301
+ killTimer = setTimeout(() => {
103302
+ timedOut = true;
103303
+ killProc();
103304
+ }, idleTimeoutMs);
103305
+ };
103306
+ if (streaming)
103307
+ resetIdle();
103308
+ else
103309
+ armFixed();
103310
+ const onAbort = () => killProc();
102944
103311
  opts.signal?.addEventListener("abort", onAbort, { once: true });
102945
103312
  try {
103313
+ const stdoutPromise = streaming ? readStdoutWithProgress(proc.stdout, opts.onProgress, resetIdle) : new Response(proc.stdout).text();
102946
103314
  const [stdout, stderr, exitCode] = await Promise.all([
102947
- new Response(proc.stdout).text(),
103315
+ stdoutPromise,
102948
103316
  new Response(proc.stderr).text(),
102949
103317
  proc.exited
102950
103318
  ]);
@@ -102954,7 +103322,8 @@ async function runRsync(argv, opts = {}) {
102954
103322
  }
102955
103323
  return { stdout, stderr, exitCode };
102956
103324
  } finally {
102957
- clearTimeout(killTimer);
103325
+ if (killTimer)
103326
+ clearTimeout(killTimer);
102958
103327
  opts.signal?.removeEventListener("abort", onAbort);
102959
103328
  }
102960
103329
  }
@@ -103136,17 +103505,25 @@ function rsyncListArgs(spec, remotePath) {
103136
103505
  return args;
103137
103506
  }
103138
103507
  function rsyncCopyArgs(spec, remotePath, dest) {
103139
- const args = ["-L"];
103508
+ const args = ["-L", "--progress"];
103140
103509
  if (spec.rsh)
103141
103510
  args.push("-e", spec.rsh);
103142
103511
  args.push(rsyncTargetArg(spec, remotePath), dest);
103143
103512
  return args;
103144
103513
  }
103514
+ function rsyncUploadArgs(spec, localSource, remoteDest) {
103515
+ const args = ["--progress"];
103516
+ if (spec.rsh)
103517
+ args.push("-e", spec.rsh);
103518
+ args.push(localSource, rsyncTargetArg(spec, remoteDest));
103519
+ return args;
103520
+ }
103145
103521
 
103146
103522
  // ../../apps/gateway/src/files/device-storage.ts
103147
103523
  var RAW_MAX_BYTES = 50 * 1024 * 1024;
103148
103524
  var LIST_TIMEOUT_MS = 20000;
103149
103525
  var COPY_TIMEOUT_MS = 60000;
103526
+ var TRANSFER_IDLE_TIMEOUT_MS = 120000;
103150
103527
  function ok(data) {
103151
103528
  return { ok: true, data };
103152
103529
  }
@@ -103456,8 +103833,241 @@ async function readRawFile(rootId, inputPath) {
103456
103833
  }
103457
103834
  });
103458
103835
  }
103836
+ function sanitizeUploadName(raw) {
103837
+ const base = raw.split("/").pop() ?? "";
103838
+ if (base === "" || base === "." || base === "..")
103839
+ return null;
103840
+ if (base.includes("/") || base.includes("\\") || base.includes("\x00"))
103841
+ return null;
103842
+ return base;
103843
+ }
103844
+ async function pushFileToDevice(rootId, destDir, srcPath, name24, opts = {}) {
103845
+ const safeName = sanitizeUploadName(name24);
103846
+ if (!safeName)
103847
+ return fail("invalid");
103848
+ const r = resolveContext(rootId);
103849
+ if (!r.ok)
103850
+ return fail(r.code);
103851
+ const { root, device } = r.ctx;
103852
+ const norm = checkAndNormalize(device, root.path, destDir);
103853
+ if (!norm.ok)
103854
+ return fail(norm.code);
103855
+ return enqueueDeviceJob(device.id, async () => {
103856
+ let spec;
103857
+ try {
103858
+ spec = await buildRsyncDeviceSpec(device);
103859
+ } catch (error51) {
103860
+ if (error51 instanceof RsyncAuthError)
103861
+ return fail(error51.code, error51.message);
103862
+ throw error51;
103863
+ }
103864
+ try {
103865
+ const destStat = await statViaRsync(spec, norm.path);
103866
+ if (!destStat.ok)
103867
+ return destStat;
103868
+ if (destStat.data.type !== "dir")
103869
+ return fail("not_a_directory");
103870
+ const remoteDest = posixJoin(norm.path, safeName);
103871
+ let res;
103872
+ try {
103873
+ res = await runRsync(rsyncUploadArgs(spec, srcPath, remoteDest), {
103874
+ env: spec.env,
103875
+ onProgress: opts.onProgress,
103876
+ idleTimeoutMs: TRANSFER_IDLE_TIMEOUT_MS,
103877
+ signal: opts.signal
103878
+ });
103879
+ } catch (error51) {
103880
+ if (error51 instanceof RsyncMissingLocalError)
103881
+ return fail("rsync_missing_local");
103882
+ throw error51;
103883
+ }
103884
+ if (res.exitCode !== 0)
103885
+ return fail(classifyRsyncFailure(res.exitCode, res.stderr), res.stderr);
103886
+ return ok({ uploaded: safeName });
103887
+ } finally {
103888
+ spec.cleanup();
103889
+ }
103890
+ });
103891
+ }
103892
+ async function pullFileFromDevice(rootId, inputPath, opts = {}) {
103893
+ const r = resolveContext(rootId);
103894
+ if (!r.ok)
103895
+ return fail(r.code);
103896
+ const { root, device } = r.ctx;
103897
+ const norm = checkAndNormalize(device, root.path, inputPath);
103898
+ if (!norm.ok)
103899
+ return fail(norm.code);
103900
+ return enqueueDeviceJob(device.id, async () => {
103901
+ let spec;
103902
+ try {
103903
+ spec = await buildRsyncDeviceSpec(device);
103904
+ } catch (error51) {
103905
+ if (error51 instanceof RsyncAuthError)
103906
+ return fail(error51.code, error51.message);
103907
+ throw error51;
103908
+ }
103909
+ try {
103910
+ const st = await statViaRsync(spec, norm.path);
103911
+ if (!st.ok)
103912
+ return st;
103913
+ if (st.data.type === "dir")
103914
+ return fail("is_directory");
103915
+ if (st.data.size != null && st.data.size > config.transferMaxBytes)
103916
+ return fail("too_large");
103917
+ const dir2 = mkdtempSync2(join5(tmpdir2(), "tmex-dl-"));
103918
+ const dest = join5(dir2, "f");
103919
+ const cleanup = () => {
103920
+ try {
103921
+ rmSync2(dir2, { recursive: true, force: true });
103922
+ } catch {}
103923
+ };
103924
+ let res;
103925
+ try {
103926
+ res = await runRsync(rsyncCopyArgs(spec, norm.path, dest), {
103927
+ env: spec.env,
103928
+ onProgress: opts.onProgress,
103929
+ idleTimeoutMs: TRANSFER_IDLE_TIMEOUT_MS,
103930
+ signal: opts.signal
103931
+ });
103932
+ } catch (error51) {
103933
+ cleanup();
103934
+ if (error51 instanceof RsyncMissingLocalError)
103935
+ return fail("rsync_missing_local");
103936
+ throw error51;
103937
+ }
103938
+ if (res.exitCode !== 0) {
103939
+ cleanup();
103940
+ return fail(classifyRsyncFailure(res.exitCode, res.stderr), res.stderr);
103941
+ }
103942
+ const name24 = posixBasename(norm.path);
103943
+ let size = st.data.size ?? 0;
103944
+ try {
103945
+ size = statSync(dest).size;
103946
+ } catch {}
103947
+ if (size > config.transferMaxBytes) {
103948
+ cleanup();
103949
+ return fail("too_large");
103950
+ }
103951
+ return ok({ tmpPath: dest, size, name: name24, mime: mimeOf(name24), cleanup });
103952
+ } finally {
103953
+ spec.cleanup();
103954
+ }
103955
+ });
103956
+ }
103957
+
103958
+ // ../../apps/gateway/src/files/transfer-session.ts
103959
+ import { appendFileSync, mkdtempSync as mkdtempSync3, readdirSync, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
103960
+ import { tmpdir as tmpdir3 } from "os";
103961
+ import { join as join6 } from "path";
103962
+ var sessions = new Map;
103963
+ var SESSION_TTL_MS = 30 * 60000;
103964
+ var downloads = new Map;
103965
+ function sweepStale(now2) {
103966
+ for (const [id, s] of sessions) {
103967
+ if (!s.committing && now2 - s.createdAt > SESSION_TTL_MS) {
103968
+ removeUploadSession(id);
103969
+ }
103970
+ }
103971
+ for (const [id, d] of downloads) {
103972
+ if (now2 - d.createdAt > SESSION_TTL_MS) {
103973
+ removeDownloadSession(id);
103974
+ }
103975
+ }
103976
+ }
103977
+ function createDownloadSession(data) {
103978
+ sweepStale(Date.now());
103979
+ const session = { id: crypto.randomUUID(), createdAt: Date.now(), ...data };
103980
+ downloads.set(session.id, session);
103981
+ return session;
103982
+ }
103983
+ function getDownloadSession(id) {
103984
+ return downloads.get(id);
103985
+ }
103986
+ function removeDownloadSession(id) {
103987
+ const d = downloads.get(id);
103988
+ if (!d)
103989
+ return;
103990
+ downloads.delete(id);
103991
+ try {
103992
+ d.cleanup();
103993
+ } catch {}
103994
+ }
103995
+ function createUploadSession(args) {
103996
+ const now2 = Date.now();
103997
+ sweepStale(now2);
103998
+ const tmpDir = mkdtempSync3(join6(tmpdir3(), "tmex-up-"));
103999
+ const tmpPath = join6(tmpDir, "f");
104000
+ writeFileSync2(tmpPath, new Uint8Array(0));
104001
+ const session = {
104002
+ id: crypto.randomUUID(),
104003
+ rootId: args.rootId,
104004
+ destDir: args.destDir,
104005
+ name: args.name,
104006
+ size: args.size,
104007
+ received: 0,
104008
+ tmpDir,
104009
+ tmpPath,
104010
+ abort: new AbortController,
104011
+ createdAt: now2,
104012
+ committing: false
104013
+ };
104014
+ sessions.set(session.id, session);
104015
+ return session;
104016
+ }
104017
+ function getUploadSession(id) {
104018
+ return sessions.get(id);
104019
+ }
104020
+ function appendUploadChunk(id, offset, bytes) {
104021
+ const s = sessions.get(id);
104022
+ if (!s)
104023
+ return { ok: false, reason: "not_found" };
104024
+ if (offset !== s.received)
104025
+ return { ok: false, reason: "bad_offset" };
104026
+ if (s.received + bytes.byteLength > s.size)
104027
+ return { ok: false, reason: "too_large" };
104028
+ appendFileSync(s.tmpPath, bytes);
104029
+ s.received += bytes.byteLength;
104030
+ return { ok: true, received: s.received };
104031
+ }
104032
+ function removeUploadSession(id) {
104033
+ const s = sessions.get(id);
104034
+ if (!s)
104035
+ return;
104036
+ sessions.delete(id);
104037
+ try {
104038
+ s.abort.abort();
104039
+ } catch {}
104040
+ try {
104041
+ rmSync3(s.tmpDir, { recursive: true, force: true });
104042
+ } catch {}
104043
+ }
104044
+ var transferGcTimer = setInterval(() => sweepStale(Date.now()), 5 * 60000);
104045
+ transferGcTimer.unref?.();
104046
+ var ORPHAN_PREFIXES = ["tmex-up-", "tmex-dl-"];
104047
+ var ORPHAN_MAX_AGE_MS = 60 * 60000;
104048
+ function sweepOrphanTransferTemps() {
104049
+ const base = tmpdir3();
104050
+ let entries;
104051
+ try {
104052
+ entries = readdirSync(base);
104053
+ } catch {
104054
+ return;
104055
+ }
104056
+ const now2 = Date.now();
104057
+ for (const name24 of entries) {
104058
+ if (!ORPHAN_PREFIXES.some((p) => name24.startsWith(p)))
104059
+ continue;
104060
+ const full = join6(base, name24);
104061
+ try {
104062
+ if (now2 - statSync2(full).mtimeMs > ORPHAN_MAX_AGE_MS) {
104063
+ rmSync3(full, { recursive: true, force: true });
104064
+ }
104065
+ } catch {}
104066
+ }
104067
+ }
103459
104068
 
103460
104069
  // ../../apps/gateway/src/api/files.ts
104070
+ var UPLOAD_CHUNK_SIZE = 8 * 1024 * 1024;
103461
104071
  function json4(data, status = 200, headers = {}) {
103462
104072
  return new Response(JSON.stringify(data), {
103463
104073
  status,
@@ -103621,6 +104231,223 @@ async function handleRaw(url2) {
103621
104231
  }
103622
104232
  return new Response(result.data.data, { status: 200, headers });
103623
104233
  }
104234
+ async function handleUploadInit(req) {
104235
+ let body;
104236
+ try {
104237
+ body = await req.json();
104238
+ } catch {
104239
+ return json4({ error: t2("apiError.invalidRequest") }, 400);
104240
+ }
104241
+ const rootId = typeof body.rootId === "string" ? body.rootId : "";
104242
+ const destDir = typeof body.path === "string" ? body.path : "";
104243
+ const rawName = typeof body.name === "string" ? body.name : "";
104244
+ const size = typeof body.size === "number" && Number.isFinite(body.size) ? body.size : -1;
104245
+ if (!rootId || !destDir || !rawName || size < 0) {
104246
+ return json4({ error: t2("apiError.invalidRequest") }, 400);
104247
+ }
104248
+ const name24 = sanitizeUploadName(rawName);
104249
+ if (!name24)
104250
+ return codeError("invalid");
104251
+ if (size > config.transferMaxBytes)
104252
+ return codeError("too_large");
104253
+ const stat = await statFile(rootId, destDir);
104254
+ if (!stat.ok)
104255
+ return codeError(stat.code, stat.detail);
104256
+ if (stat.data.type !== "dir")
104257
+ return codeError("not_a_directory");
104258
+ const session = createUploadSession({ rootId, destDir, name: name24, size });
104259
+ return json4({ uploadId: session.id, chunkSize: UPLOAD_CHUNK_SIZE });
104260
+ }
104261
+ async function handleUploadChunk(req, id, url2) {
104262
+ const offset = Number.parseInt(url2.searchParams.get("offset") ?? "", 10);
104263
+ if (Number.isNaN(offset) || offset < 0)
104264
+ return json4({ error: t2("apiError.invalidRequest") }, 400);
104265
+ const bytes = new Uint8Array(await req.arrayBuffer());
104266
+ const res = appendUploadChunk(id, offset, bytes);
104267
+ if (!res.ok) {
104268
+ if (res.reason === "not_found")
104269
+ return codeError("not_found");
104270
+ if (res.reason === "too_large")
104271
+ return codeError("too_large");
104272
+ return json4({ error: t2("apiError.invalidRequest") }, 409);
104273
+ }
104274
+ return json4({ received: res.received });
104275
+ }
104276
+ function handleUploadCommit(id) {
104277
+ const session = getUploadSession(id);
104278
+ if (!session)
104279
+ return codeError("not_found");
104280
+ if (session.received !== session.size)
104281
+ return codeError("invalid", "incomplete upload");
104282
+ session.committing = true;
104283
+ const encoder3 = new TextEncoder;
104284
+ const stream = new ReadableStream({
104285
+ start(controller) {
104286
+ const emit = (obj) => {
104287
+ try {
104288
+ controller.enqueue(encoder3.encode(`${JSON.stringify(obj)}
104289
+ `));
104290
+ } catch {}
104291
+ };
104292
+ pushFileToDevice(session.rootId, session.destDir, session.tmpPath, session.name, {
104293
+ signal: session.abort.signal,
104294
+ onProgress: (p) => emit({ type: "progress", ...p })
104295
+ }).then((res) => {
104296
+ if (res.ok)
104297
+ emit({ type: "done", uploaded: res.data.uploaded });
104298
+ else
104299
+ emit({ type: "error", code: res.code, detail: res.detail });
104300
+ }).catch((e) => emit({ type: "error", code: "unknown", detail: String(e) })).finally(() => {
104301
+ try {
104302
+ controller.close();
104303
+ } catch {}
104304
+ removeUploadSession(id);
104305
+ });
104306
+ },
104307
+ cancel() {
104308
+ removeUploadSession(id);
104309
+ }
104310
+ });
104311
+ return new Response(stream, {
104312
+ status: 200,
104313
+ headers: { "Content-Type": "application/x-ndjson; charset=utf-8", "Cache-Control": "no-store" }
104314
+ });
104315
+ }
104316
+ function handleUploadCancel(id) {
104317
+ removeUploadSession(id);
104318
+ return json4({ success: true });
104319
+ }
104320
+ function attachmentHeaders(name24, mime, size) {
104321
+ const encoded = encodeURIComponent(name24);
104322
+ const ascii = name24.replace(/["\\\r\n]/g, "_");
104323
+ return {
104324
+ "Content-Type": mime ?? "application/octet-stream",
104325
+ "Content-Length": String(size),
104326
+ "Content-Disposition": `attachment; filename="${ascii}"; filename*=UTF-8''${encoded}`,
104327
+ "Cache-Control": "no-store"
104328
+ };
104329
+ }
104330
+ function streamTempFile(tmpPath, cleanupAfter) {
104331
+ let reader;
104332
+ try {
104333
+ reader = Bun.file(tmpPath).stream().getReader();
104334
+ } catch {
104335
+ cleanupAfter();
104336
+ return null;
104337
+ }
104338
+ return new ReadableStream({
104339
+ async pull(controller) {
104340
+ try {
104341
+ const { done, value } = await reader.read();
104342
+ if (done) {
104343
+ controller.close();
104344
+ cleanupAfter();
104345
+ return;
104346
+ }
104347
+ controller.enqueue(value);
104348
+ } catch (e) {
104349
+ controller.error(e);
104350
+ cleanupAfter();
104351
+ }
104352
+ },
104353
+ cancel() {
104354
+ reader.cancel();
104355
+ cleanupAfter();
104356
+ }
104357
+ });
104358
+ }
104359
+ function handleDownloadPrepare(req) {
104360
+ let abort = null;
104361
+ const encoder3 = new TextEncoder;
104362
+ const stream = new ReadableStream({
104363
+ async start(controller) {
104364
+ const emit = (obj) => {
104365
+ try {
104366
+ controller.enqueue(encoder3.encode(`${JSON.stringify(obj)}
104367
+ `));
104368
+ } catch {}
104369
+ };
104370
+ const close = () => {
104371
+ try {
104372
+ controller.close();
104373
+ } catch {}
104374
+ };
104375
+ let body;
104376
+ try {
104377
+ body = await req.json();
104378
+ } catch {
104379
+ emit({ type: "error", code: "invalid" });
104380
+ close();
104381
+ return;
104382
+ }
104383
+ const rootId = typeof body.rootId === "string" ? body.rootId : "";
104384
+ const path = typeof body.path === "string" ? body.path : "";
104385
+ if (!rootId || !path) {
104386
+ emit({ type: "error", code: "invalid" });
104387
+ close();
104388
+ return;
104389
+ }
104390
+ abort = new AbortController;
104391
+ const result = await pullFileFromDevice(rootId, path, {
104392
+ signal: abort.signal,
104393
+ onProgress: (p) => emit({ type: "progress", ...p })
104394
+ });
104395
+ if (result.ok) {
104396
+ const s = createDownloadSession({
104397
+ tmpPath: result.data.tmpPath,
104398
+ size: result.data.size,
104399
+ name: result.data.name,
104400
+ mime: result.data.mime,
104401
+ cleanup: result.data.cleanup
104402
+ });
104403
+ emit({ type: "done", downloadId: s.id, size: s.size, name: s.name });
104404
+ } else {
104405
+ emit({ type: "error", code: result.code, detail: result.detail });
104406
+ }
104407
+ close();
104408
+ },
104409
+ cancel() {
104410
+ abort?.abort();
104411
+ }
104412
+ });
104413
+ return new Response(stream, {
104414
+ status: 200,
104415
+ headers: { "Content-Type": "application/x-ndjson; charset=utf-8", "Cache-Control": "no-store" }
104416
+ });
104417
+ }
104418
+ function handleDownloadContent(id) {
104419
+ const session = getDownloadSession(id);
104420
+ if (!session)
104421
+ return codeError("not_found");
104422
+ const body = streamTempFile(session.tmpPath, () => removeDownloadSession(id));
104423
+ if (!body)
104424
+ return codeError("unknown");
104425
+ return new Response(body, {
104426
+ status: 200,
104427
+ headers: attachmentHeaders(session.name, session.mime, session.size)
104428
+ });
104429
+ }
104430
+ function handleDownloadCancel(id) {
104431
+ removeDownloadSession(id);
104432
+ return json4({ success: true });
104433
+ }
104434
+ async function handleDownload(req, url2) {
104435
+ const rootId = url2.searchParams.get("rootId");
104436
+ const path = url2.searchParams.get("path");
104437
+ if (!rootId || !path)
104438
+ return json4({ error: t2("apiError.invalidRequest") }, 400);
104439
+ const result = await pullFileFromDevice(rootId, path, {
104440
+ signal: req.signal,
104441
+ onProgress: () => {}
104442
+ });
104443
+ if (!result.ok)
104444
+ return codeError(result.code, result.detail);
104445
+ const { tmpPath, size, name: name24, mime, cleanup } = result.data;
104446
+ const body = streamTempFile(tmpPath, cleanup);
104447
+ if (!body)
104448
+ return codeError("unknown");
104449
+ return new Response(body, { status: 200, headers: attachmentHeaders(name24, mime, size) });
104450
+ }
103624
104451
  function handleFilesApiRequest(req, path) {
103625
104452
  const url2 = new URL(req.url);
103626
104453
  if (path === "/api/files/roots" && req.method === "GET")
@@ -103641,6 +104468,32 @@ function handleFilesApiRequest(req, path) {
103641
104468
  return handleStat(url2);
103642
104469
  if (path === "/api/files/raw" && req.method === "GET")
103643
104470
  return handleRaw(url2);
104471
+ if (path === "/api/files/download" && req.method === "GET")
104472
+ return handleDownload(req, url2);
104473
+ if (path === "/api/files/download/prepare" && req.method === "POST") {
104474
+ return handleDownloadPrepare(req);
104475
+ }
104476
+ const dlContentMatch = path.match(/^\/api\/files\/download\/([^/]+)\/content$/);
104477
+ if (dlContentMatch && req.method === "GET") {
104478
+ return handleDownloadContent(decodeURIComponent(dlContentMatch[1]));
104479
+ }
104480
+ const dlCancelMatch = path.match(/^\/api\/files\/download\/([^/]+)$/);
104481
+ if (dlCancelMatch && req.method === "DELETE") {
104482
+ return handleDownloadCancel(decodeURIComponent(dlCancelMatch[1]));
104483
+ }
104484
+ if (path === "/api/files/upload/init" && req.method === "POST")
104485
+ return handleUploadInit(req);
104486
+ const commitMatch = path.match(/^\/api\/files\/upload\/([^/]+)\/commit$/);
104487
+ if (commitMatch && req.method === "POST") {
104488
+ return handleUploadCommit(decodeURIComponent(commitMatch[1]));
104489
+ }
104490
+ const uploadMatch = path.match(/^\/api\/files\/upload\/([^/]+)$/);
104491
+ if (uploadMatch && req.method === "PUT") {
104492
+ return handleUploadChunk(req, decodeURIComponent(uploadMatch[1]), url2);
104493
+ }
104494
+ if (uploadMatch && req.method === "DELETE") {
104495
+ return handleUploadCancel(decodeURIComponent(uploadMatch[1]));
104496
+ }
103644
104497
  return;
103645
104498
  }
103646
104499
 
@@ -103919,6 +104772,345 @@ function json5(data, status = 200) {
103919
104772
  });
103920
104773
  }
103921
104774
 
104775
+ // ../../apps/gateway/src/system/install-info.ts
104776
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
104777
+ import { resolve as resolve3 } from "path";
104778
+ function resolveInstallDir() {
104779
+ const feDist = process.env.TMEX_FE_DIST_DIR;
104780
+ if (feDist) {
104781
+ return resolve3(feDist, "..", "..");
104782
+ }
104783
+ return process.cwd();
104784
+ }
104785
+ function readInstallMeta() {
104786
+ const metaPath = resolve3(resolveInstallDir(), "install-meta.json");
104787
+ try {
104788
+ if (!existsSync4(metaPath))
104789
+ return null;
104790
+ return JSON.parse(readFileSync4(metaPath, "utf8"));
104791
+ } catch {
104792
+ return null;
104793
+ }
104794
+ }
104795
+ function deploymentFromPlatform(platform) {
104796
+ if (platform === "darwin")
104797
+ return "launchd";
104798
+ if (platform === "linux")
104799
+ return "systemd";
104800
+ return "none";
104801
+ }
104802
+ function getInstallInfo() {
104803
+ if (!config.isProd) {
104804
+ return {
104805
+ installedViaCli: false,
104806
+ deployment: "none",
104807
+ installDir: null,
104808
+ serviceName: null,
104809
+ cliVersion: null
104810
+ };
104811
+ }
104812
+ const meta3 = readInstallMeta();
104813
+ if (!meta3) {
104814
+ return {
104815
+ installedViaCli: false,
104816
+ deployment: "none",
104817
+ installDir: resolveInstallDir(),
104818
+ serviceName: null,
104819
+ cliVersion: null
104820
+ };
104821
+ }
104822
+ return {
104823
+ installedViaCli: true,
104824
+ deployment: deploymentFromPlatform(meta3.platform ?? process.platform),
104825
+ installDir: meta3.installDir ?? resolveInstallDir(),
104826
+ serviceName: meta3.serviceName ?? null,
104827
+ cliVersion: meta3.cliVersion ?? null
104828
+ };
104829
+ }
104830
+
104831
+ // ../../apps/gateway/src/system/version.ts
104832
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
104833
+ import { resolve as resolve4 } from "path";
104834
+ var cachedBase;
104835
+ function readRepoPackageVersion() {
104836
+ const dir2 = import.meta.dir;
104837
+ if (!dir2)
104838
+ return null;
104839
+ const path = resolve4(dir2, "../../../../packages/app/package.json");
104840
+ try {
104841
+ if (!existsSync5(path))
104842
+ return null;
104843
+ const pkg = JSON.parse(readFileSync5(path, "utf8"));
104844
+ return pkg.version ?? null;
104845
+ } catch {
104846
+ return null;
104847
+ }
104848
+ }
104849
+ function getBaseVersion() {
104850
+ if (cachedBase !== undefined)
104851
+ return cachedBase;
104852
+ let base = null;
104853
+ if ("0.12.0") {
104854
+ base = "0.12.0";
104855
+ }
104856
+ if (!base && config.isProd) {
104857
+ base = readInstallMeta()?.cliVersion ?? null;
104858
+ }
104859
+ if (!base) {
104860
+ base = readRepoPackageVersion();
104861
+ }
104862
+ cachedBase = base ?? "unknown";
104863
+ return cachedBase;
104864
+ }
104865
+ function getDisplayVersion() {
104866
+ return formatDisplayVersion(getBaseVersion(), config.isProd);
104867
+ }
104868
+ // ../../apps/gateway/src/system/semver.ts
104869
+ function parse5(input) {
104870
+ const match = input.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
104871
+ if (!match)
104872
+ return null;
104873
+ return {
104874
+ major: Number(match[1]),
104875
+ minor: Number(match[2]),
104876
+ patch: Number(match[3]),
104877
+ prerelease: match[4] ?? null
104878
+ };
104879
+ }
104880
+ function compareVersions(a, b3) {
104881
+ const pa = parse5(a);
104882
+ const pb = parse5(b3);
104883
+ if (!pa || !pb)
104884
+ return 0;
104885
+ if (pa.major !== pb.major)
104886
+ return pa.major > pb.major ? 1 : -1;
104887
+ if (pa.minor !== pb.minor)
104888
+ return pa.minor > pb.minor ? 1 : -1;
104889
+ if (pa.patch !== pb.patch)
104890
+ return pa.patch > pb.patch ? 1 : -1;
104891
+ if (pa.prerelease === pb.prerelease)
104892
+ return 0;
104893
+ if (pa.prerelease === null)
104894
+ return 1;
104895
+ if (pb.prerelease === null)
104896
+ return -1;
104897
+ return pa.prerelease > pb.prerelease ? 1 : pa.prerelease < pb.prerelease ? -1 : 0;
104898
+ }
104899
+
104900
+ // ../../apps/gateway/src/system/update-check.ts
104901
+ var REGISTRY_URL = "https://registry.npmjs.org/tmex-cli";
104902
+ var changelogCdnUrl = (version3) => `https://cdn.jsdelivr.net/npm/tmex-cli@${version3}/CHANGELOG.md`;
104903
+ var FETCH_TIMEOUT_MS = 1e4;
104904
+ async function checkForUpdate() {
104905
+ const current = getBaseVersion();
104906
+ const res = await fetch(REGISTRY_URL, {
104907
+ cache: "no-store",
104908
+ headers: { accept: "application/json", "cache-control": "no-cache" },
104909
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
104910
+ });
104911
+ if (!res.ok) {
104912
+ throw new Error(`npm registry HTTP ${res.status}`);
104913
+ }
104914
+ const packument = await res.json();
104915
+ const latest = packument["dist-tags"]?.latest ?? null;
104916
+ const publishedAt = latest ? packument.time?.[latest] ?? null : null;
104917
+ const hasUpdate = latest !== null && current !== "unknown" && compareVersions(latest, current) > 0;
104918
+ const changelog = latest ? await fetchChangelog(latest) : null;
104919
+ return {
104920
+ currentVersion: current,
104921
+ latestVersion: latest,
104922
+ hasUpdate,
104923
+ changelog,
104924
+ publishedAt
104925
+ };
104926
+ }
104927
+ async function fetchChangelog(version3) {
104928
+ try {
104929
+ const res = await fetch(changelogCdnUrl(version3), {
104930
+ cache: "no-store",
104931
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
104932
+ });
104933
+ if (!res.ok)
104934
+ return null;
104935
+ const text3 = await res.text();
104936
+ return text3.trim() ? text3 : null;
104937
+ } catch {
104938
+ return null;
104939
+ }
104940
+ }
104941
+ // ../../apps/gateway/src/system/upgrade.ts
104942
+ import { spawn } from "child_process";
104943
+ import { closeSync, existsSync as existsSync6, openSync } from "fs";
104944
+ import { mkdtemp, rm, writeFile } from "fs/promises";
104945
+ import { tmpdir as tmpdir4 } from "os";
104946
+ import { join as join7 } from "path";
104947
+ class UpgradeController {
104948
+ state = "idle";
104949
+ targetVersion = null;
104950
+ error = null;
104951
+ startedAt = null;
104952
+ status() {
104953
+ return {
104954
+ state: this.state,
104955
+ targetVersion: this.targetVersion,
104956
+ error: this.error,
104957
+ startedAt: this.startedAt
104958
+ };
104959
+ }
104960
+ isBusy() {
104961
+ return this.state !== "idle";
104962
+ }
104963
+ start(version3) {
104964
+ if (this.isBusy())
104965
+ return false;
104966
+ this.state = "downloading";
104967
+ this.targetVersion = version3;
104968
+ this.error = null;
104969
+ this.startedAt = new Date().toISOString();
104970
+ this.run(version3);
104971
+ return true;
104972
+ }
104973
+ async run(version3) {
104974
+ const install = getInstallInfo();
104975
+ const installDir = install.installDir;
104976
+ let stageDir = null;
104977
+ let cacheDir = null;
104978
+ try {
104979
+ if (!installDir) {
104980
+ throw new Error("install directory could not be resolved");
104981
+ }
104982
+ stageDir = await mkdtemp(join7(tmpdir4(), "tmex-upg-"));
104983
+ cacheDir = await mkdtemp(join7(tmpdir4(), "tmex-upg-cache-"));
104984
+ await writeFile(join7(stageDir, "package.json"), `${JSON.stringify({ name: "tmex-upgrade-stage", private: true })}
104985
+ `);
104986
+ await this.runBunAdd(stageDir, cacheDir, version3);
104987
+ const binPath = join7(stageDir, "node_modules", "tmex-cli", "bin", "tmex.js");
104988
+ if (!existsSync6(binPath)) {
104989
+ throw new Error(`downloaded tmex-cli binary not found at ${binPath}`);
104990
+ }
104991
+ this.state = "executing";
104992
+ this.spawnUpgrade(binPath, installDir, version3);
104993
+ } catch (err) {
104994
+ this.error = err instanceof Error ? err.message : String(err);
104995
+ this.state = "idle";
104996
+ this.targetVersion = null;
104997
+ if (stageDir)
104998
+ await rm(stageDir, { recursive: true, force: true }).catch(() => {});
104999
+ if (cacheDir)
105000
+ await rm(cacheDir, { recursive: true, force: true }).catch(() => {});
105001
+ }
105002
+ }
105003
+ runBunAdd(stageDir, cacheDir, version3) {
105004
+ return new Promise((resolve5, reject) => {
105005
+ const child = spawn(process.execPath, ["add", `tmex-cli@${version3}`], {
105006
+ cwd: stageDir,
105007
+ env: { ...process.env, BUN_INSTALL_CACHE_DIR: cacheDir },
105008
+ stdio: "ignore"
105009
+ });
105010
+ child.on("error", reject);
105011
+ child.on("close", (code2) => {
105012
+ if (code2 === 0)
105013
+ resolve5();
105014
+ else
105015
+ reject(new Error(`bun add tmex-cli@${version3} exited with code ${code2 ?? "null"}`));
105016
+ });
105017
+ });
105018
+ }
105019
+ spawnUpgrade(binPath, installDir, version3) {
105020
+ let logFd = null;
105021
+ try {
105022
+ logFd = openSync(join7(installDir, "upgrade.log"), "a");
105023
+ } catch {
105024
+ logFd = null;
105025
+ }
105026
+ const child = spawn(process.execPath, [
105027
+ binPath,
105028
+ "upgrade",
105029
+ "--apply-current-package",
105030
+ "--install-dir",
105031
+ installDir,
105032
+ "--version",
105033
+ version3
105034
+ ], {
105035
+ cwd: installDir,
105036
+ env: process.env,
105037
+ detached: true,
105038
+ stdio: logFd !== null ? ["ignore", logFd, logFd] : "ignore"
105039
+ });
105040
+ child.unref();
105041
+ if (logFd !== null)
105042
+ closeSync(logFd);
105043
+ }
105044
+ }
105045
+ var upgradeController = new UpgradeController;
105046
+
105047
+ // ../../apps/gateway/src/system/index.ts
105048
+ function getSystemInfo() {
105049
+ const install = getInstallInfo();
105050
+ const canSelfUpdate = config.isProd && install.installedViaCli && install.deployment !== "none";
105051
+ return {
105052
+ version: getDisplayVersion(),
105053
+ baseVersion: getBaseVersion(),
105054
+ isProd: config.isProd,
105055
+ installedViaCli: install.installedViaCli,
105056
+ deployment: install.deployment,
105057
+ canSelfUpdate,
105058
+ serviceName: install.serviceName,
105059
+ transferMaxBytes: config.transferMaxBytes
105060
+ };
105061
+ }
105062
+
105063
+ // ../../apps/gateway/src/api/system.ts
105064
+ function json6(data, status = 200) {
105065
+ return new Response(JSON.stringify(data), {
105066
+ status,
105067
+ headers: { "Content-Type": "application/json" }
105068
+ });
105069
+ }
105070
+ function handleSystemApiRequest(req, path) {
105071
+ if (path === "/api/system/info" && req.method === "GET") {
105072
+ return json6(getSystemInfo());
105073
+ }
105074
+ if (path === "/api/system/update-check" && req.method === "GET") {
105075
+ return handleUpdateCheck();
105076
+ }
105077
+ if (path === "/api/system/upgrade" && req.method === "GET") {
105078
+ return json6(upgradeController.status());
105079
+ }
105080
+ if (path === "/api/system/upgrade" && req.method === "POST") {
105081
+ return handleStartUpgrade(req);
105082
+ }
105083
+ return;
105084
+ }
105085
+ async function handleUpdateCheck() {
105086
+ try {
105087
+ return json6(await checkForUpdate());
105088
+ } catch {
105089
+ return json6({ error: t2("apiError.updateCheckFailed") }, 502);
105090
+ }
105091
+ }
105092
+ async function handleStartUpgrade(req) {
105093
+ const info = getSystemInfo();
105094
+ if (!info.canSelfUpdate) {
105095
+ return json6({ error: t2("apiError.upgradeNotAllowed") }, 403);
105096
+ }
105097
+ let version3 = "";
105098
+ try {
105099
+ const body = await req.json();
105100
+ version3 = (body?.version ?? "").trim();
105101
+ } catch {
105102
+ version3 = "";
105103
+ }
105104
+ if (!version3) {
105105
+ return json6({ error: t2("apiError.upgradeVersionRequired") }, 400);
105106
+ }
105107
+ const started = upgradeController.start(version3);
105108
+ if (!started) {
105109
+ return json6({ ...upgradeController.status(), error: t2("apiError.upgradeInProgress") }, 409);
105110
+ }
105111
+ return json6(upgradeController.status());
105112
+ }
105113
+
103922
105114
  // ../../apps/gateway/src/api/test-connection.ts
103923
105115
  function inferFailurePhase(errorType) {
103924
105116
  if (errorType === "tmux_unavailable") {
@@ -103926,7 +105118,7 @@ function inferFailurePhase(errorType) {
103926
105118
  }
103927
105119
  return "connect";
103928
105120
  }
103929
- function json6(data, status = 200) {
105121
+ function json7(data, status = 200) {
103930
105122
  return new Response(JSON.stringify(data), {
103931
105123
  status,
103932
105124
  headers: {
@@ -103945,7 +105137,7 @@ async function handleDeviceTestConnection(deviceId, inputDeps = {}) {
103945
105137
  };
103946
105138
  const device = deps.getDevice(deviceId);
103947
105139
  if (!device) {
103948
- return json6({ error: deps.translate("apiError.deviceNotFound") }, 404);
105140
+ return json7({ error: deps.translate("apiError.deviceNotFound") }, 404);
103949
105141
  }
103950
105142
  const classifyErrorResponse = (error51) => {
103951
105143
  const rawMessage = error51 instanceof Error ? error51.message : String(error51);
@@ -103958,7 +105150,7 @@ async function handleDeviceTestConnection(deviceId, inputDeps = {}) {
103958
105150
  message: deps.translate(classified.messageKey, classified.messageParams),
103959
105151
  rawMessage
103960
105152
  };
103961
- return json6(payload);
105153
+ return json7(payload);
103962
105154
  };
103963
105155
  let runtime = null;
103964
105156
  try {
@@ -103971,7 +105163,7 @@ async function handleDeviceTestConnection(deviceId, inputDeps = {}) {
103971
105163
  phase: "ready",
103972
105164
  message: deps.translate("common.success")
103973
105165
  };
103974
- return json6(payload);
105166
+ return json7(payload);
103975
105167
  } catch (error51) {
103976
105168
  return classifyErrorResponse(error51);
103977
105169
  } finally {
@@ -105078,35 +106270,35 @@ async function handleListRules(req) {
105078
106270
  if (paneId) {
105079
106271
  rules = rules.filter((rule) => rule.paneId === paneId);
105080
106272
  }
105081
- return json7({ rules: rules.map(toRuleDto) });
106273
+ return json8({ rules: rules.map(toRuleDto) });
105082
106274
  }
105083
106275
  async function handleCreateRule(req, deps) {
105084
106276
  const raw = await readJsonObjectBody3(req);
105085
106277
  if (!raw) {
105086
- return json7({ error: t2("apiError.invalidRequest") }, 400);
106278
+ return json8({ error: t2("apiError.invalidRequest") }, 400);
105087
106279
  }
105088
106280
  const body = raw;
105089
106281
  const name24 = typeof body.name === "string" ? body.name.trim() : "";
105090
106282
  if (!name24) {
105091
- return json7({ error: t2("apiError.watchNameRequired") }, 400);
106283
+ return json8({ error: t2("apiError.watchNameRequired") }, 400);
105092
106284
  }
105093
106285
  const deviceId = typeof body.deviceId === "string" ? body.deviceId.trim() : "";
105094
106286
  if (!deviceId) {
105095
- return json7({ error: t2("apiError.agentDeviceRequired") }, 400);
106287
+ return json8({ error: t2("apiError.agentDeviceRequired") }, 400);
105096
106288
  }
105097
106289
  if (!getDeviceById(deviceId)) {
105098
- return json7({ error: t2("apiError.deviceNotFound") }, 404);
106290
+ return json8({ error: t2("apiError.deviceNotFound") }, 404);
105099
106291
  }
105100
106292
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
105101
106293
  if (!paneId) {
105102
- return json7({ error: t2("apiError.agentPaneRequired") }, 400);
106294
+ return json8({ error: t2("apiError.agentPaneRequired") }, 400);
105103
106295
  }
105104
106296
  if (!TRIGGER_TYPES.includes(body.triggerType)) {
105105
- return json7({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
106297
+ return json8({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
105106
106298
  }
105107
106299
  const parsed = parseRuleFields(raw);
105108
106300
  if (!parsed.ok) {
105109
- return json7({ error: parsed.error }, 400);
106301
+ return json8({ error: parsed.error }, 400);
105110
106302
  }
105111
106303
  const fields = parsed.fields;
105112
106304
  const effective = {
@@ -105119,7 +106311,7 @@ async function handleCreateRule(req, deps) {
105119
106311
  };
105120
106312
  const semanticError = validateRuleSemantics(effective);
105121
106313
  if (semanticError) {
105122
- return json7({ error: semanticError }, 400);
106314
+ return json8({ error: semanticError }, 400);
105123
106315
  }
105124
106316
  const rule = createWatchRule({
105125
106317
  name: name24,
@@ -105142,50 +106334,50 @@ async function handleCreateRule(req, deps) {
105142
106334
  cooldownSeconds: fields.cooldownSeconds
105143
106335
  });
105144
106336
  await deps.service.refreshRule(rule.id);
105145
- return json7({ rule: toRuleDto(rule), state: null }, 201);
106337
+ return json8({ rule: toRuleDto(rule), state: null }, 201);
105146
106338
  }
105147
106339
  async function handleGetRule(id) {
105148
106340
  const rule = getWatchRuleById(id);
105149
106341
  if (!rule) {
105150
- return json7({ error: t2("apiError.watchRuleNotFound") }, 404);
106342
+ return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
105151
106343
  }
105152
106344
  const state = getWatchRuleState(id);
105153
- return json7({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
106345
+ return json8({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
105154
106346
  }
105155
106347
  async function handleUpdateRule(req, id, deps) {
105156
106348
  const existing = getWatchRuleById(id);
105157
106349
  if (!existing) {
105158
- return json7({ error: t2("apiError.watchRuleNotFound") }, 404);
106350
+ return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
105159
106351
  }
105160
106352
  const raw = await readJsonObjectBody3(req);
105161
106353
  if (!raw) {
105162
- return json7({ error: t2("apiError.invalidRequest") }, 400);
106354
+ return json8({ error: t2("apiError.invalidRequest") }, 400);
105163
106355
  }
105164
106356
  const body = raw;
105165
106357
  const updates = {};
105166
106358
  if (body.name !== undefined) {
105167
106359
  const name24 = typeof body.name === "string" ? body.name.trim() : "";
105168
106360
  if (!name24) {
105169
- return json7({ error: t2("apiError.watchNameRequired") }, 400);
106361
+ return json8({ error: t2("apiError.watchNameRequired") }, 400);
105170
106362
  }
105171
106363
  updates.name = name24;
105172
106364
  }
105173
106365
  if (body.paneId !== undefined) {
105174
106366
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
105175
106367
  if (!paneId) {
105176
- return json7({ error: t2("apiError.agentPaneRequired") }, 400);
106368
+ return json8({ error: t2("apiError.agentPaneRequired") }, 400);
105177
106369
  }
105178
106370
  updates.paneId = paneId;
105179
106371
  }
105180
106372
  if (body.triggerType !== undefined) {
105181
106373
  if (!TRIGGER_TYPES.includes(body.triggerType)) {
105182
- return json7({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
106374
+ return json8({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
105183
106375
  }
105184
106376
  updates.triggerType = body.triggerType;
105185
106377
  }
105186
106378
  const parsed = parseRuleFields(raw);
105187
106379
  if (!parsed.ok) {
105188
- return json7({ error: parsed.error }, 400);
106380
+ return json8({ error: parsed.error }, 400);
105189
106381
  }
105190
106382
  const fields = parsed.fields;
105191
106383
  Object.assign(updates, fields);
@@ -105199,32 +106391,32 @@ async function handleUpdateRule(req, id, deps) {
105199
106391
  };
105200
106392
  const semanticError = validateRuleSemantics(effective);
105201
106393
  if (semanticError) {
105202
- return json7({ error: semanticError }, 400);
106394
+ return json8({ error: semanticError }, 400);
105203
106395
  }
105204
106396
  const rule = updateWatchRule(id, updates);
105205
106397
  if (!rule) {
105206
- return json7({ error: t2("apiError.watchRuleNotFound") }, 404);
106398
+ return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
105207
106399
  }
105208
106400
  await deps.service.refreshRule(id);
105209
106401
  const state = getWatchRuleState(id);
105210
- return json7({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
106402
+ return json8({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
105211
106403
  }
105212
106404
  async function handleDeleteRule(id, deps) {
105213
106405
  const existing = getWatchRuleById(id);
105214
106406
  if (!existing) {
105215
- return json7({ error: t2("apiError.watchRuleNotFound") }, 404);
106407
+ return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
105216
106408
  }
105217
106409
  deleteWatchRule(id);
105218
106410
  await deps.service.removeRule(id);
105219
- return json7({ success: true });
106411
+ return json8({ success: true });
105220
106412
  }
105221
106413
  async function handleGetRuleState(id, deps) {
105222
106414
  const rule = getWatchRuleById(id);
105223
106415
  if (!rule) {
105224
- return json7({ error: t2("apiError.watchRuleNotFound") }, 404);
106416
+ return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
105225
106417
  }
105226
106418
  const state = getWatchRuleState(id);
105227
- return json7({
106419
+ return json8({
105228
106420
  state: state ? toStateDto(state) : null,
105229
106421
  samples: deps.service.getSamples(id)
105230
106422
  });
@@ -105248,17 +106440,17 @@ function buildAssistPrompt(description, screen) {
105248
106440
  async function handleAssistRegex(req, deps) {
105249
106441
  const raw = await readJsonObjectBody3(req);
105250
106442
  if (!raw) {
105251
- return json7({ error: t2("apiError.invalidRequest") }, 400);
106443
+ return json8({ error: t2("apiError.invalidRequest") }, 400);
105252
106444
  }
105253
106445
  const body = raw;
105254
106446
  const description = typeof body.description === "string" ? body.description.trim() : "";
105255
106447
  if (!description) {
105256
- return json7({ error: t2("apiError.watchAssistDescriptionRequired") }, 400);
106448
+ return json8({ error: t2("apiError.watchAssistDescriptionRequired") }, 400);
105257
106449
  }
105258
106450
  let providerId = null;
105259
106451
  if (body.providerId !== undefined && body.providerId !== null) {
105260
106452
  if (typeof body.providerId !== "string" || !getLlmProviderById(body.providerId)) {
105261
- return json7({ error: t2("apiError.llmProviderNotFound") }, 400);
106453
+ return json8({ error: t2("apiError.llmProviderNotFound") }, 400);
105262
106454
  }
105263
106455
  providerId = body.providerId;
105264
106456
  }
@@ -105268,7 +106460,7 @@ async function handleAssistRegex(req, deps) {
105268
106460
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
105269
106461
  if (deviceId && paneId) {
105270
106462
  if (!getDeviceById(deviceId)) {
105271
- return json7({ error: t2("apiError.deviceNotFound") }, 404);
106463
+ return json8({ error: t2("apiError.deviceNotFound") }, 404);
105272
106464
  }
105273
106465
  try {
105274
106466
  screen = await deps.captureScreen(deviceId, paneId);
@@ -105289,14 +106481,14 @@ async function handleAssistRegex(req, deps) {
105289
106481
  object3 = result.object;
105290
106482
  } catch (error51) {
105291
106483
  const detail = error51 instanceof Error ? error51.message : String(error51);
105292
- return json7({ error: t2("apiError.watchAssistModelUnavailable", { detail }) }, 502);
106484
+ return json8({ error: t2("apiError.watchAssistModelUnavailable", { detail }) }, 502);
105293
106485
  }
105294
106486
  let regex;
105295
106487
  try {
105296
106488
  regex = compileWatchPattern(object3.pattern, object3.flags);
105297
106489
  } catch (error51) {
105298
106490
  const detail = error51 instanceof Error ? error51.message : String(error51);
105299
- return json7({ error: t2("apiError.watchPatternInvalid", { detail }) }, 502);
106491
+ return json8({ error: t2("apiError.watchPatternInvalid", { detail }) }, 502);
105300
106492
  }
105301
106493
  const preview = [];
105302
106494
  if (screen) {
@@ -105310,7 +106502,7 @@ async function handleAssistRegex(req, deps) {
105310
106502
  match = regex.exec(screen);
105311
106503
  }
105312
106504
  }
105313
- return json7({
106505
+ return json8({
105314
106506
  pattern: object3.pattern,
105315
106507
  flags: object3.flags,
105316
106508
  extractGroup: object3.extractGroup >= 0 ? object3.extractGroup : 0,
@@ -105318,7 +106510,7 @@ async function handleAssistRegex(req, deps) {
105318
106510
  preview
105319
106511
  });
105320
106512
  }
105321
- function json7(data, status = 200) {
106513
+ function json8(data, status = 200) {
105322
106514
  return new Response(JSON.stringify(data), {
105323
106515
  status,
105324
106516
  headers: {
@@ -105508,6 +106700,12 @@ function handleApiRequest(req, _server) {
105508
106700
  return filesResponse;
105509
106701
  }
105510
106702
  }
106703
+ if (path.startsWith("/api/system/")) {
106704
+ const systemResponse = handleSystemApiRequest(req, path);
106705
+ if (systemResponse) {
106706
+ return systemResponse;
106707
+ }
106708
+ }
105511
106709
  if (path === "/api/webhooks" && req.method === "GET") {
105512
106710
  return handleGetWebhooks();
105513
106711
  }
@@ -105521,9 +106719,13 @@ function handleApiRequest(req, _server) {
105521
106719
  return handleGetManifest(req.method);
105522
106720
  }
105523
106721
  if (path === "/healthz" && req.method === "GET") {
105524
- return json8({ status: "ok", restarting: runtimeController.isRestarting() });
106722
+ return json9({
106723
+ status: "ok",
106724
+ restarting: runtimeController.isRestarting(),
106725
+ env: "development"
106726
+ });
105525
106727
  }
105526
- return json8({ error: t2("apiError.notFound") }, 404);
106728
+ return json9({ error: t2("apiError.notFound") }, 404);
105527
106729
  }
105528
106730
  function enrichDeviceWithRuntime(device) {
105529
106731
  const status = getDeviceRuntimeStatus(device.id);
@@ -105537,22 +106739,22 @@ function enrichDeviceWithRuntime(device) {
105537
106739
  }
105538
106740
  async function handleGetDevices() {
105539
106741
  const devices2 = getAllDevices().map(enrichDeviceWithRuntime);
105540
- return json8({ devices: devices2 });
106742
+ return json9({ devices: devices2 });
105541
106743
  }
105542
106744
  async function handleGetDevice(id) {
105543
106745
  const device = getDeviceById(id);
105544
106746
  if (!device) {
105545
- return json8({ error: t2("apiError.deviceNotFound") }, 404);
106747
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
105546
106748
  }
105547
- return json8({ device: enrichDeviceWithRuntime(device) });
106749
+ return json9({ device: enrichDeviceWithRuntime(device) });
105548
106750
  }
105549
106751
  async function handleCreateDevice(req) {
105550
106752
  const body = await req.json();
105551
106753
  if (!body.name || !body.type || !body.authMode) {
105552
- return json8({ error: t2("apiError.missingFields") }, 400);
106754
+ return json9({ error: t2("apiError.missingFields") }, 400);
105553
106755
  }
105554
106756
  if (body.type === "ssh" && !body.host && !body.sshConfigRef) {
105555
- return json8({ error: t2("apiError.sshRequiresHost") }, 400);
106757
+ return json9({ error: t2("apiError.sshRequiresHost") }, 400);
105556
106758
  }
105557
106759
  const now2 = new Date().toISOString();
105558
106760
  const device = {
@@ -105574,12 +106776,12 @@ async function handleCreateDevice(req) {
105574
106776
  };
105575
106777
  createDevice(device);
105576
106778
  await pushSupervisor.upsert(device.id);
105577
- return json8({ device: getDeviceById(device.id) ?? device }, 201);
106779
+ return json9({ device: getDeviceById(device.id) ?? device }, 201);
105578
106780
  }
105579
106781
  async function handleUpdateDevice(req, id) {
105580
106782
  const existing = getDeviceById(id);
105581
106783
  if (!existing) {
105582
- return json8({ error: t2("apiError.deviceNotFound") }, 404);
106784
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
105583
106785
  }
105584
106786
  const body = await req.json();
105585
106787
  const updates = {};
@@ -105609,61 +106811,61 @@ async function handleUpdateDevice(req, id) {
105609
106811
  await pushSupervisor.reconnect(id);
105610
106812
  }
105611
106813
  const device = getDeviceById(id);
105612
- return json8({ device });
106814
+ return json9({ device });
105613
106815
  }
105614
106816
  async function handleReorderDevices(req) {
105615
106817
  const body = await req.json();
105616
106818
  if (!Array.isArray(body.deviceIds) || body.deviceIds.some((id) => typeof id !== "string")) {
105617
- return json8({ error: t2("apiError.invalidRequest") }, 400);
106819
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
105618
106820
  }
105619
106821
  reorderDevices(body.deviceIds);
105620
- return json8({ devices: getAllDevices().map(enrichDeviceWithRuntime) });
106822
+ return json9({ devices: getAllDevices().map(enrichDeviceWithRuntime) });
105621
106823
  }
105622
106824
  async function handleDeleteDevice(id) {
105623
106825
  const existing = getDeviceById(id);
105624
106826
  if (!existing) {
105625
- return json8({ error: t2("apiError.deviceNotFound") }, 404);
106827
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
105626
106828
  }
105627
106829
  deleteDevice(id);
105628
106830
  pushSupervisor.remove(id);
105629
- return json8({ success: true });
106831
+ return json9({ success: true });
105630
106832
  }
105631
106833
  async function handleTestConnection(id) {
105632
106834
  return handleDeviceTestConnection(id);
105633
106835
  }
105634
106836
  async function handleGetSiteSettings() {
105635
- return json8({ settings: getSiteSettings() });
106837
+ return json9({ settings: getSiteSettings() });
105636
106838
  }
105637
106839
  async function handleUpdateSiteSettings(req) {
105638
106840
  try {
105639
106841
  const body = await req.json();
105640
106842
  const updates = normalizeSiteSettingsInput(body);
105641
106843
  const settings = updateSiteSettings(updates);
105642
- return json8({ settings });
106844
+ return json9({ settings });
105643
106845
  } catch (err) {
105644
- return json8({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
106846
+ return json9({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
105645
106847
  }
105646
106848
  }
105647
106849
  async function handleRestartGateway() {
105648
106850
  setTimeout(() => {
105649
106851
  runtimeController.requestRestart();
105650
106852
  }, 50);
105651
- return json8({
106853
+ return json9({
105652
106854
  success: true,
105653
106855
  message: t2("settings.restartScheduled")
105654
106856
  });
105655
106857
  }
105656
106858
  async function handleGetTelegramBots() {
105657
106859
  const bots = getTelegramBotsWithStats();
105658
- return json8({ bots });
106860
+ return json9({ bots });
105659
106861
  }
105660
106862
  async function handleCreateTelegramBot(req) {
105661
106863
  const body = await req.json();
105662
106864
  if (!body.name?.trim()) {
105663
- return json8({ error: t2("apiError.botNameRequired") }, 400);
106865
+ return json9({ error: t2("apiError.botNameRequired") }, 400);
105664
106866
  }
105665
106867
  if (!body.token?.trim()) {
105666
- return json8({ error: t2("apiError.botTokenRequired") }, 400);
106868
+ return json9({ error: t2("apiError.botTokenRequired") }, 400);
105667
106869
  }
105668
106870
  const now2 = new Date().toISOString();
105669
106871
  createTelegramBot({
@@ -105677,26 +106879,26 @@ async function handleCreateTelegramBot(req) {
105677
106879
  updatedAt: now2
105678
106880
  });
105679
106881
  await telegramService.refresh();
105680
- return json8({ success: true }, 201);
106882
+ return json9({ success: true }, 201);
105681
106883
  }
105682
106884
  async function handleUpdateTelegramBot(req, botId) {
105683
106885
  const existing = getTelegramBotById(botId);
105684
106886
  if (!existing) {
105685
- return json8({ error: t2("apiError.botNotFound") }, 404);
106887
+ return json9({ error: t2("apiError.botNotFound") }, 404);
105686
106888
  }
105687
106889
  const body = await req.json();
105688
106890
  const updates = {};
105689
106891
  if (body.name !== undefined) {
105690
106892
  const value = body.name.trim();
105691
106893
  if (!value) {
105692
- return json8({ error: t2("apiError.botNameRequired") }, 400);
106894
+ return json9({ error: t2("apiError.botNameRequired") }, 400);
105693
106895
  }
105694
106896
  updates.name = value;
105695
106897
  }
105696
106898
  if (body.token !== undefined) {
105697
106899
  const token = body.token.trim();
105698
106900
  if (!token) {
105699
- return json8({ error: t2("apiError.botTokenRequired") }, 400);
106901
+ return json9({ error: t2("apiError.botTokenRequired") }, 400);
105700
106902
  }
105701
106903
  updates.tokenEnc = await encrypt(token);
105702
106904
  }
@@ -105708,69 +106910,69 @@ async function handleUpdateTelegramBot(req, botId) {
105708
106910
  }
105709
106911
  updateTelegramBot(botId, updates);
105710
106912
  await telegramService.refresh();
105711
- return json8({ success: true });
106913
+ return json9({ success: true });
105712
106914
  }
105713
106915
  async function handleDeleteTelegramBot(botId) {
105714
106916
  const existing = getTelegramBotById(botId);
105715
106917
  if (!existing) {
105716
- return json8({ error: t2("apiError.botNotFound") }, 404);
106918
+ return json9({ error: t2("apiError.botNotFound") }, 404);
105717
106919
  }
105718
106920
  deleteTelegramBot(botId);
105719
106921
  await telegramService.refresh();
105720
- return json8({ success: true });
106922
+ return json9({ success: true });
105721
106923
  }
105722
106924
  async function handleListTelegramChats(botId) {
105723
106925
  const existing = getTelegramBotById(botId);
105724
106926
  if (!existing) {
105725
- return json8({ error: t2("apiError.botNotFound") }, 404);
106927
+ return json9({ error: t2("apiError.botNotFound") }, 404);
105726
106928
  }
105727
106929
  const chats = listTelegramChatsByBot(botId);
105728
- return json8({ chats });
106930
+ return json9({ chats });
105729
106931
  }
105730
106932
  async function handleApproveTelegramChat(botId, chatId) {
105731
106933
  const existing = getTelegramBotById(botId);
105732
106934
  if (!existing) {
105733
- return json8({ error: t2("apiError.botNotFound") }, 404);
106935
+ return json9({ error: t2("apiError.botNotFound") }, 404);
105734
106936
  }
105735
106937
  const chat = approveTelegramChat(botId, chatId);
105736
106938
  if (!chat) {
105737
- return json8({ error: t2("apiError.chatNotFound") }, 404);
106939
+ return json9({ error: t2("apiError.chatNotFound") }, 404);
105738
106940
  }
105739
106941
  const settings = getSiteSettings();
105740
106942
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.approveMessageTemplate", {
105741
106943
  botName: existing.name,
105742
106944
  time: new Date().toLocaleString(toBCP47(settings.language))
105743
106945
  }));
105744
- return json8({ chat });
106946
+ return json9({ chat });
105745
106947
  }
105746
106948
  async function handleDeleteTelegramChat(botId, chatId) {
105747
106949
  const existing = getTelegramBotById(botId);
105748
106950
  if (!existing) {
105749
- return json8({ error: t2("apiError.botNotFound") }, 404);
106951
+ return json9({ error: t2("apiError.botNotFound") }, 404);
105750
106952
  }
105751
106953
  deleteTelegramChat(botId, chatId);
105752
- return json8({ success: true });
106954
+ return json9({ success: true });
105753
106955
  }
105754
106956
  async function handleTestTelegramChat(botId, chatId) {
105755
106957
  const bot = getTelegramBotById(botId);
105756
106958
  if (!bot) {
105757
- return json8({ error: t2("apiError.botNotFound") }, 404);
106959
+ return json9({ error: t2("apiError.botNotFound") }, 404);
105758
106960
  }
105759
106961
  const settings = getSiteSettings();
105760
106962
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.testMessageTemplate", {
105761
106963
  siteName: settings.siteName,
105762
106964
  time: new Date().toLocaleString(toBCP47(settings.language))
105763
106965
  }));
105764
- return json8({ success: true });
106966
+ return json9({ success: true });
105765
106967
  }
105766
106968
  async function handleGetWebhooks() {
105767
106969
  const webhooks = getAllWebhookEndpoints();
105768
- return json8({ webhooks });
106970
+ return json9({ webhooks });
105769
106971
  }
105770
106972
  async function handleCreateWebhook(req) {
105771
106973
  const body = await req.json();
105772
106974
  if (!body.url || !body.secret) {
105773
- return json8({ error: t2("apiError.urlAndSecretRequired") }, 400);
106975
+ return json9({ error: t2("apiError.urlAndSecretRequired") }, 400);
105774
106976
  }
105775
106977
  const now2 = new Date().toISOString();
105776
106978
  const endpoint = {
@@ -105783,11 +106985,11 @@ async function handleCreateWebhook(req) {
105783
106985
  updatedAt: now2
105784
106986
  };
105785
106987
  createWebhookEndpoint(endpoint);
105786
- return json8({ webhook: endpoint }, 201);
106988
+ return json9({ webhook: endpoint }, 201);
105787
106989
  }
105788
106990
  async function handleDeleteWebhook(id) {
105789
106991
  deleteWebhookEndpoint(id);
105790
- return json8({ success: true });
106992
+ return json9({ success: true });
105791
106993
  }
105792
106994
  async function handleGetManifest(method) {
105793
106995
  const settings = getSiteSettings();
@@ -105808,7 +107010,7 @@ async function handleGetManifest(method) {
105808
107010
  purpose: "any"
105809
107011
  },
105810
107012
  {
105811
- src: "/tmex.png",
107013
+ src: "/tmex-maskable.png",
105812
107014
  sizes: "1024x1024",
105813
107015
  type: "image/png",
105814
107016
  purpose: "maskable"
@@ -105826,7 +107028,7 @@ function manifestJson(data, method) {
105826
107028
  }
105827
107029
  });
105828
107030
  }
105829
- function json8(data, status = 200, headers = {}) {
107031
+ function json9(data, status = 200, headers = {}) {
105830
107032
  return new Response(JSON.stringify(data), {
105831
107033
  status,
105832
107034
  headers: {
@@ -105837,8 +107039,8 @@ function json8(data, status = 200, headers = {}) {
105837
107039
  }
105838
107040
 
105839
107041
  // ../../apps/gateway/src/db/migrate.ts
105840
- import { existsSync as existsSync4 } from "fs";
105841
- import { resolve as resolve3 } from "path";
107042
+ import { existsSync as existsSync7 } from "fs";
107043
+ import { resolve as resolve5 } from "path";
105842
107044
 
105843
107045
  // ../../node_modules/.bun/drizzle-orm@0.45.1+d3720f6f296df04b/node_modules/drizzle-orm/migrator.js
105844
107046
  import crypto2 from "crypto";
@@ -105883,10 +107085,10 @@ function resolveMigrationsFolder() {
105883
107085
  const fromEnv = process.env.TMEX_MIGRATIONS_DIR;
105884
107086
  if (fromEnv)
105885
107087
  return fromEnv;
105886
- const byCwd = resolve3(process.cwd(), "drizzle");
105887
- if (existsSync4(byCwd))
107088
+ const byCwd = resolve5(process.cwd(), "drizzle");
107089
+ if (existsSync7(byCwd))
105888
107090
  return byCwd;
105889
- return resolve3(import.meta.dir, "../../drizzle");
107091
+ return resolve5(import.meta.dir, "../../drizzle");
105890
107092
  }
105891
107093
  var migrationsFolder = resolveMigrationsFolder();
105892
107094
  function runMigrations() {
@@ -107390,6 +108592,7 @@ async function createGatewayRuntime(options = {}) {
107390
108592
  }
107391
108593
  runtimeController.reset();
107392
108594
  primeLocalShellPath();
108595
+ sweepOrphanTransferTemps();
107393
108596
  const wsServer = new WebSocketServer;
107394
108597
  connectionAlertNotifier.setBroadcaster((deviceId, payload) => {
107395
108598
  wsServer.broadcastDeviceError(deviceId, payload);
@@ -107688,16 +108891,16 @@ function contentTypeByPath(path) {
107688
108891
  }
107689
108892
  function resolveStaticRoot() {
107690
108893
  if (process.env.TMEX_FE_DIST_DIR) {
107691
- return resolve4(process.env.TMEX_FE_DIST_DIR);
108894
+ return resolve6(process.env.TMEX_FE_DIST_DIR);
107692
108895
  }
107693
- return resolve4(import.meta.dir, "../../resources/fe-dist");
108896
+ return resolve6(import.meta.dir, "../../resources/fe-dist");
107694
108897
  }
107695
108898
  function resolveRequestedFile(staticRoot, pathname) {
107696
- const root = resolve4(staticRoot);
108899
+ const root = resolve6(staticRoot);
107697
108900
  const decoded = decodeURIComponent(pathname);
107698
108901
  const normalized = normalize(decoded).replace(/^\.\.(\/|\\|$)/, "");
107699
108902
  const requested = normalized === "/" ? "/index.html" : normalized;
107700
- const absolutePath = resolve4(root, `.${requested}`);
108903
+ const absolutePath = resolve6(root, `.${requested}`);
107701
108904
  if (absolutePath !== root && !absolutePath.startsWith(`${root}${sep}`)) {
107702
108905
  return null;
107703
108906
  }
@@ -107712,12 +108915,12 @@ async function serveFrontend(req, staticRoot) {
107712
108915
  if (!requestedPath) {
107713
108916
  return new Response(t3("runtime.forbidden"), { status: 403 });
107714
108917
  }
107715
- if (!existsSync5(requestedPath) && extname2(url2.pathname) !== "") {
108918
+ if (!existsSync8(requestedPath) && extname2(url2.pathname) !== "") {
107716
108919
  return new Response(t3("runtime.notFound"), { status: 404 });
107717
108920
  }
107718
- const indexPath = join6(staticRoot, "index.html");
107719
- const targetPath = existsSync5(requestedPath) ? requestedPath : indexPath;
107720
- if (!existsSync5(targetPath)) {
108921
+ const indexPath = join8(staticRoot, "index.html");
108922
+ const targetPath = existsSync8(requestedPath) ? requestedPath : indexPath;
108923
+ if (!existsSync8(targetPath)) {
107721
108924
  return new Response(t3("runtime.frontendMissing"), { status: 500 });
107722
108925
  }
107723
108926
  const headers = new Headers;
@@ -107728,6 +108931,7 @@ async function serveFrontend(req, staticRoot) {
107728
108931
  return new Response(Bun.file(targetPath), { headers });
107729
108932
  }
107730
108933
  async function main() {
108934
+ console.log(`[tmex] version ${getDisplayVersion()}`);
107731
108935
  const host = process.env.TMEX_BIND_HOST || "127.0.0.1";
107732
108936
  const port = Number(process.env.GATEWAY_PORT || "9883");
107733
108937
  const staticRoot = resolveStaticRoot();