@bahulam/code 0.1.1
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 +93 -0
- package/package.json +56 -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 +223 -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 +312 -0
- package/src/commands/agent.mjs +221 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +202 -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/model-catalog.mjs +57 -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/prose-chunker.mjs +255 -0
- package/src/context/retriever.mjs +425 -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 +145 -0
- package/src/core/approval.mjs +700 -0
- package/src/core/attachments.mjs +666 -0
- package/src/core/backend-url.mjs +68 -0
- package/src/core/bundled-runtime.mjs +418 -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 +331 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +460 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +538 -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 +388 -0
- package/src/core/safety.mjs +260 -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 +957 -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 +2231 -0
- package/src/core/trust.mjs +160 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -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 +374 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +700 -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 +122 -0
- package/src/terminal/agents.mjs +353 -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 +310 -0
- package/src/terminal/repl-ask-form.mjs +120 -0
- package/src/terminal/repl-explore.mjs +44 -0
- package/src/terminal/repl-format.mjs +317 -0
- package/src/terminal/repl-model-form.mjs +132 -0
- package/src/terminal/repl-render.mjs +833 -0
- package/src/terminal/repl-resume.mjs +640 -0
- package/src/terminal/repl-state.mjs +120 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +5032 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +392 -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 +703 -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 +510 -0
- package/src/ui/banner.mjs +232 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +170 -0
- package/src/ui/input-dock.mjs +772 -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/render-queue.mjs +500 -0
- package/src/ui/slash-commands.mjs +257 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +167 -0
- package/src/ui/term.mjs +174 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +740 -0
- package/src/ui/tool-details.mjs +504 -0
- package/src/ui/transcript-block.mjs +20 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool detail formatters — Mission Control (PRD-055 §6.3).
|
|
3
|
+
*
|
|
4
|
+
* One function per high-value tool, all sharing the same signature so the
|
|
5
|
+
* expand handler can dispatch by `tool`:
|
|
6
|
+
*
|
|
7
|
+
* detailFor(card) → string // multi-line, ANSI-styled, no trailing \n
|
|
8
|
+
*
|
|
9
|
+
* Falls back to a generic dump when there's no dedicated formatter.
|
|
10
|
+
*
|
|
11
|
+
* Pure: no I/O. The REPL writes the returned string to stderr.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { paint } from './palette.mjs';
|
|
15
|
+
import { icon, toolFamily } from './icons.mjs';
|
|
16
|
+
import { shellCommandProfile, toolDisplayLabel, toolDisplaySummary } from '../terminal/tool-display.mjs';
|
|
17
|
+
import { isSensitiveConfigPath } from '../core/safety.mjs';
|
|
18
|
+
|
|
19
|
+
const MAX_DETAIL_LINES = 60;
|
|
20
|
+
const MAX_SHELL_DETAIL_LINES = 220;
|
|
21
|
+
const MAX_LINE_WIDTH = 220;
|
|
22
|
+
|
|
23
|
+
// ── Dispatch ─────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export function detailFor(card) {
|
|
26
|
+
if (!card) return paint.text.dim(' (no card to expand)');
|
|
27
|
+
const { tool } = card;
|
|
28
|
+
|
|
29
|
+
const header = renderHeader(card);
|
|
30
|
+
const body = renderBody(card);
|
|
31
|
+
return body ? `${header}\n${body}` : header;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function renderBody(card) {
|
|
35
|
+
const { tool } = card;
|
|
36
|
+
switch (tool) {
|
|
37
|
+
case 'read_file': return detailReadFile(card);
|
|
38
|
+
case 'read_files': return detailReadFiles(card);
|
|
39
|
+
case 'search_code':
|
|
40
|
+
case 'search_files':
|
|
41
|
+
case 'grep': return detailSearch(card);
|
|
42
|
+
case 'list_files': return detailListFiles(card);
|
|
43
|
+
case 'edit_file': return detailEditFile(card);
|
|
44
|
+
case 'write_file': return detailWriteFile(card);
|
|
45
|
+
case 'write_project': return detailWriteProject(card);
|
|
46
|
+
case 'delete_file': return detailDeleteFile(card);
|
|
47
|
+
case 'shell': return detailShell(card);
|
|
48
|
+
case 'run_tests':
|
|
49
|
+
case 'validate_build':
|
|
50
|
+
case 'lint_check':
|
|
51
|
+
case 'validate_file':
|
|
52
|
+
case 'validate_structure': return detailValidator(card);
|
|
53
|
+
case 'plan': return detailPlan(card);
|
|
54
|
+
case 'Agent':
|
|
55
|
+
case 'agent':
|
|
56
|
+
case 'task': return detailAgent(card);
|
|
57
|
+
case 'sub_agent_tools': return detailSubAgentTools(card);
|
|
58
|
+
case 'explore':
|
|
59
|
+
case 'verify':
|
|
60
|
+
case 'debug':
|
|
61
|
+
case 'refactor':
|
|
62
|
+
case 'analyze_code': return detailGenericOutput(card);
|
|
63
|
+
default: return detailGenericOutput(card);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Header / framing ─────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function renderHeader(card) {
|
|
70
|
+
const { tool, args, durationMs, result } = card;
|
|
71
|
+
const label = toolDisplayLabel(tool);
|
|
72
|
+
const fam = toolFamily(tool);
|
|
73
|
+
const accent = fam === 'write' ? paint.brand.primary
|
|
74
|
+
: fam === 'shell' ? paint.state.warn
|
|
75
|
+
: fam === 'subAgent' ? paint.brand.data
|
|
76
|
+
: paint.text.primary;
|
|
77
|
+
|
|
78
|
+
const lines = [
|
|
79
|
+
` ${paint.text.dim('━━')} ${icon(tool)} ${accent(label)} ${paint.text.dim('━━')}`,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const args1 = oneLineArgs(tool, args);
|
|
83
|
+
if (args1) lines.push(` ${paint.text.dim('args ')} ${args1}`);
|
|
84
|
+
if (durationMs != null) {
|
|
85
|
+
lines.push(` ${paint.text.dim('time ')} ${paint.text.muted(formatDuration(durationMs))}`);
|
|
86
|
+
}
|
|
87
|
+
if (result?.success === false) {
|
|
88
|
+
lines.push(` ${paint.state.danger('error ')} ${result?.error || 'failed'}`);
|
|
89
|
+
}
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function oneLineArgs(tool, args) {
|
|
94
|
+
if (!args) return '';
|
|
95
|
+
try {
|
|
96
|
+
const compact = JSON.stringify(safeDetailArgs(tool, args));
|
|
97
|
+
if (compact.length <= 140) return paint.text.muted(compact);
|
|
98
|
+
return paint.text.muted(compact.slice(0, 137) + '…');
|
|
99
|
+
} catch {
|
|
100
|
+
return paint.text.muted(String(args));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function safeDetailArgs(tool, args) {
|
|
105
|
+
if (tool === 'shell') {
|
|
106
|
+
const profile = shellCommandProfile(args?.command || args?.cmd || '');
|
|
107
|
+
return {
|
|
108
|
+
command: profile.summary,
|
|
109
|
+
...(profile.cwdLabel ? { cwd: profile.cwdLabel } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (tool === 'write_file') {
|
|
113
|
+
const { content, ...rest } = args || {};
|
|
114
|
+
return {
|
|
115
|
+
...rest,
|
|
116
|
+
content: typeof content === 'string' ? `[${content.split('\n').length} lines omitted]` : content,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (tool === 'write_project') {
|
|
120
|
+
return {
|
|
121
|
+
...args,
|
|
122
|
+
files: (args.files || []).map(file => ({
|
|
123
|
+
path: file.path || file.file_path,
|
|
124
|
+
content: typeof file.content === 'string' ? `[${file.content.split('\n').length} lines omitted]` : file.content,
|
|
125
|
+
})),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (tool === 'Agent' || tool === 'agent' || tool === 'task') {
|
|
129
|
+
const prompt = args?.prompt || args?.task || args?.query || args?.description || args?.instruction || '';
|
|
130
|
+
return {
|
|
131
|
+
...(args?.subagent_type ? { subagent_type: args.subagent_type } : {}),
|
|
132
|
+
...(args?.agent ? { agent: args.agent } : {}),
|
|
133
|
+
...(args?.name ? { name: args.name } : {}),
|
|
134
|
+
...(Array.isArray(args?.allowed_tools) ? { allowed_tools: args.allowed_tools } : {}),
|
|
135
|
+
...(prompt ? { prompt: `[${String(prompt).split('\n').length} lines] ${String(prompt).split('\n').find(Boolean)?.slice(0, 80) || ''}` } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (tool === 'edit_file') {
|
|
139
|
+
const next = { ...args };
|
|
140
|
+
if (isSensitiveConfigPath(next.file_path || next.path)) {
|
|
141
|
+
for (const key of ['search', 'replace', 'old_string', 'new_string']) {
|
|
142
|
+
if (typeof next[key] === 'string') next[key] = '[redacted]';
|
|
143
|
+
}
|
|
144
|
+
return next;
|
|
145
|
+
}
|
|
146
|
+
for (const key of ['search', 'replace', 'old_string', 'new_string']) {
|
|
147
|
+
if (typeof next[key] === 'string' && next[key].length > 80) {
|
|
148
|
+
next[key] = `[${next[key].split('\n').length} lines omitted]`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return next;
|
|
152
|
+
}
|
|
153
|
+
return args;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Read ────────────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
function detailReadFile(card) {
|
|
159
|
+
const output = String(card.result?.output ?? card.result?.output_preview ?? '');
|
|
160
|
+
if (!output) return paint.text.dim(' (empty file)');
|
|
161
|
+
const startLine = Number(card.args?.start_line) || 1;
|
|
162
|
+
return numbered(output, startLine);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function detailReadFiles(card) {
|
|
166
|
+
const output = String(card.result?.output ?? '');
|
|
167
|
+
return clip(output);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Search / list ───────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
function detailSearch(card) {
|
|
173
|
+
const output = String(card.result?.output ?? '');
|
|
174
|
+
if (!output.trim()) return paint.text.dim(' (no matches)');
|
|
175
|
+
|
|
176
|
+
const grouped = groupSearchByFile(output);
|
|
177
|
+
if (!grouped) return clip(output);
|
|
178
|
+
|
|
179
|
+
const out = [];
|
|
180
|
+
let totalLines = 0;
|
|
181
|
+
for (const [file, hits] of grouped) {
|
|
182
|
+
if (totalLines > MAX_DETAIL_LINES) {
|
|
183
|
+
out.push(paint.text.dim(` … ${grouped.size - out.length / 2} more file(s)`));
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
out.push(` ${paint.brand.data(file)}`);
|
|
187
|
+
for (const hit of hits.slice(0, 8)) {
|
|
188
|
+
out.push(` ${paint.text.dim(hit.line + ':')} ${paint.text.primary(hit.text)}`);
|
|
189
|
+
totalLines++;
|
|
190
|
+
}
|
|
191
|
+
if (hits.length > 8) {
|
|
192
|
+
out.push(paint.text.dim(` … ${hits.length - 8} more match(es)`));
|
|
193
|
+
}
|
|
194
|
+
totalLines += 1;
|
|
195
|
+
}
|
|
196
|
+
return out.join('\n');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function groupSearchByFile(output) {
|
|
200
|
+
const groups = new Map();
|
|
201
|
+
let any = false;
|
|
202
|
+
for (const line of output.split('\n')) {
|
|
203
|
+
const m = line.match(/^([^:]+):(\d+):(.*)$/);
|
|
204
|
+
if (!m) continue;
|
|
205
|
+
any = true;
|
|
206
|
+
const [, file, ln, text] = m;
|
|
207
|
+
if (!groups.has(file)) groups.set(file, []);
|
|
208
|
+
groups.get(file).push({ line: ln, text: text.trim().slice(0, MAX_LINE_WIDTH) });
|
|
209
|
+
}
|
|
210
|
+
return any ? groups : null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function detailListFiles(card) {
|
|
214
|
+
const output = String(card.result?.output ?? '');
|
|
215
|
+
if (!output.trim()) return paint.text.dim(' (empty)');
|
|
216
|
+
const lines = output.split('\n').filter(Boolean).slice(0, MAX_DETAIL_LINES);
|
|
217
|
+
return lines.map(l => ` ${paint.text.primary(l)}`).join('\n');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── Write / edit ────────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
function detailEditFile(card) {
|
|
223
|
+
const redacted = redactedFileDiff(card.result?.file_diff)
|
|
224
|
+
|| (isSensitiveConfigPath(card.args?.file_path || card.args?.path)
|
|
225
|
+
? sensitiveFallbackDiff(card)
|
|
226
|
+
: null);
|
|
227
|
+
if (redacted) return renderRedactedDiff(redacted);
|
|
228
|
+
|
|
229
|
+
const diff = unifiedForFileDiff(card.result?.file_diff)
|
|
230
|
+
|| card.result?.diff
|
|
231
|
+
|| card.result?.patch
|
|
232
|
+
|| card.result?.output;
|
|
233
|
+
if (diff) return renderDiff(String(diff));
|
|
234
|
+
|
|
235
|
+
const before = card.args?.search;
|
|
236
|
+
const after = card.args?.replace;
|
|
237
|
+
if (before != null && after != null) {
|
|
238
|
+
return [
|
|
239
|
+
` ${paint.state.danger('- ' + String(before).split('\n')[0].slice(0, 160))}`,
|
|
240
|
+
` ${paint.state.success('+ ' + String(after).split('\n')[0].slice(0, 160))}`,
|
|
241
|
+
].join('\n');
|
|
242
|
+
}
|
|
243
|
+
return paint.text.dim(' (edit applied, no diff returned)');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function detailWriteFile(card) {
|
|
247
|
+
const redacted = redactedFileDiff(card.result?.file_diff)
|
|
248
|
+
|| (isSensitiveConfigPath(card.args?.file_path || card.args?.path)
|
|
249
|
+
? sensitiveFallbackDiff(card)
|
|
250
|
+
: null);
|
|
251
|
+
if (redacted) return renderRedactedDiff(redacted);
|
|
252
|
+
|
|
253
|
+
const diff = unifiedForFileDiff(card.result?.file_diff) || card.result?.diff;
|
|
254
|
+
if (diff) return renderDiff(String(diff));
|
|
255
|
+
const content = card.args?.content;
|
|
256
|
+
if (!content) return paint.text.dim(' (no content)');
|
|
257
|
+
return numbered(String(content), 1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function detailWriteProject(card) {
|
|
261
|
+
const diffs = card.result?.file_diffs || [];
|
|
262
|
+
if (diffs.length) {
|
|
263
|
+
return diffs.map(diff => {
|
|
264
|
+
const redacted = redactedFileDiff(diff);
|
|
265
|
+
if (redacted) return renderRedactedDiff(redacted);
|
|
266
|
+
const unified = unifiedForFileDiff(diff);
|
|
267
|
+
return unified ? renderDiff(unified) : '';
|
|
268
|
+
}).filter(Boolean).join('\n');
|
|
269
|
+
}
|
|
270
|
+
const files = card.args?.files || [];
|
|
271
|
+
if (!files.length) return paint.text.dim(' (no files)');
|
|
272
|
+
return files.slice(0, 30).map(f => {
|
|
273
|
+
const p = f.path || f.file_path || '';
|
|
274
|
+
const lines = typeof f.content === 'string' ? f.content.split('\n').length : '?';
|
|
275
|
+
return ` ${paint.brand.primary(p)} ${paint.text.dim(`(${lines} lines)`)}`;
|
|
276
|
+
}).join('\n');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function redactedFileDiff(diff) {
|
|
280
|
+
return diff?.redacted ? diff : null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function sensitiveFallbackDiff(card) {
|
|
284
|
+
return {
|
|
285
|
+
relative_path: card.args?.file_path || card.args?.path || 'sensitive config',
|
|
286
|
+
lines_added: card.result?.lines_added,
|
|
287
|
+
lines_removed: card.result?.lines_removed,
|
|
288
|
+
redacted: true,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function renderRedactedDiff(diff = {}) {
|
|
293
|
+
const file = diff.relative_path || diff.path || 'sensitive config';
|
|
294
|
+
const add = diff.lines_added ?? 0;
|
|
295
|
+
const rem = diff.lines_removed ?? 0;
|
|
296
|
+
return ` ${paint.brand.primary(file)} ${paint.text.dim(`+${add} −${rem}`)}\n ${paint.text.dim('diff redacted for sensitive config')}`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function detailDeleteFile(card) {
|
|
300
|
+
const p = card.args?.file_path || card.args?.path || '';
|
|
301
|
+
return ` ${paint.state.danger('✗')} ${paint.text.primary(p)}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ── Shell / validators ──────────────────────────────────────────────────
|
|
305
|
+
|
|
306
|
+
function detailShell(card) {
|
|
307
|
+
const command = String(card.args?.command || card.args?.cmd || '').trim();
|
|
308
|
+
const stdout = String(card.result?.stdout ?? card.result?.output ?? '');
|
|
309
|
+
const stderr = String(card.result?.stderr ?? '');
|
|
310
|
+
const out = [];
|
|
311
|
+
if (command) {
|
|
312
|
+
const profile = shellCommandProfile(command);
|
|
313
|
+
if (profile.cwdLabel) {
|
|
314
|
+
out.push(paint.text.dim(' cwd'));
|
|
315
|
+
out.push(` ${paint.brand.data(profile.cwdLabel)}`);
|
|
316
|
+
out.push('');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
out.push(paint.text.dim(' command'));
|
|
320
|
+
if (profile.script?.body) {
|
|
321
|
+
out.push(` ${paint.text.primary(profile.script.invocation || profile.command.split('\n')[0] || profile.command)}`);
|
|
322
|
+
out.push('');
|
|
323
|
+
out.push(paint.text.dim(' script'));
|
|
324
|
+
out.push(numbered(profile.script.body, 1, { maxLines: MAX_SHELL_DETAIL_LINES }));
|
|
325
|
+
} else {
|
|
326
|
+
out.push(clip(profile.command, paint.text.primary, { maxLines: MAX_SHELL_DETAIL_LINES }));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (stdout) {
|
|
330
|
+
if (out.length) out.push('');
|
|
331
|
+
out.push(paint.text.dim(' stdout'));
|
|
332
|
+
out.push(clip(stdout, paint.text.primary, { maxLines: MAX_DETAIL_LINES }));
|
|
333
|
+
}
|
|
334
|
+
if (stderr) {
|
|
335
|
+
if (out.length) out.push('');
|
|
336
|
+
out.push(paint.state.warn(' stderr'));
|
|
337
|
+
out.push(clip(stderr, paint.state.danger, { maxLines: MAX_DETAIL_LINES }));
|
|
338
|
+
}
|
|
339
|
+
return out.length ? out.join('\n') : paint.text.dim(' (no output)');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function detailValidator(card) {
|
|
343
|
+
return detailShell(card);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Sub-agent output ────────────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
function detailPlan(card) {
|
|
349
|
+
const text = String(card.result?.output ?? card.result?.plan ?? '');
|
|
350
|
+
if (!text.trim()) return paint.text.dim(' (no plan)');
|
|
351
|
+
// Number list items, highlight headers.
|
|
352
|
+
return text.split('\n').slice(0, MAX_DETAIL_LINES).map(line => {
|
|
353
|
+
if (/^\s*\d+\./.test(line)) return ` ${paint.brand.accent(line.trim())}`;
|
|
354
|
+
if (/^#+\s/.test(line)) return ` ${paint.bold(paint.brand.primary(line.trim()))}`;
|
|
355
|
+
return ` ${paint.text.primary(line)}`;
|
|
356
|
+
}).join('\n');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function detailAgent(card) {
|
|
360
|
+
const args = card.args || {};
|
|
361
|
+
const prompt = args.prompt || args.task || args.query || args.description || args.instruction || '';
|
|
362
|
+
const out = [];
|
|
363
|
+
const meta = [
|
|
364
|
+
args.subagent_type ? `type ${args.subagent_type}` : '',
|
|
365
|
+
args.agent || args.name || '',
|
|
366
|
+
Array.isArray(args.allowed_tools) && args.allowed_tools.length
|
|
367
|
+
? `${args.allowed_tools.length} allowed tools`
|
|
368
|
+
: '',
|
|
369
|
+
].filter(Boolean).join(' · ');
|
|
370
|
+
if (meta) out.push(` ${paint.text.dim(meta)}`);
|
|
371
|
+
if (prompt) {
|
|
372
|
+
out.push(paint.text.dim(' prompt'));
|
|
373
|
+
out.push(clip(prompt, paint.text.primary, { maxLines: MAX_DETAIL_LINES }));
|
|
374
|
+
}
|
|
375
|
+
const result = String(card.result?.output ?? card.result?.output_preview ?? '');
|
|
376
|
+
if (result) {
|
|
377
|
+
if (out.length) out.push('');
|
|
378
|
+
out.push(paint.text.dim(' result'));
|
|
379
|
+
out.push(clip(result));
|
|
380
|
+
}
|
|
381
|
+
return out.length ? out.join('\n') : detailGenericOutput(card);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function detailSubAgentTools(card) {
|
|
385
|
+
const entries = Array.isArray(card.result?.tools) ? card.result.tools : [];
|
|
386
|
+
if (!entries.length) return paint.text.dim(' (no folded tool calls)');
|
|
387
|
+
|
|
388
|
+
const out = [];
|
|
389
|
+
entries.forEach((entry, index) => {
|
|
390
|
+
const tool = entry.tool || 'tool';
|
|
391
|
+
const args = entry.args || {};
|
|
392
|
+
const summary = entry.summary || toolDisplaySummary(tool, args);
|
|
393
|
+
const outcome = entry.outcome ? ` ${paint.text.dim('—')} ${paint.text.muted(entry.outcome)}` : '';
|
|
394
|
+
const duration = entry.durationMs != null ? ` ${paint.text.dim('· ' + formatDuration(entry.durationMs))}` : '';
|
|
395
|
+
out.push(` ${paint.text.dim(`${index + 1}.`)} ${icon(tool)} ${toolDisplayLabel(tool)}${summary ? ` ${paint.text.muted(summary)}` : ''}${outcome}${duration}`);
|
|
396
|
+
|
|
397
|
+
const child = {
|
|
398
|
+
tool,
|
|
399
|
+
args,
|
|
400
|
+
result: entry.result || null,
|
|
401
|
+
durationMs: entry.durationMs ?? null,
|
|
402
|
+
};
|
|
403
|
+
const detail = renderBody(child);
|
|
404
|
+
if (detail) out.push(indentBlock(detail, ' '));
|
|
405
|
+
});
|
|
406
|
+
return out.join('\n');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function detailGenericOutput(card) {
|
|
410
|
+
const out = String(card.result?.output ?? card.result?.output_preview ?? '');
|
|
411
|
+
return clip(out);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
function numbered(text, start, { maxLines = MAX_DETAIL_LINES } = {}) {
|
|
417
|
+
const lines = String(text).split('\n');
|
|
418
|
+
const total = lines.length;
|
|
419
|
+
const width = String(start + total - 1).length;
|
|
420
|
+
return lines.slice(0, maxLines).map((line, i) => {
|
|
421
|
+
const n = String(start + i).padStart(width);
|
|
422
|
+
return ` ${paint.text.dim(n)} ${paint.text.primary(line.slice(0, MAX_LINE_WIDTH))}`;
|
|
423
|
+
}).join('\n') + (total > maxLines
|
|
424
|
+
? `\n ${paint.text.dim(`… ${total - maxLines} more line(s)`)}`
|
|
425
|
+
: '');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function clip(text, painter = paint.text.primary, { maxLines = MAX_DETAIL_LINES } = {}) {
|
|
429
|
+
if (!text) return paint.text.dim(' (empty)');
|
|
430
|
+
const lines = String(text).split('\n');
|
|
431
|
+
const head = lines.slice(0, maxLines).map(l => ` ${painter(l.slice(0, MAX_LINE_WIDTH))}`);
|
|
432
|
+
if (lines.length > maxLines) {
|
|
433
|
+
head.push(` ${paint.text.dim(`… ${lines.length - maxLines} more line(s)`)}`);
|
|
434
|
+
}
|
|
435
|
+
return head.join('\n');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function indentBlock(text, prefix) {
|
|
439
|
+
return String(text || '')
|
|
440
|
+
.split('\n')
|
|
441
|
+
.map(line => `${prefix}${line.trimStart()}`)
|
|
442
|
+
.join('\n');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function renderDiff(text) {
|
|
446
|
+
return text.split('\n').slice(0, MAX_DETAIL_LINES).map(line => {
|
|
447
|
+
if (line.startsWith('+++') || line.startsWith('---')) return ` ${paint.bold(paint.text.muted(line))}`;
|
|
448
|
+
if (line.startsWith('@@')) return ` ${paint.brand.data(line)}`;
|
|
449
|
+
if (line.startsWith('+')) return ` ${paint.state.success(line)}`;
|
|
450
|
+
if (line.startsWith('-')) return ` ${paint.state.danger(line)}`;
|
|
451
|
+
return ` ${paint.text.dim(line)}`;
|
|
452
|
+
}).join('\n');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function unifiedForFileDiff(diff) {
|
|
456
|
+
if (!diff) return '';
|
|
457
|
+
if (diff.unified) return String(diff.unified);
|
|
458
|
+
const hunks = Array.isArray(diff.hunks) ? diff.hunks : [];
|
|
459
|
+
if (!hunks.length) return '';
|
|
460
|
+
const file = diff.relative_path || diff.path || 'file';
|
|
461
|
+
const out = [`--- a/${file}`, `+++ b/${file}`];
|
|
462
|
+
for (const hunk of hunks) {
|
|
463
|
+
const lines = hunkLines(hunk);
|
|
464
|
+
if (!lines.length) continue;
|
|
465
|
+
const oldCount = hunk.old_count ?? hunk.old_lines ?? countLinesForSide(lines, 'old');
|
|
466
|
+
const newCount = hunk.new_count ?? hunk.new_lines ?? countLinesForSide(lines, 'new');
|
|
467
|
+
out.push(`@@ -${hunk.old_start ?? 1},${oldCount} +${hunk.new_start ?? 1},${newCount} @@`);
|
|
468
|
+
for (const line of lines) out.push(toUnifiedLine(line));
|
|
469
|
+
}
|
|
470
|
+
return out.length > 2 ? out.join('\n') : '';
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function hunkLines(hunk = {}) {
|
|
474
|
+
if (Array.isArray(hunk.lines)) return hunk.lines;
|
|
475
|
+
if (typeof hunk.body !== 'string') return [];
|
|
476
|
+
return hunk.body.replace(/\r\n?/g, '\n').split('\n').filter(line => line && !line.startsWith('@@'));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function toUnifiedLine(line) {
|
|
480
|
+
if (typeof line === 'string') {
|
|
481
|
+
if (line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) return line;
|
|
482
|
+
return ` ${line}`;
|
|
483
|
+
}
|
|
484
|
+
const type = String(line?.type || '').toLowerCase();
|
|
485
|
+
const text = String(line?.text ?? line?.content ?? '');
|
|
486
|
+
if (type === 'add' || type === 'added') return `+${text}`;
|
|
487
|
+
if (type === 'remove' || type === 'removed' || type === 'delete') return `-${text}`;
|
|
488
|
+
return ` ${text}`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function countLinesForSide(lines, side) {
|
|
492
|
+
return lines.filter(line => {
|
|
493
|
+
const type = typeof line === 'string'
|
|
494
|
+
? (line.startsWith('+') ? 'add' : line.startsWith('-') ? 'remove' : 'context')
|
|
495
|
+
: String(line?.type || 'context').toLowerCase();
|
|
496
|
+
if (side === 'old') return type !== 'add' && type !== 'added';
|
|
497
|
+
return type !== 'remove' && type !== 'removed' && type !== 'delete';
|
|
498
|
+
}).length;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function formatDuration(ms) {
|
|
502
|
+
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
503
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
504
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { paint } from './palette.mjs';
|
|
2
|
+
|
|
3
|
+
function tonePaint(tone) {
|
|
4
|
+
return tone === 'user' ? paint.brand.accent : paint.brand.primary;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function transcriptHeader(label, { tone = 'assistant' } = {}) {
|
|
8
|
+
const p = tonePaint(tone);
|
|
9
|
+
return `${paint.bold(p(label))} ${p('›')}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function transcriptLine(line = '', { tone = 'assistant' } = {}) {
|
|
13
|
+
return ` ${line}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function transcriptLines(text, opts = {}) {
|
|
17
|
+
return String(text ?? '')
|
|
18
|
+
.split('\n')
|
|
19
|
+
.map(line => transcriptLine(line, opts));
|
|
20
|
+
}
|