@bahulam/code 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/package.json +49 -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 +222 -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 +306 -0
- package/src/commands/agent.mjs +220 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +200 -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/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/retriever.mjs +308 -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 +104 -0
- package/src/core/approval.mjs +476 -0
- package/src/core/attachments.mjs +380 -0
- package/src/core/backend-url.mjs +55 -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 +311 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +448 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +537 -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 +337 -0
- package/src/core/safety.mjs +203 -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 +829 -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 +1950 -0
- package/src/core/trust.mjs +158 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +426 -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 +360 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +652 -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 +96 -0
- package/src/terminal/agents.mjs +177 -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 +269 -0
- package/src/terminal/repl-explore.mjs +35 -0
- package/src/terminal/repl-format.mjs +257 -0
- package/src/terminal/repl-render.mjs +561 -0
- package/src/terminal/repl-resume.mjs +625 -0
- package/src/terminal/repl-state.mjs +103 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +3832 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +240 -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 +641 -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 +263 -0
- package/src/ui/banner.mjs +235 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +164 -0
- package/src/ui/input-dock.mjs +444 -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/slash-commands.mjs +245 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +152 -0
- package/src/ui/term.mjs +159 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +463 -0
- package/src/ui/tool-details.mjs +312 -0
- package/src/ui/transcript-block.mjs +21 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Argument Parser — supports all major Claude Code flags.
|
|
3
|
+
*
|
|
4
|
+
* Flags:
|
|
5
|
+
* --model, -m Model to use
|
|
6
|
+
* --permission-mode Permission mode
|
|
7
|
+
* --print, -p Print mode (non-interactive prompt)
|
|
8
|
+
* --output-format json, text, stream-json
|
|
9
|
+
* --system-prompt Override system prompt
|
|
10
|
+
* --add-dir Additional CLAUDE.md directories
|
|
11
|
+
* --max-turns Maximum conversation turns
|
|
12
|
+
* --allowedTools Comma-separated allowed tools
|
|
13
|
+
* --disallowedTools Comma-separated denied tools
|
|
14
|
+
* --verbose, -v Verbose output
|
|
15
|
+
* --debug, -d Debug mode
|
|
16
|
+
* --version Show version
|
|
17
|
+
* --help, -h Show help
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export function parseArgs(args) {
|
|
21
|
+
const result = {
|
|
22
|
+
prompt: null,
|
|
23
|
+
model: null,
|
|
24
|
+
permissionMode: null,
|
|
25
|
+
outputFormat: null,
|
|
26
|
+
systemPrompt: null,
|
|
27
|
+
addDirs: [],
|
|
28
|
+
maxTurns: null,
|
|
29
|
+
timeout: null,
|
|
30
|
+
allowedTools: null,
|
|
31
|
+
disallowedTools: null,
|
|
32
|
+
resume: false,
|
|
33
|
+
resumeSessionId: null,
|
|
34
|
+
headless: false,
|
|
35
|
+
freeswim: false,
|
|
36
|
+
vision: [],
|
|
37
|
+
verbose: false,
|
|
38
|
+
debug: false,
|
|
39
|
+
showVersion: false,
|
|
40
|
+
showHelp: false,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < args.length; i++) {
|
|
44
|
+
const arg = args[i];
|
|
45
|
+
|
|
46
|
+
switch (arg) {
|
|
47
|
+
case '--model':
|
|
48
|
+
case '-m':
|
|
49
|
+
result.model = args[++i];
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case '--permission-mode':
|
|
53
|
+
result.permissionMode = args[++i];
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case '--print':
|
|
57
|
+
case '-p':
|
|
58
|
+
result.prompt = args[++i];
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case '--output-format':
|
|
62
|
+
result.outputFormat = args[++i];
|
|
63
|
+
break;
|
|
64
|
+
|
|
65
|
+
case '--system-prompt':
|
|
66
|
+
result.systemPrompt = args[++i];
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
case '--add-dir':
|
|
70
|
+
result.addDirs.push(args[++i]);
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case '--max-turns':
|
|
74
|
+
result.maxTurns = parseInt(args[++i], 10);
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case '--timeout':
|
|
78
|
+
result.timeout = parseInt(args[++i], 10);
|
|
79
|
+
break;
|
|
80
|
+
|
|
81
|
+
case '--allowedTools':
|
|
82
|
+
result.allowedTools = args[++i]?.split(',').map(s => s.trim());
|
|
83
|
+
break;
|
|
84
|
+
|
|
85
|
+
case '--disallowedTools':
|
|
86
|
+
result.disallowedTools = args[++i]?.split(',').map(s => s.trim());
|
|
87
|
+
break;
|
|
88
|
+
|
|
89
|
+
case '--resume':
|
|
90
|
+
case '--continue':
|
|
91
|
+
case '-r': {
|
|
92
|
+
result.resume = true;
|
|
93
|
+
// Optional: --resume <sessionId>
|
|
94
|
+
const next = args[i + 1];
|
|
95
|
+
if (next && !next.startsWith('-')) {
|
|
96
|
+
result.resumeSessionId = next;
|
|
97
|
+
i++;
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
case '--headless':
|
|
103
|
+
result.headless = true;
|
|
104
|
+
result.freeswim = true; // headless implies skip permissions
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case '--cache-report':
|
|
108
|
+
// PRD-071 §1.5 — write a machine-readable cache summary to
|
|
109
|
+
// <path> at end of run. Consumed by benchmark/cache-check.sh.
|
|
110
|
+
result.cacheReport = args[++i];
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case '--local':
|
|
114
|
+
// Force LocalAgent path (bypass backend). Meant for benchmarks
|
|
115
|
+
// that need to exercise the CLI's own LLM code — cache_control
|
|
116
|
+
// wiring, prompt-cache stats, etc.
|
|
117
|
+
result.local = true;
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case '--vision': {
|
|
121
|
+
const value = args[++i];
|
|
122
|
+
if (value) result.vision.push(value);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case '--freeswim-open-waters':
|
|
127
|
+
case '--freeswim':
|
|
128
|
+
case '--yes':
|
|
129
|
+
result.freeswim = true;
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case '--verbose':
|
|
133
|
+
case '-v':
|
|
134
|
+
result.verbose = true;
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
case '--debug':
|
|
138
|
+
case '-d':
|
|
139
|
+
result.debug = true;
|
|
140
|
+
break;
|
|
141
|
+
|
|
142
|
+
case '--version':
|
|
143
|
+
result.showVersion = true;
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
case '--help':
|
|
147
|
+
case '-h':
|
|
148
|
+
result.showHelp = true;
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
// Bare argument becomes prompt
|
|
153
|
+
if (!arg.startsWith('-')) {
|
|
154
|
+
result.prompt = arg;
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Print usage/help text.
|
|
165
|
+
* @returns {string}
|
|
166
|
+
*/
|
|
167
|
+
export function getUsageText() {
|
|
168
|
+
return `
|
|
169
|
+
Usage: occ [options] [prompt]
|
|
170
|
+
|
|
171
|
+
Options:
|
|
172
|
+
--model, -m <model> Model to use (default: claude-sonnet-4-6)
|
|
173
|
+
--permission-mode <mode> Permission mode (bypassPermissions, acceptEdits, plan, auto, dontAsk)
|
|
174
|
+
--print, -p <prompt> Non-interactive mode: run prompt and exit
|
|
175
|
+
--output-format <fmt> Output format: text, json, stream-json
|
|
176
|
+
--system-prompt <text> Override system prompt
|
|
177
|
+
--add-dir <dir> Additional directory to search for CLAUDE.md
|
|
178
|
+
--max-turns <n> Maximum conversation turns
|
|
179
|
+
--allowedTools <tools> Comma-separated list of allowed tools
|
|
180
|
+
--disallowedTools <tools> Comma-separated list of denied tools
|
|
181
|
+
--resume, -r [sessionId] Resume last session (or specific session)
|
|
182
|
+
--continue Alias for --resume
|
|
183
|
+
--headless Non-interactive mode: auto-approve, JSONL output
|
|
184
|
+
--cache-report <file> Write prompt-cache summary JSON to <file> (headless only)
|
|
185
|
+
--vision <image-path> Attach image path in headless mode
|
|
186
|
+
--freeswim-open-waters Skip all approval prompts (no boundaries)
|
|
187
|
+
--freeswim Alias for --freeswim-open-waters
|
|
188
|
+
--yes Alias for --freeswim-open-waters
|
|
189
|
+
--verbose, -v Verbose output
|
|
190
|
+
--debug, -d Debug mode
|
|
191
|
+
--version Show version
|
|
192
|
+
--help, -h Show this help
|
|
193
|
+
|
|
194
|
+
Examples:
|
|
195
|
+
occ Start interactive REPL
|
|
196
|
+
occ -p "What is 2+2?" Run prompt and exit
|
|
197
|
+
occ -m claude-haiku-4-5 Use Haiku model
|
|
198
|
+
occ --debug -p "Fix bug" Debug mode with prompt
|
|
199
|
+
`.trim();
|
|
200
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Variables — support for Claude Code env vars.
|
|
3
|
+
*
|
|
4
|
+
* Reads and normalizes the ~50 most important environment variables
|
|
5
|
+
* that control Claude Code behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* All supported environment variables with defaults and descriptions.
|
|
10
|
+
*/
|
|
11
|
+
export const ENV_SCHEMA = {
|
|
12
|
+
// API Configuration
|
|
13
|
+
ANTHROPIC_API_KEY: { type: 'string', description: 'Anthropic API key' },
|
|
14
|
+
ANTHROPIC_BASE_URL: { type: 'string', default: 'https://api.anthropic.com', description: 'Anthropic API base URL' },
|
|
15
|
+
ANTHROPIC_MODEL: { type: 'string', description: 'Override default model' },
|
|
16
|
+
OPENAI_API_KEY: { type: 'string', description: 'OpenAI API key for compatible models' },
|
|
17
|
+
OPENAI_BASE_URL: { type: 'string', default: 'https://api.openai.com/v1', description: 'OpenAI-compatible base URL' },
|
|
18
|
+
GOOGLE_API_KEY: { type: 'string', description: 'Google AI API key' },
|
|
19
|
+
GEMINI_API_KEY: { type: 'string', description: 'Alias for GOOGLE_API_KEY' },
|
|
20
|
+
|
|
21
|
+
// Model Configuration
|
|
22
|
+
CLAUDE_CODE_MAX_OUTPUT_TOKENS: { type: 'number', default: 16384, description: 'Max output tokens' },
|
|
23
|
+
CLAUDE_CODE_SUBAGENT_MODEL: { type: 'string', description: 'Model for subagents' },
|
|
24
|
+
CLAUDE_CODE_EFFORT_LEVEL: { type: 'string', default: 'normal', description: 'Effort level (low/normal/high)' },
|
|
25
|
+
|
|
26
|
+
// Behavior Flags
|
|
27
|
+
CLAUDE_CODE_BRIEF: { type: 'boolean', default: false, description: 'Brief output mode' },
|
|
28
|
+
CLAUDE_CODE_DISABLE_CRON: { type: 'boolean', default: false, description: 'Disable cron tasks' },
|
|
29
|
+
CLAUDE_CODE_ENABLE_TASKS: { type: 'boolean', default: false, description: 'Enable task system' },
|
|
30
|
+
KEPLER_STAGNATION_DETECTION: { type: 'boolean', default: false, description: 'Detect consecutive identical tool-call loops' },
|
|
31
|
+
KEPLER_STAGNATION_THRESHOLD: { type: 'number', default: 3, description: 'Consecutive identical calls before warning' },
|
|
32
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: { type: 'boolean', default: false, description: 'Enable agent teams' },
|
|
33
|
+
CLAUDE_CODE_DEBUG: { type: 'boolean', default: false, description: 'Debug mode' },
|
|
34
|
+
CLAUDE_CODE_DISABLE_TELEMETRY: { type: 'boolean', default: false, description: 'Disable telemetry' },
|
|
35
|
+
|
|
36
|
+
// Permission and Security
|
|
37
|
+
CLAUDE_CODE_PERMISSION_MODE: { type: 'string', default: 'default', description: 'Permission mode' },
|
|
38
|
+
CLAUDE_CODE_SANDBOX: { type: 'boolean', default: true, description: 'Enable sandbox' },
|
|
39
|
+
|
|
40
|
+
// Context and Memory
|
|
41
|
+
CLAUDE_CODE_MAX_CONTEXT_TOKENS: { type: 'number', default: 180000, description: 'Max context window tokens' },
|
|
42
|
+
CLAUDE_CODE_AUTO_COMPACT: { type: 'boolean', default: true, description: 'Auto-compact context' },
|
|
43
|
+
|
|
44
|
+
// UI and Display
|
|
45
|
+
SHOW_THINKING: { type: 'boolean', default: false, description: 'Show thinking blocks' },
|
|
46
|
+
SHOW_TOOL_RESULTS: { type: 'boolean', default: false, description: 'Show tool results in REPL' },
|
|
47
|
+
NO_COLOR: { type: 'boolean', default: false, description: 'Disable colored output' },
|
|
48
|
+
TERM: { type: 'string', description: 'Terminal type' },
|
|
49
|
+
|
|
50
|
+
// MCP
|
|
51
|
+
MCP_DEBUG: { type: 'boolean', default: false, description: 'MCP debug logging' },
|
|
52
|
+
|
|
53
|
+
// Remote
|
|
54
|
+
REMOTE_AGENT_URL: { type: 'string', description: 'Remote agent endpoint' },
|
|
55
|
+
REMOTE_AGENT_TOKEN: { type: 'string', description: 'Remote agent auth token' },
|
|
56
|
+
|
|
57
|
+
// Search
|
|
58
|
+
BRAVE_API_KEY: { type: 'string', description: 'Brave Search API key' },
|
|
59
|
+
SEARXNG_URL: { type: 'string', description: 'SearXNG instance URL' },
|
|
60
|
+
|
|
61
|
+
// Networking
|
|
62
|
+
HTTP_PROXY: { type: 'string', description: 'HTTP proxy URL' },
|
|
63
|
+
HTTPS_PROXY: { type: 'string', description: 'HTTPS proxy URL' },
|
|
64
|
+
NO_PROXY: { type: 'string', description: 'No-proxy list' },
|
|
65
|
+
|
|
66
|
+
// Agent Identity
|
|
67
|
+
AGENT_ID: { type: 'string', default: 'main', description: 'Agent identifier for teams' },
|
|
68
|
+
|
|
69
|
+
// Feature Flags
|
|
70
|
+
CLAUDE_CODE_THINKING: { type: 'boolean', default: false, description: 'Enable extended thinking' },
|
|
71
|
+
CLAUDE_CODE_THINKING_BUDGET: { type: 'number', default: 10000, description: 'Thinking token budget' },
|
|
72
|
+
CLAUDE_CODE_STREAMING: { type: 'boolean', default: true, description: 'Enable streaming' },
|
|
73
|
+
|
|
74
|
+
// Paths
|
|
75
|
+
CLAUDE_CONFIG_DIR: { type: 'string', description: 'Custom config directory' },
|
|
76
|
+
CLAUDE_CACHE_DIR: { type: 'string', description: 'Custom cache directory' },
|
|
77
|
+
|
|
78
|
+
// Extended: OAuth & Auth
|
|
79
|
+
CLAUDE_OAUTH_CLIENT_ID: { type: 'string', description: 'OAuth client ID' },
|
|
80
|
+
CLAUDE_OAUTH_REDIRECT_URI: { type: 'string', default: 'http://localhost:9876/callback', description: 'OAuth redirect URI' },
|
|
81
|
+
ANTHROPIC_AUTH_TOKEN: { type: 'string', description: 'Anthropic auth token (OAuth)' },
|
|
82
|
+
|
|
83
|
+
// Extended: Sandbox & Security
|
|
84
|
+
CLAUDE_CODE_SANDBOX_PLATFORM: { type: 'string', description: 'Override sandbox platform (linux/darwin)' },
|
|
85
|
+
CLAUDE_CODE_INJECTION_CHECK: { type: 'boolean', default: true, description: 'Enable command injection checks' },
|
|
86
|
+
CLAUDE_CODE_PATH_CHECK: { type: 'boolean', default: true, description: 'Enable file path validation' },
|
|
87
|
+
|
|
88
|
+
// Extended: Rate Limiting
|
|
89
|
+
CLAUDE_CODE_MAX_RETRIES: { type: 'number', default: 5, description: 'Max API retries on 429/529' },
|
|
90
|
+
CLAUDE_CODE_RETRY_BASE_DELAY: { type: 'number', default: 1000, description: 'Base retry delay in ms' },
|
|
91
|
+
CLAUDE_CODE_RETRY_MAX_DELAY: { type: 'number', default: 60000, description: 'Max retry delay in ms' },
|
|
92
|
+
|
|
93
|
+
// Extended: Agent Teams
|
|
94
|
+
CLAUDE_CODE_TEAM_SIZE: { type: 'number', default: 5, description: 'Max team size for multi-agent' },
|
|
95
|
+
CLAUDE_CODE_TEAM_BROADCAST: { type: 'boolean', default: false, description: 'Broadcast messages to all teammates' },
|
|
96
|
+
|
|
97
|
+
// Extended: Providers
|
|
98
|
+
AWS_ACCESS_KEY_ID: { type: 'string', description: 'AWS access key for Bedrock' },
|
|
99
|
+
AWS_SECRET_ACCESS_KEY: { type: 'string', description: 'AWS secret key for Bedrock' },
|
|
100
|
+
AWS_REGION: { type: 'string', default: 'us-east-1', description: 'AWS region for Bedrock' },
|
|
101
|
+
GOOGLE_APPLICATION_CREDENTIALS: { type: 'string', description: 'GCP service account credentials path' },
|
|
102
|
+
VERTEX_PROJECT: { type: 'string', description: 'GCP project for Vertex AI' },
|
|
103
|
+
VERTEX_REGION: { type: 'string', default: 'us-central1', description: 'GCP region for Vertex AI' },
|
|
104
|
+
|
|
105
|
+
// Extended: Cron & Scheduling
|
|
106
|
+
CLAUDE_CODE_CRON_INTERVAL: { type: 'number', default: 60000, description: 'Cron check interval in ms' },
|
|
107
|
+
CLAUDE_CODE_SCHEDULED_TASKS_FILE: { type: 'string', description: 'Path to scheduled tasks JSON' },
|
|
108
|
+
|
|
109
|
+
// Extended: Plugins
|
|
110
|
+
CLAUDE_CODE_PLUGIN_DIR: { type: 'string', description: 'Custom plugin directory' },
|
|
111
|
+
CLAUDE_CODE_DISABLE_PLUGINS: { type: 'boolean', default: false, description: 'Disable all plugins' },
|
|
112
|
+
|
|
113
|
+
// Extended: Session
|
|
114
|
+
CLAUDE_CODE_SESSION_TTL: { type: 'number', default: 86400000, description: 'Session TTL in ms (default 24h)' },
|
|
115
|
+
CLAUDE_CODE_AUTO_SAVE: { type: 'boolean', default: true, description: 'Auto-save sessions' },
|
|
116
|
+
|
|
117
|
+
// Extended: Logging
|
|
118
|
+
CLAUDE_CODE_LOG_LEVEL: { type: 'string', default: 'info', description: 'Log level (debug/info/warn/error)' },
|
|
119
|
+
CLAUDE_CODE_LOG_FILE: { type: 'string', description: 'Log file path' },
|
|
120
|
+
|
|
121
|
+
// Extended: Hooks
|
|
122
|
+
CLAUDE_CODE_HOOK_TIMEOUT: { type: 'number', default: 10000, description: 'Hook execution timeout in ms' },
|
|
123
|
+
CLAUDE_CODE_HOOK_FAIL_OPEN: { type: 'boolean', default: true, description: 'Allow on hook failure' },
|
|
124
|
+
|
|
125
|
+
// Extended: Context
|
|
126
|
+
CLAUDE_CODE_COMPACT_THRESHOLD: { type: 'number', default: 0.8, description: 'Context compaction threshold (0-1)' },
|
|
127
|
+
CLAUDE_CODE_MAX_MESSAGES: { type: 'number', default: 200, description: 'Max messages in context' },
|
|
128
|
+
|
|
129
|
+
// Extended: Cache
|
|
130
|
+
CLAUDE_CODE_PROMPT_CACHE: { type: 'boolean', default: true, description: 'Enable prompt caching' },
|
|
131
|
+
CLAUDE_CODE_CACHE_TTL: { type: 'number', default: 300000, description: 'Cache TTL in ms (default 5min)' },
|
|
132
|
+
|
|
133
|
+
// Extended: Output
|
|
134
|
+
CLAUDE_CODE_MAX_LINES: { type: 'number', default: 200, description: 'Max output lines for tools' },
|
|
135
|
+
CLAUDE_CODE_TRUNCATE: { type: 'boolean', default: true, description: 'Truncate long outputs' },
|
|
136
|
+
CLAUDE_CODE_JSON_OUTPUT: { type: 'boolean', default: false, description: 'Output JSON instead of text' },
|
|
137
|
+
|
|
138
|
+
// Extended: Git
|
|
139
|
+
CLAUDE_CODE_GIT_ENABLED: { type: 'boolean', default: true, description: 'Enable git operations' },
|
|
140
|
+
CLAUDE_CODE_GIT_AUTO_COMMIT: { type: 'boolean', default: false, description: 'Auto-commit changes' },
|
|
141
|
+
|
|
142
|
+
// Extended: Editor
|
|
143
|
+
EDITOR: { type: 'string', description: 'Default text editor' },
|
|
144
|
+
VISUAL: { type: 'string', description: 'Default visual editor' },
|
|
145
|
+
|
|
146
|
+
// Extended: Language
|
|
147
|
+
LANG: { type: 'string', description: 'System locale' },
|
|
148
|
+
LC_ALL: { type: 'string', description: 'Locale override' },
|
|
149
|
+
|
|
150
|
+
// Extended: CI/CD
|
|
151
|
+
CI: { type: 'boolean', default: false, description: 'Running in CI environment' },
|
|
152
|
+
GITHUB_ACTIONS: { type: 'boolean', default: false, description: 'Running in GitHub Actions' },
|
|
153
|
+
GITLAB_CI: { type: 'boolean', default: false, description: 'Running in GitLab CI' },
|
|
154
|
+
|
|
155
|
+
// Extended: Container
|
|
156
|
+
CONTAINER: { type: 'boolean', default: false, description: 'Running in container' },
|
|
157
|
+
DOCKER: { type: 'boolean', default: false, description: 'Running in Docker' },
|
|
158
|
+
CODESPACE_NAME: { type: 'string', description: 'GitHub Codespace name' },
|
|
159
|
+
|
|
160
|
+
// Extended: Notification
|
|
161
|
+
CLAUDE_CODE_NOTIFY: { type: 'boolean', default: false, description: 'Enable desktop notifications' },
|
|
162
|
+
CLAUDE_CODE_WEBHOOK_URL: { type: 'string', description: 'Webhook URL for notifications' },
|
|
163
|
+
|
|
164
|
+
// Extended: Experimental
|
|
165
|
+
CLAUDE_CODE_EXPERIMENTAL_MCP: { type: 'boolean', default: false, description: 'Enable experimental MCP features' },
|
|
166
|
+
CLAUDE_CODE_EXPERIMENTAL_TOOLS: { type: 'boolean', default: false, description: 'Enable experimental tools' },
|
|
167
|
+
CLAUDE_CODE_EXPERIMENTAL_VISION: { type: 'boolean', default: false, description: 'Enable vision/image support' },
|
|
168
|
+
CLAUDE_CODE_EXPERIMENTAL_MEMORY: { type: 'boolean', default: false, description: 'Enable persistent memory' },
|
|
169
|
+
|
|
170
|
+
// Extended: Worktree
|
|
171
|
+
CLAUDE_CODE_WORKTREE_DIR: { type: 'string', description: 'Default worktree base directory' },
|
|
172
|
+
CLAUDE_CODE_AUTO_WORKTREE: { type: 'boolean', default: false, description: 'Auto-create worktrees for branches' },
|
|
173
|
+
|
|
174
|
+
// Extended: Notebook
|
|
175
|
+
CLAUDE_CODE_NOTEBOOK_KERNEL: { type: 'string', default: 'python3', description: 'Default Jupyter kernel' },
|
|
176
|
+
|
|
177
|
+
// Extended: LSP
|
|
178
|
+
CLAUDE_CODE_LSP_ENABLED: { type: 'boolean', default: false, description: 'Enable LSP integration' },
|
|
179
|
+
CLAUDE_CODE_LSP_PORT: { type: 'number', default: 0, description: 'LSP server port (0 = auto)' },
|
|
180
|
+
|
|
181
|
+
// Extended: Timeouts
|
|
182
|
+
CLAUDE_CODE_TOOL_TIMEOUT: { type: 'number', default: 120000, description: 'Tool execution timeout in ms' },
|
|
183
|
+
CLAUDE_CODE_API_TIMEOUT: { type: 'number', default: 300000, description: 'API call timeout in ms' },
|
|
184
|
+
CLAUDE_CODE_MCP_TIMEOUT: { type: 'number', default: 30000, description: 'MCP operation timeout in ms' },
|
|
185
|
+
|
|
186
|
+
// Extended: Permissions (additional)
|
|
187
|
+
CLAUDE_CODE_ALLOWED_TOOLS: { type: 'string', description: 'Comma-separated list of allowed tools' },
|
|
188
|
+
CLAUDE_CODE_DISALLOWED_TOOLS: { type: 'string', description: 'Comma-separated list of disallowed tools' },
|
|
189
|
+
|
|
190
|
+
// Extended: Telemetry (additional)
|
|
191
|
+
CLAUDE_CODE_TELEMETRY_ENDPOINT: { type: 'string', description: 'Custom telemetry endpoint URL' },
|
|
192
|
+
CLAUDE_CODE_SENTRY_DSN: { type: 'string', description: 'Sentry DSN for error reporting' },
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Read and normalize all environment variables.
|
|
197
|
+
* @returns {object} normalized env config
|
|
198
|
+
*/
|
|
199
|
+
export function readEnv() {
|
|
200
|
+
const env = {};
|
|
201
|
+
|
|
202
|
+
for (const [key, schema] of Object.entries(ENV_SCHEMA)) {
|
|
203
|
+
const raw = process.env[key];
|
|
204
|
+
if (raw === undefined) {
|
|
205
|
+
if (schema.default !== undefined) {
|
|
206
|
+
env[key] = schema.default;
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
switch (schema.type) {
|
|
212
|
+
case 'boolean':
|
|
213
|
+
env[key] = raw === '1' || raw === 'true' || raw === 'yes';
|
|
214
|
+
break;
|
|
215
|
+
case 'number':
|
|
216
|
+
env[key] = parseInt(raw, 10);
|
|
217
|
+
if (isNaN(env[key])) env[key] = schema.default;
|
|
218
|
+
break;
|
|
219
|
+
default:
|
|
220
|
+
env[key] = raw;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return env;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get a specific env var with type coercion.
|
|
229
|
+
* @param {string} key
|
|
230
|
+
* @param {*} [defaultValue]
|
|
231
|
+
*/
|
|
232
|
+
export function getEnv(key, defaultValue) {
|
|
233
|
+
const schema = ENV_SCHEMA[key];
|
|
234
|
+
const raw = process.env[key];
|
|
235
|
+
|
|
236
|
+
if (raw === undefined) return defaultValue ?? schema?.default;
|
|
237
|
+
|
|
238
|
+
if (schema?.type === 'boolean') return raw === '1' || raw === 'true';
|
|
239
|
+
if (schema?.type === 'number') {
|
|
240
|
+
const n = parseInt(raw, 10);
|
|
241
|
+
return isNaN(n) ? defaultValue : n;
|
|
242
|
+
}
|
|
243
|
+
return raw;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* List all supported env vars with their current values.
|
|
248
|
+
*/
|
|
249
|
+
export function listEnvVars() {
|
|
250
|
+
const result = [];
|
|
251
|
+
for (const [key, schema] of Object.entries(ENV_SCHEMA)) {
|
|
252
|
+
const value = process.env[key];
|
|
253
|
+
result.push({
|
|
254
|
+
key,
|
|
255
|
+
type: schema.type,
|
|
256
|
+
value: value || undefined,
|
|
257
|
+
default: schema.default,
|
|
258
|
+
description: schema.description,
|
|
259
|
+
isSet: value !== undefined,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { loadKeplerSettings } from './settings-loader.mjs';
|
|
4
|
+
|
|
5
|
+
function asArray(value) {
|
|
6
|
+
if (!value) return [];
|
|
7
|
+
return Array.isArray(value) ? value : [value];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function matchesHook(hook, toolName) {
|
|
11
|
+
const matcher = hook.matcher || hook.toolName || hook.tool;
|
|
12
|
+
if (!matcher) return true;
|
|
13
|
+
try {
|
|
14
|
+
return new RegExp(matcher).test(toolName);
|
|
15
|
+
} catch {
|
|
16
|
+
return matcher === toolName;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function runCommand(command, { cwd, env, timeoutMs, input }) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const child = execFile('/bin/sh', ['-lc', command], {
|
|
23
|
+
cwd,
|
|
24
|
+
env,
|
|
25
|
+
timeout: timeoutMs,
|
|
26
|
+
windowsHide: true,
|
|
27
|
+
}, (error, stdout, stderr) => {
|
|
28
|
+
let parsed = null;
|
|
29
|
+
try { parsed = stdout.trim() ? JSON.parse(stdout.trim()) : null; }
|
|
30
|
+
catch { parsed = stdout.trim() ? { output: stdout.trim() } : null; }
|
|
31
|
+
resolve({
|
|
32
|
+
ok: !error,
|
|
33
|
+
code: error?.code ?? 0,
|
|
34
|
+
signal: error?.signal || null,
|
|
35
|
+
stdout,
|
|
36
|
+
stderr,
|
|
37
|
+
parsed,
|
|
38
|
+
error,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
if (input) child.stdin?.end(JSON.stringify(input));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class HookRunner {
|
|
46
|
+
constructor({ cwd = process.cwd(), settings = null, sessionId = null } = {}) {
|
|
47
|
+
this.cwd = cwd;
|
|
48
|
+
this.sessionId = sessionId;
|
|
49
|
+
this.settings = settings || loadKeplerSettings({ cwd }).settings;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
reload() {
|
|
53
|
+
this.settings = loadKeplerSettings({ cwd: this.cwd }).settings;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
hooksFor(event) {
|
|
57
|
+
return asArray(this.settings?.hooks?.[event]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async run(event, payload = {}) {
|
|
61
|
+
const results = [];
|
|
62
|
+
for (const hook of this.hooksFor(event)) {
|
|
63
|
+
const toolName = payload.toolName || payload.tool || '';
|
|
64
|
+
if ((event === 'PreToolUse' || event === 'PostToolUse') && !matchesHook(hook, toolName)) continue;
|
|
65
|
+
const timeoutSeconds = hook.timeout ?? this.settings?.hooks?.timeout ?? 5;
|
|
66
|
+
const input = {
|
|
67
|
+
event,
|
|
68
|
+
tool_name: toolName,
|
|
69
|
+
tool_input: payload.input || payload.args || {},
|
|
70
|
+
tool_result: payload.result || null,
|
|
71
|
+
project_dir: this.cwd,
|
|
72
|
+
session_id: this.sessionId || '',
|
|
73
|
+
};
|
|
74
|
+
const env = {
|
|
75
|
+
...process.env,
|
|
76
|
+
...(this.settings?.env || {}),
|
|
77
|
+
KEPLER_TOOL_NAME: toolName,
|
|
78
|
+
KEPLER_TOOL_INPUT_FILE_PATH: input.tool_input.file_path || input.tool_input.path || '',
|
|
79
|
+
KEPLER_PROJECT_DIR: this.cwd,
|
|
80
|
+
KEPLER_SESSION_ID: this.sessionId || '',
|
|
81
|
+
KEPLER_TURN_ID: payload.turnId || '',
|
|
82
|
+
};
|
|
83
|
+
const result = await runCommand(hook.command, {
|
|
84
|
+
cwd: path.resolve(this.cwd),
|
|
85
|
+
env,
|
|
86
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
87
|
+
input,
|
|
88
|
+
});
|
|
89
|
+
results.push({ hook, ...result });
|
|
90
|
+
if (result.code === 2 || result.parsed?.block === true || result.parsed?.decision === 'deny') {
|
|
91
|
+
return {
|
|
92
|
+
blocked: true,
|
|
93
|
+
message: result.parsed?.message || result.stderr?.trim() || `Blocked by hook: ${hook.command}`,
|
|
94
|
+
results,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { blocked: false, results };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { bahulamHome, projectConfigDir } from '../core/paths.mjs';
|
|
4
|
+
|
|
5
|
+
function readIfExists(filePath, maxChars = 12000) {
|
|
6
|
+
try {
|
|
7
|
+
if (!fs.existsSync(filePath)) return null;
|
|
8
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
9
|
+
return {
|
|
10
|
+
path: filePath,
|
|
11
|
+
content: content.length > maxChars
|
|
12
|
+
? content.slice(0, maxChars) + '\n\n[...truncated...]'
|
|
13
|
+
: content,
|
|
14
|
+
};
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function loadKeplerMemory({ cwd = process.cwd() } = {}) {
|
|
21
|
+
const files = [];
|
|
22
|
+
const global = readIfExists(path.join(bahulamHome(), 'KEPLER.md'));
|
|
23
|
+
if (global) files.push({ source: 'global', ...global });
|
|
24
|
+
|
|
25
|
+
const topLevel = readIfExists(path.join(cwd, 'KEPLER.md'));
|
|
26
|
+
if (topLevel) files.push({ source: 'project-top-level', ...topLevel });
|
|
27
|
+
|
|
28
|
+
const project = readIfExists(path.join(projectConfigDir(cwd), 'KEPLER.md'));
|
|
29
|
+
if (project) files.push({ source: 'project', ...project });
|
|
30
|
+
|
|
31
|
+
return files;
|
|
32
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { deepMerge } from '../core/policy-resolver.mjs';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_KEPLER_SETTINGS = Object.freeze({
|
|
6
|
+
env: {},
|
|
7
|
+
permissions: {
|
|
8
|
+
shellAllowlist: [],
|
|
9
|
+
editDenylist: [],
|
|
10
|
+
},
|
|
11
|
+
hooks: {
|
|
12
|
+
UserPromptSubmit: [],
|
|
13
|
+
PreToolUse: [],
|
|
14
|
+
PostToolUse: [],
|
|
15
|
+
Stop: [],
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function readJson(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(filePath)) return null;
|
|
22
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return { __error: err.message };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function loadKeplerSettings({ cwd = process.cwd() } = {}) {
|
|
29
|
+
const base = path.join(cwd, '.bahulam');
|
|
30
|
+
const layers = [
|
|
31
|
+
{ name: 'default', path: null, data: DEFAULT_KEPLER_SETTINGS },
|
|
32
|
+
];
|
|
33
|
+
for (const [name, file] of [
|
|
34
|
+
['project', path.join(base, 'settings.json')],
|
|
35
|
+
['local', path.join(base, 'settings.local.json')],
|
|
36
|
+
]) {
|
|
37
|
+
const data = readJson(file);
|
|
38
|
+
if (data && !data.__error) layers.push({ name, path: file, data });
|
|
39
|
+
else if (data?.__error) layers.push({ name, path: file, error: data.__error, data: {} });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let settings = {};
|
|
43
|
+
for (const layer of layers) settings = deepMerge(settings, layer.data || {});
|
|
44
|
+
return { settings, layers };
|
|
45
|
+
}
|