pim-agent 0.0.1 → 0.10.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 (275) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -7
  3. package/bin/pim.ts +151 -0
  4. package/package.json +82 -4
  5. package/packages/core/src/attachments/AttachmentStore.ts +99 -0
  6. package/packages/core/src/attachments/Attachments.ts +69 -0
  7. package/packages/core/src/extensions/CoreExtensions.ts +54 -0
  8. package/packages/core/src/extensions/apply-patch/coordinator.ts +53 -0
  9. package/packages/core/src/extensions/apply-patch/executor.ts +550 -0
  10. package/packages/core/src/extensions/apply-patch/index.ts +69 -0
  11. package/packages/core/src/extensions/apply-patch/matcher.ts +47 -0
  12. package/packages/core/src/extensions/apply-patch/model.ts +39 -0
  13. package/packages/core/src/extensions/apply-patch/parser.ts +379 -0
  14. package/packages/core/src/extensions/apply-patch/render.ts +177 -0
  15. package/packages/core/src/extensions/apply-patch/schema.ts +37 -0
  16. package/packages/core/src/extensions/apply-patch/types.ts +30 -0
  17. package/packages/core/src/extensions/bash/capture.ts +86 -0
  18. package/packages/core/src/extensions/bash/format.ts +110 -0
  19. package/packages/core/src/extensions/bash/image.ts +31 -0
  20. package/packages/core/src/extensions/bash/index.ts +88 -0
  21. package/packages/core/src/extensions/bash/render.ts +31 -0
  22. package/packages/core/src/extensions/bash/run.ts +221 -0
  23. package/packages/core/src/extensions/bash/schema.ts +64 -0
  24. package/packages/core/src/extensions/edit/edit.ts +366 -0
  25. package/packages/core/src/extensions/edit/index.ts +36 -0
  26. package/packages/core/src/extensions/edit/render.ts +16 -0
  27. package/packages/core/src/extensions/edit/schema.ts +37 -0
  28. package/packages/core/src/extensions/glob/glob.ts +34 -0
  29. package/packages/core/src/extensions/glob/index.ts +83 -0
  30. package/packages/core/src/extensions/glob/render.ts +72 -0
  31. package/packages/core/src/extensions/glob/schema.ts +66 -0
  32. package/packages/core/src/extensions/grep/grep.ts +256 -0
  33. package/packages/core/src/extensions/grep/index.ts +114 -0
  34. package/packages/core/src/extensions/grep/render.ts +271 -0
  35. package/packages/core/src/extensions/grep/schema.ts +92 -0
  36. package/packages/core/src/extensions/read/ImageMemory.ts +42 -0
  37. package/packages/core/src/extensions/read/image.ts +57 -0
  38. package/packages/core/src/extensions/read/index.ts +88 -0
  39. package/packages/core/src/extensions/read/read.ts +191 -0
  40. package/packages/core/src/extensions/read/render.ts +58 -0
  41. package/packages/core/src/extensions/read/schema.ts +49 -0
  42. package/packages/core/src/extensions/subagent/index.ts +33 -0
  43. package/packages/core/src/extensions/subagent/render.ts +127 -0
  44. package/packages/core/src/extensions/subagent/schema.ts +9 -0
  45. package/packages/core/src/extensions/subagent/subagent.ts +419 -0
  46. package/packages/core/src/extensions/system-prompt/index.ts +36 -0
  47. package/packages/core/src/extensions/system-prompt/prompt.ts +232 -0
  48. package/packages/core/src/extensions/todo/index.ts +123 -0
  49. package/packages/core/src/extensions/todo/render.ts +156 -0
  50. package/packages/core/src/extensions/todo/schema.ts +24 -0
  51. package/packages/core/src/extensions/todo/todo.ts +185 -0
  52. package/packages/core/src/extensions/web-fetch/JinaReaderClient.ts +187 -0
  53. package/packages/core/src/extensions/web-fetch/WebViewFetchClient.ts +183 -0
  54. package/packages/core/src/extensions/web-fetch/WebViewMarkdownSnapshot.ts +512 -0
  55. package/packages/core/src/extensions/web-fetch/fetch.ts +265 -0
  56. package/packages/core/src/extensions/web-fetch/image.ts +165 -0
  57. package/packages/core/src/extensions/web-fetch/index.ts +83 -0
  58. package/packages/core/src/extensions/web-fetch/render.ts +100 -0
  59. package/packages/core/src/extensions/web-fetch/schema.ts +46 -0
  60. package/packages/core/src/extensions/web-search/ExaMcpClient.ts +251 -0
  61. package/packages/core/src/extensions/web-search/SearchBreaker.ts +147 -0
  62. package/packages/core/src/extensions/web-search/SearchChain.ts +77 -0
  63. package/packages/core/src/extensions/web-search/index.ts +90 -0
  64. package/packages/core/src/extensions/web-search/providers/DuckDuckGoProvider.ts +195 -0
  65. package/packages/core/src/extensions/web-search/providers/ExaProvider.ts +54 -0
  66. package/packages/core/src/extensions/web-search/providers/FirecrawlProvider.ts +144 -0
  67. package/packages/core/src/extensions/web-search/providers/SearchProvider.ts +118 -0
  68. package/packages/core/src/extensions/web-search/render.ts +49 -0
  69. package/packages/core/src/extensions/web-search/schema.ts +29 -0
  70. package/packages/core/src/extensions/web-search/search.ts +23 -0
  71. package/packages/core/src/extensions/write/index.ts +58 -0
  72. package/packages/core/src/extensions/write/render.ts +16 -0
  73. package/packages/core/src/extensions/write/schema.ts +14 -0
  74. package/packages/core/src/extensions/write/write.ts +87 -0
  75. package/packages/core/src/picker/FilePickerSuggestionEngine.ts +14 -0
  76. package/packages/core/src/picker/InProcessFilePickerSuggestionEngine.ts +52 -0
  77. package/packages/core/src/picker/PickerItem.ts +6 -0
  78. package/packages/core/src/picker/PickerService.ts +132 -0
  79. package/packages/core/src/picker/RemoteFilePickerSuggestionEngine.ts +56 -0
  80. package/packages/core/src/picker/WorkerFilePickerSuggestionEngine.ts +268 -0
  81. package/packages/core/src/picker/catalog.ts +220 -0
  82. package/packages/core/src/picker/commandRanker.ts +17 -0
  83. package/packages/core/src/picker/filePickerWorker.ts +66 -0
  84. package/packages/core/src/picker/filePickerWorkerMessages.ts +39 -0
  85. package/packages/core/src/picker/ranker.ts +245 -0
  86. package/packages/core/src/picker/token.ts +77 -0
  87. package/packages/core/src/session/AgentRuntime.ts +85 -0
  88. package/packages/core/src/session/EventLog.ts +156 -0
  89. package/packages/core/src/session/MessageText.ts +21 -0
  90. package/packages/core/src/session/ReadCursors.ts +94 -0
  91. package/packages/core/src/session/SearchIndex.ts +752 -0
  92. package/packages/core/src/session/SearchTokens.ts +97 -0
  93. package/packages/core/src/session/SessionCache.ts +68 -0
  94. package/packages/core/src/session/SessionDigest.ts +161 -0
  95. package/packages/core/src/session/SessionHost.ts +892 -0
  96. package/packages/core/src/session/SessionLease.ts +279 -0
  97. package/packages/core/src/session/SessionMeta.ts +267 -0
  98. package/packages/core/src/session/SessionName.ts +18 -0
  99. package/packages/core/src/session/SessionRegistry.ts +244 -0
  100. package/packages/core/src/session/WriteMark.ts +51 -0
  101. package/packages/core/src/shared/Cli.ts +25 -0
  102. package/packages/core/src/shared/DaemonUnit.ts +13 -0
  103. package/packages/core/src/shared/DiffLines.ts +304 -0
  104. package/packages/core/src/shared/DiffPatch.ts +70 -0
  105. package/packages/core/src/shared/DiffRenderer.ts +283 -0
  106. package/packages/core/src/shared/DiffView.ts +29 -0
  107. package/packages/core/src/shared/Directories.ts +61 -0
  108. package/packages/core/src/shared/EditMatcher.ts +767 -0
  109. package/packages/core/src/shared/Errors.ts +9 -0
  110. package/packages/core/src/shared/ExtensionToggles.ts +130 -0
  111. package/packages/core/src/shared/FileEnumerator.ts +361 -0
  112. package/packages/core/src/shared/FileScanner.ts +57 -0
  113. package/packages/core/src/shared/FileWatch.ts +121 -0
  114. package/packages/core/src/shared/Format.ts +74 -0
  115. package/packages/core/src/shared/Fs.ts +91 -0
  116. package/packages/core/src/shared/FsErrors.ts +67 -0
  117. package/packages/core/src/shared/FuzzyMatcher.ts +71 -0
  118. package/packages/core/src/shared/Git.ts +530 -0
  119. package/packages/core/src/shared/GitMonitor.ts +221 -0
  120. package/packages/core/src/shared/GlobExclusions.ts +13 -0
  121. package/packages/core/src/shared/Http.ts +14 -0
  122. package/packages/core/src/shared/ImageMime.ts +36 -0
  123. package/packages/core/src/shared/Images.ts +357 -0
  124. package/packages/core/src/shared/Json.ts +19 -0
  125. package/packages/core/src/shared/Languages.ts +95 -0
  126. package/packages/core/src/shared/Levenshtein.ts +83 -0
  127. package/packages/core/src/shared/Lines.ts +79 -0
  128. package/packages/core/src/shared/McpClient.ts +444 -0
  129. package/packages/core/src/shared/OutputBudget.ts +84 -0
  130. package/packages/core/src/shared/PatchSummary.ts +40 -0
  131. package/packages/core/src/shared/Paths.ts +80 -0
  132. package/packages/core/src/shared/PimSettings.ts +128 -0
  133. package/packages/core/src/shared/PimVersion.ts +93 -0
  134. package/packages/core/src/shared/Pool.ts +26 -0
  135. package/packages/core/src/shared/Proc.ts +50 -0
  136. package/packages/core/src/shared/RateLimiter.ts +50 -0
  137. package/packages/core/src/shared/Renderer.ts +371 -0
  138. package/packages/core/src/shared/RepoDiff.ts +741 -0
  139. package/packages/core/src/shared/SafePath.ts +17 -0
  140. package/packages/core/src/shared/SearchRender.ts +82 -0
  141. package/packages/core/src/shared/SendFile.ts +23 -0
  142. package/packages/core/src/shared/SpillCache.ts +78 -0
  143. package/packages/core/src/shared/SubagentLogs.ts +102 -0
  144. package/packages/core/src/shared/Supervisor.ts +460 -0
  145. package/packages/core/src/shared/Surface.ts +2 -0
  146. package/packages/core/src/shared/Sweeper.ts +32 -0
  147. package/packages/core/src/shared/Tools.ts +801 -0
  148. package/packages/core/src/shared/Updater.ts +209 -0
  149. package/packages/core/src/view/AnsiPainter.ts +227 -0
  150. package/packages/core/src/view/BodyRenderer.ts +102 -0
  151. package/packages/core/src/view/DiffBlocks.ts +52 -0
  152. package/packages/core/src/view/DiffExpand.ts +214 -0
  153. package/packages/core/src/view/DiffLayout.ts +69 -0
  154. package/packages/core/src/view/DiffPairs.ts +47 -0
  155. package/packages/core/src/view/MarkdownPainter.ts +314 -0
  156. package/packages/core/src/view/MovePath.ts +70 -0
  157. package/packages/core/src/view/Painting.ts +182 -0
  158. package/packages/core/src/view/ViewBlock.ts +124 -0
  159. package/packages/daemon/src/Daemon.ts +63 -0
  160. package/packages/daemon/src/DaemonInstall.ts +115 -0
  161. package/packages/daemon/src/Surfaces.ts +66 -0
  162. package/packages/daemon/src/TelegramSurface.ts +30 -0
  163. package/packages/daemon/src/WebSurface.ts +56 -0
  164. package/packages/daemon/src/index.ts +37 -0
  165. package/packages/protocol/src/Command.ts +219 -0
  166. package/packages/protocol/src/Diff.ts +15 -0
  167. package/packages/protocol/src/ImageRoute.ts +23 -0
  168. package/packages/protocol/src/ServerEvent.ts +406 -0
  169. package/packages/server/src/AttachmentEndpoint.ts +184 -0
  170. package/packages/server/src/ClientConnection.ts +277 -0
  171. package/packages/server/src/ImageEndpoint.ts +36 -0
  172. package/packages/server/src/ProbeClient.ts +374 -0
  173. package/packages/server/src/Reloader.ts +93 -0
  174. package/packages/server/src/SendFileTool.ts +88 -0
  175. package/packages/server/src/SessionCatalogue.ts +456 -0
  176. package/packages/server/src/SessionProjection.ts +194 -0
  177. package/packages/server/src/SessionStream.ts +627 -0
  178. package/packages/server/src/StaticClient.ts +178 -0
  179. package/packages/server/src/WebOptions.ts +72 -0
  180. package/packages/server/src/WsGateway.ts +798 -0
  181. package/packages/server/src/probe.ts +200 -0
  182. package/packages/telegram/src/Bot.ts +229 -0
  183. package/packages/telegram/src/Commands.ts +768 -0
  184. package/packages/telegram/src/Config.ts +148 -0
  185. package/packages/telegram/src/Markdown.ts +235 -0
  186. package/packages/telegram/src/Message.ts +152 -0
  187. package/packages/telegram/src/Renderer.ts +456 -0
  188. package/packages/telegram/src/SendFileSchema.ts +18 -0
  189. package/packages/telegram/src/SendFileTool.ts +65 -0
  190. package/packages/telegram/src/Session.ts +253 -0
  191. package/packages/telegram/src/SessionRegistry.ts +126 -0
  192. package/packages/telegram/src/TaskScheduler.ts +296 -0
  193. package/packages/telegram/src/TaskSchema.ts +88 -0
  194. package/packages/telegram/src/TaskStore.ts +67 -0
  195. package/packages/telegram/src/TaskTool.ts +208 -0
  196. package/packages/telegram/src/TelegramHtml.ts +288 -0
  197. package/packages/telegram/src/TypingIndicator.ts +43 -0
  198. package/packages/telegram/src/UpdateConfirm.ts +61 -0
  199. package/packages/tui/src/extensions/_init/index.ts +144 -0
  200. package/packages/tui/src/extensions/command-picker/index.ts +75 -0
  201. package/packages/tui/src/extensions/file-picker/index.ts +156 -0
  202. package/packages/tui/src/extensions/footer/index.ts +93 -0
  203. package/packages/tui/src/extensions/footer/powerline.ts +87 -0
  204. package/packages/tui/src/extensions/footer/segments.ts +202 -0
  205. package/packages/tui/src/extensions/pim/index.ts +89 -0
  206. package/packages/tui/src/extensions/session-lease/index.ts +253 -0
  207. package/packages/tui/src/extensions/tps/index.ts +118 -0
  208. package/packages/tui/src/extensions/working-indicator/index.ts +75 -0
  209. package/packages/tui/src/extensions/wrapProvider.ts +18 -0
  210. package/packages/tui/src/themes/pim-dark.json +84 -0
  211. package/packages/tui/src/themes/pim-light.json +84 -0
  212. package/packages/tui/src/themes/themeCliArgs.ts +20 -0
  213. package/packages/web/dist/client/apple-touch-icon.png +0 -0
  214. package/packages/web/dist/client/assets/bash-LmRjUKlR.js +1 -0
  215. package/packages/web/dist/client/assets/c-DBVbJFpC.js +1 -0
  216. package/packages/web/dist/client/assets/clojure-CR7dvLJ4.js +1 -0
  217. package/packages/web/dist/client/assets/cmake-CgP6gB4W.js +1 -0
  218. package/packages/web/dist/client/assets/commit-mono-latin-300-italic-B48RJMeK.woff +0 -0
  219. package/packages/web/dist/client/assets/commit-mono-latin-300-italic-DBq5bJCE.woff2 +0 -0
  220. package/packages/web/dist/client/assets/commit-mono-latin-300-normal-B-iV2FbL.woff2 +0 -0
  221. package/packages/web/dist/client/assets/commit-mono-latin-300-normal-kM0OTZCv.woff +0 -0
  222. package/packages/web/dist/client/assets/commit-mono-latin-400-italic-BXinMwCi.woff +0 -0
  223. package/packages/web/dist/client/assets/commit-mono-latin-400-italic-DjSHLl2N.woff2 +0 -0
  224. package/packages/web/dist/client/assets/commit-mono-latin-400-normal-s0S3qwFW.woff +0 -0
  225. package/packages/web/dist/client/assets/commit-mono-latin-400-normal-wzhe4RuD.woff2 +0 -0
  226. package/packages/web/dist/client/assets/commit-mono-latin-600-normal-BuQDXT0M.woff +0 -0
  227. package/packages/web/dist/client/assets/commit-mono-latin-600-normal-SwgSWHaV.woff2 +0 -0
  228. package/packages/web/dist/client/assets/commit-mono-latin-700-normal-1-GU0IUE.woff +0 -0
  229. package/packages/web/dist/client/assets/commit-mono-latin-700-normal-DU8mrtj2.woff2 +0 -0
  230. package/packages/web/dist/client/assets/core-CA0aSzPu.js +3 -0
  231. package/packages/web/dist/client/assets/cpp-N2d8M7Nc.js +1 -0
  232. package/packages/web/dist/client/assets/csharp-yYt0Ikks.js +1 -0
  233. package/packages/web/dist/client/assets/css-D8M8GYaw.js +1 -0
  234. package/packages/web/dist/client/assets/diff-BhdZizse.js +1 -0
  235. package/packages/web/dist/client/assets/dockerfile-C0zufGsU.js +1 -0
  236. package/packages/web/dist/client/assets/elixir-DXIHlGsR.js +1 -0
  237. package/packages/web/dist/client/assets/erlang-BjYI8dhz.js +1 -0
  238. package/packages/web/dist/client/assets/go-CYstVVJV.js +1 -0
  239. package/packages/web/dist/client/assets/graphql-BHHhacaR.js +1 -0
  240. package/packages/web/dist/client/assets/haskell-DUkhXIda.js +1 -0
  241. package/packages/web/dist/client/assets/index-B9be-i9d.css +1 -0
  242. package/packages/web/dist/client/assets/index-CpinM5NK.js +45 -0
  243. package/packages/web/dist/client/assets/ini-B1CroMsf.js +1 -0
  244. package/packages/web/dist/client/assets/java-Dde-Wnb4.js +1 -0
  245. package/packages/web/dist/client/assets/javascript-8AmHuH5O.js +1 -0
  246. package/packages/web/dist/client/assets/json-PmyKWHmm.js +1 -0
  247. package/packages/web/dist/client/assets/kotlin-DgSK8bu-.js +2 -0
  248. package/packages/web/dist/client/assets/less-Bp0mgmpG.js +1 -0
  249. package/packages/web/dist/client/assets/lua-1dXYaL-H.js +1 -0
  250. package/packages/web/dist/client/assets/makefile-BX-o3EIz.js +1 -0
  251. package/packages/web/dist/client/assets/markdown-D6FrnlqJ.js +1 -0
  252. package/packages/web/dist/client/assets/ocaml-B-P1Uyq8.js +1 -0
  253. package/packages/web/dist/client/assets/perl-CvqMZYuf.js +1 -0
  254. package/packages/web/dist/client/assets/php-DuZl5e-L.js +2 -0
  255. package/packages/web/dist/client/assets/powershell-BUe16zE4.js +1 -0
  256. package/packages/web/dist/client/assets/protobuf-DGOW1qT8.js +1 -0
  257. package/packages/web/dist/client/assets/python-gHmu6VPG.js +1 -0
  258. package/packages/web/dist/client/assets/r-DNWPCnjT.js +1 -0
  259. package/packages/web/dist/client/assets/ruby-BGpZ3p4_.js +1 -0
  260. package/packages/web/dist/client/assets/rust-BIKPVoi6.js +1 -0
  261. package/packages/web/dist/client/assets/scala-C9lP4FWY.js +1 -0
  262. package/packages/web/dist/client/assets/scss-gOyOKiXd.js +1 -0
  263. package/packages/web/dist/client/assets/sql-DWRKyvJ-.js +1 -0
  264. package/packages/web/dist/client/assets/swift-BTn9__hL.js +1 -0
  265. package/packages/web/dist/client/assets/typescript-CxkPLmSc.js +1 -0
  266. package/packages/web/dist/client/assets/vim-Dhhcn_CA.js +1 -0
  267. package/packages/web/dist/client/assets/xml-COl5yFSC.js +1 -0
  268. package/packages/web/dist/client/assets/yaml-DjOhhVLG.js +1 -0
  269. package/packages/web/dist/client/favicon.svg +1 -0
  270. package/packages/web/dist/client/icon-192.png +0 -0
  271. package/packages/web/dist/client/icon-512.png +0 -0
  272. package/packages/web/dist/client/icon-maskable.png +0 -0
  273. package/packages/web/dist/client/index.html +24 -0
  274. package/packages/web/dist/client/manifest.webmanifest +21 -0
  275. package/packages/web/dist/client/wordmark.svg +1 -0
@@ -0,0 +1,178 @@
1
+ import { join, resolve, sep } from "node:path";
2
+ import { brotliCompressSync, constants } from "node:zlib";
3
+
4
+ import type { BunFile } from "bun";
5
+
6
+ /** The built Vite bundle, resolved relative to this file so it works installed. */
7
+ export const DEFAULT_CLIENT_DIR = resolve(
8
+ import.meta.dir,
9
+ "..",
10
+ "..",
11
+ "web",
12
+ "dist",
13
+ "client"
14
+ );
15
+
16
+ const BUILD_HINT =
17
+ "The pim web client has not been built.\n\n" +
18
+ "Run `bun run web:build` in the pim checkout, then restart `pim --mode web`.\n" +
19
+ "(Installed copies ship the bundle; a git checkout has to build it once.)\n";
20
+
21
+ /** Vite fingerprints everything under `assets/`, so it can never go stale. */
22
+ export const IMMUTABLE = "public, max-age=31536000, immutable";
23
+
24
+ const COMPRESSIBLE =
25
+ /^(?:text\/|image\/svg\+xml|application\/(?:javascript|json|wasm|xml))/;
26
+
27
+ /** Below this a deflate stream is its own header: the framing costs more than the saving. */
28
+ const MIN_COMPRESSED = 1024;
29
+
30
+ type Bytes = Uint8Array<ArrayBuffer>;
31
+
32
+ type Encoding = {
33
+ readonly name: string;
34
+ readonly compress: (bytes: Bytes) => Bytes;
35
+ };
36
+
37
+ /** Best first. Brotli at q5 beats gzip -9 on both size and time; q11 would cost 40x the CPU for 8%. */
38
+ const ENCODINGS: readonly Encoding[] = [
39
+ {
40
+ name: "br",
41
+ compress: (bytes) =>
42
+ new Uint8Array(
43
+ brotliCompressSync(bytes, {
44
+ params: { [constants.BROTLI_PARAM_QUALITY]: 5 },
45
+ })
46
+ ),
47
+ },
48
+ { name: "gzip", compress: (bytes) => Bun.gzipSync(bytes) },
49
+ ];
50
+
51
+ type Encoded = { readonly stamp: string; readonly bytes: Bytes };
52
+
53
+ function accepts(header: string, name: string): boolean {
54
+ return header.split(",").some((entry) => {
55
+ const [token, ...params] = entry.trim().split(";");
56
+ return (
57
+ token?.toLowerCase() === name &&
58
+ !params.some((param) => param.replaceAll(" ", "") === "q=0")
59
+ );
60
+ });
61
+ }
62
+
63
+ /** A file off disk under a caching policy, or the 404 that a path naming nothing is. */
64
+ export async function serveFile(
65
+ path: string,
66
+ cacheControl: string
67
+ ): Promise<Response> {
68
+ const file = Bun.file(path);
69
+ return (await file.exists())
70
+ ? new Response(file, { headers: { "cache-control": cacheControl } })
71
+ : new Response("not found", { status: 404 });
72
+ }
73
+
74
+ function looksLikeAsset(pathname: string): boolean {
75
+ const last = pathname.slice(pathname.lastIndexOf("/") + 1);
76
+ return /\.[a-zA-Z0-9]+$/.test(last);
77
+ }
78
+
79
+ /** Serves the built browser client as a static SPA; unknown routes fall back to `index.html`. */
80
+ export class StaticClient {
81
+ private readonly root: string;
82
+ private readonly encoded = new Map<string, Encoded>();
83
+
84
+ public constructor(root: string = DEFAULT_CLIENT_DIR) {
85
+ this.root = resolve(root);
86
+ }
87
+
88
+ public async handle(request: Request): Promise<Response> {
89
+ const { pathname } = new URL(request.url);
90
+ const path = this.within(pathname);
91
+ if (path !== undefined) {
92
+ const file = Bun.file(path);
93
+ if (await file.exists()) {
94
+ return await this.respond(
95
+ file,
96
+ request,
97
+ pathname.startsWith("/assets/") ? IMMUTABLE : "no-cache"
98
+ );
99
+ }
100
+ }
101
+ if (looksLikeAsset(pathname)) {
102
+ return new Response("not found", { status: 404 });
103
+ }
104
+ const index = Bun.file(join(this.root, "index.html"));
105
+ if (!(await index.exists())) {
106
+ return new Response(BUILD_HINT, {
107
+ status: 503,
108
+ headers: { "content-type": "text/plain; charset=utf-8" },
109
+ });
110
+ }
111
+ return await this.respond(
112
+ index,
113
+ request,
114
+ "no-cache",
115
+ "text/html; charset=utf-8"
116
+ );
117
+ }
118
+
119
+ // `vary` rides every answer, compressed or not: a cache must not hand one client's encoding to another.
120
+ private async respond(
121
+ file: BunFile,
122
+ request: Request,
123
+ cacheControl: string,
124
+ contentType = file.type
125
+ ): Promise<Response> {
126
+ const headers: Record<string, string> = {
127
+ "content-type": contentType,
128
+ "cache-control": cacheControl,
129
+ vary: "accept-encoding",
130
+ };
131
+ const encoding = this.negotiate(file, request);
132
+ if (encoding === undefined) {
133
+ return new Response(file, { headers });
134
+ }
135
+ return new Response(await this.compress(file, encoding), {
136
+ headers: { ...headers, "content-encoding": encoding.name },
137
+ });
138
+ }
139
+
140
+ private negotiate(file: BunFile, request: Request): Encoding | undefined {
141
+ if (!COMPRESSIBLE.test(file.type) || file.size < MIN_COMPRESSED) {
142
+ return undefined;
143
+ }
144
+ const accepted = request.headers.get("accept-encoding") ?? "";
145
+ return ENCODINGS.find((encoding) => accepts(accepted, encoding.name));
146
+ }
147
+
148
+ // Keyed by name, stamped by mtime: a rebuilt bundle replaces its entry instead of adding one.
149
+ private async compress(file: BunFile, encoding: Encoding): Promise<Bytes> {
150
+ const key = `${file.name ?? ""}\0${encoding.name}`;
151
+ const stamp = `${file.lastModified}:${file.size}`;
152
+ const cached = this.encoded.get(key);
153
+ if (cached?.stamp === stamp) {
154
+ return cached.bytes;
155
+ }
156
+ const bytes = encoding.compress(await file.bytes());
157
+ this.encoded.set(key, { stamp, bytes });
158
+ return bytes;
159
+ }
160
+
161
+ // `new URL()` normalises literal `../` but not its percent-encoded spelling: re-resolve the decoded path.
162
+ private within(pathname: string): string | undefined {
163
+ let decoded: string;
164
+ try {
165
+ decoded = decodeURIComponent(pathname);
166
+ } catch {
167
+ return undefined;
168
+ }
169
+ if (decoded.includes("\0")) {
170
+ return undefined;
171
+ }
172
+ const candidate = resolve(join(this.root, decoded));
173
+ if (candidate !== this.root && !candidate.startsWith(this.root + sep)) {
174
+ return undefined;
175
+ }
176
+ return candidate === this.root ? undefined : candidate;
177
+ }
178
+ }
@@ -0,0 +1,72 @@
1
+ import { resolve } from "node:path";
2
+
3
+ import { Cli as Argv } from "#core/shared/Cli";
4
+ import { DEFAULT_HOSTNAME, DEFAULT_PORT } from "./WsGateway";
5
+
6
+ export type WebOptions = {
7
+ readonly port: string;
8
+ readonly hostname: string;
9
+ readonly cwd: string;
10
+ readonly clientDir: string | undefined;
11
+ };
12
+
13
+ /**
14
+ * `--web-cwd` wins over `--cwd`, which the Telegram surface reads out of the
15
+ * same argv as its own default directory. Only the frozen unit uses it.
16
+ */
17
+ function parse(args: ReadonlyArray<string>): WebOptions {
18
+ // An explicit `--port` always wins over `PORT`.
19
+ let port = process.env["PORT"] ?? String(DEFAULT_PORT);
20
+ let hostname = DEFAULT_HOSTNAME;
21
+ let cwd = process.cwd();
22
+ let webCwd: string | undefined;
23
+ let clientDir: string | undefined;
24
+
25
+ Argv.scan(args, (key, take) => {
26
+ switch (key) {
27
+ case "--port":
28
+ port = take() ?? port;
29
+ break;
30
+ case "--hostname":
31
+ hostname = take() ?? hostname;
32
+ break;
33
+ case "--cwd":
34
+ cwd = take() ?? cwd;
35
+ break;
36
+ case "--web-cwd":
37
+ webCwd = take() ?? webCwd;
38
+ break;
39
+ case "--client-dir":
40
+ clientDir = take() ?? clientDir;
41
+ break;
42
+ default:
43
+ break;
44
+ }
45
+ });
46
+ return { port, hostname, cwd: webCwd ?? cwd, clientDir };
47
+ }
48
+
49
+ /** The argv a supervisor has to start the daemon with to serve these same options. */
50
+ function freeze(options: WebOptions): ReadonlyArray<string> {
51
+ return [
52
+ "--port",
53
+ options.port,
54
+ "--hostname",
55
+ options.hostname,
56
+ "--web-cwd",
57
+ resolve(options.cwd),
58
+ ...(options.clientDir === undefined
59
+ ? []
60
+ : ["--client-dir", resolve(options.clientDir)]),
61
+ ];
62
+ }
63
+
64
+ function port(options: WebOptions): number {
65
+ const value = Number(options.port);
66
+ if (!Number.isInteger(value) || value < 0 || value > 65535) {
67
+ throw new Error(`--port must be a port number, got "${options.port}"`);
68
+ }
69
+ return value;
70
+ }
71
+
72
+ export const WebOptions = { parse, freeze, port };