@clauderecallhq/cli 0.0.1 → 0.11.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 (277) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +543 -3
  3. package/README.public.md +523 -0
  4. package/dist/cli.js +354 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/activate.js +69 -0
  7. package/dist/commands/activate.js.map +1 -0
  8. package/dist/commands/audit-secrets.js +103 -0
  9. package/dist/commands/audit-secrets.js.map +1 -0
  10. package/dist/commands/blame.js +35 -0
  11. package/dist/commands/blame.js.map +1 -0
  12. package/dist/commands/config-verification.js +18 -0
  13. package/dist/commands/config-verification.js.map +1 -0
  14. package/dist/commands/context.js +144 -0
  15. package/dist/commands/context.js.map +1 -0
  16. package/dist/commands/correlate.js +70 -0
  17. package/dist/commands/correlate.js.map +1 -0
  18. package/dist/commands/digest.js +78 -0
  19. package/dist/commands/digest.js.map +1 -0
  20. package/dist/commands/health.js +62 -0
  21. package/dist/commands/health.js.map +1 -0
  22. package/dist/commands/index.js +247 -0
  23. package/dist/commands/index.js.map +1 -0
  24. package/dist/commands/install-extension.js +138 -0
  25. package/dist/commands/install-extension.js.map +1 -0
  26. package/dist/commands/license.js +39 -0
  27. package/dist/commands/license.js.map +1 -0
  28. package/dist/commands/list.js +47 -0
  29. package/dist/commands/list.js.map +1 -0
  30. package/dist/commands/mcp.js +29 -0
  31. package/dist/commands/mcp.js.map +1 -0
  32. package/dist/commands/open.js +28 -0
  33. package/dist/commands/open.js.map +1 -0
  34. package/dist/commands/paste.js +154 -0
  35. package/dist/commands/paste.js.map +1 -0
  36. package/dist/commands/projects.js +36 -0
  37. package/dist/commands/projects.js.map +1 -0
  38. package/dist/commands/search.js +67 -0
  39. package/dist/commands/search.js.map +1 -0
  40. package/dist/commands/semantic.js +173 -0
  41. package/dist/commands/semantic.js.map +1 -0
  42. package/dist/commands/show.js +121 -0
  43. package/dist/commands/show.js.map +1 -0
  44. package/dist/commands/start.js +47 -0
  45. package/dist/commands/start.js.map +1 -0
  46. package/dist/commands/stats.js +133 -0
  47. package/dist/commands/stats.js.map +1 -0
  48. package/dist/commands/status.js +45 -0
  49. package/dist/commands/status.js.map +1 -0
  50. package/dist/commands/stop.js +29 -0
  51. package/dist/commands/stop.js.map +1 -0
  52. package/dist/commands/thread.js +396 -0
  53. package/dist/commands/thread.js.map +1 -0
  54. package/dist/context/formatter.js +103 -0
  55. package/dist/context/formatter.js.map +1 -0
  56. package/dist/daemon/auto-tag-config.js +103 -0
  57. package/dist/daemon/auto-tag-config.js.map +1 -0
  58. package/dist/daemon/auto-tag-config.test.js +72 -0
  59. package/dist/daemon/auto-tag-config.test.js.map +1 -0
  60. package/dist/daemon/auto-title-config.js +70 -0
  61. package/dist/daemon/auto-title-config.js.map +1 -0
  62. package/dist/daemon/bulk-title-jobs.js +170 -0
  63. package/dist/daemon/bulk-title-jobs.js.map +1 -0
  64. package/dist/daemon/correlator.js +320 -0
  65. package/dist/daemon/correlator.js.map +1 -0
  66. package/dist/daemon/discover.js +316 -0
  67. package/dist/daemon/discover.js.map +1 -0
  68. package/dist/daemon/editor-detection.js +186 -0
  69. package/dist/daemon/editor-detection.js.map +1 -0
  70. package/dist/daemon/entrypoint.js +55 -0
  71. package/dist/daemon/entrypoint.js.map +1 -0
  72. package/dist/daemon/git-correlator.js +256 -0
  73. package/dist/daemon/git-correlator.js.map +1 -0
  74. package/dist/daemon/mcp-installer.js +108 -0
  75. package/dist/daemon/mcp-installer.js.map +1 -0
  76. package/dist/daemon/onboarding-state.js +140 -0
  77. package/dist/daemon/onboarding-state.js.map +1 -0
  78. package/dist/daemon/pidfile.js +57 -0
  79. package/dist/daemon/pidfile.js.map +1 -0
  80. package/dist/daemon/ports.js +48 -0
  81. package/dist/daemon/ports.js.map +1 -0
  82. package/dist/daemon/scanProgressRegistry.js +62 -0
  83. package/dist/daemon/scanProgressRegistry.js.map +1 -0
  84. package/dist/daemon/server.js +2010 -0
  85. package/dist/daemon/server.js.map +1 -0
  86. package/dist/daemon/tag-scanner/anthropic-client.js +40 -0
  87. package/dist/daemon/tag-scanner/anthropic-client.js.map +1 -0
  88. package/dist/daemon/tag-scanner/autopilot.js +131 -0
  89. package/dist/daemon/tag-scanner/autopilot.js.map +1 -0
  90. package/dist/daemon/tag-scanner/claude-cli-driver.js +250 -0
  91. package/dist/daemon/tag-scanner/claude-cli-driver.js.map +1 -0
  92. package/dist/daemon/tag-scanner/orchestrator.js +88 -0
  93. package/dist/daemon/tag-scanner/orchestrator.js.map +1 -0
  94. package/dist/daemon/tag-scanner/prompt.js +46 -0
  95. package/dist/daemon/tag-scanner/prompt.js.map +1 -0
  96. package/dist/daemon/tag-scanner/prompt.test.js +48 -0
  97. package/dist/daemon/tag-scanner/prompt.test.js.map +1 -0
  98. package/dist/daemon/tag-scanner/scan-state.js +49 -0
  99. package/dist/daemon/tag-scanner/scan-state.js.map +1 -0
  100. package/dist/daemon/tag-scanner/session-fetcher.js +82 -0
  101. package/dist/daemon/tag-scanner/session-fetcher.js.map +1 -0
  102. package/dist/daemon/tag-scanner/session-fetcher.test.js +34 -0
  103. package/dist/daemon/tag-scanner/session-fetcher.test.js.map +1 -0
  104. package/dist/daemon/tag-scanner/validator.js +50 -0
  105. package/dist/daemon/tag-scanner/validator.js.map +1 -0
  106. package/dist/daemon/tag-scanner/validator.test.js +41 -0
  107. package/dist/daemon/tag-scanner/validator.test.js.map +1 -0
  108. package/dist/daemon/terminal-registry.js +443 -0
  109. package/dist/daemon/terminal-registry.js.map +1 -0
  110. package/dist/daemon/ui.js +64 -0
  111. package/dist/daemon/ui.js.map +1 -0
  112. package/dist/daemon/watcher.js +256 -0
  113. package/dist/daemon/watcher.js.map +1 -0
  114. package/dist/db/client.js +22 -0
  115. package/dist/db/client.js.map +1 -0
  116. package/dist/db/schema.js +496 -0
  117. package/dist/db/schema.js.map +1 -0
  118. package/dist/license/api-base.js +13 -0
  119. package/dist/license/api-base.js.map +1 -0
  120. package/dist/license/manager.js +43 -0
  121. package/dist/license/manager.js.map +1 -0
  122. package/dist/license/public-key.js +19 -0
  123. package/dist/license/public-key.js.map +1 -0
  124. package/dist/license/storage.js +27 -0
  125. package/dist/license/storage.js.map +1 -0
  126. package/dist/license/verify.js +23 -0
  127. package/dist/license/verify.js.map +1 -0
  128. package/dist/mcp/audit.js +126 -0
  129. package/dist/mcp/audit.js.map +1 -0
  130. package/dist/mcp/prompts.js +180 -0
  131. package/dist/mcp/prompts.js.map +1 -0
  132. package/dist/mcp/server.js +502 -0
  133. package/dist/mcp/server.js.map +1 -0
  134. package/dist/mcp/thread-tools.js +363 -0
  135. package/dist/mcp/thread-tools.js.map +1 -0
  136. package/dist/mcp/write-tools.js +239 -0
  137. package/dist/mcp/write-tools.js.map +1 -0
  138. package/dist/parser/jsonl.js +150 -0
  139. package/dist/parser/jsonl.js.map +1 -0
  140. package/dist/semantic/chunker.js +47 -0
  141. package/dist/semantic/chunker.js.map +1 -0
  142. package/dist/semantic/config.js +74 -0
  143. package/dist/semantic/config.js.map +1 -0
  144. package/dist/semantic/embedder.js +54 -0
  145. package/dist/semantic/embedder.js.map +1 -0
  146. package/dist/semantic/fusion.js +38 -0
  147. package/dist/semantic/fusion.js.map +1 -0
  148. package/dist/semantic/model-download.js +69 -0
  149. package/dist/semantic/model-download.js.map +1 -0
  150. package/dist/semantic/pipeline.js +375 -0
  151. package/dist/semantic/pipeline.js.map +1 -0
  152. package/dist/semantic/query.js +42 -0
  153. package/dist/semantic/query.js.map +1 -0
  154. package/dist/semantic/worker.js +78 -0
  155. package/dist/semantic/worker.js.map +1 -0
  156. package/dist/stats/backfill.js +151 -0
  157. package/dist/stats/backfill.js.map +1 -0
  158. package/dist/stats/health.js +102 -0
  159. package/dist/stats/health.js.map +1 -0
  160. package/dist/stats/query.js +385 -0
  161. package/dist/stats/query.js.map +1 -0
  162. package/dist/utils/aliases.js +107 -0
  163. package/dist/utils/aliases.js.map +1 -0
  164. package/dist/utils/autoCollections.js +635 -0
  165. package/dist/utils/autoCollections.js.map +1 -0
  166. package/dist/utils/autoTitle.js +348 -0
  167. package/dist/utils/autoTitle.js.map +1 -0
  168. package/dist/utils/collections.js +446 -0
  169. package/dist/utils/collections.js.map +1 -0
  170. package/dist/utils/format.js +46 -0
  171. package/dist/utils/format.js.map +1 -0
  172. package/dist/utils/notes.js +270 -0
  173. package/dist/utils/notes.js.map +1 -0
  174. package/dist/utils/paths.js +50 -0
  175. package/dist/utils/paths.js.map +1 -0
  176. package/dist/utils/pricing.js +257 -0
  177. package/dist/utils/pricing.js.map +1 -0
  178. package/dist/utils/secret-scanner.js +166 -0
  179. package/dist/utils/secret-scanner.js.map +1 -0
  180. package/dist/utils/sessionLabel.js +64 -0
  181. package/dist/utils/sessionLabel.js.map +1 -0
  182. package/dist/utils/tags.js +97 -0
  183. package/dist/utils/tags.js.map +1 -0
  184. package/dist/utils/thread-context.js +129 -0
  185. package/dist/utils/thread-context.js.map +1 -0
  186. package/dist/utils/threadFilter.js +18 -0
  187. package/dist/utils/threadFilter.js.map +1 -0
  188. package/dist/utils/threads-titler.js +298 -0
  189. package/dist/utils/threads-titler.js.map +1 -0
  190. package/dist/utils/threads.js +383 -0
  191. package/dist/utils/threads.js.map +1 -0
  192. package/dist/utils/usage.js +76 -0
  193. package/dist/utils/usage.js.map +1 -0
  194. package/dist/verification/compute.js +88 -0
  195. package/dist/verification/compute.js.map +1 -0
  196. package/dist/verification/config.js +34 -0
  197. package/dist/verification/config.js.map +1 -0
  198. package/dist/web/assets/index-CIr6J4Fw.js +1201 -0
  199. package/dist/web/assets/index-Ctc8g9Jw.css +1 -0
  200. package/dist/web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  201. package/dist/web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  202. package/dist/web/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  203. package/dist/web/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  204. package/dist/web/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  205. package/dist/web/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  206. package/dist/web/assets/inter-cyrillic-700-normal-CjBOestx.woff2 +0 -0
  207. package/dist/web/assets/inter-cyrillic-700-normal-DrXBdSj3.woff +0 -0
  208. package/dist/web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  209. package/dist/web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  210. package/dist/web/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  211. package/dist/web/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  212. package/dist/web/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  213. package/dist/web/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  214. package/dist/web/assets/inter-cyrillic-ext-700-normal-BjwYoWNd.woff2 +0 -0
  215. package/dist/web/assets/inter-cyrillic-ext-700-normal-LO58E6JB.woff +0 -0
  216. package/dist/web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  217. package/dist/web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  218. package/dist/web/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  219. package/dist/web/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  220. package/dist/web/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  221. package/dist/web/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  222. package/dist/web/assets/inter-greek-700-normal-BUv2fZ6O.woff +0 -0
  223. package/dist/web/assets/inter-greek-700-normal-C3JjAnD8.woff2 +0 -0
  224. package/dist/web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  225. package/dist/web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  226. package/dist/web/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  227. package/dist/web/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  228. package/dist/web/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  229. package/dist/web/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  230. package/dist/web/assets/inter-greek-ext-700-normal-BoQ6DsYi.woff +0 -0
  231. package/dist/web/assets/inter-greek-ext-700-normal-qfdV9bQt.woff2 +0 -0
  232. package/dist/web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  233. package/dist/web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  234. package/dist/web/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  235. package/dist/web/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  236. package/dist/web/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  237. package/dist/web/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  238. package/dist/web/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
  239. package/dist/web/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  240. package/dist/web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  241. package/dist/web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  242. package/dist/web/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  243. package/dist/web/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  244. package/dist/web/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  245. package/dist/web/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  246. package/dist/web/assets/inter-latin-ext-700-normal-Ca8adRJv.woff2 +0 -0
  247. package/dist/web/assets/inter-latin-ext-700-normal-TidjK2hL.woff +0 -0
  248. package/dist/web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  249. package/dist/web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  250. package/dist/web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  251. package/dist/web/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  252. package/dist/web/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  253. package/dist/web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  254. package/dist/web/assets/inter-vietnamese-700-normal-BZaoP0fm.woff +0 -0
  255. package/dist/web/assets/inter-vietnamese-700-normal-DlLaEgI2.woff2 +0 -0
  256. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  257. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  258. package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  259. package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  260. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  261. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  262. package/dist/web/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  263. package/dist/web/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  264. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  265. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  266. package/dist/web/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  267. package/dist/web/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  268. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  269. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  270. package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  271. package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  272. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  273. package/dist/web/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  274. package/dist/web/favicon.svg +9 -0
  275. package/dist/web/index.html +15 -0
  276. package/package.json +79 -9
  277. package/bin/cli.js +0 -12
@@ -0,0 +1,256 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { stat } from 'node:fs/promises';
4
+ import { getDb } from '../db/client.js';
5
+ const execFileP = promisify(execFile);
6
+ /**
7
+ * v0.10b — Git correlator.
8
+ *
9
+ * For any session whose `cwd` is a live git worktree, we record every commit
10
+ * authored in that worktree during the session's [started_at, ended_at]
11
+ * window. The invariants are non-negotiable:
12
+ *
13
+ * - `git` is always spawned with `cwd: sessionCwd`. It is never invoked at
14
+ * the user's HOME or anywhere else. This is what keeps blast radius
15
+ * scoped to the worktree the user was working in.
16
+ * - Only read-only subcommands run (`rev-parse`, `log`). No `fetch`, no
17
+ * `push`, no `pull`, no `checkout`. We never touch refs or disk state.
18
+ * - Arguments are passed via the execFile array form — no shell, no
19
+ * interpolation. A malicious branch name or commit subject cannot inject
20
+ * a command.
21
+ * - Missing cwds, non-repos, or permission errors are swallowed silently.
22
+ * This is a best-effort enrichment; it must never crash ingest.
23
+ */
24
+ const GIT_TIMEOUT_MS = 10_000;
25
+ const LOG_PRETTY = '%H%x09%aI%x09%s';
26
+ async function isGitWorktree(cwd) {
27
+ try {
28
+ const { stdout } = await execFileP('git', ['rev-parse', '--is-inside-work-tree'], { cwd, timeout: GIT_TIMEOUT_MS });
29
+ return stdout.trim() === 'true';
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ async function gitLogCommits(cwd, since, until) {
36
+ // --no-color / --no-pager for scriptable output.
37
+ // --all ensures we catch commits on any branch authored during the window,
38
+ // not just the current HEAD lineage. Recall users frequently rebase or
39
+ // branch-hop mid-session.
40
+ const args = [
41
+ '--no-pager',
42
+ 'log',
43
+ '--all',
44
+ '--no-color',
45
+ '--since',
46
+ since,
47
+ '--until',
48
+ until,
49
+ `--pretty=format:${LOG_PRETTY}`,
50
+ ];
51
+ const { stdout } = await execFileP('git', args, {
52
+ cwd,
53
+ timeout: GIT_TIMEOUT_MS,
54
+ maxBuffer: 8 * 1024 * 1024,
55
+ });
56
+ const rows = [];
57
+ const seen = new Set();
58
+ for (const line of stdout.split('\n')) {
59
+ if (!line)
60
+ continue;
61
+ const [sha, iso, ...rest] = line.split('\t');
62
+ if (!sha || seen.has(sha))
63
+ continue;
64
+ seen.add(sha);
65
+ rows.push({
66
+ commit_sha: sha,
67
+ committed_at: iso ?? null,
68
+ subject: rest.join('\t') || null,
69
+ });
70
+ }
71
+ return rows;
72
+ }
73
+ function getSession(sessionId) {
74
+ const db = getDb();
75
+ return (db
76
+ .prepare(`SELECT id, cwd, started_at, ended_at
77
+ FROM sessions WHERE id = ?`)
78
+ .get(sessionId) ?? null);
79
+ }
80
+ function persistCommits(sessionId, cwdSnapshot, commits) {
81
+ if (commits.length === 0)
82
+ return 0;
83
+ const db = getDb();
84
+ const now = new Date().toISOString();
85
+ const ins = db.prepare(`INSERT OR IGNORE INTO session_commits
86
+ (session_id, commit_sha, committed_at, subject, cwd_snapshot, correlated_at)
87
+ VALUES (?, ?, ?, ?, ?, ?)`);
88
+ let inserted = 0;
89
+ const txn = db.transaction((rows) => {
90
+ for (const r of rows) {
91
+ const result = ins.run(sessionId, r.commit_sha, r.committed_at, r.subject, cwdSnapshot, now);
92
+ if (result.changes > 0)
93
+ inserted += 1;
94
+ }
95
+ });
96
+ txn(commits);
97
+ return inserted;
98
+ }
99
+ /**
100
+ * Correlate one session. Silently skips any graceful-failure path — the
101
+ * CLI and API endpoints surface the result code for user-facing calls; the
102
+ * watcher hook discards it.
103
+ */
104
+ export async function correlateSession(sessionId) {
105
+ const session = getSession(sessionId);
106
+ if (!session) {
107
+ return { sessionId, status: 'error', commitsFound: 0, commitsInserted: 0, error: 'session not found' };
108
+ }
109
+ if (!session.cwd) {
110
+ return { sessionId, status: 'no-cwd', commitsFound: 0, commitsInserted: 0 };
111
+ }
112
+ if (!session.started_at || !session.ended_at) {
113
+ return { sessionId, status: 'no-window', commitsFound: 0, commitsInserted: 0 };
114
+ }
115
+ // Tolerate zero-length windows by extending the upper bound by 1 second —
116
+ // very short sessions sometimes share a timestamp across started_at and
117
+ // ended_at, which `git log --since=X --until=X` rejects.
118
+ const sinceIso = session.started_at;
119
+ const untilIso = session.ended_at === session.started_at
120
+ ? new Date(Date.parse(session.ended_at) + 1000).toISOString()
121
+ : session.ended_at;
122
+ try {
123
+ const s = await stat(session.cwd);
124
+ if (!s.isDirectory()) {
125
+ return { sessionId, status: 'cwd-missing', commitsFound: 0, commitsInserted: 0 };
126
+ }
127
+ }
128
+ catch {
129
+ return { sessionId, status: 'cwd-missing', commitsFound: 0, commitsInserted: 0 };
130
+ }
131
+ const isRepo = await isGitWorktree(session.cwd);
132
+ if (!isRepo) {
133
+ return { sessionId, status: 'not-a-repo', commitsFound: 0, commitsInserted: 0 };
134
+ }
135
+ try {
136
+ const commits = await gitLogCommits(session.cwd, sinceIso, untilIso);
137
+ const inserted = persistCommits(sessionId, session.cwd, commits);
138
+ return {
139
+ sessionId,
140
+ status: 'ok',
141
+ commitsFound: commits.length,
142
+ commitsInserted: inserted,
143
+ };
144
+ }
145
+ catch (err) {
146
+ return {
147
+ sessionId,
148
+ status: 'error',
149
+ commitsFound: 0,
150
+ commitsInserted: 0,
151
+ error: err.message,
152
+ };
153
+ }
154
+ }
155
+ /**
156
+ * Correlate every session that has a cwd and a closed time window. Safe to
157
+ * re-run: INSERT OR IGNORE on the composite PK means a second pass just
158
+ * tops up newly-visible commits.
159
+ */
160
+ export async function correlateAll(opts = {}) {
161
+ const db = getDb();
162
+ const limit = Math.max(1, Math.min(100_000, opts.limit ?? 10_000));
163
+ const rows = db
164
+ .prepare(`SELECT id FROM sessions
165
+ WHERE cwd IS NOT NULL AND started_at IS NOT NULL AND ended_at IS NOT NULL
166
+ ORDER BY COALESCE(ended_at, started_at) DESC
167
+ LIMIT ?`)
168
+ .all(limit);
169
+ const summary = {
170
+ total: rows.length,
171
+ ok: 0,
172
+ skipped: 0,
173
+ errors: 0,
174
+ commitsInserted: 0,
175
+ results: [],
176
+ };
177
+ let done = 0;
178
+ for (const r of rows) {
179
+ const res = await correlateSession(r.id);
180
+ summary.results.push(res);
181
+ if (res.status === 'ok') {
182
+ summary.ok += 1;
183
+ summary.commitsInserted += res.commitsInserted;
184
+ }
185
+ else if (res.status === 'error') {
186
+ summary.errors += 1;
187
+ }
188
+ else {
189
+ summary.skipped += 1;
190
+ }
191
+ done += 1;
192
+ opts.onProgress?.(done, rows.length);
193
+ }
194
+ return summary;
195
+ }
196
+ export function findSessionsByCommit(shaOrPrefix) {
197
+ const db = getDb();
198
+ const trimmed = shaOrPrefix.trim();
199
+ if (!/^[0-9a-fA-F]{4,40}$/.test(trimmed))
200
+ return [];
201
+ const like = `${trimmed.toLowerCase()}%`;
202
+ return db
203
+ .prepare(`SELECT sc.session_id AS sessionId,
204
+ NULLIF(sa.alias, '') AS alias,
205
+ p.name AS project,
206
+ s.started_at AS startedAt,
207
+ s.ended_at AS endedAt,
208
+ sc.commit_sha AS commitSha,
209
+ sc.committed_at AS committedAt,
210
+ sc.subject AS subject
211
+ FROM session_commits sc
212
+ JOIN sessions s ON s.id = sc.session_id
213
+ JOIN projects p ON p.id = s.project_id
214
+ LEFT JOIN session_aliases sa ON sa.session_id = sc.session_id
215
+ WHERE lower(sc.commit_sha) = lower(?)
216
+ OR lower(sc.commit_sha) LIKE ?
217
+ ORDER BY COALESCE(sc.committed_at, s.started_at, '') DESC`)
218
+ .all(trimmed, like);
219
+ }
220
+ /** Commits already correlated for a given session. Used by the UI strip. */
221
+ export function getCommitsForSession(sessionId) {
222
+ const db = getDb();
223
+ return db
224
+ .prepare(`SELECT commit_sha, committed_at, subject, correlated_at
225
+ FROM session_commits
226
+ WHERE session_id = ?
227
+ ORDER BY COALESCE(committed_at, correlated_at) ASC`)
228
+ .all(sessionId);
229
+ }
230
+ /**
231
+ * Non-blocking hook for the watcher. Fires correlation for a session id,
232
+ * swallows all errors, and never blocks the caller. If the session already
233
+ * has correlated rows younger than `freshness_ms`, skip to avoid hammering
234
+ * git on every debounced reindex while a session is still being written.
235
+ */
236
+ const RECORRELATE_COOLDOWN_MS = 30_000;
237
+ export function maybeCorrelateInBackground(sessionId) {
238
+ try {
239
+ const db = getDb();
240
+ const row = db
241
+ .prepare(`SELECT MAX(correlated_at) AS last_at
242
+ FROM session_commits WHERE session_id = ?`)
243
+ .get(sessionId);
244
+ const lastAt = row?.last_at ? Date.parse(row.last_at) : 0;
245
+ if (lastAt && Date.now() - lastAt < RECORRELATE_COOLDOWN_MS)
246
+ return;
247
+ }
248
+ catch {
249
+ // DB read failures should never prevent correlation from attempting.
250
+ }
251
+ void correlateSession(sessionId).catch((err) => {
252
+ // eslint-disable-next-line no-console
253
+ console.error(`[git-correlator] ${sessionId.slice(0, 8)} failed:`, err);
254
+ });
255
+ }
256
+ //# sourceMappingURL=git-correlator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-correlator.js","sourceRoot":"","sources":["../../src/daemon/git-correlator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ /**
7
+ * One-click "Install Claude Recall MCP in Claude Code" helper. Reads the
8
+ * user's Claude Code user config at ~/.claude.json, surgically adds a
9
+ * `mcpServers.recall` entry, and writes it back preserving every other key.
10
+ *
11
+ * We pick the best command shape at install time:
12
+ * 1. If `claude-recall-mcp` is on PATH (globally installed via npm), use
13
+ * that as the command — shortest, works anywhere.
14
+ * 2. Otherwise, locate the bundled `dist/mcp/server.js` relative to this
15
+ * module and write an absolute-path invocation via `node`. This is
16
+ * what local-dev users (npm link not run) need.
17
+ */
18
+ const CLAUDE_CONFIG_FILE = join(homedir(), '.claude.json');
19
+ // Resolve dist/mcp/server.js by walking up from this file. When built,
20
+ // this file lives at <repo>/dist/daemon/mcp-installer.js → go up two → <repo>/dist
21
+ // → join mcp/server.js.
22
+ const HERE = dirname(fileURLToPath(import.meta.url));
23
+ const BUNDLED_MCP_SCRIPT = resolve(HERE, '..', 'mcp', 'server.js');
24
+ function readClaudeConfig() {
25
+ if (!existsSync(CLAUDE_CONFIG_FILE))
26
+ return {};
27
+ try {
28
+ const raw = readFileSync(CLAUDE_CONFIG_FILE, 'utf8');
29
+ const parsed = JSON.parse(raw);
30
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
31
+ return parsed;
32
+ }
33
+ return {};
34
+ }
35
+ catch (err) {
36
+ console.error('[mcp-installer] failed to parse ~/.claude.json:', err);
37
+ return {};
38
+ }
39
+ }
40
+ function hasBinaryOnPath(name) {
41
+ try {
42
+ execSync(`command -v ${name}`, { stdio: 'ignore' });
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ /**
50
+ * Pick the best MCP invocation for THIS machine. Globally-installed users
51
+ * get the short form; local-dev / npm-link-skipped users get an absolute
52
+ * path to the bundled script so Claude Code can actually find it.
53
+ */
54
+ function preferredCommand() {
55
+ if (hasBinaryOnPath('claude-recall-mcp')) {
56
+ return { command: 'claude-recall-mcp', args: [] };
57
+ }
58
+ return { command: process.execPath, args: [BUNDLED_MCP_SCRIPT] };
59
+ }
60
+ export function getMcpInstallStatus() {
61
+ const cfg = readClaudeConfig();
62
+ const entry = cfg.mcpServers?.['recall'];
63
+ return {
64
+ configPath: CLAUDE_CONFIG_FILE,
65
+ configExists: existsSync(CLAUDE_CONFIG_FILE),
66
+ installed: Boolean(entry && typeof entry.command === 'string'),
67
+ command: entry?.command ?? null,
68
+ args: entry?.args ?? null,
69
+ };
70
+ }
71
+ export function installMcp() {
72
+ const cfg = readClaudeConfig();
73
+ const existing = (cfg.mcpServers ?? {});
74
+ const desired = preferredCommand();
75
+ const already = existing['recall'];
76
+ const alreadyMatches = already?.command === desired.command &&
77
+ JSON.stringify(already?.args ?? []) === JSON.stringify(desired.args);
78
+ if (alreadyMatches)
79
+ return getMcpInstallStatus();
80
+ // Build the entry — omit `args` field when empty to keep the config tidy.
81
+ const recallEntry = { command: desired.command };
82
+ if (desired.args.length > 0)
83
+ recallEntry.args = desired.args;
84
+ const next = {
85
+ ...cfg,
86
+ mcpServers: {
87
+ ...existing,
88
+ recall: recallEntry,
89
+ },
90
+ };
91
+ writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(next, null, 2));
92
+ return getMcpInstallStatus();
93
+ }
94
+ export function uninstallMcp() {
95
+ const cfg = readClaudeConfig();
96
+ const existing = (cfg.mcpServers ?? {});
97
+ if (!existing['recall'])
98
+ return getMcpInstallStatus();
99
+ const { recall: _removed, ...rest } = existing;
100
+ void _removed;
101
+ const next = {
102
+ ...cfg,
103
+ mcpServers: rest,
104
+ };
105
+ writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(next, null, 2));
106
+ return getMcpInstallStatus();
107
+ }
108
+ //# sourceMappingURL=mcp-installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-installer.js","sourceRoot":"","sources":["../../src/daemon/mcp-installer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { z } from 'zod';
5
+ /**
6
+ * First-run onboarding state (v0.14a).
7
+ *
8
+ * The local web UI fires a 3-step tour on first open to put the
9
+ * context-re-injection moat in front of the user inside the first minute.
10
+ * Completion is persisted here so the tour does not fire again. A "replay
11
+ * onboarding" button in the Command Center resets the state.
12
+ *
13
+ * File: `$RECALL_HOME/onboarding.json` (default `~/.recall/onboarding.json`).
14
+ * Dedicated file — not merged into `config.json` — so it is easy to inspect,
15
+ * reset, or wipe without risking auto-tag configuration.
16
+ *
17
+ * Reads are tolerant: a missing or malformed file yields defaults rather than
18
+ * throwing. The daemon must boot even if a prior version wrote an incompatible
19
+ * shape. `RECALL_HOME` is resolved lazily on every call so tests can redirect
20
+ * the data root per-test without a module-cache dance (matches the approach
21
+ * used by auto-tag-config.ts).
22
+ *
23
+ * No secrets ever live in this file — it stores only booleans and timestamps,
24
+ * so default filesystem permissions are fine (no chmod 0600 needed).
25
+ */
26
+ function recallHome() {
27
+ return process.env.RECALL_HOME ?? join(homedir(), '.recall');
28
+ }
29
+ function ensureHome() {
30
+ const home = recallHome();
31
+ if (!existsSync(home))
32
+ mkdirSync(home, { recursive: true });
33
+ }
34
+ function stateFile() {
35
+ return join(recallHome(), 'onboarding.json');
36
+ }
37
+ /**
38
+ * Persisted onboarding state. `version` lets us evolve the shape later
39
+ * without silently misreading old files — if an incompatible version
40
+ * lands, `read` falls back to defaults.
41
+ */
42
+ export const OnboardingStateSchema = z.object({
43
+ version: z.literal(1).default(1),
44
+ /** True once the user reaches the final step. */
45
+ completed: z.boolean().default(false),
46
+ /** True if the user dismissed via "Skip" / "I'll explore myself". */
47
+ skipped: z.boolean().default(false),
48
+ /** ISO timestamp of the terminal event (complete OR skip). Null until then. */
49
+ finishedAt: z.string().nullable().default(null),
50
+ /** Which step ids the user made it through. Append-only; history-friendly. */
51
+ completedSteps: z.array(z.string()).default([]),
52
+ /**
53
+ * v0.15a.2 — first-run explainer for the Threads view. Flipped to true
54
+ * the first time the user either finishes or skips the Threads intro
55
+ * overlay. The "Replay threads tour" button in the Command Center and
56
+ * the matching Cmd+K command reset this flag back to false.
57
+ */
58
+ threadsIntroSeen: z.boolean().default(false),
59
+ });
60
+ const DEFAULTS = {
61
+ version: 1,
62
+ completed: false,
63
+ skipped: false,
64
+ finishedAt: null,
65
+ completedSteps: [],
66
+ threadsIntroSeen: false,
67
+ };
68
+ /**
69
+ * Read the current state. A missing or malformed file yields defaults —
70
+ * the web UI treats defaults as "fire the tour".
71
+ */
72
+ export function readOnboardingState() {
73
+ const path = stateFile();
74
+ if (!existsSync(path))
75
+ return { ...DEFAULTS };
76
+ try {
77
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
78
+ const parsed = OnboardingStateSchema.safeParse(raw);
79
+ return parsed.success ? parsed.data : { ...DEFAULTS };
80
+ }
81
+ catch (err) {
82
+ console.error('[onboarding-state] failed to parse onboarding.json, using defaults:', err);
83
+ return { ...DEFAULTS };
84
+ }
85
+ }
86
+ /**
87
+ * Merge a patch into the on-disk state and return the merged result.
88
+ * Writes are immutable: we build a new object and never mutate the
89
+ * existing one in place.
90
+ */
91
+ export function writeOnboardingState(patch) {
92
+ ensureHome();
93
+ const existing = readOnboardingState();
94
+ const merged = OnboardingStateSchema.parse({
95
+ ...existing,
96
+ ...patch,
97
+ // completedSteps merges additively — never drops history.
98
+ completedSteps: dedupe([
99
+ ...(existing.completedSteps ?? []),
100
+ ...(patch.completedSteps ?? []),
101
+ ]),
102
+ version: 1,
103
+ });
104
+ writeFileSync(stateFile(), JSON.stringify(merged, null, 2));
105
+ return merged;
106
+ }
107
+ /**
108
+ * Reset the flags so the tour fires again. Keeps the completedSteps
109
+ * history intact as a breadcrumb of prior runs (useful for debugging
110
+ * "why does the modal keep showing?" later on).
111
+ */
112
+ export function resetOnboardingState() {
113
+ ensureHome();
114
+ const existing = readOnboardingState();
115
+ const reset = {
116
+ version: 1,
117
+ completed: false,
118
+ skipped: false,
119
+ finishedAt: null,
120
+ completedSteps: existing.completedSteps,
121
+ // The Threads first-run explainer is an independent flow; resetting the
122
+ // main onboarding tour does not roll it back. Replaying the Threads tour
123
+ // goes through a separate PUT { threadsIntroSeen: false } patch.
124
+ threadsIntroSeen: existing.threadsIntroSeen,
125
+ };
126
+ writeFileSync(stateFile(), JSON.stringify(reset, null, 2));
127
+ return reset;
128
+ }
129
+ function dedupe(xs) {
130
+ const seen = new Set();
131
+ const out = [];
132
+ for (const x of xs) {
133
+ if (seen.has(x))
134
+ continue;
135
+ seen.add(x);
136
+ out.push(x);
137
+ }
138
+ return out;
139
+ }
140
+ //# sourceMappingURL=onboarding-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding-state.js","sourceRoot":"","sources":["../../src/daemon/onboarding-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { RECALL_HOME, ensureRecallHome } from '../utils/paths.js';
4
+ export const PID_FILE = join(RECALL_HOME, 'daemon.pid');
5
+ export const PORT_FILE = join(RECALL_HOME, 'daemon.port');
6
+ export const LOG_FILE = join(RECALL_HOME, 'daemon.log');
7
+ export function writeDaemonInfo(info) {
8
+ ensureRecallHome();
9
+ writeFileSync(PID_FILE, JSON.stringify(info), { encoding: 'utf8' });
10
+ writeFileSync(PORT_FILE, String(info.port), { encoding: 'utf8' });
11
+ }
12
+ export function readDaemonInfo() {
13
+ if (!existsSync(PID_FILE))
14
+ return null;
15
+ try {
16
+ const data = JSON.parse(readFileSync(PID_FILE, 'utf8'));
17
+ if (typeof data.pid !== 'number' || typeof data.port !== 'number')
18
+ return null;
19
+ return data;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ export function clearDaemonInfo() {
26
+ for (const path of [PID_FILE, PORT_FILE]) {
27
+ if (existsSync(path)) {
28
+ try {
29
+ unlinkSync(path);
30
+ }
31
+ catch {
32
+ /* ignore */
33
+ }
34
+ }
35
+ }
36
+ }
37
+ export function isProcessAlive(pid) {
38
+ try {
39
+ // Signal 0 checks process existence without sending a signal.
40
+ process.kill(pid, 0);
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ export function getRunningDaemon() {
48
+ const info = readDaemonInfo();
49
+ if (!info)
50
+ return null;
51
+ if (!isProcessAlive(info.pid)) {
52
+ clearDaemonInfo();
53
+ return null;
54
+ }
55
+ return info;
56
+ }
57
+ //# sourceMappingURL=pidfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pidfile.js","sourceRoot":"","sources":["../../src/daemon/pidfile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAElE,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AACxD,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;AAC1D,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AAQxD,MAAM,UAAU,eAAe,CAAC,IAAgB;IAC9C,gBAAgB,EAAE,CAAC;IACnB,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACpE,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAe,CAAC;QACtE,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC/E,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;QACzC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC;gBACH,UAAU,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,8DAA8D;QAC9D,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,eAAe,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,48 @@
1
+ import { createServer } from 'node:net';
2
+ function tryPort(port) {
3
+ return new Promise((resolve) => {
4
+ const server = createServer();
5
+ server.once('error', () => resolve(false));
6
+ server.once('listening', () => {
7
+ server.close(() => resolve(true));
8
+ });
9
+ server.listen(port, '127.0.0.1');
10
+ });
11
+ }
12
+ /**
13
+ * Pick a free localhost port from the ephemeral range.
14
+ * We deliberately avoid well-known dev ports (3000, 3001, 4200, 5000, 5173, 8000, 8080, 8888, 9000)
15
+ * to prevent clobbering the user's other local services.
16
+ */
17
+ export async function pickFreePort() {
18
+ const blocked = new Set([3000, 3001, 4200, 5000, 5173, 8000, 8080, 8888, 9000]);
19
+ // Try a deterministic preferred port first so the port is predictable across restarts.
20
+ const preferred = 51370;
21
+ if (!blocked.has(preferred) && (await tryPort(preferred)))
22
+ return preferred;
23
+ // Then try random ephemeral ports.
24
+ for (let i = 0; i < 50; i++) {
25
+ const candidate = 49152 + Math.floor(Math.random() * (65535 - 49152));
26
+ if (blocked.has(candidate))
27
+ continue;
28
+ if (await tryPort(candidate))
29
+ return candidate;
30
+ }
31
+ // Last resort: ask the OS for any free port.
32
+ return new Promise((resolve, reject) => {
33
+ const server = createServer();
34
+ server.once('error', reject);
35
+ server.listen(0, '127.0.0.1', () => {
36
+ const addr = server.address();
37
+ if (addr && typeof addr === 'object') {
38
+ const port = addr.port;
39
+ server.close(() => resolve(port));
40
+ }
41
+ else {
42
+ server.close();
43
+ reject(new Error('could not determine a free port'));
44
+ }
45
+ });
46
+ });
47
+ }
48
+ //# sourceMappingURL=ports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.js","sourceRoot":"","sources":["../../src/daemon/ports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAEhF,uFAAuF;IACvF,MAAM,SAAS,GAAG,KAAK,CAAC;IACxB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAE5E,mCAAmC;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;QACtE,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,SAAS;QACrC,IAAI,MAAM,OAAO,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IACjD,CAAC;IAED,6CAA6C;IAC7C,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACjC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;gBACvB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;YACvD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}