tmex-cli 0.16.2 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +6 -18
  2. package/dist/runtime/server.js +826 -243
  3. package/package.json +1 -1
  4. package/resources/fe-dist/assets/DevicePage-DxFUn3eM.js +24 -0
  5. package/resources/fe-dist/assets/{DevicesPage-B_jvUZtl.js → DevicesPage-CJF229QB.js} +1 -1
  6. package/resources/fe-dist/assets/{FilePage-DTSmWM1D.js → FilePage-CkEaJiW3.js} +1 -1
  7. package/resources/fe-dist/assets/{SettingsPage-LTHV37Rj.js → SettingsPage-BjsRYOBG.js} +4 -4
  8. package/resources/fe-dist/assets/{agent-tab-vPjPGa5f.js → agent-tab-6MAsxcE4.js} +1 -1
  9. package/resources/fe-dist/assets/{api-D_FToAy0.js → api-B3b18azE.js} +1 -1
  10. package/resources/fe-dist/assets/{arc-BSCyoyGW.js → arc-BAINpc3Q.js} +1 -1
  11. package/resources/fe-dist/assets/{architectureDiagram-3BPJPVTR-CBO0dAe2.js → architectureDiagram-3BPJPVTR-CWFap8y8.js} +1 -1
  12. package/resources/fe-dist/assets/{blockDiagram-GPEHLZMM-0ONANm30.js → blockDiagram-GPEHLZMM-BnR3WVDx.js} +1 -1
  13. package/resources/fe-dist/assets/{c4Diagram-AAUBKEIU-DpvhCnF8.js → c4Diagram-AAUBKEIU-BT4ZOccY.js} +1 -1
  14. package/resources/fe-dist/assets/{card-CD9i-fLq.js → card-TMGETMIE.js} +1 -1
  15. package/resources/fe-dist/assets/channel-C_D-Pin5.js +1 -0
  16. package/resources/fe-dist/assets/{chunk-2J33WTMH-MmS9_ur_.js → chunk-2J33WTMH-B4Tvbi6K.js} +1 -1
  17. package/resources/fe-dist/assets/{chunk-4BX2VUAB-CLd4Yxwh.js → chunk-4BX2VUAB-e7mC2fAZ.js} +1 -1
  18. package/resources/fe-dist/assets/{chunk-55IACEB6-DgCH4WwT.js → chunk-55IACEB6-CIYZgFr_.js} +1 -1
  19. package/resources/fe-dist/assets/{chunk-727SXJPM-B9DM5PB4.js → chunk-727SXJPM-Cp8xQ_Up.js} +1 -1
  20. package/resources/fe-dist/assets/{chunk-AQP2D5EJ-DQwC9D9W.js → chunk-AQP2D5EJ-C-NtHCtH.js} +1 -1
  21. package/resources/fe-dist/assets/{chunk-FMBD7UC4-BnvyNYGh.js → chunk-FMBD7UC4-DmjdaPSW.js} +1 -1
  22. package/resources/fe-dist/assets/{chunk-ND2GUHAM-BzcZgEWG.js → chunk-ND2GUHAM-Zg5krkW_.js} +1 -1
  23. package/resources/fe-dist/assets/{chunk-QZHKN3VN-C9BaHIUI.js → chunk-QZHKN3VN-BIuK8Di1.js} +1 -1
  24. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-BKAkOexI.js +1 -0
  25. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-BKAkOexI.js +1 -0
  26. package/resources/fe-dist/assets/{copy-DzIk8AxV.js → copy-BfbLkQQH.js} +1 -1
  27. package/resources/fe-dist/assets/{cose-bilkent-S5V4N54A-NkTc0ObP.js → cose-bilkent-S5V4N54A-DjsBN2Gw.js} +1 -1
  28. package/resources/fe-dist/assets/{dagre-BM42HDAG-BdFDBfE_.js → dagre-BM42HDAG-EHYEOgd4.js} +1 -1
  29. package/resources/fe-dist/assets/{diagram-2AECGRRQ-Ba4Y0igR.js → diagram-2AECGRRQ-DP2qaS44.js} +1 -1
  30. package/resources/fe-dist/assets/{diagram-5GNKFQAL-B4_Or8Vp.js → diagram-5GNKFQAL-CXjsd6rW.js} +1 -1
  31. package/resources/fe-dist/assets/{diagram-KO2AKTUF-DbmmHTYj.js → diagram-KO2AKTUF-C3imvzg0.js} +1 -1
  32. package/resources/fe-dist/assets/{diagram-LMA3HP47-CRrd6lc4.js → diagram-LMA3HP47-BWfVIlqx.js} +1 -1
  33. package/resources/fe-dist/assets/{diagram-OG6HWLK6-BOEazFIu.js → diagram-OG6HWLK6-DjPzNtRm.js} +1 -1
  34. package/resources/fe-dist/assets/{erDiagram-TEJ5UH35-Dk-UowK8.js → erDiagram-TEJ5UH35-B0NYaEtR.js} +1 -1
  35. package/resources/fe-dist/assets/{files-tab-D2vddvsj.js → files-tab-BFIlXST7.js} +1 -1
  36. package/resources/fe-dist/assets/{flowDiagram-I6XJVG4X-BhbGYx-g.js → flowDiagram-I6XJVG4X-CdStxlad.js} +1 -1
  37. package/resources/fe-dist/assets/{ganttDiagram-6RSMTGT7-iezClDTv.js → ganttDiagram-6RSMTGT7-DdEyq_eg.js} +1 -1
  38. package/resources/fe-dist/assets/{gitGraphDiagram-PVQCEYII-Ncqf3CQw.js → gitGraphDiagram-PVQCEYII-DVt7acpU.js} +1 -1
  39. package/resources/fe-dist/assets/{index-UoApkLSY.js → index-C1akMMWP.js} +63 -63
  40. package/resources/fe-dist/assets/{index-DOfY8kwB.js → index-c9jmHxDX.js} +1 -1
  41. package/resources/fe-dist/assets/{infoDiagram-5YYISTIA-CUg3PkAb.js → infoDiagram-5YYISTIA-BfBQlpuG.js} +1 -1
  42. package/resources/fe-dist/assets/{ishikawaDiagram-YF4QCWOH-DU0ekJw5.js → ishikawaDiagram-YF4QCWOH-DUn8fG3F.js} +1 -1
  43. package/resources/fe-dist/assets/{journeyDiagram-JHISSGLW-Ji7WtMH7.js → journeyDiagram-JHISSGLW-D1YteFVs.js} +1 -1
  44. package/resources/fe-dist/assets/{kanban-definition-UN3LZRKU-B8zVyM8Q.js → kanban-definition-UN3LZRKU-Do0o8nlT.js} +1 -1
  45. package/resources/fe-dist/assets/{linear-tNPPicfI.js → linear-Bmap7qQK.js} +1 -1
  46. package/resources/fe-dist/assets/{markdown-preview-CzbcKxcJ.js → markdown-preview-ByBN48Ac.js} +3 -3
  47. package/resources/fe-dist/assets/{mermaid.core-BoNU6G1d.js → mermaid.core-CloKtMOb.js} +5 -5
  48. package/resources/fe-dist/assets/{mindmap-definition-RKZ34NQL-D_v1LaNP.js → mindmap-definition-RKZ34NQL-BFPgS54r.js} +1 -1
  49. package/resources/fe-dist/assets/{pieDiagram-4H26LBE5-B1CHCpFf.js → pieDiagram-4H26LBE5-DwY4DDxv.js} +1 -1
  50. package/resources/fe-dist/assets/{quadrantDiagram-W4KKPZXB-DvA3zgR7.js → quadrantDiagram-W4KKPZXB-3jSLHV3z.js} +1 -1
  51. package/resources/fe-dist/assets/{requirementDiagram-4Y6WPE33-BCna6ZG5.js → requirementDiagram-4Y6WPE33-DHrqJmHL.js} +1 -1
  52. package/resources/fe-dist/assets/{sankeyDiagram-5OEKKPKP-KEllFLAf.js → sankeyDiagram-5OEKKPKP-CIaPmx-2.js} +1 -1
  53. package/resources/fe-dist/assets/{send-BgCF67Uc.js → send-CAJeq7VS.js} +1 -1
  54. package/resources/fe-dist/assets/{sequenceDiagram-3UESZ5HK-DRsq_jg_.js → sequenceDiagram-3UESZ5HK-DhRU1sO_.js} +1 -1
  55. package/resources/fe-dist/assets/{stateDiagram-AJRCARHV-DhATBo8D.js → stateDiagram-AJRCARHV-DZWC0te0.js} +1 -1
  56. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-BJcIn84l.js +1 -0
  57. package/resources/fe-dist/assets/{terminal-settings-panel-DsMMZFBi.js → terminal-settings-panel-BiDSWGtf.js} +2 -2
  58. package/resources/fe-dist/assets/{timeline-definition-PNZ67QCA-Det7AzPd.js → timeline-definition-PNZ67QCA-Y0cAlUby.js} +1 -1
  59. package/resources/fe-dist/assets/{transfer-toast-CHcCj3qf.js → transfer-toast-yJBQc29j.js} +1 -1
  60. package/resources/fe-dist/assets/{triangle-alert-BjHP6Ipw.js → triangle-alert-Bs89Dzy0.js} +1 -1
  61. package/resources/fe-dist/assets/{vennDiagram-CIIHVFJN-BF4R466L.js → vennDiagram-CIIHVFJN-BVWElA6Q.js} +1 -1
  62. package/resources/fe-dist/assets/{wardley-L42UT6IY-DLHZ_6-V.js → wardley-L42UT6IY-BBf8FmIs.js} +1 -1
  63. package/resources/fe-dist/assets/{wardleyDiagram-YWT4CUSO-CekN3Ye6.js → wardleyDiagram-YWT4CUSO-Cyk4va1x.js} +1 -1
  64. package/resources/fe-dist/assets/{xychartDiagram-2RQKCTM6-CJD3yg0M.js → xychartDiagram-2RQKCTM6-DmEvYdFT.js} +1 -1
  65. package/resources/fe-dist/assets/{zap-Dx7JTXJN.js → zap-B28XlJER.js} +1 -1
  66. package/resources/fe-dist/index.html +1 -1
  67. package/resources/gateway-drizzle/0014_lucky_killraven.sql +1 -0
  68. package/resources/gateway-drizzle/meta/_journal.json +7 -0
  69. package/resources/fe-dist/assets/DevicePage-CgDkuNLp.js +0 -24
  70. package/resources/fe-dist/assets/channel-DBhb6_En.js +0 -1
  71. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-9V2iXOG7.js +0 -1
  72. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-9V2iXOG7.js +0 -1
  73. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-Dn_gMFVD.js +0 -1
@@ -25668,6 +25668,8 @@ __export(exports_ws_borsh, {
25668
25668
  WATCH_EVENT_TRIGGERED: () => WATCH_EVENT_TRIGGERED,
25669
25669
  WATCH_EVENT_RULE_ERROR: () => WATCH_EVENT_RULE_ERROR,
25670
25670
  WATCH_EVENT_MODEL_UNAVAILABLE: () => WATCH_EVENT_MODEL_UNAVAILABLE,
25671
+ SITE_THEME_LIGHT: () => SITE_THEME_LIGHT,
25672
+ SITE_THEME_DARK: () => SITE_THEME_DARK,
25671
25673
  MAX_CHUNK_STREAMS: () => MAX_CHUNK_STREAMS,
25672
25674
  MAX_CHUNKS_PER_MESSAGE: () => MAX_CHUNKS_PER_MESSAGE,
25673
25675
  MAGIC: () => MAGIC,
@@ -25700,6 +25702,7 @@ __export(exports_ws_borsh, {
25700
25702
  KIND_SWITCH_ACK: () => KIND_SWITCH_ACK,
25701
25703
  KIND_STATE_SNAPSHOT_DIFF: () => KIND_STATE_SNAPSHOT_DIFF,
25702
25704
  KIND_STATE_SNAPSHOT: () => KIND_STATE_SNAPSHOT,
25705
+ KIND_SITE_THEME_UPDATE: () => KIND_SITE_THEME_UPDATE,
25703
25706
  KIND_PONG: () => KIND_PONG,
25704
25707
  KIND_PING: () => KIND_PING,
25705
25708
  KIND_LIVE_RESUME: () => KIND_LIVE_RESUME,
@@ -25800,6 +25803,7 @@ var KIND_AGENT_SUBSCRIBE = 1537;
25800
25803
  var KIND_AGENT_UNSUBSCRIBE = 1538;
25801
25804
  var KIND_AGENT_EVENT = 1539;
25802
25805
  var KIND_WATCH_EVENT = 1793;
25806
+ var KIND_SITE_THEME_UPDATE = 2049;
25803
25807
  var VALID_KINDS = new Set([
25804
25808
  KIND_HELLO_C2S,
25805
25809
  KIND_HELLO_S2C,
@@ -25845,7 +25849,8 @@ var VALID_KINDS = new Set([
25845
25849
  KIND_AGENT_SUBSCRIBE,
25846
25850
  KIND_AGENT_UNSUBSCRIBE,
25847
25851
  KIND_AGENT_EVENT,
25848
- KIND_WATCH_EVENT
25852
+ KIND_WATCH_EVENT,
25853
+ KIND_SITE_THEME_UPDATE
25849
25854
  ]);
25850
25855
  function isValidKind(kind) {
25851
25856
  return VALID_KINDS.has(kind);
@@ -25896,68 +25901,11 @@ function kindToString(kind) {
25896
25901
  [KIND_AGENT_SUBSCRIBE]: "AGENT_SUBSCRIBE",
25897
25902
  [KIND_AGENT_UNSUBSCRIBE]: "AGENT_UNSUBSCRIBE",
25898
25903
  [KIND_AGENT_EVENT]: "AGENT_EVENT",
25899
- [KIND_WATCH_EVENT]: "WATCH_EVENT"
25904
+ [KIND_WATCH_EVENT]: "WATCH_EVENT",
25905
+ [KIND_SITE_THEME_UPDATE]: "SITE_THEME_UPDATE"
25900
25906
  };
25901
25907
  return kindMap[kind] ?? `UNKNOWN(0x${kind.toString(16).padStart(4, "0")})`;
25902
25908
  }
25903
- // ../shared/src/ws-borsh/agent.ts
25904
- var AGENT_EVENT_SYNC = 1;
25905
- var AGENT_EVENT_STATUS = 2;
25906
- var AGENT_EVENT_TEXT_DELTA = 3;
25907
- var AGENT_EVENT_REASONING_DELTA = 4;
25908
- var AGENT_EVENT_TOOL_CALL = 5;
25909
- var AGENT_EVENT_TOOL_RESULT = 6;
25910
- var AGENT_EVENT_CONFIRMATION_REQUEST = 7;
25911
- var AGENT_EVENT_CONFIRMATION_RESOLVED = 8;
25912
- var AGENT_EVENT_MESSAGE_PERSISTED = 9;
25913
- var AGENT_EVENT_ERROR = 10;
25914
- var AGENT_EVENT_TURN_FINISHED = 11;
25915
- var AGENT_EVENT_CREDENTIAL_WARNING = 12;
25916
- var AGENT_EVENT_QUEUE_UPDATED = 13;
25917
- var WATCH_EVENT_TRIGGERED = 1;
25918
- var WATCH_EVENT_MODEL_UNAVAILABLE = 2;
25919
- var WATCH_EVENT_RULE_ERROR = 3;
25920
- // ../shared/src/ws-borsh/errors.ts
25921
- var ERROR_UNSUPPORTED_PROTOCOL = 1001;
25922
- var ERROR_INVALID_FRAME = 1002;
25923
- var ERROR_UNKNOWN_KIND = 1003;
25924
- var ERROR_PAYLOAD_DECODE_FAILED = 1004;
25925
- var ERROR_FRAME_TOO_LARGE = 1005;
25926
- var ERROR_DEVICE_NOT_FOUND = 1101;
25927
- var ERROR_DEVICE_CONNECT_FAILED = 1102;
25928
- var ERROR_TMUX_TARGET_NOT_FOUND = 1201;
25929
- var ERROR_TMUX_NOT_READY = 1202;
25930
- var ERROR_SELECT_CONFLICT = 1301;
25931
- var ERROR_SELECT_TOKEN_MISMATCH = 1302;
25932
- var ERROR_INTERNAL_ERROR = 1401;
25933
- var ERROR_MESSAGES = {
25934
- [ERROR_UNSUPPORTED_PROTOCOL]: "Unsupported protocol version",
25935
- [ERROR_INVALID_FRAME]: "Invalid frame format",
25936
- [ERROR_UNKNOWN_KIND]: "Unknown message kind",
25937
- [ERROR_PAYLOAD_DECODE_FAILED]: "Failed to decode payload",
25938
- [ERROR_FRAME_TOO_LARGE]: "Frame exceeds maximum size",
25939
- [ERROR_DEVICE_NOT_FOUND]: "Device not found",
25940
- [ERROR_DEVICE_CONNECT_FAILED]: "Failed to connect device",
25941
- [ERROR_TMUX_TARGET_NOT_FOUND]: "Tmux target not found",
25942
- [ERROR_TMUX_NOT_READY]: "Tmux not ready",
25943
- [ERROR_SELECT_CONFLICT]: "Select conflict",
25944
- [ERROR_SELECT_TOKEN_MISMATCH]: "Select token mismatch",
25945
- [ERROR_INTERNAL_ERROR]: "Internal server error"
25946
- };
25947
- function getErrorMessage(code) {
25948
- return ERROR_MESSAGES[code] ?? `Unknown error code: ${code}`;
25949
- }
25950
-
25951
- class WsBorshError extends Error {
25952
- code;
25953
- retryable;
25954
- constructor(code, retryable = false, message) {
25955
- super(message ?? getErrorMessage(code));
25956
- this.code = code;
25957
- this.retryable = retryable;
25958
- this.name = "WsBorshError";
25959
- }
25960
- }
25961
25909
  // ../shared/src/ws-borsh/schema.ts
25962
25910
  var exports_schema = {};
25963
25911
  __export(exports_schema, {
@@ -25995,7 +25943,11 @@ __export(exports_schema, {
25995
25943
  SwitchAckSchema: () => SwitchAckSchema,
25996
25944
  StateSnapshotSchema: () => StateSnapshotSchema,
25997
25945
  StateSnapshotDiffSchema: () => StateSnapshotDiffSchema,
25946
+ SiteThemeUpdateS2CSchema: () => SiteThemeUpdateS2CSchema,
25947
+ SiteThemeUpdateC2SSchema: () => SiteThemeUpdateC2SSchema,
25998
25948
  SessionWireSchema: () => SessionWireSchema,
25949
+ SITE_THEME_LIGHT: () => SITE_THEME_LIGHT,
25950
+ SITE_THEME_DARK: () => SITE_THEME_DARK,
25999
25951
  PingPongSchema: () => PingPongSchema,
26000
25952
  PaneWireSchema: () => PaneWireSchema,
26001
25953
  PaneCloseEventSchema: () => PaneCloseEventSchema,
@@ -26341,6 +26293,73 @@ var NotificationEventSchema = import_zorsh.b.struct({
26341
26293
  paneTitle: OptionStringSchema,
26342
26294
  paneCurrentCommand: OptionStringSchema
26343
26295
  });
26296
+ var SITE_THEME_DARK = 0;
26297
+ var SITE_THEME_LIGHT = 1;
26298
+ var SiteThemeUpdateC2SSchema = import_zorsh.b.struct({
26299
+ theme: import_zorsh.b.u8()
26300
+ });
26301
+ var SiteThemeUpdateS2CSchema = import_zorsh.b.struct({
26302
+ theme: import_zorsh.b.u8(),
26303
+ serverTimestamp: import_zorsh.b.u64()
26304
+ });
26305
+ // ../shared/src/ws-borsh/agent.ts
26306
+ var AGENT_EVENT_SYNC = 1;
26307
+ var AGENT_EVENT_STATUS = 2;
26308
+ var AGENT_EVENT_TEXT_DELTA = 3;
26309
+ var AGENT_EVENT_REASONING_DELTA = 4;
26310
+ var AGENT_EVENT_TOOL_CALL = 5;
26311
+ var AGENT_EVENT_TOOL_RESULT = 6;
26312
+ var AGENT_EVENT_CONFIRMATION_REQUEST = 7;
26313
+ var AGENT_EVENT_CONFIRMATION_RESOLVED = 8;
26314
+ var AGENT_EVENT_MESSAGE_PERSISTED = 9;
26315
+ var AGENT_EVENT_ERROR = 10;
26316
+ var AGENT_EVENT_TURN_FINISHED = 11;
26317
+ var AGENT_EVENT_CREDENTIAL_WARNING = 12;
26318
+ var AGENT_EVENT_QUEUE_UPDATED = 13;
26319
+ var WATCH_EVENT_TRIGGERED = 1;
26320
+ var WATCH_EVENT_MODEL_UNAVAILABLE = 2;
26321
+ var WATCH_EVENT_RULE_ERROR = 3;
26322
+ // ../shared/src/ws-borsh/errors.ts
26323
+ var ERROR_UNSUPPORTED_PROTOCOL = 1001;
26324
+ var ERROR_INVALID_FRAME = 1002;
26325
+ var ERROR_UNKNOWN_KIND = 1003;
26326
+ var ERROR_PAYLOAD_DECODE_FAILED = 1004;
26327
+ var ERROR_FRAME_TOO_LARGE = 1005;
26328
+ var ERROR_DEVICE_NOT_FOUND = 1101;
26329
+ var ERROR_DEVICE_CONNECT_FAILED = 1102;
26330
+ var ERROR_TMUX_TARGET_NOT_FOUND = 1201;
26331
+ var ERROR_TMUX_NOT_READY = 1202;
26332
+ var ERROR_SELECT_CONFLICT = 1301;
26333
+ var ERROR_SELECT_TOKEN_MISMATCH = 1302;
26334
+ var ERROR_INTERNAL_ERROR = 1401;
26335
+ var ERROR_MESSAGES = {
26336
+ [ERROR_UNSUPPORTED_PROTOCOL]: "Unsupported protocol version",
26337
+ [ERROR_INVALID_FRAME]: "Invalid frame format",
26338
+ [ERROR_UNKNOWN_KIND]: "Unknown message kind",
26339
+ [ERROR_PAYLOAD_DECODE_FAILED]: "Failed to decode payload",
26340
+ [ERROR_FRAME_TOO_LARGE]: "Frame exceeds maximum size",
26341
+ [ERROR_DEVICE_NOT_FOUND]: "Device not found",
26342
+ [ERROR_DEVICE_CONNECT_FAILED]: "Failed to connect device",
26343
+ [ERROR_TMUX_TARGET_NOT_FOUND]: "Tmux target not found",
26344
+ [ERROR_TMUX_NOT_READY]: "Tmux not ready",
26345
+ [ERROR_SELECT_CONFLICT]: "Select conflict",
26346
+ [ERROR_SELECT_TOKEN_MISMATCH]: "Select token mismatch",
26347
+ [ERROR_INTERNAL_ERROR]: "Internal server error"
26348
+ };
26349
+ function getErrorMessage(code) {
26350
+ return ERROR_MESSAGES[code] ?? `Unknown error code: ${code}`;
26351
+ }
26352
+
26353
+ class WsBorshError extends Error {
26354
+ code;
26355
+ retryable;
26356
+ constructor(code, retryable = false, message) {
26357
+ super(message ?? getErrorMessage(code));
26358
+ this.code = code;
26359
+ this.retryable = retryable;
26360
+ this.name = "WsBorshError";
26361
+ }
26362
+ }
26344
26363
  // ../shared/src/ws-borsh/codec.ts
26345
26364
  var MAGIC = new Uint8Array([84, 88]);
26346
26365
  var CURRENT_VERSION = 1;
@@ -26860,6 +26879,59 @@ function decodePaneWire(wire) {
26860
26879
  top: wire.top ?? undefined
26861
26880
  };
26862
26881
  }
26882
+ // ../shared/src/appearance.ts
26883
+ var TERMINAL_THEME_LIGHT = {
26884
+ background: "#e1e1e1",
26885
+ foreground: "#616161",
26886
+ cursor: "#616161",
26887
+ selectionBackground: "rgba(97, 97, 97, 0.25)",
26888
+ black: "#171717",
26889
+ red: "#bf2172",
26890
+ green: "#009799",
26891
+ yellow: "#9a7200",
26892
+ blue: "#007299",
26893
+ magenta: "#9b1d72",
26894
+ cyan: "#007173",
26895
+ white: "#d9d9d9",
26896
+ brightBlack: "#4e4e4e",
26897
+ brightRed: "#e12672",
26898
+ brightGreen: "#00bddf",
26899
+ brightYellow: "#ffdd00",
26900
+ brightBlue: "#7299bc",
26901
+ brightMagenta: "#e17899",
26902
+ brightCyan: "#6fbcbd",
26903
+ brightWhite: "#f1f1f1"
26904
+ };
26905
+ var TERMINAL_THEME_DARK = {
26906
+ background: "#262626",
26907
+ foreground: "#d0d0d0",
26908
+ cursor: "#c5c5c5",
26909
+ selectionBackground: "rgba(197, 197, 197, 0.25)",
26910
+ black: "#000000",
26911
+ red: "#ba3c3c",
26912
+ green: "#5d876d",
26913
+ yellow: "#d5a54e",
26914
+ blue: "#887c8d",
26915
+ magenta: "#cd6d6d",
26916
+ cyan: "#618484",
26917
+ white: "#cfcdc3",
26918
+ brightBlack: "#000000",
26919
+ brightRed: "#ea7171",
26920
+ brightGreen: "#7aab7a",
26921
+ brightYellow: "#d1d194",
26922
+ brightBlue: "#afa3b5",
26923
+ brightMagenta: "#e29f9f",
26924
+ brightCyan: "#a0aea3",
26925
+ brightWhite: "#d0d0d0"
26926
+ };
26927
+ function getTerminalTheme(name) {
26928
+ return name === "light" ? TERMINAL_THEME_LIGHT : TERMINAL_THEME_DARK;
26929
+ }
26930
+ function getTmuxWindowStyle(theme) {
26931
+ const colors = getTerminalTheme(theme);
26932
+ return `fg=${colors.foreground},bg=${colors.background}`;
26933
+ }
26934
+
26863
26935
  // ../shared/src/index.ts
26864
26936
  var TERMINAL_SHORTCUT_ACTIONS = [
26865
26937
  "paste",
@@ -28304,6 +28376,7 @@ var config = {
28304
28376
  bellThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_BELL_THROTTLE_SECONDS", "6"), 10),
28305
28377
  notificationThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_NOTIFICATION_THROTTLE_SECONDS", "3"), 10),
28306
28378
  tmuxAllowPassthrough: getBooleanEnv("TMEX_TMUX_ALLOW_PASSTHROUGH", false),
28379
+ themeNotify2031Enabled: getBooleanEnv("TMEX_THEME_NOTIFY_2031", true),
28307
28380
  tmuxTermProgram: getEnv("TMEX_TMUX_TERM_PROGRAM", "ghostty"),
28308
28381
  tmuxWindowStyle: getEnv("TMEX_TMUX_WINDOW_STYLE", "fg=#d0d0d0,bg=#262626"),
28309
28382
  tmuxSocket: getEnv("TMEX_TMUX_SOCKET", ""),
@@ -33329,8 +33402,12 @@ var siteSettings = sqliteTable("site_settings", {
33329
33402
  sshReconnectMaxRetries: integer("ssh_reconnect_max_retries").notNull(),
33330
33403
  sshReconnectDelaySeconds: integer("ssh_reconnect_delay_seconds").notNull(),
33331
33404
  language: text("language").notNull().default("en_US"),
33405
+ theme: text("theme").notNull().default("dark"),
33332
33406
  updatedAt: text("updated_at").notNull()
33333
- }, (table) => [check("site_settings_singleton_check", sql`${table.id} = 1`)]);
33407
+ }, (table) => [
33408
+ check("site_settings_singleton_check", sql`${table.id} = 1`),
33409
+ check("site_settings_theme_check", sql`${table.theme} in ('dark', 'light')`)
33410
+ ]);
33334
33411
  var terminalShortcutSettings = sqliteTable("terminal_shortcut_settings", {
33335
33412
  id: integer("id").primaryKey(),
33336
33413
  items: text("items", { mode: "json" }).$type().notNull().default(DEFAULT_TERMINAL_SHORTCUTS),
@@ -33624,6 +33701,7 @@ function toSiteSettings(row) {
33624
33701
  sshReconnectMaxRetries: row.sshReconnectMaxRetries,
33625
33702
  sshReconnectDelaySeconds: row.sshReconnectDelaySeconds,
33626
33703
  language: normalizeLocale(row.language),
33704
+ theme: row.theme,
33627
33705
  updatedAt: row.updatedAt
33628
33706
  };
33629
33707
  }
@@ -33917,6 +33995,7 @@ function updateSiteSettings(updates) {
33917
33995
  sshReconnectMaxRetries: updates.sshReconnectMaxRetries ?? current.sshReconnectMaxRetries,
33918
33996
  sshReconnectDelaySeconds: updates.sshReconnectDelaySeconds ?? current.sshReconnectDelaySeconds,
33919
33997
  language: updates.language ? normalizeLocale(updates.language) : current.language,
33998
+ theme: updates.theme ?? current.theme,
33920
33999
  updatedAt: new Date().toISOString()
33921
34000
  };
33922
34001
  const orm = getDb();
@@ -33928,7 +34007,8 @@ function updateSiteSettings(updates) {
33928
34007
  enableBrowserNotificationToast: next.enableBrowserNotificationToast,
33929
34008
  enableNotificationPush: next.enableNotificationPush,
33930
34009
  enableBellPush: next.enableBellPush,
33931
- enableBellSound: next.enableBellSound
34010
+ enableBellSound: next.enableBellSound,
34011
+ theme: next.theme
33932
34012
  }).where(eq(siteSettings.id, 1)).run();
33933
34013
  siteSettingsCache = { value: next, expiresAt: Date.now() + SITE_SETTINGS_TTL_MS };
33934
34014
  if (instance.language !== next.language) {
@@ -98354,7 +98434,9 @@ var MAX_OSC_KIND_BYTES = 16;
98354
98434
  var MAX_OSC_PAYLOAD_BYTES = 8 * 1024;
98355
98435
  var MAX_DCS_PASSTHROUGH_BYTES = 64 * 1024;
98356
98436
  var MAX_KITTY_PENDING_IDS = 16;
98437
+ var MAX_CSI_BYTES = 64;
98357
98438
  var TMUX_PASSTHROUGH_PREFIX = "tmux;";
98439
+ var THEME_UPDATES_MODE = "2031";
98358
98440
  function createPaneStreamParser(options) {
98359
98441
  let phase = "normal";
98360
98442
  let oscKind = "";
@@ -98364,6 +98446,8 @@ function createPaneStreamParser(options) {
98364
98446
  let warnedDcsOverflow = false;
98365
98447
  let dcsPrefix = "";
98366
98448
  let dcsBytes = [];
98449
+ let csiBytes = [];
98450
+ let inTmuxPassthrough = false;
98367
98451
  const kittyPending = new Map;
98368
98452
  function resetOscState() {
98369
98453
  oscKind = "";
@@ -98513,8 +98597,19 @@ function createPaneStreamParser(options) {
98513
98597
  dcsBytes = [];
98514
98598
  dcsPrefix = "";
98515
98599
  phase = "normal";
98516
- for (const byte of content) {
98517
- processByte(byte);
98600
+ inTmuxPassthrough = true;
98601
+ try {
98602
+ for (const byte of content) {
98603
+ processByte(byte);
98604
+ }
98605
+ } finally {
98606
+ inTmuxPassthrough = false;
98607
+ }
98608
+ const phaseAfterFlush = phase;
98609
+ if (phaseAfterFlush === "csi") {
98610
+ output.push(27, 91, ...csiBytes);
98611
+ csiBytes = [];
98612
+ phase = "normal";
98518
98613
  }
98519
98614
  }
98520
98615
  function appendDcsByte(byte) {
@@ -98559,10 +98654,38 @@ function createPaneStreamParser(options) {
98559
98654
  phase = "dcs-detect";
98560
98655
  return;
98561
98656
  }
98657
+ if (byte === 91) {
98658
+ csiBytes = [];
98659
+ phase = "csi";
98660
+ return;
98661
+ }
98562
98662
  output.push(27, byte);
98563
98663
  phase = "normal";
98564
98664
  return;
98565
98665
  }
98666
+ if (phase === "csi") {
98667
+ if (byte >= 64 && byte <= 126) {
98668
+ output.push(27, 91, ...csiBytes, byte);
98669
+ if ((byte === 104 || byte === 108) && csiBytes[0] === 63 && !inTmuxPassthrough) {
98670
+ const params = decoder2.decode(new Uint8Array(csiBytes.slice(1))).split(";");
98671
+ if (params.includes(THEME_UPDATES_MODE)) {
98672
+ options.onThemeSubscription?.(byte === 104);
98673
+ }
98674
+ }
98675
+ csiBytes = [];
98676
+ phase = "normal";
98677
+ return;
98678
+ }
98679
+ if (byte >= 32 && byte <= 63 && csiBytes.length < MAX_CSI_BYTES) {
98680
+ csiBytes.push(byte);
98681
+ return;
98682
+ }
98683
+ output.push(27, 91, ...csiBytes);
98684
+ csiBytes = [];
98685
+ phase = "normal";
98686
+ processByte(byte);
98687
+ return;
98688
+ }
98566
98689
  if (phase === "dcs-detect") {
98567
98690
  const expected = TMUX_PASSTHROUGH_PREFIX.charCodeAt(dcsPrefix.length);
98568
98691
  if (byte === expected) {
@@ -98755,7 +98878,8 @@ function createControlModeSubscription(callbacks) {
98755
98878
  onBell: () => callbacks.onBell(paneId),
98756
98879
  onNotification: (notification) => callbacks.onNotification(paneId, notification),
98757
98880
  onPromptMarker: (marker24) => callbacks.onPromptMarker?.(paneId, marker24),
98758
- onClipboardWrite: (text3) => callbacks.onClipboardWrite?.(paneId, text3)
98881
+ onClipboardWrite: (text3) => callbacks.onClipboardWrite?.(paneId, text3),
98882
+ onThemeSubscription: (subscribed) => callbacks.onThemeSubscription?.(paneId, subscribed)
98759
98883
  });
98760
98884
  paneParsers.set(paneId, parser2);
98761
98885
  return parser2;
@@ -99096,6 +99220,44 @@ function isTargetMissingMessage(message) {
99096
99220
  return normalized.includes("can't find window") || normalized.includes("can't find pane") || normalized.includes("no such window") || normalized.includes("no such pane");
99097
99221
  }
99098
99222
 
99223
+ // ../../apps/gateway/src/tmux-client/theme-subscriptions.ts
99224
+ function createThemeSubscriptionTracker() {
99225
+ const subscribed = new Set;
99226
+ return {
99227
+ note(paneId, isSubscribed) {
99228
+ if (isSubscribed) {
99229
+ subscribed.add(paneId);
99230
+ } else {
99231
+ subscribed.delete(paneId);
99232
+ }
99233
+ },
99234
+ clear(paneId) {
99235
+ subscribed.delete(paneId);
99236
+ },
99237
+ prune(validPaneIds) {
99238
+ for (const paneId of subscribed) {
99239
+ if (!validPaneIds.has(paneId)) {
99240
+ subscribed.delete(paneId);
99241
+ }
99242
+ }
99243
+ },
99244
+ restore(paneIds) {
99245
+ for (const paneId of paneIds) {
99246
+ subscribed.add(paneId);
99247
+ }
99248
+ },
99249
+ has(paneId) {
99250
+ return subscribed.has(paneId);
99251
+ },
99252
+ list() {
99253
+ return [...subscribed];
99254
+ },
99255
+ reset() {
99256
+ subscribed.clear();
99257
+ }
99258
+ };
99259
+ }
99260
+
99099
99261
  // ../../apps/gateway/src/tmux-client/tmux-version.ts
99100
99262
  var MIN_CONTROL_MODE_VERSION = { major: 3, minor: 0 };
99101
99263
  function parseTmuxVersion(versionOutput) {
@@ -99233,6 +99395,8 @@ class LocalExternalTmuxConnection {
99233
99395
  heartbeatTimer = null;
99234
99396
  heartbeatPending = false;
99235
99397
  heartbeatTimeoutTimer = null;
99398
+ themeSubscriptions = createThemeSubscriptionTracker();
99399
+ themeSubscriptionsRestored = false;
99236
99400
  constructor(options, inputDeps = {}) {
99237
99401
  this.deviceId = options.deviceId;
99238
99402
  this.callbacks = options;
@@ -99345,7 +99509,13 @@ class LocalExternalTmuxConnection {
99345
99509
  if (!this.connected) {
99346
99510
  return;
99347
99511
  }
99348
- const argv = ["new-window", "-t", this.sessionName, "-c", cwd ?? this.resolveDefaultWorkingDir()];
99512
+ const argv = [
99513
+ "new-window",
99514
+ "-t",
99515
+ this.sessionName,
99516
+ "-c",
99517
+ cwd ?? this.resolveDefaultWorkingDir()
99518
+ ];
99349
99519
  if (name24) {
99350
99520
  argv.push("-n", name24);
99351
99521
  }
@@ -99481,17 +99651,69 @@ class LocalExternalTmuxConnection {
99481
99651
  ]);
99482
99652
  }
99483
99653
  }
99484
- setWindowStyle(style) {
99654
+ async setWindowStyle(style) {
99485
99655
  if (!this.connected) {
99486
99656
  return;
99487
99657
  }
99488
99658
  if (!resolveTmuxWindowStyle(config.tmuxWindowStyle)) {
99489
99659
  return;
99490
99660
  }
99491
- this.configureWindowStyle(style).catch((error51) => {
99661
+ await this.configureWindowStyle(style).catch((error51) => {
99492
99662
  this.callbacks.onError(error51);
99493
99663
  });
99494
99664
  }
99665
+ signalThemeChange(paneId, theme) {
99666
+ if (!this.connected || !config.themeNotify2031Enabled) {
99667
+ return;
99668
+ }
99669
+ if (!this.themeSubscriptions.has(paneId)) {
99670
+ return;
99671
+ }
99672
+ this.sendInput(paneId, `\x1B[?997;${theme === "dark" ? "1" : "2"}n`);
99673
+ }
99674
+ noteThemeSubscription(paneId, subscribed) {
99675
+ this.themeSubscriptions.note(paneId, subscribed);
99676
+ this.runTmuxAllowFailure([
99677
+ "set-option",
99678
+ "-p",
99679
+ "-t",
99680
+ paneId,
99681
+ "@tmex_2031",
99682
+ subscribed ? "on" : "off"
99683
+ ]).catch(() => {});
99684
+ }
99685
+ clearThemeSubscription(paneId) {
99686
+ if (!this.themeSubscriptions.has(paneId)) {
99687
+ return;
99688
+ }
99689
+ this.themeSubscriptions.clear(paneId);
99690
+ this.runTmuxAllowFailure(["set-option", "-p", "-t", paneId, "@tmex_2031", "off"]).catch(() => {});
99691
+ }
99692
+ restoreThemeSubscriptionsOnce() {
99693
+ if (this.themeSubscriptionsRestored) {
99694
+ return;
99695
+ }
99696
+ this.themeSubscriptionsRestored = true;
99697
+ this.runTmuxAllowFailure([
99698
+ "list-panes",
99699
+ "-a",
99700
+ "-F",
99701
+ "#{pane_id}|#{@tmex_2031}"
99702
+ ]).then((result) => {
99703
+ if (!result || result.exitCode !== 0) {
99704
+ return;
99705
+ }
99706
+ const restored = [];
99707
+ for (const line of result.stdout.split(`
99708
+ `)) {
99709
+ const [paneId, flag] = line.trim().split("|");
99710
+ if (paneId && flag === "on") {
99711
+ restored.push(paneId);
99712
+ }
99713
+ }
99714
+ this.themeSubscriptions.restore(restored);
99715
+ }).catch(() => {});
99716
+ }
99495
99717
  async capturePaneText(paneId, opts) {
99496
99718
  if (!this.connected) {
99497
99719
  throw new Error(`tmux connection not available: ${this.deviceId}`);
@@ -99518,7 +99740,14 @@ class LocalExternalTmuxConnection {
99518
99740
  if (exists3.exitCode === 0) {
99519
99741
  return;
99520
99742
  }
99521
- await this.runTmux(["new-session", "-d", "-c", this.resolveDefaultWorkingDir(), "-s", this.sessionName]);
99743
+ await this.runTmux([
99744
+ "new-session",
99745
+ "-d",
99746
+ "-c",
99747
+ this.resolveDefaultWorkingDir(),
99748
+ "-s",
99749
+ this.sessionName
99750
+ ]);
99522
99751
  }
99523
99752
  async configureSessionOptions() {
99524
99753
  await this.runTmuxAllowFailure([
@@ -99529,7 +99758,14 @@ class LocalExternalTmuxConnection {
99529
99758
  "allow-passthrough",
99530
99759
  config.tmuxAllowPassthrough ? "on" : "off"
99531
99760
  ]);
99532
- await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "extended-keys", "on"]);
99761
+ await this.runTmuxAllowFailure([
99762
+ "set-option",
99763
+ "-t",
99764
+ this.sessionName,
99765
+ "-g",
99766
+ "extended-keys",
99767
+ "on"
99768
+ ]);
99533
99769
  await this.runTmuxAllowFailure([
99534
99770
  "set-option",
99535
99771
  "-t",
@@ -99538,7 +99774,14 @@ class LocalExternalTmuxConnection {
99538
99774
  "extended-keys-format",
99539
99775
  "csi-u"
99540
99776
  ]);
99541
- await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "off"]);
99777
+ await this.runTmuxAllowFailure([
99778
+ "set-option",
99779
+ "-t",
99780
+ this.sessionName,
99781
+ "-g",
99782
+ "focus-events",
99783
+ "off"
99784
+ ]);
99542
99785
  await this.runTmuxAllowFailure([
99543
99786
  "set-option",
99544
99787
  "-t",
@@ -99697,11 +99940,17 @@ class LocalExternalTmuxConnection {
99697
99940
  this.emitNotification(paneId, notification);
99698
99941
  },
99699
99942
  onPromptMarker: (paneId, marker24) => {
99943
+ if (marker24.kind === "A") {
99944
+ this.clearThemeSubscription(paneId);
99945
+ }
99700
99946
  this.callbacks.onPromptMarker?.(paneId, marker24);
99701
99947
  },
99702
99948
  onClipboardWrite: (paneId, text3) => {
99703
99949
  this.callbacks.onClipboardWrite?.(paneId, text3);
99704
99950
  },
99951
+ onThemeSubscription: (paneId, subscribed) => {
99952
+ this.noteThemeSubscription(paneId, subscribed);
99953
+ },
99705
99954
  onStructureChanged: () => {
99706
99955
  this.requestSnapshot();
99707
99956
  },
@@ -99899,7 +100148,14 @@ class LocalExternalTmuxConnection {
99899
100148
  async closeWindowInternal(windowId) {
99900
100149
  const count2 = Number.parseInt((await this.runTmux(["display-message", "-p", "-t", this.sessionName, "#{session_windows}"])).stdout.trim() || "0", 10);
99901
100150
  if (count2 <= 1) {
99902
- await this.runTmux(["new-window", "-d", "-t", this.sessionName, "-c", this.resolveDefaultWorkingDir()]);
100151
+ await this.runTmux([
100152
+ "new-window",
100153
+ "-d",
100154
+ "-t",
100155
+ this.sessionName,
100156
+ "-c",
100157
+ this.resolveDefaultWorkingDir()
100158
+ ]);
99903
100159
  }
99904
100160
  await this.runAndRefresh(["kill-window", "-t", windowId], true);
99905
100161
  }
@@ -100008,7 +100264,14 @@ class LocalExternalTmuxConnection {
100008
100264
  "-F",
100009
100265
  WINDOW_SNAPSHOT_FORMAT
100010
100266
  ]),
100011
- this.runTmuxAllowFailure(["list-panes", "-s", "-t", this.sessionName, "-F", PANE_SNAPSHOT_FORMAT])
100267
+ this.runTmuxAllowFailure([
100268
+ "list-panes",
100269
+ "-s",
100270
+ "-t",
100271
+ this.sessionName,
100272
+ "-F",
100273
+ PANE_SNAPSHOT_FORMAT
100274
+ ])
100012
100275
  ]);
100013
100276
  const transientResult = [sessionRes, windowsRes, panesRes].find((res) => res.exitCode === TMUX_SPAWN_UNAVAILABLE_EXIT);
100014
100277
  if (transientResult) {
@@ -100037,7 +100300,10 @@ ${panesRes.stderr}`;
100037
100300
  this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
100038
100301
  this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
100039
100302
  this.discardInvalidSnapshot();
100040
- this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
100303
+ const expectedPaneIds = new Set(this.getExpectedPaneIds());
100304
+ this.controlSubscription?.prunePanes(expectedPaneIds);
100305
+ this.themeSubscriptions.prune(expectedPaneIds);
100306
+ this.restoreThemeSubscriptionsOnce();
100041
100307
  this.markSpawnRecovered();
100042
100308
  this.emitSnapshot();
100043
100309
  }
@@ -100294,6 +100560,45 @@ function joinShellArgs(argv) {
100294
100560
  return argv.map((arg) => quoteShellArg(arg)).join(" ");
100295
100561
  }
100296
100562
 
100563
+ // ../../apps/gateway/src/tmux-client/ssh-bootstrap.ts
100564
+ function buildSshBootstrapScript() {
100565
+ return [
100566
+ ". /etc/profile 2>/dev/null || true",
100567
+ '[ -f "$HOME/.profile" ] && . "$HOME/.profile" 2>/dev/null || true',
100568
+ '[ -f "$HOME/.bash_profile" ] && . "$HOME/.bash_profile" 2>/dev/null || true',
100569
+ 'TMUX_BIN="$(command -v tmux 2>/dev/null || true)"',
100570
+ 'if [ -z "$TMUX_BIN" ]; then',
100571
+ " for p in /usr/local/bin/tmux /opt/homebrew/bin/tmux /usr/bin/tmux /bin/tmux; do",
100572
+ ' [ -x "$p" ] && TMUX_BIN="$p" && break',
100573
+ " done",
100574
+ "fi",
100575
+ 'HOME_DIR="${HOME:-$(pwd)}"',
100576
+ 'if [ -z "$TMUX_BIN" ]; then',
100577
+ " printf 'TMEX_BOOT_FAIL\\ttmux_not_found\\n'",
100578
+ "else",
100579
+ ` printf 'TMEX_BOOT_OK\\t%s\\t%s\\t%s\\n' "$TMUX_BIN" "$("$TMUX_BIN" -V 2>/dev/null)" "$HOME_DIR"`,
100580
+ "fi"
100581
+ ].join(`
100582
+ `);
100583
+ }
100584
+ function parseSshBootstrapOutput(output) {
100585
+ const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
100586
+ for (const line of lines) {
100587
+ if (line.startsWith("TMEX_BOOT_OK\t")) {
100588
+ const [, tmuxBin = "", tmuxVersion = "", homeDir = ""] = line.split("\t");
100589
+ if (!tmuxBin || !homeDir) {
100590
+ return { ok: false, reason: "invalid_bootstrap_payload" };
100591
+ }
100592
+ return { ok: true, tmuxBin, tmuxVersion, homeDir };
100593
+ }
100594
+ if (line.startsWith("TMEX_BOOT_FAIL\t")) {
100595
+ const [, reason = "tmux_bootstrap_failed"] = line.split("\t");
100596
+ return { ok: false, reason };
100597
+ }
100598
+ }
100599
+ return { ok: false, reason: "missing_bootstrap_marker" };
100600
+ }
100601
+
100297
100602
  // ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
100298
100603
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
100299
100604
  import { join as join3 } from "path";
@@ -100586,45 +100891,6 @@ async function resolveSshConnectConfig(device, decrypt2, inputDeps = {}) {
100586
100891
  return authConfig;
100587
100892
  }
100588
100893
 
100589
- // ../../apps/gateway/src/tmux-client/ssh-bootstrap.ts
100590
- function buildSshBootstrapScript() {
100591
- return [
100592
- ". /etc/profile 2>/dev/null || true",
100593
- '[ -f "$HOME/.profile" ] && . "$HOME/.profile" 2>/dev/null || true',
100594
- '[ -f "$HOME/.bash_profile" ] && . "$HOME/.bash_profile" 2>/dev/null || true',
100595
- 'TMUX_BIN="$(command -v tmux 2>/dev/null || true)"',
100596
- 'if [ -z "$TMUX_BIN" ]; then',
100597
- " for p in /usr/local/bin/tmux /opt/homebrew/bin/tmux /usr/bin/tmux /bin/tmux; do",
100598
- ' [ -x "$p" ] && TMUX_BIN="$p" && break',
100599
- " done",
100600
- "fi",
100601
- 'HOME_DIR="${HOME:-$(pwd)}"',
100602
- 'if [ -z "$TMUX_BIN" ]; then',
100603
- " printf 'TMEX_BOOT_FAIL\\ttmux_not_found\\n'",
100604
- "else",
100605
- ` printf 'TMEX_BOOT_OK\\t%s\\t%s\\t%s\\n' "$TMUX_BIN" "$("$TMUX_BIN" -V 2>/dev/null)" "$HOME_DIR"`,
100606
- "fi"
100607
- ].join(`
100608
- `);
100609
- }
100610
- function parseSshBootstrapOutput(output) {
100611
- const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
100612
- for (const line of lines) {
100613
- if (line.startsWith("TMEX_BOOT_OK\t")) {
100614
- const [, tmuxBin = "", tmuxVersion = "", homeDir = ""] = line.split("\t");
100615
- if (!tmuxBin || !homeDir) {
100616
- return { ok: false, reason: "invalid_bootstrap_payload" };
100617
- }
100618
- return { ok: true, tmuxBin, tmuxVersion, homeDir };
100619
- }
100620
- if (line.startsWith("TMEX_BOOT_FAIL\t")) {
100621
- const [, reason = "tmux_bootstrap_failed"] = line.split("\t");
100622
- return { ok: false, reason };
100623
- }
100624
- }
100625
- return { ok: false, reason: "missing_bootstrap_marker" };
100626
- }
100627
-
100628
100894
  // ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
100629
100895
  function hasRenderableTerminalContent2(value) {
100630
100896
  return value.trim().length > 0;
@@ -100664,6 +100930,8 @@ class SshExternalTmuxConnection {
100664
100930
  heartbeatTimer = null;
100665
100931
  heartbeatPending = false;
100666
100932
  heartbeatTimeoutTimer = null;
100933
+ themeSubscriptions = createThemeSubscriptionTracker();
100934
+ themeSubscriptionsRestored = false;
100667
100935
  sshClient = null;
100668
100936
  commandStream = null;
100669
100937
  commandStdoutBuffer = "";
@@ -100725,6 +100993,58 @@ class SshExternalTmuxConnection {
100725
100993
  });
100726
100994
  }
100727
100995
  }
100996
+ signalThemeChange(paneId, theme) {
100997
+ if (!this.connected || !config.themeNotify2031Enabled) {
100998
+ return;
100999
+ }
101000
+ if (!this.themeSubscriptions.has(paneId)) {
101001
+ return;
101002
+ }
101003
+ this.sendInput(paneId, `\x1B[?997;${theme === "dark" ? "1" : "2"}n`);
101004
+ }
101005
+ noteThemeSubscription(paneId, subscribed) {
101006
+ this.themeSubscriptions.note(paneId, subscribed);
101007
+ this.runTmuxAllowFailure([
101008
+ "set-option",
101009
+ "-p",
101010
+ "-t",
101011
+ paneId,
101012
+ "@tmex_2031",
101013
+ subscribed ? "on" : "off"
101014
+ ]).catch(() => {});
101015
+ }
101016
+ clearThemeSubscription(paneId) {
101017
+ if (!this.themeSubscriptions.has(paneId)) {
101018
+ return;
101019
+ }
101020
+ this.themeSubscriptions.clear(paneId);
101021
+ this.runTmuxAllowFailure(["set-option", "-p", "-t", paneId, "@tmex_2031", "off"]).catch(() => {});
101022
+ }
101023
+ restoreThemeSubscriptionsOnce() {
101024
+ if (this.themeSubscriptionsRestored) {
101025
+ return;
101026
+ }
101027
+ this.themeSubscriptionsRestored = true;
101028
+ this.runTmuxAllowFailure([
101029
+ "list-panes",
101030
+ "-a",
101031
+ "-F",
101032
+ "#{pane_id}|#{@tmex_2031}"
101033
+ ]).then((result) => {
101034
+ if (!result || result.exitCode !== 0) {
101035
+ return;
101036
+ }
101037
+ const restored = [];
101038
+ for (const line of result.stdout.split(`
101039
+ `)) {
101040
+ const [paneId, flag] = line.trim().split("|");
101041
+ if (paneId && flag === "on") {
101042
+ restored.push(paneId);
101043
+ }
101044
+ }
101045
+ this.themeSubscriptions.restore(restored);
101046
+ }).catch(() => {});
101047
+ }
100728
101048
  resizePane(paneId, cols, rows) {
100729
101049
  if (!this.connected) {
100730
101050
  return;
@@ -100761,7 +101081,13 @@ class SshExternalTmuxConnection {
100761
101081
  if (!this.connected) {
100762
101082
  return;
100763
101083
  }
100764
- const argv = ["new-window", "-t", this.sessionName, "-c", cwd ?? this.resolveDefaultWorkingDir()];
101084
+ const argv = [
101085
+ "new-window",
101086
+ "-t",
101087
+ this.sessionName,
101088
+ "-c",
101089
+ cwd ?? this.resolveDefaultWorkingDir()
101090
+ ];
100765
101091
  if (name24) {
100766
101092
  argv.push("-n", name24);
100767
101093
  }
@@ -100897,14 +101223,14 @@ class SshExternalTmuxConnection {
100897
101223
  ]);
100898
101224
  }
100899
101225
  }
100900
- setWindowStyle(style) {
101226
+ async setWindowStyle(style) {
100901
101227
  if (!this.connected) {
100902
101228
  return;
100903
101229
  }
100904
101230
  if (!resolveTmuxWindowStyle(config.tmuxWindowStyle)) {
100905
101231
  return;
100906
101232
  }
100907
- this.configureWindowStyle(style).catch((error51) => {
101233
+ await this.configureWindowStyle(style).catch((error51) => {
100908
101234
  this.callbacks.onError(error51);
100909
101235
  });
100910
101236
  }
@@ -101040,7 +101366,14 @@ class SshExternalTmuxConnection {
101040
101366
  if (exists3.exitCode === 0) {
101041
101367
  return;
101042
101368
  }
101043
- await this.runTmux(["new-session", "-d", "-c", this.resolveDefaultWorkingDir(), "-s", this.sessionName]);
101369
+ await this.runTmux([
101370
+ "new-session",
101371
+ "-d",
101372
+ "-c",
101373
+ this.resolveDefaultWorkingDir(),
101374
+ "-s",
101375
+ this.sessionName
101376
+ ]);
101044
101377
  }
101045
101378
  async configureSessionOptions() {
101046
101379
  await this.runTmuxAllowFailure([
@@ -101051,7 +101384,14 @@ class SshExternalTmuxConnection {
101051
101384
  "allow-passthrough",
101052
101385
  config.tmuxAllowPassthrough ? "on" : "off"
101053
101386
  ]);
101054
- await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "extended-keys", "on"]);
101387
+ await this.runTmuxAllowFailure([
101388
+ "set-option",
101389
+ "-t",
101390
+ this.sessionName,
101391
+ "-g",
101392
+ "extended-keys",
101393
+ "on"
101394
+ ]);
101055
101395
  await this.runTmuxAllowFailure([
101056
101396
  "set-option",
101057
101397
  "-t",
@@ -101060,7 +101400,14 @@ class SshExternalTmuxConnection {
101060
101400
  "extended-keys-format",
101061
101401
  "csi-u"
101062
101402
  ]);
101063
- await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "off"]);
101403
+ await this.runTmuxAllowFailure([
101404
+ "set-option",
101405
+ "-t",
101406
+ this.sessionName,
101407
+ "-g",
101408
+ "focus-events",
101409
+ "off"
101410
+ ]);
101064
101411
  await this.runTmuxAllowFailure([
101065
101412
  "set-option",
101066
101413
  "-t",
@@ -101108,6 +101455,7 @@ class SshExternalTmuxConnection {
101108
101455
  if (!windowStyle) {
101109
101456
  return;
101110
101457
  }
101458
+ const startedAt = config.isDev ? Date.now() : 0;
101111
101459
  await this.runTmuxAllowFailure([
101112
101460
  "set-hook",
101113
101461
  "-t",
@@ -101123,22 +101471,26 @@ class SshExternalTmuxConnection {
101123
101471
  "#{window_id}"
101124
101472
  ]);
101125
101473
  if (windows.exitCode !== 0) {
101474
+ if (config.isDev) {
101475
+ console.debug(`[ssh] configureWindowStyle deviceId=${this.deviceId} elapsed=${Date.now() - startedAt}ms (list-windows failed)`);
101476
+ }
101126
101477
  return;
101127
101478
  }
101479
+ const windowIds = [];
101128
101480
  for (const line of windows.stdout.split(`
101129
101481
  `)) {
101130
101482
  const windowId = line.trim();
101131
101483
  if (!windowId) {
101132
101484
  continue;
101133
101485
  }
101134
- await this.runTmuxAllowFailure([
101135
- "set-option",
101136
- "-w",
101137
- "-t",
101138
- windowId,
101139
- "window-style",
101140
- windowStyle
101141
- ]);
101486
+ windowIds.push(windowId);
101487
+ }
101488
+ if (windowIds.length > 0) {
101489
+ const setOptions = windowIds.map((id) => `${quoteShellArg(this.tmuxBin)} set-option -w -t ${quoteShellArg(id)} window-style ${quoteShellArg(windowStyle)}`).join(" && ");
101490
+ await this.runShellAllowFailure(setOptions);
101491
+ }
101492
+ if (config.isDev) {
101493
+ console.debug(`[ssh] configureWindowStyle deviceId=${this.deviceId} windows=${windowIds.length} elapsed=${Date.now() - startedAt}ms`);
101142
101494
  }
101143
101495
  }
101144
101496
  async ensureGhosttyTerminfo() {
@@ -101215,11 +101567,17 @@ class SshExternalTmuxConnection {
101215
101567
  this.emitNotification(paneId, notification);
101216
101568
  },
101217
101569
  onPromptMarker: (paneId, marker24) => {
101570
+ if (marker24.kind === "A") {
101571
+ this.clearThemeSubscription(paneId);
101572
+ }
101218
101573
  this.callbacks.onPromptMarker?.(paneId, marker24);
101219
101574
  },
101220
101575
  onClipboardWrite: (paneId, text3) => {
101221
101576
  this.callbacks.onClipboardWrite?.(paneId, text3);
101222
101577
  },
101578
+ onThemeSubscription: (paneId, subscribed) => {
101579
+ this.noteThemeSubscription(paneId, subscribed);
101580
+ },
101223
101581
  onStructureChanged: () => {
101224
101582
  this.requestSnapshot();
101225
101583
  },
@@ -101380,7 +101738,14 @@ class SshExternalTmuxConnection {
101380
101738
  async closeWindowInternal(windowId) {
101381
101739
  const count2 = Number.parseInt((await this.runTmux(["display-message", "-p", "-t", this.sessionName, "#{session_windows}"])).stdout.trim() || "0", 10);
101382
101740
  if (count2 <= 1) {
101383
- await this.runTmux(["new-window", "-d", "-t", this.sessionName, "-c", this.resolveDefaultWorkingDir()]);
101741
+ await this.runTmux([
101742
+ "new-window",
101743
+ "-d",
101744
+ "-t",
101745
+ this.sessionName,
101746
+ "-c",
101747
+ this.resolveDefaultWorkingDir()
101748
+ ]);
101384
101749
  }
101385
101750
  await this.runAndRefresh(["kill-window", "-t", windowId], true);
101386
101751
  }
@@ -101489,7 +101854,14 @@ class SshExternalTmuxConnection {
101489
101854
  "-F",
101490
101855
  WINDOW_SNAPSHOT_FORMAT
101491
101856
  ]),
101492
- this.runTmuxAllowFailure(["list-panes", "-s", "-t", this.sessionName, "-F", PANE_SNAPSHOT_FORMAT])
101857
+ this.runTmuxAllowFailure([
101858
+ "list-panes",
101859
+ "-s",
101860
+ "-t",
101861
+ this.sessionName,
101862
+ "-F",
101863
+ PANE_SNAPSHOT_FORMAT
101864
+ ])
101493
101865
  ]);
101494
101866
  if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
101495
101867
  const stderrBlob = `${sessionRes.stderr}
@@ -101513,7 +101885,10 @@ ${panesRes.stderr}`;
101513
101885
  this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
101514
101886
  this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
101515
101887
  this.discardInvalidSnapshot();
101516
- this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
101888
+ const expectedPaneIds = new Set(this.getExpectedPaneIds());
101889
+ this.controlSubscription?.prunePanes(expectedPaneIds);
101890
+ this.themeSubscriptions.prune(expectedPaneIds);
101891
+ this.restoreThemeSubscriptionsOnce();
101517
101892
  this.emitSnapshot();
101518
101893
  }
101519
101894
  parseSnapshotSession(lines) {
@@ -102013,7 +102388,10 @@ class DeviceSessionRuntime {
102013
102388
  this.connection.renameWindow(windowId, name24);
102014
102389
  }
102015
102390
  setWindowStyle(style) {
102016
- this.connection.setWindowStyle(style);
102391
+ return this.connection.setWindowStyle(style);
102392
+ }
102393
+ signalThemeChange(paneId, theme) {
102394
+ this.connection.signalThemeChange(paneId, theme);
102017
102395
  }
102018
102396
  async capturePaneText(paneId, opts) {
102019
102397
  return this.connection.capturePaneText(paneId, opts);
@@ -107545,8 +107923,8 @@ function getBaseVersion() {
107545
107923
  if (cachedBase !== undefined)
107546
107924
  return cachedBase;
107547
107925
  let base = null;
107548
- if ("0.16.2") {
107549
- base = "0.16.2";
107926
+ if ("0.16.4") {
107927
+ base = "0.16.4";
107550
107928
  }
107551
107929
  if (!base && config.isProd) {
107552
107930
  base = readInstallMeta()?.cliVersion ?? null;
@@ -107921,6 +108299,62 @@ async function handleDeviceTestConnection(deviceId, inputDeps = {}) {
107921
108299
  }
107922
108300
  }
107923
108301
 
108302
+ // ../../apps/gateway/src/tmux/theme-broadcaster.ts
108303
+ var tmuxBroadcaster = null;
108304
+ var s2cBroadcaster = null;
108305
+ function registerThemeBroadcaster(tmux, s2c = null) {
108306
+ tmuxBroadcaster = tmux;
108307
+ s2cBroadcaster = s2c;
108308
+ }
108309
+ function broadcastThemeChange(theme) {
108310
+ tmuxBroadcaster?.(theme);
108311
+ }
108312
+ function broadcastSiteThemeUpdateS2C(theme) {
108313
+ s2cBroadcaster?.(theme);
108314
+ }
108315
+
108316
+ // ../../apps/gateway/src/api/theme.ts
108317
+ var VALID_THEMES = ["dark", "light"];
108318
+ function handleThemeApiRequest(req, path) {
108319
+ if (path === "/api/settings/theme" && req.method === "GET") {
108320
+ return handleGetTheme();
108321
+ }
108322
+ if (path === "/api/settings/theme" && req.method === "POST") {
108323
+ return handleUpdateTheme(req);
108324
+ }
108325
+ return null;
108326
+ }
108327
+ function handleGetTheme() {
108328
+ const settings = getSiteSettings();
108329
+ return json8({ theme: settings.theme, serverTimestamp: Date.now() });
108330
+ }
108331
+ async function handleUpdateTheme(req) {
108332
+ let body;
108333
+ try {
108334
+ body = await req.json();
108335
+ } catch {
108336
+ return json8({ error: "invalid request body" }, 400);
108337
+ }
108338
+ if (typeof body !== "object" || body === null || Array.isArray(body)) {
108339
+ return json8({ error: "invalid request body" }, 400);
108340
+ }
108341
+ const { theme } = body;
108342
+ if (typeof theme !== "string" || !VALID_THEMES.includes(theme)) {
108343
+ return json8({ error: "theme must be one of: dark, light" }, 400);
108344
+ }
108345
+ const serverTimestamp = Date.now();
108346
+ updateSiteSettings({ theme });
108347
+ broadcastThemeChange(theme);
108348
+ broadcastSiteThemeUpdateS2C(theme);
108349
+ return json8({ theme, serverTimestamp });
108350
+ }
108351
+ function json8(data, status = 200) {
108352
+ return new Response(JSON.stringify(data), {
108353
+ status,
108354
+ headers: { "Content-Type": "application/json" }
108355
+ });
108356
+ }
108357
+
107924
108358
  // ../../apps/gateway/src/db/watch.ts
107925
108359
  function createWatchRule(input) {
107926
108360
  const orm = getDb();
@@ -109019,35 +109453,35 @@ async function handleListRules(req) {
109019
109453
  if (paneId) {
109020
109454
  rules = rules.filter((rule) => rule.paneId === paneId);
109021
109455
  }
109022
- return json8({ rules: rules.map(toRuleDto) });
109456
+ return json9({ rules: rules.map(toRuleDto) });
109023
109457
  }
109024
109458
  async function handleCreateRule(req, deps) {
109025
109459
  const raw = await readJsonObjectBody3(req);
109026
109460
  if (!raw) {
109027
- return json8({ error: t2("apiError.invalidRequest") }, 400);
109461
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
109028
109462
  }
109029
109463
  const body = raw;
109030
109464
  const name24 = typeof body.name === "string" ? body.name.trim() : "";
109031
109465
  if (!name24) {
109032
- return json8({ error: t2("apiError.watchNameRequired") }, 400);
109466
+ return json9({ error: t2("apiError.watchNameRequired") }, 400);
109033
109467
  }
109034
109468
  const deviceId = typeof body.deviceId === "string" ? body.deviceId.trim() : "";
109035
109469
  if (!deviceId) {
109036
- return json8({ error: t2("apiError.agentDeviceRequired") }, 400);
109470
+ return json9({ error: t2("apiError.agentDeviceRequired") }, 400);
109037
109471
  }
109038
109472
  if (!getDeviceById(deviceId)) {
109039
- return json8({ error: t2("apiError.deviceNotFound") }, 404);
109473
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
109040
109474
  }
109041
109475
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
109042
109476
  if (!paneId) {
109043
- return json8({ error: t2("apiError.agentPaneRequired") }, 400);
109477
+ return json9({ error: t2("apiError.agentPaneRequired") }, 400);
109044
109478
  }
109045
109479
  if (!TRIGGER_TYPES.includes(body.triggerType)) {
109046
- return json8({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109480
+ return json9({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109047
109481
  }
109048
109482
  const parsed = parseRuleFields(raw);
109049
109483
  if (!parsed.ok) {
109050
- return json8({ error: parsed.error }, 400);
109484
+ return json9({ error: parsed.error }, 400);
109051
109485
  }
109052
109486
  const fields = parsed.fields;
109053
109487
  const effective = {
@@ -109060,7 +109494,7 @@ async function handleCreateRule(req, deps) {
109060
109494
  };
109061
109495
  const semanticError = validateRuleSemantics(effective);
109062
109496
  if (semanticError) {
109063
- return json8({ error: semanticError }, 400);
109497
+ return json9({ error: semanticError }, 400);
109064
109498
  }
109065
109499
  const rule = createWatchRule({
109066
109500
  name: name24,
@@ -109083,50 +109517,50 @@ async function handleCreateRule(req, deps) {
109083
109517
  cooldownSeconds: fields.cooldownSeconds
109084
109518
  });
109085
109519
  await deps.service.refreshRule(rule.id);
109086
- return json8({ rule: toRuleDto(rule), state: null }, 201);
109520
+ return json9({ rule: toRuleDto(rule), state: null }, 201);
109087
109521
  }
109088
109522
  async function handleGetRule(id) {
109089
109523
  const rule = getWatchRuleById(id);
109090
109524
  if (!rule) {
109091
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109525
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109092
109526
  }
109093
109527
  const state = getWatchRuleState(id);
109094
- return json8({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109528
+ return json9({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109095
109529
  }
109096
109530
  async function handleUpdateRule(req, id, deps) {
109097
109531
  const existing = getWatchRuleById(id);
109098
109532
  if (!existing) {
109099
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109533
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109100
109534
  }
109101
109535
  const raw = await readJsonObjectBody3(req);
109102
109536
  if (!raw) {
109103
- return json8({ error: t2("apiError.invalidRequest") }, 400);
109537
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
109104
109538
  }
109105
109539
  const body = raw;
109106
109540
  const updates = {};
109107
109541
  if (body.name !== undefined) {
109108
109542
  const name24 = typeof body.name === "string" ? body.name.trim() : "";
109109
109543
  if (!name24) {
109110
- return json8({ error: t2("apiError.watchNameRequired") }, 400);
109544
+ return json9({ error: t2("apiError.watchNameRequired") }, 400);
109111
109545
  }
109112
109546
  updates.name = name24;
109113
109547
  }
109114
109548
  if (body.paneId !== undefined) {
109115
109549
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
109116
109550
  if (!paneId) {
109117
- return json8({ error: t2("apiError.agentPaneRequired") }, 400);
109551
+ return json9({ error: t2("apiError.agentPaneRequired") }, 400);
109118
109552
  }
109119
109553
  updates.paneId = paneId;
109120
109554
  }
109121
109555
  if (body.triggerType !== undefined) {
109122
109556
  if (!TRIGGER_TYPES.includes(body.triggerType)) {
109123
- return json8({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109557
+ return json9({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109124
109558
  }
109125
109559
  updates.triggerType = body.triggerType;
109126
109560
  }
109127
109561
  const parsed = parseRuleFields(raw);
109128
109562
  if (!parsed.ok) {
109129
- return json8({ error: parsed.error }, 400);
109563
+ return json9({ error: parsed.error }, 400);
109130
109564
  }
109131
109565
  const fields = parsed.fields;
109132
109566
  Object.assign(updates, fields);
@@ -109140,32 +109574,32 @@ async function handleUpdateRule(req, id, deps) {
109140
109574
  };
109141
109575
  const semanticError = validateRuleSemantics(effective);
109142
109576
  if (semanticError) {
109143
- return json8({ error: semanticError }, 400);
109577
+ return json9({ error: semanticError }, 400);
109144
109578
  }
109145
109579
  const rule = updateWatchRule(id, updates);
109146
109580
  if (!rule) {
109147
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109581
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109148
109582
  }
109149
109583
  await deps.service.refreshRule(id);
109150
109584
  const state = getWatchRuleState(id);
109151
- return json8({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109585
+ return json9({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109152
109586
  }
109153
109587
  async function handleDeleteRule(id, deps) {
109154
109588
  const existing = getWatchRuleById(id);
109155
109589
  if (!existing) {
109156
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109590
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109157
109591
  }
109158
109592
  deleteWatchRule(id);
109159
109593
  await deps.service.removeRule(id);
109160
- return json8({ success: true });
109594
+ return json9({ success: true });
109161
109595
  }
109162
109596
  async function handleGetRuleState(id, deps) {
109163
109597
  const rule = getWatchRuleById(id);
109164
109598
  if (!rule) {
109165
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109599
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109166
109600
  }
109167
109601
  const state = getWatchRuleState(id);
109168
- return json8({
109602
+ return json9({
109169
109603
  state: state ? toStateDto(state) : null,
109170
109604
  samples: deps.service.getSamples(id)
109171
109605
  });
@@ -109189,17 +109623,17 @@ function buildAssistPrompt(description, screen) {
109189
109623
  async function handleAssistRegex(req, deps) {
109190
109624
  const raw = await readJsonObjectBody3(req);
109191
109625
  if (!raw) {
109192
- return json8({ error: t2("apiError.invalidRequest") }, 400);
109626
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
109193
109627
  }
109194
109628
  const body = raw;
109195
109629
  const description = typeof body.description === "string" ? body.description.trim() : "";
109196
109630
  if (!description) {
109197
- return json8({ error: t2("apiError.watchAssistDescriptionRequired") }, 400);
109631
+ return json9({ error: t2("apiError.watchAssistDescriptionRequired") }, 400);
109198
109632
  }
109199
109633
  let providerId = null;
109200
109634
  if (body.providerId !== undefined && body.providerId !== null) {
109201
109635
  if (typeof body.providerId !== "string" || !getLlmProviderById(body.providerId)) {
109202
- return json8({ error: t2("apiError.llmProviderNotFound") }, 400);
109636
+ return json9({ error: t2("apiError.llmProviderNotFound") }, 400);
109203
109637
  }
109204
109638
  providerId = body.providerId;
109205
109639
  }
@@ -109209,7 +109643,7 @@ async function handleAssistRegex(req, deps) {
109209
109643
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
109210
109644
  if (deviceId && paneId) {
109211
109645
  if (!getDeviceById(deviceId)) {
109212
- return json8({ error: t2("apiError.deviceNotFound") }, 404);
109646
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
109213
109647
  }
109214
109648
  try {
109215
109649
  screen = await deps.captureScreen(deviceId, paneId);
@@ -109230,14 +109664,14 @@ async function handleAssistRegex(req, deps) {
109230
109664
  object3 = result.object;
109231
109665
  } catch (error51) {
109232
109666
  const detail = error51 instanceof Error ? error51.message : String(error51);
109233
- return json8({ error: t2("apiError.watchAssistModelUnavailable", { detail }) }, 502);
109667
+ return json9({ error: t2("apiError.watchAssistModelUnavailable", { detail }) }, 502);
109234
109668
  }
109235
109669
  let regex;
109236
109670
  try {
109237
109671
  regex = compileWatchPattern(object3.pattern, object3.flags);
109238
109672
  } catch (error51) {
109239
109673
  const detail = error51 instanceof Error ? error51.message : String(error51);
109240
- return json8({ error: t2("apiError.watchPatternInvalid", { detail }) }, 502);
109674
+ return json9({ error: t2("apiError.watchPatternInvalid", { detail }) }, 502);
109241
109675
  }
109242
109676
  const preview = [];
109243
109677
  if (screen) {
@@ -109251,7 +109685,7 @@ async function handleAssistRegex(req, deps) {
109251
109685
  match = regex.exec(screen);
109252
109686
  }
109253
109687
  }
109254
- return json8({
109688
+ return json9({
109255
109689
  pattern: object3.pattern,
109256
109690
  flags: object3.flags,
109257
109691
  extractGroup: object3.extractGroup >= 0 ? object3.extractGroup : 0,
@@ -109259,7 +109693,7 @@ async function handleAssistRegex(req, deps) {
109259
109693
  preview
109260
109694
  });
109261
109695
  }
109262
- function json8(data, status = 200) {
109696
+ function json9(data, status = 200) {
109263
109697
  return new Response(JSON.stringify(data), {
109264
109698
  status,
109265
109699
  headers: {
@@ -109404,6 +109838,12 @@ function handleApiRequest(req, _server) {
109404
109838
  if (path === "/api/settings/terminal-shortcuts" && req.method === "PATCH") {
109405
109839
  return handleUpdateTerminalShortcuts(req);
109406
109840
  }
109841
+ if (path === "/api/settings/theme" && (req.method === "GET" || req.method === "POST")) {
109842
+ const themeResponse = handleThemeApiRequest(req, path);
109843
+ if (themeResponse) {
109844
+ return themeResponse;
109845
+ }
109846
+ }
109407
109847
  if (path === "/api/settings/restart" && req.method === "POST") {
109408
109848
  return handleRestartGateway();
109409
109849
  }
@@ -109507,13 +109947,13 @@ function handleApiRequest(req, _server) {
109507
109947
  return handleGetManifest(req.method);
109508
109948
  }
109509
109949
  if (path === "/healthz" && req.method === "GET") {
109510
- return json9({
109950
+ return json10({
109511
109951
  status: "ok",
109512
109952
  restarting: runtimeController.isRestarting(),
109513
109953
  env: "development"
109514
109954
  });
109515
109955
  }
109516
- return json9({ error: t2("apiError.notFound") }, 404);
109956
+ return json10({ error: t2("apiError.notFound") }, 404);
109517
109957
  }
109518
109958
  function enrichDeviceWithRuntime(device) {
109519
109959
  const status = getDeviceRuntimeStatus(device.id);
@@ -109527,22 +109967,22 @@ function enrichDeviceWithRuntime(device) {
109527
109967
  }
109528
109968
  async function handleGetDevices() {
109529
109969
  const devices2 = getAllDevices().map(enrichDeviceWithRuntime);
109530
- return json9({ devices: devices2 });
109970
+ return json10({ devices: devices2 });
109531
109971
  }
109532
109972
  async function handleGetDevice(id) {
109533
109973
  const device = getDeviceById(id);
109534
109974
  if (!device) {
109535
- return json9({ error: t2("apiError.deviceNotFound") }, 404);
109975
+ return json10({ error: t2("apiError.deviceNotFound") }, 404);
109536
109976
  }
109537
- return json9({ device: enrichDeviceWithRuntime(device) });
109977
+ return json10({ device: enrichDeviceWithRuntime(device) });
109538
109978
  }
109539
109979
  async function handleCreateDevice(req) {
109540
109980
  const body = await req.json();
109541
109981
  if (!body.name || !body.type || !body.authMode) {
109542
- return json9({ error: t2("apiError.missingFields") }, 400);
109982
+ return json10({ error: t2("apiError.missingFields") }, 400);
109543
109983
  }
109544
109984
  if (body.type === "ssh" && !body.host && !body.sshConfigRef) {
109545
- return json9({ error: t2("apiError.sshRequiresHost") }, 400);
109985
+ return json10({ error: t2("apiError.sshRequiresHost") }, 400);
109546
109986
  }
109547
109987
  const now2 = new Date().toISOString();
109548
109988
  const device = {
@@ -109565,12 +110005,12 @@ async function handleCreateDevice(req) {
109565
110005
  };
109566
110006
  createDevice(device);
109567
110007
  await pushSupervisor.upsert(device.id);
109568
- return json9({ device: getDeviceById(device.id) ?? device }, 201);
110008
+ return json10({ device: getDeviceById(device.id) ?? device }, 201);
109569
110009
  }
109570
110010
  async function handleUpdateDevice(req, id) {
109571
110011
  const existing = getDeviceById(id);
109572
110012
  if (!existing) {
109573
- return json9({ error: t2("apiError.deviceNotFound") }, 404);
110013
+ return json10({ error: t2("apiError.deviceNotFound") }, 404);
109574
110014
  }
109575
110015
  const body = await req.json();
109576
110016
  const updates = {};
@@ -109604,74 +110044,74 @@ async function handleUpdateDevice(req, id) {
109604
110044
  pushSupervisor.updateDefaultWorkingDir(id, updates.defaultWorkingDir);
109605
110045
  }
109606
110046
  const device = getDeviceById(id);
109607
- return json9({ device });
110047
+ return json10({ device });
109608
110048
  }
109609
110049
  async function handleReorderDevices(req) {
109610
110050
  const body = await req.json();
109611
110051
  if (!Array.isArray(body.deviceIds) || body.deviceIds.some((id) => typeof id !== "string")) {
109612
- return json9({ error: t2("apiError.invalidRequest") }, 400);
110052
+ return json10({ error: t2("apiError.invalidRequest") }, 400);
109613
110053
  }
109614
110054
  reorderDevices(body.deviceIds);
109615
- return json9({ devices: getAllDevices().map(enrichDeviceWithRuntime) });
110055
+ return json10({ devices: getAllDevices().map(enrichDeviceWithRuntime) });
109616
110056
  }
109617
110057
  async function handleDeleteDevice(id) {
109618
110058
  const existing = getDeviceById(id);
109619
110059
  if (!existing) {
109620
- return json9({ error: t2("apiError.deviceNotFound") }, 404);
110060
+ return json10({ error: t2("apiError.deviceNotFound") }, 404);
109621
110061
  }
109622
110062
  deleteDevice(id);
109623
110063
  pushSupervisor.remove(id);
109624
- return json9({ success: true });
110064
+ return json10({ success: true });
109625
110065
  }
109626
110066
  async function handleTestConnection(id) {
109627
110067
  return handleDeviceTestConnection(id);
109628
110068
  }
109629
110069
  async function handleGetSiteSettings() {
109630
- return json9({ settings: getSiteSettings() });
110070
+ return json10({ settings: getSiteSettings() });
109631
110071
  }
109632
110072
  async function handleUpdateSiteSettings(req) {
109633
110073
  try {
109634
110074
  const body = await req.json();
109635
110075
  const updates = normalizeSiteSettingsInput(body);
109636
110076
  const settings = updateSiteSettings(updates);
109637
- return json9({ settings });
110077
+ return json10({ settings });
109638
110078
  } catch (err) {
109639
- return json9({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
110079
+ return json10({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
109640
110080
  }
109641
110081
  }
109642
110082
  async function handleGetTerminalShortcuts() {
109643
- return json9({ settings: getTerminalShortcutSettings() });
110083
+ return json10({ settings: getTerminalShortcutSettings() });
109644
110084
  }
109645
110085
  async function handleUpdateTerminalShortcuts(req) {
109646
110086
  try {
109647
110087
  const body = await req.json();
109648
110088
  const updates = normalizeTerminalShortcutsInput(body);
109649
110089
  const settings = updateTerminalShortcutSettings(updates);
109650
- return json9({ settings });
110090
+ return json10({ settings });
109651
110091
  } catch (err) {
109652
- return json9({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
110092
+ return json10({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
109653
110093
  }
109654
110094
  }
109655
110095
  async function handleRestartGateway() {
109656
110096
  setTimeout(() => {
109657
110097
  runtimeController.requestRestart();
109658
110098
  }, 50);
109659
- return json9({
110099
+ return json10({
109660
110100
  success: true,
109661
110101
  message: t2("settings.restartScheduled")
109662
110102
  });
109663
110103
  }
109664
110104
  async function handleGetTelegramBots() {
109665
110105
  const bots = getTelegramBotsWithStats();
109666
- return json9({ bots });
110106
+ return json10({ bots });
109667
110107
  }
109668
110108
  async function handleCreateTelegramBot(req) {
109669
110109
  const body = await req.json();
109670
110110
  if (!body.name?.trim()) {
109671
- return json9({ error: t2("apiError.botNameRequired") }, 400);
110111
+ return json10({ error: t2("apiError.botNameRequired") }, 400);
109672
110112
  }
109673
110113
  if (!body.token?.trim()) {
109674
- return json9({ error: t2("apiError.botTokenRequired") }, 400);
110114
+ return json10({ error: t2("apiError.botTokenRequired") }, 400);
109675
110115
  }
109676
110116
  const now2 = new Date().toISOString();
109677
110117
  createTelegramBot({
@@ -109685,26 +110125,26 @@ async function handleCreateTelegramBot(req) {
109685
110125
  updatedAt: now2
109686
110126
  });
109687
110127
  await telegramService.refresh();
109688
- return json9({ success: true }, 201);
110128
+ return json10({ success: true }, 201);
109689
110129
  }
109690
110130
  async function handleUpdateTelegramBot(req, botId) {
109691
110131
  const existing = getTelegramBotById(botId);
109692
110132
  if (!existing) {
109693
- return json9({ error: t2("apiError.botNotFound") }, 404);
110133
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109694
110134
  }
109695
110135
  const body = await req.json();
109696
110136
  const updates = {};
109697
110137
  if (body.name !== undefined) {
109698
110138
  const value = body.name.trim();
109699
110139
  if (!value) {
109700
- return json9({ error: t2("apiError.botNameRequired") }, 400);
110140
+ return json10({ error: t2("apiError.botNameRequired") }, 400);
109701
110141
  }
109702
110142
  updates.name = value;
109703
110143
  }
109704
110144
  if (body.token !== undefined) {
109705
110145
  const token = body.token.trim();
109706
110146
  if (!token) {
109707
- return json9({ error: t2("apiError.botTokenRequired") }, 400);
110147
+ return json10({ error: t2("apiError.botTokenRequired") }, 400);
109708
110148
  }
109709
110149
  updates.tokenEnc = await encrypt(token);
109710
110150
  }
@@ -109716,69 +110156,69 @@ async function handleUpdateTelegramBot(req, botId) {
109716
110156
  }
109717
110157
  updateTelegramBot(botId, updates);
109718
110158
  await telegramService.refresh();
109719
- return json9({ success: true });
110159
+ return json10({ success: true });
109720
110160
  }
109721
110161
  async function handleDeleteTelegramBot(botId) {
109722
110162
  const existing = getTelegramBotById(botId);
109723
110163
  if (!existing) {
109724
- return json9({ error: t2("apiError.botNotFound") }, 404);
110164
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109725
110165
  }
109726
110166
  deleteTelegramBot(botId);
109727
110167
  await telegramService.refresh();
109728
- return json9({ success: true });
110168
+ return json10({ success: true });
109729
110169
  }
109730
110170
  async function handleListTelegramChats(botId) {
109731
110171
  const existing = getTelegramBotById(botId);
109732
110172
  if (!existing) {
109733
- return json9({ error: t2("apiError.botNotFound") }, 404);
110173
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109734
110174
  }
109735
110175
  const chats = listTelegramChatsByBot(botId);
109736
- return json9({ chats });
110176
+ return json10({ chats });
109737
110177
  }
109738
110178
  async function handleApproveTelegramChat(botId, chatId) {
109739
110179
  const existing = getTelegramBotById(botId);
109740
110180
  if (!existing) {
109741
- return json9({ error: t2("apiError.botNotFound") }, 404);
110181
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109742
110182
  }
109743
110183
  const chat = approveTelegramChat(botId, chatId);
109744
110184
  if (!chat) {
109745
- return json9({ error: t2("apiError.chatNotFound") }, 404);
110185
+ return json10({ error: t2("apiError.chatNotFound") }, 404);
109746
110186
  }
109747
110187
  const settings = getSiteSettings();
109748
110188
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.approveMessageTemplate", {
109749
110189
  botName: existing.name,
109750
110190
  time: new Date().toLocaleString(toBCP47(settings.language))
109751
110191
  }));
109752
- return json9({ chat });
110192
+ return json10({ chat });
109753
110193
  }
109754
110194
  async function handleDeleteTelegramChat(botId, chatId) {
109755
110195
  const existing = getTelegramBotById(botId);
109756
110196
  if (!existing) {
109757
- return json9({ error: t2("apiError.botNotFound") }, 404);
110197
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109758
110198
  }
109759
110199
  deleteTelegramChat(botId, chatId);
109760
- return json9({ success: true });
110200
+ return json10({ success: true });
109761
110201
  }
109762
110202
  async function handleTestTelegramChat(botId, chatId) {
109763
110203
  const bot = getTelegramBotById(botId);
109764
110204
  if (!bot) {
109765
- return json9({ error: t2("apiError.botNotFound") }, 404);
110205
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109766
110206
  }
109767
110207
  const settings = getSiteSettings();
109768
110208
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.testMessageTemplate", {
109769
110209
  siteName: settings.siteName,
109770
110210
  time: new Date().toLocaleString(toBCP47(settings.language))
109771
110211
  }));
109772
- return json9({ success: true });
110212
+ return json10({ success: true });
109773
110213
  }
109774
110214
  async function handleGetWeixinAccounts() {
109775
110215
  const accounts = getWeixinAccountsWithStats();
109776
- return json9({ accounts });
110216
+ return json10({ accounts });
109777
110217
  }
109778
110218
  async function handleCreateWeixinAccount(req) {
109779
110219
  const body = await req.json();
109780
110220
  if (!body.name?.trim()) {
109781
- return json9({ error: t2("weixin.accountNameRequired") }, 400);
110221
+ return json10({ error: t2("weixin.accountNameRequired") }, 400);
109782
110222
  }
109783
110223
  const now2 = new Date().toISOString();
109784
110224
  const id = v4_default();
@@ -109795,19 +110235,19 @@ async function handleCreateWeixinAccount(req) {
109795
110235
  createdAt: now2,
109796
110236
  updatedAt: now2
109797
110237
  });
109798
- return json9({ success: true, accountId: id }, 201);
110238
+ return json10({ success: true, accountId: id }, 201);
109799
110239
  }
109800
110240
  async function handleUpdateWeixinAccount(req, accountId) {
109801
110241
  const existing = getWeixinAccountById(accountId);
109802
110242
  if (!existing) {
109803
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110243
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109804
110244
  }
109805
110245
  const body = await req.json();
109806
110246
  const updates = {};
109807
110247
  if (body.name !== undefined) {
109808
110248
  const value = body.name.trim();
109809
110249
  if (!value) {
109810
- return json9({ error: t2("weixin.accountNameRequired") }, 400);
110250
+ return json10({ error: t2("weixin.accountNameRequired") }, 400);
109811
110251
  }
109812
110252
  updates.name = value;
109813
110253
  }
@@ -109819,52 +110259,52 @@ async function handleUpdateWeixinAccount(req, accountId) {
109819
110259
  }
109820
110260
  updateWeixinAccount(accountId, updates);
109821
110261
  await weixinService.refresh();
109822
- return json9({ success: true });
110262
+ return json10({ success: true });
109823
110263
  }
109824
110264
  async function handleDeleteWeixinAccount(accountId) {
109825
110265
  const existing = getWeixinAccountById(accountId);
109826
110266
  if (!existing) {
109827
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110267
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109828
110268
  }
109829
110269
  deleteWeixinAccount(accountId);
109830
110270
  await weixinService.refresh();
109831
- return json9({ success: true });
110271
+ return json10({ success: true });
109832
110272
  }
109833
110273
  async function handleStartWeixinLogin(accountId) {
109834
110274
  const existing = getWeixinAccountById(accountId);
109835
110275
  if (!existing) {
109836
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110276
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109837
110277
  }
109838
110278
  try {
109839
110279
  const result = await weixinService.startLogin(accountId);
109840
- return json9(result);
110280
+ return json10(result);
109841
110281
  } catch (err) {
109842
- return json9({ error: err instanceof Error ? err.message : t2("weixin.loginFailed") }, 502);
110282
+ return json10({ error: err instanceof Error ? err.message : t2("weixin.loginFailed") }, 502);
109843
110283
  }
109844
110284
  }
109845
110285
  async function handleGetWeixinLoginStatus(accountId) {
109846
110286
  const existing = getWeixinAccountById(accountId);
109847
110287
  if (!existing) {
109848
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110288
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109849
110289
  }
109850
- return json9(weixinService.getLoginStatus(accountId));
110290
+ return json10(weixinService.getLoginStatus(accountId));
109851
110291
  }
109852
110292
  async function handleListWeixinUsers(accountId) {
109853
110293
  const existing = getWeixinAccountById(accountId);
109854
110294
  if (!existing) {
109855
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110295
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109856
110296
  }
109857
110297
  const users = listWeixinUsersByAccount(accountId);
109858
- return json9({ users });
110298
+ return json10({ users });
109859
110299
  }
109860
110300
  async function handleApproveWeixinUser(accountId, userId) {
109861
110301
  const existing = getWeixinAccountById(accountId);
109862
110302
  if (!existing) {
109863
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110303
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109864
110304
  }
109865
110305
  const user = approveWeixinUser(accountId, userId);
109866
110306
  if (!user) {
109867
- return json9({ error: t2("weixin.userNotFound") }, 404);
110307
+ return json10({ error: t2("weixin.userNotFound") }, 404);
109868
110308
  }
109869
110309
  const settings = getSiteSettings();
109870
110310
  try {
@@ -109875,12 +110315,12 @@ async function handleApproveWeixinUser(accountId, userId) {
109875
110315
  } catch (err) {
109876
110316
  console.error("[weixin] approve ack failed:", err);
109877
110317
  }
109878
- return json9({ user });
110318
+ return json10({ user });
109879
110319
  }
109880
110320
  async function handleTestWeixinUser(accountId, userId) {
109881
110321
  const existing = getWeixinAccountById(accountId);
109882
110322
  if (!existing) {
109883
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110323
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109884
110324
  }
109885
110325
  const settings = getSiteSettings();
109886
110326
  try {
@@ -109889,14 +110329,14 @@ async function handleTestWeixinUser(accountId, userId) {
109889
110329
  time: new Date().toLocaleString(toBCP47(settings.language))
109890
110330
  }));
109891
110331
  } catch (err) {
109892
- return json9({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
110332
+ return json10({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
109893
110333
  }
109894
- return json9({ success: true });
110334
+ return json10({ success: true });
109895
110335
  }
109896
110336
  async function handleTestWeixinAccount(accountId) {
109897
110337
  const existing = getWeixinAccountById(accountId);
109898
110338
  if (!existing) {
109899
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110339
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109900
110340
  }
109901
110341
  const settings = getSiteSettings();
109902
110342
  try {
@@ -109905,26 +110345,26 @@ async function handleTestWeixinAccount(accountId) {
109905
110345
  time: new Date().toLocaleString(toBCP47(settings.language))
109906
110346
  }));
109907
110347
  } catch (err) {
109908
- return json9({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
110348
+ return json10({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
109909
110349
  }
109910
- return json9({ success: true });
110350
+ return json10({ success: true });
109911
110351
  }
109912
110352
  async function handleDeleteWeixinUser(accountId, userId) {
109913
110353
  const existing = getWeixinAccountById(accountId);
109914
110354
  if (!existing) {
109915
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110355
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109916
110356
  }
109917
110357
  deleteWeixinUser(accountId, userId);
109918
- return json9({ success: true });
110358
+ return json10({ success: true });
109919
110359
  }
109920
110360
  async function handleGetWebhooks() {
109921
110361
  const webhooks = getAllWebhookEndpoints();
109922
- return json9({ webhooks });
110362
+ return json10({ webhooks });
109923
110363
  }
109924
110364
  async function handleCreateWebhook(req) {
109925
110365
  const body = await req.json();
109926
110366
  if (!body.url || !body.secret) {
109927
- return json9({ error: t2("apiError.urlAndSecretRequired") }, 400);
110367
+ return json10({ error: t2("apiError.urlAndSecretRequired") }, 400);
109928
110368
  }
109929
110369
  const now2 = new Date().toISOString();
109930
110370
  const endpoint = {
@@ -109937,11 +110377,11 @@ async function handleCreateWebhook(req) {
109937
110377
  updatedAt: now2
109938
110378
  };
109939
110379
  createWebhookEndpoint(endpoint);
109940
- return json9({ webhook: endpoint }, 201);
110380
+ return json10({ webhook: endpoint }, 201);
109941
110381
  }
109942
110382
  async function handleDeleteWebhook(id) {
109943
110383
  deleteWebhookEndpoint(id);
109944
- return json9({ success: true });
110384
+ return json10({ success: true });
109945
110385
  }
109946
110386
  async function handleGetManifest(method) {
109947
110387
  const settings = getSiteSettings();
@@ -109980,7 +110420,7 @@ function manifestJson(data, method) {
109980
110420
  }
109981
110421
  });
109982
110422
  }
109983
- function json9(data, status = 200, headers = {}) {
110423
+ function json10(data, status = 200, headers = {}) {
109984
110424
  return new Response(JSON.stringify(data), {
109985
110425
  status,
109986
110426
  headers: {
@@ -110693,12 +111133,27 @@ var defaultDeps5 = {
110693
111133
  await tmuxRuntimeRegistry.release(deviceId, runtime);
110694
111134
  }
110695
111135
  };
111136
+ function parseWindowLayoutSize(layout) {
111137
+ if (!layout)
111138
+ return null;
111139
+ const match = /^[0-9a-fA-F]{4},(\d+)x(\d+)/.exec(layout);
111140
+ if (!match)
111141
+ return null;
111142
+ return { cols: Number(match[1]), rows: Number(match[2]) };
111143
+ }
110696
111144
 
110697
111145
  class WebSocketServer {
110698
111146
  connections = new Map;
110699
111147
  pendingConnectionEntries = new Map;
111148
+ connectedClients = new Set;
110700
111149
  windowCustomNames = new Map;
110701
111150
  paneCustomNames = new Map;
111151
+ currentTheme = null;
111152
+ themeSignalLast = new Map;
111153
+ lastThemeTimestamp = 0n;
111154
+ pendingTmuxTheme = null;
111155
+ themeApplyInFlight = false;
111156
+ lastBroadcastTheme = new Map;
110702
111157
  deps;
110703
111158
  constructor(options = {}) {
110704
111159
  this.deps = {
@@ -110730,6 +111185,8 @@ class WebSocketServer {
110730
111185
  this.clearReconnectTimer(entry);
110731
111186
  entry.detachRuntime?.();
110732
111187
  entry.detachRuntime = null;
111188
+ this.themeSignalLast.delete(deviceId);
111189
+ this.lastBroadcastTheme.delete(deviceId);
110733
111190
  this.deps.releaseRuntime(deviceId, entry.runtime);
110734
111191
  }
110735
111192
  attachRuntime(deviceId, runtime) {
@@ -110814,6 +111271,7 @@ class WebSocketServer {
110814
111271
  handleOpen(ws) {
110815
111272
  console.log("[ws] client connected");
110816
111273
  sessionStateStore.create(ws);
111274
+ this.connectedClients.add(ws);
110817
111275
  }
110818
111276
  handleMessage(ws, message) {
110819
111277
  if (typeof message === "string") {
@@ -110852,6 +111310,7 @@ class WebSocketServer {
110852
111310
  }
110853
111311
  handleClose(ws) {
110854
111312
  console.log("[ws] client disconnected");
111313
+ this.connectedClients.delete(ws);
110855
111314
  switchBarrier.cleanupClient(ws);
110856
111315
  sessionStateStore.cleanup(ws);
110857
111316
  agentWsHub.removeClient(ws);
@@ -111038,6 +111497,11 @@ class WebSocketServer {
111038
111497
  agentWsHub.unsubscribe(ws, decoded.sessionId);
111039
111498
  return;
111040
111499
  }
111500
+ case exports_ws_borsh.KIND_SITE_THEME_UPDATE: {
111501
+ const decoded = exports_ws_borsh.decodePayload(exports_ws_borsh.schema.SiteThemeUpdateC2SSchema, payload);
111502
+ this.handleSiteThemeUpdate(ws, decoded);
111503
+ return;
111504
+ }
111041
111505
  default:
111042
111506
  this.sendError(ws, refSeq, exports_ws_borsh.ERROR_UNKNOWN_KIND, `Unknown kind: ${kind}`, false);
111043
111507
  }
@@ -111258,13 +111722,18 @@ class WebSocketServer {
111258
111722
  const entry = this.connections.get(deviceId);
111259
111723
  if (!entry)
111260
111724
  return;
111261
- const snapshot = entry.lastSnapshot;
111262
- if (snapshot?.session?.windows) {
111263
- const window2 = snapshot.session.windows.find((w) => w.panes && w.panes.some((p) => p.id === paneId));
111264
- if (window2 && window2.panes && window2.panes.length > 1) {
111265
- entry.runtime.resizeWindow(window2.id, cols, rows);
111725
+ const window2 = entry.lastSnapshot?.session?.windows?.find((w) => w.panes?.some((p) => p.id === paneId));
111726
+ if (window2?.panes && window2.panes.length > 1) {
111727
+ const currentSize = parseWindowLayoutSize(window2.layout);
111728
+ if (currentSize && currentSize.cols === cols && currentSize.rows === rows) {
111266
111729
  return;
111267
111730
  }
111731
+ entry.runtime.resizeWindow(window2.id, cols, rows);
111732
+ return;
111733
+ }
111734
+ const pane = window2?.panes.find((p) => p.id === paneId);
111735
+ if (pane && pane.width === cols && pane.height === rows) {
111736
+ return;
111268
111737
  }
111269
111738
  entry.runtime.resizePane(paneId, cols, rows);
111270
111739
  }
@@ -111355,7 +111824,111 @@ class WebSocketServer {
111355
111824
  const entry = this.connections.get(deviceId);
111356
111825
  if (!entry)
111357
111826
  return;
111358
- entry.runtime.setWindowStyle(style);
111827
+ (async () => {
111828
+ try {
111829
+ await entry.runtime.setWindowStyle(style);
111830
+ } catch (err) {
111831
+ console.error("[ws] setWindowStyle failed:", err);
111832
+ }
111833
+ if (this.currentTheme !== null) {
111834
+ const theme = this.currentTheme;
111835
+ if (this.lastBroadcastTheme.get(deviceId) !== theme) {
111836
+ this.lastBroadcastTheme.set(deviceId, theme);
111837
+ this.broadcastThemeChange(theme);
111838
+ }
111839
+ }
111840
+ })();
111841
+ }
111842
+ handleSiteThemeUpdate(ws, decoded) {
111843
+ if (decoded.theme !== exports_ws_borsh.SITE_THEME_DARK && decoded.theme !== exports_ws_borsh.SITE_THEME_LIGHT) {
111844
+ this.sendError(ws, null, exports_ws_borsh.ERROR_PAYLOAD_DECODE_FAILED, `invalid theme value: ${decoded.theme}`, false);
111845
+ return;
111846
+ }
111847
+ const themeName = decoded.theme === exports_ws_borsh.SITE_THEME_LIGHT ? "light" : "dark";
111848
+ updateSiteSettings({ theme: themeName });
111849
+ this.scheduleTmuxThemeApply(themeName);
111850
+ this.broadcastSiteThemeUpdateS2C(themeName);
111851
+ }
111852
+ scheduleTmuxThemeApply(theme) {
111853
+ this.pendingTmuxTheme = theme;
111854
+ if (this.themeApplyInFlight) {
111855
+ return;
111856
+ }
111857
+ this.themeApplyInFlight = true;
111858
+ (async () => {
111859
+ try {
111860
+ while (this.pendingTmuxTheme !== null) {
111861
+ const next = this.pendingTmuxTheme;
111862
+ this.pendingTmuxTheme = null;
111863
+ await this.handleSiteThemeChange(next);
111864
+ this.broadcastThemeChange(next);
111865
+ }
111866
+ } finally {
111867
+ this.themeApplyInFlight = false;
111868
+ }
111869
+ })();
111870
+ }
111871
+ broadcastSiteThemeUpdateS2C(theme) {
111872
+ const now2 = BigInt(Date.now());
111873
+ if (now2 <= this.lastThemeTimestamp) {
111874
+ this.lastThemeTimestamp += 1n;
111875
+ } else {
111876
+ this.lastThemeTimestamp = now2;
111877
+ }
111878
+ const effectiveTimestamp = this.lastThemeTimestamp;
111879
+ const themeCode = theme === "light" ? exports_ws_borsh.SITE_THEME_LIGHT : exports_ws_borsh.SITE_THEME_DARK;
111880
+ const payloadBytes = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.SiteThemeUpdateS2CSchema, {
111881
+ theme: themeCode,
111882
+ serverTimestamp: effectiveTimestamp
111883
+ });
111884
+ for (const client of this.connectedClients) {
111885
+ this.sendEnvelope(client, exports_ws_borsh.KIND_SITE_THEME_UPDATE, payloadBytes);
111886
+ }
111887
+ }
111888
+ async handleSiteThemeChange(theme) {
111889
+ if (theme !== "dark" && theme !== "light") {
111890
+ return;
111891
+ }
111892
+ this.currentTheme = theme;
111893
+ const style = getTmuxWindowStyle(theme);
111894
+ const results = await Promise.allSettled([...this.connections.values()].map((entry) => entry.runtime.setWindowStyle(style)));
111895
+ for (const result of results) {
111896
+ if (result.status === "rejected") {
111897
+ console.error("[ws] setWindowStyle on theme change failed:", result.reason);
111898
+ }
111899
+ }
111900
+ }
111901
+ applyThemeToDevice(deviceId) {
111902
+ if (this.currentTheme === null) {
111903
+ return;
111904
+ }
111905
+ const entry = this.connections.get(deviceId);
111906
+ if (!entry) {
111907
+ return;
111908
+ }
111909
+ const style = getTmuxWindowStyle(this.currentTheme);
111910
+ entry.runtime.setWindowStyle(style).catch((err) => {
111911
+ console.error(`[ws] setWindowStyle on device ${deviceId} failed:`, err);
111912
+ });
111913
+ }
111914
+ broadcastThemeChange(theme) {
111915
+ const now2 = Date.now();
111916
+ for (const [deviceId, entry] of this.connections) {
111917
+ const last = this.themeSignalLast.get(deviceId);
111918
+ if (last && last.theme === theme && now2 - last.at < 1000) {
111919
+ continue;
111920
+ }
111921
+ this.themeSignalLast.set(deviceId, { theme, at: now2 });
111922
+ this.lastBroadcastTheme.set(deviceId, theme);
111923
+ const panes = entry.lastSnapshot?.session?.windows?.flatMap((w) => w.panes) ?? [];
111924
+ for (const pane of panes) {
111925
+ try {
111926
+ entry.runtime.signalThemeChange(pane.id, theme);
111927
+ } catch (err) {
111928
+ console.error(`[ws] signalThemeChange failed for ${deviceId}/${pane.id}:`, err);
111929
+ }
111930
+ }
111931
+ }
111359
111932
  }
111360
111933
  handleReorderWindows(deviceId, windowIds) {
111361
111934
  setWindowOrder(deviceId, windowIds);
@@ -111499,6 +112072,9 @@ class WebSocketServer {
111499
112072
  runtime = await this.deps.acquireRuntime(deviceId);
111500
112073
  detachRuntime = this.attachRuntime(deviceId, runtime);
111501
112074
  await runtime.connect();
112075
+ if (this.currentTheme !== null) {
112076
+ await runtime.setWindowStyle(getTmuxWindowStyle(this.currentTheme));
112077
+ }
111502
112078
  return {
111503
112079
  runtime,
111504
112080
  detachRuntime,
@@ -111801,10 +112377,16 @@ async function createGatewayRuntime(options = {}) {
111801
112377
  primeLocalShellPath();
111802
112378
  sweepOrphanTransferTemps();
111803
112379
  const wsServer = new WebSocketServer;
112380
+ wsServer.currentTheme = getSiteSettings().theme;
111804
112381
  connectionAlertNotifier.setBroadcaster((deviceId, payload) => {
111805
112382
  wsServer.broadcastDeviceError(deviceId, payload);
111806
112383
  });
111807
112384
  registerSnapshotLookup((deviceId) => wsServer.getLastSnapshot(deviceId));
112385
+ registerThemeBroadcaster((theme) => {
112386
+ wsServer.scheduleTmuxThemeApply(theme);
112387
+ }, (theme) => {
112388
+ wsServer.broadcastSiteThemeUpdateS2C(theme);
112389
+ });
111808
112390
  await telegramService.refresh();
111809
112391
  await weixinService.refresh();
111810
112392
  await pushSupervisor.start();
@@ -111852,6 +112434,7 @@ async function createGatewayRuntime(options = {}) {
111852
112434
  },
111853
112435
  async stop() {
111854
112436
  connectionAlertNotifier.setBroadcaster(null);
112437
+ registerThemeBroadcaster(null);
111855
112438
  wsServer.closeAll();
111856
112439
  await watchService.stop();
111857
112440
  await agentSupervisor.stop();