tmex-cli 0.11.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 (66) hide show
  1. package/CHANGELOG.md +36 -12
  2. package/dist/runtime/server.js +685 -41
  3. package/package.json +1 -1
  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-YWma96aj.js → FilePage-DtXS6Krk.js} +8 -8
  7. package/resources/fe-dist/assets/{SettingsPage-LhUD03Ln.js → SettingsPage-Bxat-d_V.js} +3 -3
  8. package/resources/fe-dist/assets/{arc-DLVa298O.js → arc-CWkT1O24.js} +2 -2
  9. package/resources/fe-dist/assets/{architectureDiagram-3BPJPVTR-EOUpuRby.js → architectureDiagram-3BPJPVTR-nu1LX68y.js} +2 -2
  10. package/resources/fe-dist/assets/{blockDiagram-GPEHLZMM-BTwwkzZh.js → blockDiagram-GPEHLZMM-DLFLNzT_.js} +2 -2
  11. package/resources/fe-dist/assets/{c4Diagram-AAUBKEIU-xvTqXxl9.js → c4Diagram-AAUBKEIU-BE8aWxc1.js} +2 -2
  12. package/resources/fe-dist/assets/{card-cbVDy0Xp.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-Blr6l1Nk.js → chunk-2J33WTMH-Dlc3MV0y.js} +2 -2
  15. package/resources/fe-dist/assets/{chunk-4BX2VUAB-D_Y2TeLg.js → chunk-4BX2VUAB-E9XGobks.js} +2 -2
  16. package/resources/fe-dist/assets/{chunk-55IACEB6-CimNoYDH.js → chunk-55IACEB6-Bp7ueFZf.js} +2 -2
  17. package/resources/fe-dist/assets/{chunk-727SXJPM-JPYDN0Cb.js → chunk-727SXJPM-CQrvyHfQ.js} +2 -2
  18. package/resources/fe-dist/assets/{chunk-AQP2D5EJ-Cb3NZBib.js → chunk-AQP2D5EJ-c0B1XNZr.js} +2 -2
  19. package/resources/fe-dist/assets/{chunk-FMBD7UC4-DyZ7Ot2F.js → chunk-FMBD7UC4-BWQBkKWs.js} +2 -2
  20. package/resources/fe-dist/assets/{chunk-ND2GUHAM-SU6Ea3wF.js → chunk-ND2GUHAM-D9TROogS.js} +2 -2
  21. package/resources/fe-dist/assets/{chunk-QZHKN3VN-B6qXX6sU.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-BAyMopHS.js → cose-bilkent-S5V4N54A-CBgJRn7g.js} +2 -2
  25. package/resources/fe-dist/assets/{dagre-BM42HDAG-Ckrx9BXZ.js → dagre-BM42HDAG-ClYznWvX.js} +2 -2
  26. package/resources/fe-dist/assets/{diagram-2AECGRRQ-DTnZtf2k.js → diagram-2AECGRRQ-Br_41uIc.js} +2 -2
  27. package/resources/fe-dist/assets/{diagram-5GNKFQAL-DBpgZj4a.js → diagram-5GNKFQAL-BD9o1BrS.js} +2 -2
  28. package/resources/fe-dist/assets/{diagram-KO2AKTUF-CfijYCeY.js → diagram-KO2AKTUF-Diq2zS9Y.js} +2 -2
  29. package/resources/fe-dist/assets/{diagram-LMA3HP47-D7nBmQ-E.js → diagram-LMA3HP47-CLaOalPy.js} +2 -2
  30. package/resources/fe-dist/assets/{diagram-OG6HWLK6-Bi8sHV0O.js → diagram-OG6HWLK6-BcSlvdPy.js} +2 -2
  31. package/resources/fe-dist/assets/{erDiagram-TEJ5UH35-DcoDesO2.js → erDiagram-TEJ5UH35-BNlad0c4.js} +2 -2
  32. package/resources/fe-dist/assets/{flowDiagram-I6XJVG4X-Bbcl9kxS.js → flowDiagram-I6XJVG4X-Zb1FLY00.js} +2 -2
  33. package/resources/fe-dist/assets/{ganttDiagram-6RSMTGT7-BM8Y7jwp.js → ganttDiagram-6RSMTGT7-DPRPR5Is.js} +2 -2
  34. package/resources/fe-dist/assets/{gitGraphDiagram-PVQCEYII-x3wpNQcW.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-Bl6cgwVb.js → infoDiagram-5YYISTIA-NfMlH21e.js} +2 -2
  38. package/resources/fe-dist/assets/{ishikawaDiagram-YF4QCWOH-BfQ7aZxN.js → ishikawaDiagram-YF4QCWOH-CzK9-7uY.js} +2 -2
  39. package/resources/fe-dist/assets/{journeyDiagram-JHISSGLW-DSwQzmdL.js → journeyDiagram-JHISSGLW-CIX5Ahuw.js} +2 -2
  40. package/resources/fe-dist/assets/{kanban-definition-UN3LZRKU-B1aWDl4E.js → kanban-definition-UN3LZRKU-C5Ccl5Cw.js} +2 -2
  41. package/resources/fe-dist/assets/{linear-CfhRC4iX.js → linear-CBC08yeS.js} +2 -2
  42. package/resources/fe-dist/assets/{markdown-preview-CfBRMViB.js → markdown-preview-Drd503bU.js} +4 -4
  43. package/resources/fe-dist/assets/{mermaid.core-M-o4g4TO.js → mermaid.core-DYl6mxS0.js} +6 -6
  44. package/resources/fe-dist/assets/{mindmap-definition-RKZ34NQL-D976sxOA.js → mindmap-definition-RKZ34NQL-ACR2SUDm.js} +2 -2
  45. package/resources/fe-dist/assets/{pieDiagram-4H26LBE5-PVQ3-EB6.js → pieDiagram-4H26LBE5-BuDDdwwy.js} +2 -2
  46. package/resources/fe-dist/assets/{quadrantDiagram-W4KKPZXB-BNEFhYNq.js → quadrantDiagram-W4KKPZXB-CzM2Ydhy.js} +2 -2
  47. package/resources/fe-dist/assets/{refresh-cw-cfX5JiIH.js → refresh-cw-DhzvbVDA.js} +2 -2
  48. package/resources/fe-dist/assets/{requirementDiagram-4Y6WPE33-B7Zd2lpN.js → requirementDiagram-4Y6WPE33-BveKh_kW.js} +2 -2
  49. package/resources/fe-dist/assets/{sankeyDiagram-5OEKKPKP-D_Td4j-B.js → sankeyDiagram-5OEKKPKP-BGQ6jtyE.js} +2 -2
  50. package/resources/fe-dist/assets/{sequenceDiagram-3UESZ5HK-DQSVX5mL.js → sequenceDiagram-3UESZ5HK-D2fdqire.js} +2 -2
  51. package/resources/fe-dist/assets/{stateDiagram-AJRCARHV-C0PNCTfs.js → stateDiagram-AJRCARHV-DtuVBIq9.js} +2 -2
  52. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-gun__PvU.js +2 -0
  53. package/resources/fe-dist/assets/{timeline-definition-PNZ67QCA-BlMj8Fl1.js → timeline-definition-PNZ67QCA-Dslo0JFS.js} +2 -2
  54. package/resources/fe-dist/assets/{vennDiagram-CIIHVFJN-DuGxwZiq.js → vennDiagram-CIIHVFJN-BLcoN4xU.js} +2 -2
  55. package/resources/fe-dist/assets/{wardley-L42UT6IY-4VhWbg10.js → wardley-L42UT6IY-OsbD5r1L.js} +2 -2
  56. package/resources/fe-dist/assets/{wardleyDiagram-YWT4CUSO-Bu4k6xIx.js → wardleyDiagram-YWT4CUSO-B_33zIif.js} +2 -2
  57. package/resources/fe-dist/assets/{xychartDiagram-2RQKCTM6-Cv88IDWh.js → xychartDiagram-2RQKCTM6-DcB27Ap1.js} +2 -2
  58. package/resources/fe-dist/index.html +2 -2
  59. package/resources/fe-dist/assets/DevicePage-fGI-do8S.js +0 -26
  60. package/resources/fe-dist/assets/DevicesPage-kGrzwhGj.js +0 -2
  61. package/resources/fe-dist/assets/channel-DbG6qNF6.js +0 -2
  62. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-rB1DgxOZ.js +0 -2
  63. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-rB1DgxOZ.js +0 -2
  64. package/resources/fe-dist/assets/index-cfY8KuW7.js +0 -466
  65. package/resources/fe-dist/assets/index-vcS0VPIW.css +0 -1
  66. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-CB5OePKf.js +0 -2
@@ -22550,7 +22550,7 @@ loadEnv();
22550
22550
 
22551
22551
  // src/runtime/server.ts
22552
22552
  import { existsSync as existsSync8 } from "fs";
22553
- import { extname as extname2, join as join7, normalize, resolve as resolve6, sep } from "path";
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-----",
@@ -23382,12 +23384,45 @@ Time: {{time}}`,
23382
23384
  unknown: "Failed to load"
23383
23385
  },
23384
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
+ },
23385
23422
  install: {
23386
23423
  button: "Install rsync",
23387
23424
  scopeLocal: "server",
23388
23425
  scopeRemote: "remote",
23389
- connectFailed: "Failed to connect to the device",
23390
- windowFailed: "Failed to create a window",
23391
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."
23392
23427
  }
23393
23428
  },
@@ -23487,6 +23522,8 @@ Time: {{time}}`,
23487
23522
  authKey: "\u79C1\u94A5",
23488
23523
  authAgent: "SSH Agent",
23489
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",
23490
23527
  password: "\u5BC6\u7801",
23491
23528
  privateKey: "\u79C1\u94A5",
23492
23529
  privateKeyPlaceholder: "-----BEGIN OPENSSH PRIVATE KEY-----",
@@ -24210,12 +24247,45 @@ Bot\uFF1A{{botName}}
24210
24247
  unknown: "\u52A0\u8F7D\u5931\u8D25"
24211
24248
  },
24212
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
+ },
24213
24285
  install: {
24214
24286
  button: "\u5B89\u88C5 rsync",
24215
24287
  scopeLocal: "\u670D\u52A1\u5668",
24216
24288
  scopeRemote: "\u8FDC\u7A0B",
24217
- connectFailed: "\u8FDE\u63A5\u8BBE\u5907\u5931\u8D25",
24218
- windowFailed: "\u521B\u5EFA\u7A97\u53E3\u5931\u8D25",
24219
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"
24220
24290
  }
24221
24291
  },
@@ -24315,6 +24385,8 @@ Bot\uFF1A{{botName}}
24315
24385
  authKey: "\u79D8\u5BC6\u9375",
24316
24386
  authAgent: "SSH Agent",
24317
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",
24318
24390
  password: "\u30D1\u30B9\u30EF\u30FC\u30C9",
24319
24391
  privateKey: "\u79D8\u5BC6\u9375",
24320
24392
  privateKeyPlaceholder: "-----BEGIN OPENSSH PRIVATE KEY-----",
@@ -25038,12 +25110,45 @@ Bot\uFF1A{{botName}}
25038
25110
  unknown: "\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
25039
25111
  },
25040
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
+ },
25041
25148
  install: {
25042
25149
  button: "rsync \u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB",
25043
25150
  scopeLocal: "\u30B5\u30FC\u30D0\u30FC",
25044
25151
  scopeRemote: "\u30EA\u30E2\u30FC\u30C8",
25045
- connectFailed: "\u30C7\u30D0\u30A4\u30B9\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25046
- windowFailed: "\u30A6\u30A3\u30F3\u30C9\u30A6\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25047
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"
25048
25153
  }
25049
25154
  },
@@ -27583,6 +27688,7 @@ var config = {
27583
27688
  baseUrl: getEnv("TMEX_BASE_URL", "http://127.0.0.1:8085"),
27584
27689
  siteNameDefault: getEnv("TMEX_SITE_NAME", "tmex"),
27585
27690
  databaseUrl: getEnv("DATABASE_URL", "./tmex.db"),
27691
+ transferMaxBytes: Number.parseInt(getEnv("TMEX_TRANSFER_MAX_BYTES", "2147483648"), 10),
27586
27692
  bellThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_BELL_THROTTLE_SECONDS", "6"), 10),
27587
27693
  notificationThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_NOTIFICATION_THROTTLE_SECONDS", "3"), 10),
27588
27694
  tmuxAllowPassthrough: getBooleanEnv("TMEX_TMUX_ALLOW_PASSTHROUGH", false),
@@ -86753,9 +86859,6 @@ function normalizeHttpUrl(input) {
86753
86859
  return null;
86754
86860
  }
86755
86861
  }
86756
- function encodePercentForTelegramUrl(url2) {
86757
- return url2.replace(/%/g, "%25");
86758
- }
86759
86862
 
86760
86863
  class EventNotifier {
86761
86864
  webhooks = [];
@@ -86899,8 +87002,7 @@ class EventNotifier {
86899
87002
  lines.push(...this.buildPaneMetaLines(event));
86900
87003
  const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
86901
87004
  if (paneUrl) {
86902
- const tgSafePaneUrl = encodePercentForTelegramUrl(paneUrl);
86903
- 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>`);
86904
87006
  }
86905
87007
  return lines.join(`
86906
87008
  `);
@@ -86920,8 +87022,7 @@ class EventNotifier {
86920
87022
  const topbarLabel = this.buildTerminalTopbarLabel(event);
86921
87023
  const footer = `from ${event.site.name}: ${topbarLabel}`;
86922
87024
  if (paneUrl) {
86923
- const tgSafePaneUrl = encodePercentForTelegramUrl(paneUrl);
86924
- lines.push("", `<a href="${escapeTelegramHtmlAttribute(tgSafePaneUrl)}">${escapeTelegramHtmlText(footer)}</a>`);
87025
+ lines.push("", `<a href="${escapeTelegramHtmlAttribute(paneUrl)}">${escapeTelegramHtmlText(footer)}</a>`);
86925
87026
  } else {
86926
87027
  lines.push("", escapeTelegramHtmlText(footer));
86927
87028
  }
@@ -86961,8 +87062,7 @@ class EventNotifier {
86961
87062
  }
86962
87063
  const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
86963
87064
  if (paneUrl) {
86964
- const tgSafePaneUrl = encodePercentForTelegramUrl(paneUrl);
86965
- lines.push("", `<a href="${escapeTelegramHtmlAttribute(tgSafePaneUrl)}">${esc2(t2("notification.directLink"))}</a>`);
87065
+ lines.push("", `<a href="${escapeTelegramHtmlAttribute(paneUrl)}">${esc2(t2("notification.directLink"))}</a>`);
86966
87066
  }
86967
87067
  return lines.join(`
86968
87068
  `);
@@ -95883,10 +95983,12 @@ class ConnectionAlertNotifier {
95883
95983
  const classified = classifySshError(errObj);
95884
95984
  const friendlyMessage = t2(classified.messageKey, { ...classified.messageParams });
95885
95985
  const rawMessage = errObj.message;
95986
+ const persistedMessage = rawMessage && !friendlyMessage.includes(rawMessage) ? `${friendlyMessage}
95987
+ ${rawMessage}` : friendlyMessage;
95886
95988
  console.error(`[conn-alert] device ${device.id} (${device.name}) source=${source} type=${classified.type}: ${rawMessage}`);
95887
95989
  if (persist) {
95888
95990
  try {
95889
- this.persister(device.id, friendlyMessage, classified.type);
95991
+ this.persister(device.id, persistedMessage, classified.type);
95890
95992
  } catch (dbErr) {
95891
95993
  console.error("[conn-alert] failed to persist runtime status:", dbErr);
95892
95994
  }
@@ -98251,7 +98353,7 @@ function resolveSshConfigRef(device, deps) {
98251
98353
  return parseSshConfigOutput(result.stdout, deps.env);
98252
98354
  }
98253
98355
  function resolveImplicitIdentityFilesForAgentAuth(device, host, port, username, deps) {
98254
- if (device.authMode !== "agent" || device.sshConfigRef?.trim()) {
98356
+ if (device.authMode !== "agent") {
98255
98357
  return [];
98256
98358
  }
98257
98359
  const target = username ? `${username}@${host}` : host;
@@ -98273,7 +98375,7 @@ async function resolveSshConnectConfig(device, decrypt2, inputDeps = {}) {
98273
98375
  readTextFile: inputDeps.readTextFile ?? ((path) => readFileSync2(path, "utf8"))
98274
98376
  };
98275
98377
  const sshEnv = toSshAuthEnv(deps.env);
98276
- const resolvedConfig = resolveSshConfigRef(device, deps);
98378
+ const resolvedConfig = device.authMode === "configRef" ? resolveSshConfigRef(device, deps) : null;
98277
98379
  const host = resolvedConfig?.host ?? device.host;
98278
98380
  const port = resolvedConfig?.port ?? device.port ?? 22;
98279
98381
  const username = resolvedConfig?.username ?? resolveSshUsername(device.username, device.authMode, sshEnv);
@@ -102871,7 +102973,7 @@ function deleteFileRoot(id) {
102871
102973
  }
102872
102974
 
102873
102975
  // ../../apps/gateway/src/files/device-storage.ts
102874
- 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";
102875
102977
  import { tmpdir as tmpdir2 } from "os";
102876
102978
  import { join as join5 } from "path";
102877
102979
 
@@ -103106,6 +103208,18 @@ function enqueueDeviceJob(deviceId, job) {
103106
103208
  }
103107
103209
 
103108
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
+
103109
103223
  class RsyncMissingLocalError extends Error {
103110
103224
  constructor(message) {
103111
103225
  super(message);
@@ -103127,8 +103241,36 @@ function baseSubprocessEnv() {
103127
103241
  out.LC_ALL = "C";
103128
103242
  return out;
103129
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
+ }
103130
103270
  async function runRsync(argv, opts = {}) {
103271
+ const streaming = typeof opts.onProgress === "function";
103131
103272
  const timeoutMs = opts.timeoutMs ?? 20000;
103273
+ const idleTimeoutMs = opts.idleTimeoutMs ?? 120000;
103132
103274
  let proc;
103133
103275
  try {
103134
103276
  proc = Bun.spawn(["rsync", ...argv], {
@@ -103141,21 +103283,36 @@ async function runRsync(argv, opts = {}) {
103141
103283
  throw new RsyncMissingLocalError(error51 instanceof Error ? error51.message : String(error51));
103142
103284
  }
103143
103285
  let timedOut = false;
103144
- const killTimer = setTimeout(() => {
103145
- timedOut = true;
103146
- try {
103147
- proc.kill();
103148
- } catch {}
103149
- }, timeoutMs);
103150
- const onAbort = () => {
103286
+ const killProc = () => {
103151
103287
  try {
103152
103288
  proc.kill();
103153
103289
  } catch {}
103154
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();
103155
103311
  opts.signal?.addEventListener("abort", onAbort, { once: true });
103156
103312
  try {
103313
+ const stdoutPromise = streaming ? readStdoutWithProgress(proc.stdout, opts.onProgress, resetIdle) : new Response(proc.stdout).text();
103157
103314
  const [stdout, stderr, exitCode] = await Promise.all([
103158
- new Response(proc.stdout).text(),
103315
+ stdoutPromise,
103159
103316
  new Response(proc.stderr).text(),
103160
103317
  proc.exited
103161
103318
  ]);
@@ -103165,7 +103322,8 @@ async function runRsync(argv, opts = {}) {
103165
103322
  }
103166
103323
  return { stdout, stderr, exitCode };
103167
103324
  } finally {
103168
- clearTimeout(killTimer);
103325
+ if (killTimer)
103326
+ clearTimeout(killTimer);
103169
103327
  opts.signal?.removeEventListener("abort", onAbort);
103170
103328
  }
103171
103329
  }
@@ -103347,17 +103505,25 @@ function rsyncListArgs(spec, remotePath) {
103347
103505
  return args;
103348
103506
  }
103349
103507
  function rsyncCopyArgs(spec, remotePath, dest) {
103350
- const args = ["-L"];
103508
+ const args = ["-L", "--progress"];
103351
103509
  if (spec.rsh)
103352
103510
  args.push("-e", spec.rsh);
103353
103511
  args.push(rsyncTargetArg(spec, remotePath), dest);
103354
103512
  return args;
103355
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
+ }
103356
103521
 
103357
103522
  // ../../apps/gateway/src/files/device-storage.ts
103358
103523
  var RAW_MAX_BYTES = 50 * 1024 * 1024;
103359
103524
  var LIST_TIMEOUT_MS = 20000;
103360
103525
  var COPY_TIMEOUT_MS = 60000;
103526
+ var TRANSFER_IDLE_TIMEOUT_MS = 120000;
103361
103527
  function ok(data) {
103362
103528
  return { ok: true, data };
103363
103529
  }
@@ -103667,8 +103833,241 @@ async function readRawFile(rootId, inputPath) {
103667
103833
  }
103668
103834
  });
103669
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
+ }
103670
104068
 
103671
104069
  // ../../apps/gateway/src/api/files.ts
104070
+ var UPLOAD_CHUNK_SIZE = 8 * 1024 * 1024;
103672
104071
  function json4(data, status = 200, headers = {}) {
103673
104072
  return new Response(JSON.stringify(data), {
103674
104073
  status,
@@ -103832,6 +104231,223 @@ async function handleRaw(url2) {
103832
104231
  }
103833
104232
  return new Response(result.data.data, { status: 200, headers });
103834
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
+ }
103835
104451
  function handleFilesApiRequest(req, path) {
103836
104452
  const url2 = new URL(req.url);
103837
104453
  if (path === "/api/files/roots" && req.method === "GET")
@@ -103852,6 +104468,32 @@ function handleFilesApiRequest(req, path) {
103852
104468
  return handleStat(url2);
103853
104469
  if (path === "/api/files/raw" && req.method === "GET")
103854
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
+ }
103855
104497
  return;
103856
104498
  }
103857
104499
 
@@ -104208,8 +104850,8 @@ function getBaseVersion() {
104208
104850
  if (cachedBase !== undefined)
104209
104851
  return cachedBase;
104210
104852
  let base = null;
104211
- if ("0.11.0") {
104212
- base = "0.11.0";
104853
+ if ("0.12.0") {
104854
+ base = "0.12.0";
104213
104855
  }
104214
104856
  if (!base && config.isProd) {
104215
104857
  base = readInstallMeta()?.cliVersion ?? null;
@@ -104300,8 +104942,8 @@ async function fetchChangelog(version3) {
104300
104942
  import { spawn } from "child_process";
104301
104943
  import { closeSync, existsSync as existsSync6, openSync } from "fs";
104302
104944
  import { mkdtemp, rm, writeFile } from "fs/promises";
104303
- import { tmpdir as tmpdir3 } from "os";
104304
- import { join as join6 } from "path";
104945
+ import { tmpdir as tmpdir4 } from "os";
104946
+ import { join as join7 } from "path";
104305
104947
  class UpgradeController {
104306
104948
  state = "idle";
104307
104949
  targetVersion = null;
@@ -104337,12 +104979,12 @@ class UpgradeController {
104337
104979
  if (!installDir) {
104338
104980
  throw new Error("install directory could not be resolved");
104339
104981
  }
104340
- stageDir = await mkdtemp(join6(tmpdir3(), "tmex-upg-"));
104341
- cacheDir = await mkdtemp(join6(tmpdir3(), "tmex-upg-cache-"));
104342
- await writeFile(join6(stageDir, "package.json"), `${JSON.stringify({ name: "tmex-upgrade-stage", private: true })}
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 })}
104343
104985
  `);
104344
104986
  await this.runBunAdd(stageDir, cacheDir, version3);
104345
- const binPath = join6(stageDir, "node_modules", "tmex-cli", "bin", "tmex.js");
104987
+ const binPath = join7(stageDir, "node_modules", "tmex-cli", "bin", "tmex.js");
104346
104988
  if (!existsSync6(binPath)) {
104347
104989
  throw new Error(`downloaded tmex-cli binary not found at ${binPath}`);
104348
104990
  }
@@ -104377,7 +105019,7 @@ class UpgradeController {
104377
105019
  spawnUpgrade(binPath, installDir, version3) {
104378
105020
  let logFd = null;
104379
105021
  try {
104380
- logFd = openSync(join6(installDir, "upgrade.log"), "a");
105022
+ logFd = openSync(join7(installDir, "upgrade.log"), "a");
104381
105023
  } catch {
104382
105024
  logFd = null;
104383
105025
  }
@@ -104413,7 +105055,8 @@ function getSystemInfo() {
104413
105055
  installedViaCli: install.installedViaCli,
104414
105056
  deployment: install.deployment,
104415
105057
  canSelfUpdate,
104416
- serviceName: install.serviceName
105058
+ serviceName: install.serviceName,
105059
+ transferMaxBytes: config.transferMaxBytes
104417
105060
  };
104418
105061
  }
104419
105062
 
@@ -107949,6 +108592,7 @@ async function createGatewayRuntime(options = {}) {
107949
108592
  }
107950
108593
  runtimeController.reset();
107951
108594
  primeLocalShellPath();
108595
+ sweepOrphanTransferTemps();
107952
108596
  const wsServer = new WebSocketServer;
107953
108597
  connectionAlertNotifier.setBroadcaster((deviceId, payload) => {
107954
108598
  wsServer.broadcastDeviceError(deviceId, payload);
@@ -108274,7 +108918,7 @@ async function serveFrontend(req, staticRoot) {
108274
108918
  if (!existsSync8(requestedPath) && extname2(url2.pathname) !== "") {
108275
108919
  return new Response(t3("runtime.notFound"), { status: 404 });
108276
108920
  }
108277
- const indexPath = join7(staticRoot, "index.html");
108921
+ const indexPath = join8(staticRoot, "index.html");
108278
108922
  const targetPath = existsSync8(requestedPath) ? requestedPath : indexPath;
108279
108923
  if (!existsSync8(targetPath)) {
108280
108924
  return new Response(t3("runtime.frontendMissing"), { status: 500 });