@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,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kepler palette — semantic color tokens for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Every feature module imports from here, never from raw ANSI. Tokens resolve
|
|
5
|
+
* at call time so changing terminal capabilities (resize, `refresh()`) is
|
|
6
|
+
* picked up without restarting the process.
|
|
7
|
+
*
|
|
8
|
+
* import { paint } from './palette.mjs';
|
|
9
|
+
* process.stdout.write(paint.brand.primary('KEPLER'));
|
|
10
|
+
*
|
|
11
|
+
* Composition (multiple styles on one string):
|
|
12
|
+
*
|
|
13
|
+
* paint.bold(paint.brand.primary('KEPLER'))
|
|
14
|
+
*
|
|
15
|
+
* Tier behavior:
|
|
16
|
+
* truecolor → 24-bit RGB
|
|
17
|
+
* ansi256 → nearest 256-color index
|
|
18
|
+
* ansi16 → nearest basic color
|
|
19
|
+
* none → identity (input returned unchanged)
|
|
20
|
+
*
|
|
21
|
+
* Brand identity (Mission Control PRD-055):
|
|
22
|
+
* primary Deep Space Purple #7c3aed
|
|
23
|
+
* accent Stellar Magenta #ec4899
|
|
24
|
+
* data Neon Cyan #22d3ee
|
|
25
|
+
* success Aligned green #22c55e
|
|
26
|
+
* warn Soft amber #eab308
|
|
27
|
+
* danger Failure red #ef4444
|
|
28
|
+
* dim Sub-agent / hint #6b7280
|
|
29
|
+
* text Primary text #c9d1d9
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { term } from './term.mjs';
|
|
33
|
+
|
|
34
|
+
const ESC = '\x1b[';
|
|
35
|
+
const RESET = `${ESC}0m`;
|
|
36
|
+
|
|
37
|
+
// ── Brand tokens ─────────────────────────────────────────────────────────
|
|
38
|
+
// Each token is { rgb: [r,g,b], ansi256: n, ansi16: 'fgName' }.
|
|
39
|
+
// `ansi16` maps to a key in BASIC_FG below.
|
|
40
|
+
|
|
41
|
+
export const TOKENS = Object.freeze({
|
|
42
|
+
// Brand
|
|
43
|
+
'brand.primary': { rgb: [124, 58, 237], ansi256: 99, ansi16: 'magenta' }, // #7c3aed
|
|
44
|
+
'brand.accent': { rgb: [236, 72, 153], ansi256: 198, ansi16: 'magenta' }, // #ec4899
|
|
45
|
+
'brand.data': { rgb: [34, 211, 238], ansi256: 87, ansi16: 'cyan' }, // #22d3ee
|
|
46
|
+
|
|
47
|
+
// State
|
|
48
|
+
'state.success': { rgb: [34, 197, 94], ansi256: 41, ansi16: 'green' }, // #22c55e
|
|
49
|
+
'state.warn': { rgb: [234, 179, 8], ansi256: 220, ansi16: 'yellow' }, // #eab308
|
|
50
|
+
'state.danger': { rgb: [239, 68, 68], ansi256: 196, ansi16: 'red' }, // #ef4444
|
|
51
|
+
|
|
52
|
+
// Text
|
|
53
|
+
'text.primary': { rgb: [201, 209, 217], ansi256: 250, ansi16: 'white' }, // #c9d1d9
|
|
54
|
+
'text.dim': { rgb: [107, 114, 128], ansi256: 245, ansi16: 'gray' }, // #6b7280
|
|
55
|
+
'text.muted': { rgb: [156, 163, 175], ansi256: 247, ansi16: 'gray' }, // #9ca3af
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ── ANSI 16-color foreground codes ───────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
const BASIC_FG = {
|
|
61
|
+
black: 30,
|
|
62
|
+
red: 31,
|
|
63
|
+
green: 32,
|
|
64
|
+
yellow: 33,
|
|
65
|
+
blue: 34,
|
|
66
|
+
magenta: 35,
|
|
67
|
+
cyan: 36,
|
|
68
|
+
white: 37,
|
|
69
|
+
gray: 90,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// ── Style codes (work at every tier) ─────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
const STYLE_CODES = {
|
|
75
|
+
bold: [1, 22],
|
|
76
|
+
dim: [2, 22],
|
|
77
|
+
italic: [3, 23],
|
|
78
|
+
underline: [4, 24],
|
|
79
|
+
inverse: [7, 27],
|
|
80
|
+
strike: [9, 29],
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// ── Open / close sequence builders ───────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
function openForToken(token, capability) {
|
|
86
|
+
const def = TOKENS[token];
|
|
87
|
+
if (!def) return '';
|
|
88
|
+
|
|
89
|
+
if (capability === 'truecolor') {
|
|
90
|
+
const [r, g, b] = def.rgb;
|
|
91
|
+
return `${ESC}38;2;${r};${g};${b}m`;
|
|
92
|
+
}
|
|
93
|
+
if (capability === 'ansi256') {
|
|
94
|
+
return `${ESC}38;5;${def.ansi256}m`;
|
|
95
|
+
}
|
|
96
|
+
if (capability === 'ansi16') {
|
|
97
|
+
return `${ESC}${BASIC_FG[def.ansi16] || BASIC_FG.white}m`;
|
|
98
|
+
}
|
|
99
|
+
return '';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function wrap(open) {
|
|
103
|
+
if (!open) return (input) => String(input ?? '');
|
|
104
|
+
// Re-open after every embedded reset so nested styles compose.
|
|
105
|
+
// Cheap and predictable; most tool output is short enough that the cost
|
|
106
|
+
// is negligible compared to writing to the TTY.
|
|
107
|
+
return (input) => {
|
|
108
|
+
const text = String(input ?? '');
|
|
109
|
+
if (!text) return '';
|
|
110
|
+
if (!text.includes(RESET)) return `${open}${text}${RESET}`;
|
|
111
|
+
return `${open}${text.split(RESET).join(`${RESET}${open}`)}${RESET}`;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function styleWrap(openCode, closeCode) {
|
|
116
|
+
return (input) => {
|
|
117
|
+
const text = String(input ?? '');
|
|
118
|
+
if (!text) return '';
|
|
119
|
+
// No color tier check here — styles like bold/dim work even in ansi16.
|
|
120
|
+
if (!term().color) return text;
|
|
121
|
+
const open = `${ESC}${openCode}m`;
|
|
122
|
+
const close = `${ESC}${closeCode}m`;
|
|
123
|
+
if (!text.includes(close)) return `${open}${text}${close}`;
|
|
124
|
+
return `${open}${text.split(close).join(`${close}${open}`)}${close}`;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Build a structured `paint` object once per token ─────────────────────
|
|
129
|
+
|
|
130
|
+
function buildPaint() {
|
|
131
|
+
const paint = {};
|
|
132
|
+
|
|
133
|
+
// Brand / state / text colorizers, nested by namespace.
|
|
134
|
+
for (const token of Object.keys(TOKENS)) {
|
|
135
|
+
const [ns, name] = token.split('.');
|
|
136
|
+
if (!paint[ns]) paint[ns] = {};
|
|
137
|
+
paint[ns][name] = (input) => {
|
|
138
|
+
const t = term();
|
|
139
|
+
if (!t.color) return String(input ?? '');
|
|
140
|
+
return wrap(openForToken(token, t.colorLevel))(input);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Style colorizers (callable directly).
|
|
145
|
+
for (const [style, [open, close]] of Object.entries(STYLE_CODES)) {
|
|
146
|
+
paint[style] = styleWrap(open, close);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Compose helper — apply multiple styles left-to-right.
|
|
150
|
+
paint.compose = (...fns) => (input) =>
|
|
151
|
+
fns.reduce((acc, fn) => (typeof fn === 'function' ? fn(acc) : acc), input);
|
|
152
|
+
|
|
153
|
+
// Raw token accessor for callers that need to inject codes around their
|
|
154
|
+
// own text (e.g. status bar repaint loops that re-style a buffer).
|
|
155
|
+
paint.token = (key) => {
|
|
156
|
+
const t = term();
|
|
157
|
+
if (!t.color) return { open: '', close: '' };
|
|
158
|
+
return { open: openForToken(key, t.colorLevel), close: RESET };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return paint;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const paint = buildPaint();
|
|
165
|
+
|
|
166
|
+
// ── Plain-text helper (always strips colors) ─────────────────────────────
|
|
167
|
+
|
|
168
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
169
|
+
|
|
170
|
+
export function strip(input) {
|
|
171
|
+
return String(input ?? '').replace(ANSI_RE, '');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Visible length of a string (ignoring ANSI codes).
|
|
176
|
+
* Surrogate pairs (emoji) count as 1 visual cell for layout purposes — this
|
|
177
|
+
* is consistent with most terminals' rendering of single-codepoint emoji.
|
|
178
|
+
*/
|
|
179
|
+
export function width(input) {
|
|
180
|
+
const plain = strip(input);
|
|
181
|
+
// Strip variation selectors so "🛰️" measures as one cell.
|
|
182
|
+
return [...plain.replace(/︎|️/g, '')].length;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Backwards compatibility re-exports ───────────────────────────────────
|
|
186
|
+
// `ansi.mjs` and other legacy modules import these names. New code should
|
|
187
|
+
// prefer `paint.brand.primary(...)` etc.
|
|
188
|
+
|
|
189
|
+
export const RESET_CODE = RESET;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash Commands — canonical catalog + input normalizer.
|
|
3
|
+
*
|
|
4
|
+
* This module is the ONE source of truth for the REPL's slash commands.
|
|
5
|
+
* Consumers:
|
|
6
|
+
* - src/terminal/repl.mjs imports COMMANDS, HELP_GROUPS, etc. for its
|
|
7
|
+
* runtime dispatch and slash-hint suggestions.
|
|
8
|
+
* - src/index.mjs iterates COMMANDS for `bahulam-code --help` output.
|
|
9
|
+
*
|
|
10
|
+
* The runtime *handlers* live in repl.mjs (they need session/ctx access
|
|
11
|
+
* that only the REPL has). This module is data-only + pure normalization.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// name -> one-line description (bare string).
|
|
15
|
+
export const COMMANDS = {
|
|
16
|
+
'/help': 'Show commands',
|
|
17
|
+
'/login': 'Sign in via browser',
|
|
18
|
+
'/whoami': 'Show logged-in user',
|
|
19
|
+
'/status': 'Session status & system info',
|
|
20
|
+
'/plan': 'Show plan/tasks',
|
|
21
|
+
'/tasks': 'Show or update project tasks',
|
|
22
|
+
'/stats': 'Progress bars & metrics',
|
|
23
|
+
'/new': 'Start a new session',
|
|
24
|
+
'/clear': 'Clear conversation',
|
|
25
|
+
'/git': 'Git status',
|
|
26
|
+
'/diff': 'Git diff',
|
|
27
|
+
'/cost': 'Show session cost',
|
|
28
|
+
'/model': 'Show or set session model overrides',
|
|
29
|
+
'/attach': 'Attach image path or clipboard image to next prompt',
|
|
30
|
+
'/attachments': 'List or clear pending image attachments',
|
|
31
|
+
'/history': 'Show conversation',
|
|
32
|
+
'/settings': 'Show policy/settings',
|
|
33
|
+
'/last': 'Expand last tool output',
|
|
34
|
+
'/expand': 'Expand tool output by index (or "all")',
|
|
35
|
+
'/fold': 'Hide previously expanded tool output',
|
|
36
|
+
'/checkpoint': 'List recent file checkpoints',
|
|
37
|
+
'/undo': 'Restore the last file checkpoint',
|
|
38
|
+
'/preflight': 'Re-run the onboarding diagnostic',
|
|
39
|
+
'/report': 'Save the mission report as markdown',
|
|
40
|
+
'/why': 'Print the agent reasoning for the last decision',
|
|
41
|
+
'/map': 'Show the registered project tree',
|
|
42
|
+
'/budget': 'Set / clear a hard session cost cap',
|
|
43
|
+
'/quiet': 'Verbosity: hide sub-agent inner tools',
|
|
44
|
+
'/verbose': 'Verbosity: show sub-agent inner tools',
|
|
45
|
+
'/surgical': 'Verbosity: show everything (reasoning, expanded tools)',
|
|
46
|
+
'/compact': 'Compact conversation context',
|
|
47
|
+
'/agents': 'List available agents',
|
|
48
|
+
'/explore': 'Code explorer agent',
|
|
49
|
+
'/review': 'Code review agent',
|
|
50
|
+
'/architect': 'Feature architect agent',
|
|
51
|
+
'/safety': 'Show safety guardrail status',
|
|
52
|
+
'/revoke': 'Revoke auto-approvals',
|
|
53
|
+
'/resume': 'Resume a previous session',
|
|
54
|
+
'/sessions': 'List resumable sessions',
|
|
55
|
+
'/logout': 'Sign out and clear credentials',
|
|
56
|
+
'/exit': 'Exit CLI',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Grouped view for the `/help` interactive display. Each entry is
|
|
60
|
+
// [command-with-args, description] so subcommands appear inline.
|
|
61
|
+
export const HELP_GROUPS = [
|
|
62
|
+
{
|
|
63
|
+
key: 'plan',
|
|
64
|
+
title: 'Plan',
|
|
65
|
+
summary: 'plan and project tasks',
|
|
66
|
+
commands: [
|
|
67
|
+
['/plan', 'Plan and task overview'],
|
|
68
|
+
['/plan status', 'Plan owner and task files'],
|
|
69
|
+
['/plan edit', 'Show editable task/plan paths'],
|
|
70
|
+
['/tasks', 'List project tasks'],
|
|
71
|
+
['/tasks add <text>', 'Add backlog task'],
|
|
72
|
+
['/tasks active|blocked|done <text>', 'Append to a task list'],
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: 'status',
|
|
77
|
+
title: 'Status',
|
|
78
|
+
summary: 'session, usage, budget',
|
|
79
|
+
commands: [
|
|
80
|
+
['/status', 'Session snapshot'],
|
|
81
|
+
['/status context', 'Loaded .bahulam context'],
|
|
82
|
+
['/status metrics', 'Progress bars and runtime metrics'],
|
|
83
|
+
['/status cost', 'Credits and message window'],
|
|
84
|
+
['/model [role] [model]', 'Show or set session model override'],
|
|
85
|
+
['/attach <image>', 'Attach image to the next prompt'],
|
|
86
|
+
['/attach clipboard', 'Attach image currently copied to macOS/Windows clipboard'],
|
|
87
|
+
['/attachments', 'List pending image attachments'],
|
|
88
|
+
['/attachments clear', 'Clear pending image attachments'],
|
|
89
|
+
['/status budget <amount|clear>', 'Set or clear session budget'],
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: 'history',
|
|
94
|
+
title: 'History',
|
|
95
|
+
summary: 'transcript, reports, undo',
|
|
96
|
+
commands: [
|
|
97
|
+
['/history', 'Recent transcript'],
|
|
98
|
+
['/history approvals', 'Approval log'],
|
|
99
|
+
['/history last', 'Expand last tool output'],
|
|
100
|
+
['/history expand [n|all]', 'Expand tool output'],
|
|
101
|
+
['/history checkpoint', 'List checkpoints'],
|
|
102
|
+
['/history undo', 'Restore latest checkpoint'],
|
|
103
|
+
['/history report', 'Save mission report'],
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: 'settings',
|
|
108
|
+
title: 'Settings',
|
|
109
|
+
summary: 'auth, policy, verbosity',
|
|
110
|
+
commands: [
|
|
111
|
+
['/settings policy', 'Effective project policy'],
|
|
112
|
+
['/settings login', 'Sign in'],
|
|
113
|
+
['/settings logout', 'Sign out'],
|
|
114
|
+
['/settings whoami', 'Current user'],
|
|
115
|
+
['/settings quiet|verbose|surgical', 'Verbosity'],
|
|
116
|
+
['/settings revoke', 'Revoke auto-approvals'],
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: 'worktree',
|
|
121
|
+
title: 'Worktree',
|
|
122
|
+
summary: 'git and files',
|
|
123
|
+
commands: [
|
|
124
|
+
['/git', 'Git status'],
|
|
125
|
+
['/diff', 'Git diff'],
|
|
126
|
+
['/map', 'Registered project tree'],
|
|
127
|
+
['/preflight', 'Onboarding diagnostic'],
|
|
128
|
+
['/safety', 'Safety guardrail status'],
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
key: 'agents',
|
|
133
|
+
title: 'Agents',
|
|
134
|
+
summary: 'specialist modes',
|
|
135
|
+
commands: [
|
|
136
|
+
['/agents', 'List built-in and local agents'],
|
|
137
|
+
['/agents create <name>', 'Create .bahulam/agents/<name>.yaml'],
|
|
138
|
+
['/agents edit <name>', 'Open local agent YAML'],
|
|
139
|
+
['/agents sync [name]', 'Sync all or one local agent to cloud'],
|
|
140
|
+
['/explore <instruction>', 'Explore code'],
|
|
141
|
+
['/review <instruction>', 'Review code'],
|
|
142
|
+
['/architect <instruction>', 'Design an approach'],
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: 'session',
|
|
147
|
+
title: 'Session',
|
|
148
|
+
summary: 'resume and clear',
|
|
149
|
+
commands: [
|
|
150
|
+
['/sessions', 'List resumable sessions'],
|
|
151
|
+
['/resume [id]', 'Resume a session'],
|
|
152
|
+
['/compact', 'Compact conversation context'],
|
|
153
|
+
['/new', 'Start a fresh session'],
|
|
154
|
+
['/clear', 'Clear conversation'],
|
|
155
|
+
['/exit', 'Exit CLI'],
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
export const HELP_GROUP_ALIASES = new Map(
|
|
161
|
+
HELP_GROUPS.flatMap(group => [[group.key, group], [group.title.toLowerCase(), group]])
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Old flat commands that the user might still type. Map them to the new
|
|
165
|
+
// namespaced form so completion + help nudge them toward the current UX.
|
|
166
|
+
export const LEGACY_COMMAND_HINTS = {
|
|
167
|
+
'/stats': '/status metrics',
|
|
168
|
+
'/cost': '/status cost',
|
|
169
|
+
'/budget': '/status budget',
|
|
170
|
+
'/last': '/history last',
|
|
171
|
+
'/expand': '/history expand',
|
|
172
|
+
'/fold': '/history fold',
|
|
173
|
+
'/undo': '/history undo',
|
|
174
|
+
'/checkpoint': '/history checkpoint',
|
|
175
|
+
'/report': '/history report',
|
|
176
|
+
'/login': '/settings login',
|
|
177
|
+
'/logout': '/settings logout',
|
|
178
|
+
'/whoami': '/settings whoami',
|
|
179
|
+
'/quiet': '/settings quiet',
|
|
180
|
+
'/verbose': '/settings verbose',
|
|
181
|
+
'/surgical': '/settings surgical',
|
|
182
|
+
'/revoke': '/settings revoke',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Namespaced subcommands. `/status metrics` -> `/stats`, etc.
|
|
186
|
+
export const NAMESPACED_COMMANDS = {
|
|
187
|
+
'/status': {
|
|
188
|
+
metrics: '/stats',
|
|
189
|
+
stats: '/stats',
|
|
190
|
+
cost: '/cost',
|
|
191
|
+
credits: '/cost',
|
|
192
|
+
budget: '/budget',
|
|
193
|
+
},
|
|
194
|
+
'/history': {
|
|
195
|
+
last: '/last',
|
|
196
|
+
expand: '/expand',
|
|
197
|
+
fold: '/fold',
|
|
198
|
+
undo: '/undo',
|
|
199
|
+
checkpoint: '/checkpoint',
|
|
200
|
+
checkpoints: '/checkpoint',
|
|
201
|
+
report: '/report',
|
|
202
|
+
},
|
|
203
|
+
'/settings': {
|
|
204
|
+
login: '/login',
|
|
205
|
+
logout: '/logout',
|
|
206
|
+
whoami: '/whoami',
|
|
207
|
+
quiet: '/quiet',
|
|
208
|
+
verbose: '/verbose',
|
|
209
|
+
surgical: '/surgical',
|
|
210
|
+
revoke: '/revoke',
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Normalize a raw slash-command input like "/status metrics arg" into
|
|
216
|
+
* { cmd: '/stats', rest: 'arg', rawCmd, aliasTarget }, resolving legacy
|
|
217
|
+
* aliases and namespaced subcommands to their canonical form.
|
|
218
|
+
*
|
|
219
|
+
* - `rawCmd` the lower-cased first token as the user typed it
|
|
220
|
+
* - `cmd` the canonical command (namespaced hit if any, else rawCmd)
|
|
221
|
+
* - `rest` the remaining args joined with spaces
|
|
222
|
+
* - `aliasTarget` for legacy flat commands, the new namespaced form to
|
|
223
|
+
* suggest to the user (null when not aliased)
|
|
224
|
+
*/
|
|
225
|
+
export function normalizeCommandInput(input) {
|
|
226
|
+
const parts = String(input || '').trim().split(/\s+/).filter(Boolean);
|
|
227
|
+
const rawCmd = (parts[0] || '').toLowerCase();
|
|
228
|
+
const restParts = parts.slice(1);
|
|
229
|
+
const sub = (restParts[0] || '').toLowerCase();
|
|
230
|
+
const namespaced = NAMESPACED_COMMANDS[rawCmd]?.[sub];
|
|
231
|
+
if (namespaced) {
|
|
232
|
+
return {
|
|
233
|
+
cmd: namespaced,
|
|
234
|
+
rest: restParts.slice(1).join(' '),
|
|
235
|
+
rawCmd,
|
|
236
|
+
aliasTarget: null,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
cmd: rawCmd,
|
|
241
|
+
rest: restParts.join(' '),
|
|
242
|
+
rawCmd,
|
|
243
|
+
aliasTarget: LEGACY_COMMAND_HINTS[rawCmd] || null,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single shared spinner — Mission Control (PRD-055 §4.4).
|
|
3
|
+
*
|
|
4
|
+
* One spinner instance per process. The repl and status bar consume frames
|
|
5
|
+
* from the same source so the eye never sees two animations out of phase.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
*
|
|
9
|
+
* const stop = startSpinner('Reading auth.py');
|
|
10
|
+
* await doWork();
|
|
11
|
+
* stop(); // clears the line and stops the timer
|
|
12
|
+
*
|
|
13
|
+
* Or, for a managed line that already exists (e.g. the status bar):
|
|
14
|
+
*
|
|
15
|
+
* const tick = spinnerFrame(); // current frame, advances on next call
|
|
16
|
+
*
|
|
17
|
+
* Behavior:
|
|
18
|
+
* - 120ms per frame.
|
|
19
|
+
* - Suppressed entirely when stdout is not a TTY or when KEPLER_PLAIN=1.
|
|
20
|
+
* The `start`/`stop` API is still safe to call (no-op).
|
|
21
|
+
* - Color follows the current orbit (orchestrated by the caller via the
|
|
22
|
+
* palette token). Default: brand.primary.
|
|
23
|
+
* - ASCII fallback (when no Unicode): 'plain dot rotation'.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { paint } from './palette.mjs';
|
|
27
|
+
import { term } from './term.mjs';
|
|
28
|
+
|
|
29
|
+
// 8-step rotation. The PRD spec calls for ◯ → ◔ → ◑ → ◕ → ● → ◕ → ◑ → ◔
|
|
30
|
+
// which yields a perceptual "breathing" cycle rather than a left-right spin.
|
|
31
|
+
const FRAMES_UTF = ['◯', '◔', '◑', '◕', '●', '◕', '◑', '◔'];
|
|
32
|
+
const FRAMES_ASCII = ['.', 'o', 'O', '@', 'O', 'o', '.', ' '];
|
|
33
|
+
|
|
34
|
+
const INTERVAL_MS = 120;
|
|
35
|
+
|
|
36
|
+
let _frame = 0;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Current spinner glyph. Advances the cursor each call.
|
|
40
|
+
* Honors capability detection automatically.
|
|
41
|
+
*/
|
|
42
|
+
export function spinnerFrame(painter = paint.brand.primary) {
|
|
43
|
+
const frames = term().unicode ? FRAMES_UTF : FRAMES_ASCII;
|
|
44
|
+
const ch = frames[_frame % frames.length];
|
|
45
|
+
_frame = (_frame + 1) % frames.length;
|
|
46
|
+
return painter ? painter(ch) : ch;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Reset to frame 0 — useful at the start of a new turn so consecutive
|
|
51
|
+
* tool calls do not inherit each other's phase.
|
|
52
|
+
*/
|
|
53
|
+
export function resetSpinner() {
|
|
54
|
+
_frame = 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Start an inline spinner attached to `text`. Returns a stop function.
|
|
59
|
+
*
|
|
60
|
+
* The line is re-rendered in place using carriage return + erase, so the
|
|
61
|
+
* caller does not need to manage cursor state. If the terminal cannot
|
|
62
|
+
* render in place (non-TTY, dumb terminal, plain mode), the spinner becomes
|
|
63
|
+
* a single static line `"… text"` written once.
|
|
64
|
+
*/
|
|
65
|
+
export function startSpinner(text, { stream = process.stderr, painter, color = 'brand.primary' } = {}) {
|
|
66
|
+
const t = term();
|
|
67
|
+
if (!t.isTTY || t.plain || !t.color) {
|
|
68
|
+
try { stream.write(`… ${text}\n`); } catch {}
|
|
69
|
+
return () => {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const paintFn = painter || tokenPainter(color);
|
|
73
|
+
let stopped = false;
|
|
74
|
+
|
|
75
|
+
const render = () => {
|
|
76
|
+
if (stopped) return;
|
|
77
|
+
const glyph = spinnerFrame(paintFn);
|
|
78
|
+
try {
|
|
79
|
+
stream.write(`\r\x1b[2K${glyph} ${paint.text.dim(text)}`);
|
|
80
|
+
} catch {
|
|
81
|
+
// Stream closed mid-spin — stop quietly.
|
|
82
|
+
stop();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
render();
|
|
87
|
+
const handle = setInterval(render, INTERVAL_MS);
|
|
88
|
+
|
|
89
|
+
function stop() {
|
|
90
|
+
if (stopped) return;
|
|
91
|
+
stopped = true;
|
|
92
|
+
clearInterval(handle);
|
|
93
|
+
try {
|
|
94
|
+
stream.write('\r\x1b[2K');
|
|
95
|
+
} catch {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return stop;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Look up a painter function from a dotted token name (e.g. 'brand.accent').
|
|
103
|
+
* Falls back to the identity painter when the token does not exist.
|
|
104
|
+
*/
|
|
105
|
+
function tokenPainter(tokenPath) {
|
|
106
|
+
const [ns, name] = String(tokenPath || '').split('.');
|
|
107
|
+
const group = paint[ns];
|
|
108
|
+
if (group && typeof group[name] === 'function') return group[name];
|
|
109
|
+
return (s) => String(s ?? '');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Interval used by the shared spinner. Exposed for the status bar to
|
|
114
|
+
* synchronize its own re-paints with the spinner phase.
|
|
115
|
+
*/
|
|
116
|
+
export const SPINNER_INTERVAL_MS = INTERVAL_MS;
|