@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,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed input dock — Bahulam Code identity + meta + tips.
|
|
3
|
+
*
|
|
4
|
+
* Reserves rows at the bottom of the terminal so agent/tool output scrolls
|
|
5
|
+
* above the prompt. The input area GROWS DYNAMICALLY as the user types or
|
|
6
|
+
* pastes multi-line content: starts at 1 row, expands up to `MAX_INPUT_ROWS`
|
|
7
|
+
* (default 6) to fit the wrapped buffer, shrinks back when the buffer is
|
|
8
|
+
* cleared. On resize, the scroll region moves so content above reflows
|
|
9
|
+
* smoothly.
|
|
10
|
+
*
|
|
11
|
+
* When the wrapped buffer still exceeds the max, only the LAST max rows
|
|
12
|
+
* render with a leading '…' marker (explicit truncation).
|
|
13
|
+
*
|
|
14
|
+
* Multi-line paste — including bracketed paste bursts — renders as one
|
|
15
|
+
* visible block in the dock and submits as a single message. The dock
|
|
16
|
+
* itself doesn't parse newlines; wrapToLines treats them as hard breaks.
|
|
17
|
+
*
|
|
18
|
+
* Layout (fixed 8 rows + N input rows):
|
|
19
|
+
* ── ▎Bahulam Code ══════════════ 12.3k tok · 2m 41s ═══ ← top rule (colored)
|
|
20
|
+
* ← spacer
|
|
21
|
+
* + add instruction › foo bar ← input row 1
|
|
22
|
+
* baz quux ← input row 2 (added as needed)
|
|
23
|
+
* ... ← up to MAX_INPUT_ROWS
|
|
24
|
+
* ← spacer
|
|
25
|
+
* ══════════════════════════════════════════════════════ ← bottom rule (colored)
|
|
26
|
+
* deepseek-chat-v3 · Tarang-Orca ⎇ main · turn 4 ← meta row
|
|
27
|
+
* [Enter] send · [/] commands … ← tips row
|
|
28
|
+
* (one blank row at very bottom for cursor safety)
|
|
29
|
+
*
|
|
30
|
+
* Config:
|
|
31
|
+
* BAHULAM_INPUT_ROWS_MAX / KEPLER_INPUT_ROWS_MAX 1..12 hard cap on input row growth (default 6)
|
|
32
|
+
* BAHULAM_FIXED_INPUT=0 / KEPLER_FIXED_INPUT=0 disable dock entirely (fallback readline)
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { paint, width as visibleWidth } from './palette.mjs';
|
|
36
|
+
import { term, onResize } from './term.mjs';
|
|
37
|
+
import { wrapToLines, tailWithEllipsis, cursorPositionInLines } from './text-layout.mjs';
|
|
38
|
+
|
|
39
|
+
const ESC = '\x1b[';
|
|
40
|
+
const OUT = process.stderr;
|
|
41
|
+
|
|
42
|
+
const BRAND_LABEL = 'Bahulam Code';
|
|
43
|
+
const LABEL_ACCENT = '▎'; // left-edge accent bar
|
|
44
|
+
|
|
45
|
+
// Horizontal margin: text starts INPUT_INDENT cols from the left edge and
|
|
46
|
+
// leaves INPUT_RIGHT_PAD cols of clear space on the right so wrapped lines
|
|
47
|
+
// don't kiss the terminal edge.
|
|
48
|
+
const INPUT_INDENT = 5;
|
|
49
|
+
const INPUT_RIGHT_PAD = 2;
|
|
50
|
+
const META_INDENT = 4;
|
|
51
|
+
|
|
52
|
+
const DEFAULT_MAX_INPUT_ROWS = 6;
|
|
53
|
+
const MIN_INPUT_ROWS = 1;
|
|
54
|
+
const MAX_INPUT_ROWS_CAP = 12;
|
|
55
|
+
|
|
56
|
+
// Fixed rows around the input area:
|
|
57
|
+
// 1 top rule + 1 spacer above + input(N) + 1 spacer below + 1 bottom rule
|
|
58
|
+
// + 1 meta row + 1 tips row + 1 safety row
|
|
59
|
+
// = 7 + N
|
|
60
|
+
const FIXED_ROWS = 7;
|
|
61
|
+
|
|
62
|
+
let mounted = false;
|
|
63
|
+
let inputRowsMax = DEFAULT_MAX_INPUT_ROWS;
|
|
64
|
+
let inputRows = MIN_INPUT_ROWS;
|
|
65
|
+
let reservedRows = FIXED_ROWS + MIN_INPUT_ROWS;
|
|
66
|
+
let unsubResize = null;
|
|
67
|
+
let lastFrame = { context: '', meta: '', tips: '' };
|
|
68
|
+
let resetting = false;
|
|
69
|
+
|
|
70
|
+
function write(s) { try { OUT.write(s); } catch {} }
|
|
71
|
+
function setScrollRegion(top, bottom) { write(`${ESC}${top};${bottom}r`); }
|
|
72
|
+
function clearScrollRegion() { write(`${ESC}r`); }
|
|
73
|
+
function saveCursor() { write(`${ESC}s`); }
|
|
74
|
+
function restoreCursor() { write(`${ESC}u`); }
|
|
75
|
+
function moveTo(row, col) { write(`${ESC}${row};${col}H`); }
|
|
76
|
+
function clearLine() { write(`${ESC}2K`); }
|
|
77
|
+
|
|
78
|
+
function rows() {
|
|
79
|
+
return Math.max(10, term().rows || 24);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function cols() {
|
|
83
|
+
return Math.max(40, term().columns || 80);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function contentBottomRow() {
|
|
87
|
+
return Math.max(1, rows() - reservedRows);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Row map (top → bottom of the reserved region).
|
|
91
|
+
function topRuleRow() { return contentBottomRow() + 1; }
|
|
92
|
+
function spacerAboveRow() { return topRuleRow() + 1; }
|
|
93
|
+
function inputRowStart() { return spacerAboveRow() + 1; }
|
|
94
|
+
function inputRowEnd() { return inputRowStart() + inputRows - 1; }
|
|
95
|
+
function spacerBelowRow() { return inputRowEnd() + 1; }
|
|
96
|
+
function bottomRuleRow() { return spacerBelowRow() + 1; }
|
|
97
|
+
function metaRow() { return bottomRuleRow() + 1; }
|
|
98
|
+
function tipsRow() { return metaRow() + 1; }
|
|
99
|
+
|
|
100
|
+
function inputTextBudget() {
|
|
101
|
+
return Math.max(8, cols() - INPUT_INDENT - INPUT_RIGHT_PAD - 1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveMaxInputRows(requested) {
|
|
105
|
+
const raw = requested != null
|
|
106
|
+
? requested
|
|
107
|
+
: (process.env.BAHULAM_INPUT_ROWS_MAX || process.env.KEPLER_INPUT_ROWS_MAX);
|
|
108
|
+
const n = Number.parseInt(String(raw), 10);
|
|
109
|
+
if (!Number.isFinite(n)) return DEFAULT_MAX_INPUT_ROWS;
|
|
110
|
+
return Math.max(MIN_INPUT_ROWS, Math.min(MAX_INPUT_ROWS_CAP, n));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// How many input rows does this (prefix + value) buffer need? Wrapped line
|
|
114
|
+
// count clamped to [1, inputRowsMax]. Beyond the cap, tail-with-ellipsis
|
|
115
|
+
// takes over inside drawInputLines so at most inputRowsMax rows render.
|
|
116
|
+
function computeInputRowsForBuffer(prefix, value) {
|
|
117
|
+
const budget = inputTextBudget();
|
|
118
|
+
const wrapped = wrapToLines(`${prefix || ''}${value || ''}`, budget);
|
|
119
|
+
const wanted = Math.max(MIN_INPUT_ROWS, wrapped.length);
|
|
120
|
+
return Math.min(inputRowsMax, wanted);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Resize the input area to a new row count. Moves the scroll region so
|
|
124
|
+
// content above shifts accordingly; the dock frame is redrawn at the
|
|
125
|
+
// new position. No-op if the count didn't change.
|
|
126
|
+
function setInputRowsTo(nextRows) {
|
|
127
|
+
const clamped = Math.max(MIN_INPUT_ROWS, Math.min(inputRowsMax, Math.floor(nextRows)));
|
|
128
|
+
if (clamped === inputRows) return false;
|
|
129
|
+
inputRows = clamped;
|
|
130
|
+
reservedRows = FIXED_ROWS + inputRows;
|
|
131
|
+
if (mounted) {
|
|
132
|
+
const bottom = contentBottomRow();
|
|
133
|
+
setScrollRegion(1, bottom);
|
|
134
|
+
renderFrame(lastFrame);
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function padLine(text) {
|
|
140
|
+
const value = String(text || '');
|
|
141
|
+
const pad = Math.max(0, cols() - visibleWidth(value));
|
|
142
|
+
return value + ' '.repeat(pad);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function fitText(text, maxWidth) {
|
|
146
|
+
const plain = String(text || '').replace(/\s+/g, ' ').trim();
|
|
147
|
+
if (visibleWidth(plain) <= maxWidth) return plain;
|
|
148
|
+
if (maxWidth <= 1) return '';
|
|
149
|
+
return plain.slice(0, Math.max(0, maxWidth - 1)) + '…';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function ruleChars(count) {
|
|
153
|
+
return '═'.repeat(Math.max(0, count));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Top rule: `── ▎Bahulam Code ═════════════ [context] ═══`
|
|
157
|
+
// - Leading two rule chars for a clean edge
|
|
158
|
+
// - Colored accent bar + brand label on the left
|
|
159
|
+
// - Optional right-aligned context strip (session tokens/elapsed) with rule
|
|
160
|
+
// fill between label and context
|
|
161
|
+
function topRuleLine(context = '') {
|
|
162
|
+
const w = Math.max(0, cols() - 1);
|
|
163
|
+
const brand = paint.brand.primary;
|
|
164
|
+
const bold = paint.bold;
|
|
165
|
+
|
|
166
|
+
const leadRule = brand(ruleChars(2));
|
|
167
|
+
const accent = brand(LABEL_ACCENT);
|
|
168
|
+
const label = bold(brand(BRAND_LABEL));
|
|
169
|
+
const leftBlock = `${leadRule} ${accent}${label}`;
|
|
170
|
+
const leftWidth = visibleWidth(leftBlock);
|
|
171
|
+
|
|
172
|
+
const ctx = fitText(context, Math.max(0, Math.floor(w / 2)));
|
|
173
|
+
const ctxWidth = ctx ? visibleWidth(ctx) : 0;
|
|
174
|
+
|
|
175
|
+
// Rule fill in the middle. Reserve 1 space around ctx when present.
|
|
176
|
+
const gap = 1;
|
|
177
|
+
const rightPadRule = 2;
|
|
178
|
+
const middleWidth = Math.max(1, w - leftWidth - gap - ctxWidth - (ctx ? gap + rightPadRule : 0));
|
|
179
|
+
const middleRule = brand(ruleChars(middleWidth));
|
|
180
|
+
|
|
181
|
+
if (!ctx) {
|
|
182
|
+
return `${leftBlock} ${middleRule}`;
|
|
183
|
+
}
|
|
184
|
+
const tailRule = brand(ruleChars(rightPadRule));
|
|
185
|
+
return `${leftBlock} ${middleRule} ${paint.text.dim(ctx)} ${tailRule}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function bottomRuleLine() {
|
|
189
|
+
return paint.brand.primary(ruleChars(Math.max(0, cols() - 1)));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function applyLayout() {
|
|
193
|
+
if (!mounted) return;
|
|
194
|
+
const bottom = contentBottomRow();
|
|
195
|
+
setScrollRegion(1, bottom);
|
|
196
|
+
renderFrame(lastFrame);
|
|
197
|
+
moveTo(bottom, 1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function renderFrame(frame = {}) {
|
|
201
|
+
if (!mounted) return;
|
|
202
|
+
lastFrame = { ...lastFrame, ...frame };
|
|
203
|
+
saveCursor();
|
|
204
|
+
|
|
205
|
+
moveTo(topRuleRow(), 1);
|
|
206
|
+
clearLine();
|
|
207
|
+
write(padLine(topRuleLine(lastFrame.context)));
|
|
208
|
+
|
|
209
|
+
// Spacer above input.
|
|
210
|
+
moveTo(spacerAboveRow(), 1);
|
|
211
|
+
clearLine();
|
|
212
|
+
|
|
213
|
+
// (input rows are written by drawInputLines / clearInputRows)
|
|
214
|
+
|
|
215
|
+
// Spacer below input.
|
|
216
|
+
moveTo(spacerBelowRow(), 1);
|
|
217
|
+
clearLine();
|
|
218
|
+
|
|
219
|
+
moveTo(bottomRuleRow(), 1);
|
|
220
|
+
clearLine();
|
|
221
|
+
write(padLine(bottomRuleLine()));
|
|
222
|
+
|
|
223
|
+
// Meta row: model · cwd ⎇ branch · turn N · tokens · elapsed
|
|
224
|
+
moveTo(metaRow(), 1);
|
|
225
|
+
clearLine();
|
|
226
|
+
const metaIndent = ' '.repeat(META_INDENT);
|
|
227
|
+
const metaBudget = Math.max(0, cols() - META_INDENT - 1);
|
|
228
|
+
const metaText = lastFrame.meta ? paint.text.muted(fitText(lastFrame.meta, metaBudget)) : '';
|
|
229
|
+
write(padLine(`${metaIndent}${metaText}`));
|
|
230
|
+
|
|
231
|
+
// Tips row.
|
|
232
|
+
moveTo(tipsRow(), 1);
|
|
233
|
+
clearLine();
|
|
234
|
+
const tipsIndent = ' '.repeat(META_INDENT);
|
|
235
|
+
const tipsBudget = Math.max(0, cols() - META_INDENT - 1);
|
|
236
|
+
write(padLine(`${tipsIndent}${paint.text.dim(fitText(lastFrame.tips, tipsBudget))}`));
|
|
237
|
+
|
|
238
|
+
// Safety row.
|
|
239
|
+
moveTo(rows(), 1);
|
|
240
|
+
clearLine();
|
|
241
|
+
restoreCursor();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function clearInputRows() {
|
|
245
|
+
saveCursor();
|
|
246
|
+
for (let row = inputRowStart(); row <= inputRowEnd(); row++) {
|
|
247
|
+
moveTo(row, 1);
|
|
248
|
+
clearLine();
|
|
249
|
+
}
|
|
250
|
+
restoreCursor();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function clearDockArea({ restore = true } = {}) {
|
|
254
|
+
if (!mounted) return false;
|
|
255
|
+
if (restore) saveCursor();
|
|
256
|
+
for (let row = topRuleRow(); row <= rows(); row++) {
|
|
257
|
+
moveTo(row, 1);
|
|
258
|
+
clearLine();
|
|
259
|
+
}
|
|
260
|
+
if (restore) restoreCursor();
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Wrap `prefix + value` into visible lines, keeping only the last N rows
|
|
266
|
+
* with an ellipsis marker on overflow. Returned lines are already indented
|
|
267
|
+
* for the input area.
|
|
268
|
+
*/
|
|
269
|
+
function layoutInput(prefix, value) {
|
|
270
|
+
const budget = inputTextBudget();
|
|
271
|
+
const combined = `${prefix || ''}${value || ''}`;
|
|
272
|
+
const wrapped = wrapToLines(combined, budget);
|
|
273
|
+
const tail = tailWithEllipsis(wrapped, inputRows);
|
|
274
|
+
return {
|
|
275
|
+
lines: tail.visible,
|
|
276
|
+
truncated: tail.truncated,
|
|
277
|
+
dropped: tail.dropped,
|
|
278
|
+
wrapped,
|
|
279
|
+
prefix: prefix || '',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function drawInputLines(lines) {
|
|
284
|
+
saveCursor();
|
|
285
|
+
const indent = ' '.repeat(INPUT_INDENT);
|
|
286
|
+
for (let i = 0; i < inputRows; i++) {
|
|
287
|
+
const row = inputRowStart() + i;
|
|
288
|
+
moveTo(row, 1);
|
|
289
|
+
clearLine();
|
|
290
|
+
if (i < lines.length) {
|
|
291
|
+
write(`${indent}${lines[i]}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
restoreCursor();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function isInputDockMounted() {
|
|
298
|
+
return mounted;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function mountInputDock({ inputRowsMax: requestedMax } = {}) {
|
|
302
|
+
const t = term();
|
|
303
|
+
if (!t.isTTY || t.plain) return false;
|
|
304
|
+
if (process.env.BAHULAM_FIXED_INPUT === '0' || process.env.KEPLER_FIXED_INPUT === '0') return false;
|
|
305
|
+
if (mounted) return true;
|
|
306
|
+
|
|
307
|
+
inputRowsMax = resolveMaxInputRows(requestedMax);
|
|
308
|
+
inputRows = MIN_INPUT_ROWS;
|
|
309
|
+
reservedRows = FIXED_ROWS + inputRows;
|
|
310
|
+
mounted = true;
|
|
311
|
+
applyLayout();
|
|
312
|
+
|
|
313
|
+
unsubResize = onResize(() => applyLayout());
|
|
314
|
+
process.once('exit', safeUnmount);
|
|
315
|
+
process.once('SIGTERM', () => { safeUnmount(); process.exit(143); });
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function unmountInputDock() {
|
|
320
|
+
if (!mounted || resetting) return;
|
|
321
|
+
resetting = true;
|
|
322
|
+
try {
|
|
323
|
+
const exitRow = topRuleRow();
|
|
324
|
+
clearDockArea({ restore: false });
|
|
325
|
+
clearScrollRegion();
|
|
326
|
+
moveTo(Math.min(rows(), exitRow), 1);
|
|
327
|
+
if (unsubResize) { unsubResize(); unsubResize = null; }
|
|
328
|
+
} finally {
|
|
329
|
+
mounted = false;
|
|
330
|
+
resetting = false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function safeUnmount() { try { unmountInputDock(); } catch {} }
|
|
335
|
+
|
|
336
|
+
export function moveToContent() {
|
|
337
|
+
if (!mounted) return false;
|
|
338
|
+
moveTo(contentBottomRow(), 1);
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// One row above the bottom of the scroll region. Writes here won't trigger
|
|
343
|
+
// the scroll-on-LF that happens when writing '\n' at the region's bottom row —
|
|
344
|
+
// exactly what the persistent explore/spinner line needs to overwrite in place
|
|
345
|
+
// without piling copies of itself into scrollback.
|
|
346
|
+
export function pinnedStatusRow() {
|
|
347
|
+
if (!mounted) return null;
|
|
348
|
+
return Math.max(1, contentBottomRow() - 1);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function drawPinnedStatus(line) {
|
|
352
|
+
if (!mounted) return false;
|
|
353
|
+
const row = pinnedStatusRow();
|
|
354
|
+
if (row == null) return false;
|
|
355
|
+
saveCursor();
|
|
356
|
+
moveTo(row, 1);
|
|
357
|
+
clearLine();
|
|
358
|
+
write(String(line || ''));
|
|
359
|
+
restoreCursor();
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function clearPinnedStatus() {
|
|
364
|
+
if (!mounted) return false;
|
|
365
|
+
const row = pinnedStatusRow();
|
|
366
|
+
if (row == null) return false;
|
|
367
|
+
saveCursor();
|
|
368
|
+
moveTo(row, 1);
|
|
369
|
+
clearLine();
|
|
370
|
+
restoreCursor();
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Redraw just the frame (rules, spacers, meta, tips, safety row) without
|
|
375
|
+
// touching the input rows. Callers that write into the dock's non-input
|
|
376
|
+
// rows (e.g. slash-hint overlays) use this to restore the frame after
|
|
377
|
+
// clearing their overlay.
|
|
378
|
+
export function redrawDockFrame() {
|
|
379
|
+
if (!mounted) return false;
|
|
380
|
+
renderFrame(lastFrame);
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export function prepareInputPrompt({ context = '', tips = '', meta = '' } = {}) {
|
|
385
|
+
if (!mounted) return false;
|
|
386
|
+
setInputRowsTo(MIN_INPUT_ROWS);
|
|
387
|
+
clearInputRows();
|
|
388
|
+
renderFrame({ context, tips, meta });
|
|
389
|
+
moveTo(inputRowStart(), INPUT_INDENT + 1);
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function clearInputPrompt() {
|
|
394
|
+
if (!mounted) return false;
|
|
395
|
+
clearInputRows();
|
|
396
|
+
renderFrame(lastFrame);
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function renderDockInput(prefix, value, { context = '', tips = '', meta = '' } = {}) {
|
|
401
|
+
if (!mounted) return false;
|
|
402
|
+
setInputRowsTo(computeInputRowsForBuffer(prefix, value));
|
|
403
|
+
renderFrame({ context, tips, meta });
|
|
404
|
+
const layout = layoutInput(prefix, value);
|
|
405
|
+
drawInputLines(layout.lines);
|
|
406
|
+
focusDockInput(prefix, value);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Move the terminal cursor to the position that corresponds to the logical
|
|
412
|
+
* end of `prefix + value` within the (possibly wrapped and truncated) input
|
|
413
|
+
* area. If the buffer overflowed, the cursor lands on the last visible row.
|
|
414
|
+
*/
|
|
415
|
+
export function focusDockInput(prefix, value = '') {
|
|
416
|
+
if (!mounted) return false;
|
|
417
|
+
const layout = layoutInput(prefix, value);
|
|
418
|
+
const offset = visibleWidth(`${prefix || ''}${value || ''}`);
|
|
419
|
+
const pos = cursorPositionInLines(layout.wrapped, offset);
|
|
420
|
+
const visibleRowIdx = Math.max(
|
|
421
|
+
0,
|
|
422
|
+
Math.min(inputRows - 1, pos.row - Math.max(0, layout.wrapped.length - inputRows)),
|
|
423
|
+
);
|
|
424
|
+
const row = inputRowStart() + visibleRowIdx;
|
|
425
|
+
const col = Math.min(cols(), INPUT_INDENT + 1 + Math.max(0, pos.col));
|
|
426
|
+
moveTo(row, col);
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function inputRowColumn() {
|
|
431
|
+
return INPUT_INDENT + 1;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Test-only accessors.
|
|
435
|
+
export function _internals() {
|
|
436
|
+
return {
|
|
437
|
+
inputRows: () => inputRows,
|
|
438
|
+
reservedRows: () => reservedRows,
|
|
439
|
+
layoutInput,
|
|
440
|
+
inputTextBudget,
|
|
441
|
+
FIXED_ROWS,
|
|
442
|
+
BRAND_LABEL,
|
|
443
|
+
};
|
|
444
|
+
}
|