codehere 0.2.2 → 0.3.0-rc.10

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 (238) hide show
  1. package/README.md +88 -68
  2. package/dist/domain/entities/task.js +4 -4
  3. package/dist/error-handler.js +4 -9
  4. package/dist/index.js +261 -1224
  5. package/dist/infrastructure/config/product-context.js +7 -6
  6. package/dist/infrastructure/hooks/claude-sdk-hook.js +348 -72
  7. package/dist/infrastructure/observability/execution-tracer.js +11 -6
  8. package/dist/infrastructure/orchestration/agent-adapter.js +13 -0
  9. package/dist/infrastructure/orchestration/agent-installer.js +101 -0
  10. package/dist/infrastructure/orchestration/agents-config-seed.js +56 -0
  11. package/dist/infrastructure/orchestration/aider-adapter.js +319 -0
  12. package/dist/infrastructure/orchestration/artifacts-extractor.js +175 -0
  13. package/dist/infrastructure/orchestration/claude-code-adapter.js +352 -0
  14. package/dist/infrastructure/orchestration/codex-adapter.js +310 -0
  15. package/dist/infrastructure/orchestration/conductor-adapter.js +340 -0
  16. package/dist/infrastructure/orchestration/generic-cli-adapter.js +553 -0
  17. package/dist/infrastructure/orchestration/opencode-adapter.js +275 -0
  18. package/dist/infrastructure/orchestration/progress-aggregator.js +189 -0
  19. package/dist/infrastructure/orchestration/readiness.js +106 -0
  20. package/dist/infrastructure/orchestration/session-manager.js +278 -0
  21. package/dist/infrastructure/orchestration/session-persistence.js +109 -0
  22. package/dist/infrastructure/protocols/mcp-helpers.js +10 -30
  23. package/dist/infrastructure/protocols/mcp-server-impl.js +359 -21
  24. package/dist/infrastructure/protocols/probe-port.js +36 -0
  25. package/dist/infrastructure/protocols/ui-page.js +1827 -0
  26. package/dist/infrastructure/security/file-collection.js +115 -0
  27. package/dist/infrastructure/security/trust-scorer.js +301 -56
  28. package/dist/infrastructure/storage/task-repository.js +8 -7
  29. package/dist/monitoring.js +9 -38
  30. package/dist/presentation/cli/commands/help-command.js +96 -139
  31. package/dist/presentation/cli/commands/hook-command.js +17 -24
  32. package/dist/presentation/cli/commands/report-command.js +59 -72
  33. package/dist/presentation/cli/commands/setup-command.js +4 -4
  34. package/dist/presentation/cli/commands/status-command.js +321 -30
  35. package/dist/presentation/cli/commands/trust-command.js +84 -72
  36. package/dist/presentation/cli/error-display.js +17 -21
  37. package/dist/presentation/cli/repl.js +474 -0
  38. package/dist/ui.js +11 -11
  39. package/package.json +24 -62
  40. package/dist/agents/base.js +0 -101
  41. package/dist/agents/orchestrator-model-driven.js +0 -141
  42. package/dist/agents/react-orchestrator-agent.js +0 -54
  43. package/dist/application/agents/execution-agent.js +0 -508
  44. package/dist/application/agents/multi-agent-orchestrator.js +0 -295
  45. package/dist/application/agents/planning-agent.js +0 -433
  46. package/dist/application/agents/validation-agent.js +0 -317
  47. package/dist/application/architectures/plan-and-act.js +0 -242
  48. package/dist/application/index.js +0 -12
  49. package/dist/application/services/agent-factory.js +0 -28
  50. package/dist/application/services/dependency-container.js +0 -596
  51. package/dist/application/services/git-context-service.js +0 -83
  52. package/dist/application/services/intelligent-router.js +0 -934
  53. package/dist/application/services/tool-executor-service.js +0 -94
  54. package/dist/application/use-cases/ask-question-use-case.js +0 -338
  55. package/dist/application/use-cases/edit-file-use-case.js +0 -830
  56. package/dist/application/use-cases/explain-file-use-case.js +0 -50
  57. package/dist/application/use-cases/index-codebase-use-case.js +0 -113
  58. package/dist/application/use-cases/planning-use-case.js +0 -276
  59. package/dist/application/use-cases/react-orchestration-use-case.js +0 -329
  60. package/dist/application/use-cases/reflexion-use-case.js +0 -30
  61. package/dist/application/workflows/deterministic-workflows.js +0 -189
  62. package/dist/ast-parser.js +0 -331
  63. package/dist/cache.js +0 -180
  64. package/dist/chat.js +0 -251
  65. package/dist/code-analysis.js +0 -327
  66. package/dist/context.js +0 -187
  67. package/dist/docs.js +0 -146
  68. package/dist/domain/entities/code-chunk.js +0 -6
  69. package/dist/domain/entities/decision.js +0 -9
  70. package/dist/domain/entities/edit.js +0 -6
  71. package/dist/domain/entities/knowledge-node.js +0 -9
  72. package/dist/domain/entities/knowledge-preservation.js +0 -10
  73. package/dist/domain/entities/memory.js +0 -6
  74. package/dist/domain/entities/pattern.js +0 -7
  75. package/dist/domain/entities/plan.js +0 -6
  76. package/dist/domain/entities/query.js +0 -6
  77. package/dist/domain/errors/embeddings-not-found-error.js +0 -13
  78. package/dist/domain/index.js +0 -19
  79. package/dist/domain/interfaces/ai-service.interface.js +0 -6
  80. package/dist/domain/interfaces/code-analyzer.interface.js +0 -6
  81. package/dist/domain/interfaces/decision-repository.interface.js +0 -9
  82. package/dist/domain/interfaces/embedding-repository.interface.js +0 -6
  83. package/dist/domain/interfaces/knowledge-graph-repository.interface.js +0 -9
  84. package/dist/domain/interfaces/knowledge-preservation-repository.interface.js +0 -8
  85. package/dist/domain/interfaces/memory-repository.interface.js +0 -6
  86. package/dist/domain/interfaces/pattern-repository.interface.js +0 -6
  87. package/dist/domain/interfaces/verification-service.interface.js +0 -6
  88. package/dist/domain/services/cross-team-pattern-sharing.js +0 -125
  89. package/dist/domain/services/decision-recorder.js +0 -223
  90. package/dist/domain/services/decision-retriever.js +0 -133
  91. package/dist/domain/services/environment-mode-manager.js +0 -22
  92. package/dist/domain/services/graded-memory-activation.js +0 -69
  93. package/dist/domain/services/knowledge-access.js +0 -132
  94. package/dist/domain/services/knowledge-extractor.js +0 -247
  95. package/dist/domain/services/knowledge-graph-builder.js +0 -268
  96. package/dist/domain/services/knowledge-graph-query.js +0 -187
  97. package/dist/domain/services/memory-retriever.js +0 -82
  98. package/dist/domain/services/memory-synthesizer.js +0 -91
  99. package/dist/domain/services/pattern-extractor.js +0 -442
  100. package/dist/domain/services/query-intent-classifier.js +0 -140
  101. package/dist/domain/services/react-loop.js +0 -186
  102. package/dist/domain/services/risk-tier-detector.js +0 -78
  103. package/dist/edit.js +0 -319
  104. package/dist/embed.js +0 -459
  105. package/dist/formatter.js +0 -168
  106. package/dist/git.js +0 -198
  107. package/dist/infrastructure/ai/cohere-ai-service.js +0 -607
  108. package/dist/infrastructure/ai/command-r-evaluation.js +0 -206
  109. package/dist/infrastructure/ai/model-config.js +0 -84
  110. package/dist/infrastructure/ai/provider-ai-service.js +0 -477
  111. package/dist/infrastructure/cache/embedding-cache.js +0 -175
  112. package/dist/infrastructure/cache/query-result-cache.js +0 -66
  113. package/dist/infrastructure/cache/response-cache.js +0 -48
  114. package/dist/infrastructure/cache/security-scan-cache.js +0 -47
  115. package/dist/infrastructure/chunking/ast-code-chunker.js +0 -19
  116. package/dist/infrastructure/chunking/code-aware-chunker.js +0 -397
  117. package/dist/infrastructure/context/context-compressor.js +0 -329
  118. package/dist/infrastructure/context/conversation-context.js +0 -128
  119. package/dist/infrastructure/context/session-manager.js +0 -94
  120. package/dist/infrastructure/cost/cost-tracker.js +0 -360
  121. package/dist/infrastructure/file-scanner/file-scanner.js +0 -106
  122. package/dist/infrastructure/governance/nist-ai-rmf.js +0 -292
  123. package/dist/infrastructure/governance/policy-as-code.js +0 -258
  124. package/dist/infrastructure/governance/prompt-to-code-lineage.js +0 -239
  125. package/dist/infrastructure/index.js +0 -7
  126. package/dist/infrastructure/observability/prompt-versioner.js +0 -145
  127. package/dist/infrastructure/protocols/a2a-metadata.js +0 -137
  128. package/dist/infrastructure/protocols/a2a-protocol.js +0 -314
  129. package/dist/infrastructure/protocols/mcp-oauth.js +0 -184
  130. package/dist/infrastructure/protocols/mcp-server.js +0 -333
  131. package/dist/infrastructure/protocols/streaming-handler.js +0 -103
  132. package/dist/infrastructure/providers/cohere-provider.js +0 -444
  133. package/dist/infrastructure/providers/fake-provider.js +0 -130
  134. package/dist/infrastructure/providers/local-llm-provider.js +0 -330
  135. package/dist/infrastructure/providers/model-config.types.js +0 -9
  136. package/dist/infrastructure/providers/model-provider.interface.js +0 -8
  137. package/dist/infrastructure/providers/openai-provider.js +0 -413
  138. package/dist/infrastructure/providers/openrouter-provider.js +0 -399
  139. package/dist/infrastructure/providers/operation-provider-resolver.js +0 -105
  140. package/dist/infrastructure/providers/provider-config-loader.js +0 -120
  141. package/dist/infrastructure/providers/provider-registry.js +0 -88
  142. package/dist/infrastructure/safety/pattern-detector.js +0 -133
  143. package/dist/infrastructure/security/ai-sast-scanner.js +0 -290
  144. package/dist/infrastructure/security/docker-sandbox.js +0 -178
  145. package/dist/infrastructure/security/enhanced-security-gate.js +0 -151
  146. package/dist/infrastructure/security/license-scanner.js +0 -167
  147. package/dist/infrastructure/security/provider-bias-detector.js +0 -164
  148. package/dist/infrastructure/security/sandbox-executor.js +0 -64
  149. package/dist/infrastructure/serialization/toon-serializer.js +0 -119
  150. package/dist/infrastructure/storage/memory-stream-service.js +0 -204
  151. package/dist/infrastructure/storage/plan-repository.js +0 -96
  152. package/dist/infrastructure/storage/sqlite-decision-repository.js +0 -232
  153. package/dist/infrastructure/storage/sqlite-embedding-repository.js +0 -311
  154. package/dist/infrastructure/storage/sqlite-knowledge-graph-repository.js +0 -417
  155. package/dist/infrastructure/storage/sqlite-knowledge-preservation-repository.js +0 -230
  156. package/dist/infrastructure/storage/sqlite-memory-repository.js +0 -229
  157. package/dist/infrastructure/storage/sqlite-pattern-repository.js +0 -249
  158. package/dist/infrastructure/storage/task-helpers.js +0 -197
  159. package/dist/infrastructure/telemetry/telemetry-service.js +0 -167
  160. package/dist/infrastructure/ux/appropriate-friction.js +0 -213
  161. package/dist/infrastructure/ux/bi-directional-learning.js +0 -337
  162. package/dist/infrastructure/ux/capability-boundaries.js +0 -42
  163. package/dist/infrastructure/ux/comprehensive-formatter.js +0 -117
  164. package/dist/infrastructure/ux/contextual-feature-discovery.js +0 -10
  165. package/dist/infrastructure/ux/expectation-management.js +0 -327
  166. package/dist/infrastructure/ux/feature-discovery.js +0 -14
  167. package/dist/infrastructure/ux/feedback-prompt.js +0 -45
  168. package/dist/infrastructure/ux/hitl-validation.js +0 -297
  169. package/dist/infrastructure/ux/progress-indicator.js +0 -116
  170. package/dist/infrastructure/ux/progressive-disclosure.js +0 -236
  171. package/dist/infrastructure/ux/review-checkpoint.js +0 -72
  172. package/dist/infrastructure/ux/staged-feature-intro.js +0 -24
  173. package/dist/infrastructure/ux/stream-markdown.js +0 -151
  174. package/dist/infrastructure/ux/syntax-highlighter.js +0 -91
  175. package/dist/infrastructure/ux/team-standards.js +0 -196
  176. package/dist/infrastructure/ux/transparency.js +0 -71
  177. package/dist/infrastructure/validation/agent-feedback.js +0 -55
  178. package/dist/infrastructure/validation/agent-validation-helper.js +0 -137
  179. package/dist/infrastructure/validation/review-handler.js +0 -40
  180. package/dist/infrastructure/validation/summary-aggregator.js +0 -70
  181. package/dist/infrastructure/validation/summary-extractor.js +0 -45
  182. package/dist/infrastructure/validation/trace-summary.js +0 -52
  183. package/dist/infrastructure/verification/verification-service.js +0 -47
  184. package/dist/infrastructure/xai/attention-visualizer.js +0 -172
  185. package/dist/infrastructure/xai/cot-visualizer.js +0 -283
  186. package/dist/infrastructure/xai/decision-tree-log.js +0 -212
  187. package/dist/infrastructure/xai/interactive-xai.js +0 -260
  188. package/dist/infrastructure/xai/uncertainty-quantifier.js +0 -198
  189. package/dist/intelligent-retrieval.js +0 -272
  190. package/dist/intent/classifier.js +0 -2
  191. package/dist/intent/context.js +0 -2
  192. package/dist/iterative-refinement.js +0 -189
  193. package/dist/learning.js +0 -232
  194. package/dist/log.js +0 -31
  195. package/dist/migrate.js +0 -133
  196. package/dist/parallel-processor.js +0 -534
  197. package/dist/planner.js +0 -198
  198. package/dist/policy.js +0 -26
  199. package/dist/presentation/cli/commands/ask-command.js +0 -314
  200. package/dist/presentation/cli/commands/cost-command.js +0 -191
  201. package/dist/presentation/cli/commands/decision-command.js +0 -207
  202. package/dist/presentation/cli/commands/docs-command.js +0 -25
  203. package/dist/presentation/cli/commands/fix-command.js +0 -53
  204. package/dist/presentation/cli/commands/knowledge-command.js +0 -129
  205. package/dist/presentation/cli/commands/learning-dashboard-command.js +0 -183
  206. package/dist/presentation/cli/commands/models-command.js +0 -194
  207. package/dist/presentation/cli/commands/orchestrate-command.js +0 -207
  208. package/dist/presentation/cli/commands/plan-command.js +0 -246
  209. package/dist/presentation/cli/commands/product-command.js +0 -199
  210. package/dist/presentation/cli/commands/react-command.js +0 -181
  211. package/dist/presentation/cli/commands/review-command.js +0 -154
  212. package/dist/presentation/cli/commands/run-command.js +0 -188
  213. package/dist/presentation/cli/commands/smart-command.js +0 -395
  214. package/dist/presentation/cli/commands/suggest-command.js +0 -138
  215. package/dist/presentation/cli/commands/trace-command.js +0 -129
  216. package/dist/presentation/cli/commands/undo-command.js +0 -150
  217. package/dist/query-optimizer.js +0 -205
  218. package/dist/refactor.js +0 -116
  219. package/dist/review.js +0 -206
  220. package/dist/search.js +0 -163
  221. package/dist/session.js +0 -704
  222. package/dist/task-verification.js +0 -336
  223. package/dist/testgen.js +0 -166
  224. package/dist/token-optimizer.js +0 -277
  225. package/dist/tools/bash.js +0 -213
  226. package/dist/tools/edit.js +0 -235
  227. package/dist/tools/executor.js +0 -153
  228. package/dist/tools/git-tool.js +0 -292
  229. package/dist/tools/parser.js +0 -158
  230. package/dist/tools/registry.js +0 -302
  231. package/dist/tools/search.js +0 -100
  232. package/dist/types.js +0 -2
  233. package/dist/utils/check-api-status.js +0 -100
  234. package/dist/utils/diff-parser.js +0 -162
  235. package/dist/verification/backup.js +0 -259
  236. package/dist/verification/compiler.js +0 -264
  237. package/dist/verification/index.js +0 -273
  238. package/dist/verification/test-runner.js +0 -379
@@ -19,10 +19,16 @@
19
19
  * codehere serve --port 8080 # custom port
20
20
  */
21
21
  import http from 'http';
22
- import { readFileSync, existsSync } from 'fs';
22
+ import { readFileSync, existsSync, statSync, watchFile, unwatchFile } from 'fs';
23
23
  import { join, relative } from 'path';
24
24
  import { collectFilesForMCP, scoreFileForMCP } from './mcp-helpers.js';
25
- import { scanPayload, calculateRiskScore } from '../hooks/claude-sdk-hook.js';
25
+ import { scanPayload, calculateRiskScore, AUDIT_FILE } from '../hooks/claude-sdk-hook.js';
26
+ import { getVersion } from '../../utils/version.js';
27
+ import { TASK_HTML, HISTORY_HTML } from './ui-page.js';
28
+ import { SessionManager, availableAgents, readinessForAllAgents, getAdapter, allAdapters, } from '../orchestration/session-manager.js';
29
+ import { isAbsolute } from 'path';
30
+ import { installAgent } from '../orchestration/agent-installer.js';
31
+ import { detectReadiness } from '../orchestration/readiness.js';
26
32
  // ── Tool definitions ─────────────────────────────────────────────────────────
27
33
  const TOOLS = [
28
34
  {
@@ -100,8 +106,8 @@ function handleTrust(params) {
100
106
  score: result.score,
101
107
  grade: result.grade,
102
108
  summary: result.summary,
103
- criticalCount: result.findings.filter(f => f.severity === 'critical').length,
104
- highCount: result.findings.filter(f => f.severity === 'high').length,
109
+ criticalCount: result.findings.filter((f) => f.severity === 'critical').length,
110
+ highCount: result.findings.filter((f) => f.severity === 'high').length,
105
111
  recommendation: result.score >= 75
106
112
  ? 'Safe — minor issues can be addressed in follow-up'
107
113
  : result.score >= 60
@@ -115,7 +121,7 @@ function handleScan(params) {
115
121
  const text = String(params.text ?? '');
116
122
  const findings = scanPayload(text);
117
123
  const riskScore = calculateRiskScore(findings);
118
- const criticalFindings = findings.filter(f => f.severity === 'critical');
124
+ const criticalFindings = findings.filter((f) => f.severity === 'critical');
119
125
  const blocked = criticalFindings.length > 0 || riskScore >= 60;
120
126
  return JSON.stringify({
121
127
  blocked,
@@ -137,7 +143,7 @@ function handleReview(params) {
137
143
  if (files.length === 0) {
138
144
  return JSON.stringify({ message: 'No code files found', path: targetPath });
139
145
  }
140
- const results = files.map(f => scoreFileForMCP(relative(targetPath, f.filepath), f.code));
146
+ const results = files.map((f) => scoreFileForMCP(relative(targetPath, f.filepath), f.code));
141
147
  results.sort((a, b) => a.score - b.score); // worst first
142
148
  const top = results.slice(0, topN);
143
149
  const overallScore = Math.round(results.reduce((s, r) => s + r.score, 0) / results.length);
@@ -145,13 +151,13 @@ function handleReview(params) {
145
151
  overallScore,
146
152
  grade: overallScore >= 90 ? 'A' : overallScore >= 75 ? 'B' : overallScore >= 60 ? 'C' : overallScore >= 40 ? 'D' : 'F',
147
153
  totalFiles: results.length,
148
- riskiestFiles: top.map(r => ({
154
+ riskiestFiles: top.map((r) => ({
149
155
  filepath: r.filepath,
150
156
  score: r.score,
151
157
  grade: r.grade,
152
158
  summary: r.summary,
153
- criticalCount: r.findings.filter(f => f.severity === 'critical').length,
154
- highCount: r.findings.filter(f => f.severity === 'high').length,
159
+ criticalCount: r.findings.filter((f) => f.severity === 'critical').length,
160
+ highCount: r.findings.filter((f) => f.severity === 'high').length,
155
161
  })),
156
162
  });
157
163
  }
@@ -163,19 +169,24 @@ function handleAudit(params) {
163
169
  const limit = Number(params.limit ?? 20);
164
170
  const blockedOnly = Boolean(params.blockedOnly);
165
171
  if (!existsSync(AUDIT_FILE_PATH)) {
166
- return JSON.stringify({ message: 'No audit log yet — install hooks first with: codehere hook install', entries: [] });
172
+ return JSON.stringify({
173
+ message: 'No audit log yet — install hooks first with: codehere hook install',
174
+ entries: [],
175
+ });
167
176
  }
168
177
  const lines = readFileSync(AUDIT_FILE_PATH, 'utf-8')
169
178
  .split('\n')
170
179
  .filter(Boolean)
171
180
  .slice(-Math.min(limit * 3, 500)); // read more than needed to account for filter
172
181
  const entries = lines
173
- .map(l => { try {
174
- return JSON.parse(l);
175
- }
176
- catch {
177
- return null;
178
- } })
182
+ .map((l) => {
183
+ try {
184
+ return JSON.parse(l);
185
+ }
186
+ catch {
187
+ return null;
188
+ }
189
+ })
179
190
  .filter(Boolean)
180
191
  .filter((e) => !blockedOnly || e.blocked)
181
192
  .slice(-limit);
@@ -189,11 +200,12 @@ function dispatch(req) {
189
200
  switch (req.method) {
190
201
  case 'initialize':
191
202
  return {
192
- jsonrpc: '2.0', id,
203
+ jsonrpc: '2.0',
204
+ id,
193
205
  result: {
194
206
  protocolVersion: '2024-11-05',
195
207
  capabilities: { tools: {} },
196
- serverInfo: { name: 'codehere', version: '0.2.0' },
208
+ serverInfo: { name: 'codehere', version: getVersion() },
197
209
  },
198
210
  };
199
211
  case 'tools/list':
@@ -219,7 +231,8 @@ function dispatch(req) {
219
231
  return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${toolName}` } };
220
232
  }
221
233
  return {
222
- jsonrpc: '2.0', id,
234
+ jsonrpc: '2.0',
235
+ id,
223
236
  result: { content: [{ type: 'text', text: content }] },
224
237
  };
225
238
  }
@@ -250,13 +263,338 @@ export function startMCPServer(options = {}) {
250
263
  // Health check
251
264
  if (req.method === 'GET' && req.url === '/health') {
252
265
  res.writeHead(200, { 'Content-Type': 'application/json' });
253
- res.end(JSON.stringify({ status: 'ok', server: 'codehere-mcp', version: '0.2.0', tools: TOOLS.length }));
266
+ res.end(JSON.stringify({ status: 'ok', server: 'codehere-mcp', version: getVersion(), tools: TOOLS.length }));
267
+ return;
268
+ }
269
+ // v0.3 primary UI: task intake + active sessions
270
+ if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
271
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
272
+ res.end(TASK_HTML);
273
+ return;
274
+ }
275
+ // History / audit feed (what we built Day 0; now secondary)
276
+ if (req.method === 'GET' && (req.url === '/history' || req.url === '/history/')) {
277
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
278
+ res.end(HISTORY_HTML);
279
+ return;
280
+ }
281
+ // Backwards-compat: /ui used to be the Mission Control audit view — now at /history
282
+ if (req.method === 'GET' && (req.url === '/ui' || req.url === '/ui/')) {
283
+ res.writeHead(302, { Location: '/history' });
284
+ res.end();
285
+ return;
286
+ }
287
+ // Live audit event stream (SSE)
288
+ if (req.method === 'GET' && req.url === '/events') {
289
+ res.writeHead(200, {
290
+ 'Content-Type': 'text/event-stream',
291
+ 'Cache-Control': 'no-cache',
292
+ Connection: 'keep-alive',
293
+ });
294
+ // Replay last 50 events on connect so the UI isn't empty
295
+ let lastSize = 0;
296
+ if (existsSync(AUDIT_FILE)) {
297
+ try {
298
+ const content = readFileSync(AUDIT_FILE, 'utf-8');
299
+ lastSize = Buffer.byteLength(content, 'utf-8');
300
+ const lines = content.split('\n').filter((l) => l.trim().length > 0);
301
+ for (const line of lines.slice(-50)) {
302
+ res.write(`data: ${line}\n\n`);
303
+ }
304
+ }
305
+ catch {
306
+ /* no audit yet */
307
+ }
308
+ }
309
+ // Tail the audit file for new events
310
+ const onChange = () => {
311
+ try {
312
+ if (!existsSync(AUDIT_FILE))
313
+ return;
314
+ const currentSize = statSync(AUDIT_FILE).size;
315
+ if (currentSize <= lastSize)
316
+ return;
317
+ const fd = readFileSync(AUDIT_FILE, 'utf-8');
318
+ const fresh = fd.slice(lastSize);
319
+ lastSize = Buffer.byteLength(fd, 'utf-8');
320
+ for (const line of fresh.split('\n').filter((l) => l.trim().length > 0)) {
321
+ res.write(`data: ${line}\n\n`);
322
+ }
323
+ }
324
+ catch {
325
+ /* transient read errors are fine */
326
+ }
327
+ };
328
+ watchFile(AUDIT_FILE, { interval: 500 }, onChange);
329
+ // Keep-alive ping every 15s so proxies don't kill the stream
330
+ const ping = setInterval(() => res.write(': ping\n\n'), 15000);
331
+ req.on('close', () => {
332
+ clearInterval(ping);
333
+ unwatchFile(AUDIT_FILE, onChange);
334
+ });
335
+ return;
336
+ }
337
+ // ── F2 agent registry ─────────────────────────────────────────────────────
338
+ if (req.method === 'GET' && req.url === '/api/agents') {
339
+ res.writeHead(200, { 'Content-Type': 'application/json' });
340
+ res.end(JSON.stringify({ agents: availableAgents() }));
341
+ return;
342
+ }
343
+ // ── Gate B3 · Audit summary (moat visible on main page) ───────────────────
344
+ // Principle #3 status-at-a-glance + #7 agent-provenance. Reads the hook audit
345
+ // log's last 24h, aggregates: how many actions fired across all agents, how
346
+ // many codehere blocked, mean trust (= 100 - mean riskScore).
347
+ if (req.method === 'GET' && req.url === '/api/audit/summary') {
348
+ const windowMs = 24 * 60 * 60 * 1000;
349
+ const cutoff = Date.now() - windowMs;
350
+ let actions = 0, blocked = 0, riskSum = 0, riskN = 0;
351
+ const sessionIds = new Set();
352
+ try {
353
+ if (existsSync(AUDIT_FILE)) {
354
+ const content = readFileSync(AUDIT_FILE, 'utf-8');
355
+ const lines = content.split('\n');
356
+ // Walk backwards; stop at first line older than cutoff (log is append-chronological)
357
+ for (let i = lines.length - 1; i >= 0; i--) {
358
+ const line = lines[i].trim();
359
+ if (!line)
360
+ continue;
361
+ let entry;
362
+ try {
363
+ entry = JSON.parse(line);
364
+ }
365
+ catch {
366
+ continue;
367
+ }
368
+ const ts = typeof entry.timestamp === 'number'
369
+ ? entry.timestamp
370
+ : entry.timestamp
371
+ ? Date.parse(String(entry.timestamp))
372
+ : 0;
373
+ if (!ts || ts < cutoff)
374
+ break;
375
+ actions += 1;
376
+ if (entry.blocked)
377
+ blocked += 1;
378
+ if (typeof entry.riskScore === 'number') {
379
+ riskSum += entry.riskScore;
380
+ riskN += 1;
381
+ }
382
+ if (entry.sessionId)
383
+ sessionIds.add(entry.sessionId);
384
+ }
385
+ }
386
+ }
387
+ catch {
388
+ /* empty audit — zeros are fine */
389
+ }
390
+ const avgTrust = riskN > 0 ? Math.max(0, Math.round(100 - riskSum / riskN)) : null;
391
+ res.writeHead(200, { 'Content-Type': 'application/json' });
392
+ res.end(JSON.stringify({
393
+ windowHours: 24,
394
+ actions,
395
+ blocked,
396
+ agents: sessionIds.size,
397
+ avgTrust,
398
+ }));
399
+ return;
400
+ }
401
+ // ── F1 · Agent install (zero-terminal setup — principle #10) ──────────────
402
+ // POST /api/agents/:id/install → SSE stream of install output.
403
+ // The command comes from the adapter's checkReadiness().installHint — never
404
+ // from the user over the wire. codehere is the thing that runs the install,
405
+ // not a page that tells the user how to run it.
406
+ const installMatch = req.url?.match(/^\/api\/agents\/([a-z0-9-]+)\/install$/i);
407
+ if (req.method === 'POST' && installMatch) {
408
+ const adapterId = installMatch[1];
409
+ const adapter = getAdapter(adapterId);
410
+ if (!adapter) {
411
+ res.writeHead(404, { 'Content-Type': 'application/json' });
412
+ res.end(JSON.stringify({ error: `No such agent: ${adapterId}` }));
413
+ return;
414
+ }
415
+ const session = installAgent(adapter);
416
+ if (!session) {
417
+ res.writeHead(400, { 'Content-Type': 'application/json' });
418
+ res.end(JSON.stringify({ error: 'Agent already installed or has no install hint' }));
419
+ return;
420
+ }
421
+ res.writeHead(200, {
422
+ 'Content-Type': 'text/event-stream',
423
+ 'Cache-Control': 'no-cache',
424
+ Connection: 'keep-alive',
425
+ });
426
+ const unsubscribe = session.subscribe((evt) => {
427
+ res.write(`data: ${JSON.stringify(evt)}\n\n`);
428
+ });
429
+ const ping = setInterval(() => res.write(': ping\n\n'), 15000);
430
+ req.on('close', () => {
431
+ clearInterval(ping);
432
+ unsubscribe();
433
+ });
434
+ return;
435
+ }
436
+ // ── E2 · Per-agent readiness (onboarding surface) ─────────────────────────
437
+ // Full registry snapshot: install state + auth state + hints. Powers the
438
+ // setup-aware agent dropdown and the one-click swap flow on auth errors.
439
+ if (req.method === 'GET' && req.url === '/api/readiness/agents') {
440
+ try {
441
+ res.writeHead(200, { 'Content-Type': 'application/json' });
442
+ res.end(JSON.stringify({ agents: readinessForAllAgents() }));
443
+ }
444
+ catch (e) {
445
+ res.writeHead(500, { 'Content-Type': 'application/json' });
446
+ res.end(JSON.stringify({ error: 'readiness_check_failed', detail: String(e) }));
447
+ }
448
+ return;
449
+ }
450
+ // ── F1a readiness check ───────────────────────────────────────────────────
451
+ if (req.method === 'GET' && req.url === '/api/readiness') {
452
+ try {
453
+ const report = detectReadiness();
454
+ res.writeHead(200, { 'Content-Type': 'application/json' });
455
+ res.end(JSON.stringify(report));
456
+ }
457
+ catch (e) {
458
+ res.writeHead(500, { 'Content-Type': 'application/json' });
459
+ res.end(JSON.stringify({ error: 'readiness_check_failed', detail: String(e) }));
460
+ }
461
+ return;
462
+ }
463
+ // ── F1 task orchestration routes ─────────────────────────────────────────
464
+ // POST /api/task — { prompt, cwd? } → { sessionId }
465
+ // GET /api/tasks — list all sessions
466
+ // GET /api/task/:id/events — SSE stream
467
+ // POST /api/task/:id/kill — terminate
468
+ if (req.url && req.url.startsWith('/api/task')) {
469
+ const manager = SessionManager.getInstance();
470
+ if (req.method === 'POST' && req.url === '/api/task') {
471
+ let body = '';
472
+ req.on('data', (c) => {
473
+ body += c;
474
+ });
475
+ req.on('end', () => {
476
+ try {
477
+ const { prompt, cwd, agent } = JSON.parse(body || '{}');
478
+ if (!prompt || typeof prompt !== 'string') {
479
+ res.writeHead(400, { 'Content-Type': 'application/json' });
480
+ res.end(JSON.stringify({ error: 'prompt (string) required' }));
481
+ return;
482
+ }
483
+ // CRIT-5 fix · whitelist agent ID against the registry BEFORE any
484
+ // log/spawn. Stops log injection (`agent: "claude-code\n[FAKE]"`),
485
+ // future-proofs against partial-match exploits, and fails fast
486
+ // with a clear 400 instead of letting SessionManager.spawn throw.
487
+ const validAgentIds = new Set(allAdapters().map((a) => a.id));
488
+ const agentId = agent ?? 'claude-code';
489
+ if (typeof agentId !== 'string' || !validAgentIds.has(agentId)) {
490
+ res.writeHead(400, { 'Content-Type': 'application/json' });
491
+ res.end(JSON.stringify({
492
+ error: `Unknown agent: ${typeof agentId === 'string' ? agentId.slice(0, 64) : '<non-string>'}`,
493
+ valid: [...validAgentIds],
494
+ }));
495
+ return;
496
+ }
497
+ // Path-traversal + null-byte defense · cwd must be absolute and
498
+ // free of NUL bytes (some filesystems treat \0 as terminator).
499
+ if (cwd !== undefined) {
500
+ if (typeof cwd !== 'string' || cwd.includes('\0') || !isAbsolute(cwd)) {
501
+ res.writeHead(400, { 'Content-Type': 'application/json' });
502
+ res.end(JSON.stringify({ error: 'cwd must be an absolute path with no NUL bytes' }));
503
+ return;
504
+ }
505
+ }
506
+ const session = manager.spawn(prompt, agentId, { cwd });
507
+ res.writeHead(200, { 'Content-Type': 'application/json' });
508
+ res.end(JSON.stringify({
509
+ sessionId: session.sessionId,
510
+ startedAt: session.startedAt,
511
+ status: session.status,
512
+ agent: session.agent,
513
+ }));
514
+ }
515
+ catch (e) {
516
+ res.writeHead(400, { 'Content-Type': 'application/json' });
517
+ res.end(JSON.stringify({ error: 'Invalid JSON body', detail: String(e) }));
518
+ }
519
+ });
520
+ return;
521
+ }
522
+ if (req.method === 'GET' && req.url === '/api/tasks') {
523
+ const spawned = manager.list().map((s) => ({
524
+ sessionId: s.sessionId,
525
+ prompt: s.prompt,
526
+ startedAt: s.startedAt,
527
+ status: s.status,
528
+ agent: s.agent,
529
+ steps: s.steps ?? [],
530
+ artifacts: s.artifacts,
531
+ provenance: 'spawned',
532
+ }));
533
+ const activeAgents = manager.listActiveAgents();
534
+ const persisted = manager.listPersisted();
535
+ // Layer 7: timestamp of most recent hook-audit line (= "last edit on this machine")
536
+ let lastEditAt = null;
537
+ try {
538
+ if (existsSync(AUDIT_FILE))
539
+ lastEditAt = statSync(AUDIT_FILE).mtimeMs;
540
+ }
541
+ catch {
542
+ /* no audit yet — null */
543
+ }
544
+ res.writeHead(200, { 'Content-Type': 'application/json' });
545
+ res.end(JSON.stringify({
546
+ spawned,
547
+ persisted,
548
+ activeAgents,
549
+ lastEditAt,
550
+ sessions: spawned, // back-compat for earlier UI code
551
+ observed: manager.listObserved(), // legacy, kept for /history or future use
552
+ }));
553
+ return;
554
+ }
555
+ // /api/task/:id/events or /api/task/:id/kill
556
+ const m = req.url.match(/^\/api\/task\/([^/]+)\/(events|kill)$/);
557
+ if (m) {
558
+ const [, sessionId, verb] = m;
559
+ const session = manager.get(sessionId);
560
+ if (!session) {
561
+ res.writeHead(404, { 'Content-Type': 'application/json' });
562
+ res.end(JSON.stringify({ error: `No session: ${sessionId}` }));
563
+ return;
564
+ }
565
+ if (verb === 'events' && req.method === 'GET') {
566
+ res.writeHead(200, {
567
+ 'Content-Type': 'text/event-stream',
568
+ 'Cache-Control': 'no-cache',
569
+ Connection: 'keep-alive',
570
+ });
571
+ const unsubscribe = session.subscribe((event) => {
572
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
573
+ });
574
+ const ping = setInterval(() => res.write(': ping\n\n'), 15000);
575
+ req.on('close', () => {
576
+ clearInterval(ping);
577
+ unsubscribe();
578
+ });
579
+ return;
580
+ }
581
+ if (verb === 'kill' && req.method === 'POST') {
582
+ session.kill();
583
+ res.writeHead(200, { 'Content-Type': 'application/json' });
584
+ res.end(JSON.stringify({ sessionId, status: session.status }));
585
+ return;
586
+ }
587
+ }
588
+ res.writeHead(405, { 'Content-Type': 'application/json' });
589
+ res.end(JSON.stringify({ error: 'Method not allowed for this path' }));
254
590
  return;
255
591
  }
256
592
  // MCP endpoint
257
593
  if (req.method === 'POST' && req.url === '/mcp') {
258
594
  let body = '';
259
- req.on('data', chunk => { body += chunk; });
595
+ req.on('data', (chunk) => {
596
+ body += chunk;
597
+ });
260
598
  req.on('end', () => {
261
599
  try {
262
600
  const mcpReq = JSON.parse(body);
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Detect whether a given host:port is already serving a codehere instance.
3
+ *
4
+ * Used by `codehere start` when the listen() fails with EADDRINUSE: we'd
5
+ * rather give the user a friendly "codehere already running, opening browser"
6
+ * than a Node stack trace. If something ELSE holds the port we tell them to
7
+ * pick a different one.
8
+ *
9
+ * Approach: hit the existing server's /health endpoint. The codehere MCP
10
+ * server returns a JSON body with `server: 'codehere-mcp'` — no other tool
11
+ * uses that exact identifier. Generous 1.5-second timeout so a slow loopback
12
+ * doesn't false-negative.
13
+ */
14
+ export async function isCodehereOnPort(host, port, timeoutMs = 1500) {
15
+ const ac = new AbortController();
16
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
17
+ try {
18
+ const res = await fetch(`http://${host}:${port}/health`, { signal: ac.signal });
19
+ if (!res.ok)
20
+ return { isCodehere: false };
21
+ const body = (await res.json());
22
+ if (body?.server === 'codehere-mcp') {
23
+ return { isCodehere: true, version: body.version };
24
+ }
25
+ return { isCodehere: false };
26
+ }
27
+ catch {
28
+ // Connection refused, timeout, malformed response — anything that's not
29
+ // a positive codehere identification. Treat as "not codehere."
30
+ return { isCodehere: false };
31
+ }
32
+ finally {
33
+ clearTimeout(timer);
34
+ }
35
+ }
36
+ //# sourceMappingURL=probe-port.js.map