@lbroth/rothunter 1.0.0-rc.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 (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/dist/adapters/llm.d.ts +68 -0
  4. package/dist/adapters/llm.d.ts.map +1 -0
  5. package/dist/adapters/llm.js +189 -0
  6. package/dist/adapters/llm.js.map +1 -0
  7. package/dist/config.d.ts +37 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +81 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/detector-registry.d.ts +32 -0
  12. package/dist/detector-registry.d.ts.map +1 -0
  13. package/dist/detector-registry.js +74 -0
  14. package/dist/detector-registry.js.map +1 -0
  15. package/dist/detectors/api-race.d.ts +6 -0
  16. package/dist/detectors/api-race.d.ts.map +1 -0
  17. package/dist/detectors/api-race.js +222 -0
  18. package/dist/detectors/api-race.js.map +1 -0
  19. package/dist/detectors/bad-config.d.ts +6 -0
  20. package/dist/detectors/bad-config.d.ts.map +1 -0
  21. package/dist/detectors/bad-config.js +529 -0
  22. package/dist/detectors/bad-config.js.map +1 -0
  23. package/dist/detectors/console-log-prod.d.ts +6 -0
  24. package/dist/detectors/console-log-prod.d.ts.map +1 -0
  25. package/dist/detectors/console-log-prod.js +72 -0
  26. package/dist/detectors/console-log-prod.js.map +1 -0
  27. package/dist/detectors/dead-api.d.ts +10 -0
  28. package/dist/detectors/dead-api.d.ts.map +1 -0
  29. package/dist/detectors/dead-api.js +115 -0
  30. package/dist/detectors/dead-api.js.map +1 -0
  31. package/dist/detectors/dead-export.d.ts +12 -0
  32. package/dist/detectors/dead-export.d.ts.map +1 -0
  33. package/dist/detectors/dead-export.js +140 -0
  34. package/dist/detectors/dead-export.js.map +1 -0
  35. package/dist/detectors/dead-handler.d.ts +12 -0
  36. package/dist/detectors/dead-handler.d.ts.map +1 -0
  37. package/dist/detectors/dead-handler.js +40 -0
  38. package/dist/detectors/dead-handler.js.map +1 -0
  39. package/dist/detectors/dead-module.d.ts +14 -0
  40. package/dist/detectors/dead-module.d.ts.map +1 -0
  41. package/dist/detectors/dead-module.js +50 -0
  42. package/dist/detectors/dead-module.js.map +1 -0
  43. package/dist/detectors/deep-nesting.d.ts +12 -0
  44. package/dist/detectors/deep-nesting.d.ts.map +1 -0
  45. package/dist/detectors/deep-nesting.js +133 -0
  46. package/dist/detectors/deep-nesting.js.map +1 -0
  47. package/dist/detectors/duplicate-function.d.ts +9 -0
  48. package/dist/detectors/duplicate-function.d.ts.map +1 -0
  49. package/dist/detectors/duplicate-function.js +199 -0
  50. package/dist/detectors/duplicate-function.js.map +1 -0
  51. package/dist/detectors/duplicate-type.d.ts +9 -0
  52. package/dist/detectors/duplicate-type.d.ts.map +1 -0
  53. package/dist/detectors/duplicate-type.js +166 -0
  54. package/dist/detectors/duplicate-type.js.map +1 -0
  55. package/dist/detectors/hot-hub-file.d.ts +11 -0
  56. package/dist/detectors/hot-hub-file.d.ts.map +1 -0
  57. package/dist/detectors/hot-hub-file.js +42 -0
  58. package/dist/detectors/hot-hub-file.js.map +1 -0
  59. package/dist/detectors/long-file.d.ts +12 -0
  60. package/dist/detectors/long-file.d.ts.map +1 -0
  61. package/dist/detectors/long-file.js +82 -0
  62. package/dist/detectors/long-file.js.map +1 -0
  63. package/dist/detectors/long-function.d.ts +12 -0
  64. package/dist/detectors/long-function.d.ts.map +1 -0
  65. package/dist/detectors/long-function.js +45 -0
  66. package/dist/detectors/long-function.js.map +1 -0
  67. package/dist/detectors/magic-numbers.d.ts +10 -0
  68. package/dist/detectors/magic-numbers.d.ts.map +1 -0
  69. package/dist/detectors/magic-numbers.js +332 -0
  70. package/dist/detectors/magic-numbers.js.map +1 -0
  71. package/dist/detectors/mutable-globals.d.ts +6 -0
  72. package/dist/detectors/mutable-globals.d.ts.map +1 -0
  73. package/dist/detectors/mutable-globals.js +95 -0
  74. package/dist/detectors/mutable-globals.js.map +1 -0
  75. package/dist/detectors/mutation.d.ts +11 -0
  76. package/dist/detectors/mutation.d.ts.map +1 -0
  77. package/dist/detectors/mutation.js +397 -0
  78. package/dist/detectors/mutation.js.map +1 -0
  79. package/dist/detectors/public-any.d.ts +6 -0
  80. package/dist/detectors/public-any.d.ts.map +1 -0
  81. package/dist/detectors/public-any.js +52 -0
  82. package/dist/detectors/public-any.js.map +1 -0
  83. package/dist/detectors/race-condition.d.ts +6 -0
  84. package/dist/detectors/race-condition.d.ts.map +1 -0
  85. package/dist/detectors/race-condition.js +608 -0
  86. package/dist/detectors/race-condition.js.map +1 -0
  87. package/dist/detectors/shared-db-write.d.ts +6 -0
  88. package/dist/detectors/shared-db-write.d.ts.map +1 -0
  89. package/dist/detectors/shared-db-write.js +656 -0
  90. package/dist/detectors/shared-db-write.js.map +1 -0
  91. package/dist/detectors/silent-catch.d.ts +6 -0
  92. package/dist/detectors/silent-catch.d.ts.map +1 -0
  93. package/dist/detectors/silent-catch.js +167 -0
  94. package/dist/detectors/silent-catch.js.map +1 -0
  95. package/dist/detectors/similar-functions.d.ts +15 -0
  96. package/dist/detectors/similar-functions.d.ts.map +1 -0
  97. package/dist/detectors/similar-functions.js +334 -0
  98. package/dist/detectors/similar-functions.js.map +1 -0
  99. package/dist/detectors/skip-tests.d.ts +6 -0
  100. package/dist/detectors/skip-tests.d.ts.map +1 -0
  101. package/dist/detectors/skip-tests.js +69 -0
  102. package/dist/detectors/skip-tests.js.map +1 -0
  103. package/dist/detectors/todo-comments.d.ts +29 -0
  104. package/dist/detectors/todo-comments.d.ts.map +1 -0
  105. package/dist/detectors/todo-comments.js +154 -0
  106. package/dist/detectors/todo-comments.js.map +1 -0
  107. package/dist/detectors/unused-deps.d.ts +8 -0
  108. package/dist/detectors/unused-deps.d.ts.map +1 -0
  109. package/dist/detectors/unused-deps.js +115 -0
  110. package/dist/detectors/unused-deps.js.map +1 -0
  111. package/dist/extraction/api-race-confirmer.d.ts +31 -0
  112. package/dist/extraction/api-race-confirmer.d.ts.map +1 -0
  113. package/dist/extraction/api-race-confirmer.js +110 -0
  114. package/dist/extraction/api-race-confirmer.js.map +1 -0
  115. package/dist/extraction/llm-confirmer.d.ts +25 -0
  116. package/dist/extraction/llm-confirmer.d.ts.map +1 -0
  117. package/dist/extraction/llm-confirmer.js +118 -0
  118. package/dist/extraction/llm-confirmer.js.map +1 -0
  119. package/dist/extraction/mutation-confirmer.d.ts +30 -0
  120. package/dist/extraction/mutation-confirmer.d.ts.map +1 -0
  121. package/dist/extraction/mutation-confirmer.js +73 -0
  122. package/dist/extraction/mutation-confirmer.js.map +1 -0
  123. package/dist/extraction/prompt-chunking.d.ts +37 -0
  124. package/dist/extraction/prompt-chunking.d.ts.map +1 -0
  125. package/dist/extraction/prompt-chunking.js +61 -0
  126. package/dist/extraction/prompt-chunking.js.map +1 -0
  127. package/dist/extraction/race-confirmer.d.ts +28 -0
  128. package/dist/extraction/race-confirmer.d.ts.map +1 -0
  129. package/dist/extraction/race-confirmer.js +68 -0
  130. package/dist/extraction/race-confirmer.js.map +1 -0
  131. package/dist/extraction/shared-db-write-confirmer.d.ts +31 -0
  132. package/dist/extraction/shared-db-write-confirmer.d.ts.map +1 -0
  133. package/dist/extraction/shared-db-write-confirmer.js +141 -0
  134. package/dist/extraction/shared-db-write-confirmer.js.map +1 -0
  135. package/dist/extraction/triage-confirmer.d.ts +59 -0
  136. package/dist/extraction/triage-confirmer.d.ts.map +1 -0
  137. package/dist/extraction/triage-confirmer.js +104 -0
  138. package/dist/extraction/triage-confirmer.js.map +1 -0
  139. package/dist/graph/cfg.d.ts +45 -0
  140. package/dist/graph/cfg.d.ts.map +1 -0
  141. package/dist/graph/cfg.js +198 -0
  142. package/dist/graph/cfg.js.map +1 -0
  143. package/dist/graph/decorator-entries.d.ts +2 -0
  144. package/dist/graph/decorator-entries.d.ts.map +1 -0
  145. package/dist/graph/decorator-entries.js +89 -0
  146. package/dist/graph/decorator-entries.js.map +1 -0
  147. package/dist/graph/entry-points.d.ts +12 -0
  148. package/dist/graph/entry-points.d.ts.map +1 -0
  149. package/dist/graph/entry-points.js +282 -0
  150. package/dist/graph/entry-points.js.map +1 -0
  151. package/dist/graph/handler-conventions.d.ts +2 -0
  152. package/dist/graph/handler-conventions.d.ts.map +1 -0
  153. package/dist/graph/handler-conventions.js +26 -0
  154. package/dist/graph/handler-conventions.js.map +1 -0
  155. package/dist/graph/iac-entries.d.ts +2 -0
  156. package/dist/graph/iac-entries.d.ts.map +1 -0
  157. package/dist/graph/iac-entries.js +123 -0
  158. package/dist/graph/iac-entries.js.map +1 -0
  159. package/dist/graph/import-graph.d.ts +48 -0
  160. package/dist/graph/import-graph.d.ts.map +1 -0
  161. package/dist/graph/import-graph.js +86 -0
  162. package/dist/graph/import-graph.js.map +1 -0
  163. package/dist/graph/monorepo-detect.d.ts +3 -0
  164. package/dist/graph/monorepo-detect.d.ts.map +1 -0
  165. package/dist/graph/monorepo-detect.js +166 -0
  166. package/dist/graph/monorepo-detect.js.map +1 -0
  167. package/dist/graph/tsconfig-paths.d.ts +23 -0
  168. package/dist/graph/tsconfig-paths.d.ts.map +1 -0
  169. package/dist/graph/tsconfig-paths.js +217 -0
  170. package/dist/graph/tsconfig-paths.js.map +1 -0
  171. package/dist/multi-workspace-scanner.d.ts +13 -0
  172. package/dist/multi-workspace-scanner.d.ts.map +1 -0
  173. package/dist/multi-workspace-scanner.js +130 -0
  174. package/dist/multi-workspace-scanner.js.map +1 -0
  175. package/dist/normalizers/type-normalizer.d.ts +16 -0
  176. package/dist/normalizers/type-normalizer.d.ts.map +1 -0
  177. package/dist/normalizers/type-normalizer.js +189 -0
  178. package/dist/normalizers/type-normalizer.js.map +1 -0
  179. package/dist/parsers/typescript-parser.d.ts +57 -0
  180. package/dist/parsers/typescript-parser.d.ts.map +1 -0
  181. package/dist/parsers/typescript-parser.js +502 -0
  182. package/dist/parsers/typescript-parser.js.map +1 -0
  183. package/dist/reporter/json-reporter.d.ts +12 -0
  184. package/dist/reporter/json-reporter.d.ts.map +1 -0
  185. package/dist/reporter/json-reporter.js +28 -0
  186. package/dist/reporter/json-reporter.js.map +1 -0
  187. package/dist/reporter/markdown-reporter.d.ts +11 -0
  188. package/dist/reporter/markdown-reporter.d.ts.map +1 -0
  189. package/dist/reporter/markdown-reporter.js +77 -0
  190. package/dist/reporter/markdown-reporter.js.map +1 -0
  191. package/dist/rothunter.d.ts +125 -0
  192. package/dist/rothunter.d.ts.map +1 -0
  193. package/dist/rothunter.js +1038 -0
  194. package/dist/rothunter.js.map +1 -0
  195. package/dist/server/false-positives.d.ts +34 -0
  196. package/dist/server/false-positives.d.ts.map +1 -0
  197. package/dist/server/false-positives.js +85 -0
  198. package/dist/server/false-positives.js.map +1 -0
  199. package/dist/server/index.d.ts +2 -0
  200. package/dist/server/index.d.ts.map +1 -0
  201. package/dist/server/index.js +1529 -0
  202. package/dist/server/index.js.map +1 -0
  203. package/dist/server/marked-to-fix.d.ts +16 -0
  204. package/dist/server/marked-to-fix.d.ts.map +1 -0
  205. package/dist/server/marked-to-fix.js +36 -0
  206. package/dist/server/marked-to-fix.js.map +1 -0
  207. package/dist/server/scan-store.d.ts +147 -0
  208. package/dist/server/scan-store.d.ts.map +1 -0
  209. package/dist/server/scan-store.js +291 -0
  210. package/dist/server/scan-store.js.map +1 -0
  211. package/dist/server/settings-store.d.ts +28 -0
  212. package/dist/server/settings-store.d.ts.map +1 -0
  213. package/dist/server/settings-store.js +46 -0
  214. package/dist/server/settings-store.js.map +1 -0
  215. package/dist/server/workspace-store.d.ts +39 -0
  216. package/dist/server/workspace-store.d.ts.map +1 -0
  217. package/dist/server/workspace-store.js +108 -0
  218. package/dist/server/workspace-store.js.map +1 -0
  219. package/dist/types/detector-input.d.ts +37 -0
  220. package/dist/types/detector-input.d.ts.map +1 -0
  221. package/dist/types/detector-input.js +2 -0
  222. package/dist/types/detector-input.js.map +1 -0
  223. package/dist/types.d.ts +110 -0
  224. package/dist/types.d.ts.map +1 -0
  225. package/dist/types.js +2 -0
  226. package/dist/types.js.map +1 -0
  227. package/dist/utils/clustering.d.ts +14 -0
  228. package/dist/utils/clustering.d.ts.map +1 -0
  229. package/dist/utils/clustering.js +56 -0
  230. package/dist/utils/clustering.js.map +1 -0
  231. package/dist/utils/gitignore.d.ts +32 -0
  232. package/dist/utils/gitignore.d.ts.map +1 -0
  233. package/dist/utils/gitignore.js +122 -0
  234. package/dist/utils/gitignore.js.map +1 -0
  235. package/dist/utils/hash.d.ts +11 -0
  236. package/dist/utils/hash.d.ts.map +1 -0
  237. package/dist/utils/hash.js +14 -0
  238. package/dist/utils/hash.js.map +1 -0
  239. package/dist/utils/ignore-annotation.d.ts +28 -0
  240. package/dist/utils/ignore-annotation.d.ts.map +1 -0
  241. package/dist/utils/ignore-annotation.js +46 -0
  242. package/dist/utils/ignore-annotation.js.map +1 -0
  243. package/dist/utils/llm-json.d.ts +2 -0
  244. package/dist/utils/llm-json.d.ts.map +1 -0
  245. package/dist/utils/llm-json.js +53 -0
  246. package/dist/utils/llm-json.js.map +1 -0
  247. package/dist/utils/logger.d.ts +3 -0
  248. package/dist/utils/logger.d.ts.map +1 -0
  249. package/dist/utils/logger.js +4 -0
  250. package/dist/utils/logger.js.map +1 -0
  251. package/dist/utils/project-conventions.d.ts +2 -0
  252. package/dist/utils/project-conventions.d.ts.map +1 -0
  253. package/dist/utils/project-conventions.js +108 -0
  254. package/dist/utils/project-conventions.js.map +1 -0
  255. package/dist/utils/regex.d.ts +9 -0
  256. package/dist/utils/regex.d.ts.map +1 -0
  257. package/dist/utils/regex.js +11 -0
  258. package/dist/utils/regex.js.map +1 -0
  259. package/dist/utils/snippet.d.ts +20 -0
  260. package/dist/utils/snippet.d.ts.map +1 -0
  261. package/dist/utils/snippet.js +28 -0
  262. package/dist/utils/snippet.js.map +1 -0
  263. package/dist/utils/source-reader.d.ts +19 -0
  264. package/dist/utils/source-reader.d.ts.map +1 -0
  265. package/dist/utils/source-reader.js +32 -0
  266. package/dist/utils/source-reader.js.map +1 -0
  267. package/logo.png +0 -0
  268. package/package.json +92 -0
  269. package/scripts/start-llm.mjs +161 -0
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@lbroth/rothunter",
3
+ "version": "1.0.0-rc.1",
4
+ "description": "Self-hosted code-hygiene engine for TypeScript / JavaScript codebases. Deterministic detectors + local LLM verdicts + dashboard.",
5
+ "license": "MIT",
6
+ "author": "lBroth",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/lBroth/rothunter.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/lBroth/rothunter/issues"
13
+ },
14
+ "homepage": "https://github.com/lBroth/rothunter#readme",
15
+ "keywords": [
16
+ "static-analysis",
17
+ "code-quality",
18
+ "typescript",
19
+ "race-condition",
20
+ "dead-code",
21
+ "duplicate-detection",
22
+ "monorepo",
23
+ "llm",
24
+ "self-hosted"
25
+ ],
26
+ "type": "module",
27
+ "main": "dist/rothunter.js",
28
+ "types": "dist/rothunter.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/rothunter.d.ts",
32
+ "default": "./dist/rothunter.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "scripts/start-llm.mjs",
38
+ "README.md",
39
+ "LICENSE",
40
+ "logo.png"
41
+ ],
42
+ "engines": {
43
+ "node": ">=24"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "build:ui": "cd src/ui && npm install && npm run build",
48
+ "prepublishOnly": "npm run build",
49
+ "setup": "npm install && cd src/ui && npm install",
50
+ "server": "tsx watch src/server/index.ts",
51
+ "server:lan": "ROTHUNTER_HOST=0.0.0.0 tsx watch src/server/index.ts",
52
+ "ui": "cd src/ui && npm run dev",
53
+ "llm": "node scripts/start-llm.mjs",
54
+ "dev": "concurrently -k -n SERVER,UI -c blue,magenta \"npm:server\" \"npm:ui\"",
55
+ "dev:full": "concurrently -k -n LLM,SERVER,UI -c green,blue,magenta \"npm:llm\" \"npm:server\" \"npm:ui\"",
56
+ "dev:lan": "concurrently -k -n SERVER,UI -c blue,magenta \"npm:server:lan\" \"npm:ui\"",
57
+ "dev:full:lan": "concurrently -k -n LLM,SERVER,UI -c green,blue,magenta \"npm:llm\" \"npm:server:lan\" \"npm:ui\"",
58
+ "docker": "docker compose -f src/docker/docker-compose.yml up --build",
59
+ "lint": "eslint .",
60
+ "lint:fix": "eslint . --fix",
61
+ "format": "prettier --write .",
62
+ "format:check": "prettier --check .",
63
+ "depcheck": "depcheck",
64
+ "test": "NODE_OPTIONS='--experimental-vm-modules' jest"
65
+ },
66
+ "dependencies": {
67
+ "@fastify/static": "^9.1.3",
68
+ "fastify": "^5.8.5",
69
+ "ignore": "^7.0.5",
70
+ "pino": "^10.3.1",
71
+ "ts-morph": "^28.0.0",
72
+ "zod": "^4.4.3"
73
+ },
74
+ "devDependencies": {
75
+ "@eslint/js": "^10.0.1",
76
+ "@jest/globals": "^30.4.1",
77
+ "@resvg/resvg-js": "^2.6.2",
78
+ "@types/jest": "^30.0.0",
79
+ "@types/node": "^24.0.0",
80
+ "concurrently": "^9.2.1",
81
+ "depcheck": "^1.4.7",
82
+ "eslint": "^10.4.0",
83
+ "eslint-plugin-react-hooks": "^7.1.1",
84
+ "globals": "^17.6.0",
85
+ "jest": "^30.4.2",
86
+ "prettier": "^3.8.3",
87
+ "ts-jest": "^29.4.10",
88
+ "tsx": "^4.22.3",
89
+ "typescript": "^5.9.3",
90
+ "typescript-eslint": "^8.59.4"
91
+ }
92
+ }
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+ // Launch the LLM sidecar for the dev loop.
3
+ //
4
+ // Strategy — auto-detect the fastest backend available on this host:
5
+ //
6
+ // 1. `llama-server` on PATH → run native llama.cpp (uses Metal on
7
+ // macOS, CUDA / Vulkan on Linux when the binary was built with
8
+ // GPU support).
9
+ // 2. Docker Desktop available → fall back to the docker-compose
10
+ // `rothunter-llm` service. Slower on macOS (no Metal in the
11
+ // Linux VM) but works on any platform with Docker.
12
+ //
13
+ // Set ROTHUNTER_LLM_BACKEND=llamacpp|docker to force a specific
14
+ // backend; otherwise we pick the first available from the list above.
15
+ //
16
+ // The script blocks until its child exits — `npm run dev:full` runs it
17
+ // alongside the server + UI via `concurrently -k` so killing one
18
+ // tears the whole stack down.
19
+
20
+ import { spawn, spawnSync } from 'node:child_process';
21
+ import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
22
+ import { homedir } from 'node:os';
23
+ import { resolve, join } from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ const REPO_ROOT = resolve(fileURLToPath(import.meta.url), '..', '..');
27
+
28
+ /**
29
+ * Reject env values that would let a hostile environment inject extra
30
+ * llama-server flags via the `spawn(..., [args])` argv. `spawn` does
31
+ * not invoke a shell, so true shell injection is already blocked, but
32
+ * a value starting with `-` would still be interpreted as a flag
33
+ * (e.g. setting `ROTHUNTER_LLM_PORT='--alias --rpc 0.0.0.0:5000'`).
34
+ * Tight allow-lists per env: model repo / file paths, integer port,
35
+ * IPv4-ish host.
36
+ */
37
+ function requireEnv(name, fallback, pattern) {
38
+ const v = process.env[name] ?? fallback;
39
+ if (!pattern.test(v)) {
40
+ console.error(`[llm] refusing to launch — env ${name}=${JSON.stringify(v)} does not match ${pattern}`);
41
+ process.exit(1);
42
+ }
43
+ return v;
44
+ }
45
+ const MODEL_LLAMACPP_REPO = requireEnv(
46
+ 'ROTHUNTER_LLM_MODEL',
47
+ 'bartowski/Qwen2.5-Coder-14B-Instruct-GGUF',
48
+ /^[A-Za-z0-9._\-/]+$/,
49
+ );
50
+ const MODEL_LLAMACPP_FILE = requireEnv(
51
+ 'ROTHUNTER_LLM_MODEL_FILE',
52
+ 'Qwen2.5-Coder-14B-Instruct-Q4_K_M.gguf',
53
+ /^[A-Za-z0-9._\-/]+$/,
54
+ );
55
+ const PORT = requireEnv('ROTHUNTER_LLM_PORT', '8080', /^\d{1,5}$/);
56
+ // Loopback by default — the llama-server endpoint has no auth, so
57
+ // `--host 0.0.0.0` would let anyone on the LAN use the GPU and read
58
+ // the prompts we send. Override with `ROTHUNTER_LLM_HOST=0.0.0.0`
59
+ // only when you intentionally want LAN access (and ideally only
60
+ // behind a reverse proxy / VPN).
61
+ const HOST = requireEnv('ROTHUNTER_LLM_HOST', '127.0.0.1', /^[0-9.:a-fA-F]+$/);
62
+
63
+ function has(cmd) {
64
+ return spawnSync('which', [cmd], { stdio: 'ignore' }).status === 0;
65
+ }
66
+
67
+ function hasDocker() {
68
+ return spawnSync('docker', ['version'], { stdio: 'ignore' }).status === 0;
69
+ }
70
+
71
+ function pickBackend() {
72
+ const forced = process.env.ROTHUNTER_LLM_BACKEND;
73
+ if (forced) return forced;
74
+ if (has('llama-server')) return 'llamacpp';
75
+ if (hasDocker()) return 'docker';
76
+ return null;
77
+ }
78
+
79
+ /**
80
+ * Marker file the server reads to know which model + port this script
81
+ * launched. Lets the server pick the right model id without the
82
+ * operator having to mirror env vars between processes.
83
+ */
84
+ const MARKER_DIR = join(homedir(), '.rothunter');
85
+ const MARKER_PATH = join(MARKER_DIR, 'llm-active.json');
86
+
87
+ function writeMarker(payload) {
88
+ try {
89
+ mkdirSync(MARKER_DIR, { recursive: true });
90
+ writeFileSync(MARKER_PATH, JSON.stringify(payload, null, 2));
91
+ } catch (err) {
92
+ console.warn(`[llm] failed to write marker file: ${err.message}`);
93
+ }
94
+ }
95
+
96
+ function clearMarker() {
97
+ try { unlinkSync(MARKER_PATH); } catch { /* already gone */ }
98
+ }
99
+
100
+ function run(cmd, args, marker) {
101
+ console.log(`[llm] launching: ${cmd} ${args.join(' ')}`);
102
+ if (marker) writeMarker(marker);
103
+ const child = spawn(cmd, args, { stdio: 'inherit' });
104
+ child.on('exit', (code) => {
105
+ clearMarker();
106
+ process.exit(code ?? 0);
107
+ });
108
+ const forward = (sig) => () => {
109
+ clearMarker();
110
+ child.kill(sig);
111
+ };
112
+ process.on('SIGINT', forward('SIGINT'));
113
+ process.on('SIGTERM', forward('SIGTERM'));
114
+ }
115
+
116
+ const backend = pickBackend();
117
+ switch (backend) {
118
+ case 'llamacpp':
119
+ console.log(`[llm] native llama-server — ${MODEL_LLAMACPP_REPO}`);
120
+ run(
121
+ 'llama-server',
122
+ [
123
+ '--hf-repo', MODEL_LLAMACPP_REPO,
124
+ '--hf-file', MODEL_LLAMACPP_FILE,
125
+ '--port', PORT,
126
+ '--host', HOST,
127
+ '--jinja',
128
+ '-c', '8192',
129
+ '-n', '256',
130
+ ],
131
+ { backend: 'llamacpp', model: MODEL_LLAMACPP_REPO, port: PORT },
132
+ );
133
+ break;
134
+ case 'docker': {
135
+ const compose = resolve(REPO_ROOT, 'src/docker/docker-compose.yml');
136
+ if (!existsSync(compose)) {
137
+ console.error(`[llm] docker-compose file not found at ${compose}`);
138
+ process.exit(1);
139
+ }
140
+ console.log('[llm] docker fallback — slower on macOS (no Metal in the VM)');
141
+ run(
142
+ 'docker',
143
+ ['compose', '-f', compose, 'up', 'rothunter-llm'],
144
+ { backend: 'docker', model: MODEL_LLAMACPP_REPO, port: PORT },
145
+ );
146
+ break;
147
+ }
148
+ default:
149
+ console.error(
150
+ [
151
+ '[llm] no LLM backend available. Install ONE of:',
152
+ ' • brew install llama.cpp (native llama-server, recommended)',
153
+ ' • Docker Desktop (docker-compose sidecar fallback)',
154
+ '',
155
+ 'Or set ROTHUNTER_LLM_BASE_URL to an existing OpenAI-compatible',
156
+ 'endpoint (vLLM, OpenRouter, LM Studio, …) and start the server /',
157
+ 'UI directly with `npm run server` and `npm run ui`.',
158
+ ].join('\n'),
159
+ );
160
+ process.exit(1);
161
+ }