termdock 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +385 -0
  3. package/dist/client/apple-splash-default-1170x2532.png +0 -0
  4. package/dist/client/apple-splash-landscape-1136x640.png +0 -0
  5. package/dist/client/apple-splash-landscape-1334x750.png +0 -0
  6. package/dist/client/apple-splash-landscape-1792x828.png +0 -0
  7. package/dist/client/apple-splash-landscape-2048x1536.png +0 -0
  8. package/dist/client/apple-splash-landscape-2160x1620.png +0 -0
  9. package/dist/client/apple-splash-landscape-2208x1242.png +0 -0
  10. package/dist/client/apple-splash-landscape-2224x1668.png +0 -0
  11. package/dist/client/apple-splash-landscape-2266x1488.png +0 -0
  12. package/dist/client/apple-splash-landscape-2360x1640.png +0 -0
  13. package/dist/client/apple-splash-landscape-2388x1668.png +0 -0
  14. package/dist/client/apple-splash-landscape-2436x1125.png +0 -0
  15. package/dist/client/apple-splash-landscape-2532x1170.png +0 -0
  16. package/dist/client/apple-splash-landscape-2556x1179.png +0 -0
  17. package/dist/client/apple-splash-landscape-2622x1206.png +0 -0
  18. package/dist/client/apple-splash-landscape-2688x1242.png +0 -0
  19. package/dist/client/apple-splash-landscape-2732x2048.png +0 -0
  20. package/dist/client/apple-splash-landscape-2778x1284.png +0 -0
  21. package/dist/client/apple-splash-landscape-2796x1290.png +0 -0
  22. package/dist/client/apple-splash-landscape-2868x1320.png +0 -0
  23. package/dist/client/apple-splash-portrait-1125x2436.png +0 -0
  24. package/dist/client/apple-splash-portrait-1170x2532.png +0 -0
  25. package/dist/client/apple-splash-portrait-1179x2556.png +0 -0
  26. package/dist/client/apple-splash-portrait-1206x2622.png +0 -0
  27. package/dist/client/apple-splash-portrait-1242x2208.png +0 -0
  28. package/dist/client/apple-splash-portrait-1242x2688.png +0 -0
  29. package/dist/client/apple-splash-portrait-1284x2778.png +0 -0
  30. package/dist/client/apple-splash-portrait-1290x2796.png +0 -0
  31. package/dist/client/apple-splash-portrait-1320x2868.png +0 -0
  32. package/dist/client/apple-splash-portrait-1488x2266.png +0 -0
  33. package/dist/client/apple-splash-portrait-1536x2048.png +0 -0
  34. package/dist/client/apple-splash-portrait-1620x2160.png +0 -0
  35. package/dist/client/apple-splash-portrait-1640x2360.png +0 -0
  36. package/dist/client/apple-splash-portrait-1668x2224.png +0 -0
  37. package/dist/client/apple-splash-portrait-1668x2388.png +0 -0
  38. package/dist/client/apple-splash-portrait-2048x2732.png +0 -0
  39. package/dist/client/apple-splash-portrait-640x1136.png +0 -0
  40. package/dist/client/apple-splash-portrait-750x1334.png +0 -0
  41. package/dist/client/apple-splash-portrait-828x1792.png +0 -0
  42. package/dist/client/apple-touch-icon-180x180.png +0 -0
  43. package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  44. package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  45. package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  46. package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  47. package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  48. package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  49. package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  50. package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  51. package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  52. package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  53. package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  54. package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  55. package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  56. package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  57. package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  58. package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  59. package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  60. package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  61. package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  62. package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  63. package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  64. package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  65. package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  66. package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  67. package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  68. package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  69. package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  70. package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  71. package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  72. package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  73. package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  74. package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  75. package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  76. package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  77. package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  78. package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  79. package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  80. package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  81. package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  82. package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  83. package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  84. package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  85. package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  86. package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  87. package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  88. package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  89. package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  90. package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  91. package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  92. package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  93. package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  94. package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  95. package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  96. package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  97. package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  98. package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  99. package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  100. package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  101. package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  102. package/dist/client/assets/abnfDiagram-VRR7QNED-D0NxeSHA.js +1 -0
  103. package/dist/client/assets/arc-B7GoKc6u.js +1 -0
  104. package/dist/client/assets/architectureDiagram-ZJ3FMSHR-BQPW-wUx.js +36 -0
  105. package/dist/client/assets/blockDiagram-677ZJIJ3-CX_hbcgG.js +132 -0
  106. package/dist/client/assets/c4Diagram-LMCZKHZV-BwOMQA4x.js +10 -0
  107. package/dist/client/assets/channel-C2gUFcyU.js +1 -0
  108. package/dist/client/assets/chunk-2Q5K7J3B-BLJzEQXz.js +1 -0
  109. package/dist/client/assets/chunk-32BRIVSS-CjBDr_gz.js +1 -0
  110. package/dist/client/assets/chunk-5VM5RSS4-lddvv0x2.js +15 -0
  111. package/dist/client/assets/chunk-EX3LRPZG-Dtsq1Stg.js +231 -0
  112. package/dist/client/assets/chunk-JWPE2WC7-BG3PZx4j.js +1 -0
  113. package/dist/client/assets/chunk-MOJQB5TN-CMtlCuQg.js +88 -0
  114. package/dist/client/assets/chunk-RYQCIY6F-uplKqw64.js +1 -0
  115. package/dist/client/assets/chunk-V7JOEXUC-CpteLsY3.js +206 -0
  116. package/dist/client/assets/chunk-VR4S4FIN-1zYOJQXL.js +1 -0
  117. package/dist/client/assets/chunk-XXDRQBXY-C0wXH91I.js +1 -0
  118. package/dist/client/assets/classDiagram-OUVF2IWQ-BTOnA9HS.js +1 -0
  119. package/dist/client/assets/classDiagram-v2-EOCWNBFH-BTOnA9HS.js +1 -0
  120. package/dist/client/assets/cose-bilkent-JH36ORCC-BceT5ki2.js +1 -0
  121. package/dist/client/assets/cynefin-VYW2F7L2-BqCIkNeC.js +166 -0
  122. package/dist/client/assets/cynefinDiagram-TSTJHNR4-Dm7y-tsN.js +62 -0
  123. package/dist/client/assets/cytoscape.esm-D3_iZ_3b.js +321 -0
  124. package/dist/client/assets/dagre-VKFMJZFB-Bn8eh0lE.js +4 -0
  125. package/dist/client/assets/defaultLocale-CrowFXzY.js +1 -0
  126. package/dist/client/assets/diagram-FQU43EPY-DmZGMAsX.js +3 -0
  127. package/dist/client/assets/diagram-G47NLZAW-BLWSm_Rn.js +24 -0
  128. package/dist/client/assets/diagram-NH7WQ7WH-C2-HK8pv.js +24 -0
  129. package/dist/client/assets/diagram-OA4YK3LP-8Ex4XIU5.js +30 -0
  130. package/dist/client/assets/diagram-WEI45ONY-MRNIpBXG.js +41 -0
  131. package/dist/client/assets/diffWorker-B6msVyjo.js +18 -0
  132. package/dist/client/assets/dnd-BCFAPC0L.js +1 -0
  133. package/dist/client/assets/ebnfDiagram-CCIWWBDH-D7Z6wZi_.js +1 -0
  134. package/dist/client/assets/erDiagram-Q63AITRT-C96Q5x9y.js +85 -0
  135. package/dist/client/assets/flowDiagram-23GEKE2U-DzQp40QH.js +156 -0
  136. package/dist/client/assets/ganttDiagram-NO4QXBWP-ZzkchZJ8.js +292 -0
  137. package/dist/client/assets/gitGraphDiagram-IHSO6WYX-CFacEf54.js +106 -0
  138. package/dist/client/assets/graph-D2o_JWn5.js +1 -0
  139. package/dist/client/assets/index-Ca-rq7yX.js +6 -0
  140. package/dist/client/assets/index-Cv7q4nZR.css +1 -0
  141. package/dist/client/assets/index-XktPMm51.js +394 -0
  142. package/dist/client/assets/infoDiagram-FWYZ7A6U-CVRXs5rF.js +2 -0
  143. package/dist/client/assets/init-Gi6I4Gst.js +1 -0
  144. package/dist/client/assets/ishikawaDiagram-FXEZZL3T-B8FP_wzM.js +70 -0
  145. package/dist/client/assets/journeyDiagram-5HDEW3XC-BR__4Hbd.js +139 -0
  146. package/dist/client/assets/kanban-definition-HUTT4EX6-v39oZe6X.js +89 -0
  147. package/dist/client/assets/katex-55ZPZeOF.css +1 -0
  148. package/dist/client/assets/katex-DnlPpQZa.js +257 -0
  149. package/dist/client/assets/katex-HP8lGamR.js +257 -0
  150. package/dist/client/assets/layout-1eBIAoSl.js +1 -0
  151. package/dist/client/assets/linear-jlTiHqO6.js +1 -0
  152. package/dist/client/assets/map-BdqsVdb3.js +1 -0
  153. package/dist/client/assets/mermaid.core-DiiIN0EC.js +306 -0
  154. package/dist/client/assets/mindmap-definition-LN4V7U3C-AeUxAZeq.js +96 -0
  155. package/dist/client/assets/ordinal-Cboi1Yqb.js +1 -0
  156. package/dist/client/assets/pegDiagram-2B236MQR-Dh-bgAbv.js +1 -0
  157. package/dist/client/assets/pieDiagram-ENE6RG2P-De7Si7Tm.js +39 -0
  158. package/dist/client/assets/purify.es-CC4Brkr_.js +3 -0
  159. package/dist/client/assets/quadrantDiagram-ABIIQ3AL-CgWfHqy0.js +7 -0
  160. package/dist/client/assets/railroadDiagram-RFXS5EU6-BXnQW-pI.js +1 -0
  161. package/dist/client/assets/react-XDCjal2T.js +8 -0
  162. package/dist/client/assets/requirementDiagram-TGXJPOKE-CU1nnpUM.js +84 -0
  163. package/dist/client/assets/sankeyDiagram-HTMAVEWB-DFK-AXY6.js +40 -0
  164. package/dist/client/assets/sequenceDiagram-DBY2YBRQ-D_bVDWgq.js +162 -0
  165. package/dist/client/assets/sizeCapture-X5ZJPWSS-nh89ExHT.js +1 -0
  166. package/dist/client/assets/stateDiagram-2N3HPSRC-DUvT6Ruk.js +1 -0
  167. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Dz-3r-Yq.js +1 -0
  168. package/dist/client/assets/swimlanes-5IMT3BWC-ZYa52gjG.js +2 -0
  169. package/dist/client/assets/swimlanesDiagram-G3AALYLV-DXR_9lf9.js +8 -0
  170. package/dist/client/assets/terminal-C7X98Amf.js +10 -0
  171. package/dist/client/assets/timeline-definition-FHXFAJF6-Bp6A49KQ.js +120 -0
  172. package/dist/client/assets/vennDiagram-L72KCM5P-CDDUmLOI.js +34 -0
  173. package/dist/client/assets/wardleyDiagram-EHGQE667-CRc2ptu7.js +78 -0
  174. package/dist/client/assets/xychartDiagram-FW5EYKEG-ht_S0LUA.js +7 -0
  175. package/dist/client/favicon.ico +0 -0
  176. package/dist/client/fonts/JetBrainsMonoNL-Bold.woff2 +0 -0
  177. package/dist/client/fonts/JetBrainsMonoNL-BoldItalic.woff2 +0 -0
  178. package/dist/client/fonts/JetBrainsMonoNL-Italic.woff2 +0 -0
  179. package/dist/client/fonts/JetBrainsMonoNL-Regular.woff2 +0 -0
  180. package/dist/client/fonts/JetBrainsMonoNLNerdFontMono-Bold.ttf +0 -0
  181. package/dist/client/fonts/JetBrainsMonoNLNerdFontMono-BoldItalic.ttf +0 -0
  182. package/dist/client/fonts/JetBrainsMonoNLNerdFontMono-Italic.ttf +0 -0
  183. package/dist/client/fonts/JetBrainsMonoNLNerdFontMono-Regular.ttf +0 -0
  184. package/dist/client/fonts/NotoSansMonoCJKsc-Bold-Subset.woff2 +0 -0
  185. package/dist/client/fonts/NotoSansMonoCJKsc-Subset.woff2 +0 -0
  186. package/dist/client/fonts/NotoSansSymbols2-Subset.woff2 +0 -0
  187. package/dist/client/fonts/SymbolsNerdFontMono-Regular.woff2 +0 -0
  188. package/dist/client/fonts/TermdockStarSymbol-Subset.woff2 +0 -0
  189. package/dist/client/index.html +255 -0
  190. package/dist/client/manifest.webmanifest +1 -0
  191. package/dist/client/maskable-icon-512x512.png +0 -0
  192. package/dist/client/pwa-192x192.png +0 -0
  193. package/dist/client/pwa-192x192.svg +5 -0
  194. package/dist/client/pwa-512x512.png +0 -0
  195. package/dist/client/pwa-512x512.svg +5 -0
  196. package/dist/client/pwa-64x64.png +0 -0
  197. package/dist/client/registerSW.js +1 -0
  198. package/dist/client/robots.txt +4 -0
  199. package/dist/client/shell-integration/termdock.bash +107 -0
  200. package/dist/client/shell-integration/termdock.fish +60 -0
  201. package/dist/client/shell-integration/termdock.zsh +164 -0
  202. package/dist/client/sw-notifications.js +42 -0
  203. package/dist/client/sw.js +1 -0
  204. package/dist/client/workbox-e4022e15.js +1 -0
  205. package/dist/server/certificateWatcher.js +111 -0
  206. package/dist/server/cli.js +2534 -0
  207. package/dist/server/config.js +54 -0
  208. package/dist/server/entry.js +571 -0
  209. package/dist/server/onboardingServer.js +54 -0
  210. package/dist/server/routes/auth.js +48 -0
  211. package/dist/server/routes/filesystem.js +3749 -0
  212. package/dist/server/routes/local.js +32 -0
  213. package/dist/server/routes/onboarding.js +134 -0
  214. package/dist/server/routes/terminal.js +4879 -0
  215. package/dist/server/utils/authProtection.js +338 -0
  216. package/dist/server/utils/caffeinate.js +124 -0
  217. package/dist/server/utils/changeAuditStore.js +521 -0
  218. package/dist/server/utils/cookieSecurity.js +13 -0
  219. package/dist/server/utils/csrfProtection.js +112 -0
  220. package/dist/server/utils/localAccess.js +211 -0
  221. package/dist/server/utils/pathValidator.js +272 -0
  222. package/dist/server/utils/requestSecurity.js +103 -0
  223. package/dist/server/utils/serverLogger.js +57 -0
  224. package/dist/server/utils/settings.js +157 -0
  225. package/dist/server/utils/tmuxFocus.js +45 -0
  226. package/dist/server/utils/tmuxProgramDetection.js +148 -0
  227. package/package.json +125 -0
@@ -0,0 +1,157 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { LOCAL_ACCESS } from '../config.js';
6
+ const SETTINGS_DIR = path.join(os.homedir(), '.termdock');
7
+ const SETTINGS_FILE = path.join(SETTINGS_DIR, 'settings.json');
8
+ function generateAutoName() {
9
+ const alphabet = LOCAL_ACCESS.generatedNameAlphabet;
10
+ const bytes = crypto.randomBytes(LOCAL_ACCESS.generatedNameLength);
11
+ let value = '';
12
+ for (const byte of bytes) {
13
+ value += alphabet[byte % alphabet.length];
14
+ }
15
+ return value;
16
+ }
17
+ function normalizeSource(value) {
18
+ return value === 'manual' ? 'manual' : 'auto';
19
+ }
20
+ export function normalizeLocalAccessName(input) {
21
+ if (typeof input !== 'string')
22
+ return null;
23
+ const normalized = input.trim().toLowerCase();
24
+ if (normalized.length === 0)
25
+ return null;
26
+ // DNS label: 1..63 chars, alnum at both ends, hyphen allowed inside.
27
+ if (normalized.length > 63)
28
+ return null;
29
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(normalized))
30
+ return null;
31
+ return normalized;
32
+ }
33
+ export function createAutoLocalAccessName() {
34
+ return generateAutoName();
35
+ }
36
+ function normalizeLocalAccessSettings(value) {
37
+ const raw = value && typeof value === 'object'
38
+ ? value
39
+ : {};
40
+ const source = normalizeSource(raw.source);
41
+ const normalizedName = normalizeLocalAccessName(raw.name ?? raw.shortName);
42
+ return {
43
+ name: normalizedName ?? generateAutoName(),
44
+ source: normalizedName ? source : 'auto',
45
+ };
46
+ }
47
+ function normalizeSettings(value) {
48
+ const raw = value && typeof value === 'object'
49
+ ? value
50
+ : {};
51
+ return {
52
+ version: 1,
53
+ preventSleep: raw.preventSleep === true,
54
+ localAccess: normalizeLocalAccessSettings(raw.localAccess),
55
+ firstRunCompleted: raw.firstRunCompleted === true,
56
+ updatedAt: typeof raw.updatedAt === 'number' ? raw.updatedAt : Date.now(),
57
+ };
58
+ }
59
+ export function loadSettings() {
60
+ try {
61
+ const raw = fs.readFileSync(SETTINGS_FILE, 'utf-8');
62
+ const doc = normalizeSettings(JSON.parse(raw));
63
+ // Persist migrated defaults (for example the first generated local-access name)
64
+ // so the advertised hostname stays stable across restarts.
65
+ saveSettings(doc);
66
+ return doc;
67
+ }
68
+ catch { /* ignore missing/malformed settings */ }
69
+ const initial = normalizeSettings(null);
70
+ try {
71
+ saveSettings(initial);
72
+ }
73
+ catch { /* best effort */ }
74
+ return initial;
75
+ }
76
+ export async function loadSettingsAsync() {
77
+ try {
78
+ const raw = await fs.promises.readFile(SETTINGS_FILE, 'utf-8');
79
+ const doc = normalizeSettings(JSON.parse(raw));
80
+ await saveSettingsAsync(doc);
81
+ return doc;
82
+ }
83
+ catch { /* ignore missing/malformed settings */ }
84
+ const initial = normalizeSettings(null);
85
+ try {
86
+ await saveSettingsAsync(initial);
87
+ }
88
+ catch { /* best effort */ }
89
+ return initial;
90
+ }
91
+ export function saveSettings(next) {
92
+ fs.mkdirSync(SETTINGS_DIR, { recursive: true });
93
+ fs.writeFileSync(SETTINGS_FILE, JSON.stringify(next, null, 2), 'utf-8');
94
+ }
95
+ export async function saveSettingsAsync(next) {
96
+ await fs.promises.mkdir(SETTINGS_DIR, { recursive: true });
97
+ await fs.promises.writeFile(SETTINGS_FILE, JSON.stringify(next, null, 2), 'utf-8');
98
+ }
99
+ export function updateSettings(mutator) {
100
+ const current = loadSettings();
101
+ const next = mutator(current) ?? current;
102
+ next.updatedAt = Date.now();
103
+ saveSettings(next);
104
+ return next;
105
+ }
106
+ export async function updateSettingsAsync(mutator) {
107
+ const current = await loadSettingsAsync();
108
+ const next = mutator(current) ?? current;
109
+ next.updatedAt = Date.now();
110
+ await saveSettingsAsync(next);
111
+ return next;
112
+ }
113
+ export function getPreventSleepSetting() {
114
+ return loadSettings().preventSleep;
115
+ }
116
+ export async function getPreventSleepSettingAsync() {
117
+ return (await loadSettingsAsync()).preventSleep;
118
+ }
119
+ export function setPreventSleepSetting(enabled) {
120
+ return updateSettings((settings) => {
121
+ settings.preventSleep = enabled;
122
+ });
123
+ }
124
+ export async function setPreventSleepSettingAsync(enabled) {
125
+ return updateSettingsAsync((settings) => {
126
+ settings.preventSleep = enabled;
127
+ });
128
+ }
129
+ export function getLocalAccessSetting() {
130
+ return loadSettings().localAccess;
131
+ }
132
+ export async function getLocalAccessSettingAsync() {
133
+ return (await loadSettingsAsync()).localAccess;
134
+ }
135
+ export function setLocalAccessSetting(next) {
136
+ return updateSettings((settings) => {
137
+ settings.localAccess = next;
138
+ });
139
+ }
140
+ export async function setLocalAccessSettingAsync(next) {
141
+ return updateSettingsAsync((settings) => {
142
+ settings.localAccess = next;
143
+ });
144
+ }
145
+ export function resetLocalAccessSetting() {
146
+ return updateSettings((settings) => {
147
+ settings.localAccess = { name: generateAutoName(), source: 'auto' };
148
+ });
149
+ }
150
+ export function markFirstRunCompleted() {
151
+ return updateSettings((settings) => {
152
+ settings.firstRunCompleted = true;
153
+ });
154
+ }
155
+ export function isFirstRunCompleted() {
156
+ return loadSettings().firstRunCompleted;
157
+ }
@@ -0,0 +1,45 @@
1
+ export const FOCUS_IN_SEQUENCE = '\x1b[I';
2
+ export const FOCUS_OUT_SEQUENCE = '\x1b[O';
3
+ const FOCUS_MODE_PATTERN = /\x1b\[\?1004([hl])/g;
4
+ const MAX_FOCUS_MODE_BUFFER = 16;
5
+ export function scanFocusTrackingMode(chunk, previous) {
6
+ const combined = previous.buffer + chunk;
7
+ let requested = previous.requested;
8
+ let changed = false;
9
+ let match;
10
+ FOCUS_MODE_PATTERN.lastIndex = 0;
11
+ while ((match = FOCUS_MODE_PATTERN.exec(combined)) !== null) {
12
+ const nextRequested = match[1] === 'h';
13
+ if (requested !== nextRequested) {
14
+ requested = nextRequested;
15
+ changed = true;
16
+ }
17
+ }
18
+ return {
19
+ buffer: combined.slice(-MAX_FOCUS_MODE_BUFFER),
20
+ requested,
21
+ changed,
22
+ };
23
+ }
24
+ export function setClientFocusState(state, clientId, focused) {
25
+ if (focused) {
26
+ state.focusedClients.set(clientId, true);
27
+ }
28
+ else {
29
+ state.focusedClients.delete(clientId);
30
+ }
31
+ return recomputeEffectiveFocus(state);
32
+ }
33
+ export function removeClientFocusState(state, clientId) {
34
+ state.focusedClients.delete(clientId);
35
+ return recomputeEffectiveFocus(state);
36
+ }
37
+ export function recomputeEffectiveFocus(state) {
38
+ const nextFocused = Array.from(state.focusedClients.values()).some(Boolean);
39
+ const changed = state.effectiveFocused !== nextFocused;
40
+ state.effectiveFocused = nextFocused;
41
+ return { effectiveFocused: nextFocused, changed };
42
+ }
43
+ export function getFocusSequence(focused) {
44
+ return focused ? FOCUS_IN_SEQUENCE : FOCUS_OUT_SEQUENCE;
45
+ }
@@ -0,0 +1,148 @@
1
+ export const DEFAULT_TMUX_HELPER_PROGRAM_NAMES = new Set([
2
+ 'caffeinate',
3
+ 'bytecloud-auth-rpc',
4
+ 'bytecloud-auth-rpc-darwin-arm64',
5
+ 'gitstatusd',
6
+ 'gitstatusd-darwin-arm64',
7
+ ]);
8
+ export function normalizeProgramName(command) {
9
+ if (typeof command !== 'string') {
10
+ return null;
11
+ }
12
+ const normalized = command.trim();
13
+ if (!normalized) {
14
+ return null;
15
+ }
16
+ const lastSegment = normalized.split(/[\\/]/).pop()?.trim();
17
+ return lastSegment && lastSegment.length > 0 ? lastSegment : normalized;
18
+ }
19
+ export function extractProgramLabelFromArgs(args, options) {
20
+ if (!args)
21
+ return null;
22
+ const parts = args.split(/\s+/);
23
+ const exe = parts[0];
24
+ const exeName = normalizeProgramName(exe) || exe;
25
+ if (options.genericProgramNames.has(exeName) && parts.length > 1) {
26
+ const script = parts[1];
27
+ const scriptName = normalizeProgramName(script) || script;
28
+ const withoutExt = scriptName.replace(/\.(js|ts|mjs|cjs|py|sh|rb|pl|lua)$/, '');
29
+ if (withoutExt && withoutExt.length > 0) {
30
+ if (options.wrapperScriptNames.has(withoutExt)) {
31
+ const remaining = parts.slice(2);
32
+ const skipTokens = new Set(['x', 'run', 'exec', 'use']);
33
+ let idx = 0;
34
+ if (remaining.length > 0 && skipTokens.has(remaining[0])) {
35
+ idx = 1;
36
+ }
37
+ for (let i = idx; i < remaining.length; i += 1) {
38
+ const token = remaining[i];
39
+ if (token.startsWith('-'))
40
+ continue;
41
+ return normalizeProgramName(token);
42
+ }
43
+ return withoutExt;
44
+ }
45
+ return withoutExt;
46
+ }
47
+ }
48
+ return exeName;
49
+ }
50
+ function lowerProgramName(value) {
51
+ return normalizeProgramName(value)?.toLowerCase() ?? null;
52
+ }
53
+ function isKnownProgram(value, names) {
54
+ const normalized = lowerProgramName(value);
55
+ if (!normalized)
56
+ return false;
57
+ if (names.has(normalized))
58
+ return true;
59
+ for (const name of names) {
60
+ if (normalized.includes(name))
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+ function ancestorDistance(row, panePid, rowsByPid) {
66
+ let current = row;
67
+ const seen = new Set();
68
+ for (let depth = 0; depth < 25; depth += 1) {
69
+ if (!current || seen.has(current.pid))
70
+ return null;
71
+ seen.add(current.pid);
72
+ if (current.ppid === panePid)
73
+ return depth + 1;
74
+ current = rowsByPid.get(current.ppid);
75
+ }
76
+ return null;
77
+ }
78
+ function isDirectProgramMatch(row, command) {
79
+ if (!command)
80
+ return false;
81
+ const normalized = command.toLowerCase();
82
+ const comm = lowerProgramName(row.comm);
83
+ const exe = lowerProgramName(row.args.split(/\s+/)[0]);
84
+ return comm === normalized || exe === normalized;
85
+ }
86
+ export function selectTmuxForegroundProgram(input) {
87
+ const helperProgramNames = input.helperProgramNames ?? DEFAULT_TMUX_HELPER_PROGRAM_NAMES;
88
+ const rowsByPid = new Map(input.rows.map((row) => [row.pid, row]));
89
+ const foregroundRows = input.rows.filter((row) => row.pid !== input.panePid && row.tpgid > 0 && row.pgid === row.tpgid && !row.stat.startsWith('Z'));
90
+ if (foregroundRows.length === 0) {
91
+ return null;
92
+ }
93
+ const decorated = foregroundRows.map((row) => {
94
+ const command = normalizeProgramName(input.extractProgramLabel(row.args) ?? row.comm);
95
+ const commandKey = command?.toLowerCase() ?? null;
96
+ const distance = ancestorDistance(row, input.panePid, rowsByPid);
97
+ const isHelper = isKnownProgram(command, helperProgramNames) || isKnownProgram(row.comm, helperProgramNames) || isKnownProgram(row.args.split(/\s+/)[0], helperProgramNames);
98
+ const isShell = commandKey ? input.shellNames.has(commandKey) : false;
99
+ const isGeneric = commandKey ? input.genericProgramNames.has(commandKey) : false;
100
+ let score = 0;
101
+ if (!isHelper)
102
+ score += 1000;
103
+ if (!isShell && !isGeneric)
104
+ score += 200;
105
+ if (isDirectProgramMatch(row, command))
106
+ score += 120;
107
+ if (distance !== null)
108
+ score += 50 + distance;
109
+ if (isHelper)
110
+ score -= 1000;
111
+ if (isShell)
112
+ score -= 200;
113
+ if (isGeneric)
114
+ score -= 80;
115
+ return { row, command, isHelper, score };
116
+ });
117
+ const nonHelper = decorated.filter((candidate) => candidate.command && !candidate.isHelper);
118
+ const pool = nonHelper.length > 0
119
+ ? nonHelper
120
+ : decorated.filter((candidate) => candidate.command);
121
+ pool.sort((a, b) => {
122
+ if (b.score !== a.score)
123
+ return b.score - a.score;
124
+ return a.row.pid - b.row.pid;
125
+ });
126
+ const selected = pool[0];
127
+ if (!selected?.command) {
128
+ return null;
129
+ }
130
+ return {
131
+ command: selected.command,
132
+ rawArgs: selected.row.args,
133
+ };
134
+ }
135
+ export function normalizeTmuxMetadataProgram(program, options) {
136
+ const normalized = normalizeProgramName(program);
137
+ if (!normalized)
138
+ return null;
139
+ const key = normalized.toLowerCase();
140
+ if (options.shellNames.has(key))
141
+ return null;
142
+ if (isKnownProgram(normalized, options.helperProgramNames ?? DEFAULT_TMUX_HELPER_PROGRAM_NAMES))
143
+ return null;
144
+ return normalized;
145
+ }
146
+ export function tmuxMetadataChanged(previous, next) {
147
+ return !previous || previous.program !== next.program || previous.cwd !== next.cwd || previous.label !== next.label;
148
+ }
package/package.json ADDED
@@ -0,0 +1,125 @@
1
+ {
2
+ "name": "termdock",
3
+ "version": "1.0.0",
4
+ "description": "A complete web-based terminal application with modern UI",
5
+ "type": "module",
6
+ "bin": {
7
+ "termdock": "dist/server/cli.js",
8
+ "td": "dist/server/cli.js"
9
+ },
10
+ "exports": {
11
+ ".": "./dist/server/entry.js"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "clean": "rimraf dist",
20
+ "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\"",
21
+ "dev:client": "vite",
22
+ "dev:server": "cross-env PORT=9835 tsx watch src/server/entry.ts",
23
+ "build": "npm run clean && npm run build:client && npm run build:server",
24
+ "build:client": "vite build",
25
+ "build:server": "tsc -p tsconfig.server.json && node -e \"require('fs').chmodSync('dist/server/cli.js', 0o755)\"",
26
+ "generate:pwa-assets": "pwa-assets-generator --config pwa-assets.config.ts",
27
+ "preview": "vite preview",
28
+ "start": "node dist/server/cli.js",
29
+ "lint": "tsc --noEmit",
30
+ "trace:diff": "node scripts/inspect-diff-trace.mjs",
31
+ "trace:fsio": "node scripts/inspect-fs-io.mjs",
32
+ "test": "vitest --passWithNoTests",
33
+ "test:ui": "vitest --ui",
34
+ "prepublishOnly": "npm run build"
35
+ },
36
+ "dependencies": {
37
+ "@dnd-kit/core": "^6.3.1",
38
+ "@dnd-kit/sortable": "^10.0.0",
39
+ "@dnd-kit/utilities": "^3.2.2",
40
+ "@hello-pangea/dnd": "^18.0.1",
41
+ "@parcel/watcher": "^2.5.6",
42
+ "@types/cookie-parser": "^1.4.10",
43
+ "@types/csurf": "^1.11.5",
44
+ "@types/refractor": "^2.8.0",
45
+ "@use-gesture/react": "^10.3.1",
46
+ "@xterm/addon-clipboard": "^0.3.0-beta.287",
47
+ "@xterm/addon-fit": "^0.12.0-beta.287",
48
+ "@xterm/addon-image": "^0.10.0-beta.287",
49
+ "@xterm/addon-ligatures": "^0.11.0-beta.287",
50
+ "@xterm/addon-progress": "^0.3.0-beta.287",
51
+ "@xterm/addon-search": "^0.17.0-beta.287",
52
+ "@xterm/addon-serialize": "^0.15.0-beta.287",
53
+ "@xterm/addon-unicode11": "^0.10.0-beta.287",
54
+ "@xterm/addon-web-links": "^0.13.0-beta.287",
55
+ "@xterm/addon-webgl": "^0.20.0-beta.286",
56
+ "@xterm/xterm": "^6.1.0-beta.287",
57
+ "browser-haptic": "^1.0.13",
58
+ "busboy": "^1.6.0",
59
+ "cookie-parser": "^1.4.7",
60
+ "dompurify": "^3.4.11",
61
+ "dotenv": "^17.4.2",
62
+ "express": "^5.2.1",
63
+ "katex": "^0.17.0",
64
+ "lucide-react": "^1.17.0",
65
+ "mermaid": "^11.16.0",
66
+ "motion": "^12.40.0",
67
+ "multicast-dns": "^7.2.5",
68
+ "node-pty": "^1.1.0",
69
+ "qrcode": "^1.5.4",
70
+ "react": "^18.2.0",
71
+ "react-diff-view": "^3.3.3",
72
+ "react-dom": "^18.2.0",
73
+ "refractor": "^2.10.1",
74
+ "swiper": "^12.1.3",
75
+ "tailwind-merge": "^2.2.0",
76
+ "uuid": "^13.0.0",
77
+ "ws": "^8.21.0",
78
+ "zustand": "^4.5.0"
79
+ },
80
+ "devDependencies": {
81
+ "@testing-library/react": "^14.1.0",
82
+ "@testing-library/user-event": "^14.5.0",
83
+ "@types/busboy": "^1.5.4",
84
+ "@types/express": "^4.17.21",
85
+ "@types/node": "^20.11.0",
86
+ "@types/react": "^18.2.0",
87
+ "@types/react-dom": "^18.2.0",
88
+ "@types/uuid": "^10.0.0",
89
+ "@types/ws": "^8.18.1",
90
+ "@vite-pwa/assets-generator": "^1.0.2",
91
+ "@vitejs/plugin-react": "^4.2.0",
92
+ "autoprefixer": "^10.4.0",
93
+ "concurrently": "^8.2.0",
94
+ "cross-env": "^10.1.0",
95
+ "eslint": "^9.39.2",
96
+ "jsdom": "^24.0.0",
97
+ "postcss": "^8.4.0",
98
+ "rimraf": "^6.1.3",
99
+ "tailwindcss": "^3.4.0",
100
+ "tsx": "^4.7.0",
101
+ "typescript": "^5.3.0",
102
+ "vite": "^7.3.1",
103
+ "vite-plugin-pwa": "^1.2.0",
104
+ "vitest": "^4.0.17"
105
+ },
106
+ "engines": {
107
+ "node": ">=18.0.0"
108
+ },
109
+ "keywords": [
110
+ "terminal",
111
+ "termdock",
112
+ "xterm",
113
+ "express",
114
+ "cli"
115
+ ],
116
+ "license": "MIT",
117
+ "repository": {
118
+ "type": "git",
119
+ "url": "git+https://github.com/Jovines/termdock.git"
120
+ },
121
+ "homepage": "https://github.com/Jovines/termdock#readme",
122
+ "bugs": {
123
+ "url": "https://github.com/Jovines/termdock/issues"
124
+ }
125
+ }