tmex-cli 0.16.2 → 0.16.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +9 -21
  2. package/dist/runtime/server.js +583 -227
  3. package/package.json +1 -1
  4. package/resources/fe-dist/assets/DevicePage-DtByPprm.js +24 -0
  5. package/resources/fe-dist/assets/{DevicesPage-B_jvUZtl.js → DevicesPage-WMW0FcaU.js} +1 -1
  6. package/resources/fe-dist/assets/{FilePage-DTSmWM1D.js → FilePage-C3yjgRDh.js} +1 -1
  7. package/resources/fe-dist/assets/{SettingsPage-LTHV37Rj.js → SettingsPage-CKZEPwEq.js} +4 -4
  8. package/resources/fe-dist/assets/{agent-tab-vPjPGa5f.js → agent-tab-B77EotmX.js} +1 -1
  9. package/resources/fe-dist/assets/{api-D_FToAy0.js → api-DZDjPn8l.js} +1 -1
  10. package/resources/fe-dist/assets/{arc-BSCyoyGW.js → arc-DNrUdLWP.js} +1 -1
  11. package/resources/fe-dist/assets/{architectureDiagram-3BPJPVTR-CBO0dAe2.js → architectureDiagram-3BPJPVTR-B5DYf-5o.js} +1 -1
  12. package/resources/fe-dist/assets/{blockDiagram-GPEHLZMM-0ONANm30.js → blockDiagram-GPEHLZMM-DSgjcUbw.js} +1 -1
  13. package/resources/fe-dist/assets/{c4Diagram-AAUBKEIU-DpvhCnF8.js → c4Diagram-AAUBKEIU-DKjqulHT.js} +1 -1
  14. package/resources/fe-dist/assets/{card-CD9i-fLq.js → card-DWQzPt-3.js} +1 -1
  15. package/resources/fe-dist/assets/channel-CpZANba-.js +1 -0
  16. package/resources/fe-dist/assets/{chunk-2J33WTMH-MmS9_ur_.js → chunk-2J33WTMH-DHQ_-AlN.js} +1 -1
  17. package/resources/fe-dist/assets/{chunk-4BX2VUAB-CLd4Yxwh.js → chunk-4BX2VUAB-C7sszhBq.js} +1 -1
  18. package/resources/fe-dist/assets/{chunk-55IACEB6-DgCH4WwT.js → chunk-55IACEB6-BkPT2okv.js} +1 -1
  19. package/resources/fe-dist/assets/{chunk-727SXJPM-B9DM5PB4.js → chunk-727SXJPM-BsD5c54G.js} +1 -1
  20. package/resources/fe-dist/assets/{chunk-AQP2D5EJ-DQwC9D9W.js → chunk-AQP2D5EJ-Dx3mc4xO.js} +1 -1
  21. package/resources/fe-dist/assets/{chunk-FMBD7UC4-BnvyNYGh.js → chunk-FMBD7UC4-CyZO-UAp.js} +1 -1
  22. package/resources/fe-dist/assets/{chunk-ND2GUHAM-BzcZgEWG.js → chunk-ND2GUHAM-DNxRBeA7.js} +1 -1
  23. package/resources/fe-dist/assets/{chunk-QZHKN3VN-C9BaHIUI.js → chunk-QZHKN3VN-DsPa138W.js} +1 -1
  24. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-BW09ep_0.js +1 -0
  25. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-BW09ep_0.js +1 -0
  26. package/resources/fe-dist/assets/{copy-DzIk8AxV.js → copy-DwXKDgGT.js} +1 -1
  27. package/resources/fe-dist/assets/{cose-bilkent-S5V4N54A-NkTc0ObP.js → cose-bilkent-S5V4N54A-C1d180_v.js} +1 -1
  28. package/resources/fe-dist/assets/{dagre-BM42HDAG-BdFDBfE_.js → dagre-BM42HDAG-Bv8hvnH7.js} +1 -1
  29. package/resources/fe-dist/assets/{diagram-2AECGRRQ-Ba4Y0igR.js → diagram-2AECGRRQ-DfbJBHI6.js} +1 -1
  30. package/resources/fe-dist/assets/{diagram-5GNKFQAL-B4_Or8Vp.js → diagram-5GNKFQAL-COnOrAiu.js} +1 -1
  31. package/resources/fe-dist/assets/{diagram-KO2AKTUF-DbmmHTYj.js → diagram-KO2AKTUF-BOtU5EdV.js} +1 -1
  32. package/resources/fe-dist/assets/{diagram-LMA3HP47-CRrd6lc4.js → diagram-LMA3HP47-B-WcT-Mc.js} +1 -1
  33. package/resources/fe-dist/assets/{diagram-OG6HWLK6-BOEazFIu.js → diagram-OG6HWLK6-BptGbu0F.js} +1 -1
  34. package/resources/fe-dist/assets/{erDiagram-TEJ5UH35-Dk-UowK8.js → erDiagram-TEJ5UH35-H06uoCHU.js} +1 -1
  35. package/resources/fe-dist/assets/{files-tab-D2vddvsj.js → files-tab-C841Mmn_.js} +1 -1
  36. package/resources/fe-dist/assets/{flowDiagram-I6XJVG4X-BhbGYx-g.js → flowDiagram-I6XJVG4X-QOk_IVWN.js} +1 -1
  37. package/resources/fe-dist/assets/{ganttDiagram-6RSMTGT7-iezClDTv.js → ganttDiagram-6RSMTGT7-Q7s2jHyi.js} +1 -1
  38. package/resources/fe-dist/assets/{gitGraphDiagram-PVQCEYII-Ncqf3CQw.js → gitGraphDiagram-PVQCEYII-Q7tAtx1u.js} +1 -1
  39. package/resources/fe-dist/assets/{index-DOfY8kwB.js → index-C2v9tHtI.js} +1 -1
  40. package/resources/fe-dist/assets/{index-UoApkLSY.js → index-Dc3Fwpho.js} +63 -63
  41. package/resources/fe-dist/assets/{infoDiagram-5YYISTIA-CUg3PkAb.js → infoDiagram-5YYISTIA-B9n7MrtF.js} +1 -1
  42. package/resources/fe-dist/assets/{ishikawaDiagram-YF4QCWOH-DU0ekJw5.js → ishikawaDiagram-YF4QCWOH-oZeau8IS.js} +1 -1
  43. package/resources/fe-dist/assets/{journeyDiagram-JHISSGLW-Ji7WtMH7.js → journeyDiagram-JHISSGLW-ClXjUmc1.js} +1 -1
  44. package/resources/fe-dist/assets/{kanban-definition-UN3LZRKU-B8zVyM8Q.js → kanban-definition-UN3LZRKU-Cwn4VzH2.js} +1 -1
  45. package/resources/fe-dist/assets/{linear-tNPPicfI.js → linear-BNOQBDiB.js} +1 -1
  46. package/resources/fe-dist/assets/{markdown-preview-CzbcKxcJ.js → markdown-preview-BsYVAcJ1.js} +3 -3
  47. package/resources/fe-dist/assets/{mermaid.core-BoNU6G1d.js → mermaid.core-C3nmuA6R.js} +5 -5
  48. package/resources/fe-dist/assets/{mindmap-definition-RKZ34NQL-D_v1LaNP.js → mindmap-definition-RKZ34NQL-BKstxajf.js} +1 -1
  49. package/resources/fe-dist/assets/{pieDiagram-4H26LBE5-B1CHCpFf.js → pieDiagram-4H26LBE5-D_2bHoC9.js} +1 -1
  50. package/resources/fe-dist/assets/{quadrantDiagram-W4KKPZXB-DvA3zgR7.js → quadrantDiagram-W4KKPZXB-D6K_QmUy.js} +1 -1
  51. package/resources/fe-dist/assets/{requirementDiagram-4Y6WPE33-BCna6ZG5.js → requirementDiagram-4Y6WPE33-B1SWUDkA.js} +1 -1
  52. package/resources/fe-dist/assets/{sankeyDiagram-5OEKKPKP-KEllFLAf.js → sankeyDiagram-5OEKKPKP-DPmalO0b.js} +1 -1
  53. package/resources/fe-dist/assets/{send-BgCF67Uc.js → send-J45szXFM.js} +1 -1
  54. package/resources/fe-dist/assets/{sequenceDiagram-3UESZ5HK-DRsq_jg_.js → sequenceDiagram-3UESZ5HK-C08DcZug.js} +1 -1
  55. package/resources/fe-dist/assets/{stateDiagram-AJRCARHV-DhATBo8D.js → stateDiagram-AJRCARHV-K9bVAVhg.js} +1 -1
  56. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-DTGkSC6X.js +1 -0
  57. package/resources/fe-dist/assets/{terminal-settings-panel-DsMMZFBi.js → terminal-settings-panel-tZuaqdqz.js} +2 -2
  58. package/resources/fe-dist/assets/{timeline-definition-PNZ67QCA-Det7AzPd.js → timeline-definition-PNZ67QCA-DG0FtOvK.js} +1 -1
  59. package/resources/fe-dist/assets/{transfer-toast-CHcCj3qf.js → transfer-toast-D72q4082.js} +1 -1
  60. package/resources/fe-dist/assets/{triangle-alert-BjHP6Ipw.js → triangle-alert-CagsqsWp.js} +1 -1
  61. package/resources/fe-dist/assets/{vennDiagram-CIIHVFJN-BF4R466L.js → vennDiagram-CIIHVFJN-CKBEKUkA.js} +1 -1
  62. package/resources/fe-dist/assets/{wardley-L42UT6IY-DLHZ_6-V.js → wardley-L42UT6IY-DTlpnptg.js} +1 -1
  63. package/resources/fe-dist/assets/{wardleyDiagram-YWT4CUSO-CekN3Ye6.js → wardleyDiagram-YWT4CUSO-CnwEikRb.js} +1 -1
  64. package/resources/fe-dist/assets/{xychartDiagram-2RQKCTM6-CJD3yg0M.js → xychartDiagram-2RQKCTM6-uaYRX4Ue.js} +1 -1
  65. package/resources/fe-dist/assets/{zap-Dx7JTXJN.js → zap-DcJ8Gp1I.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",
@@ -33329,8 +33401,12 @@ var siteSettings = sqliteTable("site_settings", {
33329
33401
  sshReconnectMaxRetries: integer("ssh_reconnect_max_retries").notNull(),
33330
33402
  sshReconnectDelaySeconds: integer("ssh_reconnect_delay_seconds").notNull(),
33331
33403
  language: text("language").notNull().default("en_US"),
33404
+ theme: text("theme").notNull().default("dark"),
33332
33405
  updatedAt: text("updated_at").notNull()
33333
- }, (table) => [check("site_settings_singleton_check", sql`${table.id} = 1`)]);
33406
+ }, (table) => [
33407
+ check("site_settings_singleton_check", sql`${table.id} = 1`),
33408
+ check("site_settings_theme_check", sql`${table.theme} in ('dark', 'light')`)
33409
+ ]);
33334
33410
  var terminalShortcutSettings = sqliteTable("terminal_shortcut_settings", {
33335
33411
  id: integer("id").primaryKey(),
33336
33412
  items: text("items", { mode: "json" }).$type().notNull().default(DEFAULT_TERMINAL_SHORTCUTS),
@@ -33624,6 +33700,7 @@ function toSiteSettings(row) {
33624
33700
  sshReconnectMaxRetries: row.sshReconnectMaxRetries,
33625
33701
  sshReconnectDelaySeconds: row.sshReconnectDelaySeconds,
33626
33702
  language: normalizeLocale(row.language),
33703
+ theme: row.theme,
33627
33704
  updatedAt: row.updatedAt
33628
33705
  };
33629
33706
  }
@@ -33917,6 +33994,7 @@ function updateSiteSettings(updates) {
33917
33994
  sshReconnectMaxRetries: updates.sshReconnectMaxRetries ?? current.sshReconnectMaxRetries,
33918
33995
  sshReconnectDelaySeconds: updates.sshReconnectDelaySeconds ?? current.sshReconnectDelaySeconds,
33919
33996
  language: updates.language ? normalizeLocale(updates.language) : current.language,
33997
+ theme: updates.theme ?? current.theme,
33920
33998
  updatedAt: new Date().toISOString()
33921
33999
  };
33922
34000
  const orm = getDb();
@@ -33928,7 +34006,8 @@ function updateSiteSettings(updates) {
33928
34006
  enableBrowserNotificationToast: next.enableBrowserNotificationToast,
33929
34007
  enableNotificationPush: next.enableNotificationPush,
33930
34008
  enableBellPush: next.enableBellPush,
33931
- enableBellSound: next.enableBellSound
34009
+ enableBellSound: next.enableBellSound,
34010
+ theme: next.theme
33932
34011
  }).where(eq(siteSettings.id, 1)).run();
33933
34012
  siteSettingsCache = { value: next, expiresAt: Date.now() + SITE_SETTINGS_TTL_MS };
33934
34013
  if (instance.language !== next.language) {
@@ -99345,7 +99424,13 @@ class LocalExternalTmuxConnection {
99345
99424
  if (!this.connected) {
99346
99425
  return;
99347
99426
  }
99348
- const argv = ["new-window", "-t", this.sessionName, "-c", cwd ?? this.resolveDefaultWorkingDir()];
99427
+ const argv = [
99428
+ "new-window",
99429
+ "-t",
99430
+ this.sessionName,
99431
+ "-c",
99432
+ cwd ?? this.resolveDefaultWorkingDir()
99433
+ ];
99349
99434
  if (name24) {
99350
99435
  argv.push("-n", name24);
99351
99436
  }
@@ -99492,6 +99577,11 @@ class LocalExternalTmuxConnection {
99492
99577
  this.callbacks.onError(error51);
99493
99578
  });
99494
99579
  }
99580
+ signalThemeChange(_paneId, _theme) {
99581
+ if (!this.connected) {
99582
+ return;
99583
+ }
99584
+ }
99495
99585
  async capturePaneText(paneId, opts) {
99496
99586
  if (!this.connected) {
99497
99587
  throw new Error(`tmux connection not available: ${this.deviceId}`);
@@ -99518,7 +99608,14 @@ class LocalExternalTmuxConnection {
99518
99608
  if (exists3.exitCode === 0) {
99519
99609
  return;
99520
99610
  }
99521
- await this.runTmux(["new-session", "-d", "-c", this.resolveDefaultWorkingDir(), "-s", this.sessionName]);
99611
+ await this.runTmux([
99612
+ "new-session",
99613
+ "-d",
99614
+ "-c",
99615
+ this.resolveDefaultWorkingDir(),
99616
+ "-s",
99617
+ this.sessionName
99618
+ ]);
99522
99619
  }
99523
99620
  async configureSessionOptions() {
99524
99621
  await this.runTmuxAllowFailure([
@@ -99529,7 +99626,14 @@ class LocalExternalTmuxConnection {
99529
99626
  "allow-passthrough",
99530
99627
  config.tmuxAllowPassthrough ? "on" : "off"
99531
99628
  ]);
99532
- await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "extended-keys", "on"]);
99629
+ await this.runTmuxAllowFailure([
99630
+ "set-option",
99631
+ "-t",
99632
+ this.sessionName,
99633
+ "-g",
99634
+ "extended-keys",
99635
+ "on"
99636
+ ]);
99533
99637
  await this.runTmuxAllowFailure([
99534
99638
  "set-option",
99535
99639
  "-t",
@@ -99538,7 +99642,14 @@ class LocalExternalTmuxConnection {
99538
99642
  "extended-keys-format",
99539
99643
  "csi-u"
99540
99644
  ]);
99541
- await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "off"]);
99645
+ await this.runTmuxAllowFailure([
99646
+ "set-option",
99647
+ "-t",
99648
+ this.sessionName,
99649
+ "-g",
99650
+ "focus-events",
99651
+ "off"
99652
+ ]);
99542
99653
  await this.runTmuxAllowFailure([
99543
99654
  "set-option",
99544
99655
  "-t",
@@ -99899,7 +100010,14 @@ class LocalExternalTmuxConnection {
99899
100010
  async closeWindowInternal(windowId) {
99900
100011
  const count2 = Number.parseInt((await this.runTmux(["display-message", "-p", "-t", this.sessionName, "#{session_windows}"])).stdout.trim() || "0", 10);
99901
100012
  if (count2 <= 1) {
99902
- await this.runTmux(["new-window", "-d", "-t", this.sessionName, "-c", this.resolveDefaultWorkingDir()]);
100013
+ await this.runTmux([
100014
+ "new-window",
100015
+ "-d",
100016
+ "-t",
100017
+ this.sessionName,
100018
+ "-c",
100019
+ this.resolveDefaultWorkingDir()
100020
+ ]);
99903
100021
  }
99904
100022
  await this.runAndRefresh(["kill-window", "-t", windowId], true);
99905
100023
  }
@@ -100008,7 +100126,14 @@ class LocalExternalTmuxConnection {
100008
100126
  "-F",
100009
100127
  WINDOW_SNAPSHOT_FORMAT
100010
100128
  ]),
100011
- this.runTmuxAllowFailure(["list-panes", "-s", "-t", this.sessionName, "-F", PANE_SNAPSHOT_FORMAT])
100129
+ this.runTmuxAllowFailure([
100130
+ "list-panes",
100131
+ "-s",
100132
+ "-t",
100133
+ this.sessionName,
100134
+ "-F",
100135
+ PANE_SNAPSHOT_FORMAT
100136
+ ])
100012
100137
  ]);
100013
100138
  const transientResult = [sessionRes, windowsRes, panesRes].find((res) => res.exitCode === TMUX_SPAWN_UNAVAILABLE_EXIT);
100014
100139
  if (transientResult) {
@@ -100294,6 +100419,45 @@ function joinShellArgs(argv) {
100294
100419
  return argv.map((arg) => quoteShellArg(arg)).join(" ");
100295
100420
  }
100296
100421
 
100422
+ // ../../apps/gateway/src/tmux-client/ssh-bootstrap.ts
100423
+ function buildSshBootstrapScript() {
100424
+ return [
100425
+ ". /etc/profile 2>/dev/null || true",
100426
+ '[ -f "$HOME/.profile" ] && . "$HOME/.profile" 2>/dev/null || true',
100427
+ '[ -f "$HOME/.bash_profile" ] && . "$HOME/.bash_profile" 2>/dev/null || true',
100428
+ 'TMUX_BIN="$(command -v tmux 2>/dev/null || true)"',
100429
+ 'if [ -z "$TMUX_BIN" ]; then',
100430
+ " for p in /usr/local/bin/tmux /opt/homebrew/bin/tmux /usr/bin/tmux /bin/tmux; do",
100431
+ ' [ -x "$p" ] && TMUX_BIN="$p" && break',
100432
+ " done",
100433
+ "fi",
100434
+ 'HOME_DIR="${HOME:-$(pwd)}"',
100435
+ 'if [ -z "$TMUX_BIN" ]; then',
100436
+ " printf 'TMEX_BOOT_FAIL\\ttmux_not_found\\n'",
100437
+ "else",
100438
+ ` printf 'TMEX_BOOT_OK\\t%s\\t%s\\t%s\\n' "$TMUX_BIN" "$("$TMUX_BIN" -V 2>/dev/null)" "$HOME_DIR"`,
100439
+ "fi"
100440
+ ].join(`
100441
+ `);
100442
+ }
100443
+ function parseSshBootstrapOutput(output) {
100444
+ const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
100445
+ for (const line of lines) {
100446
+ if (line.startsWith("TMEX_BOOT_OK\t")) {
100447
+ const [, tmuxBin = "", tmuxVersion = "", homeDir = ""] = line.split("\t");
100448
+ if (!tmuxBin || !homeDir) {
100449
+ return { ok: false, reason: "invalid_bootstrap_payload" };
100450
+ }
100451
+ return { ok: true, tmuxBin, tmuxVersion, homeDir };
100452
+ }
100453
+ if (line.startsWith("TMEX_BOOT_FAIL\t")) {
100454
+ const [, reason = "tmux_bootstrap_failed"] = line.split("\t");
100455
+ return { ok: false, reason };
100456
+ }
100457
+ }
100458
+ return { ok: false, reason: "missing_bootstrap_marker" };
100459
+ }
100460
+
100297
100461
  // ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
100298
100462
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
100299
100463
  import { join as join3 } from "path";
@@ -100586,45 +100750,6 @@ async function resolveSshConnectConfig(device, decrypt2, inputDeps = {}) {
100586
100750
  return authConfig;
100587
100751
  }
100588
100752
 
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
100753
  // ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
100629
100754
  function hasRenderableTerminalContent2(value) {
100630
100755
  return value.trim().length > 0;
@@ -100725,6 +100850,11 @@ class SshExternalTmuxConnection {
100725
100850
  });
100726
100851
  }
100727
100852
  }
100853
+ signalThemeChange(_paneId, _theme) {
100854
+ if (!this.connected) {
100855
+ return;
100856
+ }
100857
+ }
100728
100858
  resizePane(paneId, cols, rows) {
100729
100859
  if (!this.connected) {
100730
100860
  return;
@@ -100761,7 +100891,13 @@ class SshExternalTmuxConnection {
100761
100891
  if (!this.connected) {
100762
100892
  return;
100763
100893
  }
100764
- const argv = ["new-window", "-t", this.sessionName, "-c", cwd ?? this.resolveDefaultWorkingDir()];
100894
+ const argv = [
100895
+ "new-window",
100896
+ "-t",
100897
+ this.sessionName,
100898
+ "-c",
100899
+ cwd ?? this.resolveDefaultWorkingDir()
100900
+ ];
100765
100901
  if (name24) {
100766
100902
  argv.push("-n", name24);
100767
100903
  }
@@ -101040,7 +101176,14 @@ class SshExternalTmuxConnection {
101040
101176
  if (exists3.exitCode === 0) {
101041
101177
  return;
101042
101178
  }
101043
- await this.runTmux(["new-session", "-d", "-c", this.resolveDefaultWorkingDir(), "-s", this.sessionName]);
101179
+ await this.runTmux([
101180
+ "new-session",
101181
+ "-d",
101182
+ "-c",
101183
+ this.resolveDefaultWorkingDir(),
101184
+ "-s",
101185
+ this.sessionName
101186
+ ]);
101044
101187
  }
101045
101188
  async configureSessionOptions() {
101046
101189
  await this.runTmuxAllowFailure([
@@ -101051,7 +101194,14 @@ class SshExternalTmuxConnection {
101051
101194
  "allow-passthrough",
101052
101195
  config.tmuxAllowPassthrough ? "on" : "off"
101053
101196
  ]);
101054
- await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "extended-keys", "on"]);
101197
+ await this.runTmuxAllowFailure([
101198
+ "set-option",
101199
+ "-t",
101200
+ this.sessionName,
101201
+ "-g",
101202
+ "extended-keys",
101203
+ "on"
101204
+ ]);
101055
101205
  await this.runTmuxAllowFailure([
101056
101206
  "set-option",
101057
101207
  "-t",
@@ -101060,7 +101210,14 @@ class SshExternalTmuxConnection {
101060
101210
  "extended-keys-format",
101061
101211
  "csi-u"
101062
101212
  ]);
101063
- await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "off"]);
101213
+ await this.runTmuxAllowFailure([
101214
+ "set-option",
101215
+ "-t",
101216
+ this.sessionName,
101217
+ "-g",
101218
+ "focus-events",
101219
+ "off"
101220
+ ]);
101064
101221
  await this.runTmuxAllowFailure([
101065
101222
  "set-option",
101066
101223
  "-t",
@@ -101108,6 +101265,7 @@ class SshExternalTmuxConnection {
101108
101265
  if (!windowStyle) {
101109
101266
  return;
101110
101267
  }
101268
+ const startedAt = config.isDev ? Date.now() : 0;
101111
101269
  await this.runTmuxAllowFailure([
101112
101270
  "set-hook",
101113
101271
  "-t",
@@ -101123,22 +101281,26 @@ class SshExternalTmuxConnection {
101123
101281
  "#{window_id}"
101124
101282
  ]);
101125
101283
  if (windows.exitCode !== 0) {
101284
+ if (config.isDev) {
101285
+ console.debug(`[ssh] configureWindowStyle deviceId=${this.deviceId} elapsed=${Date.now() - startedAt}ms (list-windows failed)`);
101286
+ }
101126
101287
  return;
101127
101288
  }
101289
+ const windowIds = [];
101128
101290
  for (const line of windows.stdout.split(`
101129
101291
  `)) {
101130
101292
  const windowId = line.trim();
101131
101293
  if (!windowId) {
101132
101294
  continue;
101133
101295
  }
101134
- await this.runTmuxAllowFailure([
101135
- "set-option",
101136
- "-w",
101137
- "-t",
101138
- windowId,
101139
- "window-style",
101140
- windowStyle
101141
- ]);
101296
+ windowIds.push(windowId);
101297
+ }
101298
+ if (windowIds.length > 0) {
101299
+ const setOptions = windowIds.map((id) => `${quoteShellArg(this.tmuxBin)} set-option -w -t ${quoteShellArg(id)} window-style ${quoteShellArg(windowStyle)}`).join(" && ");
101300
+ await this.runShellAllowFailure(setOptions);
101301
+ }
101302
+ if (config.isDev) {
101303
+ console.debug(`[ssh] configureWindowStyle deviceId=${this.deviceId} windows=${windowIds.length} elapsed=${Date.now() - startedAt}ms`);
101142
101304
  }
101143
101305
  }
101144
101306
  async ensureGhosttyTerminfo() {
@@ -101380,7 +101542,14 @@ class SshExternalTmuxConnection {
101380
101542
  async closeWindowInternal(windowId) {
101381
101543
  const count2 = Number.parseInt((await this.runTmux(["display-message", "-p", "-t", this.sessionName, "#{session_windows}"])).stdout.trim() || "0", 10);
101382
101544
  if (count2 <= 1) {
101383
- await this.runTmux(["new-window", "-d", "-t", this.sessionName, "-c", this.resolveDefaultWorkingDir()]);
101545
+ await this.runTmux([
101546
+ "new-window",
101547
+ "-d",
101548
+ "-t",
101549
+ this.sessionName,
101550
+ "-c",
101551
+ this.resolveDefaultWorkingDir()
101552
+ ]);
101384
101553
  }
101385
101554
  await this.runAndRefresh(["kill-window", "-t", windowId], true);
101386
101555
  }
@@ -101489,7 +101658,14 @@ class SshExternalTmuxConnection {
101489
101658
  "-F",
101490
101659
  WINDOW_SNAPSHOT_FORMAT
101491
101660
  ]),
101492
- this.runTmuxAllowFailure(["list-panes", "-s", "-t", this.sessionName, "-F", PANE_SNAPSHOT_FORMAT])
101661
+ this.runTmuxAllowFailure([
101662
+ "list-panes",
101663
+ "-s",
101664
+ "-t",
101665
+ this.sessionName,
101666
+ "-F",
101667
+ PANE_SNAPSHOT_FORMAT
101668
+ ])
101493
101669
  ]);
101494
101670
  if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
101495
101671
  const stderrBlob = `${sessionRes.stderr}
@@ -102015,6 +102191,9 @@ class DeviceSessionRuntime {
102015
102191
  setWindowStyle(style) {
102016
102192
  this.connection.setWindowStyle(style);
102017
102193
  }
102194
+ signalThemeChange(paneId, theme) {
102195
+ this.connection.signalThemeChange(paneId, theme);
102196
+ }
102018
102197
  async capturePaneText(paneId, opts) {
102019
102198
  return this.connection.capturePaneText(paneId, opts);
102020
102199
  }
@@ -107545,8 +107724,8 @@ function getBaseVersion() {
107545
107724
  if (cachedBase !== undefined)
107546
107725
  return cachedBase;
107547
107726
  let base = null;
107548
- if ("0.16.2") {
107549
- base = "0.16.2";
107727
+ if ("0.16.3") {
107728
+ base = "0.16.3";
107550
107729
  }
107551
107730
  if (!base && config.isProd) {
107552
107731
  base = readInstallMeta()?.cliVersion ?? null;
@@ -107921,6 +108100,62 @@ async function handleDeviceTestConnection(deviceId, inputDeps = {}) {
107921
108100
  }
107922
108101
  }
107923
108102
 
108103
+ // ../../apps/gateway/src/tmux/theme-broadcaster.ts
108104
+ var tmuxBroadcaster = null;
108105
+ var s2cBroadcaster = null;
108106
+ function registerThemeBroadcaster(tmux, s2c = null) {
108107
+ tmuxBroadcaster = tmux;
108108
+ s2cBroadcaster = s2c;
108109
+ }
108110
+ function broadcastThemeChange(theme) {
108111
+ tmuxBroadcaster?.(theme);
108112
+ }
108113
+ function broadcastSiteThemeUpdateS2C(theme) {
108114
+ s2cBroadcaster?.(theme);
108115
+ }
108116
+
108117
+ // ../../apps/gateway/src/api/theme.ts
108118
+ var VALID_THEMES = ["dark", "light"];
108119
+ function handleThemeApiRequest(req, path) {
108120
+ if (path === "/api/settings/theme" && req.method === "GET") {
108121
+ return handleGetTheme();
108122
+ }
108123
+ if (path === "/api/settings/theme" && req.method === "POST") {
108124
+ return handleUpdateTheme(req);
108125
+ }
108126
+ return null;
108127
+ }
108128
+ function handleGetTheme() {
108129
+ const settings = getSiteSettings();
108130
+ return json8({ theme: settings.theme, serverTimestamp: Date.now() });
108131
+ }
108132
+ async function handleUpdateTheme(req) {
108133
+ let body;
108134
+ try {
108135
+ body = await req.json();
108136
+ } catch {
108137
+ return json8({ error: "invalid request body" }, 400);
108138
+ }
108139
+ if (typeof body !== "object" || body === null || Array.isArray(body)) {
108140
+ return json8({ error: "invalid request body" }, 400);
108141
+ }
108142
+ const { theme } = body;
108143
+ if (typeof theme !== "string" || !VALID_THEMES.includes(theme)) {
108144
+ return json8({ error: "theme must be one of: dark, light" }, 400);
108145
+ }
108146
+ const serverTimestamp = Date.now();
108147
+ updateSiteSettings({ theme });
108148
+ broadcastThemeChange(theme);
108149
+ broadcastSiteThemeUpdateS2C(theme);
108150
+ return json8({ theme, serverTimestamp });
108151
+ }
108152
+ function json8(data, status = 200) {
108153
+ return new Response(JSON.stringify(data), {
108154
+ status,
108155
+ headers: { "Content-Type": "application/json" }
108156
+ });
108157
+ }
108158
+
107924
108159
  // ../../apps/gateway/src/db/watch.ts
107925
108160
  function createWatchRule(input) {
107926
108161
  const orm = getDb();
@@ -109019,35 +109254,35 @@ async function handleListRules(req) {
109019
109254
  if (paneId) {
109020
109255
  rules = rules.filter((rule) => rule.paneId === paneId);
109021
109256
  }
109022
- return json8({ rules: rules.map(toRuleDto) });
109257
+ return json9({ rules: rules.map(toRuleDto) });
109023
109258
  }
109024
109259
  async function handleCreateRule(req, deps) {
109025
109260
  const raw = await readJsonObjectBody3(req);
109026
109261
  if (!raw) {
109027
- return json8({ error: t2("apiError.invalidRequest") }, 400);
109262
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
109028
109263
  }
109029
109264
  const body = raw;
109030
109265
  const name24 = typeof body.name === "string" ? body.name.trim() : "";
109031
109266
  if (!name24) {
109032
- return json8({ error: t2("apiError.watchNameRequired") }, 400);
109267
+ return json9({ error: t2("apiError.watchNameRequired") }, 400);
109033
109268
  }
109034
109269
  const deviceId = typeof body.deviceId === "string" ? body.deviceId.trim() : "";
109035
109270
  if (!deviceId) {
109036
- return json8({ error: t2("apiError.agentDeviceRequired") }, 400);
109271
+ return json9({ error: t2("apiError.agentDeviceRequired") }, 400);
109037
109272
  }
109038
109273
  if (!getDeviceById(deviceId)) {
109039
- return json8({ error: t2("apiError.deviceNotFound") }, 404);
109274
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
109040
109275
  }
109041
109276
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
109042
109277
  if (!paneId) {
109043
- return json8({ error: t2("apiError.agentPaneRequired") }, 400);
109278
+ return json9({ error: t2("apiError.agentPaneRequired") }, 400);
109044
109279
  }
109045
109280
  if (!TRIGGER_TYPES.includes(body.triggerType)) {
109046
- return json8({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109281
+ return json9({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109047
109282
  }
109048
109283
  const parsed = parseRuleFields(raw);
109049
109284
  if (!parsed.ok) {
109050
- return json8({ error: parsed.error }, 400);
109285
+ return json9({ error: parsed.error }, 400);
109051
109286
  }
109052
109287
  const fields = parsed.fields;
109053
109288
  const effective = {
@@ -109060,7 +109295,7 @@ async function handleCreateRule(req, deps) {
109060
109295
  };
109061
109296
  const semanticError = validateRuleSemantics(effective);
109062
109297
  if (semanticError) {
109063
- return json8({ error: semanticError }, 400);
109298
+ return json9({ error: semanticError }, 400);
109064
109299
  }
109065
109300
  const rule = createWatchRule({
109066
109301
  name: name24,
@@ -109083,50 +109318,50 @@ async function handleCreateRule(req, deps) {
109083
109318
  cooldownSeconds: fields.cooldownSeconds
109084
109319
  });
109085
109320
  await deps.service.refreshRule(rule.id);
109086
- return json8({ rule: toRuleDto(rule), state: null }, 201);
109321
+ return json9({ rule: toRuleDto(rule), state: null }, 201);
109087
109322
  }
109088
109323
  async function handleGetRule(id) {
109089
109324
  const rule = getWatchRuleById(id);
109090
109325
  if (!rule) {
109091
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109326
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109092
109327
  }
109093
109328
  const state = getWatchRuleState(id);
109094
- return json8({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109329
+ return json9({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109095
109330
  }
109096
109331
  async function handleUpdateRule(req, id, deps) {
109097
109332
  const existing = getWatchRuleById(id);
109098
109333
  if (!existing) {
109099
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109334
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109100
109335
  }
109101
109336
  const raw = await readJsonObjectBody3(req);
109102
109337
  if (!raw) {
109103
- return json8({ error: t2("apiError.invalidRequest") }, 400);
109338
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
109104
109339
  }
109105
109340
  const body = raw;
109106
109341
  const updates = {};
109107
109342
  if (body.name !== undefined) {
109108
109343
  const name24 = typeof body.name === "string" ? body.name.trim() : "";
109109
109344
  if (!name24) {
109110
- return json8({ error: t2("apiError.watchNameRequired") }, 400);
109345
+ return json9({ error: t2("apiError.watchNameRequired") }, 400);
109111
109346
  }
109112
109347
  updates.name = name24;
109113
109348
  }
109114
109349
  if (body.paneId !== undefined) {
109115
109350
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
109116
109351
  if (!paneId) {
109117
- return json8({ error: t2("apiError.agentPaneRequired") }, 400);
109352
+ return json9({ error: t2("apiError.agentPaneRequired") }, 400);
109118
109353
  }
109119
109354
  updates.paneId = paneId;
109120
109355
  }
109121
109356
  if (body.triggerType !== undefined) {
109122
109357
  if (!TRIGGER_TYPES.includes(body.triggerType)) {
109123
- return json8({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109358
+ return json9({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109124
109359
  }
109125
109360
  updates.triggerType = body.triggerType;
109126
109361
  }
109127
109362
  const parsed = parseRuleFields(raw);
109128
109363
  if (!parsed.ok) {
109129
- return json8({ error: parsed.error }, 400);
109364
+ return json9({ error: parsed.error }, 400);
109130
109365
  }
109131
109366
  const fields = parsed.fields;
109132
109367
  Object.assign(updates, fields);
@@ -109140,32 +109375,32 @@ async function handleUpdateRule(req, id, deps) {
109140
109375
  };
109141
109376
  const semanticError = validateRuleSemantics(effective);
109142
109377
  if (semanticError) {
109143
- return json8({ error: semanticError }, 400);
109378
+ return json9({ error: semanticError }, 400);
109144
109379
  }
109145
109380
  const rule = updateWatchRule(id, updates);
109146
109381
  if (!rule) {
109147
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109382
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109148
109383
  }
109149
109384
  await deps.service.refreshRule(id);
109150
109385
  const state = getWatchRuleState(id);
109151
- return json8({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109386
+ return json9({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109152
109387
  }
109153
109388
  async function handleDeleteRule(id, deps) {
109154
109389
  const existing = getWatchRuleById(id);
109155
109390
  if (!existing) {
109156
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109391
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109157
109392
  }
109158
109393
  deleteWatchRule(id);
109159
109394
  await deps.service.removeRule(id);
109160
- return json8({ success: true });
109395
+ return json9({ success: true });
109161
109396
  }
109162
109397
  async function handleGetRuleState(id, deps) {
109163
109398
  const rule = getWatchRuleById(id);
109164
109399
  if (!rule) {
109165
- return json8({ error: t2("apiError.watchRuleNotFound") }, 404);
109400
+ return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109166
109401
  }
109167
109402
  const state = getWatchRuleState(id);
109168
- return json8({
109403
+ return json9({
109169
109404
  state: state ? toStateDto(state) : null,
109170
109405
  samples: deps.service.getSamples(id)
109171
109406
  });
@@ -109189,17 +109424,17 @@ function buildAssistPrompt(description, screen) {
109189
109424
  async function handleAssistRegex(req, deps) {
109190
109425
  const raw = await readJsonObjectBody3(req);
109191
109426
  if (!raw) {
109192
- return json8({ error: t2("apiError.invalidRequest") }, 400);
109427
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
109193
109428
  }
109194
109429
  const body = raw;
109195
109430
  const description = typeof body.description === "string" ? body.description.trim() : "";
109196
109431
  if (!description) {
109197
- return json8({ error: t2("apiError.watchAssistDescriptionRequired") }, 400);
109432
+ return json9({ error: t2("apiError.watchAssistDescriptionRequired") }, 400);
109198
109433
  }
109199
109434
  let providerId = null;
109200
109435
  if (body.providerId !== undefined && body.providerId !== null) {
109201
109436
  if (typeof body.providerId !== "string" || !getLlmProviderById(body.providerId)) {
109202
- return json8({ error: t2("apiError.llmProviderNotFound") }, 400);
109437
+ return json9({ error: t2("apiError.llmProviderNotFound") }, 400);
109203
109438
  }
109204
109439
  providerId = body.providerId;
109205
109440
  }
@@ -109209,7 +109444,7 @@ async function handleAssistRegex(req, deps) {
109209
109444
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
109210
109445
  if (deviceId && paneId) {
109211
109446
  if (!getDeviceById(deviceId)) {
109212
- return json8({ error: t2("apiError.deviceNotFound") }, 404);
109447
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
109213
109448
  }
109214
109449
  try {
109215
109450
  screen = await deps.captureScreen(deviceId, paneId);
@@ -109230,14 +109465,14 @@ async function handleAssistRegex(req, deps) {
109230
109465
  object3 = result.object;
109231
109466
  } catch (error51) {
109232
109467
  const detail = error51 instanceof Error ? error51.message : String(error51);
109233
- return json8({ error: t2("apiError.watchAssistModelUnavailable", { detail }) }, 502);
109468
+ return json9({ error: t2("apiError.watchAssistModelUnavailable", { detail }) }, 502);
109234
109469
  }
109235
109470
  let regex;
109236
109471
  try {
109237
109472
  regex = compileWatchPattern(object3.pattern, object3.flags);
109238
109473
  } catch (error51) {
109239
109474
  const detail = error51 instanceof Error ? error51.message : String(error51);
109240
- return json8({ error: t2("apiError.watchPatternInvalid", { detail }) }, 502);
109475
+ return json9({ error: t2("apiError.watchPatternInvalid", { detail }) }, 502);
109241
109476
  }
109242
109477
  const preview = [];
109243
109478
  if (screen) {
@@ -109251,7 +109486,7 @@ async function handleAssistRegex(req, deps) {
109251
109486
  match = regex.exec(screen);
109252
109487
  }
109253
109488
  }
109254
- return json8({
109489
+ return json9({
109255
109490
  pattern: object3.pattern,
109256
109491
  flags: object3.flags,
109257
109492
  extractGroup: object3.extractGroup >= 0 ? object3.extractGroup : 0,
@@ -109259,7 +109494,7 @@ async function handleAssistRegex(req, deps) {
109259
109494
  preview
109260
109495
  });
109261
109496
  }
109262
- function json8(data, status = 200) {
109497
+ function json9(data, status = 200) {
109263
109498
  return new Response(JSON.stringify(data), {
109264
109499
  status,
109265
109500
  headers: {
@@ -109404,6 +109639,12 @@ function handleApiRequest(req, _server) {
109404
109639
  if (path === "/api/settings/terminal-shortcuts" && req.method === "PATCH") {
109405
109640
  return handleUpdateTerminalShortcuts(req);
109406
109641
  }
109642
+ if (path === "/api/settings/theme" && (req.method === "GET" || req.method === "POST")) {
109643
+ const themeResponse = handleThemeApiRequest(req, path);
109644
+ if (themeResponse) {
109645
+ return themeResponse;
109646
+ }
109647
+ }
109407
109648
  if (path === "/api/settings/restart" && req.method === "POST") {
109408
109649
  return handleRestartGateway();
109409
109650
  }
@@ -109507,13 +109748,13 @@ function handleApiRequest(req, _server) {
109507
109748
  return handleGetManifest(req.method);
109508
109749
  }
109509
109750
  if (path === "/healthz" && req.method === "GET") {
109510
- return json9({
109751
+ return json10({
109511
109752
  status: "ok",
109512
109753
  restarting: runtimeController.isRestarting(),
109513
109754
  env: "development"
109514
109755
  });
109515
109756
  }
109516
- return json9({ error: t2("apiError.notFound") }, 404);
109757
+ return json10({ error: t2("apiError.notFound") }, 404);
109517
109758
  }
109518
109759
  function enrichDeviceWithRuntime(device) {
109519
109760
  const status = getDeviceRuntimeStatus(device.id);
@@ -109527,22 +109768,22 @@ function enrichDeviceWithRuntime(device) {
109527
109768
  }
109528
109769
  async function handleGetDevices() {
109529
109770
  const devices2 = getAllDevices().map(enrichDeviceWithRuntime);
109530
- return json9({ devices: devices2 });
109771
+ return json10({ devices: devices2 });
109531
109772
  }
109532
109773
  async function handleGetDevice(id) {
109533
109774
  const device = getDeviceById(id);
109534
109775
  if (!device) {
109535
- return json9({ error: t2("apiError.deviceNotFound") }, 404);
109776
+ return json10({ error: t2("apiError.deviceNotFound") }, 404);
109536
109777
  }
109537
- return json9({ device: enrichDeviceWithRuntime(device) });
109778
+ return json10({ device: enrichDeviceWithRuntime(device) });
109538
109779
  }
109539
109780
  async function handleCreateDevice(req) {
109540
109781
  const body = await req.json();
109541
109782
  if (!body.name || !body.type || !body.authMode) {
109542
- return json9({ error: t2("apiError.missingFields") }, 400);
109783
+ return json10({ error: t2("apiError.missingFields") }, 400);
109543
109784
  }
109544
109785
  if (body.type === "ssh" && !body.host && !body.sshConfigRef) {
109545
- return json9({ error: t2("apiError.sshRequiresHost") }, 400);
109786
+ return json10({ error: t2("apiError.sshRequiresHost") }, 400);
109546
109787
  }
109547
109788
  const now2 = new Date().toISOString();
109548
109789
  const device = {
@@ -109565,12 +109806,12 @@ async function handleCreateDevice(req) {
109565
109806
  };
109566
109807
  createDevice(device);
109567
109808
  await pushSupervisor.upsert(device.id);
109568
- return json9({ device: getDeviceById(device.id) ?? device }, 201);
109809
+ return json10({ device: getDeviceById(device.id) ?? device }, 201);
109569
109810
  }
109570
109811
  async function handleUpdateDevice(req, id) {
109571
109812
  const existing = getDeviceById(id);
109572
109813
  if (!existing) {
109573
- return json9({ error: t2("apiError.deviceNotFound") }, 404);
109814
+ return json10({ error: t2("apiError.deviceNotFound") }, 404);
109574
109815
  }
109575
109816
  const body = await req.json();
109576
109817
  const updates = {};
@@ -109604,74 +109845,74 @@ async function handleUpdateDevice(req, id) {
109604
109845
  pushSupervisor.updateDefaultWorkingDir(id, updates.defaultWorkingDir);
109605
109846
  }
109606
109847
  const device = getDeviceById(id);
109607
- return json9({ device });
109848
+ return json10({ device });
109608
109849
  }
109609
109850
  async function handleReorderDevices(req) {
109610
109851
  const body = await req.json();
109611
109852
  if (!Array.isArray(body.deviceIds) || body.deviceIds.some((id) => typeof id !== "string")) {
109612
- return json9({ error: t2("apiError.invalidRequest") }, 400);
109853
+ return json10({ error: t2("apiError.invalidRequest") }, 400);
109613
109854
  }
109614
109855
  reorderDevices(body.deviceIds);
109615
- return json9({ devices: getAllDevices().map(enrichDeviceWithRuntime) });
109856
+ return json10({ devices: getAllDevices().map(enrichDeviceWithRuntime) });
109616
109857
  }
109617
109858
  async function handleDeleteDevice(id) {
109618
109859
  const existing = getDeviceById(id);
109619
109860
  if (!existing) {
109620
- return json9({ error: t2("apiError.deviceNotFound") }, 404);
109861
+ return json10({ error: t2("apiError.deviceNotFound") }, 404);
109621
109862
  }
109622
109863
  deleteDevice(id);
109623
109864
  pushSupervisor.remove(id);
109624
- return json9({ success: true });
109865
+ return json10({ success: true });
109625
109866
  }
109626
109867
  async function handleTestConnection(id) {
109627
109868
  return handleDeviceTestConnection(id);
109628
109869
  }
109629
109870
  async function handleGetSiteSettings() {
109630
- return json9({ settings: getSiteSettings() });
109871
+ return json10({ settings: getSiteSettings() });
109631
109872
  }
109632
109873
  async function handleUpdateSiteSettings(req) {
109633
109874
  try {
109634
109875
  const body = await req.json();
109635
109876
  const updates = normalizeSiteSettingsInput(body);
109636
109877
  const settings = updateSiteSettings(updates);
109637
- return json9({ settings });
109878
+ return json10({ settings });
109638
109879
  } catch (err) {
109639
- return json9({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
109880
+ return json10({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
109640
109881
  }
109641
109882
  }
109642
109883
  async function handleGetTerminalShortcuts() {
109643
- return json9({ settings: getTerminalShortcutSettings() });
109884
+ return json10({ settings: getTerminalShortcutSettings() });
109644
109885
  }
109645
109886
  async function handleUpdateTerminalShortcuts(req) {
109646
109887
  try {
109647
109888
  const body = await req.json();
109648
109889
  const updates = normalizeTerminalShortcutsInput(body);
109649
109890
  const settings = updateTerminalShortcutSettings(updates);
109650
- return json9({ settings });
109891
+ return json10({ settings });
109651
109892
  } catch (err) {
109652
- return json9({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
109893
+ return json10({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
109653
109894
  }
109654
109895
  }
109655
109896
  async function handleRestartGateway() {
109656
109897
  setTimeout(() => {
109657
109898
  runtimeController.requestRestart();
109658
109899
  }, 50);
109659
- return json9({
109900
+ return json10({
109660
109901
  success: true,
109661
109902
  message: t2("settings.restartScheduled")
109662
109903
  });
109663
109904
  }
109664
109905
  async function handleGetTelegramBots() {
109665
109906
  const bots = getTelegramBotsWithStats();
109666
- return json9({ bots });
109907
+ return json10({ bots });
109667
109908
  }
109668
109909
  async function handleCreateTelegramBot(req) {
109669
109910
  const body = await req.json();
109670
109911
  if (!body.name?.trim()) {
109671
- return json9({ error: t2("apiError.botNameRequired") }, 400);
109912
+ return json10({ error: t2("apiError.botNameRequired") }, 400);
109672
109913
  }
109673
109914
  if (!body.token?.trim()) {
109674
- return json9({ error: t2("apiError.botTokenRequired") }, 400);
109915
+ return json10({ error: t2("apiError.botTokenRequired") }, 400);
109675
109916
  }
109676
109917
  const now2 = new Date().toISOString();
109677
109918
  createTelegramBot({
@@ -109685,26 +109926,26 @@ async function handleCreateTelegramBot(req) {
109685
109926
  updatedAt: now2
109686
109927
  });
109687
109928
  await telegramService.refresh();
109688
- return json9({ success: true }, 201);
109929
+ return json10({ success: true }, 201);
109689
109930
  }
109690
109931
  async function handleUpdateTelegramBot(req, botId) {
109691
109932
  const existing = getTelegramBotById(botId);
109692
109933
  if (!existing) {
109693
- return json9({ error: t2("apiError.botNotFound") }, 404);
109934
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109694
109935
  }
109695
109936
  const body = await req.json();
109696
109937
  const updates = {};
109697
109938
  if (body.name !== undefined) {
109698
109939
  const value = body.name.trim();
109699
109940
  if (!value) {
109700
- return json9({ error: t2("apiError.botNameRequired") }, 400);
109941
+ return json10({ error: t2("apiError.botNameRequired") }, 400);
109701
109942
  }
109702
109943
  updates.name = value;
109703
109944
  }
109704
109945
  if (body.token !== undefined) {
109705
109946
  const token = body.token.trim();
109706
109947
  if (!token) {
109707
- return json9({ error: t2("apiError.botTokenRequired") }, 400);
109948
+ return json10({ error: t2("apiError.botTokenRequired") }, 400);
109708
109949
  }
109709
109950
  updates.tokenEnc = await encrypt(token);
109710
109951
  }
@@ -109716,69 +109957,69 @@ async function handleUpdateTelegramBot(req, botId) {
109716
109957
  }
109717
109958
  updateTelegramBot(botId, updates);
109718
109959
  await telegramService.refresh();
109719
- return json9({ success: true });
109960
+ return json10({ success: true });
109720
109961
  }
109721
109962
  async function handleDeleteTelegramBot(botId) {
109722
109963
  const existing = getTelegramBotById(botId);
109723
109964
  if (!existing) {
109724
- return json9({ error: t2("apiError.botNotFound") }, 404);
109965
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109725
109966
  }
109726
109967
  deleteTelegramBot(botId);
109727
109968
  await telegramService.refresh();
109728
- return json9({ success: true });
109969
+ return json10({ success: true });
109729
109970
  }
109730
109971
  async function handleListTelegramChats(botId) {
109731
109972
  const existing = getTelegramBotById(botId);
109732
109973
  if (!existing) {
109733
- return json9({ error: t2("apiError.botNotFound") }, 404);
109974
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109734
109975
  }
109735
109976
  const chats = listTelegramChatsByBot(botId);
109736
- return json9({ chats });
109977
+ return json10({ chats });
109737
109978
  }
109738
109979
  async function handleApproveTelegramChat(botId, chatId) {
109739
109980
  const existing = getTelegramBotById(botId);
109740
109981
  if (!existing) {
109741
- return json9({ error: t2("apiError.botNotFound") }, 404);
109982
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109742
109983
  }
109743
109984
  const chat = approveTelegramChat(botId, chatId);
109744
109985
  if (!chat) {
109745
- return json9({ error: t2("apiError.chatNotFound") }, 404);
109986
+ return json10({ error: t2("apiError.chatNotFound") }, 404);
109746
109987
  }
109747
109988
  const settings = getSiteSettings();
109748
109989
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.approveMessageTemplate", {
109749
109990
  botName: existing.name,
109750
109991
  time: new Date().toLocaleString(toBCP47(settings.language))
109751
109992
  }));
109752
- return json9({ chat });
109993
+ return json10({ chat });
109753
109994
  }
109754
109995
  async function handleDeleteTelegramChat(botId, chatId) {
109755
109996
  const existing = getTelegramBotById(botId);
109756
109997
  if (!existing) {
109757
- return json9({ error: t2("apiError.botNotFound") }, 404);
109998
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109758
109999
  }
109759
110000
  deleteTelegramChat(botId, chatId);
109760
- return json9({ success: true });
110001
+ return json10({ success: true });
109761
110002
  }
109762
110003
  async function handleTestTelegramChat(botId, chatId) {
109763
110004
  const bot = getTelegramBotById(botId);
109764
110005
  if (!bot) {
109765
- return json9({ error: t2("apiError.botNotFound") }, 404);
110006
+ return json10({ error: t2("apiError.botNotFound") }, 404);
109766
110007
  }
109767
110008
  const settings = getSiteSettings();
109768
110009
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.testMessageTemplate", {
109769
110010
  siteName: settings.siteName,
109770
110011
  time: new Date().toLocaleString(toBCP47(settings.language))
109771
110012
  }));
109772
- return json9({ success: true });
110013
+ return json10({ success: true });
109773
110014
  }
109774
110015
  async function handleGetWeixinAccounts() {
109775
110016
  const accounts = getWeixinAccountsWithStats();
109776
- return json9({ accounts });
110017
+ return json10({ accounts });
109777
110018
  }
109778
110019
  async function handleCreateWeixinAccount(req) {
109779
110020
  const body = await req.json();
109780
110021
  if (!body.name?.trim()) {
109781
- return json9({ error: t2("weixin.accountNameRequired") }, 400);
110022
+ return json10({ error: t2("weixin.accountNameRequired") }, 400);
109782
110023
  }
109783
110024
  const now2 = new Date().toISOString();
109784
110025
  const id = v4_default();
@@ -109795,19 +110036,19 @@ async function handleCreateWeixinAccount(req) {
109795
110036
  createdAt: now2,
109796
110037
  updatedAt: now2
109797
110038
  });
109798
- return json9({ success: true, accountId: id }, 201);
110039
+ return json10({ success: true, accountId: id }, 201);
109799
110040
  }
109800
110041
  async function handleUpdateWeixinAccount(req, accountId) {
109801
110042
  const existing = getWeixinAccountById(accountId);
109802
110043
  if (!existing) {
109803
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110044
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109804
110045
  }
109805
110046
  const body = await req.json();
109806
110047
  const updates = {};
109807
110048
  if (body.name !== undefined) {
109808
110049
  const value = body.name.trim();
109809
110050
  if (!value) {
109810
- return json9({ error: t2("weixin.accountNameRequired") }, 400);
110051
+ return json10({ error: t2("weixin.accountNameRequired") }, 400);
109811
110052
  }
109812
110053
  updates.name = value;
109813
110054
  }
@@ -109819,52 +110060,52 @@ async function handleUpdateWeixinAccount(req, accountId) {
109819
110060
  }
109820
110061
  updateWeixinAccount(accountId, updates);
109821
110062
  await weixinService.refresh();
109822
- return json9({ success: true });
110063
+ return json10({ success: true });
109823
110064
  }
109824
110065
  async function handleDeleteWeixinAccount(accountId) {
109825
110066
  const existing = getWeixinAccountById(accountId);
109826
110067
  if (!existing) {
109827
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110068
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109828
110069
  }
109829
110070
  deleteWeixinAccount(accountId);
109830
110071
  await weixinService.refresh();
109831
- return json9({ success: true });
110072
+ return json10({ success: true });
109832
110073
  }
109833
110074
  async function handleStartWeixinLogin(accountId) {
109834
110075
  const existing = getWeixinAccountById(accountId);
109835
110076
  if (!existing) {
109836
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110077
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109837
110078
  }
109838
110079
  try {
109839
110080
  const result = await weixinService.startLogin(accountId);
109840
- return json9(result);
110081
+ return json10(result);
109841
110082
  } catch (err) {
109842
- return json9({ error: err instanceof Error ? err.message : t2("weixin.loginFailed") }, 502);
110083
+ return json10({ error: err instanceof Error ? err.message : t2("weixin.loginFailed") }, 502);
109843
110084
  }
109844
110085
  }
109845
110086
  async function handleGetWeixinLoginStatus(accountId) {
109846
110087
  const existing = getWeixinAccountById(accountId);
109847
110088
  if (!existing) {
109848
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110089
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109849
110090
  }
109850
- return json9(weixinService.getLoginStatus(accountId));
110091
+ return json10(weixinService.getLoginStatus(accountId));
109851
110092
  }
109852
110093
  async function handleListWeixinUsers(accountId) {
109853
110094
  const existing = getWeixinAccountById(accountId);
109854
110095
  if (!existing) {
109855
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110096
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109856
110097
  }
109857
110098
  const users = listWeixinUsersByAccount(accountId);
109858
- return json9({ users });
110099
+ return json10({ users });
109859
110100
  }
109860
110101
  async function handleApproveWeixinUser(accountId, userId) {
109861
110102
  const existing = getWeixinAccountById(accountId);
109862
110103
  if (!existing) {
109863
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110104
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109864
110105
  }
109865
110106
  const user = approveWeixinUser(accountId, userId);
109866
110107
  if (!user) {
109867
- return json9({ error: t2("weixin.userNotFound") }, 404);
110108
+ return json10({ error: t2("weixin.userNotFound") }, 404);
109868
110109
  }
109869
110110
  const settings = getSiteSettings();
109870
110111
  try {
@@ -109875,12 +110116,12 @@ async function handleApproveWeixinUser(accountId, userId) {
109875
110116
  } catch (err) {
109876
110117
  console.error("[weixin] approve ack failed:", err);
109877
110118
  }
109878
- return json9({ user });
110119
+ return json10({ user });
109879
110120
  }
109880
110121
  async function handleTestWeixinUser(accountId, userId) {
109881
110122
  const existing = getWeixinAccountById(accountId);
109882
110123
  if (!existing) {
109883
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110124
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109884
110125
  }
109885
110126
  const settings = getSiteSettings();
109886
110127
  try {
@@ -109889,14 +110130,14 @@ async function handleTestWeixinUser(accountId, userId) {
109889
110130
  time: new Date().toLocaleString(toBCP47(settings.language))
109890
110131
  }));
109891
110132
  } catch (err) {
109892
- return json9({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
110133
+ return json10({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
109893
110134
  }
109894
- return json9({ success: true });
110135
+ return json10({ success: true });
109895
110136
  }
109896
110137
  async function handleTestWeixinAccount(accountId) {
109897
110138
  const existing = getWeixinAccountById(accountId);
109898
110139
  if (!existing) {
109899
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110140
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109900
110141
  }
109901
110142
  const settings = getSiteSettings();
109902
110143
  try {
@@ -109905,26 +110146,26 @@ async function handleTestWeixinAccount(accountId) {
109905
110146
  time: new Date().toLocaleString(toBCP47(settings.language))
109906
110147
  }));
109907
110148
  } catch (err) {
109908
- return json9({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
110149
+ return json10({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
109909
110150
  }
109910
- return json9({ success: true });
110151
+ return json10({ success: true });
109911
110152
  }
109912
110153
  async function handleDeleteWeixinUser(accountId, userId) {
109913
110154
  const existing = getWeixinAccountById(accountId);
109914
110155
  if (!existing) {
109915
- return json9({ error: t2("weixin.accountNotFound") }, 404);
110156
+ return json10({ error: t2("weixin.accountNotFound") }, 404);
109916
110157
  }
109917
110158
  deleteWeixinUser(accountId, userId);
109918
- return json9({ success: true });
110159
+ return json10({ success: true });
109919
110160
  }
109920
110161
  async function handleGetWebhooks() {
109921
110162
  const webhooks = getAllWebhookEndpoints();
109922
- return json9({ webhooks });
110163
+ return json10({ webhooks });
109923
110164
  }
109924
110165
  async function handleCreateWebhook(req) {
109925
110166
  const body = await req.json();
109926
110167
  if (!body.url || !body.secret) {
109927
- return json9({ error: t2("apiError.urlAndSecretRequired") }, 400);
110168
+ return json10({ error: t2("apiError.urlAndSecretRequired") }, 400);
109928
110169
  }
109929
110170
  const now2 = new Date().toISOString();
109930
110171
  const endpoint = {
@@ -109937,11 +110178,11 @@ async function handleCreateWebhook(req) {
109937
110178
  updatedAt: now2
109938
110179
  };
109939
110180
  createWebhookEndpoint(endpoint);
109940
- return json9({ webhook: endpoint }, 201);
110181
+ return json10({ webhook: endpoint }, 201);
109941
110182
  }
109942
110183
  async function handleDeleteWebhook(id) {
109943
110184
  deleteWebhookEndpoint(id);
109944
- return json9({ success: true });
110185
+ return json10({ success: true });
109945
110186
  }
109946
110187
  async function handleGetManifest(method) {
109947
110188
  const settings = getSiteSettings();
@@ -109980,7 +110221,7 @@ function manifestJson(data, method) {
109980
110221
  }
109981
110222
  });
109982
110223
  }
109983
- function json9(data, status = 200, headers = {}) {
110224
+ function json10(data, status = 200, headers = {}) {
109984
110225
  return new Response(JSON.stringify(data), {
109985
110226
  status,
109986
110227
  headers: {
@@ -110697,8 +110938,14 @@ var defaultDeps5 = {
110697
110938
  class WebSocketServer {
110698
110939
  connections = new Map;
110699
110940
  pendingConnectionEntries = new Map;
110941
+ connectedClients = new Set;
110700
110942
  windowCustomNames = new Map;
110701
110943
  paneCustomNames = new Map;
110944
+ currentTheme = null;
110945
+ themeSignalLast = new Map;
110946
+ lastThemeTimestamp = 0n;
110947
+ lastBroadcastTheme = new Map;
110948
+ lastBroadcastSize = new Map;
110702
110949
  deps;
110703
110950
  constructor(options = {}) {
110704
110951
  this.deps = {
@@ -110730,6 +110977,9 @@ class WebSocketServer {
110730
110977
  this.clearReconnectTimer(entry);
110731
110978
  entry.detachRuntime?.();
110732
110979
  entry.detachRuntime = null;
110980
+ this.themeSignalLast.delete(deviceId);
110981
+ this.lastBroadcastTheme.delete(deviceId);
110982
+ this.lastBroadcastSize.delete(deviceId);
110733
110983
  this.deps.releaseRuntime(deviceId, entry.runtime);
110734
110984
  }
110735
110985
  attachRuntime(deviceId, runtime) {
@@ -110814,6 +111064,7 @@ class WebSocketServer {
110814
111064
  handleOpen(ws) {
110815
111065
  console.log("[ws] client connected");
110816
111066
  sessionStateStore.create(ws);
111067
+ this.connectedClients.add(ws);
110817
111068
  }
110818
111069
  handleMessage(ws, message) {
110819
111070
  if (typeof message === "string") {
@@ -110852,6 +111103,7 @@ class WebSocketServer {
110852
111103
  }
110853
111104
  handleClose(ws) {
110854
111105
  console.log("[ws] client disconnected");
111106
+ this.connectedClients.delete(ws);
110855
111107
  switchBarrier.cleanupClient(ws);
110856
111108
  sessionStateStore.cleanup(ws);
110857
111109
  agentWsHub.removeClient(ws);
@@ -111038,6 +111290,11 @@ class WebSocketServer {
111038
111290
  agentWsHub.unsubscribe(ws, decoded.sessionId);
111039
111291
  return;
111040
111292
  }
111293
+ case exports_ws_borsh.KIND_SITE_THEME_UPDATE: {
111294
+ const decoded = exports_ws_borsh.decodePayload(exports_ws_borsh.schema.SiteThemeUpdateC2SSchema, payload);
111295
+ this.handleSiteThemeUpdate(ws, decoded);
111296
+ return;
111297
+ }
111041
111298
  default:
111042
111299
  this.sendError(ws, refSeq, exports_ws_borsh.ERROR_UNKNOWN_KIND, `Unknown kind: ${kind}`, false);
111043
111300
  }
@@ -111258,6 +111515,11 @@ class WebSocketServer {
111258
111515
  const entry = this.connections.get(deviceId);
111259
111516
  if (!entry)
111260
111517
  return;
111518
+ const last = this.lastBroadcastSize.get(deviceId);
111519
+ if (last && last.cols === cols && last.rows === rows) {
111520
+ return;
111521
+ }
111522
+ this.lastBroadcastSize.set(deviceId, { cols, rows });
111261
111523
  const snapshot = entry.lastSnapshot;
111262
111524
  if (snapshot?.session?.windows) {
111263
111525
  const window2 = snapshot.session.windows.find((w) => w.panes && w.panes.some((p) => p.id === paneId));
@@ -111356,6 +111618,89 @@ class WebSocketServer {
111356
111618
  if (!entry)
111357
111619
  return;
111358
111620
  entry.runtime.setWindowStyle(style);
111621
+ if (this.currentTheme !== null) {
111622
+ const theme = this.currentTheme;
111623
+ if (this.lastBroadcastTheme.get(deviceId) !== theme) {
111624
+ this.lastBroadcastTheme.set(deviceId, theme);
111625
+ this.broadcastThemeChange(theme);
111626
+ }
111627
+ }
111628
+ }
111629
+ handleSiteThemeUpdate(ws, decoded) {
111630
+ if (decoded.theme !== exports_ws_borsh.SITE_THEME_DARK && decoded.theme !== exports_ws_borsh.SITE_THEME_LIGHT) {
111631
+ this.sendError(ws, null, exports_ws_borsh.ERROR_PAYLOAD_DECODE_FAILED, `invalid theme value: ${decoded.theme}`, false);
111632
+ return;
111633
+ }
111634
+ const themeName = decoded.theme === exports_ws_borsh.SITE_THEME_LIGHT ? "light" : "dark";
111635
+ updateSiteSettings({ theme: themeName });
111636
+ this.handleSiteThemeChange(themeName);
111637
+ this.broadcastThemeChange(themeName);
111638
+ this.broadcastSiteThemeUpdateS2C(themeName);
111639
+ }
111640
+ broadcastSiteThemeUpdateS2C(theme) {
111641
+ const now2 = BigInt(Date.now());
111642
+ if (now2 <= this.lastThemeTimestamp) {
111643
+ this.lastThemeTimestamp += 1n;
111644
+ } else {
111645
+ this.lastThemeTimestamp = now2;
111646
+ }
111647
+ const effectiveTimestamp = this.lastThemeTimestamp;
111648
+ const themeCode = theme === "light" ? exports_ws_borsh.SITE_THEME_LIGHT : exports_ws_borsh.SITE_THEME_DARK;
111649
+ const payloadBytes = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.SiteThemeUpdateS2CSchema, {
111650
+ theme: themeCode,
111651
+ serverTimestamp: effectiveTimestamp
111652
+ });
111653
+ for (const client of this.connectedClients) {
111654
+ this.sendEnvelope(client, exports_ws_borsh.KIND_SITE_THEME_UPDATE, payloadBytes);
111655
+ }
111656
+ }
111657
+ handleSiteThemeChange(theme) {
111658
+ if (theme !== "dark" && theme !== "light") {
111659
+ return;
111660
+ }
111661
+ this.currentTheme = theme;
111662
+ const style = getTmuxWindowStyle(theme);
111663
+ for (const [, entry] of this.connections) {
111664
+ try {
111665
+ entry.runtime.setWindowStyle(style);
111666
+ } catch (err) {
111667
+ console.error("[ws] setWindowStyle on theme change failed:", err);
111668
+ }
111669
+ }
111670
+ }
111671
+ applyThemeToDevice(deviceId) {
111672
+ if (this.currentTheme === null) {
111673
+ return;
111674
+ }
111675
+ const entry = this.connections.get(deviceId);
111676
+ if (!entry) {
111677
+ return;
111678
+ }
111679
+ const style = getTmuxWindowStyle(this.currentTheme);
111680
+ try {
111681
+ entry.runtime.setWindowStyle(style);
111682
+ } catch (err) {
111683
+ console.error(`[ws] setWindowStyle on device ${deviceId} failed:`, err);
111684
+ }
111685
+ }
111686
+ broadcastThemeChange(theme) {
111687
+ const now2 = Date.now();
111688
+ for (const [deviceId, entry] of this.connections) {
111689
+ const last = this.themeSignalLast.get(deviceId);
111690
+ if (last && last.theme === theme && now2 - last.at < 1000) {
111691
+ continue;
111692
+ }
111693
+ this.themeSignalLast.set(deviceId, { theme, at: now2 });
111694
+ this.lastBroadcastTheme.set(deviceId, theme);
111695
+ const panes = entry.lastSnapshot?.session?.windows?.flatMap((w) => w.panes) ?? [];
111696
+ for (const pane of panes) {
111697
+ try {
111698
+ entry.runtime.signalThemeChange(pane.id, theme);
111699
+ } catch (err) {
111700
+ console.error(`[ws] signalThemeChange failed for ${deviceId}/${pane.id}:`, err);
111701
+ }
111702
+ }
111703
+ }
111359
111704
  }
111360
111705
  handleReorderWindows(deviceId, windowIds) {
111361
111706
  setWindowOrder(deviceId, windowIds);
@@ -111499,6 +111844,9 @@ class WebSocketServer {
111499
111844
  runtime = await this.deps.acquireRuntime(deviceId);
111500
111845
  detachRuntime = this.attachRuntime(deviceId, runtime);
111501
111846
  await runtime.connect();
111847
+ if (this.currentTheme !== null) {
111848
+ runtime.setWindowStyle(getTmuxWindowStyle(this.currentTheme));
111849
+ }
111502
111850
  return {
111503
111851
  runtime,
111504
111852
  detachRuntime,
@@ -111801,10 +112149,17 @@ async function createGatewayRuntime(options = {}) {
111801
112149
  primeLocalShellPath();
111802
112150
  sweepOrphanTransferTemps();
111803
112151
  const wsServer = new WebSocketServer;
112152
+ wsServer.currentTheme = getSiteSettings().theme;
111804
112153
  connectionAlertNotifier.setBroadcaster((deviceId, payload) => {
111805
112154
  wsServer.broadcastDeviceError(deviceId, payload);
111806
112155
  });
111807
112156
  registerSnapshotLookup((deviceId) => wsServer.getLastSnapshot(deviceId));
112157
+ registerThemeBroadcaster((theme) => {
112158
+ wsServer.handleSiteThemeChange(theme);
112159
+ wsServer.broadcastThemeChange(theme);
112160
+ }, (theme) => {
112161
+ wsServer.broadcastSiteThemeUpdateS2C(theme);
112162
+ });
111808
112163
  await telegramService.refresh();
111809
112164
  await weixinService.refresh();
111810
112165
  await pushSupervisor.start();
@@ -111852,6 +112207,7 @@ async function createGatewayRuntime(options = {}) {
111852
112207
  },
111853
112208
  async stop() {
111854
112209
  connectionAlertNotifier.setBroadcaster(null);
112210
+ registerThemeBroadcaster(null);
111855
112211
  wsServer.closeAll();
111856
112212
  await watchService.stop();
111857
112213
  await agentSupervisor.stop();