@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.
- package/README.md +93 -0
- package/package.json +56 -0
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +50 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +143 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +164 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +53 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +183 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/bahulam-paths.ts +23 -0
- package/pulse/lib/claude-reader.ts +592 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +94 -0
- package/src/agents/multi_workflow_loader.mjs +330 -0
- package/src/agents/parser.mjs +205 -0
- package/src/agents/scaffold.mjs +223 -0
- package/src/agents/teams.mjs +123 -0
- package/src/agents/workflow_loader.mjs +122 -0
- package/src/agents/workflow_scaffold.mjs +249 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +312 -0
- package/src/commands/agent.mjs +221 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +202 -0
- package/src/config/env.mjs +263 -0
- package/src/config/hook-runner.mjs +100 -0
- package/src/config/memory-loader.mjs +32 -0
- package/src/config/model-catalog.mjs +57 -0
- package/src/config/settings-loader.mjs +45 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/prose-chunker.mjs +255 -0
- package/src/context/retriever.mjs +425 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/agent-history.mjs +111 -0
- package/src/core/agent-loop.mjs +486 -0
- package/src/core/approval-log.mjs +145 -0
- package/src/core/approval.mjs +700 -0
- package/src/core/attachments.mjs +666 -0
- package/src/core/backend-url.mjs +68 -0
- package/src/core/bundled-runtime.mjs +418 -0
- package/src/core/cache-control.mjs +92 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +180 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/compact-history.mjs +127 -0
- package/src/core/context-envelope.mjs +54 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/error-guidance.mjs +331 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +460 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +538 -0
- package/src/core/local-store.mjs +836 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +190 -0
- package/src/core/policy-resolver.mjs +156 -0
- package/src/core/pricing.mjs +336 -0
- package/src/core/project-artifacts.mjs +39 -0
- package/src/core/project-context-loader.mjs +139 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limit-display.mjs +121 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/resume-mode.mjs +192 -0
- package/src/core/risk-tier.mjs +388 -0
- package/src/core/safety.mjs +260 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +360 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +957 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +140 -0
- package/src/core/tasks.mjs +196 -0
- package/src/core/tool-executor.mjs +2231 -0
- package/src/core/trust.mjs +160 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/onboarding/preflight.mjs +374 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +700 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +252 -0
- package/src/skills/runner.mjs +55 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/telemetry/index.mjs +122 -0
- package/src/terminal/agents.mjs +353 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +695 -0
- package/src/terminal/init.mjs +145 -0
- package/src/terminal/main.mjs +310 -0
- package/src/terminal/repl-ask-form.mjs +120 -0
- package/src/terminal/repl-explore.mjs +44 -0
- package/src/terminal/repl-format.mjs +317 -0
- package/src/terminal/repl-model-form.mjs +132 -0
- package/src/terminal/repl-render.mjs +833 -0
- package/src/terminal/repl-resume.mjs +640 -0
- package/src/terminal/repl-state.mjs +120 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +5032 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +392 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +231 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/project-overview.mjs +703 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +116 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/approval.mjs +510 -0
- package/src/ui/banner.mjs +232 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +170 -0
- package/src/ui/input-dock.mjs +772 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/mission-report.mjs +296 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/render-queue.mjs +500 -0
- package/src/ui/slash-commands.mjs +257 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +167 -0
- package/src/ui/term.mjs +174 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +740 -0
- package/src/ui/tool-details.mjs +504 -0
- 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
|
+
}
|