@axplusb/kepler 1.0.4 → 1.0.5
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/KEPLER-README.md +94 -2
- package/package.json +1 -1
- package/pulse/app/activity/page.tsx +1 -1
- package/pulse/app/api/import/route.ts +1 -1
- package/pulse/app/api/memory/route.ts +2 -2
- package/pulse/app/costs/page.tsx +1 -1
- package/pulse/app/export/page.tsx +3 -3
- package/pulse/app/globals.css +3 -3
- package/pulse/app/help/page.tsx +11 -11
- package/pulse/app/history/page.tsx +2 -2
- package/pulse/app/layout.tsx +2 -2
- package/pulse/app/memory/page.tsx +2 -2
- package/pulse/app/overview-client.tsx +1 -1
- package/pulse/app/page.tsx +2 -2
- package/pulse/app/plans/page.tsx +2 -2
- package/pulse/app/projects/page.tsx +1 -1
- package/pulse/app/sessions/page.tsx +1 -1
- package/pulse/app/settings/page.tsx +4 -4
- package/pulse/app/todos/page.tsx +2 -2
- package/pulse/app/tools/page.tsx +1 -1
- package/pulse/cli.js +15 -25
- package/pulse/components/layout/sidebar.tsx +2 -2
- package/pulse/components/sessions/replay/user-tool-result.tsx +1 -1
- package/pulse/lib/claude-reader.ts +1 -1
- package/pulse/lib/decode.ts +1 -1
- package/pulse/package.json +3 -3
- package/src/auth/tarang-auth.mjs +1 -1
- package/src/config/cli-args.mjs +5 -0
- package/src/context/retriever.mjs +1 -1
- package/src/context/skeleton.mjs +1 -1
- package/src/core/approval.mjs +22 -53
- package/src/core/paths.mjs +1 -1
- package/src/core/stream-client.mjs +6 -1
- package/src/core/tool-executor.mjs +1 -1
- package/src/terminal/main.mjs +1 -1
- package/src/terminal/repl.mjs +14 -58
- package/src/terminal/tool-display.mjs +82 -0
- package/src/ui/banner.mjs +7 -14
- package/src/ui/formatter.mjs +6 -40
- package/README.md.orca +0 -82
package/KEPLER-README.md
CHANGED
|
@@ -84,8 +84,100 @@ Platform default included free. Bring your own API key for unlimited.
|
|
|
84
84
|
|
|
85
85
|
| Model | Score | Cost |
|
|
86
86
|
|-------|-------|------|
|
|
87
|
-
| DeepSeek V4 Flash |
|
|
88
|
-
| DeepSeek V4
|
|
87
|
+
| DeepSeek V4 Flash | 39.7% (119/300) | $0.046/instance |
|
|
88
|
+
| DeepSeek V4 Flash (baseline) | 30.7% (92/300) | $0.03/instance |
|
|
89
|
+
| DeepSeek V4 Pro | 50% (14/28 sample) | $0.48/instance |
|
|
90
|
+
|
|
91
|
+
### Running SWE-bench
|
|
92
|
+
|
|
93
|
+
Benchmarks run on an Azure VM (`swebench-eval-vm`, D8s_v3) with a local backend + CLI.
|
|
94
|
+
|
|
95
|
+
**Setup (one-time):**
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Deploy backend + CLI + harness to VM
|
|
99
|
+
./benchmark/vm-setup.sh
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Run benchmark:**
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# SSH into VM
|
|
106
|
+
ssh azureuser@20.9.77.9
|
|
107
|
+
|
|
108
|
+
# Start backend
|
|
109
|
+
source ~/.tarang-benchmark.env
|
|
110
|
+
cd ~/tarang-backend
|
|
111
|
+
source ~/backend-env/bin/activate
|
|
112
|
+
uvicorn app.main:app --port 8000 &
|
|
113
|
+
|
|
114
|
+
# Run all 300 instances (3 parallel workers, skip already-done)
|
|
115
|
+
source ~/swebench-env/bin/activate
|
|
116
|
+
cd ~/tarang-npm
|
|
117
|
+
python3 benchmark/swe-bench/harness.py \
|
|
118
|
+
--dataset lite \
|
|
119
|
+
--model deepseek/deepseek-v4-flash \
|
|
120
|
+
--parallel 3 \
|
|
121
|
+
--skip-done \
|
|
122
|
+
--timeout 600
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Flags:**
|
|
126
|
+
|
|
127
|
+
| Flag | Description |
|
|
128
|
+
|------|-------------|
|
|
129
|
+
| `--dataset lite\|verified\|full` | SWE-bench split (default: lite, 300 instances) |
|
|
130
|
+
| `--model <id>` | OpenRouter model ID |
|
|
131
|
+
| `--parallel <n>` | Number of parallel workers (default: 1) |
|
|
132
|
+
| `--skip-done` | Skip instances already in the output file |
|
|
133
|
+
| `--timeout <s>` | Per-instance timeout in seconds (default: 600) |
|
|
134
|
+
| `--limit <n>` | Only run first N instances |
|
|
135
|
+
| `--instance <id>` | Run a single instance by ID |
|
|
136
|
+
| `--gen-only` | Generate patches only (skip test evaluation) |
|
|
137
|
+
| `--output <path>` | Custom output file path |
|
|
138
|
+
|
|
139
|
+
**Results** are saved incrementally to `benchmark/results/<model>_<dataset>.json` — no data loss on kill.
|
|
140
|
+
|
|
141
|
+
**Convenience scripts on VM:**
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Watchdog (auto-restarts on crash)
|
|
145
|
+
tmux new-session -d -s bench "bash /tmp/start-parallel.sh"
|
|
146
|
+
|
|
147
|
+
# Check progress
|
|
148
|
+
tmux capture-pane -t bench -p | tail -20
|
|
149
|
+
|
|
150
|
+
# Or use the pre-built start script
|
|
151
|
+
./start-benchmark.sh deepseek/deepseek-v4-flash
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Terminal-Bench
|
|
155
|
+
|
|
156
|
+
Use the benchmark-only adapter under `benchmark/terminal-bench/` when you want to
|
|
157
|
+
evaluate Kepler in Terminal-Bench without touching the production CLI.
|
|
158
|
+
|
|
159
|
+
It runs with:
|
|
160
|
+
|
|
161
|
+
- a separate backend venv
|
|
162
|
+
- a separate backend port (`8001` by default)
|
|
163
|
+
- a custom `--agent-import-path kepler_agent:KeplerAgent`
|
|
164
|
+
- per-trial workspace isolation so memory does not leak across tasks
|
|
165
|
+
|
|
166
|
+
Reusable entrypoints:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
benchmark/terminal-bench/setup-kepler-backend.sh
|
|
170
|
+
benchmark/terminal-bench/run-kepler-backend.sh
|
|
171
|
+
benchmark/terminal-bench/run-kepler-benchmark.sh --task-id hello-world
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Environment variables that can be overridden in another machine or CI setup:
|
|
175
|
+
|
|
176
|
+
`BACKEND_DIR`, `FRAMEWORK_DIR`, `VENV_DIR`, `TB_VENV`, `TARANG_NPM_DIR`,
|
|
177
|
+
`ENV_FILE`, `BACKEND_URL`, `PORT`, `MODEL`, `DATASET`, `CONCURRENCY`.
|
|
178
|
+
|
|
179
|
+
Create a local `.tarang-tbench.env` with the benchmark credentials and backend
|
|
180
|
+
auth token, then point `ENV_FILE` at it if needed.
|
|
89
181
|
|
|
90
182
|
## Links
|
|
91
183
|
|
package/package.json
CHANGED
|
@@ -175,7 +175,7 @@ export default function ActivityPage() {
|
|
|
175
175
|
<CalendarDays className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
176
176
|
Day of Week
|
|
177
177
|
</CardTitle>
|
|
178
|
-
<CardDescription>Which days you use
|
|
178
|
+
<CardDescription>Which days you use Kepler most</CardDescription>
|
|
179
179
|
</CardHeader>
|
|
180
180
|
<CardContent className="flex flex-1 flex-col justify-between pt-3 pb-0">
|
|
181
181
|
<DayOfWeekChart data={data.dow_counts} />
|
|
@@ -24,7 +24,7 @@ export async function POST(req: Request) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Note: actual file writing is intentionally not implemented here
|
|
27
|
-
// to prevent accidental corruption of ~/.
|
|
27
|
+
// to prevent accidental corruption of ~/.kepler/ live data.
|
|
28
28
|
// The import feature shows a diff preview only.
|
|
29
29
|
|
|
30
30
|
return NextResponse.json(diff)
|
|
@@ -6,7 +6,7 @@ import { readMemories } from '@/lib/claude-reader'
|
|
|
6
6
|
|
|
7
7
|
export const dynamic = 'force-dynamic'
|
|
8
8
|
|
|
9
|
-
const CLAUDE_DIR = path.join(os.homedir(), '.
|
|
9
|
+
const CLAUDE_DIR = path.join(os.homedir(), '.kepler')
|
|
10
10
|
|
|
11
11
|
export async function GET() {
|
|
12
12
|
const memories = await readMemories()
|
|
@@ -37,7 +37,7 @@ export async function PATCH(req: Request) {
|
|
|
37
37
|
|
|
38
38
|
const filePath = path.join(CLAUDE_DIR, 'projects', projectSlug, 'memory', file)
|
|
39
39
|
|
|
40
|
-
// Ensure the resolved path stays within ~/.
|
|
40
|
+
// Ensure the resolved path stays within ~/.kepler/projects/
|
|
41
41
|
const allowedRoot = path.join(CLAUDE_DIR, 'projects')
|
|
42
42
|
if (!filePath.startsWith(allowedRoot + path.sep)) {
|
|
43
43
|
return NextResponse.json({ error: 'Path outside allowed directory' }, { status: 403 })
|
package/pulse/app/costs/page.tsx
CHANGED
|
@@ -23,7 +23,7 @@ export default function CostsPage() {
|
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<div className="flex flex-col min-h-screen">
|
|
26
|
-
<TopBar title="Costs" subtitle="Estimated spend from ~/.
|
|
26
|
+
<TopBar title="Costs" subtitle="Estimated spend from ~/.kepler/" />
|
|
27
27
|
<div className="p-6 space-y-6">
|
|
28
28
|
|
|
29
29
|
{error && (
|
|
@@ -146,7 +146,7 @@ export default function ExportPage() {
|
|
|
146
146
|
<div className="flex flex-col min-h-screen">
|
|
147
147
|
<TopBar
|
|
148
148
|
title="Export & import"
|
|
149
|
-
subtitle="Download a portable backup of ~/.
|
|
149
|
+
subtitle="Download a portable backup of ~/.kepler/ analytics or merge data from another machine"
|
|
150
150
|
/>
|
|
151
151
|
|
|
152
152
|
<div className="p-6 space-y-6 flex-1">
|
|
@@ -239,7 +239,7 @@ export default function ExportPage() {
|
|
|
239
239
|
</CardTitle>
|
|
240
240
|
</CardHeader>
|
|
241
241
|
<CardContent className="pt-0">
|
|
242
|
-
<p className="text-xs text-muted-foreground">From ~/.
|
|
242
|
+
<p className="text-xs text-muted-foreground">From ~/.kepler/ when available</p>
|
|
243
243
|
</CardContent>
|
|
244
244
|
</Card>
|
|
245
245
|
</>
|
|
@@ -436,7 +436,7 @@ export default function ExportPage() {
|
|
|
436
436
|
<Alert className="border-amber-500/40 bg-amber-500/5">
|
|
437
437
|
<AlertTriangle className="h-4 w-4 text-amber-600" />
|
|
438
438
|
<AlertDescription className="text-xs text-amber-800 dark:text-amber-200/90">
|
|
439
|
-
Writing merged data to ~/.
|
|
439
|
+
Writing merged data to ~/.kepler/ is not implemented in this build — this is a preview only.
|
|
440
440
|
</AlertDescription>
|
|
441
441
|
</Alert>
|
|
442
442
|
<div className="max-h-36 overflow-auto rounded-md border border-border/60 bg-background/50 space-y-1 p-2 font-mono text-[11px] text-muted-foreground">
|
package/pulse/app/globals.css
CHANGED
|
@@ -86,9 +86,9 @@
|
|
|
86
86
|
--radius-lg: var(--radius);
|
|
87
87
|
--radius-xl: calc(var(--radius) + 4px);
|
|
88
88
|
|
|
89
|
-
/*
|
|
90
|
-
--color-
|
|
91
|
-
--color-
|
|
89
|
+
/* Kepler accent colors */
|
|
90
|
+
--color-kepler-cyan: #06b6d4;
|
|
91
|
+
--color-kepler-cyan-light: #22d3ee;
|
|
92
92
|
--color-terminal-cyan: #06b6d4;
|
|
93
93
|
--color-terminal-green: #34d399;
|
|
94
94
|
--color-terminal-amber: #f59e0b;
|
package/pulse/app/help/page.tsx
CHANGED
|
@@ -2,14 +2,14 @@ import { TopBar } from '@/components/layout/top-bar'
|
|
|
2
2
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
3
3
|
|
|
4
4
|
const CLI_COMMANDS = [
|
|
5
|
-
{ cmd: '
|
|
6
|
-
{ cmd: '
|
|
7
|
-
{ cmd: '
|
|
8
|
-
{ cmd: '
|
|
9
|
-
{ cmd: '
|
|
10
|
-
{ cmd: '
|
|
11
|
-
{ cmd: '
|
|
12
|
-
{ cmd: '
|
|
5
|
+
{ cmd: 'kepler', desc: 'Start interactive REPL' },
|
|
6
|
+
{ cmd: 'kepler "instruction"', desc: 'Run a single instruction and exit' },
|
|
7
|
+
{ cmd: 'kepler dashboard', desc: 'Open Kepler Pulse analytics dashboard' },
|
|
8
|
+
{ cmd: 'kepler sessions', desc: 'List recent local sessions' },
|
|
9
|
+
{ cmd: 'kepler stats', desc: 'Show aggregate local session stats' },
|
|
10
|
+
{ cmd: 'kepler history', desc: 'Show recent prompt history' },
|
|
11
|
+
{ cmd: 'kepler login', desc: 'Sign in via browser' },
|
|
12
|
+
{ cmd: 'kepler version', desc: 'Show version' },
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
const REPL_COMMANDS = [
|
|
@@ -38,7 +38,7 @@ const ENV_VARS = [
|
|
|
38
38
|
{ name: 'TARANG_ENV', desc: 'Set backend environment (local, development, production)' },
|
|
39
39
|
{ name: 'ANTHROPIC_API_KEY', desc: 'Direct Anthropic API key for local mode' },
|
|
40
40
|
{ name: 'OPENROUTER_API_KEY', desc: 'OpenRouter API key' },
|
|
41
|
-
{ name: '
|
|
41
|
+
{ name: 'KEPLER_CONFIG_DIR', desc: 'Override config directory (default: ~/.kepler)' },
|
|
42
42
|
{ name: 'TARANG_BACKEND_URL', desc: 'Override backend URL explicitly' },
|
|
43
43
|
]
|
|
44
44
|
|
|
@@ -46,7 +46,7 @@ const FEATURES = [
|
|
|
46
46
|
{ title: 'Multi-Agent Orchestration', desc: 'Primary agent spawns explore, plan, and review sub-agents for complex tasks.' },
|
|
47
47
|
{ title: 'Smart Tool Routing', desc: 'Write operations routed to CLI for local execution. Read tools run server-side.' },
|
|
48
48
|
{ title: 'Safety Guardrails', desc: 'Destructive commands (rm, delete) always require approval. Path validation on all file operations.' },
|
|
49
|
-
{ title: 'Local JSONL Analytics', desc: 'Every session is recorded to ~/.
|
|
49
|
+
{ title: 'Local JSONL Analytics', desc: 'Every session is recorded to ~/.kepler/ for offline analytics via Kepler Pulse.' },
|
|
50
50
|
{ title: 'Conversation History', desc: 'Multi-turn conversations within a REPL session. Context preserved across turns.' },
|
|
51
51
|
{ title: 'BM25 Code Index', desc: 'Project files indexed on startup for fast semantic code search.' },
|
|
52
52
|
]
|
|
@@ -67,7 +67,7 @@ function CommandTable({ commands }: { commands: { cmd: string; desc: string }[]
|
|
|
67
67
|
export default function HelpPage() {
|
|
68
68
|
return (
|
|
69
69
|
<div className="flex flex-col min-h-screen">
|
|
70
|
-
<TopBar title="Help" subtitle="
|
|
70
|
+
<TopBar title="Help" subtitle="Kepler CLI commands, shortcuts, and configuration" />
|
|
71
71
|
<div className="px-6 py-6 space-y-6">
|
|
72
72
|
|
|
73
73
|
<Card>
|
|
@@ -55,7 +55,7 @@ export default function HistoryPage() {
|
|
|
55
55
|
|
|
56
56
|
return (
|
|
57
57
|
<div className="flex flex-col min-h-screen">
|
|
58
|
-
<TopBar title="History" subtitle="~/.
|
|
58
|
+
<TopBar title="History" subtitle="~/.kepler/history.jsonl" />
|
|
59
59
|
<div className="p-4 md:p-6 space-y-4">
|
|
60
60
|
|
|
61
61
|
{error && (
|
|
@@ -92,7 +92,7 @@ export default function HistoryPage() {
|
|
|
92
92
|
{pageEntries.length === 0 ? (
|
|
93
93
|
<div className="text-center py-16 text-muted-foreground text-sm">
|
|
94
94
|
{(data.history?.length ?? 0) === 0
|
|
95
|
-
? 'No history found in ~/.
|
|
95
|
+
? 'No history found in ~/.kepler/history.jsonl'
|
|
96
96
|
: 'No entries match your search.'}
|
|
97
97
|
</div>
|
|
98
98
|
) : (
|
package/pulse/app/layout.tsx
CHANGED
|
@@ -20,8 +20,8 @@ const pressStart2P = Press_Start_2P({
|
|
|
20
20
|
})
|
|
21
21
|
|
|
22
22
|
export const metadata: Metadata = {
|
|
23
|
-
title: '
|
|
24
|
-
description: 'Real-time analytics for your
|
|
23
|
+
title: 'Kepler Pulse',
|
|
24
|
+
description: 'Real-time analytics for your Kepler AI agent sessions',
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export default function RootLayout({
|
|
@@ -260,7 +260,7 @@ export default function MemoryPage() {
|
|
|
260
260
|
|
|
261
261
|
return (
|
|
262
262
|
<div className="flex flex-col min-h-screen">
|
|
263
|
-
<TopBar title="
|
|
263
|
+
<TopBar title="kepler-pulse · memory" subtitle="~/.kepler/projects/*/memory/" />
|
|
264
264
|
<div className="p-4 md:p-6 space-y-5">
|
|
265
265
|
|
|
266
266
|
{error && <p className="text-[#f87171] text-sm font-mono">Error loading memories.</p>}
|
|
@@ -338,7 +338,7 @@ export default function MemoryPage() {
|
|
|
338
338
|
<p className="text-3xl mb-3">🧠</p>
|
|
339
339
|
<p className="text-muted-foreground/60 text-sm font-mono">
|
|
340
340
|
{memories.length === 0
|
|
341
|
-
? 'No memory files found in ~/.
|
|
341
|
+
? 'No memory files found in ~/.kepler/projects/*/memory/'
|
|
342
342
|
: 'No memories match your filter.'}
|
|
343
343
|
</p>
|
|
344
344
|
</div>
|
|
@@ -381,7 +381,7 @@ export function OverviewClient() {
|
|
|
381
381
|
<Card>
|
|
382
382
|
<CardHeader>
|
|
383
383
|
<CardTitle>Recent Sessions</CardTitle>
|
|
384
|
-
<CardDescription>Your latest
|
|
384
|
+
<CardDescription>Your latest Kepler sessions</CardDescription>
|
|
385
385
|
</CardHeader>
|
|
386
386
|
<CardContent>
|
|
387
387
|
<OverviewConversationTable sessions={sessions} />
|
package/pulse/app/page.tsx
CHANGED
|
@@ -5,8 +5,8 @@ export default function OverviewPage() {
|
|
|
5
5
|
return (
|
|
6
6
|
<div className="flex flex-col min-h-screen">
|
|
7
7
|
<TopBar
|
|
8
|
-
title="
|
|
9
|
-
subtitle="Real-time analytics for your
|
|
8
|
+
title="Kepler Pulse"
|
|
9
|
+
subtitle="Real-time analytics for your Kepler AI agent sessions"
|
|
10
10
|
/>
|
|
11
11
|
<OverviewClient />
|
|
12
12
|
</div>
|
package/pulse/app/plans/page.tsx
CHANGED
|
@@ -251,7 +251,7 @@ export default function PlansPage() {
|
|
|
251
251
|
|
|
252
252
|
return (
|
|
253
253
|
<div className="flex flex-col min-h-screen">
|
|
254
|
-
<TopBar title="
|
|
254
|
+
<TopBar title="kepler-pulse · plans" subtitle="~/.kepler/plans/" />
|
|
255
255
|
<div className="p-4 md:p-6 space-y-5">
|
|
256
256
|
|
|
257
257
|
{error && <p className="text-[#f87171] text-sm font-mono">Error: {String(error)}</p>}
|
|
@@ -289,7 +289,7 @@ export default function PlansPage() {
|
|
|
289
289
|
<p className="text-[#d97706] text-2xl mb-3">📋</p>
|
|
290
290
|
<p className="text-muted-foreground/60 text-sm font-mono">
|
|
291
291
|
{plans.length === 0
|
|
292
|
-
? 'No plans found in ~/.
|
|
292
|
+
? 'No plans found in ~/.kepler/plans/'
|
|
293
293
|
: 'No plans match your search.'}
|
|
294
294
|
</p>
|
|
295
295
|
</div>
|
|
@@ -101,7 +101,7 @@ export default function ProjectsPage() {
|
|
|
101
101
|
|
|
102
102
|
{!isLoading && sorted.length === 0 && (
|
|
103
103
|
<div className="text-center py-16 text-muted-foreground text-sm">
|
|
104
|
-
{search ? 'No projects match your search.' : 'No projects found in ~/.
|
|
104
|
+
{search ? 'No projects match your search.' : 'No projects found in ~/.kepler/'}
|
|
105
105
|
</div>
|
|
106
106
|
)}
|
|
107
107
|
</div>
|
|
@@ -18,7 +18,7 @@ export default function SessionsPage() {
|
|
|
18
18
|
return (
|
|
19
19
|
<div className="flex flex-col min-h-screen">
|
|
20
20
|
<TopBar
|
|
21
|
-
title="
|
|
21
|
+
title="Kepler Pulse · Sessions"
|
|
22
22
|
subtitle={data ? `${data.total} total sessions` : 'loading...'}
|
|
23
23
|
/>
|
|
24
24
|
<div className="p-6">
|
|
@@ -83,7 +83,7 @@ export default function SettingsPage() {
|
|
|
83
83
|
|
|
84
84
|
return (
|
|
85
85
|
<div className="flex flex-col min-h-screen">
|
|
86
|
-
<TopBar title="
|
|
86
|
+
<TopBar title="kepler-pulse · settings" subtitle="~/.kepler/settings.json" />
|
|
87
87
|
<div className="p-4 md:p-6 space-y-6">
|
|
88
88
|
{error && <p className="text-[#f87171] text-sm font-mono">Error: {String(error)}</p>}
|
|
89
89
|
{isLoading && (
|
|
@@ -100,13 +100,13 @@ export default function SettingsPage() {
|
|
|
100
100
|
<span className="text-primary text-2xl font-mono font-bold">
|
|
101
101
|
{formatBytes(data.storageBytes)}
|
|
102
102
|
</span>
|
|
103
|
-
<span className="text-muted-foreground text-sm font-mono">used by ~/.
|
|
103
|
+
<span className="text-muted-foreground text-sm font-mono">used by ~/.kepler/</span>
|
|
104
104
|
</div>
|
|
105
105
|
</Section>
|
|
106
106
|
|
|
107
107
|
<Section title="Settings">
|
|
108
108
|
{Object.keys(data.settings).length === 0 ? (
|
|
109
|
-
<p className="text-muted-foreground/60 text-sm font-mono">No settings found in ~/.
|
|
109
|
+
<p className="text-muted-foreground/60 text-sm font-mono">No settings found in ~/.kepler/settings.json</p>
|
|
110
110
|
) : (
|
|
111
111
|
<div className="font-mono text-sm leading-relaxed overflow-x-auto">
|
|
112
112
|
<JsonValue value={data.settings} />
|
|
@@ -139,7 +139,7 @@ export default function SettingsPage() {
|
|
|
139
139
|
|
|
140
140
|
<Section title={`Skills (${data.skills.length})`}>
|
|
141
141
|
{data.skills.length === 0 ? (
|
|
142
|
-
<p className="text-muted-foreground/60 text-sm font-mono">No skills found in ~/.
|
|
142
|
+
<p className="text-muted-foreground/60 text-sm font-mono">No skills found in ~/.kepler/skills/</p>
|
|
143
143
|
) : (
|
|
144
144
|
<div className="grid gap-2">
|
|
145
145
|
{data.skills.map(skill => (
|
package/pulse/app/todos/page.tsx
CHANGED
|
@@ -126,7 +126,7 @@ export default function TodosPage() {
|
|
|
126
126
|
|
|
127
127
|
return (
|
|
128
128
|
<div className="flex flex-col min-h-screen">
|
|
129
|
-
<TopBar title="Todos" subtitle="~/.
|
|
129
|
+
<TopBar title="Todos" subtitle="~/.kepler/todos/" />
|
|
130
130
|
<div className="p-4 md:p-6 space-y-5">
|
|
131
131
|
|
|
132
132
|
{error && (
|
|
@@ -192,7 +192,7 @@ export default function TodosPage() {
|
|
|
192
192
|
<CircleCheck className="w-8 h-8 mx-auto mb-3 text-muted-foreground/40" />
|
|
193
193
|
<p className="text-muted-foreground text-sm">
|
|
194
194
|
{allItems.length === 0
|
|
195
|
-
? 'No todos found in ~/.
|
|
195
|
+
? 'No todos found in ~/.kepler/todos/'
|
|
196
196
|
: 'No todos match your filter.'}
|
|
197
197
|
</p>
|
|
198
198
|
</div>
|
package/pulse/app/tools/page.tsx
CHANGED
|
@@ -201,7 +201,7 @@ export default function ToolsPage() {
|
|
|
201
201
|
{data.versions.length > 0 && (
|
|
202
202
|
<Card>
|
|
203
203
|
<CardHeader>
|
|
204
|
-
<CardTitle>
|
|
204
|
+
<CardTitle>Kepler Version History</CardTitle>
|
|
205
205
|
<CardDescription>Versions seen across your sessions</CardDescription>
|
|
206
206
|
</CardHeader>
|
|
207
207
|
<CardContent>
|
package/pulse/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Launches a Next.js dev server from ~/.
|
|
3
|
+
* Kepler Pulse — local analytics dashboard launcher.
|
|
4
|
+
* Launches a Next.js dev server from ~/.kepler-pulse/ cache directory.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const { spawn, exec } = require('child_process')
|
|
@@ -11,9 +11,9 @@ const path = require('path')
|
|
|
11
11
|
const fs = require('fs')
|
|
12
12
|
|
|
13
13
|
const PKG_DIR = __dirname
|
|
14
|
-
const CACHE_DIR = path.join(os.homedir(), '.
|
|
14
|
+
const CACHE_DIR = path.join(os.homedir(), '.kepler-pulse')
|
|
15
15
|
|
|
16
|
-
// ANSI helpers —
|
|
16
|
+
// ANSI helpers — Kepler cyan palette
|
|
17
17
|
const C = '\x1b[36m' // cyan
|
|
18
18
|
const C2 = '\x1b[96m' // bright cyan
|
|
19
19
|
const DIM = '\x1b[2m'
|
|
@@ -22,20 +22,10 @@ const R = '\x1b[0m'
|
|
|
22
22
|
const G = '\x1b[32m'
|
|
23
23
|
|
|
24
24
|
function printBanner() {
|
|
25
|
-
const art = [
|
|
26
|
-
`${C}${B} ██████╗ ██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗██╗ ███████╗███████╗${R}`,
|
|
27
|
-
`${C}${B}██╔═══██╗██╔══██╗██╔════╝██╔══██╗ ██╔══██╗██║ ██║██║ ██╔════╝██╔════╝${R}`,
|
|
28
|
-
`${C2}${B}██║ ██║██████╔╝██║ ███████║ ██████╔╝██║ ██║██║ ███████╗█████╗ ${R}`,
|
|
29
|
-
`${C2}${B}██║ ██║██╔══██╗██║ ██╔══██║ ██╔═══╝ ██║ ██║██║ ╚════██║██╔══╝ ${R}`,
|
|
30
|
-
`${C}${B}╚██████╔╝██║ ██║╚██████╗██║ ██║ ██║ ╚██████╔╝███████╗███████║███████╗${R}`,
|
|
31
|
-
`${C}${B} ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝${R}`,
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
console.log()
|
|
35
|
-
art.forEach((line) => console.log(' ' + line))
|
|
36
25
|
console.log()
|
|
37
|
-
const configDir = process.env.
|
|
38
|
-
console.log(` ${B}${C}
|
|
26
|
+
const configDir = process.env.KEPLER_CONFIG_DIR ?? path.join(os.homedir(), '.kepler')
|
|
27
|
+
console.log(` ${B}${C}K · E · P · L · E · R${R}`)
|
|
28
|
+
console.log(` ${B}${C2}Kepler Pulse${R} ${DIM}real-time agent analytics${R}`)
|
|
39
29
|
console.log()
|
|
40
30
|
console.log(` ${DIM}Data dir:${R} ${C2}${configDir}${R}`)
|
|
41
31
|
console.log()
|
|
@@ -58,7 +48,7 @@ function openBrowser(url) {
|
|
|
58
48
|
exec(cmd)
|
|
59
49
|
}
|
|
60
50
|
|
|
61
|
-
// Source dirs/files to mirror into ~/.
|
|
51
|
+
// Source dirs/files to mirror into ~/.kepler-pulse/
|
|
62
52
|
const SRC_DIRS = ['app', 'components', 'lib', 'types', 'public']
|
|
63
53
|
const SRC_FILES = ['next.config.ts', 'tsconfig.json', 'postcss.config.mjs', 'components.json']
|
|
64
54
|
|
|
@@ -78,7 +68,7 @@ function syncSource(pkg) {
|
|
|
78
68
|
}
|
|
79
69
|
// Write a minimal package.json with only runtime dependencies
|
|
80
70
|
fs.writeFileSync(path.join(CACHE_DIR, 'package.json'), JSON.stringify({
|
|
81
|
-
name: '
|
|
71
|
+
name: 'kepler-pulse-runtime',
|
|
82
72
|
version: pkg.version,
|
|
83
73
|
dependencies: pkg.dependencies,
|
|
84
74
|
}, null, 2))
|
|
@@ -89,8 +79,8 @@ async function main() {
|
|
|
89
79
|
|
|
90
80
|
const pkg = require(path.join(PKG_DIR, 'package.json'))
|
|
91
81
|
|
|
92
|
-
// Check whether ~/.
|
|
93
|
-
const versionFile = path.join(CACHE_DIR, '.
|
|
82
|
+
// Check whether ~/.kepler-pulse/ is up-to-date for this version
|
|
83
|
+
const versionFile = path.join(CACHE_DIR, '.kepler-pulse-version')
|
|
94
84
|
const cachedVersion = fs.existsSync(versionFile)
|
|
95
85
|
? fs.readFileSync(versionFile, 'utf8').trim()
|
|
96
86
|
: null
|
|
@@ -120,13 +110,13 @@ async function main() {
|
|
|
120
110
|
const port = await findFreePort(3000)
|
|
121
111
|
const url = `http://localhost:${port}`
|
|
122
112
|
|
|
123
|
-
// Pass
|
|
124
|
-
const
|
|
113
|
+
// Pass KEPLER_CONFIG_DIR to the Next.js process so it reads from ~/.kepler/
|
|
114
|
+
const keplerDir = process.env.KEPLER_CONFIG_DIR ?? path.join(os.homedir(), '.kepler')
|
|
125
115
|
const env = {
|
|
126
116
|
...process.env,
|
|
127
117
|
PORT: String(port),
|
|
128
|
-
|
|
129
|
-
CLAUDE_CONFIG_DIR:
|
|
118
|
+
KEPLER_CONFIG_DIR: keplerDir,
|
|
119
|
+
CLAUDE_CONFIG_DIR: keplerDir,
|
|
130
120
|
}
|
|
131
121
|
|
|
132
122
|
console.log(` ${DIM}Starting server on${R} ${C2}${B}${url}${R}\n`)
|
|
@@ -101,7 +101,7 @@ function SidebarContents({
|
|
|
101
101
|
<span
|
|
102
102
|
className="dark:[text-shadow:0_1px_0_#0e4f5c,0_2px_0_#083344,0_3px_6px_rgba(0,0,0,0.35)] [text-shadow:0_1px_0_rgba(255,255,255,0.4)]"
|
|
103
103
|
>
|
|
104
|
-
|
|
104
|
+
Kepler Pulse
|
|
105
105
|
</span>
|
|
106
106
|
</span>
|
|
107
107
|
)}
|
|
@@ -140,7 +140,7 @@ function SidebarContents({
|
|
|
140
140
|
)}>
|
|
141
141
|
{!collapsed && (
|
|
142
142
|
<span className="text-xs text-sidebar-foreground/50">
|
|
143
|
-
|
|
143
|
+
Kepler Pulse
|
|
144
144
|
</span>
|
|
145
145
|
)}
|
|
146
146
|
<button
|
|
@@ -19,7 +19,7 @@ function restAfterAction(s: string, idx: number, needleLen: number): string | un
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Best-effort parse of common
|
|
22
|
+
* Best-effort parse of common Kepler / tool sandbox result strings.
|
|
23
23
|
*/
|
|
24
24
|
export function parseToolResultMessage(raw: string): ParsedToolResult {
|
|
25
25
|
const s = raw.trim()
|
|
@@ -32,7 +32,7 @@ export async function resolveProjectPath(slug: string): Promise<string> {
|
|
|
32
32
|
return slugToPath(slug)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const CLAUDE_DIR = process.env.
|
|
35
|
+
const CLAUDE_DIR = process.env.KEPLER_CONFIG_DIR ?? process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), '.kepler')
|
|
36
36
|
|
|
37
37
|
export function claudePath(...segments: string[]): string {
|
|
38
38
|
return path.join(CLAUDE_DIR, ...segments)
|
package/pulse/lib/decode.ts
CHANGED
|
@@ -10,7 +10,7 @@ export function slugToPath(slug: string): string {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Encode a filesystem path to the slug format used by
|
|
13
|
+
* Encode a filesystem path to the slug format used by Kepler.
|
|
14
14
|
*/
|
|
15
15
|
export function pathToSlug(path: string): string {
|
|
16
16
|
return path.replace(/\//g, '-')
|
package/pulse/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
3
|
-
"version": "1.0.
|
|
2
|
+
"name": "kepler-pulse",
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"private": true,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Kepler Pulse — real-time analytics dashboard for Kepler AI agent sessions",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "next dev",
|
|
8
8
|
"build": "next build",
|
package/src/auth/tarang-auth.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import * as http from 'node:http';
|
|
|
10
10
|
import { getLoginSuccessHTML } from '../ui/banner.mjs';
|
|
11
11
|
import { resolveBackendUrl } from '../core/backend-url.mjs';
|
|
12
12
|
|
|
13
|
-
const CONFIG_DIR = process.env.KEPLER_HOME ||
|
|
13
|
+
const CONFIG_DIR = process.env.KEPLER_HOME || path.join(os.homedir(), '.kepler');
|
|
14
14
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
15
15
|
|
|
16
16
|
export class TarangAuth {
|
package/src/config/cli-args.mjs
CHANGED
|
@@ -26,6 +26,7 @@ export function parseArgs(args) {
|
|
|
26
26
|
systemPrompt: null,
|
|
27
27
|
addDirs: [],
|
|
28
28
|
maxTurns: null,
|
|
29
|
+
timeout: null,
|
|
29
30
|
allowedTools: null,
|
|
30
31
|
disallowedTools: null,
|
|
31
32
|
resume: false,
|
|
@@ -72,6 +73,10 @@ export function parseArgs(args) {
|
|
|
72
73
|
result.maxTurns = parseInt(args[++i], 10);
|
|
73
74
|
break;
|
|
74
75
|
|
|
76
|
+
case '--timeout':
|
|
77
|
+
result.timeout = parseInt(args[++i], 10);
|
|
78
|
+
break;
|
|
79
|
+
|
|
75
80
|
case '--allowedTools':
|
|
76
81
|
result.allowedTools = args[++i]?.split(',').map(s => s.trim());
|
|
77
82
|
break;
|
|
@@ -8,7 +8,7 @@ import * as fs from 'node:fs';
|
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import { indexDir as getIndexDir } from '../core/paths.mjs';
|
|
10
10
|
|
|
11
|
-
const IGNORED_DIRS = new Set(['.git', 'node_modules', '.
|
|
11
|
+
const IGNORED_DIRS = new Set(['.git', 'node_modules', '.kepler', '__pycache__', '.venv', 'venv', 'dist', 'build', '.next']);
|
|
12
12
|
const CODE_EXTS = new Set(['.js', '.mjs', '.ts', '.tsx', '.py', '.go', '.rs', '.java', '.rb', '.php', '.c', '.cpp', '.h', '.css', '.html', '.json', '.yaml', '.yml', '.toml', '.md', '.sh']);
|
|
13
13
|
const MAX_FILE_SIZE = 100_000; // 100KB
|
|
14
14
|
const CHUNK_LINES = 50;
|
package/src/context/skeleton.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
|
|
15
|
-
const IGNORED_DIRS = new Set(['.git', 'node_modules', '.
|
|
15
|
+
const IGNORED_DIRS = new Set(['.git', 'node_modules', '.kepler', '__pycache__', '.venv', 'venv', 'dist', 'build', '.next', '.cache', 'coverage', '.tox']);
|
|
16
16
|
const CODE_EXTS = new Set(['.js', '.mjs', '.ts', '.tsx', '.py', '.go', '.rs', '.java', '.rb', '.c', '.cpp', '.h']);
|
|
17
17
|
const MAX_FILE_SIZE = 200_000;
|
|
18
18
|
|
package/src/core/approval.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Shell commands are risk-assessed (safe/medium/high).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
13
|
+
import { toolDisplayLabel, toolDisplaySummary } from '../terminal/tool-display.mjs';
|
|
14
14
|
|
|
15
15
|
// ── Tool Classification ──
|
|
16
16
|
|
|
@@ -66,44 +66,6 @@ const WHITE = '\x1b[37m';
|
|
|
66
66
|
|
|
67
67
|
const write = (s) => process.stderr.write(s);
|
|
68
68
|
|
|
69
|
-
// ── Tool Description ──
|
|
70
|
-
|
|
71
|
-
function shortPath(p) {
|
|
72
|
-
if (!p) return '';
|
|
73
|
-
const cwd = process.cwd();
|
|
74
|
-
return p.startsWith(cwd) ? p.slice(cwd.length + 1) : p;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function toolSummary(toolName, args) {
|
|
78
|
-
switch (toolName) {
|
|
79
|
-
case 'shell':
|
|
80
|
-
return args.command || '(empty)';
|
|
81
|
-
case 'write_file':
|
|
82
|
-
return shortPath(args.path || args.file_path || '');
|
|
83
|
-
case 'write_project': {
|
|
84
|
-
const files = args.files || [];
|
|
85
|
-
if (files.length === 0) return 'batch write';
|
|
86
|
-
return files.map(f => shortPath(f.path || f.file_path || '')).join(', ');
|
|
87
|
-
}
|
|
88
|
-
case 'edit_file': {
|
|
89
|
-
const fp = shortPath(args.path || args.file_path || '');
|
|
90
|
-
const search = (args.search || '').slice(0, 30);
|
|
91
|
-
return search ? `${fp} "${search}..."` : fp;
|
|
92
|
-
}
|
|
93
|
-
case 'delete_file':
|
|
94
|
-
return shortPath(args.path || args.file_path || '');
|
|
95
|
-
case 'read_file':
|
|
96
|
-
case 'read_files':
|
|
97
|
-
return shortPath(args.file_path || args.path || (args.file_paths || [])[0] || '');
|
|
98
|
-
case 'search_code':
|
|
99
|
-
return `"${args.query || args.pattern || ''}"`;
|
|
100
|
-
case 'list_files':
|
|
101
|
-
return args.pattern || args.path || '';
|
|
102
|
-
default:
|
|
103
|
-
return Object.values(args || {}).filter(v => typeof v === 'string').join(', ').slice(0, 50) || '';
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
69
|
// ── Approval Manager ──
|
|
108
70
|
|
|
109
71
|
export class ApprovalManager {
|
|
@@ -138,7 +100,7 @@ export class ApprovalManager {
|
|
|
138
100
|
return `${DIM}ask${RST}`;
|
|
139
101
|
}
|
|
140
102
|
|
|
141
|
-
async check(toolName, args, requireApproval = false) {
|
|
103
|
+
async check(toolName, args, requireApproval = false, context = {}) {
|
|
142
104
|
if (this.planMode && WRITE_TOOLS.has(toolName)) {
|
|
143
105
|
return { approved: false, reason: `Blocked by plan mode: ${toolName}` };
|
|
144
106
|
}
|
|
@@ -157,11 +119,11 @@ export class ApprovalManager {
|
|
|
157
119
|
return { approved: true };
|
|
158
120
|
}
|
|
159
121
|
if (FORCE_APPROVAL_SHELL.some(p => p.test(args.command || ''))) {
|
|
160
|
-
return this._prompt(toolName, args);
|
|
122
|
+
return this._prompt(toolName, args, context);
|
|
161
123
|
}
|
|
162
124
|
}
|
|
163
125
|
if (NEVER_AUTO_APPROVE.has(toolName)) {
|
|
164
|
-
return this._prompt(toolName, args);
|
|
126
|
+
return this._prompt(toolName, args, context);
|
|
165
127
|
}
|
|
166
128
|
if (this.approveAll) {
|
|
167
129
|
this.history.push({ tool: toolName, decision: 'auto', time: Date.now() });
|
|
@@ -171,20 +133,27 @@ export class ApprovalManager {
|
|
|
171
133
|
this.history.push({ tool: toolName, decision: 'type-auto', time: Date.now() });
|
|
172
134
|
return { approved: true };
|
|
173
135
|
}
|
|
174
|
-
return this._prompt(toolName, args);
|
|
136
|
+
return this._prompt(toolName, args, context);
|
|
175
137
|
}
|
|
176
138
|
|
|
177
|
-
async _prompt(toolName, args) {
|
|
139
|
+
async _prompt(toolName, args, context = {}) {
|
|
178
140
|
const baseRisk = RISK_LEVELS[toolName] || 'medium';
|
|
179
|
-
const
|
|
180
|
-
const
|
|
141
|
+
const assessedRisk = toolName === 'shell' ? assessShellRisk(args.command) : baseRisk;
|
|
142
|
+
const risk = context.risk || assessedRisk;
|
|
143
|
+
const label = toolDisplayLabel(toolName);
|
|
144
|
+
const summary = toolDisplaySummary(toolName, args);
|
|
181
145
|
const isDestructive = risk === 'high';
|
|
182
146
|
|
|
183
|
-
|
|
147
|
+
write(`\n ${isDestructive ? `${YELLOW}⚠${RST}` : `${CYAN}?${RST}`} ${BOLD}Approval required${RST}\n`);
|
|
148
|
+
write(` ${GRAY}Action${RST} ${WHITE}${label}${RST}\n`);
|
|
149
|
+
if (summary) write(` ${GRAY}Target${RST} ${WHITE}${summary.slice(0, 160)}${RST}\n`);
|
|
150
|
+
write(` ${GRAY}Risk${RST} ${isDestructive ? YELLOW : CYAN}${risk}${RST}\n`);
|
|
151
|
+
if (context.reason) write(` ${GRAY}Reason${RST} ${DIM}${String(context.reason).slice(0, 160)}${RST}\n`);
|
|
152
|
+
|
|
184
153
|
if (isDestructive) {
|
|
185
|
-
write(` ${
|
|
154
|
+
write(` ${DIM}Choose${RST} ${WHITE}[y]${RST} allow once ${WHITE}[n]${RST} deny ${WHITE}[d]${RST} details\n`);
|
|
186
155
|
} else {
|
|
187
|
-
write(` ${DIM}
|
|
156
|
+
write(` ${DIM}Choose${RST} ${WHITE}[y]${RST} once ${WHITE}[n]${RST} deny ${WHITE}[t]${RST} this action ${WHITE}[a]${RST} all ${WHITE}[d]${RST} details\n`);
|
|
188
157
|
}
|
|
189
158
|
|
|
190
159
|
const key = await this._readKey();
|
|
@@ -206,7 +175,7 @@ export class ApprovalManager {
|
|
|
206
175
|
|
|
207
176
|
case 'a':
|
|
208
177
|
case 'A':
|
|
209
|
-
if (isDestructive) return this._prompt(toolName, args);
|
|
178
|
+
if (isDestructive) return this._prompt(toolName, args, context);
|
|
210
179
|
this.approveAll = true;
|
|
211
180
|
write(` ${GREEN}✓✓${RST} ${DIM}allow-all activated${RST}\n\n`);
|
|
212
181
|
this.history.push({ tool: toolName, decision: 'approve-all', time: Date.now() });
|
|
@@ -214,7 +183,7 @@ export class ApprovalManager {
|
|
|
214
183
|
|
|
215
184
|
case 't':
|
|
216
185
|
case 'T':
|
|
217
|
-
if (isDestructive) return this._prompt(toolName, args);
|
|
186
|
+
if (isDestructive) return this._prompt(toolName, args, context);
|
|
218
187
|
this.approvedToolTypes.add(toolName);
|
|
219
188
|
write(` ${GREEN}✓${RST} ${DIM}always allow ${toolName}${RST}\n\n`);
|
|
220
189
|
this.history.push({ tool: toolName, decision: 'type-approve', time: Date.now() });
|
|
@@ -223,10 +192,10 @@ export class ApprovalManager {
|
|
|
223
192
|
case 'd':
|
|
224
193
|
case 'D':
|
|
225
194
|
write(`\n${DIM}${JSON.stringify(args, null, 2)}${RST}\n\n`);
|
|
226
|
-
return this._prompt(toolName, args);
|
|
195
|
+
return this._prompt(toolName, args, context);
|
|
227
196
|
|
|
228
197
|
default:
|
|
229
|
-
return this._prompt(toolName, args);
|
|
198
|
+
return this._prompt(toolName, args, context);
|
|
230
199
|
}
|
|
231
200
|
}
|
|
232
201
|
|
package/src/core/paths.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import * as path from 'node:path';
|
|
|
22
22
|
import * as os from 'node:os';
|
|
23
23
|
import * as crypto from 'node:crypto';
|
|
24
24
|
|
|
25
|
-
const KEPLER_HOME = process.env.KEPLER_HOME ||
|
|
25
|
+
const KEPLER_HOME = process.env.KEPLER_HOME || path.join(os.homedir(), '.kepler');
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Hash a project path to a short directory name.
|
|
@@ -290,7 +290,12 @@ export class TarangStreamClient {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
// Use the same ApprovalManager for consistent UX
|
|
293
|
-
const { approved, reason: denyReason } = await this.approval.check(
|
|
293
|
+
const { approved, reason: denyReason } = await this.approval.check(
|
|
294
|
+
tool,
|
|
295
|
+
args || {},
|
|
296
|
+
true,
|
|
297
|
+
{ risk, reason },
|
|
298
|
+
);
|
|
294
299
|
|
|
295
300
|
// Map ApprovalManager decision to framework scope
|
|
296
301
|
let decision, scope;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* This bridge translates those into OCC tool calls and wraps the results.
|
|
6
6
|
*
|
|
7
7
|
* Safety guardrails integrated — prevents destructive operations on source code.
|
|
8
|
-
*
|
|
8
|
+
* 20 tools mapped across file, search, shell, validation, and Git operations.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { createToolRegistry } from '../tools/registry.mjs';
|
package/src/terminal/main.mjs
CHANGED
|
@@ -135,7 +135,7 @@ async function main() {
|
|
|
135
135
|
await runHeadless({
|
|
136
136
|
instruction: args.prompt,
|
|
137
137
|
model: args.model,
|
|
138
|
-
timeout: args.maxTurns ? args.maxTurns * 60 :
|
|
138
|
+
timeout: args.timeout || (args.maxTurns ? args.maxTurns * 60 : 600),
|
|
139
139
|
verbose: args.verbose,
|
|
140
140
|
});
|
|
141
141
|
return;
|
package/src/terminal/repl.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import { ContextRetriever } from '../context/retriever.mjs';
|
|
|
30
30
|
import { buildProjectSkeleton } from '../context/skeleton.mjs';
|
|
31
31
|
import { SessionManager } from '../core/session-manager.mjs';
|
|
32
32
|
import { parseArgs } from '../config/cli-args.mjs';
|
|
33
|
-
import { formatShellCommand, toolDisplayLabel } from './tool-display.mjs';
|
|
33
|
+
import { formatShellCommand, toolDisplayLabel, toolDisplaySummary } from './tool-display.mjs';
|
|
34
34
|
|
|
35
35
|
import { createRequire } from 'node:module';
|
|
36
36
|
const __require = createRequire(import.meta.url);
|
|
@@ -120,18 +120,19 @@ function printBanner(auth) {
|
|
|
120
120
|
const env = process.env.TARANG_ENV || 'production';
|
|
121
121
|
const authStatus = creds.token ? c.green('authenticated') : c.red('/login to start');
|
|
122
122
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
' ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝',
|
|
130
|
-
];
|
|
123
|
+
const CYAN = '\x1b[36m';
|
|
124
|
+
const DIM = '\x1b[2m';
|
|
125
|
+
const BOLD = '\x1b[1m';
|
|
126
|
+
const YELLOW = '\x1b[33m';
|
|
127
|
+
const RST = '\x1b[0m';
|
|
128
|
+
|
|
131
129
|
process.stderr.write('\n');
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
130
|
+
process.stderr.write(`${DIM} ✦${RST}\n`);
|
|
131
|
+
process.stderr.write(`${DIM} ╭──────────────────────────╮${RST}\n`);
|
|
132
|
+
process.stderr.write(`${DIM} │${RST} ${BOLD}${CYAN}K · E · P · L · E · R${RST} ${DIM}│${RST}\n`);
|
|
133
|
+
process.stderr.write(`${DIM} ╰──────── ${YELLOW}◯${RST}${DIM} ───────────────╯${RST}\n`);
|
|
134
|
+
process.stderr.write(`${DIM} ╱ ╲${RST}\n`);
|
|
135
|
+
process.stderr.write(`${DIM} the agentic os${RST}\n`);
|
|
135
136
|
process.stderr.write('\n');
|
|
136
137
|
process.stderr.write(` ${c.gray('v' + VERSION)} ${c.dim(env)} ${authStatus}\n`);
|
|
137
138
|
process.stderr.write('\n');
|
|
@@ -221,52 +222,7 @@ function renderToolCall(data) {
|
|
|
221
222
|
const args = data?.args || {};
|
|
222
223
|
const indent = session.inSubAgent ? ' ' : ' ';
|
|
223
224
|
|
|
224
|
-
|
|
225
|
-
let summary;
|
|
226
|
-
switch (tool) {
|
|
227
|
-
case 'read_file': {
|
|
228
|
-
const fp = shortPath(args.file_path || args.path || '');
|
|
229
|
-
const range = args.start_line && args.end_line
|
|
230
|
-
? ` lines ${args.start_line}-${args.end_line}`
|
|
231
|
-
: args.start_line ? ` from line ${args.start_line}` : '';
|
|
232
|
-
summary = `${fp}${range}`;
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
case 'write_file': {
|
|
236
|
-
const fp = shortPath(args.file_path || args.path || '');
|
|
237
|
-
const lines = args.content ? `, ${args.content.split('\n').length} lines` : '';
|
|
238
|
-
summary = `${fp}${lines}`;
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
case 'edit_file': {
|
|
242
|
-
const fp = shortPath(args.file_path || args.path || '');
|
|
243
|
-
const search = args.search ? `, "${(args.search || '').slice(0, 30)}..."` : '';
|
|
244
|
-
summary = `${fp}${search}`;
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
case 'shell':
|
|
248
|
-
summary = args.command || '';
|
|
249
|
-
break;
|
|
250
|
-
case 'search_code':
|
|
251
|
-
summary = `"${args.query || args.pattern || ''}"`;
|
|
252
|
-
break;
|
|
253
|
-
case 'list_files':
|
|
254
|
-
summary = `${args.pattern || '*'}${args.path ? ` in ${shortPath(args.path)}` : ''}`;
|
|
255
|
-
break;
|
|
256
|
-
case 'delete_file':
|
|
257
|
-
summary = shortPath(args.file_path || args.path || '');
|
|
258
|
-
break;
|
|
259
|
-
case 'read_files':
|
|
260
|
-
summary = (args.file_paths || args.paths || []).map(shortPath).join(', ');
|
|
261
|
-
break;
|
|
262
|
-
case 'write_project': {
|
|
263
|
-
const files = (args.files || []).map(f => shortPath(f.path || f.file_path || ''));
|
|
264
|
-
summary = files.length > 0 ? files.join(', ') : '';
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
default:
|
|
268
|
-
summary = Object.values(args || {}).filter(v => typeof v === 'string').join(', ').slice(0, 60);
|
|
269
|
-
}
|
|
225
|
+
const summary = toolDisplaySummary(tool, args, { cwd: safeCwd() });
|
|
270
226
|
|
|
271
227
|
// Render: ⏺ Human-readable action(summary)
|
|
272
228
|
// Use terminal width minus label and padding, minimum 60
|
|
@@ -21,6 +21,10 @@ const TOOL_LABELS = Object.freeze({
|
|
|
21
21
|
analyze_code: 'Analyze code',
|
|
22
22
|
explore: 'Explore codebase',
|
|
23
23
|
plan: 'Create implementation plan',
|
|
24
|
+
verify: 'Verify implementation',
|
|
25
|
+
debug: 'Debug issue',
|
|
26
|
+
refactor: 'Refactor code',
|
|
27
|
+
ask_user: 'Ask for input',
|
|
24
28
|
});
|
|
25
29
|
|
|
26
30
|
export function toolDisplayLabel(tool) {
|
|
@@ -36,6 +40,84 @@ export function toolDisplayLabel(tool) {
|
|
|
36
40
|
.join(' ');
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
function currentWorkingDirectory() {
|
|
44
|
+
try {
|
|
45
|
+
return process.cwd();
|
|
46
|
+
} catch {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function shortPath(filePath, cwd = currentWorkingDirectory()) {
|
|
52
|
+
const value = String(filePath || '');
|
|
53
|
+
if (cwd && value.startsWith(`${cwd}/`)) return value.slice(cwd.length + 1);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function toolDisplaySummary(tool, args = {}, { cwd } = {}) {
|
|
58
|
+
switch (tool) {
|
|
59
|
+
case 'shell':
|
|
60
|
+
return args.command || '(empty command)';
|
|
61
|
+
case 'read_file': {
|
|
62
|
+
const filePath = shortPath(args.file_path || args.path, cwd);
|
|
63
|
+
if (args.start_line && args.end_line) {
|
|
64
|
+
return `${filePath} · lines ${args.start_line}-${args.end_line}`;
|
|
65
|
+
}
|
|
66
|
+
if (args.start_line) return `${filePath} · from line ${args.start_line}`;
|
|
67
|
+
return filePath;
|
|
68
|
+
}
|
|
69
|
+
case 'read_files':
|
|
70
|
+
return (args.file_paths || args.paths || [])
|
|
71
|
+
.map(filePath => shortPath(filePath, cwd))
|
|
72
|
+
.join(', ');
|
|
73
|
+
case 'write_file': {
|
|
74
|
+
const filePath = shortPath(args.file_path || args.path, cwd);
|
|
75
|
+
const lineCount = typeof args.content === 'string'
|
|
76
|
+
? args.content.split('\n').length
|
|
77
|
+
: null;
|
|
78
|
+
return lineCount ? `${filePath} · ${lineCount} lines` : filePath;
|
|
79
|
+
}
|
|
80
|
+
case 'write_project':
|
|
81
|
+
return (args.files || [])
|
|
82
|
+
.map(file => shortPath(file.path || file.file_path, cwd))
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join(', ') || 'Project files';
|
|
85
|
+
case 'edit_file': {
|
|
86
|
+
const filePath = shortPath(args.file_path || args.path, cwd);
|
|
87
|
+
const search = String(args.search || '').trim();
|
|
88
|
+
return search ? `${filePath} · match "${search.slice(0, 40)}${search.length > 40 ? '...' : ''}"` : filePath;
|
|
89
|
+
}
|
|
90
|
+
case 'delete_file':
|
|
91
|
+
return shortPath(args.file_path || args.path, cwd);
|
|
92
|
+
case 'search_code':
|
|
93
|
+
case 'search_files':
|
|
94
|
+
case 'grep':
|
|
95
|
+
return `"${args.query || args.pattern || ''}"${args.path ? ` in ${shortPath(args.path, cwd)}` : ''}`;
|
|
96
|
+
case 'list_files':
|
|
97
|
+
return `${args.pattern || '*'}${args.path ? ` in ${shortPath(args.path, cwd)}` : ''}`;
|
|
98
|
+
case 'run_tests':
|
|
99
|
+
case 'validate_build':
|
|
100
|
+
case 'lint_check':
|
|
101
|
+
return args.command || args.path || args.file_path || '';
|
|
102
|
+
case 'git_diff':
|
|
103
|
+
case 'git_status':
|
|
104
|
+
return args.path ? shortPath(args.path, cwd) : '';
|
|
105
|
+
case 'explore':
|
|
106
|
+
case 'plan':
|
|
107
|
+
case 'verify':
|
|
108
|
+
case 'debug':
|
|
109
|
+
case 'refactor':
|
|
110
|
+
return args.query || args.task || args.prompt || '';
|
|
111
|
+
case 'ask_user':
|
|
112
|
+
return args.question || args.prompt || '';
|
|
113
|
+
default:
|
|
114
|
+
return Object.values(args)
|
|
115
|
+
.filter(value => typeof value === 'string' && value)
|
|
116
|
+
.join(', ')
|
|
117
|
+
.slice(0, 120);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
39
121
|
export function formatShellCommand(command, colors) {
|
|
40
122
|
const tokens = String(command || '').match(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|&&|\|\||[|;<>]|[^\s]+|\s+/g) || [];
|
|
41
123
|
let expectsCommand = true;
|
package/src/ui/banner.mjs
CHANGED
|
@@ -17,24 +17,17 @@ const BLUE = '\x1b[34m';
|
|
|
17
17
|
const BOLD_GREEN = '\x1b[1;32m';
|
|
18
18
|
const BOLD_CYAN = '\x1b[1;36m';
|
|
19
19
|
|
|
20
|
-
const BANNER_KEPLER = [
|
|
21
|
-
'██╗ ██╗ ███████╗ ██████╗ ██╗ ███████╗ ██████╗ ',
|
|
22
|
-
'██║ ██╔╝ ██╔════╝ ██╔══██╗ ██║ ██╔════╝ ██╔══██╗',
|
|
23
|
-
'█████╔╝ █████╗ ██████╔╝ ██║ █████╗ ██████╔╝',
|
|
24
|
-
'██╔═██╗ ██╔══╝ ██╔═══╝ ██║ ██╔══╝ ██╔══██╗',
|
|
25
|
-
'██║ ██╗ ███████╗ ██║ ███████╗ ███████╗ ██║ ██║',
|
|
26
|
-
'╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝',
|
|
27
|
-
];
|
|
28
|
-
|
|
29
20
|
/**
|
|
30
|
-
* Print the branded
|
|
21
|
+
* Print the branded orbital banner.
|
|
31
22
|
*/
|
|
32
23
|
export function printBanner() {
|
|
33
24
|
process.stderr.write('\n');
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
process.stderr.write(
|
|
25
|
+
process.stderr.write(`${DIM} ✦${RESET}\n`);
|
|
26
|
+
process.stderr.write(`${DIM} ╭──────────────────────────╮${RESET}\n`);
|
|
27
|
+
process.stderr.write(`${DIM} │${RESET} ${BOLD}${CYAN}K · E · P · L · E · R${RESET} ${DIM}│${RESET}\n`);
|
|
28
|
+
process.stderr.write(`${DIM} ╰──────── ${YELLOW}◯${RESET}${DIM} ───────────────╯${RESET}\n`);
|
|
29
|
+
process.stderr.write(`${DIM} ╱ ╲${RESET}\n`);
|
|
30
|
+
process.stderr.write(`${DIM} the agentic os${RESET}\n`);
|
|
38
31
|
process.stderr.write('\n');
|
|
39
32
|
}
|
|
40
33
|
|
package/src/ui/formatter.mjs
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* White for content and summaries
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { toolDisplayLabel, toolDisplaySummary } from '../terminal/tool-display.mjs';
|
|
12
|
+
|
|
11
13
|
const RESET = '\x1b[0m';
|
|
12
14
|
const BOLD = '\x1b[1m';
|
|
13
15
|
const DIM = '\x1b[2m';
|
|
@@ -179,50 +181,14 @@ export class EventFormatter {
|
|
|
179
181
|
|
|
180
182
|
this.toolCount++;
|
|
181
183
|
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
184
|
+
const label = toolDisplayLabel(tool);
|
|
185
|
+
const summary = toolDisplaySummary(tool, args);
|
|
186
|
+
const detail = summary ? `${DIM}${summary}${RESET}` : '';
|
|
187
|
+
process.stderr.write(` ${this._spinner()} [${this.toolCount}] ${CYAN}${label}${RESET}${detail ? ` ${detail}` : ''}\n`);
|
|
185
188
|
|
|
186
189
|
this.toolCalls.push({ name: tool, callId, startTime: Date.now() });
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
_toolDescription(tool, args) {
|
|
190
|
-
switch (tool) {
|
|
191
|
-
case 'read_file':
|
|
192
|
-
return `Reading ${args.file_path || 'file'}...`;
|
|
193
|
-
case 'read_files': {
|
|
194
|
-
const paths = args.file_paths || [];
|
|
195
|
-
return `Reading ${paths.length} files...`;
|
|
196
|
-
}
|
|
197
|
-
case 'write_file':
|
|
198
|
-
return `Writing ${args.file_path || 'file'}...`;
|
|
199
|
-
case 'write_project': {
|
|
200
|
-
const files = args.files || [];
|
|
201
|
-
return `Writing ${files.length} files...`;
|
|
202
|
-
}
|
|
203
|
-
case 'edit_file':
|
|
204
|
-
return `Editing ${args.file_path || 'file'}...`;
|
|
205
|
-
case 'list_files':
|
|
206
|
-
return `Listing files${args.pattern ? ' (' + args.pattern + ')' : ''}...`;
|
|
207
|
-
case 'search_files':
|
|
208
|
-
return `Searching for "${args.pattern || ''}"...`;
|
|
209
|
-
case 'search_code':
|
|
210
|
-
return `Searching code: ${args.query || ''}...`;
|
|
211
|
-
case 'shell': {
|
|
212
|
-
const cmd = (args.command || '').slice(0, 60);
|
|
213
|
-
return `Running: ${cmd}${(args.command || '').length > 60 ? '...' : ''}`;
|
|
214
|
-
}
|
|
215
|
-
case 'validate_build':
|
|
216
|
-
return `Running build validation...`;
|
|
217
|
-
case 'validate_file':
|
|
218
|
-
return `Validating ${args.file_path || 'file'}...`;
|
|
219
|
-
case 'lint_check':
|
|
220
|
-
return `Linting ${args.file_path || 'file'}...`;
|
|
221
|
-
default:
|
|
222
|
-
return `${tool}...`;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
192
|
_toolDone(data) {
|
|
227
193
|
const tool = data?.tool || '';
|
|
228
194
|
const success = data?.success !== false;
|
package/README.md.orca
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# @devtarang/orca
|
|
2
|
-
|
|
3
|
-
Orca — AI coding agent that plans, builds, and ships software from your terminal. Multi-agent orchestration with explore, review, and architect sub-agents.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g @devtarang/orca
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Quick Start
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
orca login # Sign in via browser (GitHub/Google)
|
|
15
|
-
orca # Start interactive REPL
|
|
16
|
-
orca "add user authentication" # Run a single instruction
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Commands
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
orca Start interactive REPL
|
|
23
|
-
orca "instruction" Run a single instruction and exit
|
|
24
|
-
orca login Sign in via browser
|
|
25
|
-
orca dashboard Open Orca Pulse analytics dashboard
|
|
26
|
-
orca sessions List recent local sessions
|
|
27
|
-
orca stats Aggregate session stats (tokens, cost, tools)
|
|
28
|
-
orca history Recent prompt history
|
|
29
|
-
orca version Show version
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## REPL Commands
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
/help Show available commands
|
|
36
|
-
/stats Session metrics (tokens, cost, tools)
|
|
37
|
-
/cost Detailed cost breakdown by model
|
|
38
|
-
/history Conversation history
|
|
39
|
-
/clear Clear conversation history
|
|
40
|
-
/explore <query> Spawn read-only codebase explorer
|
|
41
|
-
/review <query> Spawn code review agent
|
|
42
|
-
/architect <query> Spawn architecture planning agent
|
|
43
|
-
/safety Show safety guardrail status
|
|
44
|
-
/revoke Revoke auto-approvals
|
|
45
|
-
/exit Exit the REPL
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Keyboard
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
Esc Cancel current execution
|
|
52
|
-
Space Pause / resume execution
|
|
53
|
-
Ctrl+C Exit
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Configuration
|
|
57
|
-
|
|
58
|
-
Settings are managed via the web dashboard at [devtarang.ai/dashboard/settings](https://devtarang.ai/dashboard/settings) and synced to the CLI automatically.
|
|
59
|
-
|
|
60
|
-
- **API Key**: Add your OpenRouter/Anthropic/OpenAI key in Settings
|
|
61
|
-
- **Model**: Choose your preferred model (40+ supported)
|
|
62
|
-
- **Gateway**: Select provider (OpenRouter, Anthropic, OpenAI, Bedrock, Google AI, etc.)
|
|
63
|
-
|
|
64
|
-
Config directory: `~/.orca/`
|
|
65
|
-
|
|
66
|
-
## Environment Variables
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
TARANG_ENV Set backend (local, development, production)
|
|
70
|
-
ANTHROPIC_API_KEY Direct Anthropic API key
|
|
71
|
-
OPENROUTER_API_KEY OpenRouter API key
|
|
72
|
-
ORCA_CONFIG_DIR Override config directory (default: ~/.orca)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Links
|
|
76
|
-
|
|
77
|
-
- [Tarang Platform](https://devtarang.ai)
|
|
78
|
-
- [Documentation](https://devtarang.ai/docs)
|
|
79
|
-
|
|
80
|
-
## License
|
|
81
|
-
|
|
82
|
-
MIT
|