@axplusb/kepler 0.0.1 → 1.0.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 +82 -0
- package/package.json +36 -4
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -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 +52 -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/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 +142 -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 +159 -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 +52 -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 +182 -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/claude-reader.ts +594 -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 +89 -0
- package/src/agents/parser.mjs +98 -0
- package/src/agents/teams.mjs +123 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +277 -0
- package/src/config/cli-args.mjs +173 -0
- package/src/config/env.mjs +263 -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 +270 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/core/agent-loop.mjs +480 -0
- package/src/core/approval.mjs +273 -0
- package/src/core/backend-url.mjs +57 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +149 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/headless.mjs +168 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +351 -0
- package/src/core/local-agent.mjs +429 -0
- package/src/core/local-store.mjs +325 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +98 -0
- package/src/core/pricing.mjs +314 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/safety.mjs +200 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +317 -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 +367 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +135 -0
- package/src/core/tool-executor.mjs +725 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +370 -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/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +573 -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/loader.mjs +147 -0
- package/src/skills/runner.mjs +55 -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 +421 -0
- package/src/terminal/main.mjs +150 -0
- package/src/terminal/repl.mjs +1484 -0
- package/src/terminal/tool-display.mjs +58 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +148 -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/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +132 -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/banner.mjs +237 -0
- package/src/ui/commands.mjs +499 -0
- package/src/ui/formatter.mjs +379 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/slash-commands.mjs +258 -0
- package/index.js +0 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI Terminal Renderer — zero dependencies, zero flickering.
|
|
3
|
+
*
|
|
4
|
+
* Provides cursor control, colors, box drawing, progress bars,
|
|
5
|
+
* in-place updates, and a persistent status bar.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const ESC = '\x1b[';
|
|
9
|
+
const write = (s) => process.stderr.write(s);
|
|
10
|
+
|
|
11
|
+
// ── Cursor Control ──
|
|
12
|
+
|
|
13
|
+
export const cursor = {
|
|
14
|
+
hide: () => write(`${ESC}?25l`),
|
|
15
|
+
show: () => write(`${ESC}?25h`),
|
|
16
|
+
save: () => write(`${ESC}s`),
|
|
17
|
+
restore: () => write(`${ESC}u`),
|
|
18
|
+
to: (row, col) => write(`${ESC}${row};${col}H`),
|
|
19
|
+
up: (n = 1) => write(`${ESC}${n}A`),
|
|
20
|
+
down: (n = 1) => write(`${ESC}${n}B`),
|
|
21
|
+
right: (n = 1) => write(`${ESC}${n}C`),
|
|
22
|
+
left: (n = 1) => write(`${ESC}${n}D`),
|
|
23
|
+
col: (n = 1) => write(`${ESC}${n}G`),
|
|
24
|
+
clearLine: () => write(`${ESC}2K`),
|
|
25
|
+
clearDown: () => write(`${ESC}J`),
|
|
26
|
+
clearScreen: () => write(`${ESC}2J${ESC}H`),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ── Colors ──
|
|
30
|
+
|
|
31
|
+
export const c = {
|
|
32
|
+
reset: (s) => `${ESC}0m${s}${ESC}0m`,
|
|
33
|
+
bold: (s) => `${ESC}1m${s}${ESC}0m`,
|
|
34
|
+
dim: (s) => `${ESC}2m${s}${ESC}0m`,
|
|
35
|
+
italic: (s) => `${ESC}3m${s}${ESC}0m`,
|
|
36
|
+
underline: (s) => `${ESC}4m${s}${ESC}0m`,
|
|
37
|
+
red: (s) => `${ESC}31m${s}${ESC}0m`,
|
|
38
|
+
green: (s) => `${ESC}32m${s}${ESC}0m`,
|
|
39
|
+
yellow: (s) => `${ESC}33m${s}${ESC}0m`,
|
|
40
|
+
blue: (s) => `${ESC}34m${s}${ESC}0m`,
|
|
41
|
+
magenta: (s) => `${ESC}35m${s}${ESC}0m`,
|
|
42
|
+
brand: (s) => `${ESC}36m${s}${ESC}0m`,
|
|
43
|
+
cyan: (s) => `${ESC}94m${s}${ESC}0m`,
|
|
44
|
+
cyanRegular: (s) => `${ESC}36m${s}${ESC}0m`,
|
|
45
|
+
cyanBold: (s) => `${ESC}1;36m${s}${ESC}0m`,
|
|
46
|
+
white: (s) => `${ESC}97m${s}${ESC}0m`,
|
|
47
|
+
gray: (s) => `${ESC}90m${s}${ESC}0m`,
|
|
48
|
+
bgRed: (s) => `${ESC}41m${s}${ESC}0m`,
|
|
49
|
+
bgGreen: (s) => `${ESC}42m${s}${ESC}0m`,
|
|
50
|
+
bgCyan: (s) => `${ESC}46m${s}${ESC}0m`,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ── Box Drawing ──
|
|
54
|
+
|
|
55
|
+
const BOX = { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' };
|
|
56
|
+
|
|
57
|
+
export function drawBox(content, { borderColor = 'brand', width } = {}) {
|
|
58
|
+
const w = width || (process.stdout.columns || 80) - 2;
|
|
59
|
+
const colorFn = c[borderColor] || c.brand;
|
|
60
|
+
const lines = content.split('\n');
|
|
61
|
+
|
|
62
|
+
write(colorFn(`${BOX.tl}${BOX.h.repeat(w)}${BOX.tr}`) + '\n');
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
const plain = stripAnsi(line);
|
|
65
|
+
const pad = Math.max(0, w - plain.length);
|
|
66
|
+
write(`${colorFn(BOX.v)} ${line}${' '.repeat(pad)}${colorFn(BOX.v)}\n`);
|
|
67
|
+
}
|
|
68
|
+
write(colorFn(`${BOX.bl}${BOX.h.repeat(w)}${BOX.br}`) + '\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Progress Bar ──
|
|
72
|
+
|
|
73
|
+
export function progressBar(percent, width = 20, label = '') {
|
|
74
|
+
const p = Math.max(0, Math.min(100, percent));
|
|
75
|
+
const filled = Math.round((p / 100) * width);
|
|
76
|
+
const empty = width - filled;
|
|
77
|
+
const color = p < 50 ? c.green : p < 80 ? c.yellow : c.red;
|
|
78
|
+
const bar = color('█'.repeat(filled)) + c.gray('░'.repeat(empty));
|
|
79
|
+
const pct = `${Math.round(p)}%`.padStart(4);
|
|
80
|
+
return label ? `${c.gray(label.padEnd(8))}${bar} ${pct}` : `${bar} ${pct}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Spinner ──
|
|
84
|
+
|
|
85
|
+
const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'];
|
|
86
|
+
let _spinnerIdx = 0;
|
|
87
|
+
|
|
88
|
+
export function spinner(text) {
|
|
89
|
+
const frame = SPINNER_FRAMES[_spinnerIdx % SPINNER_FRAMES.length];
|
|
90
|
+
_spinnerIdx++;
|
|
91
|
+
return `${c.brand(frame)} ${c.brand(text)}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── In-Place Update ──
|
|
95
|
+
|
|
96
|
+
let _lastLineCount = 0;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Write text, erasing the previous in-place output.
|
|
100
|
+
* Call with '' to clear.
|
|
101
|
+
*/
|
|
102
|
+
export function inPlace(text) {
|
|
103
|
+
// Erase previous lines
|
|
104
|
+
if (_lastLineCount > 0) {
|
|
105
|
+
for (let i = 0; i < _lastLineCount; i++) {
|
|
106
|
+
cursor.up();
|
|
107
|
+
cursor.clearLine();
|
|
108
|
+
cursor.col(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (text) {
|
|
112
|
+
write(text + '\n');
|
|
113
|
+
_lastLineCount = text.split('\n').length;
|
|
114
|
+
} else {
|
|
115
|
+
_lastLineCount = 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Status Bar (persistent bottom) ──
|
|
120
|
+
|
|
121
|
+
export function statusBar(parts) {
|
|
122
|
+
const sep = c.gray(' ┃ ');
|
|
123
|
+
const line = parts.join(sep);
|
|
124
|
+
// Save position, go to bottom, write, restore
|
|
125
|
+
const rows = process.stdout.rows || 30;
|
|
126
|
+
cursor.save();
|
|
127
|
+
cursor.to(rows, 1);
|
|
128
|
+
cursor.clearLine();
|
|
129
|
+
write(` ${line}`);
|
|
130
|
+
cursor.restore();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Helpers ──
|
|
134
|
+
|
|
135
|
+
export function stripAnsi(str) {
|
|
136
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function truncate(str, max) {
|
|
140
|
+
if (!str) return '';
|
|
141
|
+
const plain = stripAnsi(str);
|
|
142
|
+
if (plain.length <= max) return str;
|
|
143
|
+
return str.slice(0, max - 3) + '...';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function hr(char = '─', color = 'gray') {
|
|
147
|
+
const w = process.stdout.columns || 80;
|
|
148
|
+
write(c[color](char.repeat(w)) + '\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Markdown Rendering ──
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Render markdown to ANSI-styled terminal text.
|
|
155
|
+
* Supports: headers, bold, italic, code, code blocks, tables, blockquotes,
|
|
156
|
+
* task lists, lists, and links.
|
|
157
|
+
*/
|
|
158
|
+
export function renderMarkdown(text) {
|
|
159
|
+
if (!text) return '';
|
|
160
|
+
|
|
161
|
+
const lines = text.split('\n');
|
|
162
|
+
const out = [];
|
|
163
|
+
let inCodeBlock = false;
|
|
164
|
+
let codeLang = '';
|
|
165
|
+
|
|
166
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
167
|
+
const line = lines[lineIndex];
|
|
168
|
+
// Code block start/end
|
|
169
|
+
if (line.trimStart().startsWith('```')) {
|
|
170
|
+
if (inCodeBlock) {
|
|
171
|
+
out.push(c.gray(' └' + '─'.repeat(40)));
|
|
172
|
+
inCodeBlock = false;
|
|
173
|
+
codeLang = '';
|
|
174
|
+
} else {
|
|
175
|
+
codeLang = line.trim().slice(3).trim();
|
|
176
|
+
out.push(c.gray(' ┌' + '─'.repeat(4) + (codeLang ? ` ${codeLang} ` : '') + '─'.repeat(Math.max(0, 35 - codeLang.length))));
|
|
177
|
+
inCodeBlock = true;
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (inCodeBlock) {
|
|
183
|
+
out.push(c.gray(' │ ') + renderCodeLine(line, codeLang));
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// GitHub-flavored Markdown table
|
|
188
|
+
if (
|
|
189
|
+
line.includes('|') &&
|
|
190
|
+
lineIndex + 1 < lines.length &&
|
|
191
|
+
isTableSeparator(lines[lineIndex + 1])
|
|
192
|
+
) {
|
|
193
|
+
const headers = parseTableRow(line);
|
|
194
|
+
const rows = [];
|
|
195
|
+
lineIndex += 2;
|
|
196
|
+
while (lineIndex < lines.length && lines[lineIndex].includes('|') && lines[lineIndex].trim()) {
|
|
197
|
+
rows.push(parseTableRow(lines[lineIndex]));
|
|
198
|
+
lineIndex++;
|
|
199
|
+
}
|
|
200
|
+
lineIndex--;
|
|
201
|
+
out.push(...renderMarkdownTable(headers, rows));
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Headers
|
|
206
|
+
if (line.startsWith('### ')) {
|
|
207
|
+
out.push(c.bold(c.brand(line.slice(4))));
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (line.startsWith('## ')) {
|
|
211
|
+
out.push(c.bold(c.brand(line.slice(3))));
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (line.startsWith('# ')) {
|
|
215
|
+
out.push(c.bold(c.brand(line.slice(2))));
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Horizontal rule
|
|
220
|
+
if (/^---+$/.test(line.trim())) {
|
|
221
|
+
out.push(c.gray('─'.repeat(40)));
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Blockquotes
|
|
226
|
+
if (/^\s*>\s?/.test(line)) {
|
|
227
|
+
const content = line.replace(/^\s*>\s?/, '');
|
|
228
|
+
out.push(`${c.gray(' │')} ${c.italic(content)}`);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Task lists
|
|
233
|
+
const task = line.match(/^(\s*)[-*]\s+\[([ xX])\]\s+(.*)/);
|
|
234
|
+
if (task) {
|
|
235
|
+
const done = task[2].toLowerCase() === 'x';
|
|
236
|
+
const renderState = done ? c.green : c.gray;
|
|
237
|
+
const marker = done ? '✓' : '○';
|
|
238
|
+
out.push(`${task[1]} ${renderState(`${marker} ${task[3]}`)}`);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Lists
|
|
243
|
+
if (/^\s*[-*]\s/.test(line)) {
|
|
244
|
+
const indent = line.match(/^(\s*)/)[1];
|
|
245
|
+
const content = line.replace(/^\s*[-*]\s/, '');
|
|
246
|
+
out.push(`${indent} ${c.brand('•')} ${inlineMarkdown(content)}`);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Numbered lists
|
|
251
|
+
if (/^\s*\d+\.\s/.test(line)) {
|
|
252
|
+
const match = line.match(/^(\s*)(\d+)\.\s(.*)/);
|
|
253
|
+
if (match) {
|
|
254
|
+
out.push(`${match[1]} ${c.brand(match[2] + '.')} ${inlineMarkdown(match[3])}`);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Regular line with inline formatting
|
|
260
|
+
out.push(inlineMarkdown(line));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return out.join('\n');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function renderCodeLine(line, language) {
|
|
267
|
+
const lang = String(language || '').toLowerCase();
|
|
268
|
+
if (lang === 'diff') {
|
|
269
|
+
if (line.startsWith('+')) return c.green(line);
|
|
270
|
+
if (line.startsWith('-')) return c.red(line);
|
|
271
|
+
if (line.startsWith('@@')) return c.brand(line);
|
|
272
|
+
}
|
|
273
|
+
if (lang === 'json' || lang === 'yaml' || lang === 'yml' || lang === 'toml') {
|
|
274
|
+
return line.replace(/^(\s*)(["']?[\w.-]+["']?)(\s*[:=])(.*)$/, (_, space, key, separator, value) =>
|
|
275
|
+
`${space}${c.cyanBold(key)}${c.gray(separator)}${c.cyanRegular(value)}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
return c.cyan(line);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function parseTableRow(line) {
|
|
282
|
+
return line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(cell => cell.trim());
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function isTableSeparator(line) {
|
|
286
|
+
const cells = parseTableRow(line);
|
|
287
|
+
return cells.length > 0 && cells.every(cell => /^:?-{3,}:?$/.test(cell));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function renderMarkdownTable(headers, rows) {
|
|
291
|
+
const columnCount = Math.max(headers.length, ...rows.map(row => row.length));
|
|
292
|
+
const widths = Array.from({ length: columnCount }, (_, index) => {
|
|
293
|
+
const values = [headers[index] || '', ...rows.map(row => row[index] || '')];
|
|
294
|
+
return Math.min(40, Math.max(...values.map(value => stripAnsi(value).length), 3));
|
|
295
|
+
});
|
|
296
|
+
const border = c.gray(` ${widths.map(width => '─'.repeat(width + 2)).join('┼')}`);
|
|
297
|
+
const formatRow = (row, header = false) => {
|
|
298
|
+
const cells = widths.map((width, index) => {
|
|
299
|
+
const value = truncate(row[index] || '', width);
|
|
300
|
+
const padded = value + ' '.repeat(Math.max(0, width - stripAnsi(value).length));
|
|
301
|
+
return header ? c.bold(padded) : inlineMarkdown(padded);
|
|
302
|
+
});
|
|
303
|
+
return ` ${cells.map(cell => ` ${cell} `).join(c.gray('│'))}`;
|
|
304
|
+
};
|
|
305
|
+
return [formatRow(headers, true), border, ...rows.map(row => formatRow(row))];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Apply inline markdown: **bold**, *italic*, `code`, [links](url)
|
|
310
|
+
*/
|
|
311
|
+
function inlineMarkdown(text) {
|
|
312
|
+
return text
|
|
313
|
+
.replace(/\*\*(.+?)\*\*/g, (_, s) => c.bold(s))
|
|
314
|
+
.replace(/\*(.+?)\*/g, (_, s) => c.italic(s))
|
|
315
|
+
.replace(/`(.+?)`/g, (_, s) => c.cyan(s))
|
|
316
|
+
.replace(
|
|
317
|
+
/\[(.+?)\]\((.+?)\)/g,
|
|
318
|
+
(_, label, url) => `${c.underline(c.white(label))} ${c.gray('(' + url + ')')}`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── Diff Display ──
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Render a unified diff with +/- color highlighting.
|
|
326
|
+
*/
|
|
327
|
+
export function renderDiff(diffText) {
|
|
328
|
+
if (!diffText) return '';
|
|
329
|
+
const lines = diffText.split('\n');
|
|
330
|
+
const out = [];
|
|
331
|
+
for (const line of lines) {
|
|
332
|
+
if (line.startsWith('+++') || line.startsWith('---')) {
|
|
333
|
+
out.push(c.bold(line));
|
|
334
|
+
} else if (line.startsWith('@@')) {
|
|
335
|
+
out.push(c.brand(line));
|
|
336
|
+
} else if (line.startsWith('+')) {
|
|
337
|
+
out.push(c.green(line));
|
|
338
|
+
} else if (line.startsWith('-')) {
|
|
339
|
+
out.push(c.red(line));
|
|
340
|
+
} else {
|
|
341
|
+
out.push(c.gray(line));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return out.join('\n');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ── Info Panel ──
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Display a labeled info panel (no box borders, just indented content).
|
|
351
|
+
* @param {string} label - Panel title
|
|
352
|
+
* @param {Array<[string, string]>} rows - [label, value] pairs
|
|
353
|
+
* @param {string} [color='brand'] - Title color
|
|
354
|
+
*/
|
|
355
|
+
export function infoPanel(label, rows, color = 'brand') {
|
|
356
|
+
const colorFn = c[color] || c.brand;
|
|
357
|
+
write(` ${colorFn(c.bold(label))}\n`);
|
|
358
|
+
write(` ${c.gray('─'.repeat(Math.min(40, (process.stdout.columns || 80) - 4)))}\n`);
|
|
359
|
+
for (const [key, val] of rows) {
|
|
360
|
+
write(` ${c.gray(key.padEnd(14))} ${val}\n`);
|
|
361
|
+
}
|
|
362
|
+
write('\n');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ── Table Display ──
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Render a simple table.
|
|
369
|
+
* @param {string[]} headers
|
|
370
|
+
* @param {string[][]} rows
|
|
371
|
+
*/
|
|
372
|
+
export function table(headers, rows) {
|
|
373
|
+
const widths = headers.map((h, i) => {
|
|
374
|
+
const maxRow = rows.reduce((max, row) => Math.max(max, stripAnsi(row[i] || '').length), 0);
|
|
375
|
+
return Math.max(stripAnsi(h).length, maxRow) + 2;
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Header
|
|
379
|
+
write(' ' + headers.map((h, i) => c.bold(c.brand(h.padEnd(widths[i])))).join('') + '\n');
|
|
380
|
+
write(' ' + widths.map(w => c.gray('─'.repeat(w))).join('') + '\n');
|
|
381
|
+
|
|
382
|
+
// Rows
|
|
383
|
+
for (const row of rows) {
|
|
384
|
+
write(' ' + row.map((cell, i) => {
|
|
385
|
+
const plain = stripAnsi(cell || '');
|
|
386
|
+
const pad = Math.max(0, widths[i] - plain.length);
|
|
387
|
+
return (cell || '') + ' '.repeat(pad);
|
|
388
|
+
}).join('') + '\n');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ── Elapsed Timer ──
|
|
393
|
+
|
|
394
|
+
export function formatElapsed(startMs) {
|
|
395
|
+
const s = Math.floor((Date.now() - startMs) / 1000);
|
|
396
|
+
if (s < 60) return `${s}s`;
|
|
397
|
+
return `${Math.floor(s / 60)}m${s % 60}s`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ── Format Cost ──
|
|
401
|
+
|
|
402
|
+
import { calculateCost, formatCostValue } from '../core/pricing.mjs';
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Format cost from token counts.
|
|
406
|
+
* Accepts either (inputTokens, outputTokens) for legacy calls,
|
|
407
|
+
* or a single usage object with optional per-model breakdown.
|
|
408
|
+
*/
|
|
409
|
+
export function formatCost(inputOrUsage, outputTokens) {
|
|
410
|
+
// New API: pass a usage object directly
|
|
411
|
+
if (typeof inputOrUsage === 'object' && inputOrUsage !== null) {
|
|
412
|
+
const { total } = calculateCost(inputOrUsage);
|
|
413
|
+
return formatCostValue(total);
|
|
414
|
+
}
|
|
415
|
+
// Legacy API: flat input/output token counts, default pricing
|
|
416
|
+
const { total } = calculateCost({
|
|
417
|
+
input_tokens: inputOrUsage || 0,
|
|
418
|
+
output_tokens: outputTokens || 0,
|
|
419
|
+
});
|
|
420
|
+
return formatCostValue(total);
|
|
421
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Orca CLI — ANSI Terminal UI.
|
|
4
|
+
* Zero React. Zero Ink. Zero flickering.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { startTerminalRepl } from './repl.mjs';
|
|
8
|
+
import {
|
|
9
|
+
runSessionsCommand,
|
|
10
|
+
runStatsCommand,
|
|
11
|
+
runHistoryCommand,
|
|
12
|
+
} from './analytics.mjs';
|
|
13
|
+
import { parseArgs } from '../config/cli-args.mjs';
|
|
14
|
+
|
|
15
|
+
// ── Subcommands ──
|
|
16
|
+
|
|
17
|
+
const subcommand = process.argv[2];
|
|
18
|
+
const subcommandArgs = process.argv.slice(3);
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
if (subcommand === 'dashboard') {
|
|
22
|
+
// Launch Orca Pulse Next.js dashboard
|
|
23
|
+
const { spawn } = await import('node:child_process');
|
|
24
|
+
const { fileURLToPath } = await import('node:url');
|
|
25
|
+
const path = await import('node:path');
|
|
26
|
+
const cliPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'pulse', 'cli.js');
|
|
27
|
+
const child = spawn(process.execPath, [cliPath, ...subcommandArgs], {
|
|
28
|
+
stdio: 'inherit',
|
|
29
|
+
env: process.env,
|
|
30
|
+
});
|
|
31
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (subcommand === 'sessions') {
|
|
36
|
+
await runSessionsCommand(subcommandArgs);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (subcommand === 'stats') {
|
|
41
|
+
await runStatsCommand(subcommandArgs);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (subcommand === 'history') {
|
|
46
|
+
await runHistoryCommand(subcommandArgs);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (subcommand === 'login') {
|
|
51
|
+
const { TarangAuth } = await import('../auth/tarang-auth.mjs');
|
|
52
|
+
const auth = new TarangAuth();
|
|
53
|
+
try {
|
|
54
|
+
await auth.login();
|
|
55
|
+
process.stderr.write('\x1b[32m✓ Login successful!\x1b[0m\n');
|
|
56
|
+
return;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
process.stderr.write(`\x1b[31m✗ Login failed: ${err.message}\x1b[0m\n`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (subcommand === 'logout') {
|
|
64
|
+
const { TarangAuth } = await import('../auth/tarang-auth.mjs');
|
|
65
|
+
const auth = new TarangAuth();
|
|
66
|
+
const success = auth.logout();
|
|
67
|
+
if (success) {
|
|
68
|
+
process.stderr.write('\x1b[32m✓ Signed out. Credentials cleared.\x1b[0m\n');
|
|
69
|
+
} else {
|
|
70
|
+
process.stderr.write('\x1b[33m! No credentials to clear.\x1b[0m\n');
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (subcommand === 'version' || subcommand === '--version' || subcommand === '-v') {
|
|
76
|
+
const { createRequire } = await import('node:module');
|
|
77
|
+
const require = createRequire(import.meta.url);
|
|
78
|
+
const { version } = require('../../package.json');
|
|
79
|
+
process.stdout.write(`orca v${version}\n`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
84
|
+
process.stderr.write(`
|
|
85
|
+
\x1b[1m\x1b[36morca\x1b[0m — Orchestration of Composable Agents
|
|
86
|
+
|
|
87
|
+
\x1b[1mUsage:\x1b[0m
|
|
88
|
+
orca Start interactive REPL
|
|
89
|
+
orca "instruction" Run a single instruction
|
|
90
|
+
orca --headless -p "x" Non-interactive: auto-approve, JSONL output
|
|
91
|
+
orca --resume Resume last conversation
|
|
92
|
+
orca dashboard Open Orca Pulse analytics dashboard
|
|
93
|
+
orca login Sign in via browser
|
|
94
|
+
orca logout Sign out and clear credentials
|
|
95
|
+
orca version Show version
|
|
96
|
+
|
|
97
|
+
\x1b[1mAnalytics:\x1b[0m
|
|
98
|
+
orca sessions List recent local sessions
|
|
99
|
+
orca stats Show aggregate local session stats
|
|
100
|
+
orca history Show recent prompt history
|
|
101
|
+
|
|
102
|
+
\x1b[1mREPL Commands:\x1b[0m
|
|
103
|
+
/help Show available commands
|
|
104
|
+
/stats Session metrics (tokens, cost, tools)
|
|
105
|
+
/cost Detailed cost breakdown by model
|
|
106
|
+
/history Conversation history
|
|
107
|
+
/clear Clear conversation history
|
|
108
|
+
/safety Show safety guardrail status
|
|
109
|
+
/revoke Revoke auto-approvals
|
|
110
|
+
/explore <query> Spawn read-only codebase explorer
|
|
111
|
+
/review <query> Spawn code review agent
|
|
112
|
+
/architect <query> Spawn architecture planning agent
|
|
113
|
+
/exit Exit the REPL
|
|
114
|
+
|
|
115
|
+
\x1b[1mKeyboard:\x1b[0m
|
|
116
|
+
Esc Cancel current execution
|
|
117
|
+
Space Pause / resume execution
|
|
118
|
+
Ctrl+C Exit
|
|
119
|
+
|
|
120
|
+
\x1b[1mEnvironment:\x1b[0m
|
|
121
|
+
TARANG_ENV Set backend (local, development, production)
|
|
122
|
+
ANTHROPIC_API_KEY Direct Anthropic API key
|
|
123
|
+
OPENROUTER_API_KEY OpenRouter API key
|
|
124
|
+
ORCA_CONFIG_DIR Override config directory (default: ~/.orca)
|
|
125
|
+
|
|
126
|
+
\x1b[2mDocs: https://devtarang.ai/docs\x1b[0m
|
|
127
|
+
`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Headless mode (benchmarks, automation) ──
|
|
132
|
+
const args = parseArgs(process.argv.slice(2));
|
|
133
|
+
if (args.prompt && (process.argv.includes('--headless') || !process.stdin.isTTY)) {
|
|
134
|
+
const { runHeadless } = await import('../core/headless.mjs');
|
|
135
|
+
await runHeadless({
|
|
136
|
+
instruction: args.prompt,
|
|
137
|
+
model: args.model,
|
|
138
|
+
timeout: args.maxTurns ? args.maxTurns * 60 : 300,
|
|
139
|
+
verbose: args.verbose,
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await startTerminalRepl();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main().catch(err => {
|
|
148
|
+
process.stderr.write(`\x1b[31mFatal: ${err.message}\x1b[0m\n`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|