@av-pi-studio/server 0.0.1

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 (315) hide show
  1. package/README.md +411 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/agent/agent-manager.d.ts +107 -0
  4. package/dist/agent/agent-manager.d.ts.map +1 -0
  5. package/dist/agent/agent-manager.js +218 -0
  6. package/dist/agent/agent-manager.js.map +1 -0
  7. package/dist/agent/agent-service.d.ts +36 -0
  8. package/dist/agent/agent-service.d.ts.map +1 -0
  9. package/dist/agent/agent-service.js +195 -0
  10. package/dist/agent/agent-service.js.map +1 -0
  11. package/dist/agent/index.d.ts +17 -0
  12. package/dist/agent/index.d.ts.map +1 -0
  13. package/dist/agent/index.js +18 -0
  14. package/dist/agent/index.js.map +1 -0
  15. package/dist/agent/manifest.d.ts +7 -0
  16. package/dist/agent/manifest.d.ts.map +1 -0
  17. package/dist/agent/manifest.js +27 -0
  18. package/dist/agent/manifest.js.map +1 -0
  19. package/dist/agent/mcp-server.d.ts +100 -0
  20. package/dist/agent/mcp-server.d.ts.map +1 -0
  21. package/dist/agent/mcp-server.js +163 -0
  22. package/dist/agent/mcp-server.js.map +1 -0
  23. package/dist/agent/permissions.d.ts +71 -0
  24. package/dist/agent/permissions.d.ts.map +1 -0
  25. package/dist/agent/permissions.js +132 -0
  26. package/dist/agent/permissions.js.map +1 -0
  27. package/dist/agent/provider-contract.d.ts +134 -0
  28. package/dist/agent/provider-contract.d.ts.map +1 -0
  29. package/dist/agent/provider-contract.js +2 -0
  30. package/dist/agent/provider-contract.js.map +1 -0
  31. package/dist/agent/provider-registry.d.ts +37 -0
  32. package/dist/agent/provider-registry.d.ts.map +1 -0
  33. package/dist/agent/provider-registry.js +92 -0
  34. package/dist/agent/provider-registry.js.map +1 -0
  35. package/dist/agent/provider-snapshot.d.ts +52 -0
  36. package/dist/agent/provider-snapshot.d.ts.map +1 -0
  37. package/dist/agent/provider-snapshot.js +73 -0
  38. package/dist/agent/provider-snapshot.js.map +1 -0
  39. package/dist/agent/providers/mock/mock-provider.d.ts +44 -0
  40. package/dist/agent/providers/mock/mock-provider.d.ts.map +1 -0
  41. package/dist/agent/providers/mock/mock-provider.js +162 -0
  42. package/dist/agent/providers/mock/mock-provider.js.map +1 -0
  43. package/dist/agent/providers/pi/agent.d.ts +80 -0
  44. package/dist/agent/providers/pi/agent.d.ts.map +1 -0
  45. package/dist/agent/providers/pi/agent.js +322 -0
  46. package/dist/agent/providers/pi/agent.js.map +1 -0
  47. package/dist/agent/providers/pi/event-mapper.d.ts +17 -0
  48. package/dist/agent/providers/pi/event-mapper.d.ts.map +1 -0
  49. package/dist/agent/providers/pi/event-mapper.js +150 -0
  50. package/dist/agent/providers/pi/event-mapper.js.map +1 -0
  51. package/dist/agent/providers/pi/rpc-transport.d.ts +53 -0
  52. package/dist/agent/providers/pi/rpc-transport.d.ts.map +1 -0
  53. package/dist/agent/providers/pi/rpc-transport.js +190 -0
  54. package/dist/agent/providers/pi/rpc-transport.js.map +1 -0
  55. package/dist/agent/providers/pi/session-hydration.d.ts +11 -0
  56. package/dist/agent/providers/pi/session-hydration.d.ts.map +1 -0
  57. package/dist/agent/providers/pi/session-hydration.js +167 -0
  58. package/dist/agent/providers/pi/session-hydration.js.map +1 -0
  59. package/dist/agent/rewind-rpc.d.ts +15 -0
  60. package/dist/agent/rewind-rpc.d.ts.map +1 -0
  61. package/dist/agent/rewind-rpc.js +30 -0
  62. package/dist/agent/rewind-rpc.js.map +1 -0
  63. package/dist/agent/session-operations.d.ts +32 -0
  64. package/dist/agent/session-operations.d.ts.map +1 -0
  65. package/dist/agent/session-operations.js +130 -0
  66. package/dist/agent/session-operations.js.map +1 -0
  67. package/dist/agent/structured-generation.d.ts +39 -0
  68. package/dist/agent/structured-generation.d.ts.map +1 -0
  69. package/dist/agent/structured-generation.js +78 -0
  70. package/dist/agent/structured-generation.js.map +1 -0
  71. package/dist/agent/timeline-rpc.d.ts +21 -0
  72. package/dist/agent/timeline-rpc.d.ts.map +1 -0
  73. package/dist/agent/timeline-rpc.js +59 -0
  74. package/dist/agent/timeline-rpc.js.map +1 -0
  75. package/dist/agent/timeline-store.d.ts +100 -0
  76. package/dist/agent/timeline-store.d.ts.map +1 -0
  77. package/dist/agent/timeline-store.js +255 -0
  78. package/dist/agent/timeline-store.js.map +1 -0
  79. package/dist/auth/index.d.ts +2 -0
  80. package/dist/auth/index.d.ts.map +1 -0
  81. package/dist/auth/index.js +3 -0
  82. package/dist/auth/index.js.map +1 -0
  83. package/dist/auth/password-auth.d.ts +44 -0
  84. package/dist/auth/password-auth.d.ts.map +1 -0
  85. package/dist/auth/password-auth.js +78 -0
  86. package/dist/auth/password-auth.js.map +1 -0
  87. package/dist/config/daemon-config.d.ts +634 -0
  88. package/dist/config/daemon-config.d.ts.map +1 -0
  89. package/dist/config/daemon-config.js +258 -0
  90. package/dist/config/daemon-config.js.map +1 -0
  91. package/dist/config/index.d.ts +3 -0
  92. package/dist/config/index.d.ts.map +1 -0
  93. package/dist/config/index.js +4 -0
  94. package/dist/config/index.js.map +1 -0
  95. package/dist/config/project-config.d.ts +104 -0
  96. package/dist/config/project-config.d.ts.map +1 -0
  97. package/dist/config/project-config.js +106 -0
  98. package/dist/config/project-config.js.map +1 -0
  99. package/dist/daemon/bootstrap.d.ts +37 -0
  100. package/dist/daemon/bootstrap.d.ts.map +1 -0
  101. package/dist/daemon/bootstrap.js +448 -0
  102. package/dist/daemon/bootstrap.js.map +1 -0
  103. package/dist/daemon/dev-bootstrap.d.ts +14 -0
  104. package/dist/daemon/dev-bootstrap.d.ts.map +1 -0
  105. package/dist/daemon/dev-bootstrap.js +231 -0
  106. package/dist/daemon/dev-bootstrap.js.map +1 -0
  107. package/dist/daemon/dev-main.d.ts +3 -0
  108. package/dist/daemon/dev-main.d.ts.map +1 -0
  109. package/dist/daemon/dev-main.js +36 -0
  110. package/dist/daemon/dev-main.js.map +1 -0
  111. package/dist/daemon/index.d.ts +15 -0
  112. package/dist/daemon/index.d.ts.map +1 -0
  113. package/dist/daemon/index.js +21 -0
  114. package/dist/daemon/index.js.map +1 -0
  115. package/dist/daemon/main.d.ts +3 -0
  116. package/dist/daemon/main.d.ts.map +1 -0
  117. package/dist/daemon/main.js +29 -0
  118. package/dist/daemon/main.js.map +1 -0
  119. package/dist/daemon/orchestration-rpc.d.ts +19 -0
  120. package/dist/daemon/orchestration-rpc.d.ts.map +1 -0
  121. package/dist/daemon/orchestration-rpc.js +136 -0
  122. package/dist/daemon/orchestration-rpc.js.map +1 -0
  123. package/dist/files/download-token-store.d.ts +14 -0
  124. package/dist/files/download-token-store.d.ts.map +1 -0
  125. package/dist/files/download-token-store.js +33 -0
  126. package/dist/files/download-token-store.js.map +1 -0
  127. package/dist/files/file-explorer.d.ts +54 -0
  128. package/dist/files/file-explorer.d.ts.map +1 -0
  129. package/dist/files/file-explorer.js +182 -0
  130. package/dist/files/file-explorer.js.map +1 -0
  131. package/dist/files/file-transfer.d.ts +30 -0
  132. package/dist/files/file-transfer.d.ts.map +1 -0
  133. package/dist/files/file-transfer.js +132 -0
  134. package/dist/files/file-transfer.js.map +1 -0
  135. package/dist/files/index.d.ts +4 -0
  136. package/dist/files/index.d.ts.map +1 -0
  137. package/dist/files/index.js +5 -0
  138. package/dist/files/index.js.map +1 -0
  139. package/dist/http/host-allowlist.d.ts +15 -0
  140. package/dist/http/host-allowlist.d.ts.map +1 -0
  141. package/dist/http/host-allowlist.js +52 -0
  142. package/dist/http/host-allowlist.js.map +1 -0
  143. package/dist/http/http-server.d.ts +27 -0
  144. package/dist/http/http-server.d.ts.map +1 -0
  145. package/dist/http/http-server.js +76 -0
  146. package/dist/http/http-server.js.map +1 -0
  147. package/dist/http/index.d.ts +3 -0
  148. package/dist/http/index.d.ts.map +1 -0
  149. package/dist/http/index.js +4 -0
  150. package/dist/http/index.js.map +1 -0
  151. package/dist/index.d.ts +18 -0
  152. package/dist/index.d.ts.map +1 -0
  153. package/dist/index.js +35 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/logging/index.d.ts +2 -0
  156. package/dist/logging/index.d.ts.map +1 -0
  157. package/dist/logging/index.js +2 -0
  158. package/dist/logging/index.js.map +1 -0
  159. package/dist/logging/logger.d.ts +48 -0
  160. package/dist/logging/logger.d.ts.map +1 -0
  161. package/dist/logging/logger.js +53 -0
  162. package/dist/logging/logger.js.map +1 -0
  163. package/dist/orchestration/chat-service.d.ts +61 -0
  164. package/dist/orchestration/chat-service.d.ts.map +1 -0
  165. package/dist/orchestration/chat-service.js +156 -0
  166. package/dist/orchestration/chat-service.js.map +1 -0
  167. package/dist/orchestration/cron.d.ts +30 -0
  168. package/dist/orchestration/cron.d.ts.map +1 -0
  169. package/dist/orchestration/cron.js +136 -0
  170. package/dist/orchestration/cron.js.map +1 -0
  171. package/dist/orchestration/index.d.ts +5 -0
  172. package/dist/orchestration/index.d.ts.map +1 -0
  173. package/dist/orchestration/index.js +6 -0
  174. package/dist/orchestration/index.js.map +1 -0
  175. package/dist/orchestration/loop-service.d.ts +129 -0
  176. package/dist/orchestration/loop-service.d.ts.map +1 -0
  177. package/dist/orchestration/loop-service.js +234 -0
  178. package/dist/orchestration/loop-service.js.map +1 -0
  179. package/dist/orchestration/schedule-service.d.ts +65 -0
  180. package/dist/orchestration/schedule-service.d.ts.map +1 -0
  181. package/dist/orchestration/schedule-service.js +201 -0
  182. package/dist/orchestration/schedule-service.js.map +1 -0
  183. package/dist/persistence/atomic-store.d.ts +45 -0
  184. package/dist/persistence/atomic-store.d.ts.map +1 -0
  185. package/dist/persistence/atomic-store.js +92 -0
  186. package/dist/persistence/atomic-store.js.map +1 -0
  187. package/dist/persistence/entity-schemas.d.ts +1707 -0
  188. package/dist/persistence/entity-schemas.d.ts.map +1 -0
  189. package/dist/persistence/entity-schemas.js +250 -0
  190. package/dist/persistence/entity-schemas.js.map +1 -0
  191. package/dist/persistence/entity-stores.d.ts +46 -0
  192. package/dist/persistence/entity-stores.d.ts.map +1 -0
  193. package/dist/persistence/entity-stores.js +167 -0
  194. package/dist/persistence/entity-stores.js.map +1 -0
  195. package/dist/persistence/index.d.ts +4 -0
  196. package/dist/persistence/index.d.ts.map +1 -0
  197. package/dist/persistence/index.js +5 -0
  198. package/dist/persistence/index.js.map +1 -0
  199. package/dist/projects/checkout-diff-manager.d.ts +44 -0
  200. package/dist/projects/checkout-diff-manager.d.ts.map +1 -0
  201. package/dist/projects/checkout-diff-manager.js +82 -0
  202. package/dist/projects/checkout-diff-manager.js.map +1 -0
  203. package/dist/projects/git-checkout-rpc.d.ts +18 -0
  204. package/dist/projects/git-checkout-rpc.d.ts.map +1 -0
  205. package/dist/projects/git-checkout-rpc.js +57 -0
  206. package/dist/projects/git-checkout-rpc.js.map +1 -0
  207. package/dist/projects/git-detect.d.ts +21 -0
  208. package/dist/projects/git-detect.d.ts.map +1 -0
  209. package/dist/projects/git-detect.js +34 -0
  210. package/dist/projects/git-detect.js.map +1 -0
  211. package/dist/projects/git-operations.d.ts +67 -0
  212. package/dist/projects/git-operations.d.ts.map +1 -0
  213. package/dist/projects/git-operations.js +200 -0
  214. package/dist/projects/git-operations.js.map +1 -0
  215. package/dist/projects/github-service.d.ts +80 -0
  216. package/dist/projects/github-service.d.ts.map +1 -0
  217. package/dist/projects/github-service.js +172 -0
  218. package/dist/projects/github-service.js.map +1 -0
  219. package/dist/projects/index.d.ts +13 -0
  220. package/dist/projects/index.d.ts.map +1 -0
  221. package/dist/projects/index.js +19 -0
  222. package/dist/projects/index.js.map +1 -0
  223. package/dist/projects/open-project.d.ts +35 -0
  224. package/dist/projects/open-project.d.ts.map +1 -0
  225. package/dist/projects/open-project.js +106 -0
  226. package/dist/projects/open-project.js.map +1 -0
  227. package/dist/projects/reconciliation.d.ts +15 -0
  228. package/dist/projects/reconciliation.d.ts.map +1 -0
  229. package/dist/projects/reconciliation.js +63 -0
  230. package/dist/projects/reconciliation.js.map +1 -0
  231. package/dist/projects/status-projection.d.ts +33 -0
  232. package/dist/projects/status-projection.d.ts.map +1 -0
  233. package/dist/projects/status-projection.js +98 -0
  234. package/dist/projects/status-projection.js.map +1 -0
  235. package/dist/projects/workspace-activity.d.ts +28 -0
  236. package/dist/projects/workspace-activity.d.ts.map +1 -0
  237. package/dist/projects/workspace-activity.js +74 -0
  238. package/dist/projects/workspace-activity.js.map +1 -0
  239. package/dist/projects/workspace-git-service.d.ts +27 -0
  240. package/dist/projects/workspace-git-service.d.ts.map +1 -0
  241. package/dist/projects/workspace-git-service.js +60 -0
  242. package/dist/projects/workspace-git-service.js.map +1 -0
  243. package/dist/projects/workspace-registry.d.ts +59 -0
  244. package/dist/projects/workspace-registry.d.ts.map +1 -0
  245. package/dist/projects/workspace-registry.js +149 -0
  246. package/dist/projects/workspace-registry.js.map +1 -0
  247. package/dist/projects/worktree-service.d.ts +100 -0
  248. package/dist/projects/worktree-service.d.ts.map +1 -0
  249. package/dist/projects/worktree-service.js +216 -0
  250. package/dist/projects/worktree-service.js.map +1 -0
  251. package/dist/proxy/index.d.ts +4 -0
  252. package/dist/proxy/index.d.ts.map +1 -0
  253. package/dist/proxy/index.js +5 -0
  254. package/dist/proxy/index.js.map +1 -0
  255. package/dist/proxy/service-hostname.d.ts +21 -0
  256. package/dist/proxy/service-hostname.d.ts.map +1 -0
  257. package/dist/proxy/service-hostname.js +44 -0
  258. package/dist/proxy/service-hostname.js.map +1 -0
  259. package/dist/proxy/service-port-registry.d.ts +36 -0
  260. package/dist/proxy/service-port-registry.d.ts.map +1 -0
  261. package/dist/proxy/service-port-registry.js +55 -0
  262. package/dist/proxy/service-port-registry.js.map +1 -0
  263. package/dist/proxy/service-proxy.d.ts +34 -0
  264. package/dist/proxy/service-proxy.d.ts.map +1 -0
  265. package/dist/proxy/service-proxy.js +54 -0
  266. package/dist/proxy/service-proxy.js.map +1 -0
  267. package/dist/terminal/index.d.ts +4 -0
  268. package/dist/terminal/index.d.ts.map +1 -0
  269. package/dist/terminal/index.js +5 -0
  270. package/dist/terminal/index.js.map +1 -0
  271. package/dist/terminal/pty-backend.d.ts +44 -0
  272. package/dist/terminal/pty-backend.d.ts.map +1 -0
  273. package/dist/terminal/pty-backend.js +119 -0
  274. package/dist/terminal/pty-backend.js.map +1 -0
  275. package/dist/terminal/screen-buffer.d.ts +21 -0
  276. package/dist/terminal/screen-buffer.d.ts.map +1 -0
  277. package/dist/terminal/screen-buffer.js +55 -0
  278. package/dist/terminal/screen-buffer.js.map +1 -0
  279. package/dist/terminal/terminal-manager.d.ts +79 -0
  280. package/dist/terminal/terminal-manager.d.ts.map +1 -0
  281. package/dist/terminal/terminal-manager.js +174 -0
  282. package/dist/terminal/terminal-manager.js.map +1 -0
  283. package/dist/terminal/terminal-rpc.d.ts +20 -0
  284. package/dist/terminal/terminal-rpc.d.ts.map +1 -0
  285. package/dist/terminal/terminal-rpc.js +122 -0
  286. package/dist/terminal/terminal-rpc.js.map +1 -0
  287. package/dist/util/concurrency.d.ts +16 -0
  288. package/dist/util/concurrency.d.ts.map +1 -0
  289. package/dist/util/concurrency.js +23 -0
  290. package/dist/util/concurrency.js.map +1 -0
  291. package/dist/util/index.d.ts +2 -0
  292. package/dist/util/index.d.ts.map +1 -0
  293. package/dist/util/index.js +2 -0
  294. package/dist/util/index.js.map +1 -0
  295. package/dist/ws/capability-store.d.ts +11 -0
  296. package/dist/ws/capability-store.d.ts.map +1 -0
  297. package/dist/ws/capability-store.js +10 -0
  298. package/dist/ws/capability-store.js.map +1 -0
  299. package/dist/ws/index.d.ts +5 -0
  300. package/dist/ws/index.d.ts.map +1 -0
  301. package/dist/ws/index.js +6 -0
  302. package/dist/ws/index.js.map +1 -0
  303. package/dist/ws/router.d.ts +34 -0
  304. package/dist/ws/router.d.ts.map +1 -0
  305. package/dist/ws/router.js +97 -0
  306. package/dist/ws/router.js.map +1 -0
  307. package/dist/ws/session.d.ts +28 -0
  308. package/dist/ws/session.d.ts.map +1 -0
  309. package/dist/ws/session.js +36 -0
  310. package/dist/ws/session.js.map +1 -0
  311. package/dist/ws/ws-server.d.ts +41 -0
  312. package/dist/ws/ws-server.d.ts.map +1 -0
  313. package/dist/ws/ws-server.js +104 -0
  314. package/dist/ws/ws-server.js.map +1 -0
  315. package/package.json +48 -0
@@ -0,0 +1,54 @@
1
+ import type { HandlerRegistry } from "../ws/router.js";
2
+ export interface DirEntry {
3
+ name: string;
4
+ kind: "file" | "directory" | "symlink" | "other";
5
+ size: number;
6
+ mtimeMs: number;
7
+ /** Lightweight icon hint (extension or "dir"); full theme mapping is a client concern. */
8
+ iconHint: string;
9
+ }
10
+ export type FileExplorerResult = {
11
+ ok: true;
12
+ kind: "directory";
13
+ path: string;
14
+ resolvedPath: string;
15
+ entries: DirEntry[];
16
+ } | {
17
+ ok: true;
18
+ kind: "text";
19
+ path: string;
20
+ resolvedPath: string;
21
+ content: string;
22
+ truncated: boolean;
23
+ } | {
24
+ ok: true;
25
+ kind: "binary";
26
+ path: string;
27
+ resolvedPath: string;
28
+ metadata: {
29
+ size: number;
30
+ mtimeMs: number;
31
+ mimeHint: string;
32
+ };
33
+ transferToken: string;
34
+ } | {
35
+ ok: false;
36
+ error: string;
37
+ };
38
+ export interface FileExplorerDeps {
39
+ /** Issue a download token for a binary file (task-005 supplies the real store). */
40
+ issueDownloadToken?: (resolvedPath: string) => string;
41
+ previewBytes?: number;
42
+ }
43
+ export declare class FileExplorerService {
44
+ private readonly deps;
45
+ constructor(deps?: FileExplorerDeps);
46
+ registerHandlers(registry: HandlerRegistry): void;
47
+ /** Normalize + resolve a path (symlinks), then list a directory or preview a file. */
48
+ listOrPreview(inputPath: string): Promise<FileExplorerResult>;
49
+ private readDirectory;
50
+ private previewFile;
51
+ /** Immediate subdirectory paths under `base` (path picker). */
52
+ directorySuggestions(base: string): Promise<string[]>;
53
+ }
54
+ //# sourceMappingURL=file-explorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-explorer.d.ts","sourceRoot":"","sources":["../../src/files/file-explorer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAavD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,EAAE,CAAA;CAAE,GACxF;IACE,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB,GACD;IACE,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,aAAa,EAAE,MAAM,CAAC;CACvB,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,GAAE,gBAAqB;IAExD,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAqBjD,sFAAsF;IAChF,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAgCrD,aAAa;YAmCb,WAAW;IA8CzB,+DAA+D;IACzD,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAa5D"}
@@ -0,0 +1,182 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { open, readdir, realpath, stat } from "node:fs/promises";
3
+ import { basename, extname, join, resolve } from "node:path";
4
+ /**
5
+ * File explorer: directory listing + file preview with server-side path normalization and symlink
6
+ * resolution (features/file-explorer-transfer.md § Behavior (listOrPreview), § Trust boundary;
7
+ * architecture/auth-security.md § Trust model). Connected clients are trusted operators — a preview
8
+ * may read any regular file the daemon can read; workspace-relative paths are a UI convenience, not a
9
+ * security boundary. Normalization/symlink checks stay server-side.
10
+ */
11
+ const DEFAULT_PREVIEW_BYTES = 256 * 1024;
12
+ const BINARY_SNIFF_BYTES = 8192;
13
+ export class FileExplorerService {
14
+ deps;
15
+ constructor(deps = {}) {
16
+ this.deps = deps;
17
+ }
18
+ registerHandlers(registry) {
19
+ registry.register("file_explorer_request", async (ctx) => ({
20
+ type: "file_explorer_response",
21
+ result: await this.listOrPreview(String(ctx.message.path ?? "")),
22
+ }));
23
+ registry.register("directory_suggestions_request", async (ctx) => ({
24
+ type: "directory_suggestions_response",
25
+ base: String(ctx.message.path ?? ctx.message.base ?? ""),
26
+ suggestions: await this.directorySuggestions(String(ctx.message.path ?? ctx.message.base ?? "")),
27
+ }));
28
+ registry.register("project_icon_request", (ctx) => ({
29
+ type: "project_icon_response",
30
+ projectId: String(ctx.message.projectId ?? ""),
31
+ icon: null, // resolution mechanism TODO(verify)
32
+ }));
33
+ }
34
+ /** Normalize + resolve a path (symlinks), then list a directory or preview a file. */
35
+ async listOrPreview(inputPath) {
36
+ if (!inputPath)
37
+ return { ok: false, error: "empty_path" };
38
+ const normalized = resolve(inputPath);
39
+ let resolvedPath;
40
+ try {
41
+ resolvedPath = await realpath(normalized); // server-side symlink resolution
42
+ }
43
+ catch {
44
+ return { ok: false, error: "not_found" };
45
+ }
46
+ let info;
47
+ try {
48
+ info = await stat(resolvedPath);
49
+ }
50
+ catch {
51
+ return { ok: false, error: "unreadable" };
52
+ }
53
+ if (info.isDirectory()) {
54
+ try {
55
+ const entries = await this.readDirectory(resolvedPath);
56
+ return { ok: true, kind: "directory", path: inputPath, resolvedPath, entries };
57
+ }
58
+ catch {
59
+ return { ok: false, error: "unreadable" };
60
+ }
61
+ }
62
+ if (!info.isFile())
63
+ return { ok: false, error: "unsupported" };
64
+ return this.previewFile(inputPath, resolvedPath, info.size, info.mtimeMs);
65
+ }
66
+ async readDirectory(dir) {
67
+ const dirents = await readdir(dir, { withFileTypes: true });
68
+ const entries = [];
69
+ for (const dirent of dirents) {
70
+ const full = join(dir, dirent.name);
71
+ let size = 0;
72
+ let mtimeMs = 0;
73
+ try {
74
+ const s = await stat(full);
75
+ size = s.size;
76
+ mtimeMs = s.mtimeMs;
77
+ }
78
+ catch {
79
+ // Unstattable entry (e.g. broken symlink) → keep with zeroed metadata.
80
+ }
81
+ const kind = dirent.isDirectory()
82
+ ? "directory"
83
+ : dirent.isSymbolicLink()
84
+ ? "symlink"
85
+ : dirent.isFile()
86
+ ? "file"
87
+ : "other";
88
+ entries.push({
89
+ name: dirent.name,
90
+ kind,
91
+ size,
92
+ mtimeMs,
93
+ iconHint: kind === "directory" ? "dir" : iconHintForFile(dirent.name),
94
+ });
95
+ }
96
+ return entries.toSorted((a, b) => {
97
+ if (a.kind !== b.kind)
98
+ return a.kind === "directory" ? -1 : b.kind === "directory" ? 1 : 0;
99
+ return a.name.localeCompare(b.name);
100
+ });
101
+ }
102
+ async previewFile(inputPath, resolvedPath, size, mtimeMs) {
103
+ const previewBytes = this.deps.previewBytes ?? DEFAULT_PREVIEW_BYTES;
104
+ let handle;
105
+ try {
106
+ handle = await open(resolvedPath, "r");
107
+ }
108
+ catch {
109
+ return { ok: false, error: "unreadable" };
110
+ }
111
+ try {
112
+ const sniffLen = Math.min(size, BINARY_SNIFF_BYTES);
113
+ const sniff = Buffer.alloc(sniffLen);
114
+ if (sniffLen > 0)
115
+ await handle.read(sniff, 0, sniffLen, 0);
116
+ if (isBinary(sniff)) {
117
+ const transferToken = this.deps.issueDownloadToken?.(resolvedPath) ?? randomUUID();
118
+ return {
119
+ ok: true,
120
+ kind: "binary",
121
+ path: inputPath,
122
+ resolvedPath,
123
+ metadata: { size, mtimeMs, mimeHint: mimeHintForFile(resolvedPath) },
124
+ transferToken,
125
+ };
126
+ }
127
+ const readLen = Math.min(size, previewBytes);
128
+ const buffer = Buffer.alloc(readLen);
129
+ if (readLen > 0)
130
+ await handle.read(buffer, 0, readLen, 0);
131
+ return {
132
+ ok: true,
133
+ kind: "text",
134
+ path: inputPath,
135
+ resolvedPath,
136
+ content: buffer.toString("utf8"),
137
+ truncated: size > previewBytes,
138
+ };
139
+ }
140
+ finally {
141
+ await handle.close();
142
+ }
143
+ }
144
+ /** Immediate subdirectory paths under `base` (path picker). */
145
+ async directorySuggestions(base) {
146
+ if (!base)
147
+ return [];
148
+ try {
149
+ const resolved = await realpath(resolve(base));
150
+ const dirents = await readdir(resolved, { withFileTypes: true });
151
+ return dirents
152
+ .filter((d) => d.isDirectory() && !d.name.startsWith("."))
153
+ .map((d) => join(resolved, d.name))
154
+ .toSorted((a, b) => basename(a).localeCompare(basename(b)));
155
+ }
156
+ catch {
157
+ return [];
158
+ }
159
+ }
160
+ }
161
+ /** A buffer is treated as binary if it contains a NUL byte in the sniffed prefix. */
162
+ function isBinary(sniff) {
163
+ return sniff.includes(0);
164
+ }
165
+ function iconHintForFile(name) {
166
+ const ext = extname(name).slice(1).toLowerCase();
167
+ return ext || "file";
168
+ }
169
+ function mimeHintForFile(path) {
170
+ const ext = extname(path).slice(1).toLowerCase();
171
+ const map = {
172
+ png: "image/png",
173
+ jpg: "image/jpeg",
174
+ jpeg: "image/jpeg",
175
+ gif: "image/gif",
176
+ pdf: "application/pdf",
177
+ zip: "application/zip",
178
+ wasm: "application/wasm",
179
+ };
180
+ return map[ext] ?? "application/octet-stream";
181
+ }
182
+ //# sourceMappingURL=file-explorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-explorer.js","sourceRoot":"","sources":["../../src/files/file-explorer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { BinaryHandler, HandlerRegistry } from "../ws/router.js";
2
+ import { DownloadTokenStore } from "./download-token-store.js";
3
+ export interface FileTransferDeps {
4
+ tokenStore?: DownloadTokenStore;
5
+ chunkBytes?: number;
6
+ }
7
+ export declare class FileTransferService {
8
+ readonly tokenStore: DownloadTokenStore;
9
+ private readonly chunkBytes;
10
+ /** Pending uploads keyed by stream number. */
11
+ private readonly uploads;
12
+ private nextStream;
13
+ constructor(deps?: FileTransferDeps);
14
+ /** Issue a download token for a resolved path (also used by the file explorer for binary files). */
15
+ issueDownloadToken: (resolvedPath: string) => string;
16
+ registerHandlers(registry: HandlerRegistry): void;
17
+ /**
18
+ * Validate + consume the token, then stream the file as `Begin → Chunk* → End` binary frames.
19
+ */
20
+ startDownload(token: string, stream: number, sink: (frame: Uint8Array) => void): Promise<{
21
+ ok: boolean;
22
+ error?: string;
23
+ transferId?: string;
24
+ }>;
25
+ /** Binary frame handler for upload streams (Begin → Chunk* → End). Frames are serialized per
26
+ * stream so an in-flight `Begin` (open) completes before any `Chunk` writes. */
27
+ binaryHandler(): BinaryHandler;
28
+ private handleUploadFrame;
29
+ }
30
+ //# sourceMappingURL=file-transfer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-transfer.d.ts","sourceRoot":"","sources":["../../src/files/file-transfer.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAU/D,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAWD,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,8CAA8C;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,UAAU,CAAK;gBAEX,IAAI,GAAE,gBAAqB;IAKvC,oGAAoG;IACpG,kBAAkB,GAAI,cAAc,MAAM,KAAG,MAAM,CAA8C;IAEjG,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAwCjD;;OAEG;IACG,aAAa,CACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAChC,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IA6BhE;oFACgF;IAChF,aAAa,IAAI,aAAa;YAUhB,iBAAiB;CA6BhC"}
@@ -0,0 +1,132 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { mkdir, open, realpath } from "node:fs/promises";
3
+ import { basename, dirname } from "node:path";
4
+ import { encodeFileTransferFrame, tryDecodeFileTransferFrame, } from "@av-pi-studio/protocol";
5
+ import { DownloadTokenStore } from "./download-token-store.js";
6
+ /**
7
+ * Chunked file download (token-authorized) + upload over the file-transfer binary frame format
8
+ * (features/file-explorer-transfer.md § Binary transfer frames, § Behavior). Downloads stream
9
+ * `Begin → Chunk* → End`; uploads consume the same frames and write to the target path.
10
+ */
11
+ const DEFAULT_CHUNK_BYTES = 32 * 1024;
12
+ export class FileTransferService {
13
+ tokenStore;
14
+ chunkBytes;
15
+ /** Pending uploads keyed by stream number. */
16
+ uploads = new Map();
17
+ nextStream = 1;
18
+ constructor(deps = {}) {
19
+ this.tokenStore = deps.tokenStore ?? new DownloadTokenStore();
20
+ this.chunkBytes = deps.chunkBytes ?? DEFAULT_CHUNK_BYTES;
21
+ }
22
+ /** Issue a download token for a resolved path (also used by the file explorer for binary files). */
23
+ issueDownloadToken = (resolvedPath) => this.tokenStore.issue(resolvedPath).token;
24
+ registerHandlers(registry) {
25
+ registry.register("file_download_token_request", async (ctx) => {
26
+ const path = String(ctx.message.path ?? "");
27
+ let resolved;
28
+ try {
29
+ resolved = await realpath(path);
30
+ }
31
+ catch {
32
+ return { type: "file_download_token_response", ok: false, error: "not_found" };
33
+ }
34
+ const { token, expiresAt } = this.tokenStore.issue(resolved);
35
+ return { type: "file_download_token_response", ok: true, token, expiresAt };
36
+ });
37
+ registry.register("file_download_request", async (ctx) => {
38
+ const token = String(ctx.message.token ?? "");
39
+ const stream = Number(ctx.message.stream ?? this.nextStream++);
40
+ const result = await this.startDownload(token, stream, (frame) => ctx.session.sendBinary(frame));
41
+ return { type: "file_download_response", stream, ...result };
42
+ });
43
+ registry.register("file_upload_request", (ctx) => {
44
+ const targetPath = String(ctx.message.path ?? "");
45
+ const transferId = String(ctx.message.transferId ?? "");
46
+ if (!targetPath || !transferId) {
47
+ return { type: "file_upload_response", ok: false, error: "missing_fields" };
48
+ }
49
+ const stream = this.nextStream++;
50
+ this.uploads.set(stream, {
51
+ transferId,
52
+ targetPath,
53
+ handle: null,
54
+ bytesWritten: 0,
55
+ chain: Promise.resolve(),
56
+ });
57
+ return { type: "file_upload_response", ok: true, stream };
58
+ });
59
+ }
60
+ /**
61
+ * Validate + consume the token, then stream the file as `Begin → Chunk* → End` binary frames.
62
+ */
63
+ async startDownload(token, stream, sink) {
64
+ const path = this.tokenStore.consume(token);
65
+ if (!path)
66
+ return { ok: false, error: "invalid_or_expired_token" };
67
+ const transferId = `dl-${stream}-${Date.now()}`;
68
+ sink(encodeFileTransferFrame({
69
+ opcode: "Begin",
70
+ stream,
71
+ meta: { transferId, fileName: basename(path) },
72
+ }));
73
+ await new Promise((resolve, reject) => {
74
+ const rs = createReadStream(path, { highWaterMark: this.chunkBytes });
75
+ rs.on("data", (chunk) => {
76
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
77
+ sink(encodeFileTransferFrame({ opcode: "Chunk", stream, data: new Uint8Array(bytes) }));
78
+ });
79
+ rs.on("end", () => resolve());
80
+ rs.on("error", reject);
81
+ }).catch(() => {
82
+ sink(encodeFileTransferFrame({ opcode: "Error", stream, message: "read_failed" }));
83
+ });
84
+ sink(encodeFileTransferFrame({ opcode: "End", stream, ok: true }));
85
+ return { ok: true, transferId };
86
+ }
87
+ /** Binary frame handler for upload streams (Begin → Chunk* → End). Frames are serialized per
88
+ * stream so an in-flight `Begin` (open) completes before any `Chunk` writes. */
89
+ binaryHandler() {
90
+ return (_session, bytes) => {
91
+ const frame = tryDecodeFileTransferFrame(bytes);
92
+ if (!frame)
93
+ return;
94
+ const upload = this.uploads.get(frame.stream);
95
+ if (!upload)
96
+ return; // unknown/closed stream
97
+ upload.chain = upload.chain.then(() => this.handleUploadFrame(frame));
98
+ };
99
+ }
100
+ async handleUploadFrame(frame) {
101
+ const upload = this.uploads.get(frame.stream);
102
+ if (!upload)
103
+ return; // unknown/closed stream
104
+ switch (frame.opcode) {
105
+ case "Begin": {
106
+ if (frame.meta.transferId !== upload.transferId)
107
+ return; // mismatched transfer
108
+ await mkdir(dirname(upload.targetPath), { recursive: true });
109
+ upload.handle = await open(upload.targetPath, "w");
110
+ return;
111
+ }
112
+ case "Chunk": {
113
+ if (!upload.handle)
114
+ return;
115
+ await upload.handle.write(Buffer.from(frame.data));
116
+ upload.bytesWritten += frame.data.length;
117
+ return;
118
+ }
119
+ case "End": {
120
+ await upload.handle?.close();
121
+ this.uploads.delete(frame.stream);
122
+ return;
123
+ }
124
+ case "Error": {
125
+ await upload.handle?.close();
126
+ this.uploads.delete(frame.stream);
127
+ return;
128
+ }
129
+ }
130
+ }
131
+ }
132
+ //# sourceMappingURL=file-transfer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-transfer.js","sourceRoot":"","sources":["../../src/files/file-transfer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EACL,uBAAuB,EAEvB,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAIhC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D;;;;GAIG;AAEH,MAAM,mBAAmB,GAAG,EAAE,GAAG,IAAI,CAAC;AAgBtC,MAAM,OAAO,mBAAmB;IACrB,UAAU,CAAqB;IACvB,UAAU,CAAS;IACpC,8CAA8C;IAC7B,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IACpD,UAAU,GAAG,CAAC,CAAC;IAEvB,YAAY,OAAyB,EAAE;QACrC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,kBAAkB,EAAE,CAAC;QAC9D,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAC3D,CAAC;IAED,oGAAoG;IACpG,kBAAkB,GAAG,CAAC,YAAoB,EAAU,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC;IAEjG,gBAAgB,CAAC,QAAyB;QACxC,QAAQ,CAAC,QAAQ,CAAC,6BAA6B,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YAC5C,IAAI,QAAgB,CAAC;YACrB,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,IAAI,EAAE,8BAA8B,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;YACjF,CAAC;YACD,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7D,OAAO,EAAE,IAAI,EAAE,8BAA8B,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAC9E,CAAC,CAAC,CAAC;QAEH,QAAQ,CAAC,QAAQ,CAAC,uBAAuB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACvD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;YAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YAC/D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAC/D,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAC9B,CAAC;YACF,OAAO,EAAE,IAAI,EAAE,wBAAwB,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,QAAQ,CAAC,QAAQ,CAAC,qBAAqB,EAAE,CAAC,GAAG,EAAE,EAAE;YAC/C,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YAClD,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;YACxD,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,EAAE,CAAC;gBAC/B,OAAO,EAAE,IAAI,EAAE,sBAAsB,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;YAC9E,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YACjC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE;gBACvB,UAAU;gBACV,UAAU;gBACV,MAAM,EAAE,IAAI;gBACZ,YAAY,EAAE,CAAC;gBACf,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE;aACzB,CAAC,CAAC;YACH,OAAO,EAAE,IAAI,EAAE,sBAAsB,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC5D,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CACjB,KAAa,EACb,MAAc,EACd,IAAiC;QAEjC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC;QAEnE,MAAM,UAAU,GAAG,MAAM,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAChD,IAAI,CACF,uBAAuB,CAAC;YACtB,MAAM,EAAE,OAAO;YACf,MAAM;YACN,IAAI,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE;SAC/C,CAAC,CACH,CAAC;QAEF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,MAAM,EAAE,GAAG,gBAAgB,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YACtE,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;gBACtB,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;gBACrE,IAAI,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1F,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9B,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;QACrF,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACnE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAClC,CAAC;IAED;oFACgF;IAChF,aAAa;QACX,OAAO,CAAC,QAAiB,EAAE,KAAiB,EAAE,EAAE;YAC9C,MAAM,KAAK,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAC;YAChD,IAAI,CAAC,KAAK;gBAAE,OAAO;YACnB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM;gBAAE,OAAO,CAAC,wBAAwB;YAC7C,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,CAAC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,KAAwB;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,wBAAwB;QAE7C,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;YACrB,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,MAAM,CAAC,UAAU;oBAAE,OAAO,CAAC,sBAAsB;gBAC/E,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC7D,MAAM,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;gBACnD,OAAO;YACT,CAAC;YACD,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,IAAI,CAAC,MAAM,CAAC,MAAM;oBAAE,OAAO;gBAC3B,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBACnD,MAAM,CAAC,YAAY,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;gBACzC,OAAO;YACT,CAAC;YACD,KAAK,KAAK,CAAC,CAAC,CAAC;gBACX,MAAM,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;gBAC7B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAClC,OAAO;YACT,CAAC;YACD,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;gBAC7B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAClC,OAAO;YACT,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ export * from "./file-explorer.js";
2
+ export * from "./download-token-store.js";
3
+ export * from "./file-transfer.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/files/index.ts"],"names":[],"mappings":"AACA,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,5 @@
1
+ // File explorer (list/preview + path safety) and binary file transfer.
2
+ export * from "./file-explorer.js";
3
+ export * from "./download-token-store.js";
4
+ export * from "./file-transfer.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/files/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Host-header allowlist — DNS-rebinding defense (architecture/auth-security.md § Host-header
3
+ * allowlist). Vite-style: `localhost`, `*.localhost`, and any literal IP are always allowed;
4
+ * `hostnames` extends the list (a `.`-prefixed entry matches a domain and its subdomains); the
5
+ * literal value `true` disables validation entirely.
6
+ */
7
+ export type HostnamesSetting = true | string[];
8
+ /** Extract the bare hostname (lowercased, port + IPv6 brackets stripped) from a Host header. */
9
+ export declare function parseHostHeader(hostHeader: string | undefined): string | null;
10
+ /**
11
+ * Build a Host-header checker for the given `hostnames` setting. Returns a predicate over the raw
12
+ * Host header value.
13
+ */
14
+ export declare function createHostChecker(hostnames: HostnamesSetting): (hostHeader: string | undefined) => boolean;
15
+ //# sourceMappingURL=host-allowlist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-allowlist.d.ts","sourceRoot":"","sources":["../../src/http/host-allowlist.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,MAAM,MAAM,gBAAgB,GAAG,IAAI,GAAG,MAAM,EAAE,CAAC;AAE/C,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAe7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,gBAAgB,GAC1B,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAwB7C"}
@@ -0,0 +1,52 @@
1
+ import { isIP } from "node:net";
2
+ /** Extract the bare hostname (lowercased, port + IPv6 brackets stripped) from a Host header. */
3
+ export function parseHostHeader(hostHeader) {
4
+ if (!hostHeader)
5
+ return null;
6
+ const value = hostHeader.trim();
7
+ if (value.length === 0)
8
+ return null;
9
+ // IPv6 literal: [::1] or [::1]:6767
10
+ const bracket = value.match(/^\[(.+)\](?::\d+)?$/);
11
+ if (bracket)
12
+ return bracket[1].toLowerCase();
13
+ // Strip a trailing :port (only when it is numeric).
14
+ const idx = value.lastIndexOf(":");
15
+ if (idx !== -1 && /^\d+$/.test(value.slice(idx + 1))) {
16
+ return value.slice(0, idx).toLowerCase();
17
+ }
18
+ return value.toLowerCase();
19
+ }
20
+ /**
21
+ * Build a Host-header checker for the given `hostnames` setting. Returns a predicate over the raw
22
+ * Host header value.
23
+ */
24
+ export function createHostChecker(hostnames) {
25
+ if (hostnames === true)
26
+ return () => true;
27
+ const extra = hostnames.map((h) => h.trim().toLowerCase()).filter((h) => h.length > 0);
28
+ return (hostHeader) => {
29
+ const host = parseHostHeader(hostHeader);
30
+ if (host === null)
31
+ return false;
32
+ // Always-allowed: localhost, *.localhost, and any literal IP (v4/v6).
33
+ if (host === "localhost" || host.endsWith(".localhost"))
34
+ return true;
35
+ if (isIP(host) !== 0)
36
+ return true;
37
+ for (const entry of extra) {
38
+ if (entry === "true")
39
+ return true; // defensive: `true` in an array still disables
40
+ if (entry.startsWith(".")) {
41
+ // ".example.com" matches example.com and any subdomain.
42
+ if (host === entry.slice(1) || host.endsWith(entry))
43
+ return true;
44
+ }
45
+ else if (host === entry) {
46
+ return true;
47
+ }
48
+ }
49
+ return false;
50
+ };
51
+ }
52
+ //# sourceMappingURL=host-allowlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-allowlist.js","sourceRoot":"","sources":["../../src/http/host-allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAWhC,gGAAgG;AAChG,MAAM,UAAU,eAAe,CAAC,UAA8B;IAC5D,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC7B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,oCAAoC;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACnD,IAAI,OAAO;QAAE,OAAQ,OAAO,CAAC,CAAC,CAAY,CAAC,WAAW,EAAE,CAAC;IAEzD,oDAAoD;IACpD,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,CAAC;IACD,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAA2B;IAE3B,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC;IAE1C,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEvF,OAAO,CAAC,UAAU,EAAE,EAAE;QACpB,MAAM,IAAI,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAEhC,sEAAsE;QACtE,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,OAAO,IAAI,CAAC;QACrE,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAElC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,KAAK,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC,CAAC,+CAA+C;YAClF,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1B,wDAAwD;gBACxD,IAAI,IAAI,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;oBAAE,OAAO,IAAI,CAAC;YACnE,CAAC;iBAAM,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;gBAC1B,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { type IncomingMessage, type Server, type ServerResponse } from "node:http";
2
+ import { type HostnamesSetting } from "./host-allowlist.js";
3
+ /**
4
+ * Daemon HTTP server (architecture/auth-security.md § Behavior, daemon-bootstrap.md § Entry points).
5
+ *
6
+ * Request pipeline:
7
+ * 1. `OPTIONS` preflight and `GET /api/health` are exempt from Host + auth checks.
8
+ * 2. Host-header allowlist (403 on mismatch).
9
+ * 3. CORS headers (`daemon.cors.allowedOrigins`).
10
+ * 4. Optional bearer auth (wired in task-003).
11
+ * 5. Delegate to `onRequest` (mcp/file routes, later sprints) else 404.
12
+ */
13
+ export declare const HEALTH_PATH = "/api/health";
14
+ export interface HttpServerDeps {
15
+ hostnames?: HostnamesSetting;
16
+ allowedOrigins?: string[];
17
+ healthPath?: string;
18
+ /** Optional bearer-auth predicate (task-003). Return `false` to reject with 401. */
19
+ authenticate?: (req: IncomingMessage) => boolean;
20
+ /** Optional application route handler. Return `true` when it has written the response. */
21
+ onRequest?: (req: IncomingMessage, res: ServerResponse) => boolean | Promise<boolean>;
22
+ }
23
+ /** Build the `(req, res)` listener implementing the security pipeline. */
24
+ export declare function createRequestListener(deps: HttpServerDeps): (req: IncomingMessage, res: ServerResponse) => void;
25
+ /** Create (but do not start) the daemon HTTP server. */
26
+ export declare function createHttpServer(deps?: HttpServerDeps): Server;
27
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../src/http/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,MAAM,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjG,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE/E;;;;;;;;;GASG;AAEH,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAEzC,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC;IACjD,0FAA0F;IAC1F,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvF;AAgBD,0EAA0E;AAC1E,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,cAAc,GACnB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,CAoDrD;AAED,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,CAElE"}
@@ -0,0 +1,76 @@
1
+ import { createServer } from "node:http";
2
+ import { createHostChecker } from "./host-allowlist.js";
3
+ /**
4
+ * Daemon HTTP server (architecture/auth-security.md § Behavior, daemon-bootstrap.md § Entry points).
5
+ *
6
+ * Request pipeline:
7
+ * 1. `OPTIONS` preflight and `GET /api/health` are exempt from Host + auth checks.
8
+ * 2. Host-header allowlist (403 on mismatch).
9
+ * 3. CORS headers (`daemon.cors.allowedOrigins`).
10
+ * 4. Optional bearer auth (wired in task-003).
11
+ * 5. Delegate to `onRequest` (mcp/file routes, later sprints) else 404.
12
+ */
13
+ export const HEALTH_PATH = "/api/health";
14
+ function applyCors(req, res, allowedOrigins) {
15
+ const origin = req.headers.origin;
16
+ if (origin && (allowedOrigins.includes("*") || allowedOrigins.includes(origin))) {
17
+ res.setHeader("Access-Control-Allow-Origin", origin);
18
+ res.setHeader("Vary", "Origin");
19
+ res.setHeader("Access-Control-Allow-Credentials", "true");
20
+ }
21
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
22
+ res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Sec-WebSocket-Protocol");
23
+ }
24
+ /** Build the `(req, res)` listener implementing the security pipeline. */
25
+ export function createRequestListener(deps) {
26
+ const healthPath = deps.healthPath ?? HEALTH_PATH;
27
+ const allowedOrigins = deps.allowedOrigins ?? [];
28
+ const hostCheck = createHostChecker(deps.hostnames ?? []);
29
+ return (req, res) => {
30
+ void (async () => {
31
+ const method = (req.method ?? "GET").toUpperCase();
32
+ const path = (req.url ?? "/").split("?")[0];
33
+ // (1) CORS preflight — exempt from host + auth.
34
+ if (method === "OPTIONS") {
35
+ applyCors(req, res, allowedOrigins);
36
+ res.writeHead(204);
37
+ res.end();
38
+ return;
39
+ }
40
+ // (1) Health probe — exempt from host + auth.
41
+ if (method === "GET" && path === healthPath) {
42
+ applyCors(req, res, allowedOrigins);
43
+ res.writeHead(200, { "content-type": "application/json" });
44
+ res.end(JSON.stringify({ status: "ok" }));
45
+ return;
46
+ }
47
+ // (2) Host allowlist.
48
+ if (!hostCheck(req.headers.host)) {
49
+ res.writeHead(403, { "content-type": "text/plain" });
50
+ res.end("Host not allowed");
51
+ return;
52
+ }
53
+ // (3) CORS.
54
+ applyCors(req, res, allowedOrigins);
55
+ // (4) Auth (task-003).
56
+ if (deps.authenticate && !deps.authenticate(req)) {
57
+ res.writeHead(401, { "content-type": "text/plain" });
58
+ res.end("Unauthorized");
59
+ return;
60
+ }
61
+ // (5) Application routes.
62
+ if (deps.onRequest) {
63
+ const handled = await deps.onRequest(req, res);
64
+ if (handled)
65
+ return;
66
+ }
67
+ res.writeHead(404, { "content-type": "text/plain" });
68
+ res.end("Not found");
69
+ })();
70
+ };
71
+ }
72
+ /** Create (but do not start) the daemon HTTP server. */
73
+ export function createHttpServer(deps = {}) {
74
+ return createServer(createRequestListener(deps));
75
+ }
76
+ //# sourceMappingURL=http-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../src/http/http-server.ts"],"names":[],"mappings":"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"}