@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,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool cards โ Mission Control (PRD-055 ยง6).
|
|
3
|
+
*
|
|
4
|
+
* One-line summary per tool: icon + label + args + outcome.
|
|
5
|
+
*
|
|
6
|
+
* ๐ญ search_code "JWT validation" โ 4 matches in 2 files
|
|
7
|
+
* ๐ญ read_file auth.py L42-L88 โ 47 lines
|
|
8
|
+
* ๐ ๏ธ edit_file auth.py โ +12 โ4
|
|
9
|
+
* โ๏ธ shell "npm test" โ passed in 1.2s
|
|
10
|
+
*
|
|
11
|
+
* Two render points:
|
|
12
|
+
*
|
|
13
|
+
* formatCardHead(tool, args) โ at tool invocation (no outcome)
|
|
14
|
+
* formatCard({ tool, args, result, โฆ }) โ once the result arrives
|
|
15
|
+
*
|
|
16
|
+
* Cards are recorded in a small ring buffer (`recordCard`, `lastCard`,
|
|
17
|
+
* `getCard`) so the expand handler in repl.mjs can re-render details on `d`
|
|
18
|
+
* / `/last` / `/expand <n>` without holding state in the REPL itself.
|
|
19
|
+
*
|
|
20
|
+
* No I/O โ callers (repl, demo, headless adapter) are responsible for
|
|
21
|
+
* `process.stderr.write(...)`. This keeps the module pure and testable.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { paint, width as visibleWidth } from './palette.mjs';
|
|
25
|
+
import { toolFamily } from './icons.mjs';
|
|
26
|
+
import { term } from './term.mjs';
|
|
27
|
+
import {
|
|
28
|
+
toolDisplayLabel,
|
|
29
|
+
toolDisplaySummary,
|
|
30
|
+
formatShellCommand,
|
|
31
|
+
shellCommandDisplay,
|
|
32
|
+
} from '../terminal/tool-display.mjs';
|
|
33
|
+
|
|
34
|
+
// โโ Family โ label colorizer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
35
|
+
|
|
36
|
+
function paintLabel(tool, label) {
|
|
37
|
+
switch (toolFamily(tool)) {
|
|
38
|
+
case 'subAgent': return paint.brand.data(label);
|
|
39
|
+
case 'search': return paint.text.primary(label);
|
|
40
|
+
case 'write': return paint.brand.primary(label);
|
|
41
|
+
case 'shell': return paint.state.warn(label);
|
|
42
|
+
case 'network': return paint.brand.accent(label);
|
|
43
|
+
default: return paint.text.primary(label);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// โโ Args summary โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
48
|
+
|
|
49
|
+
function formatArgs(tool, args, cwd) {
|
|
50
|
+
const summary = toolDisplaySummary(tool, args || {}, { cwd });
|
|
51
|
+
if (!summary) return '';
|
|
52
|
+
if (tool === 'shell') {
|
|
53
|
+
const display = shellCommandDisplay(summary, { cwd });
|
|
54
|
+
const command = formatShellCommand(display.command, paintShellAdapter);
|
|
55
|
+
return display.cwdLabel
|
|
56
|
+
? `${command} ${paint.text.dim('in')} ${paint.brand.data(display.cwdLabel)}`
|
|
57
|
+
: command;
|
|
58
|
+
}
|
|
59
|
+
return paint.text.muted(summary);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Adapter so formatShellCommand (from legacy tool-display.mjs) keeps working
|
|
63
|
+
// against the new palette. It expects an object with .red/.blue/.yellow/.white.
|
|
64
|
+
const paintShellAdapter = {
|
|
65
|
+
red: (s) => paint.state.danger(s),
|
|
66
|
+
blue: (s) => paint.brand.data(s),
|
|
67
|
+
yellow: (s) => paint.state.warn(s),
|
|
68
|
+
white: (s) => paint.text.primary(s),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// โโ Result โ outcome summary โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Summarize a tool result into a compact outcome label.
|
|
75
|
+
*
|
|
76
|
+
* @returns {{ text: string, tone: 'success'|'warn'|'danger'|'dim' }}
|
|
77
|
+
*/
|
|
78
|
+
export function summarizeResult(tool, data) {
|
|
79
|
+
if (!data) return { text: '', tone: 'dim' };
|
|
80
|
+
|
|
81
|
+
if (data._blocked) {
|
|
82
|
+
return { text: firstOutputLine(data) || 'blocked', tone: 'danger' };
|
|
83
|
+
}
|
|
84
|
+
if (data._observation_timeout) {
|
|
85
|
+
const ms = data._observation_timeout_ms;
|
|
86
|
+
const duration = typeof ms === 'number' ? formatDuration(ms) : '';
|
|
87
|
+
return { text: duration ? `observed ${duration} tail` : 'observed output tail', tone: 'warn' };
|
|
88
|
+
}
|
|
89
|
+
if (data._timed_out) {
|
|
90
|
+
return { text: 'timed out', tone: 'danger' };
|
|
91
|
+
}
|
|
92
|
+
if (data.success === false) {
|
|
93
|
+
const msg = String(data.error || firstOutputLine(data) || 'failed').slice(0, 140);
|
|
94
|
+
return { text: msg, tone: 'danger' };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
switch (tool) {
|
|
98
|
+
case 'read_file': {
|
|
99
|
+
const lines = data._total_lines || lineCount(data.output || data.output_preview);
|
|
100
|
+
return { text: `${lines} line${lines === 1 ? '' : 's'}`, tone: 'success' };
|
|
101
|
+
}
|
|
102
|
+
case 'read_files':
|
|
103
|
+
return { text: 'files read', tone: 'success' };
|
|
104
|
+
|
|
105
|
+
case 'search_code':
|
|
106
|
+
case 'search_files':
|
|
107
|
+
case 'grep': {
|
|
108
|
+
const matches = countMatches(data);
|
|
109
|
+
const files = countMatchFiles(data);
|
|
110
|
+
if (matches === 0) return { text: 'no matches', tone: 'warn' };
|
|
111
|
+
const filesPart = files > 0 ? ` in ${files} file${files === 1 ? '' : 's'}` : '';
|
|
112
|
+
return { text: `${matches} match${matches === 1 ? '' : 'es'}${filesPart}`, tone: 'success' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case 'list_files': {
|
|
116
|
+
const n = lineCount(data.output);
|
|
117
|
+
return { text: n > 0 ? `${n} item${n === 1 ? '' : 's'}` : 'empty', tone: 'success' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
case 'edit_file':
|
|
121
|
+
case 'write_file':
|
|
122
|
+
case 'write_project': {
|
|
123
|
+
const delta = diffDelta(data);
|
|
124
|
+
if (delta) return { text: delta, tone: 'success' };
|
|
125
|
+
return { text: 'updated', tone: 'success' };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case 'delete_file':
|
|
129
|
+
return { text: 'deleted', tone: 'warn' };
|
|
130
|
+
|
|
131
|
+
case 'shell':
|
|
132
|
+
case 'run_tests':
|
|
133
|
+
case 'validate_build':
|
|
134
|
+
case 'lint_check':
|
|
135
|
+
case 'validate_file':
|
|
136
|
+
case 'validate_structure': {
|
|
137
|
+
const exit = data.exit_code ?? data.exitCode;
|
|
138
|
+
if (exit != null && exit !== 0) {
|
|
139
|
+
return { text: `exit ${exit}`, tone: 'danger' };
|
|
140
|
+
}
|
|
141
|
+
const head = firstOutputLine(data).slice(0, 100);
|
|
142
|
+
return { text: head || 'ok', tone: 'success' };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case 'analyze_code': {
|
|
146
|
+
// Backend returns "filename (N lines, ext)" โ the filename already
|
|
147
|
+
// appears in the card head, so strip it and keep just the metadata.
|
|
148
|
+
const head = firstOutputLine(data);
|
|
149
|
+
const m = head.match(/\((\d+)\s+lines?,?\s+([^)]+)\)/);
|
|
150
|
+
if (m) return { text: `${m[1]} lines ยท ${m[2].trim()}`, tone: 'success' };
|
|
151
|
+
return { text: head.slice(0, 80) || 'done', tone: 'success' };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
case 'plan':
|
|
155
|
+
case 'explore':
|
|
156
|
+
case 'verify':
|
|
157
|
+
case 'debug':
|
|
158
|
+
case 'refactor': {
|
|
159
|
+
const head = firstOutputLine(data).slice(0, 100);
|
|
160
|
+
return { text: head || 'done', tone: 'success' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
default: {
|
|
164
|
+
const head = firstOutputLine(data).slice(0, 100);
|
|
165
|
+
return { text: head || 'done', tone: 'success' };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function formatCompactFileDiff(result, {
|
|
171
|
+
indent = ' ',
|
|
172
|
+
maxLines = 14,
|
|
173
|
+
maxFiles = 2,
|
|
174
|
+
columns = term().columns || 120,
|
|
175
|
+
} = {}) {
|
|
176
|
+
const diffs = fileDiffs(result).filter(diff => diff?.hunks?.length);
|
|
177
|
+
if (!diffs.length) return '';
|
|
178
|
+
|
|
179
|
+
const out = [];
|
|
180
|
+
let shown = 0;
|
|
181
|
+
let truncated = false;
|
|
182
|
+
const lineBudget = Math.max(40, columns - visibleWidth(indent) - 4);
|
|
183
|
+
|
|
184
|
+
for (const diff of diffs.slice(0, maxFiles)) {
|
|
185
|
+
if (diffs.length > 1) {
|
|
186
|
+
if (shown >= maxLines) { truncated = true; break; }
|
|
187
|
+
out.push(`${indent}${paint.brand.primary(diff.relative_path || diff.path || 'file')} ${paint.text.dim(diffDelta(diff))}`);
|
|
188
|
+
shown++;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for (const hunk of diff.hunks || []) {
|
|
192
|
+
if (shown >= maxLines) { truncated = true; break; }
|
|
193
|
+
out.push(`${indent}${paint.text.dim(`@@ -${hunk.old_start},${hunk.old_count} +${hunk.new_start},${hunk.new_count} @@`)}`);
|
|
194
|
+
shown++;
|
|
195
|
+
|
|
196
|
+
for (const line of hunk.lines || []) {
|
|
197
|
+
if (shown >= maxLines) { truncated = true; break; }
|
|
198
|
+
out.push(`${indent}${paintDiffLine(line, lineBudget)}`);
|
|
199
|
+
shown++;
|
|
200
|
+
}
|
|
201
|
+
if (truncated) break;
|
|
202
|
+
}
|
|
203
|
+
if (truncated) break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (diffs.length > maxFiles) truncated = true;
|
|
207
|
+
if (truncated) out.push(`${indent}${paint.text.dim('โฆ diff preview truncated; use /last to expand')}`);
|
|
208
|
+
return out.join('\n');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function fileDiffs(result) {
|
|
212
|
+
if (!result) return [];
|
|
213
|
+
if (Array.isArray(result.file_diffs)) return result.file_diffs;
|
|
214
|
+
if (result.file_diff) return [result.file_diff];
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function paintDiffLine(line, maxWidth) {
|
|
219
|
+
const text = truncatePlain(String(line?.text ?? ''), Math.max(20, maxWidth - 2));
|
|
220
|
+
if (line?.type === 'add') return paint.state.success(`+ ${text}`);
|
|
221
|
+
if (line?.type === 'remove') return paint.state.danger(`- ${text}`);
|
|
222
|
+
return paint.text.dim(` ${text}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function truncatePlain(text, max) {
|
|
226
|
+
if (text.length <= max) return text;
|
|
227
|
+
if (max <= 1) return '';
|
|
228
|
+
return text.slice(0, max - 1) + 'โฆ';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function firstOutputLine(data) {
|
|
232
|
+
const o = data?.output_preview || data?.output || data?.message || '';
|
|
233
|
+
return String(o).split('\n').map(l => l.trim()).find(Boolean) || '';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function lineCount(s) {
|
|
237
|
+
if (!s) return 0;
|
|
238
|
+
return String(s).split('\n').filter(Boolean).length;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function countMatches(data) {
|
|
242
|
+
if (typeof data?.match_count === 'number') return data.match_count;
|
|
243
|
+
return lineCount(data?.output);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function countMatchFiles(data) {
|
|
247
|
+
if (typeof data?.file_count === 'number') return data.file_count;
|
|
248
|
+
const out = String(data?.output || '');
|
|
249
|
+
if (!out) return 0;
|
|
250
|
+
const files = new Set();
|
|
251
|
+
for (const line of out.split('\n')) {
|
|
252
|
+
const m = line.match(/^([^:]+):/);
|
|
253
|
+
if (m) files.add(m[1]);
|
|
254
|
+
}
|
|
255
|
+
return files.size;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function diffDelta(data) {
|
|
259
|
+
const add = data?.lines_added ?? data?.additions;
|
|
260
|
+
const rem = data?.lines_removed ?? data?.deletions;
|
|
261
|
+
if (add == null && rem == null) return '';
|
|
262
|
+
const a = add ?? 0;
|
|
263
|
+
const r = rem ?? 0;
|
|
264
|
+
return `+${a} โ${r}`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function tone(text, t) {
|
|
268
|
+
switch (t) {
|
|
269
|
+
case 'success': return paint.state.success(text);
|
|
270
|
+
case 'warn': return paint.state.warn(text);
|
|
271
|
+
case 'danger': return paint.state.danger(text);
|
|
272
|
+
case 'dim':
|
|
273
|
+
default: return paint.text.dim(text);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// โโ Card head (printed at invocation) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Render the leading half of a card โ colored verb + args.
|
|
281
|
+
*
|
|
282
|
+
* v2.0.3: dropped the leading tool icon (๐ญ/๐ ๏ธ/โ๏ธ). The label itself is a
|
|
283
|
+
* present-progressive verb so the line reads like prose:
|
|
284
|
+
* "Reading src/ui/banner.mjs ยท lines 31-65 โ 36 lines"
|
|
285
|
+
* The icon was decorative noise that broke the conversational feel.
|
|
286
|
+
* Mission report and sub-agent renderers still use the icons in their
|
|
287
|
+
* own contexts.
|
|
288
|
+
*
|
|
289
|
+
* Width-aware: truncates args from the left when the line would overflow.
|
|
290
|
+
*/
|
|
291
|
+
export function formatCardHead(tool, args, opts = {}) {
|
|
292
|
+
const cwd = opts.cwd || safeCwd();
|
|
293
|
+
const cols = opts.columns || term().columns || 120;
|
|
294
|
+
const indent = opts.indent || ' ';
|
|
295
|
+
|
|
296
|
+
const label = toolDisplayLabel(tool);
|
|
297
|
+
const argsText = formatArgs(tool, args, cwd);
|
|
298
|
+
|
|
299
|
+
const leadVisible = visibleWidth(`${indent}${label}`);
|
|
300
|
+
const budget = Math.max(20, cols - leadVisible - 4);
|
|
301
|
+
|
|
302
|
+
if (tool === 'shell' && visibleWidth(argsText) > budget) {
|
|
303
|
+
const wrapWidth = Math.max(32, cols - visibleWidth(indent) - 4);
|
|
304
|
+
const display = shellCommandDisplay(toolDisplaySummary(tool, args || {}, { cwd }), { cwd });
|
|
305
|
+
const commandLines = wrapCommand(display.command, wrapWidth)
|
|
306
|
+
.map(line => `${indent}${paint.text.dim(' ')}${formatShellCommand(line, paintShellAdapter)}`);
|
|
307
|
+
const head = `${indent}${paintLabel(tool, label)}`;
|
|
308
|
+
const cwdLine = display.cwdLabel
|
|
309
|
+
? `\n${indent}${paint.text.dim(' in ')}${paint.brand.data(display.cwdLabel)}`
|
|
310
|
+
: '';
|
|
311
|
+
return `${head}\n${commandLines.join('\n')}${cwdLine}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const argsTruncated = truncateMiddle(argsText, budget);
|
|
315
|
+
|
|
316
|
+
const head = `${indent}${paintLabel(tool, label)}`;
|
|
317
|
+
return argsTruncated ? `${head} ${argsTruncated}` : head;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Render a full card with outcome.
|
|
322
|
+
*
|
|
323
|
+
* ๐ญ search_code "JWT" โ 4 matches in 2 files ยท 120ms
|
|
324
|
+
*
|
|
325
|
+
* `result` is the tool_result data from the SSE stream (same shape as
|
|
326
|
+
* `renderToolResult` consumed). `durationMs` overrides what's on the result.
|
|
327
|
+
*/
|
|
328
|
+
export function formatCard({ tool, args, result, durationMs, indent, columns, cwd } = {}) {
|
|
329
|
+
const cols = columns || term().columns || 120;
|
|
330
|
+
const head = formatCardHead(tool, args, { indent, columns: cols, cwd });
|
|
331
|
+
|
|
332
|
+
const summary = summarizeResult(tool, result);
|
|
333
|
+
const duration = formatDuration(durationMs ?? result?.duration_ms ?? (result?.duration_s != null ? result.duration_s * 1000 : null));
|
|
334
|
+
|
|
335
|
+
if (!summary.text && !duration) return head;
|
|
336
|
+
|
|
337
|
+
const arrow = paint.text.dim('โ');
|
|
338
|
+
const body = summary.text ? tone(summary.text, summary.tone) : '';
|
|
339
|
+
// Hide the duration tail when the tool was effectively instant (<200ms).
|
|
340
|
+
// For fast reads, "1ms" / "0ms" was noise that broke the prose feel.
|
|
341
|
+
const showDuration = duration && (durationMs == null || durationMs >= 200);
|
|
342
|
+
const tail = showDuration ? paint.text.dim(` ยท ${duration}`) : '';
|
|
343
|
+
|
|
344
|
+
const candidate = `${head} ${arrow} ${body}${tail}`;
|
|
345
|
+
if (!head.includes('\n') && visibleWidth(candidate) <= cols) return candidate;
|
|
346
|
+
if (!head.includes('\n') && isInlineOutcomeTool(tool)) {
|
|
347
|
+
const reserve = visibleWidth(`${arrow} ${body}${tail}`) + 4;
|
|
348
|
+
const compactHead = truncateMiddle(head, Math.max(28, cols - reserve));
|
|
349
|
+
return `${compactHead} ${arrow} ${body}${tail}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Doesn't fit on one line โ push outcome to a separate gutter line.
|
|
353
|
+
const gutterIndent = (indent || ' ') + paint.text.dim('โฟ ');
|
|
354
|
+
return `${head}\n${gutterIndent}${arrow} ${body}${tail}`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function isInlineOutcomeTool(tool) {
|
|
358
|
+
return [
|
|
359
|
+
'read_file', 'read_files', 'read_batch', 'get_file_info',
|
|
360
|
+
'search_code', 'search_files', 'grep', 'list_files',
|
|
361
|
+
].includes(String(tool || '').toLowerCase());
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function truncateMiddle(text, max) {
|
|
365
|
+
if (!text) return '';
|
|
366
|
+
if (visibleWidth(text) <= max) return text;
|
|
367
|
+
// Truncate the plain text and re-trust palette helpers to skip codes.
|
|
368
|
+
const plain = text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
369
|
+
if (plain.length <= max) return text;
|
|
370
|
+
const keep = Math.max(8, max - 3);
|
|
371
|
+
const head = plain.slice(0, Math.floor(keep / 2));
|
|
372
|
+
const tail = plain.slice(plain.length - Math.ceil(keep / 2));
|
|
373
|
+
return paint.text.muted(`${head}โฆ${tail}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function wrapCommand(command, width) {
|
|
377
|
+
const text = String(command || '');
|
|
378
|
+
if (!text) return ['(empty command)'];
|
|
379
|
+
const lines = [];
|
|
380
|
+
let line = '';
|
|
381
|
+
for (const token of text.match(/\S+\s*/g) || [text]) {
|
|
382
|
+
const next = line + token;
|
|
383
|
+
if (line && visibleWidth(next.trimEnd()) > width) {
|
|
384
|
+
lines.push(line.trimEnd());
|
|
385
|
+
line = token;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (!line && visibleWidth(token.trimEnd()) > width) {
|
|
389
|
+
lines.push(...chunkLongToken(token.trimEnd(), width));
|
|
390
|
+
line = '';
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
line = next;
|
|
394
|
+
}
|
|
395
|
+
if (line.trimEnd()) lines.push(line.trimEnd());
|
|
396
|
+
return lines.length ? lines : ['(empty command)'];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function chunkLongToken(token, width) {
|
|
400
|
+
const chunks = [];
|
|
401
|
+
const size = Math.max(8, width);
|
|
402
|
+
for (let i = 0; i < token.length; i += size) {
|
|
403
|
+
chunks.push(token.slice(i, i + size));
|
|
404
|
+
}
|
|
405
|
+
return chunks;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function formatDuration(ms) {
|
|
409
|
+
if (ms == null || !Number.isFinite(ms)) return '';
|
|
410
|
+
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
411
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function safeCwd() {
|
|
415
|
+
try { return process.cwd(); } catch { return ''; }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// โโ Ring buffer of recent cards (for expand / /last) โโโโโโโโโโโโโโโโโโโโโ
|
|
419
|
+
|
|
420
|
+
const MAX_CARDS = 50;
|
|
421
|
+
const _cards = [];
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Record a card by its call_id (or generated id). Returns the stored entry.
|
|
425
|
+
* The entry is updated in place when the matching result arrives.
|
|
426
|
+
*/
|
|
427
|
+
export function recordCard({ id, tool, args, head, result, durationMs, startedAt }) {
|
|
428
|
+
const entry = { id, tool, args, head, result: result || null, durationMs: durationMs ?? null, startedAt: startedAt ?? null };
|
|
429
|
+
// Replace if same id already exists (e.g. tool_call followed by tool_result)
|
|
430
|
+
const existing = _cards.findIndex(c => c.id != null && c.id === id);
|
|
431
|
+
if (existing >= 0) {
|
|
432
|
+
_cards[existing] = { ..._cards[existing], ...entry };
|
|
433
|
+
return _cards[existing];
|
|
434
|
+
}
|
|
435
|
+
_cards.push(entry);
|
|
436
|
+
if (_cards.length > MAX_CARDS) _cards.shift();
|
|
437
|
+
return entry;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Most recently recorded card (the one `d` / `/last` should expand). */
|
|
441
|
+
export function lastCard() {
|
|
442
|
+
return _cards[_cards.length - 1] || null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Look up a card by id, or 1-based index from the tail (-1 == lastCard). */
|
|
446
|
+
export function getCard(idOrIndex) {
|
|
447
|
+
if (idOrIndex == null) return lastCard();
|
|
448
|
+
if (typeof idOrIndex === 'number') {
|
|
449
|
+
if (idOrIndex < 0) return _cards[_cards.length + idOrIndex] || null;
|
|
450
|
+
return _cards[idOrIndex] || null;
|
|
451
|
+
}
|
|
452
|
+
return _cards.find(c => c.id === idOrIndex) || null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** All recorded cards in order. */
|
|
456
|
+
export function allCards() {
|
|
457
|
+
return _cards.slice();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Drop all recorded cards (used by tests and `/clear`). */
|
|
461
|
+
export function clearCards() {
|
|
462
|
+
_cards.length = 0;
|
|
463
|
+
}
|