@oh-my-pi/pi-coding-agent 0.1.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 (337) hide show
  1. package/CHANGELOG.md +1629 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/config-usage.md +113 -0
  5. package/docs/custom-tools.md +541 -0
  6. package/docs/extension-loading.md +1004 -0
  7. package/docs/hooks.md +867 -0
  8. package/docs/rpc.md +1040 -0
  9. package/docs/sdk.md +994 -0
  10. package/docs/session-tree-plan.md +441 -0
  11. package/docs/session.md +240 -0
  12. package/docs/skills.md +290 -0
  13. package/docs/theme.md +670 -0
  14. package/docs/tree.md +197 -0
  15. package/docs/tui.md +341 -0
  16. package/examples/README.md +21 -0
  17. package/examples/custom-tools/README.md +124 -0
  18. package/examples/custom-tools/hello/index.ts +20 -0
  19. package/examples/custom-tools/question/index.ts +84 -0
  20. package/examples/custom-tools/subagent/README.md +172 -0
  21. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  22. package/examples/custom-tools/subagent/agents/scout.md +50 -0
  23. package/examples/custom-tools/subagent/agents/worker.md +24 -0
  24. package/examples/custom-tools/subagent/agents.ts +156 -0
  25. package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
  26. package/examples/custom-tools/subagent/commands/implement.md +10 -0
  27. package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
  28. package/examples/custom-tools/subagent/index.ts +1002 -0
  29. package/examples/custom-tools/todo/index.ts +212 -0
  30. package/examples/hooks/README.md +56 -0
  31. package/examples/hooks/auto-commit-on-exit.ts +49 -0
  32. package/examples/hooks/confirm-destructive.ts +59 -0
  33. package/examples/hooks/custom-compaction.ts +116 -0
  34. package/examples/hooks/dirty-repo-guard.ts +52 -0
  35. package/examples/hooks/file-trigger.ts +41 -0
  36. package/examples/hooks/git-checkpoint.ts +53 -0
  37. package/examples/hooks/handoff.ts +150 -0
  38. package/examples/hooks/permission-gate.ts +34 -0
  39. package/examples/hooks/protected-paths.ts +30 -0
  40. package/examples/hooks/qna.ts +119 -0
  41. package/examples/hooks/snake.ts +343 -0
  42. package/examples/hooks/status-line.ts +40 -0
  43. package/examples/sdk/01-minimal.ts +22 -0
  44. package/examples/sdk/02-custom-model.ts +49 -0
  45. package/examples/sdk/03-custom-prompt.ts +44 -0
  46. package/examples/sdk/04-skills.ts +44 -0
  47. package/examples/sdk/05-tools.ts +90 -0
  48. package/examples/sdk/06-hooks.ts +61 -0
  49. package/examples/sdk/07-context-files.ts +36 -0
  50. package/examples/sdk/08-slash-commands.ts +42 -0
  51. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  52. package/examples/sdk/10-settings.ts +38 -0
  53. package/examples/sdk/11-sessions.ts +48 -0
  54. package/examples/sdk/12-full-control.ts +95 -0
  55. package/examples/sdk/README.md +154 -0
  56. package/package.json +89 -0
  57. package/src/bun-imports.d.ts +16 -0
  58. package/src/capability/context-file.ts +40 -0
  59. package/src/capability/extension.ts +48 -0
  60. package/src/capability/hook.ts +40 -0
  61. package/src/capability/index.ts +616 -0
  62. package/src/capability/instruction.ts +37 -0
  63. package/src/capability/mcp.ts +52 -0
  64. package/src/capability/prompt.ts +35 -0
  65. package/src/capability/rule.ts +56 -0
  66. package/src/capability/settings.ts +35 -0
  67. package/src/capability/skill.ts +49 -0
  68. package/src/capability/slash-command.ts +40 -0
  69. package/src/capability/system-prompt.ts +35 -0
  70. package/src/capability/tool.ts +38 -0
  71. package/src/capability/types.ts +166 -0
  72. package/src/cli/args.ts +259 -0
  73. package/src/cli/file-processor.ts +121 -0
  74. package/src/cli/list-models.ts +104 -0
  75. package/src/cli/plugin-cli.ts +661 -0
  76. package/src/cli/session-picker.ts +41 -0
  77. package/src/cli/update-cli.ts +274 -0
  78. package/src/cli.ts +10 -0
  79. package/src/config.ts +391 -0
  80. package/src/core/agent-session.ts +2178 -0
  81. package/src/core/auth-storage.ts +258 -0
  82. package/src/core/bash-executor.ts +197 -0
  83. package/src/core/compaction/branch-summarization.ts +315 -0
  84. package/src/core/compaction/compaction.ts +664 -0
  85. package/src/core/compaction/index.ts +7 -0
  86. package/src/core/compaction/utils.ts +153 -0
  87. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  88. package/src/core/custom-commands/index.ts +15 -0
  89. package/src/core/custom-commands/loader.ts +226 -0
  90. package/src/core/custom-commands/types.ts +112 -0
  91. package/src/core/custom-tools/index.ts +22 -0
  92. package/src/core/custom-tools/loader.ts +248 -0
  93. package/src/core/custom-tools/types.ts +185 -0
  94. package/src/core/custom-tools/wrapper.ts +29 -0
  95. package/src/core/exec.ts +139 -0
  96. package/src/core/export-html/index.ts +159 -0
  97. package/src/core/export-html/template.css +774 -0
  98. package/src/core/export-html/template.generated.ts +2 -0
  99. package/src/core/export-html/template.html +45 -0
  100. package/src/core/export-html/template.js +1185 -0
  101. package/src/core/export-html/template.macro.ts +24 -0
  102. package/src/core/file-mentions.ts +54 -0
  103. package/src/core/hooks/index.ts +16 -0
  104. package/src/core/hooks/loader.ts +288 -0
  105. package/src/core/hooks/runner.ts +434 -0
  106. package/src/core/hooks/tool-wrapper.ts +98 -0
  107. package/src/core/hooks/types.ts +770 -0
  108. package/src/core/index.ts +53 -0
  109. package/src/core/logger.ts +112 -0
  110. package/src/core/mcp/client.ts +185 -0
  111. package/src/core/mcp/config.ts +248 -0
  112. package/src/core/mcp/index.ts +45 -0
  113. package/src/core/mcp/loader.ts +99 -0
  114. package/src/core/mcp/manager.ts +235 -0
  115. package/src/core/mcp/tool-bridge.ts +156 -0
  116. package/src/core/mcp/transports/http.ts +316 -0
  117. package/src/core/mcp/transports/index.ts +6 -0
  118. package/src/core/mcp/transports/stdio.ts +252 -0
  119. package/src/core/mcp/types.ts +228 -0
  120. package/src/core/messages.ts +211 -0
  121. package/src/core/model-registry.ts +334 -0
  122. package/src/core/model-resolver.ts +494 -0
  123. package/src/core/plugins/doctor.ts +67 -0
  124. package/src/core/plugins/index.ts +38 -0
  125. package/src/core/plugins/installer.ts +189 -0
  126. package/src/core/plugins/loader.ts +339 -0
  127. package/src/core/plugins/manager.ts +672 -0
  128. package/src/core/plugins/parser.ts +105 -0
  129. package/src/core/plugins/paths.ts +37 -0
  130. package/src/core/plugins/types.ts +190 -0
  131. package/src/core/sdk.ts +900 -0
  132. package/src/core/session-manager.ts +1837 -0
  133. package/src/core/settings-manager.ts +860 -0
  134. package/src/core/skills.ts +352 -0
  135. package/src/core/slash-commands.ts +132 -0
  136. package/src/core/system-prompt.ts +442 -0
  137. package/src/core/timings.ts +25 -0
  138. package/src/core/title-generator.ts +110 -0
  139. package/src/core/tools/ask.ts +193 -0
  140. package/src/core/tools/bash-interceptor.ts +120 -0
  141. package/src/core/tools/bash.ts +91 -0
  142. package/src/core/tools/context.ts +32 -0
  143. package/src/core/tools/edit-diff.ts +487 -0
  144. package/src/core/tools/edit.ts +140 -0
  145. package/src/core/tools/exa/company.ts +59 -0
  146. package/src/core/tools/exa/index.ts +63 -0
  147. package/src/core/tools/exa/linkedin.ts +59 -0
  148. package/src/core/tools/exa/mcp-client.ts +368 -0
  149. package/src/core/tools/exa/render.ts +200 -0
  150. package/src/core/tools/exa/researcher.ts +90 -0
  151. package/src/core/tools/exa/search.ts +338 -0
  152. package/src/core/tools/exa/types.ts +167 -0
  153. package/src/core/tools/exa/websets.ts +248 -0
  154. package/src/core/tools/find.ts +244 -0
  155. package/src/core/tools/grep.ts +584 -0
  156. package/src/core/tools/index.ts +283 -0
  157. package/src/core/tools/ls.ts +142 -0
  158. package/src/core/tools/lsp/client.ts +767 -0
  159. package/src/core/tools/lsp/clients/biome-client.ts +207 -0
  160. package/src/core/tools/lsp/clients/index.ts +49 -0
  161. package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
  162. package/src/core/tools/lsp/config.ts +845 -0
  163. package/src/core/tools/lsp/edits.ts +110 -0
  164. package/src/core/tools/lsp/index.ts +1364 -0
  165. package/src/core/tools/lsp/render.ts +560 -0
  166. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  167. package/src/core/tools/lsp/types.ts +495 -0
  168. package/src/core/tools/lsp/utils.ts +526 -0
  169. package/src/core/tools/notebook.ts +182 -0
  170. package/src/core/tools/output.ts +198 -0
  171. package/src/core/tools/path-utils.ts +61 -0
  172. package/src/core/tools/read.ts +507 -0
  173. package/src/core/tools/renderers.ts +820 -0
  174. package/src/core/tools/review.ts +275 -0
  175. package/src/core/tools/rulebook.ts +124 -0
  176. package/src/core/tools/task/agents.ts +158 -0
  177. package/src/core/tools/task/artifacts.ts +114 -0
  178. package/src/core/tools/task/commands.ts +157 -0
  179. package/src/core/tools/task/discovery.ts +217 -0
  180. package/src/core/tools/task/executor.ts +531 -0
  181. package/src/core/tools/task/index.ts +548 -0
  182. package/src/core/tools/task/model-resolver.ts +176 -0
  183. package/src/core/tools/task/parallel.ts +38 -0
  184. package/src/core/tools/task/render.ts +502 -0
  185. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  186. package/src/core/tools/task/types.ts +142 -0
  187. package/src/core/tools/truncate.ts +265 -0
  188. package/src/core/tools/web-fetch.ts +2511 -0
  189. package/src/core/tools/web-search/auth.ts +199 -0
  190. package/src/core/tools/web-search/index.ts +583 -0
  191. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  192. package/src/core/tools/web-search/providers/exa.ts +196 -0
  193. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  194. package/src/core/tools/web-search/render.ts +372 -0
  195. package/src/core/tools/web-search/types.ts +180 -0
  196. package/src/core/tools/write.ts +63 -0
  197. package/src/core/ttsr.ts +211 -0
  198. package/src/core/utils.ts +187 -0
  199. package/src/discovery/agents-md.ts +75 -0
  200. package/src/discovery/builtin.ts +647 -0
  201. package/src/discovery/claude.ts +623 -0
  202. package/src/discovery/cline.ts +104 -0
  203. package/src/discovery/codex.ts +571 -0
  204. package/src/discovery/cursor.ts +266 -0
  205. package/src/discovery/gemini.ts +368 -0
  206. package/src/discovery/github.ts +120 -0
  207. package/src/discovery/helpers.test.ts +127 -0
  208. package/src/discovery/helpers.ts +249 -0
  209. package/src/discovery/index.ts +84 -0
  210. package/src/discovery/mcp-json.ts +127 -0
  211. package/src/discovery/vscode.ts +99 -0
  212. package/src/discovery/windsurf.ts +219 -0
  213. package/src/index.ts +192 -0
  214. package/src/main.ts +507 -0
  215. package/src/migrations.ts +156 -0
  216. package/src/modes/cleanup.ts +23 -0
  217. package/src/modes/index.ts +48 -0
  218. package/src/modes/interactive/components/armin.ts +382 -0
  219. package/src/modes/interactive/components/assistant-message.ts +86 -0
  220. package/src/modes/interactive/components/bash-execution.ts +199 -0
  221. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  222. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  223. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  224. package/src/modes/interactive/components/custom-editor.ts +122 -0
  225. package/src/modes/interactive/components/diff.ts +147 -0
  226. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  227. package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
  228. package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
  229. package/src/modes/interactive/components/extensions/index.ts +9 -0
  230. package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
  231. package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
  232. package/src/modes/interactive/components/extensions/types.ts +191 -0
  233. package/src/modes/interactive/components/hook-editor.ts +117 -0
  234. package/src/modes/interactive/components/hook-input.ts +64 -0
  235. package/src/modes/interactive/components/hook-message.ts +96 -0
  236. package/src/modes/interactive/components/hook-selector.ts +91 -0
  237. package/src/modes/interactive/components/model-selector.ts +560 -0
  238. package/src/modes/interactive/components/oauth-selector.ts +136 -0
  239. package/src/modes/interactive/components/plugin-settings.ts +481 -0
  240. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  241. package/src/modes/interactive/components/session-selector.ts +220 -0
  242. package/src/modes/interactive/components/settings-defs.ts +597 -0
  243. package/src/modes/interactive/components/settings-selector.ts +545 -0
  244. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  245. package/src/modes/interactive/components/status-line/index.ts +4 -0
  246. package/src/modes/interactive/components/status-line/presets.ts +94 -0
  247. package/src/modes/interactive/components/status-line/segments.ts +350 -0
  248. package/src/modes/interactive/components/status-line/separators.ts +55 -0
  249. package/src/modes/interactive/components/status-line/types.ts +81 -0
  250. package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
  251. package/src/modes/interactive/components/status-line.ts +384 -0
  252. package/src/modes/interactive/components/theme-selector.ts +62 -0
  253. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  254. package/src/modes/interactive/components/tool-execution.ts +946 -0
  255. package/src/modes/interactive/components/tree-selector.ts +877 -0
  256. package/src/modes/interactive/components/ttsr-notification.ts +82 -0
  257. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  258. package/src/modes/interactive/components/user-message.ts +18 -0
  259. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  260. package/src/modes/interactive/components/welcome.ts +228 -0
  261. package/src/modes/interactive/interactive-mode.ts +2669 -0
  262. package/src/modes/interactive/theme/dark.json +102 -0
  263. package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
  264. package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
  265. package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
  266. package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
  267. package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
  268. package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
  269. package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
  270. package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
  271. package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
  272. package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
  273. package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
  274. package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
  275. package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
  276. package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
  277. package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
  278. package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
  279. package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
  280. package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
  281. package/src/modes/interactive/theme/defaults/index.ts +67 -0
  282. package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
  283. package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
  284. package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
  285. package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
  286. package/src/modes/interactive/theme/defaults/light-github.json +114 -0
  287. package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
  288. package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
  289. package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
  290. package/src/modes/interactive/theme/defaults/light-one.json +105 -0
  291. package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
  292. package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
  293. package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
  294. package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
  295. package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
  296. package/src/modes/interactive/theme/light.json +99 -0
  297. package/src/modes/interactive/theme/theme-schema.json +424 -0
  298. package/src/modes/interactive/theme/theme.ts +2211 -0
  299. package/src/modes/print-mode.ts +163 -0
  300. package/src/modes/rpc/rpc-client.ts +527 -0
  301. package/src/modes/rpc/rpc-mode.ts +494 -0
  302. package/src/modes/rpc/rpc-types.ts +203 -0
  303. package/src/prompts/architect-plan.md +10 -0
  304. package/src/prompts/branch-summary-preamble.md +3 -0
  305. package/src/prompts/branch-summary.md +28 -0
  306. package/src/prompts/browser.md +71 -0
  307. package/src/prompts/compaction-summary.md +34 -0
  308. package/src/prompts/compaction-turn-prefix.md +16 -0
  309. package/src/prompts/compaction-update-summary.md +41 -0
  310. package/src/prompts/explore.md +82 -0
  311. package/src/prompts/implement-with-critic.md +11 -0
  312. package/src/prompts/implement.md +11 -0
  313. package/src/prompts/init.md +30 -0
  314. package/src/prompts/plan.md +54 -0
  315. package/src/prompts/reviewer.md +81 -0
  316. package/src/prompts/summarization-system.md +3 -0
  317. package/src/prompts/system-prompt.md +27 -0
  318. package/src/prompts/task.md +56 -0
  319. package/src/prompts/title-system.md +8 -0
  320. package/src/prompts/tools/ask.md +24 -0
  321. package/src/prompts/tools/bash.md +23 -0
  322. package/src/prompts/tools/edit.md +9 -0
  323. package/src/prompts/tools/find.md +6 -0
  324. package/src/prompts/tools/grep.md +12 -0
  325. package/src/prompts/tools/lsp.md +14 -0
  326. package/src/prompts/tools/output.md +23 -0
  327. package/src/prompts/tools/read.md +25 -0
  328. package/src/prompts/tools/web-fetch.md +8 -0
  329. package/src/prompts/tools/web-search.md +10 -0
  330. package/src/prompts/tools/write.md +10 -0
  331. package/src/utils/changelog.ts +99 -0
  332. package/src/utils/clipboard.ts +265 -0
  333. package/src/utils/fuzzy.ts +108 -0
  334. package/src/utils/mime.ts +30 -0
  335. package/src/utils/shell-snapshot.ts +218 -0
  336. package/src/utils/shell.ts +364 -0
  337. package/src/utils/tools-manager.ts +265 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Biome CLI-based linter client.
3
+ * Uses Biome's CLI with JSON output instead of LSP (which has stale diagnostics issues).
4
+ */
5
+
6
+ import path from "node:path";
7
+ import type { Diagnostic, DiagnosticSeverity, LinterClient, ServerConfig } from "../types";
8
+
9
+ // =============================================================================
10
+ // Biome JSON Output Types
11
+ // =============================================================================
12
+
13
+ interface BiomeJsonOutput {
14
+ diagnostics: BiomeDiagnostic[];
15
+ }
16
+
17
+ interface BiomeDiagnostic {
18
+ category: string; // e.g., "lint/correctness/noUnusedVariables"
19
+ severity: "error" | "warning" | "info" | "hint";
20
+ description: string;
21
+ location?: {
22
+ path?: { file: string };
23
+ span?: [number, number]; // [startOffset, endOffset] in bytes
24
+ sourceCode?: string;
25
+ };
26
+ }
27
+
28
+ // =============================================================================
29
+ // Helpers
30
+ // =============================================================================
31
+
32
+ /**
33
+ * Convert byte offset to line:column using source code.
34
+ */
35
+ function offsetToPosition(source: string, offset: number): { line: number; column: number } {
36
+ let line = 1;
37
+ let column = 1;
38
+ let byteIndex = 0;
39
+
40
+ for (const ch of source) {
41
+ const byteLen = Buffer.byteLength(ch);
42
+ if (byteIndex + byteLen > offset) {
43
+ break;
44
+ }
45
+ if (ch === "\n") {
46
+ line++;
47
+ column = 1;
48
+ } else {
49
+ column++;
50
+ }
51
+ byteIndex += byteLen;
52
+ }
53
+
54
+ return { line, column };
55
+ }
56
+
57
+ /**
58
+ * Parse Biome severity to LSP DiagnosticSeverity.
59
+ */
60
+ function parseSeverity(severity: string): DiagnosticSeverity {
61
+ switch (severity) {
62
+ case "error":
63
+ return 1;
64
+ case "warning":
65
+ return 2;
66
+ case "info":
67
+ return 3;
68
+ case "hint":
69
+ return 4;
70
+ default:
71
+ return 2;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Run a Biome CLI command.
77
+ */
78
+ async function runBiome(
79
+ args: string[],
80
+ cwd: string,
81
+ resolvedCommand?: string,
82
+ ): Promise<{ stdout: string; stderr: string; success: boolean }> {
83
+ const command = resolvedCommand ?? "biome";
84
+
85
+ try {
86
+ const proc = Bun.spawn([command, ...args], {
87
+ cwd,
88
+ stdout: "pipe",
89
+ stderr: "pipe",
90
+ });
91
+
92
+ const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
93
+ const exitCode = await proc.exited;
94
+
95
+ return { stdout, stderr, success: exitCode === 0 };
96
+ } catch (err) {
97
+ return { stdout: "", stderr: String(err), success: false };
98
+ }
99
+ }
100
+
101
+ // =============================================================================
102
+ // Biome Client
103
+ // =============================================================================
104
+
105
+ /**
106
+ * Biome CLI-based linter client.
107
+ * Parses Biome's --reporter=json output into LSP Diagnostic format.
108
+ */
109
+ export class BiomeClient implements LinterClient {
110
+ private config: ServerConfig;
111
+ private cwd: string;
112
+
113
+ constructor(config: ServerConfig, cwd: string) {
114
+ this.config = config;
115
+ this.cwd = cwd;
116
+ }
117
+
118
+ async format(filePath: string, content: string): Promise<string> {
119
+ // Write content to file first
120
+ await Bun.write(filePath, content);
121
+
122
+ // Run biome format --write
123
+ const result = await runBiome(["format", "--write", filePath], this.cwd, this.config.resolvedCommand);
124
+
125
+ if (result.success) {
126
+ // Read back formatted content
127
+ return await Bun.file(filePath).text();
128
+ }
129
+
130
+ // Format failed, return original
131
+ return content;
132
+ }
133
+
134
+ async lint(filePath: string): Promise<Diagnostic[]> {
135
+ // Run biome lint with JSON reporter
136
+ const result = await runBiome(["lint", "--reporter=json", filePath], this.cwd, this.config.resolvedCommand);
137
+
138
+ return this.parseJsonOutput(result.stdout, filePath);
139
+ }
140
+
141
+ /**
142
+ * Parse Biome's JSON output into LSP Diagnostics.
143
+ */
144
+ private parseJsonOutput(jsonOutput: string, targetFile: string): Diagnostic[] {
145
+ const diagnostics: Diagnostic[] = [];
146
+
147
+ try {
148
+ const parsed: BiomeJsonOutput = JSON.parse(jsonOutput);
149
+
150
+ for (const diag of parsed.diagnostics) {
151
+ const location = diag.location;
152
+ if (!location?.path?.file) continue;
153
+
154
+ // Resolve file path
155
+ const diagFile = path.isAbsolute(location.path.file)
156
+ ? location.path.file
157
+ : path.join(this.cwd, location.path.file);
158
+
159
+ // Only include diagnostics for the target file
160
+ if (path.resolve(diagFile) !== path.resolve(targetFile)) {
161
+ continue;
162
+ }
163
+
164
+ // Convert byte offset to line:column
165
+ let startLine = 1;
166
+ let startColumn = 1;
167
+ let endLine = 1;
168
+ let endColumn = 1;
169
+
170
+ if (location.span && location.sourceCode) {
171
+ const startPos = offsetToPosition(location.sourceCode, location.span[0]);
172
+ const endPos = offsetToPosition(location.sourceCode, location.span[1]);
173
+ startLine = startPos.line;
174
+ startColumn = startPos.column;
175
+ endLine = endPos.line;
176
+ endColumn = endPos.column;
177
+ }
178
+
179
+ diagnostics.push({
180
+ range: {
181
+ start: { line: startLine - 1, character: startColumn - 1 },
182
+ end: { line: endLine - 1, character: endColumn - 1 },
183
+ },
184
+ severity: parseSeverity(diag.severity),
185
+ message: diag.description,
186
+ source: "biome",
187
+ code: diag.category,
188
+ });
189
+ }
190
+ } catch {
191
+ // JSON parse failed, return empty
192
+ }
193
+
194
+ return diagnostics;
195
+ }
196
+
197
+ dispose(): void {
198
+ // Nothing to dispose for CLI client
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Factory function to create a Biome client.
204
+ */
205
+ export function createBiomeClient(config: ServerConfig, cwd: string): LinterClient {
206
+ return new BiomeClient(config, cwd);
207
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Linter client implementations.
3
+ *
4
+ * The LinterClient interface provides a common API for formatters and linters.
5
+ * Different implementations can use LSP protocol, CLI tools, or other mechanisms.
6
+ */
7
+
8
+ export { BiomeClient, createBiomeClient } from "./biome-client";
9
+ export { createLspLinterClient, LspLinterClient } from "./lsp-linter-client";
10
+
11
+ import type { LinterClient, ServerConfig } from "../types";
12
+ import { createLspLinterClient } from "./lsp-linter-client";
13
+
14
+ // Cache of linter clients by server name + cwd
15
+ const clientCache = new Map<string, LinterClient>();
16
+
17
+ /**
18
+ * Get or create a linter client for a server configuration.
19
+ * Uses the server's custom factory if provided, otherwise falls back to LSP.
20
+ */
21
+ export function getLinterClient(serverName: string, config: ServerConfig, cwd: string): LinterClient {
22
+ const key = `${serverName}:${cwd}`;
23
+
24
+ let client = clientCache.get(key);
25
+ if (client) {
26
+ return client;
27
+ }
28
+
29
+ // Use custom factory if provided
30
+ if (config.createClient) {
31
+ client = config.createClient(config, cwd);
32
+ } else {
33
+ // Default to LSP
34
+ client = createLspLinterClient(config, cwd);
35
+ }
36
+
37
+ clientCache.set(key, client);
38
+ return client;
39
+ }
40
+
41
+ /**
42
+ * Clear all cached linter clients.
43
+ */
44
+ export function clearLinterClientCache(): void {
45
+ for (const client of clientCache.values()) {
46
+ client.dispose?.();
47
+ }
48
+ clientCache.clear();
49
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * LSP-based linter client.
3
+ * Uses the Language Server Protocol for formatting and diagnostics.
4
+ */
5
+
6
+ import { getOrCreateClient, notifySaved, sendRequest, syncContent } from "../client";
7
+ import { applyTextEditsToString } from "../edits";
8
+ import type { Diagnostic, LinterClient, LspClient, ServerConfig, TextEdit } from "../types";
9
+ import { fileToUri } from "../utils";
10
+
11
+ /** Default formatting options for LSP */
12
+ const DEFAULT_FORMAT_OPTIONS = {
13
+ tabSize: 3,
14
+ insertSpaces: true,
15
+ trimTrailingWhitespace: true,
16
+ insertFinalNewline: true,
17
+ trimFinalNewlines: true,
18
+ };
19
+
20
+ /**
21
+ * LSP-based linter client implementation.
22
+ * Wraps the existing LSP client infrastructure.
23
+ */
24
+ export class LspLinterClient implements LinterClient {
25
+ private config: ServerConfig;
26
+ private cwd: string;
27
+ private client: LspClient | null = null;
28
+
29
+ constructor(config: ServerConfig, cwd: string) {
30
+ this.config = config;
31
+ this.cwd = cwd;
32
+ }
33
+
34
+ private async getClient(): Promise<LspClient> {
35
+ if (!this.client) {
36
+ this.client = await getOrCreateClient(this.config, this.cwd);
37
+ }
38
+ return this.client;
39
+ }
40
+
41
+ async format(filePath: string, content: string): Promise<string> {
42
+ const client = await this.getClient();
43
+ const uri = fileToUri(filePath);
44
+
45
+ // Sync content to LSP
46
+ await syncContent(client, filePath, content);
47
+
48
+ // Check if server supports formatting
49
+ const caps = client.serverCapabilities;
50
+ if (!caps?.documentFormattingProvider) {
51
+ return content;
52
+ }
53
+
54
+ // Request formatting
55
+ const edits = (await sendRequest(client, "textDocument/formatting", {
56
+ textDocument: { uri },
57
+ options: DEFAULT_FORMAT_OPTIONS,
58
+ })) as TextEdit[] | null;
59
+
60
+ if (!edits || edits.length === 0) {
61
+ return content;
62
+ }
63
+
64
+ return applyTextEditsToString(content, edits);
65
+ }
66
+
67
+ async lint(filePath: string): Promise<Diagnostic[]> {
68
+ const client = await this.getClient();
69
+ const uri = fileToUri(filePath);
70
+
71
+ // Notify that file was saved to trigger diagnostics
72
+ await notifySaved(client, filePath);
73
+
74
+ // Wait for diagnostics with timeout
75
+ const timeoutMs = 3000;
76
+ const start = Date.now();
77
+ while (Date.now() - start < timeoutMs) {
78
+ const diagnostics = client.diagnostics.get(uri);
79
+ if (diagnostics !== undefined) {
80
+ return diagnostics;
81
+ }
82
+ await new Promise((resolve) => setTimeout(resolve, 100));
83
+ }
84
+
85
+ return client.diagnostics.get(uri) ?? [];
86
+ }
87
+
88
+ dispose(): void {
89
+ // Client lifecycle is managed globally, nothing to dispose here
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Factory function to create an LSP linter client.
95
+ */
96
+ export function createLspLinterClient(config: ServerConfig, cwd: string): LinterClient {
97
+ return new LspLinterClient(config, cwd);
98
+ }