@bahulam/code 2.6.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 (273) hide show
  1. package/README.md +80 -0
  2. package/package.json +49 -0
  3. package/pulse/app/activity/page.tsx +190 -0
  4. package/pulse/app/api/activity/route.ts +138 -0
  5. package/pulse/app/api/benchmark/route.ts +113 -0
  6. package/pulse/app/api/benchmarks/route.ts +195 -0
  7. package/pulse/app/api/costs/route.ts +88 -0
  8. package/pulse/app/api/export/route.ts +77 -0
  9. package/pulse/app/api/history/route.ts +11 -0
  10. package/pulse/app/api/import/route.ts +31 -0
  11. package/pulse/app/api/memory/route.ts +50 -0
  12. package/pulse/app/api/plans/route.ts +9 -0
  13. package/pulse/app/api/projects/[slug]/route.ts +96 -0
  14. package/pulse/app/api/projects/route.ts +121 -0
  15. package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
  16. package/pulse/app/api/sessions/[id]/route.ts +31 -0
  17. package/pulse/app/api/sessions/route.ts +112 -0
  18. package/pulse/app/api/settings/route.ts +14 -0
  19. package/pulse/app/api/stats/route.ts +143 -0
  20. package/pulse/app/api/todos/route.ts +9 -0
  21. package/pulse/app/api/tools/route.ts +160 -0
  22. package/pulse/app/benchmarks/page.tsx +224 -0
  23. package/pulse/app/costs/page.tsx +179 -0
  24. package/pulse/app/export/page.tsx +465 -0
  25. package/pulse/app/favicon.ico +0 -0
  26. package/pulse/app/globals.css +263 -0
  27. package/pulse/app/help/page.tsx +143 -0
  28. package/pulse/app/history/page.tsx +157 -0
  29. package/pulse/app/layout.tsx +46 -0
  30. package/pulse/app/memory/page.tsx +365 -0
  31. package/pulse/app/overview-client.tsx +393 -0
  32. package/pulse/app/page.tsx +14 -0
  33. package/pulse/app/plans/page.tsx +308 -0
  34. package/pulse/app/projects/[slug]/page.tsx +390 -0
  35. package/pulse/app/projects/page.tsx +110 -0
  36. package/pulse/app/sessions/[id]/page.tsx +243 -0
  37. package/pulse/app/sessions/page.tsx +39 -0
  38. package/pulse/app/settings/page.tsx +188 -0
  39. package/pulse/app/todos/page.tsx +211 -0
  40. package/pulse/app/tools/page.tsx +249 -0
  41. package/pulse/cli.js +164 -0
  42. package/pulse/components/activity/day-of-week-chart.tsx +35 -0
  43. package/pulse/components/activity/streak-card.tsx +36 -0
  44. package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
  45. package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
  46. package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
  47. package/pulse/components/costs/model-token-table.tsx +60 -0
  48. package/pulse/components/global-search.tsx +193 -0
  49. package/pulse/components/keyboard-nav-provider.tsx +23 -0
  50. package/pulse/components/layout/bottom-nav.tsx +53 -0
  51. package/pulse/components/layout/client-layout.tsx +31 -0
  52. package/pulse/components/layout/sidebar-context.tsx +50 -0
  53. package/pulse/components/layout/sidebar.tsx +183 -0
  54. package/pulse/components/layout/top-bar.tsx +121 -0
  55. package/pulse/components/overview/activity-heatmap.tsx +107 -0
  56. package/pulse/components/overview/conversation-table.tsx +148 -0
  57. package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
  58. package/pulse/components/overview/peak-hours-chart.tsx +87 -0
  59. package/pulse/components/overview/project-activity-donut.tsx +96 -0
  60. package/pulse/components/overview/stat-card.tsx +102 -0
  61. package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
  62. package/pulse/components/projects/project-card.tsx +175 -0
  63. package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
  64. package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
  65. package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
  66. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
  67. package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
  68. package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
  69. package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
  70. package/pulse/components/sessions/session-badges.tsx +49 -0
  71. package/pulse/components/sessions/session-table.tsx +299 -0
  72. package/pulse/components/theme-provider.tsx +44 -0
  73. package/pulse/components/tools/feature-adoption-table.tsx +58 -0
  74. package/pulse/components/tools/mcp-server-panel.tsx +45 -0
  75. package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
  76. package/pulse/components/tools/version-history-table.tsx +32 -0
  77. package/pulse/components/ui/alert.tsx +66 -0
  78. package/pulse/components/ui/badge.tsx +48 -0
  79. package/pulse/components/ui/breadcrumb.tsx +109 -0
  80. package/pulse/components/ui/button.tsx +64 -0
  81. package/pulse/components/ui/calendar.tsx +220 -0
  82. package/pulse/components/ui/card.tsx +92 -0
  83. package/pulse/components/ui/command.tsx +158 -0
  84. package/pulse/components/ui/dialog.tsx +158 -0
  85. package/pulse/components/ui/input.tsx +21 -0
  86. package/pulse/components/ui/popover.tsx +89 -0
  87. package/pulse/components/ui/progress.tsx +31 -0
  88. package/pulse/components/ui/select.tsx +190 -0
  89. package/pulse/components/ui/separator.tsx +28 -0
  90. package/pulse/components/ui/sheet.tsx +143 -0
  91. package/pulse/components/ui/skeleton.tsx +13 -0
  92. package/pulse/components/ui/table.tsx +116 -0
  93. package/pulse/components/ui/tabs.tsx +91 -0
  94. package/pulse/components/ui/tooltip.tsx +57 -0
  95. package/pulse/components/use-global-keyboard-nav.ts +79 -0
  96. package/pulse/components.json +23 -0
  97. package/pulse/eslint.config.mjs +18 -0
  98. package/pulse/lib/bahulam-paths.ts +23 -0
  99. package/pulse/lib/claude-reader.ts +592 -0
  100. package/pulse/lib/decode.ts +129 -0
  101. package/pulse/lib/pricing.ts +102 -0
  102. package/pulse/lib/replay-parser.ts +165 -0
  103. package/pulse/lib/tool-categories.ts +127 -0
  104. package/pulse/lib/utils.ts +6 -0
  105. package/pulse/next-env.d.ts +6 -0
  106. package/pulse/next.config.ts +16 -0
  107. package/pulse/package.json +45 -0
  108. package/pulse/postcss.config.mjs +7 -0
  109. package/pulse/public/activity.png +0 -0
  110. package/pulse/public/cc-lens.png +0 -0
  111. package/pulse/public/command-k.png +0 -0
  112. package/pulse/public/costs.png +0 -0
  113. package/pulse/public/dashboard-dark.png +0 -0
  114. package/pulse/public/dashboard-white.png +0 -0
  115. package/pulse/public/export.png +0 -0
  116. package/pulse/public/file.svg +1 -0
  117. package/pulse/public/globe.svg +1 -0
  118. package/pulse/public/next.svg +1 -0
  119. package/pulse/public/projects.png +0 -0
  120. package/pulse/public/session-chat.png +0 -0
  121. package/pulse/public/todos.png +0 -0
  122. package/pulse/public/tools.png +0 -0
  123. package/pulse/public/vercel.svg +1 -0
  124. package/pulse/public/window.svg +1 -0
  125. package/pulse/tsconfig.json +34 -0
  126. package/pulse/types/claude.ts +294 -0
  127. package/src/agents/loader.mjs +94 -0
  128. package/src/agents/multi_workflow_loader.mjs +330 -0
  129. package/src/agents/parser.mjs +205 -0
  130. package/src/agents/scaffold.mjs +222 -0
  131. package/src/agents/teams.mjs +123 -0
  132. package/src/agents/workflow_loader.mjs +122 -0
  133. package/src/agents/workflow_scaffold.mjs +249 -0
  134. package/src/auth/oauth.mjs +220 -0
  135. package/src/auth/tarang-auth.mjs +306 -0
  136. package/src/commands/agent.mjs +220 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +200 -0
  139. package/src/config/env.mjs +263 -0
  140. package/src/config/hook-runner.mjs +100 -0
  141. package/src/config/memory-loader.mjs +32 -0
  142. package/src/config/settings-loader.mjs +45 -0
  143. package/src/config/settings.mjs +132 -0
  144. package/src/context/ast-parser.mjs +298 -0
  145. package/src/context/bm25.mjs +85 -0
  146. package/src/context/retriever.mjs +308 -0
  147. package/src/context/skeleton.mjs +134 -0
  148. package/src/context/symbol-indexer.mjs +375 -0
  149. package/src/core/agent-history.mjs +111 -0
  150. package/src/core/agent-loop.mjs +486 -0
  151. package/src/core/approval-log.mjs +104 -0
  152. package/src/core/approval.mjs +476 -0
  153. package/src/core/attachments.mjs +380 -0
  154. package/src/core/backend-url.mjs +55 -0
  155. package/src/core/cache-control.mjs +92 -0
  156. package/src/core/cache.mjs +105 -0
  157. package/src/core/callback-client.mjs +180 -0
  158. package/src/core/checkpoints.mjs +142 -0
  159. package/src/core/compact-history.mjs +127 -0
  160. package/src/core/context-envelope.mjs +54 -0
  161. package/src/core/context-manager.mjs +198 -0
  162. package/src/core/error-guidance.mjs +311 -0
  163. package/src/core/file-diff.mjs +217 -0
  164. package/src/core/headless.mjs +448 -0
  165. package/src/core/hooks-manager.mjs +87 -0
  166. package/src/core/jsonl-writer.mjs +449 -0
  167. package/src/core/local-agent.mjs +537 -0
  168. package/src/core/local-store.mjs +836 -0
  169. package/src/core/mode-selector.mjs +51 -0
  170. package/src/core/output-filter.mjs +177 -0
  171. package/src/core/paths.mjs +190 -0
  172. package/src/core/policy-resolver.mjs +156 -0
  173. package/src/core/pricing.mjs +336 -0
  174. package/src/core/project-artifacts.mjs +39 -0
  175. package/src/core/project-context-loader.mjs +139 -0
  176. package/src/core/providers.mjs +219 -0
  177. package/src/core/rate-limit-display.mjs +121 -0
  178. package/src/core/rate-limiter.mjs +119 -0
  179. package/src/core/resume-mode.mjs +192 -0
  180. package/src/core/risk-tier.mjs +337 -0
  181. package/src/core/safety.mjs +203 -0
  182. package/src/core/scheduler.mjs +173 -0
  183. package/src/core/session-manager.mjs +360 -0
  184. package/src/core/session.mjs +143 -0
  185. package/src/core/settings-sync.mjs +85 -0
  186. package/src/core/stagnation.mjs +57 -0
  187. package/src/core/stream-client.mjs +829 -0
  188. package/src/core/streaming.mjs +182 -0
  189. package/src/core/system-prompt.mjs +140 -0
  190. package/src/core/tasks.mjs +196 -0
  191. package/src/core/tool-executor.mjs +1950 -0
  192. package/src/core/trust.mjs +158 -0
  193. package/src/core/work-scope.mjs +248 -0
  194. package/src/hooks/engine.mjs +162 -0
  195. package/src/index.mjs +426 -0
  196. package/src/mcp/client.mjs +253 -0
  197. package/src/mcp/transport-shttp.mjs +130 -0
  198. package/src/mcp/transport-sse.mjs +131 -0
  199. package/src/mcp/transport-ws.mjs +134 -0
  200. package/src/onboarding/preflight.mjs +360 -0
  201. package/src/permissions/checker.mjs +57 -0
  202. package/src/permissions/command-classifier.mjs +652 -0
  203. package/src/permissions/injection-check.mjs +60 -0
  204. package/src/permissions/path-check.mjs +102 -0
  205. package/src/permissions/prompt.mjs +73 -0
  206. package/src/permissions/sandbox.mjs +112 -0
  207. package/src/plugins/loader.mjs +138 -0
  208. package/src/skills/installer.mjs +188 -0
  209. package/src/skills/loader.mjs +252 -0
  210. package/src/skills/runner.mjs +55 -0
  211. package/src/state/orbit.mjs +263 -0
  212. package/src/state/verbosity.mjs +99 -0
  213. package/src/telemetry/index.mjs +96 -0
  214. package/src/terminal/agents.mjs +177 -0
  215. package/src/terminal/analytics.mjs +292 -0
  216. package/src/terminal/ansi.mjs +695 -0
  217. package/src/terminal/init.mjs +145 -0
  218. package/src/terminal/main.mjs +269 -0
  219. package/src/terminal/repl-explore.mjs +35 -0
  220. package/src/terminal/repl-format.mjs +257 -0
  221. package/src/terminal/repl-render.mjs +561 -0
  222. package/src/terminal/repl-resume.mjs +625 -0
  223. package/src/terminal/repl-state.mjs +103 -0
  224. package/src/terminal/repl-utils.mjs +34 -0
  225. package/src/terminal/repl.mjs +3832 -0
  226. package/src/terminal/skills.mjs +54 -0
  227. package/src/terminal/tool-display.mjs +240 -0
  228. package/src/tools/agent.mjs +137 -0
  229. package/src/tools/ask-user.mjs +61 -0
  230. package/src/tools/bash.mjs +231 -0
  231. package/src/tools/cron-create.mjs +120 -0
  232. package/src/tools/cron-delete.mjs +49 -0
  233. package/src/tools/cron-list.mjs +37 -0
  234. package/src/tools/edit.mjs +82 -0
  235. package/src/tools/enter-worktree.mjs +69 -0
  236. package/src/tools/exit-worktree.mjs +57 -0
  237. package/src/tools/glob.mjs +117 -0
  238. package/src/tools/grep.mjs +129 -0
  239. package/src/tools/lint.mjs +71 -0
  240. package/src/tools/ls.mjs +58 -0
  241. package/src/tools/lsp.mjs +115 -0
  242. package/src/tools/multi-edit.mjs +94 -0
  243. package/src/tools/notebook-edit.mjs +96 -0
  244. package/src/tools/project-overview.mjs +641 -0
  245. package/src/tools/read-mcp-resource.mjs +57 -0
  246. package/src/tools/read.mjs +138 -0
  247. package/src/tools/registry.mjs +116 -0
  248. package/src/tools/remote-trigger.mjs +84 -0
  249. package/src/tools/send-message.mjs +64 -0
  250. package/src/tools/skill.mjs +52 -0
  251. package/src/tools/test-runner.mjs +49 -0
  252. package/src/tools/todo-write.mjs +68 -0
  253. package/src/tools/tool-search.mjs +77 -0
  254. package/src/tools/web-fetch.mjs +65 -0
  255. package/src/tools/web-search.mjs +89 -0
  256. package/src/tools/write.mjs +55 -0
  257. package/src/ui/approval.mjs +263 -0
  258. package/src/ui/banner.mjs +235 -0
  259. package/src/ui/commands.mjs +537 -0
  260. package/src/ui/formatter.mjs +409 -0
  261. package/src/ui/icons.mjs +164 -0
  262. package/src/ui/input-dock.mjs +444 -0
  263. package/src/ui/markdown.mjs +278 -0
  264. package/src/ui/mission-report.mjs +296 -0
  265. package/src/ui/palette.mjs +189 -0
  266. package/src/ui/slash-commands.mjs +245 -0
  267. package/src/ui/spinner.mjs +116 -0
  268. package/src/ui/sub-agent.mjs +152 -0
  269. package/src/ui/term.mjs +159 -0
  270. package/src/ui/text-layout.mjs +127 -0
  271. package/src/ui/tool-card.mjs +463 -0
  272. package/src/ui/tool-details.mjs +312 -0
  273. package/src/ui/transcript-block.mjs +21 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Mode Selector
3
+ *
4
+ * remote (default): All requests go to Tarang backend.
5
+ * Backend handles orchestration, model selection, tool routing.
6
+ * User's provider and models configured via web Settings page.
7
+ *
8
+ * local: For local LLMs (Ollama, LM Studio, etc.)
9
+ * Direct API call, no backend. Only when user explicitly opts in.
10
+ */
11
+
12
+ let _probeCache = { available: null, timestamp: 0 };
13
+ const PROBE_CACHE_TTL = 60_000; // 60s
14
+
15
+ export async function selectMode(instruction, options, config) {
16
+ // Explicit --local flag: user wants local LLM
17
+ if (options.local) return 'local';
18
+
19
+ // Everything else goes to the backend
20
+ return 'remote';
21
+ }
22
+
23
+ export async function probeBackend(url) {
24
+ if (!url) return false;
25
+ const now = Date.now();
26
+ if (_probeCache.available !== null && (now - _probeCache.timestamp) < PROBE_CACHE_TTL) {
27
+ return _probeCache.available;
28
+ }
29
+ try {
30
+ const resp = await fetch(`${url}/health`, { signal: AbortSignal.timeout(2000) });
31
+ _probeCache = { available: resp.ok, timestamp: now };
32
+ return resp.ok;
33
+ } catch {
34
+ _probeCache = { available: false, timestamp: now };
35
+ return false;
36
+ }
37
+ }
38
+
39
+ export function classifyTask(instruction) {
40
+ if (!instruction) return 'simple';
41
+ const isSimple = SIMPLE_PATTERNS.some(p => p.test(instruction));
42
+ const isComplex = COMPLEX_PATTERNS.some(p => p.test(instruction));
43
+ if (isComplex && !isSimple) return 'complex';
44
+ if (isSimple && !isComplex) return 'simple';
45
+ return 'medium'; // ambiguous → default to remote when available
46
+ }
47
+
48
+ /** Reset probe cache (for testing). */
49
+ export function resetProbeCache() {
50
+ _probeCache = { available: null, timestamp: 0 };
51
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Output Filter — Smart shell output filtering + auto-lint.
3
+ * Ported from tarang-cli (Python) ws/executor.py with enhanced patterns.
4
+ */
5
+
6
+ import * as path from 'node:path';
7
+ import * as fs from 'node:fs';
8
+ import { execSync } from 'node:child_process';
9
+
10
+ // ── Command Classification ──────────────────────────────────
11
+
12
+ const COMMAND_PROFILES = {
13
+ install: {
14
+ patterns: [/pip install/i, /npm install/i, /yarn add/i, /pnpm add/i, /cargo add/i, /go get/i, /brew install/i, /apt install/i],
15
+ successLimit: 500,
16
+ failureLimit: 2000,
17
+ noisePatterns: [
18
+ /^Collecting \S+/,
19
+ /^Downloading \S+/,
20
+ /^Installing collected/,
21
+ /^Successfully installed/,
22
+ /^━+/, // Progress bars
23
+ /^\s*\d+%\s*\|/, // Percentage bars
24
+ /^Using cached/,
25
+ /^Requirement already satisfied/,
26
+ /^added \d+ packages?/,
27
+ /^up to date/,
28
+ /^npm WARN/,
29
+ /^npm notice/,
30
+ /^\s*$/, // Empty lines
31
+ ],
32
+ keepPatterns: [/error/i, /failed/i, /WARN(?:ING)?/i, /not found/i, /permission denied/i],
33
+ },
34
+ test: {
35
+ patterns: [/pytest/i, /npm test/i, /cargo test/i, /go test/i, /jest/i, /vitest/i, /mocha/i],
36
+ successLimit: 2000,
37
+ failureLimit: 8000,
38
+ noisePatterns: [
39
+ /^\.+$/, // Lines of dots (pytest progress)
40
+ /^PASSED/,
41
+ /^\s*✓/, // Checkmarks
42
+ /^\s*$/,
43
+ ],
44
+ keepPatterns: [/FAILED/i, /FAIL/i, /Error/i, /AssertionError/i, /Expected/i, /Actual/i, /✗/, /✘/],
45
+ },
46
+ build: {
47
+ patterns: [/npm run build/i, /cargo build/i, /go build/i, /tsc/i, /webpack/i, /vite build/i, /make\b/i, /next build/i],
48
+ successLimit: 1000,
49
+ failureLimit: 6000,
50
+ noisePatterns: [
51
+ /^Compiling \S+/,
52
+ /^Finished \S+ target/,
53
+ /^\s*$/,
54
+ ],
55
+ keepPatterns: [/error/i, /warning/i],
56
+ },
57
+ run: {
58
+ patterns: [/python\s/i, /node\s/i, /go run/i, /cargo run/i, /npm start/i, /npm run dev/i],
59
+ successLimit: 4000,
60
+ failureLimit: 8000,
61
+ noisePatterns: [],
62
+ keepPatterns: [],
63
+ },
64
+ default: {
65
+ patterns: [],
66
+ successLimit: 3000,
67
+ failureLimit: 6000,
68
+ noisePatterns: [/^\s*$/],
69
+ keepPatterns: [],
70
+ },
71
+ };
72
+
73
+ /** Detect shell command type for smart filtering. */
74
+ export function detectCommandType(command) {
75
+ if (!command) return 'default';
76
+ for (const [type, profile] of Object.entries(COMMAND_PROFILES)) {
77
+ if (type === 'default') continue;
78
+ if (profile.patterns.some(p => p.test(command))) return type;
79
+ }
80
+ return 'default';
81
+ }
82
+
83
+ // ── Output Filtering ────────────────────────────────────────
84
+
85
+ /**
86
+ * Filter shell output based on command type.
87
+ * Reduces noise from install/build while preserving errors and useful output.
88
+ *
89
+ * @param {string} output - Raw shell output
90
+ * @param {string} command - The command that was run
91
+ * @param {boolean} success - Whether the command succeeded
92
+ * @returns {{ output: string, commandType: string, truncated: boolean, originalLines: number, filteredLines: number }}
93
+ */
94
+ export function filterOutput(output, command, success = true) {
95
+ if (!output) return { output: '', commandType: 'default', truncated: false, originalLines: 0, filteredLines: 0 };
96
+
97
+ const type = detectCommandType(command);
98
+ const profile = COMMAND_PROFILES[type];
99
+ const limit = success ? profile.successLimit : profile.failureLimit;
100
+ const lines = output.split('\n');
101
+ const originalLines = lines.length;
102
+
103
+ let filteredLines = [];
104
+
105
+ for (const line of lines) {
106
+ // Always keep lines matching keep patterns (errors, failures)
107
+ const shouldKeep = profile.keepPatterns.length > 0 &&
108
+ profile.keepPatterns.some(p => p.test(line));
109
+
110
+ // Filter out noise patterns
111
+ const isNoise = !shouldKeep && profile.noisePatterns.length > 0 &&
112
+ profile.noisePatterns.some(p => p.test(line));
113
+
114
+ if (shouldKeep || !isNoise) {
115
+ filteredLines.push(line);
116
+ }
117
+ }
118
+
119
+ let filteredOutput = filteredLines.join('\n');
120
+ let truncated = false;
121
+
122
+ // Truncate to limit
123
+ if (filteredOutput.length > limit) {
124
+ filteredOutput = filteredOutput.slice(0, limit);
125
+ const lastNewline = filteredOutput.lastIndexOf('\n');
126
+ if (lastNewline > 0) {
127
+ filteredOutput = filteredOutput.slice(0, lastNewline);
128
+ }
129
+ filteredOutput += '\n... (truncated)';
130
+ truncated = true;
131
+ }
132
+
133
+ return {
134
+ output: filteredOutput,
135
+ commandType: type,
136
+ truncated,
137
+ originalLines,
138
+ filteredLines: filteredLines.length,
139
+ };
140
+ }
141
+
142
+ // ── Auto-Lint ───────────────────────────────────────────────
143
+
144
+ /** Auto-lint a file after write/edit. Returns lint output or null. */
145
+ export function autoLint(filePath) {
146
+ if (!filePath || !fs.existsSync(filePath)) return null;
147
+ const ext = path.extname(filePath);
148
+ let cmd;
149
+
150
+ try {
151
+ switch (ext) {
152
+ case '.py':
153
+ cmd = `python3 -m py_compile "${filePath}" 2>&1`;
154
+ break;
155
+ case '.js': case '.mjs': case '.cjs':
156
+ cmd = `node --check "${filePath}" 2>&1`;
157
+ break;
158
+ case '.ts': case '.tsx':
159
+ if (fs.existsSync('node_modules/.bin/tsc'))
160
+ cmd = `npx tsc --noEmit --pretty "${filePath}" 2>&1`;
161
+ break;
162
+ case '.go':
163
+ cmd = `go vet "${filePath}" 2>&1`;
164
+ break;
165
+ case '.rs':
166
+ cmd = `rustfmt --check "${filePath}" 2>&1`;
167
+ break;
168
+ }
169
+
170
+ if (!cmd) return null;
171
+ const output = execSync(cmd, { stdio: 'pipe', timeout: 15_000, encoding: 'utf-8' });
172
+ return output.trim() || null;
173
+ } catch (err) {
174
+ const output = (err.stderr || err.stdout || '').toString().trim();
175
+ return output || null;
176
+ }
177
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Bahulam Code Paths — centralized path resolution for all CLI data.
3
+ *
4
+ * Everything lives under ~/.bahulam/:
5
+ * ~/.bahulam/
6
+ * config.json — auth credentials + settings
7
+ * history.jsonl — prompt history
8
+ * hooks.json — global hooks
9
+ * conversations/ — conversation JSONL files
10
+ * projects/
11
+ * {hash}/ — per-project data (hash of project path)
12
+ * index/ — BM25 search index
13
+ * checkpoints/ — file undo checkpoints
14
+ * state.json — current session state
15
+ * sessions/ — session metadata archive
16
+ * hooks.json — project-specific hooks
17
+ * projects.json — slug → project path mapping
18
+ *
19
+ * ── Legacy fallback ─────────────────────────────────────────────────────
20
+ * Pre-rename installs stored everything under ~/.kepler/. The resolver below
21
+ * prefers the new path but falls back to the legacy directory when it
22
+ * exists and the new one doesn't, so existing users keep their config,
23
+ * agents, workflows, and history until they explicitly migrate.
24
+ *
25
+ * Env vars:
26
+ * BAHULAM_HOME preferred; explicit override for ~/.bahulam
27
+ * KEPLER_HOME legacy; still honored for backward compat
28
+ */
29
+
30
+ import * as fs from 'node:fs';
31
+ import * as path from 'node:path';
32
+ import * as os from 'node:os';
33
+ import * as crypto from 'node:crypto';
34
+
35
+ const NEW_HOME_NAME = '.bahulam';
36
+ const LEGACY_HOME_NAME = '.kepler';
37
+
38
+ let _legacyNoticeShown = false;
39
+
40
+ /**
41
+ * Resolve the CLI home directory. Priority:
42
+ * 1. $BAHULAM_HOME (explicit new)
43
+ * 2. $KEPLER_HOME (explicit legacy — prints a one-time deprecation notice)
44
+ * 3. ~/.bahulam (if it exists)
45
+ * 4. ~/.kepler (if it exists — prints a one-time migration hint)
46
+ * 5. ~/.bahulam (fresh install, will be created on first write)
47
+ */
48
+ function resolveHome() {
49
+ if (process.env.BAHULAM_HOME) return process.env.BAHULAM_HOME;
50
+ if (process.env.KEPLER_HOME) {
51
+ maybeNoticeLegacyEnv();
52
+ return process.env.KEPLER_HOME;
53
+ }
54
+ const home = os.homedir();
55
+ const newPath = path.join(home, NEW_HOME_NAME);
56
+ const legacyPath = path.join(home, LEGACY_HOME_NAME);
57
+ try {
58
+ if (fs.existsSync(newPath)) return newPath;
59
+ } catch {}
60
+ try {
61
+ if (fs.existsSync(legacyPath)) {
62
+ maybeNoticeLegacyDir(legacyPath, newPath);
63
+ return legacyPath;
64
+ }
65
+ } catch {}
66
+ return newPath;
67
+ }
68
+
69
+ function maybeNoticeLegacyEnv() {
70
+ if (_legacyNoticeShown || process.env.B0_QUIET_MIGRATION === '1') return;
71
+ _legacyNoticeShown = true;
72
+ try {
73
+ process.stderr.write(
74
+ ' \x1b[2mnote: KEPLER_HOME is deprecated; set BAHULAM_HOME instead.\x1b[0m\n'
75
+ );
76
+ } catch {}
77
+ }
78
+
79
+ function maybeNoticeLegacyDir(legacyPath, newPath) {
80
+ if (_legacyNoticeShown || process.env.B0_QUIET_MIGRATION === '1') return;
81
+ _legacyNoticeShown = true;
82
+ try {
83
+ process.stderr.write(
84
+ ` \x1b[2mnote: reading legacy ${legacyPath}. Move to ${newPath} when convenient (silence with B0_QUIET_MIGRATION=1).\x1b[0m\n`
85
+ );
86
+ } catch {}
87
+ }
88
+
89
+ /**
90
+ * Hash a project path to a short directory name.
91
+ * Uses first 16 chars of SHA-256 (same as Claude Code).
92
+ */
93
+ export function projectHash(projectDir) {
94
+ // Resolve symlinks (macOS: /tmp → /private/tmp) so the hash is stable
95
+ let resolved = projectDir;
96
+ try {
97
+ resolved = fs.realpathSync(projectDir);
98
+ } catch {
99
+ // realpathSync fails if path doesn't exist yet — use as-is
100
+ }
101
+ return crypto.createHash('sha256')
102
+ .update(resolved)
103
+ .digest('hex')
104
+ .slice(0, 16);
105
+ }
106
+
107
+ /** Root ~/.bahulam/ directory (or legacy ~/.kepler/ if that's what's present). */
108
+ export function bahulamHome() {
109
+ return resolveHome();
110
+ }
111
+
112
+ /** Backward-compat alias. Prefer `bahulamHome()` in new code. */
113
+ export const keplerHome = bahulamHome;
114
+
115
+ /** ~/.bahulam/projects/{hash}/ for a given project path. */
116
+ export function projectDir(projectPath) {
117
+ return path.join(bahulamHome(), 'projects', projectHash(projectPath));
118
+ }
119
+
120
+ /** ~/.bahulam/projects/{hash}/index/ — BM25 search index. */
121
+ export function indexDir(projectPath) {
122
+ return path.join(projectDir(projectPath), 'index');
123
+ }
124
+
125
+ /** ~/.bahulam/projects/{hash}/checkpoints/ — file undo. */
126
+ export function checkpointsDir(projectPath) {
127
+ return path.join(projectDir(projectPath), 'checkpoints');
128
+ }
129
+
130
+ /** ~/.bahulam/projects/{hash}/state.json — current session. */
131
+ export function statePath(projectPath) {
132
+ return path.join(projectDir(projectPath), 'state.json');
133
+ }
134
+
135
+ /** ~/.bahulam/projects/{hash}/sessions/ — session archive. */
136
+ export function sessionsDir(projectPath) {
137
+ return path.join(projectDir(projectPath), 'sessions');
138
+ }
139
+
140
+ /** ~/.bahulam/projects/{hash}/hooks.json — project hooks. */
141
+ export function projectHooksPath(projectPath) {
142
+ return path.join(projectDir(projectPath), 'hooks.json');
143
+ }
144
+
145
+ /** ~/.bahulam/conversations/ — central conversation storage. */
146
+ export function conversationsDir() {
147
+ return path.join(bahulamHome(), 'conversations');
148
+ }
149
+
150
+ /** ~/.bahulam/conversations/{sessionId}.jsonl */
151
+ export function conversationPath(sessionId) {
152
+ return path.join(conversationsDir(), `${sessionId}.jsonl`);
153
+ }
154
+
155
+ /** ~/.bahulam/hooks.json — global hooks. */
156
+ export function globalHooksPath() {
157
+ return path.join(bahulamHome(), 'hooks.json');
158
+ }
159
+
160
+ /** ~/.bahulam/history.jsonl — prompt history. */
161
+ export function historyPath() {
162
+ return path.join(bahulamHome(), 'history.jsonl');
163
+ }
164
+
165
+ // ── Project-local config directory (.bahulam/ next to CLAUDE.md/etc) ────
166
+ //
167
+ // Project-scoped stuff (agents/*.yaml, memory/*.md, hooks/, settings.json,
168
+ // tasks/) used to live in .kepler/ inside the project. Same resolver logic
169
+ // applies — prefer .bahulam/, fall back to .kepler/ when only the legacy
170
+ // dir exists.
171
+
172
+ const PROJECT_NEW_NAME = '.bahulam';
173
+ const PROJECT_LEGACY_NAME = '.kepler';
174
+
175
+ /**
176
+ * Resolve the project-local config directory for `cwd`. Same priority as
177
+ * the home resolver. Returns an absolute path; the directory may not
178
+ * exist yet (callers that write should mkdir -p first).
179
+ */
180
+ export function projectConfigDir(cwd = process.cwd()) {
181
+ const newPath = path.join(cwd, PROJECT_NEW_NAME);
182
+ const legacyPath = path.join(cwd, PROJECT_LEGACY_NAME);
183
+ try {
184
+ if (fs.existsSync(newPath)) return newPath;
185
+ } catch {}
186
+ try {
187
+ if (fs.existsSync(legacyPath)) return legacyPath;
188
+ } catch {}
189
+ return newPath;
190
+ }
@@ -0,0 +1,156 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+
5
+ export const DEFAULT_POLICY = Object.freeze({
6
+ version: 1,
7
+ context: {
8
+ loadEveryTurn: ['KEPLER.md', 'project.md', 'style.md', 'goal.md', 'plan.md', 'tasks/*.md'],
9
+ showReloadNotice: true,
10
+ injectCommandOptions: true,
11
+ injectActionableTips: true,
12
+ tipTtlTurns: 2,
13
+ },
14
+ planning: {
15
+ owner: 'auto',
16
+ allowedOwners: ['main_agent', 'planner_subagent', 'auto', 'manual'],
17
+ delegateWhen: {
18
+ taskCountGte: 4,
19
+ estimatedFilesGte: 6,
20
+ touchesMultiplePackages: true,
21
+ },
22
+ onUserEditedPlan: 'prefer_user_plan',
23
+ },
24
+ tasks: {
25
+ storage: 'project_markdown',
26
+ syncTodoWrite: true,
27
+ resumePrompt: true,
28
+ },
29
+ commands: {
30
+ enabled: ['map', 'probe', 'footprint', 'heal', 'align', 'distill', 'brief', 'rewind'],
31
+ defaultMode: 'interactive',
32
+ dryRunDefault: false,
33
+ suggestions: true,
34
+ suggestOnlyWhenActionable: true,
35
+ timeouts: {
36
+ defaultSeconds: 300,
37
+ healSeconds: 600,
38
+ probeSeconds: 180,
39
+ briefSeconds: 60,
40
+ },
41
+ aliases: {
42
+ fix: 'heal',
43
+ repair: 'heal',
44
+ search: 'probe',
45
+ summarize: 'brief',
46
+ },
47
+ },
48
+ hitl: {
49
+ defaultScope: 'once',
50
+ allowSessionTrust: true,
51
+ allowProjectTrust: false,
52
+ reaskAfterMinutes: 30,
53
+ reaskOnCommandShapeChange: true,
54
+ reaskOnRiskIncrease: true,
55
+ reaskOnPathBoundaryChange: true,
56
+ alwaysAskForDangerous: true,
57
+ },
58
+ hooks: {
59
+ timeoutSeconds: 5,
60
+ },
61
+ ui: {
62
+ recentActions: true,
63
+ verbosity: 'normal',
64
+ },
65
+ });
66
+
67
+ export function deepClone(value) {
68
+ return JSON.parse(JSON.stringify(value));
69
+ }
70
+
71
+ export function deepMerge(target, source) {
72
+ if (!source || typeof source !== 'object') return deepClone(target);
73
+ const result = Array.isArray(target) ? [...target] : { ...(target || {}) };
74
+ for (const [key, value] of Object.entries(source)) {
75
+ if (
76
+ value &&
77
+ typeof value === 'object' &&
78
+ !Array.isArray(value) &&
79
+ result[key] &&
80
+ typeof result[key] === 'object' &&
81
+ !Array.isArray(result[key])
82
+ ) {
83
+ result[key] = deepMerge(result[key], value);
84
+ } else {
85
+ result[key] = deepClone(value);
86
+ }
87
+ }
88
+ return result;
89
+ }
90
+
91
+ function readJson(filePath) {
92
+ try {
93
+ if (!fs.existsSync(filePath)) return null;
94
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
95
+ } catch (err) {
96
+ return { __error: err.message };
97
+ }
98
+ }
99
+
100
+ function projectConfigPath(cwd) {
101
+ return path.join(cwd, '.bahulam', 'config.json');
102
+ }
103
+
104
+ function globalPolicyPath() {
105
+ return path.join(os.homedir(), '.bahulam', 'policy.json');
106
+ }
107
+
108
+ function flatten(obj, prefix = '', out = []) {
109
+ for (const [key, value] of Object.entries(obj || {})) {
110
+ const dotted = prefix ? `${prefix}.${key}` : key;
111
+ if (value && typeof value === 'object' && !Array.isArray(value)) flatten(value, dotted, out);
112
+ else out.push([dotted, value]);
113
+ }
114
+ return out;
115
+ }
116
+
117
+ export function loadEffectivePolicy({ cwd = process.cwd(), cli = {}, session = {} } = {}) {
118
+ const layers = [
119
+ { name: 'default', path: null, data: deepClone(DEFAULT_POLICY) },
120
+ ];
121
+
122
+ const global = readJson(globalPolicyPath());
123
+ if (global && !global.__error) layers.push({ name: 'global', path: globalPolicyPath(), data: global });
124
+ else if (global?.__error) layers.push({ name: 'global', path: globalPolicyPath(), error: global.__error, data: {} });
125
+
126
+ const project = readJson(projectConfigPath(cwd));
127
+ if (project && !project.__error) layers.push({ name: 'project', path: projectConfigPath(cwd), data: project });
128
+ else if (project?.__error) layers.push({ name: 'project', path: projectConfigPath(cwd), error: project.__error, data: {} });
129
+
130
+ if (session && Object.keys(session).length > 0) layers.push({ name: 'session', path: null, data: session });
131
+ if (cli && Object.keys(cli).length > 0) layers.push({ name: 'cli', path: null, data: cli });
132
+
133
+ let policy = {};
134
+ const sources = {};
135
+ for (const layer of layers) {
136
+ policy = deepMerge(policy, layer.data || {});
137
+ for (const [key, value] of flatten(layer.data || {})) {
138
+ sources[key] = { source: layer.name, path: layer.path, value };
139
+ }
140
+ }
141
+
142
+ return { policy, sources, layers };
143
+ }
144
+
145
+ export function formatPolicySourceRows(effective) {
146
+ const rows = [];
147
+ for (const [key, meta] of Object.entries(effective?.sources || {}).sort()) {
148
+ rows.push({
149
+ key,
150
+ source: meta.source,
151
+ path: meta.path || '',
152
+ value: meta.value,
153
+ });
154
+ }
155
+ return rows;
156
+ }