@bahulam/code 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/README.md +93 -0
  2. package/package.json +56 -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 +223 -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 +312 -0
  136. package/src/commands/agent.mjs +221 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +202 -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/model-catalog.mjs +57 -0
  143. package/src/config/settings-loader.mjs +45 -0
  144. package/src/config/settings.mjs +132 -0
  145. package/src/context/ast-parser.mjs +298 -0
  146. package/src/context/bm25.mjs +85 -0
  147. package/src/context/prose-chunker.mjs +255 -0
  148. package/src/context/retriever.mjs +425 -0
  149. package/src/context/skeleton.mjs +134 -0
  150. package/src/context/symbol-indexer.mjs +375 -0
  151. package/src/core/agent-history.mjs +111 -0
  152. package/src/core/agent-loop.mjs +486 -0
  153. package/src/core/approval-log.mjs +145 -0
  154. package/src/core/approval.mjs +700 -0
  155. package/src/core/attachments.mjs +666 -0
  156. package/src/core/backend-url.mjs +68 -0
  157. package/src/core/bundled-runtime.mjs +418 -0
  158. package/src/core/cache-control.mjs +92 -0
  159. package/src/core/cache.mjs +105 -0
  160. package/src/core/callback-client.mjs +180 -0
  161. package/src/core/checkpoints.mjs +142 -0
  162. package/src/core/compact-history.mjs +127 -0
  163. package/src/core/context-envelope.mjs +54 -0
  164. package/src/core/context-manager.mjs +198 -0
  165. package/src/core/error-guidance.mjs +331 -0
  166. package/src/core/file-diff.mjs +217 -0
  167. package/src/core/headless.mjs +460 -0
  168. package/src/core/hooks-manager.mjs +87 -0
  169. package/src/core/jsonl-writer.mjs +449 -0
  170. package/src/core/local-agent.mjs +538 -0
  171. package/src/core/local-store.mjs +836 -0
  172. package/src/core/mode-selector.mjs +51 -0
  173. package/src/core/output-filter.mjs +177 -0
  174. package/src/core/paths.mjs +190 -0
  175. package/src/core/policy-resolver.mjs +156 -0
  176. package/src/core/pricing.mjs +336 -0
  177. package/src/core/project-artifacts.mjs +39 -0
  178. package/src/core/project-context-loader.mjs +139 -0
  179. package/src/core/providers.mjs +219 -0
  180. package/src/core/rate-limit-display.mjs +121 -0
  181. package/src/core/rate-limiter.mjs +119 -0
  182. package/src/core/resume-mode.mjs +192 -0
  183. package/src/core/risk-tier.mjs +388 -0
  184. package/src/core/safety.mjs +260 -0
  185. package/src/core/scheduler.mjs +173 -0
  186. package/src/core/session-manager.mjs +360 -0
  187. package/src/core/session.mjs +143 -0
  188. package/src/core/settings-sync.mjs +85 -0
  189. package/src/core/stagnation.mjs +57 -0
  190. package/src/core/stream-client.mjs +957 -0
  191. package/src/core/streaming.mjs +182 -0
  192. package/src/core/system-prompt.mjs +140 -0
  193. package/src/core/tasks.mjs +196 -0
  194. package/src/core/tool-executor.mjs +2231 -0
  195. package/src/core/trust.mjs +160 -0
  196. package/src/core/work-scope.mjs +248 -0
  197. package/src/hooks/engine.mjs +162 -0
  198. package/src/mcp/client.mjs +253 -0
  199. package/src/mcp/transport-shttp.mjs +130 -0
  200. package/src/mcp/transport-sse.mjs +131 -0
  201. package/src/mcp/transport-ws.mjs +134 -0
  202. package/src/onboarding/preflight.mjs +374 -0
  203. package/src/permissions/checker.mjs +57 -0
  204. package/src/permissions/command-classifier.mjs +700 -0
  205. package/src/permissions/injection-check.mjs +60 -0
  206. package/src/permissions/path-check.mjs +102 -0
  207. package/src/permissions/prompt.mjs +73 -0
  208. package/src/permissions/sandbox.mjs +112 -0
  209. package/src/plugins/loader.mjs +138 -0
  210. package/src/skills/installer.mjs +188 -0
  211. package/src/skills/loader.mjs +252 -0
  212. package/src/skills/runner.mjs +55 -0
  213. package/src/state/orbit.mjs +263 -0
  214. package/src/state/verbosity.mjs +99 -0
  215. package/src/telemetry/index.mjs +122 -0
  216. package/src/terminal/agents.mjs +353 -0
  217. package/src/terminal/analytics.mjs +292 -0
  218. package/src/terminal/ansi.mjs +695 -0
  219. package/src/terminal/init.mjs +145 -0
  220. package/src/terminal/main.mjs +310 -0
  221. package/src/terminal/repl-ask-form.mjs +120 -0
  222. package/src/terminal/repl-explore.mjs +44 -0
  223. package/src/terminal/repl-format.mjs +317 -0
  224. package/src/terminal/repl-model-form.mjs +132 -0
  225. package/src/terminal/repl-render.mjs +833 -0
  226. package/src/terminal/repl-resume.mjs +640 -0
  227. package/src/terminal/repl-state.mjs +120 -0
  228. package/src/terminal/repl-utils.mjs +34 -0
  229. package/src/terminal/repl.mjs +5032 -0
  230. package/src/terminal/skills.mjs +54 -0
  231. package/src/terminal/tool-display.mjs +392 -0
  232. package/src/tools/agent.mjs +137 -0
  233. package/src/tools/ask-user.mjs +61 -0
  234. package/src/tools/bash.mjs +231 -0
  235. package/src/tools/cron-create.mjs +120 -0
  236. package/src/tools/cron-delete.mjs +49 -0
  237. package/src/tools/cron-list.mjs +37 -0
  238. package/src/tools/edit.mjs +82 -0
  239. package/src/tools/enter-worktree.mjs +69 -0
  240. package/src/tools/exit-worktree.mjs +57 -0
  241. package/src/tools/glob.mjs +117 -0
  242. package/src/tools/grep.mjs +129 -0
  243. package/src/tools/lint.mjs +71 -0
  244. package/src/tools/ls.mjs +58 -0
  245. package/src/tools/lsp.mjs +115 -0
  246. package/src/tools/multi-edit.mjs +94 -0
  247. package/src/tools/notebook-edit.mjs +96 -0
  248. package/src/tools/project-overview.mjs +703 -0
  249. package/src/tools/read-mcp-resource.mjs +57 -0
  250. package/src/tools/read.mjs +138 -0
  251. package/src/tools/registry.mjs +116 -0
  252. package/src/tools/remote-trigger.mjs +84 -0
  253. package/src/tools/send-message.mjs +64 -0
  254. package/src/tools/skill.mjs +52 -0
  255. package/src/tools/test-runner.mjs +49 -0
  256. package/src/tools/todo-write.mjs +68 -0
  257. package/src/tools/tool-search.mjs +77 -0
  258. package/src/tools/web-fetch.mjs +65 -0
  259. package/src/tools/web-search.mjs +89 -0
  260. package/src/tools/write.mjs +55 -0
  261. package/src/ui/approval.mjs +510 -0
  262. package/src/ui/banner.mjs +232 -0
  263. package/src/ui/commands.mjs +537 -0
  264. package/src/ui/formatter.mjs +409 -0
  265. package/src/ui/icons.mjs +170 -0
  266. package/src/ui/input-dock.mjs +772 -0
  267. package/src/ui/markdown.mjs +278 -0
  268. package/src/ui/mission-report.mjs +296 -0
  269. package/src/ui/palette.mjs +189 -0
  270. package/src/ui/render-queue.mjs +500 -0
  271. package/src/ui/slash-commands.mjs +257 -0
  272. package/src/ui/spinner.mjs +116 -0
  273. package/src/ui/sub-agent.mjs +167 -0
  274. package/src/ui/term.mjs +174 -0
  275. package/src/ui/text-layout.mjs +127 -0
  276. package/src/ui/tool-card.mjs +740 -0
  277. package/src/ui/tool-details.mjs +504 -0
  278. package/src/ui/transcript-block.mjs +20 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Telemetry — Funnel event emitter (PRD-076 W3).
3
+ *
4
+ * Buffers funnel events and flushes to {backend}/api/telemetry/funnel
5
+ * on a timer and on process exit. Device ID is generated once per install
6
+ * for anon-to-logged-in correlation.
7
+ *
8
+ * Opt-out: BAHULAM_DISABLE_TELEMETRY=1 or CLAUDE_CODE_DISABLE_TELEMETRY=1
9
+ */
10
+
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+ import * as crypto from 'node:crypto';
14
+ import { bahulamHome } from '../core/paths.mjs';
15
+
16
+ const BUF_LIMIT = 500;
17
+ const FLUSH_INTERVAL_MS = 30_000;
18
+
19
+ let events = [];
20
+ let enabled = true;
21
+ let flushTimer = null;
22
+ let _backendUrl = null;
23
+ let _token = null;
24
+ let _deviceId = null;
25
+
26
+ /* ── Configuration ── */
27
+
28
+ export function disable() {
29
+ enabled = false;
30
+ if (flushTimer) { clearInterval(flushTimer); flushTimer = null; }
31
+ }
32
+
33
+ export function configure(backendUrl, token) {
34
+ _backendUrl = backendUrl;
35
+ _token = token;
36
+ }
37
+
38
+ /* ── Device ID ── */
39
+
40
+ function readOrCreateDeviceId() {
41
+ const dir = bahulamHome();
42
+ const idPath = path.join(dir, 'device_id');
43
+ try {
44
+ if (fs.existsSync(idPath)) {
45
+ return fs.readFileSync(idPath, 'utf-8').trim();
46
+ }
47
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
48
+ const id = crypto.randomUUID();
49
+ fs.writeFileSync(idPath, id, { mode: 0o600 });
50
+ return id;
51
+ } catch { return 'unknown'; }
52
+ }
53
+
54
+ export function getDeviceId() {
55
+ if (!_deviceId) _deviceId = readOrCreateDeviceId();
56
+ return _deviceId;
57
+ }
58
+
59
+ /* ── Tracking ── */
60
+
61
+ export function track(event, properties = {}) {
62
+ if (!enabled) return;
63
+ if (process.env.BAHULAM_DISABLE_TELEMETRY === '1') return;
64
+ if (process.env.CLAUDE_CODE_DISABLE_TELEMETRY === '1') return;
65
+
66
+ const entry = { event, properties: { ...properties }, device_id: getDeviceId(), timestamp: Date.now() };
67
+ events.push(entry);
68
+ if (events.length > BUF_LIMIT) events.splice(0, events.length - BUF_LIMIT);
69
+
70
+ if (process.env.BAHULAM_DEBUG_TELEMETRY) {
71
+ process.stderr.write(`[telemetry] ${event} ${JSON.stringify(properties).slice(0, 200)}\n`);
72
+ }
73
+
74
+ if (!flushTimer) {
75
+ flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);
76
+ flushTimer.unref();
77
+ }
78
+ }
79
+
80
+ export function trackTiming(event, durationMs, properties = {}) {
81
+ track(event, { ...properties, durationMs });
82
+ }
83
+
84
+ export function trackError(event, error) {
85
+ track(`error.${event}`, { message: error.message, stack: error.stack?.split('\n').slice(0, 3).join('\n') });
86
+ }
87
+
88
+ /* ── Transport ── */
89
+
90
+ export async function flush() {
91
+ if (!enabled || events.length === 0 || !_backendUrl) return;
92
+ const batch = events.splice(0);
93
+ const headers = { 'Content-Type': 'application/json' };
94
+ if (_token) headers['Authorization'] = `Bearer ${_token}`;
95
+ try {
96
+ const resp = await fetch(`${_backendUrl}/api/telemetry/funnel`, {
97
+ method: 'POST', headers,
98
+ body: JSON.stringify({
99
+ events: batch,
100
+ user_id: _token ? undefined : undefined, // backend derives from token if present
101
+ source: 'cli',
102
+ }),
103
+ });
104
+ if (!resp.ok && resp.status >= 500) events.unshift(...batch);
105
+ } catch { events.unshift(...batch); if (events.length > BUF_LIMIT * 2) events.splice(0, events.length - BUF_LIMIT); }
106
+ }
107
+
108
+ export async function shutdown() {
109
+ if (flushTimer) { clearInterval(flushTimer); flushTimer = null; }
110
+ await flush();
111
+ }
112
+
113
+ /* ── Debug ── */
114
+
115
+ export function getEvents() { return [...events]; }
116
+ export function clear() { events.length = 0; }
117
+ export function setEnabled(value) { enabled = value; }
118
+ export function getStats() {
119
+ const counts = {};
120
+ for (const e of events) counts[e.event] = (counts[e.event] || 0) + 1;
121
+ return { totalEvents: events.length, enabled, eventCounts: counts };
122
+ }
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Built-in Agents — specialized agent modes invoked via slash commands.
3
+ *
4
+ * Each agent wraps the same backend SSE flow but with a specialized
5
+ * system prompt prefix that focuses the AI on a specific task type.
6
+ *
7
+ * Agents:
8
+ * - explore: Code explorer — traces execution paths, maps architecture
9
+ * - review: Code reviewer — finds bugs, security issues, quality problems
10
+ * - architect: Feature architect — designs implementations, file plans
11
+ */
12
+
13
+ import { c } from './ansi.mjs';
14
+ import { TarangStreamClient } from '../core/stream-client.mjs';
15
+
16
+ // ── Agent Definitions ────────────────────────────────────────
17
+
18
+ export const BUILTIN_AGENTS = [
19
+ {
20
+ command: 'explore',
21
+ name: 'Code Explorer',
22
+ description: 'Deeply analyze codebase features and architecture',
23
+ detail: 'Traces execution paths, maps layers, documents dependencies',
24
+ icon: '🔭',
25
+ systemPrompt: `You are a Code Explorer agent. Your job is to deeply analyze the codebase to answer the user's question.
26
+
27
+ Your approach:
28
+ 1. Start by understanding what the user wants to know
29
+ 2. Search for relevant files using search_code and list_files
30
+ 3. Read the key files to trace the execution path
31
+ 4. Map the architecture layers (entry points → business logic → data layer)
32
+ 5. Document dependencies between components
33
+ 6. Provide a clear, structured answer with file references
34
+
35
+ Rules:
36
+ - ONLY use read-only tools: read_file, search_code, list_files, search_files, get_file_info
37
+ - NEVER modify any files
38
+ - NEVER run shell commands that modify state
39
+ - Include file paths and line numbers in your references
40
+ - Structure your response with clear sections: Overview, Key Files, Execution Flow, Dependencies
41
+ - Be thorough but concise — focus on what matters for the user's question`,
42
+ readOnly: true,
43
+ },
44
+ {
45
+ command: 'review',
46
+ name: 'Code Reviewer',
47
+ description: 'Review code for bugs, security issues, and quality',
48
+ detail: 'Scans for OWASP top 10, logic errors, code smells',
49
+ icon: '🔍',
50
+ systemPrompt: `You are a Code Review agent. Your job is to review code for issues.
51
+
52
+ Your approach:
53
+ 1. Read the files or directories the user specifies
54
+ 2. Check for:
55
+ - Security vulnerabilities (OWASP top 10: injection, XSS, auth bypass, etc.)
56
+ - Logic errors and edge cases
57
+ - Error handling gaps
58
+ - Performance issues (N+1 queries, memory leaks, blocking operations)
59
+ - Code quality (naming, complexity, duplication)
60
+ 3. Rate each finding by severity: CRITICAL / HIGH / MEDIUM / LOW
61
+ 4. Provide specific fix suggestions with code
62
+
63
+ Rules:
64
+ - ONLY use read-only tools: read_file, search_code, list_files, search_files
65
+ - NEVER modify any files
66
+ - Focus on HIGH and CRITICAL issues first
67
+ - Include file:line references
68
+ - Structure: Summary → Critical Issues → Other Findings → Recommendations
69
+ - Be specific — "line 42 has SQL injection via string interpolation" not "check for SQL injection"`,
70
+ readOnly: true,
71
+ },
72
+ {
73
+ command: 'architect',
74
+ name: 'Feature Architect',
75
+ description: 'Design feature implementations with file plans',
76
+ detail: 'Analyzes patterns, designs components, maps data flows',
77
+ icon: '📐',
78
+ systemPrompt: `You are a Feature Architect agent. Your job is to design how a feature should be implemented.
79
+
80
+ Your approach:
81
+ 1. Understand the feature requirements from the user
82
+ 2. Analyze existing codebase patterns and conventions:
83
+ - File organization and naming
84
+ - Import patterns and module structure
85
+ - Error handling patterns
86
+ - Testing patterns
87
+ 3. Design the implementation:
88
+ - List all files to create/modify
89
+ - Component/module design with interfaces
90
+ - Data flow (request → processing → response)
91
+ - Database schema changes if needed
92
+ 4. Provide implementation order (what to build first)
93
+
94
+ Rules:
95
+ - ONLY use read-only tools: read_file, search_code, list_files, search_files
96
+ - NEVER modify any files — you DESIGN, you don't implement
97
+ - Follow existing project conventions
98
+ - Structure: Requirements → Architecture → File Plan → Implementation Order → Risks
99
+ - Include code sketches for key interfaces
100
+ - Call out edge cases and potential pitfalls`,
101
+ readOnly: true,
102
+ },
103
+ ];
104
+
105
+ // ── Agent Runner ─────────────────────────────────────────────
106
+
107
+ const TOOL_ALIASES = new Map([
108
+ ['bash', 'shell'],
109
+ ['shell_command', 'shell'],
110
+ ['read', 'read_file'],
111
+ ['write', 'write_file'],
112
+ ['edit', 'edit_file'],
113
+ ['grep', 'search_code'],
114
+ ]);
115
+
116
+ function canonicalToolName(value) {
117
+ const key = String(value || '').trim().toLowerCase();
118
+ return TOOL_ALIASES.get(key) || key;
119
+ }
120
+
121
+ const RUNTIME_PLACEHOLDER_CWDS = new Set([
122
+ '/workspace',
123
+ '/workspace/kepler-code',
124
+ ]);
125
+
126
+ function normalizeScopedArgs(toolName, args = {}, { projectRoot = null } = {}) {
127
+ if (canonicalToolName(toolName) !== 'shell' || !projectRoot) return args;
128
+ const next = { ...(args || {}) };
129
+ const cwd = String(next.cwd || '').trim();
130
+ if (!cwd || RUNTIME_PLACEHOLDER_CWDS.has(cwd)) {
131
+ next.cwd = projectRoot;
132
+ }
133
+ return next;
134
+ }
135
+
136
+ function createScopedToolExecutor(baseExecutor, agent, { projectRoot = null } = {}) {
137
+ const tools = Array.isArray(agent.tools) ? agent.tools : [];
138
+ const allowed = new Set(tools.map(canonicalToolName).filter(Boolean));
139
+ if (!allowed.size) return baseExecutor;
140
+
141
+ return {
142
+ ...baseExecutor,
143
+ execute: async (toolName, args = {}, options = {}) => {
144
+ const canonical = canonicalToolName(toolName);
145
+ if (!allowed.has(canonical)) {
146
+ return {
147
+ success: false,
148
+ output: `Tool '${toolName}' is not allowed for agent '${agent.name}'. Allowed tools: ${tools.join(', ')}`,
149
+ };
150
+ }
151
+ return baseExecutor.execute.call(
152
+ baseExecutor,
153
+ toolName,
154
+ normalizeScopedArgs(toolName, args, { projectRoot }),
155
+ options,
156
+ );
157
+ },
158
+ };
159
+ }
160
+
161
+ export function findBuiltinAgent(agentName) {
162
+ const target = String(agentName || '').trim().toLowerCase();
163
+ return BUILTIN_AGENTS.find(agent => agent.command === target || agent.name.toLowerCase() === target) || null;
164
+ }
165
+
166
+ export function localAgentMatches(agent, target) {
167
+ const needle = String(target || '').trim().toLowerCase();
168
+ if (!needle) return false;
169
+ return [
170
+ agent.slug,
171
+ agent.id,
172
+ agent.name,
173
+ ].some(value => String(value || '').trim().toLowerCase() === needle);
174
+ }
175
+
176
+ function normalizeRunnableAgent(agent) {
177
+ const spec = agent?.spec || {};
178
+ const config = agent?.config || spec.config || agent?.raw_config || {};
179
+ const configAgent = config.agent || {};
180
+ const tools = Array.isArray(agent?.tools)
181
+ ? agent.tools
182
+ : Array.isArray(spec.tools)
183
+ ? spec.tools
184
+ : Array.isArray(config.tools)
185
+ ? config.tools
186
+ : [];
187
+
188
+ const name = agent?.name || spec.name || agent?.slug || agent?.command || 'agent';
189
+ return {
190
+ command: agent?.command || agent?.slug || spec.slug || name,
191
+ slug: agent?.slug || spec.slug || agent?.command || name,
192
+ name,
193
+ description: agent?.description || spec.description || '',
194
+ role: agent?.role || spec.role || 'specialist',
195
+ icon: agent?.icon || '◇',
196
+ systemPrompt: agent?.systemPrompt || agent?.system_prompt || agent?.prompt || spec.system_prompt || configAgent.system_prompt || '',
197
+ readOnly: Boolean(agent?.readOnly),
198
+ model: agent?.model || spec.model || configAgent.model || null,
199
+ models: agent?.models || spec.models || configAgent.models || null,
200
+ tools,
201
+ source: agent?.source || spec.source || '',
202
+ };
203
+ }
204
+
205
+ function agentInstructionPrefix(agent, execContext = {}) {
206
+ const lines = [];
207
+ if (agent.systemPrompt) {
208
+ lines.push(agent.systemPrompt);
209
+ } else {
210
+ lines.push(`You are ${agent.name}, a Bahulam Code sub-agent.`);
211
+ }
212
+ if (agent.description) lines.push(`\nAgent description: ${agent.description}`);
213
+ if (agent.role) lines.push(`Agent role: ${agent.role}`);
214
+ if (agent.tools.length) {
215
+ lines.push(
216
+ `Allowed tools for this sub-agent: ${agent.tools.join(', ')}. ` +
217
+ 'Do not request tools outside this list.',
218
+ );
219
+ }
220
+ if (execContext.project_root) {
221
+ lines.push(`Runtime project root: ${execContext.project_root}. Use this as the cwd for shell commands unless the task explicitly requires a different registered project root.`);
222
+ }
223
+ return lines.join('\n');
224
+ }
225
+
226
+ function displayEventForDirectAgent(event, agent) {
227
+ if (!event || !event.type) return event;
228
+ if (!['tool_call', 'tool_request', 'tool_result', 'tool_done', 'sub_agent_tool'].includes(event.type)) {
229
+ return event;
230
+ }
231
+ return {
232
+ ...event,
233
+ data: {
234
+ ...(event.data || {}),
235
+ internal: true,
236
+ sub_agent: event.data?.sub_agent || agent.slug || agent.command || agent.name || 'agent',
237
+ },
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Run a normalized agent definition with the given instruction.
243
+ * @param {Object} agentDefinition - Built-in or .bahulam/agents definition
244
+ * @param {string} instruction - User's instruction
245
+ * @param {Object} ctx - { auth, toolExecutor, approval }
246
+ * @param {Object} session - Session state
247
+ * @param {Function} renderEvent - Event renderer function
248
+ * @param {Object} [options]
249
+ */
250
+ export async function runAgentDefinition(agentDefinition, instruction, ctx, session, renderEvent, options = {}) {
251
+ const agent = normalizeRunnableAgent(agentDefinition);
252
+ const userInstruction = String(instruction || '').trim() || 'Run your assigned task now.';
253
+ const suppliedContext = options.execContext || options.context || {};
254
+ const baseCwd = suppliedContext.cwd || options.cwd || process.cwd();
255
+ const suppliedSubAgent = suppliedContext.sub_agent && typeof suppliedContext.sub_agent === 'object'
256
+ ? suppliedContext.sub_agent
257
+ : {};
258
+ const projectRoot = suppliedContext.project_root || null;
259
+ const execContext = {
260
+ ...suppliedContext,
261
+ cwd: suppliedContext.cwd || baseCwd,
262
+ ...(projectRoot ? { project_root: projectRoot } : {}),
263
+ sub_agent: {
264
+ ...suppliedSubAgent,
265
+ name: agent.name,
266
+ slug: agent.slug,
267
+ role: agent.role,
268
+ description: agent.description,
269
+ tools: agent.tools,
270
+ source: agent.source,
271
+ },
272
+ };
273
+
274
+ const creds = ctx.auth.loadCredentials();
275
+ if (!creds.token) {
276
+ process.stderr.write(` ${c.red('Not logged in. Run /login first.')}\n`);
277
+ return;
278
+ }
279
+
280
+ // Header
281
+ process.stderr.write(`\n ${agent.icon} ${c.bold(c.brand(agent.name))}\n`);
282
+ process.stderr.write(` ${c.gray('─'.repeat(40))}\n`);
283
+ if (agent.description) process.stderr.write(` ${c.gray(agent.description)}\n`);
284
+ if (agent.source) process.stderr.write(` ${c.dim(agent.source)}\n`);
285
+ process.stderr.write(` ${c.gray(userInstruction)}\n\n`);
286
+
287
+ // Prepend agent system prompt to instruction
288
+ const fullInstruction = `${agentInstructionPrefix(agent, execContext)}\n\n---\n\nUser request: ${userInstruction}`;
289
+
290
+ // For read-only agents, use a restricted approval manager
291
+ const { ApprovalManager } = await import('../core/approval.mjs');
292
+ const agentApproval = agent.readOnly
293
+ ? new ApprovalManager({ planMode: true }) // planMode blocks all writes
294
+ : ctx.approval;
295
+ const toolExecutor = createScopedToolExecutor(ctx.toolExecutor, agent, {
296
+ projectRoot: execContext.project_root || null,
297
+ });
298
+
299
+ const client = new TarangStreamClient({
300
+ baseUrl: creds.backendUrl,
301
+ token: creds.token,
302
+ toolExecutor,
303
+ approvalManager: agentApproval,
304
+ });
305
+
306
+ session.turns++;
307
+ session.toolCalls = 0;
308
+ let assistantContent = '';
309
+ if (agent.model) execContext.model_override = agent.model;
310
+ if (agent.models && typeof agent.models === 'object' && Object.keys(agent.models).length) {
311
+ execContext.model_overrides = agent.models;
312
+ }
313
+
314
+ try {
315
+ for await (const event of client.execute(fullInstruction, execContext)) {
316
+ renderEvent(displayEventForDirectAgent(event, agent));
317
+
318
+ if (event.type === 'content' || event.type === 'content_partial') {
319
+ const text = event.data?.text || '';
320
+ if (text) assistantContent += text;
321
+ }
322
+ }
323
+ } catch (err) {
324
+ process.stderr.write(` ${c.red('Agent error: ' + err.message)}\n`);
325
+ }
326
+
327
+ // Save to conversation history
328
+ if (assistantContent) {
329
+ session.history.push(
330
+ { role: 'user', content: `[${agent.name}] ${userInstruction}` },
331
+ { role: 'assistant', content: assistantContent }
332
+ );
333
+ }
334
+
335
+ process.stderr.write('\n');
336
+ }
337
+
338
+ /**
339
+ * Run a built-in agent with the given instruction.
340
+ * @param {string} agentName - e.g. 'explore', 'review', 'architect'
341
+ * @param {string} instruction - User's instruction
342
+ * @param {Object} ctx - { auth, toolExecutor, approval }
343
+ * @param {Object} session - Session state
344
+ * @param {Function} renderEvent - Event renderer function
345
+ */
346
+ export async function runAgent(agentName, instruction, ctx, session, renderEvent) {
347
+ const agent = findBuiltinAgent(agentName);
348
+ if (!agent) {
349
+ process.stderr.write(` ${c.red('Unknown agent: ' + agentName)}\n`);
350
+ return;
351
+ }
352
+ return runAgentDefinition(agent, instruction, ctx, session, renderEvent);
353
+ }