@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,145 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ const FILES = {
5
+ 'README.md': `# .bahulam/ - project agent context
6
+
7
+ Bahulam Code reads and writes here to keep state between sessions.
8
+
9
+ ## Files Bahulam Code writes
10
+ - \`plan.md\` - current agent plan
11
+ - \`goal.md\` - durable session goal
12
+ - \`tasks/\` - task list
13
+ - \`reports/*.md\` - end-of-turn mission reports
14
+ - \`sessions/*.jsonl\` - turn transcripts
15
+ - \`commands.log\` - command executions
16
+ - \`approvals.log\` - HITL decisions
17
+ - \`trust.json\` - approved patterns
18
+
19
+ ## Files you can write
20
+ - \`config.json\` - project policy
21
+ - \`project.md\` - durable project context
22
+ - \`style.md\` - codebase conventions
23
+ - \`hitl.md\` - approval guidance
24
+ - \`skills/<name>/SKILL.md\` - reusable domain skills
25
+
26
+ Format v1. Markdown files are intentionally hand-editable.
27
+ `,
28
+ 'config.json': JSON.stringify({
29
+ version: 1,
30
+ context: {
31
+ loadEveryTurn: ['KEPLER.md', 'project.md', 'style.md', 'goal.md', 'plan.md', 'tasks/*.md'],
32
+ showReloadNotice: true,
33
+ },
34
+ planning: { owner: 'auto', onUserEditedPlan: 'prefer_user_plan' },
35
+ tasks: { storage: 'project_markdown', syncTodoWrite: true, resumePrompt: true },
36
+ hitl: {
37
+ defaultScope: 'once',
38
+ allowSessionTrust: true,
39
+ allowProjectTrust: false,
40
+ reaskAfterMinutes: 30,
41
+ },
42
+ commands: {
43
+ enabled: ['map', 'probe', 'footprint', 'heal', 'align', 'distill', 'brief', 'rewind'],
44
+ dryRunDefault: false,
45
+ },
46
+ }, null, 2) + '\n',
47
+ 'settings.json': JSON.stringify({
48
+ env: {},
49
+ permissions: {
50
+ shellAllowlist: ['git', 'npm', 'pnpm'],
51
+ editDenylist: ['**/*.env', 'secrets/**'],
52
+ },
53
+ hooks: {
54
+ UserPromptSubmit: [],
55
+ PreToolUse: [],
56
+ PostToolUse: [],
57
+ Stop: [],
58
+ },
59
+ }, null, 2) + '\n',
60
+ 'KEPLER.md': `# Project
61
+
62
+ ## Quick Facts
63
+ - Stack:
64
+ - Test command:
65
+ - Lint command:
66
+ - Build command:
67
+
68
+ ## Key Directories
69
+
70
+ ## Code Style
71
+
72
+ ## Critical Rules
73
+ `,
74
+ 'project.md': '# Project Context\n\nAdd durable project context here.\n',
75
+ 'style.md': '# Style\n\nAdd code and communication conventions here.\n',
76
+ 'hitl.md': '# HITL Guidance\n\nAdd project-specific approval guidance here.\n',
77
+ 'trust.json': JSON.stringify({ version: 1, rules: [] }, null, 2) + '\n',
78
+ 'tasks/README.md': `# Bahulam Tasks
79
+
80
+ Checklist files are read every turn.
81
+
82
+ - \`backlog.md\` - pending tasks
83
+ - \`active.md\` - current task
84
+ - \`done.md\` - completed tasks
85
+ - \`blocked.md\` - waiting on input
86
+ `,
87
+ 'tasks/backlog.md': '# Backlog\n\n',
88
+ 'tasks/active.md': '# Active\n\n',
89
+ 'tasks/done.md': '# Done\n\n',
90
+ 'tasks/blocked.md': '# Blocked\n\n',
91
+ 'skills/starter/SKILL.md': `---
92
+ name: starter
93
+ description: Project-specific conventions and setup notes. Use when onboarding to this repo.
94
+ ---
95
+
96
+ # Starter Skill
97
+
98
+ Add reusable project knowledge here.
99
+ `,
100
+ 'commands/onboard.md': `---
101
+ name: onboard
102
+ description: Explore the project and record onboarding notes.
103
+ ---
104
+
105
+ # Onboard
106
+
107
+ Context:
108
+ $ARGUMENTS
109
+
110
+ Explore the codebase, ask clarifying questions, and record useful notes in .bahulam/tasks/active.md.
111
+ `,
112
+ };
113
+
114
+ export function scaffoldKeplerProject({ cwd = process.cwd(), force = false } = {}) {
115
+ const root = path.join(cwd, '.bahulam');
116
+ const written = [];
117
+ const skipped = [];
118
+ for (const [rel, content] of Object.entries(FILES)) {
119
+ const filePath = path.join(root, rel);
120
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
121
+ if (fs.existsSync(filePath) && !force) {
122
+ skipped.push(filePath);
123
+ continue;
124
+ }
125
+ fs.writeFileSync(filePath, content);
126
+ written.push(filePath);
127
+ }
128
+
129
+ const gitignore = path.join(root, '.gitignore');
130
+ if (!fs.existsSync(gitignore) || force) {
131
+ fs.writeFileSync(gitignore, 'settings.local.json\nsessions/\nreports/\n*.log\n');
132
+ written.push(gitignore);
133
+ }
134
+
135
+ return { root, written, skipped };
136
+ }
137
+
138
+ export async function runInitCommand(args = [], { cwd = process.cwd() } = {}) {
139
+ const force = args.includes('--force');
140
+ const result = scaffoldKeplerProject({ cwd, force });
141
+ process.stderr.write(`\x1b[32m✓\x1b[0m Initialized .bahulam at ${result.root}\n`);
142
+ process.stderr.write(` wrote ${result.written.length} files`);
143
+ if (result.skipped.length) process.stderr.write(`, skipped ${result.skipped.length} existing files`);
144
+ process.stderr.write('\n');
145
+ }
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bahulam Code CLI — ANSI Terminal UI. Bahulam's coding agent.
4
+ * Zero React. Zero Ink. Zero flickering.
5
+ */
6
+
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import { startTerminalRepl } from './repl.mjs';
10
+ import {
11
+ runSessionsCommand,
12
+ runStatsCommand,
13
+ runHistoryCommand,
14
+ } from './analytics.mjs';
15
+ import { parseArgs } from '../config/cli-args.mjs';
16
+ import * as telemetry from '../telemetry/index.mjs';
17
+ import { bahulamHome } from '../core/paths.mjs';
18
+ import { TarangAuth as Auth } from '../auth/tarang-auth.mjs';
19
+
20
+ // ── Subcommands ──
21
+
22
+ const subcommand = process.argv[2];
23
+ const subcommandArgs = process.argv.slice(3);
24
+
25
+ function parseKeplerSubcommandArgs(command, argv) {
26
+ const parsed = {
27
+ command,
28
+ workflowSubcommand: null,
29
+ workflowSlug: null,
30
+ workflowFile: null,
31
+ workflowDir: null,
32
+ agentSubcommand: null,
33
+ agentSlug: null,
34
+ agentDir: null,
35
+ instruction: null,
36
+ pattern: null,
37
+ yes: false,
38
+ verbose: false,
39
+ debug: false,
40
+ };
41
+
42
+ for (let i = 0; i < argv.length; i++) {
43
+ const arg = argv[i];
44
+ switch (arg) {
45
+ case '--file':
46
+ case '-f':
47
+ parsed.workflowFile = argv[++i];
48
+ break;
49
+ case '--dir':
50
+ if (command === 'agent') parsed.agentDir = argv[++i];
51
+ else parsed.workflowDir = argv[++i];
52
+ break;
53
+ case '--instruction':
54
+ case '--input':
55
+ case '--print':
56
+ case '-p':
57
+ parsed.instruction = argv[++i];
58
+ break;
59
+ case '--pattern':
60
+ parsed.pattern = argv[++i];
61
+ break;
62
+ case '--yes':
63
+ case '-y':
64
+ parsed.yes = true;
65
+ break;
66
+ case '--verbose':
67
+ case '-v':
68
+ parsed.verbose = true;
69
+ break;
70
+ case '--debug':
71
+ case '-d':
72
+ parsed.debug = true;
73
+ parsed.verbose = true;
74
+ break;
75
+ default:
76
+ if (arg.startsWith('-')) break;
77
+ if (command === 'workflow') {
78
+ if (!parsed.workflowSubcommand) parsed.workflowSubcommand = arg;
79
+ else if (!parsed.workflowSlug) parsed.workflowSlug = arg;
80
+ } else if (command === 'agent') {
81
+ if (!parsed.agentSubcommand) parsed.agentSubcommand = arg;
82
+ else if (!parsed.agentSlug) parsed.agentSlug = arg;
83
+ }
84
+ break;
85
+ }
86
+ }
87
+
88
+ return parsed;
89
+ }
90
+
91
+ function detectInstallFirstRun() {
92
+ const markerPath = path.join(bahulamHome(), '.install_marker');
93
+ try {
94
+ if (fs.existsSync(markerPath)) return false;
95
+ fs.writeFileSync(markerPath, String(Date.now()), { mode: 0o600 });
96
+ return true;
97
+ } catch { return false; }
98
+ }
99
+
100
+ async function main() {
101
+ // ── Telemetry bootstrap ──
102
+ const _auth = new Auth();
103
+ const _creds = _auth.loadCredentials();
104
+ telemetry.configure(_creds.backendUrl, _creds.token);
105
+
106
+ // Fire install_first_run on first ever CLI invocation
107
+ if (detectInstallFirstRun()) {
108
+ telemetry.track('install_first_run', { version: process.env.npm_package_version || '' });
109
+ }
110
+
111
+ // Check day7_return — fire once per install
112
+ const configDir_ = bahulamHome();
113
+ const installMarker_ = path.join(configDir_, '.install_marker');
114
+ const returnFiredPath_ = path.join(configDir_, '.return_fired');
115
+ try {
116
+ if (fs.existsSync(installMarker_) && !fs.existsSync(returnFiredPath_)) {
117
+ const installTime = parseInt(fs.readFileSync(installMarker_, 'utf-8').trim(), 10);
118
+ if (!isNaN(installTime) && Date.now() - installTime >= 7 * 24 * 60 * 60 * 1000) {
119
+ telemetry.track('day7_return', {});
120
+ fs.writeFileSync(returnFiredPath_, '1', { mode: 0o600 });
121
+ }
122
+ }
123
+ } catch {}
124
+
125
+ if (subcommand === 'dashboard') {
126
+ // Launch Bahulam Pulse Next.js dashboard
127
+ const { spawn } = await import('node:child_process');
128
+ const { fileURLToPath } = await import('node:url');
129
+ const path = await import('node:path');
130
+ const cliPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'pulse', 'cli.js');
131
+ const child = spawn(process.execPath, [cliPath, ...subcommandArgs], {
132
+ stdio: 'inherit',
133
+ env: process.env,
134
+ });
135
+ child.on('exit', (code) => process.exit(code ?? 0));
136
+ return;
137
+ }
138
+
139
+ if (subcommand === 'sessions') {
140
+ await runSessionsCommand(subcommandArgs);
141
+ return;
142
+ }
143
+
144
+ if (subcommand === 'stats') {
145
+ await runStatsCommand(subcommandArgs);
146
+ return;
147
+ }
148
+
149
+ if (subcommand === 'history') {
150
+ await runHistoryCommand(subcommandArgs);
151
+ return;
152
+ }
153
+
154
+ if (subcommand === 'init') {
155
+ const { runInitCommand } = await import('./init.mjs');
156
+ await runInitCommand(subcommandArgs);
157
+ return;
158
+ }
159
+
160
+ if (subcommand === 'skills' || subcommand === 'skill') {
161
+ const { runSkillsCommand } = await import('./skills.mjs');
162
+ try {
163
+ await runSkillsCommand(subcommandArgs);
164
+ } catch (err) {
165
+ process.stderr.write(`\x1b[31m✗ Skills command failed: ${err.message}\x1b[0m\n`);
166
+ process.exitCode = 1;
167
+ }
168
+ return;
169
+ }
170
+
171
+ if (subcommand === 'login') {
172
+ const { TarangAuth } = await import('../auth/tarang-auth.mjs');
173
+ const auth = new TarangAuth();
174
+ try {
175
+ telemetry.track('login_shown', { method: 'cli_subcommand' });
176
+ await auth.login();
177
+ telemetry.track('login_completed', { method: 'cli_subcommand' });
178
+ process.stderr.write('\x1b[32m✓ Login successful!\x1b[0m\n');
179
+ return;
180
+ } catch (err) {
181
+ process.stderr.write(`\x1b[31m✗ Login failed: ${err.message}\x1b[0m\n`);
182
+ process.exit(1);
183
+ }
184
+ }
185
+
186
+ if (subcommand === 'logout') {
187
+ const { TarangAuth } = await import('../auth/tarang-auth.mjs');
188
+ const auth = new TarangAuth();
189
+ const success = auth.logout();
190
+ if (success) {
191
+ process.stderr.write('\x1b[32m✓ Signed out. Credentials cleared.\x1b[0m\n');
192
+ } else {
193
+ process.stderr.write('\x1b[33m! No credentials to clear.\x1b[0m\n');
194
+ }
195
+ return;
196
+ }
197
+
198
+ if (subcommand === 'workflow') {
199
+ const { handleWorkflowCommand } = await import('../commands/workflow.mjs');
200
+ await handleWorkflowCommand(parseKeplerSubcommandArgs('workflow', subcommandArgs));
201
+ return;
202
+ }
203
+
204
+ if (subcommand === 'agent') {
205
+ const { handleAgentCommand } = await import('../commands/agent.mjs');
206
+ await handleAgentCommand(parseKeplerSubcommandArgs('agent', subcommandArgs));
207
+ return;
208
+ }
209
+
210
+ if (subcommand === 'version' || subcommand === '--version' || subcommand === '-v') {
211
+ const { createRequire } = await import('node:module');
212
+ const require = createRequire(import.meta.url);
213
+ const { version } = require('../../package.json');
214
+ process.stdout.write(`Bahulam Code v${version}\n`);
215
+ return;
216
+ }
217
+
218
+ if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
219
+ process.stderr.write(`
220
+ \x1b[1m\x1b[36mBahulam Code\x1b[0m — Bahulam's coding agent — bahulam.ai
221
+
222
+ \x1b[1mUsage:\x1b[0m
223
+ bahulam Start interactive REPL
224
+ bahulam "instruction" Run a single instruction
225
+ bahulam --headless -p "x" Non-interactive: auto-approve, JSONL output
226
+ bahulam --headless -p "x" --vision screenshot.png
227
+ Attach an image via the vision analysis pipeline
228
+ bahulam --resume Resume last conversation
229
+ bahulam dashboard Open analytics dashboard
230
+ bahulam login Sign in via browser
231
+ bahulam logout Sign out and clear credentials
232
+ bahulam init Scaffold .bahulam config, memory, hooks, tasks
233
+ bahulam version Show version
234
+
235
+ \x1b[1mAnalytics:\x1b[0m
236
+ bahulam sessions List recent local sessions
237
+ bahulam stats Show aggregate local session stats
238
+ bahulam history Show recent prompt history
239
+
240
+ \x1b[1mSkills:\x1b[0m
241
+ bahulam skills list [--all|--project]
242
+ bahulam skills view <name> [resource]
243
+ bahulam skills install <path-or-git-url> [--project] [--force]
244
+ bahulam skills update <name> [--project]
245
+ bahulam skills remove <name> [--project]
246
+
247
+ \x1b[1mREPL Commands:\x1b[0m
248
+ /help Show available commands
249
+ /stats Session metrics (tokens, cost, tools)
250
+ /cost Detailed cost breakdown by model
251
+ /model [role] [model] Show or set session model override
252
+ /history Conversation history
253
+ /new Start a new session
254
+ /clear Clear conversation history
255
+ /safety Show safety guardrail status
256
+ /revoke Revoke auto-approvals
257
+ /explore <query> Spawn read-only codebase explorer
258
+ /review <query> Spawn code review agent
259
+ /architect <query> Spawn architecture planning agent
260
+ /agents create <name> Create project-local user-defined agent YAML
261
+ /agents edit <name> Open a local agent YAML in your editor
262
+ /agents sync [name] Sync all or one local agent to Supabase
263
+ /attach <image-path> Attach an image to next prompt
264
+ /attach clipboard Attach image copied to macOS/Windows clipboard
265
+ /exit Exit the REPL
266
+
267
+ \x1b[1mKeyboard:\x1b[0m
268
+ Esc Cancel current execution
269
+ Space Pause / resume execution
270
+ Ctrl+C Exit
271
+
272
+ \x1b[1mEnvironment:\x1b[0m
273
+ TARANG_ENV Set backend (local, treetop, production)
274
+ ANTHROPIC_API_KEY Direct Anthropic API key
275
+ OPENROUTER_API_KEY OpenRouter API key
276
+ BAHULAM_CONFIG_DIR Override config directory (default: ~/.bahulam)
277
+ KEPLER_CONFIG_DIR Legacy config directory override
278
+ KEPLER_RECONNECT_MAX_ELAPSED_MS
279
+ Max reconnect window for dropped streams
280
+ BAHULAM_TTY_MODE=stable Scrollback-safe transcript if fixed dock redraws leak
281
+ KEPLER_BLOCK_SEPARATOR Tool/content separator: space, dotted, or off
282
+
283
+ \x1b[2mDocs: https://bahulam.ai\x1b[0m
284
+ `);
285
+ return;
286
+ }
287
+
288
+ // ── Headless mode (benchmarks, automation) ──
289
+ const args = parseArgs(process.argv.slice(2));
290
+ if (args.prompt && (process.argv.includes('--headless') || !process.stdin.isTTY)) {
291
+ const { runHeadless } = await import('../core/headless.mjs');
292
+ await runHeadless({
293
+ instruction: args.prompt,
294
+ model: args.model,
295
+ timeout: args.timeout || (args.maxTurns ? args.maxTurns * 60 : 600),
296
+ verbose: args.verbose,
297
+ cacheReport: args.cacheReport,
298
+ local: args.local,
299
+ vision: args.vision,
300
+ });
301
+ return;
302
+ }
303
+
304
+ await startTerminalRepl();
305
+ }
306
+
307
+ main().catch(err => {
308
+ process.stderr.write(`\x1b[31mFatal: ${err.message}\x1b[0m\n`);
309
+ process.exit(1);
310
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Interactive ask_user form — rendered when the agent calls the ask_user
3
+ * tool mid-turn to get direction (architecture choice, design decision,
4
+ * ambiguous next step, conflicting instructions, …).
5
+ *
6
+ * Same raw-stdin overlay pattern as repl-model-form.mjs: pause readline,
7
+ * raw mode on, redraw in place, restore on exit. Two input modes:
8
+ * list — ↑↓ move across options (+ an "Other" row), Enter selects,
9
+ * Esc declines (agent proceeds with its own judgment)
10
+ * text — the "Other" row opens a free-text line; Enter submits,
11
+ * Esc returns to the list
12
+ */
13
+
14
+ import { c } from './ansi.mjs';
15
+ import { fitAnsiLine, writeOverlayFrame, eraseOverlayFrame } from './repl-format.mjs';
16
+
17
+ const OTHER_SENTINEL = '__other__';
18
+
19
+ /**
20
+ * @param {object} opts
21
+ * @param {object|null} opts.rl readline instance to pause/resume
22
+ * @param {string} opts.question the question to display
23
+ * @param {string[]} opts.options 2-4 option labels from the agent
24
+ * @param {string} [opts.context] optional one-line context above the question
25
+ * @returns {Promise<{answer: string, source: 'option'|'free_text'}|null>}
26
+ * null = user declined (Esc) — the agent should proceed on its own
27
+ */
28
+ export async function askUserForm({ rl, question, options, context }) {
29
+ if (!process.stdin.isTTY) return null;
30
+ if (rl) rl.pause();
31
+
32
+ const optionRows = (options || []).map(o => String(o || '').trim()).filter(Boolean);
33
+ const rows = [...optionRows, OTHER_SENTINEL];
34
+
35
+ return await new Promise((resolve) => {
36
+ const wasRaw = process.stdin.isRaw;
37
+ let cursor = 0;
38
+ let renderedLines = 0;
39
+ let mode = 'list'; // 'list' | 'text'
40
+ let freeText = '';
41
+
42
+ const render = () => {
43
+ const cols = Math.max(60, process.stderr.columns || 120);
44
+ const lines = [];
45
+ lines.push(` ${c.bold('Agent question')} ${c.dim('· pick an option or type your own · Esc to let the agent decide')}`);
46
+ if (context) {
47
+ lines.push(fitAnsiLine(` ${c.dim(String(context))}`, cols - 1));
48
+ }
49
+ lines.push('');
50
+ lines.push(fitAnsiLine(` ${c.brand('?')} ${c.bold(String(question || ''))}`, cols - 1));
51
+ lines.push('');
52
+ rows.forEach((row, i) => {
53
+ const active = i === cursor;
54
+ const marker = active ? c.brand('▸') : ' ';
55
+ if (row === OTHER_SENTINEL) {
56
+ if (mode === 'text' && active) {
57
+ lines.push(fitAnsiLine(` ${marker} ${c.brand('Other:')} ${freeText}${c.brand('▎')}`, cols - 1));
58
+ } else {
59
+ lines.push(fitAnsiLine(` ${marker} ${active ? c.brand('Other — type your own answer') : c.dim('Other — type your own answer')}`, cols - 1));
60
+ }
61
+ } else {
62
+ lines.push(fitAnsiLine(` ${marker} ${active ? c.brand(row) : row}`, cols - 1));
63
+ }
64
+ });
65
+ lines.push('');
66
+ lines.push(fitAnsiLine(
67
+ mode === 'text'
68
+ ? ` ${c.dim('type your answer · Enter submit · Esc back to options')}`
69
+ : ` ${c.dim('↑↓ move · Enter select · Esc decline (agent decides)')}`,
70
+ cols - 1,
71
+ ));
72
+ writeOverlayFrame(renderedLines, lines);
73
+ renderedLines = lines.length;
74
+ };
75
+
76
+ const cleanup = (value) => {
77
+ process.stdin.removeListener('data', onData);
78
+ process.stdin.setRawMode(wasRaw || false);
79
+ eraseOverlayFrame(renderedLines);
80
+ if (rl) rl.resume();
81
+ resolve(value);
82
+ };
83
+
84
+ const onData = (data) => {
85
+ const key = data.toString('utf8');
86
+
87
+ if (mode === 'text') {
88
+ if (key === '\x1b') { mode = 'list'; freeText = ''; render(); return; }
89
+ if (key === '\x03') { cleanup(null); return; }
90
+ if (key === '\r' || key === '\n') {
91
+ const answer = freeText.trim();
92
+ if (answer) { cleanup({ answer, source: 'free_text' }); }
93
+ return;
94
+ }
95
+ if (key === '\x7f' || key === '\b') { freeText = freeText.slice(0, -1); render(); return; }
96
+ // Ignore other escape sequences (arrows etc.) while typing.
97
+ if (key.startsWith('\x1b')) return;
98
+ const printable = [...key].filter(ch => ch >= ' ').join('');
99
+ if (printable) { freeText += printable; render(); }
100
+ return;
101
+ }
102
+
103
+ // list mode
104
+ if (key === '\x1b' || key === '\x03' || key === 'q') { cleanup(null); return; }
105
+ if (key === '\r' || key === '\n') {
106
+ const row = rows[cursor];
107
+ if (row === OTHER_SENTINEL) { mode = 'text'; freeText = ''; render(); return; }
108
+ cleanup({ answer: row, source: 'option' });
109
+ return;
110
+ }
111
+ if (key === '\x1b[A') { cursor = Math.max(0, cursor - 1); render(); return; }
112
+ if (key === '\x1b[B') { cursor = Math.min(rows.length - 1, cursor + 1); render(); return; }
113
+ };
114
+
115
+ process.stdin.setRawMode(true);
116
+ process.stdin.resume();
117
+ process.stdin.on('data', onData);
118
+ render();
119
+ });
120
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Explore-run classifier — pure category lookup for the read/list/search/index
3
+ * tool bursts that get collapsed into one animated summary line during a
4
+ * sub-agent run.
5
+ *
6
+ * These functions are stateless. The mutable run state (counts, recent
7
+ * paths, lineActive flag) still lives in repl.mjs during the split.
8
+ */
9
+
10
+ const EXPLORE_TOOL_CATEGORY = new Map([
11
+ ['read_file', 'read'], ['read', 'read'], ['read_files', 'read'],
12
+ ['read_batch', 'read'], ['get_file_info', 'read'],
13
+ // analyze_code is the "cheap 10x-lighter than read_file" tool the system
14
+ // prompt tells the agent to prefer for structure lookups. Burst usage is
15
+ // as common as read bursts, so classify it as a read for collapse.
16
+ ['analyze_code', 'read'],
17
+ ['list_files', 'list'], ['glob', 'list'], ['ls', 'list'],
18
+ ['search_code', 'search'], ['search_files', 'search'], ['grep', 'search'],
19
+ // validate_* tools are read-only structure/build checks the agent chains
20
+ // during post-write verification. They fit naturally in a search-ish bucket
21
+ // ("checking") rather than opening a discrete card per call.
22
+ ['validate_file', 'search'], ['validate_structure', 'search'],
23
+ ['index_project', 'index'], ['register_project', 'index'],
24
+ ['get_project_overview', 'index'],
25
+ ]);
26
+
27
+ export function exploreCollapseEnabled() {
28
+ return process.env.KEPLER_EXPLORE_COLLAPSE !== '0';
29
+ }
30
+
31
+ export function isExploreTool(tool) {
32
+ if (!exploreCollapseEnabled()) return false;
33
+ return EXPLORE_TOOL_CATEGORY.has(String(tool || '').toLowerCase());
34
+ }
35
+
36
+ export function exploreCategory(tool) {
37
+ return EXPLORE_TOOL_CATEGORY.get(String(tool || '').toLowerCase()) || 'explore';
38
+ }
39
+
40
+ // Test-only accessor so unit tests can enumerate the recognized tools
41
+ // without importing the private Map.
42
+ export function _knownExploreTools() {
43
+ return [...EXPLORE_TOOL_CATEGORY.keys()];
44
+ }