mixdog 0.9.0 → 0.9.2
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/package.json +10 -3
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/session-ingest-smoke.mjs +2 -2
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +306 -66
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +4 -2
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +4 -2
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/mixdog-debug.cjs +0 -22
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +34 -56
- package/src/mixdog-session-runtime.mjs +710 -319
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +12 -4
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +7 -8
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +7 -0
- package/src/rules/shared/01-tool.md +17 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
- package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
- package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +441 -1254
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +54 -3
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/session-discovery.mjs +0 -4
- package/src/runtime/channels/lib/telegram-format.mjs +283 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +187 -43
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
- package/src/runtime/memory/lib/memory.mjs +101 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
- package/src/runtime/memory/lib/session-ingest.mjs +116 -7
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +6 -3
- package/src/runtime/search/index.mjs +2 -7
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +0 -1
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/child-spawn-gate.mjs +0 -6
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +129 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/standalone/agent-tool.mjs +255 -109
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +8 -291
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/provider-admin.mjs +11 -0
- package/src/standalone/seeds.mjs +1 -11
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2137 -750
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +146 -9
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +177 -100
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +312 -40
- package/src/tui/components/tool-output-format.test.mjs +180 -1
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +7324 -2393
- package/src/tui/engine.mjs +287 -126
- package/src/tui/index.jsx +117 -7
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +453 -0
- package/src/tui/markdown/format-token.mjs +354 -142
- package/src/tui/markdown/format-token.test.mjs +155 -17
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +0 -11
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -647
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +81 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +26 -27
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +39 -12
- package/src/workflows/sequential/WORKFLOW.md +46 -0
- package/src/workflows/solo/WORKFLOW.md +7 -0
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +154 -20
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -5,19 +5,20 @@
|
|
|
5
5
|
* - chalk is forced to truecolor (level 3) so colors render regardless of the
|
|
6
6
|
* ambient TTY detection (we control the surface).
|
|
7
7
|
* - The `permission`/code accent and blockquote bar come from our theme.mjs.
|
|
8
|
-
* - `code` (fenced)
|
|
9
|
-
*
|
|
8
|
+
* - `code` (fenced) uses cli-highlight + theme syntax palette; `codespan`
|
|
9
|
+
* (inline) gets the accent color.
|
|
10
10
|
* - `table` is NOT handled here — the React component (MarkdownTable.jsx)
|
|
11
11
|
* renders tables with proper ink Box layout (hybrid split).
|
|
12
12
|
* - Hyperlinks/issue-ref linkify are dropped (no OSC-8 dependency); link text
|
|
13
13
|
* is shown plainly with its URL.
|
|
14
14
|
*/
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
15
16
|
import { Chalk } from 'chalk';
|
|
16
17
|
import stripAnsi from 'strip-ansi';
|
|
17
|
-
import stringWidth from 'string-width';
|
|
18
18
|
import wrapAnsi from 'wrap-ansi';
|
|
19
19
|
import { theme, getThemeVersion } from '../theme.mjs';
|
|
20
20
|
import { BLOCKQUOTE_BAR, HR_LINE } from '../figures.mjs';
|
|
21
|
+
import { displayWidth } from '../display-width.mjs';
|
|
21
22
|
|
|
22
23
|
// Force truecolor so chalk emits 24-bit SGR even when the ambient level is 0.
|
|
23
24
|
// ink's <Text> passes these escapes through verbatim.
|
|
@@ -26,26 +27,12 @@ const chalk = new Chalk({ level: 3 });
|
|
|
26
27
|
// Use \n unconditionally (os.EOL's \r breaks segment mapping).
|
|
27
28
|
const EOL = '\n';
|
|
28
29
|
|
|
29
|
-
/** Parse an `rgb(r,g,b)` theme string into a chalk colorizer. */
|
|
30
30
|
function rgbColor(str) {
|
|
31
31
|
const m = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(String(str || ''));
|
|
32
32
|
if (!m) return (s) => s;
|
|
33
33
|
return chalk.rgb(Number(m[1]), Number(m[2]), Number(m[3]));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* Parse an `rgb(r,g,b)` theme string into a TRUECOLOR BACKGROUND wrapper. Emits
|
|
38
|
-
* `48;2;R;G;B` … `49` (bg reset) around the string so AnsiText (case 48) maps it
|
|
39
|
-
* to an ink backgroundColor, giving a code line a tinted band. Returns identity
|
|
40
|
-
* when the string is not a valid rgb() so a missing key never corrupts output.
|
|
41
|
-
*/
|
|
42
|
-
function rgbBg(str) {
|
|
43
|
-
const m = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(String(str || ''));
|
|
44
|
-
if (!m) return (s) => s;
|
|
45
|
-
const open = `\x1b[48;2;${+m[1]};${+m[2]};${+m[3]}m`;
|
|
46
|
-
return (s) => `${open}${s}\x1b[49m`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
36
|
// Colorizers are derived from the active theme's md* keys. They are cached and
|
|
50
37
|
// rebuilt only when the theme version changes, so a `/theme` switch takes
|
|
51
38
|
// effect on the next render without recomputing a chalk fn per token.
|
|
@@ -80,8 +67,6 @@ export function extraColorizers() {
|
|
|
80
67
|
fenceBorder: rgbColor(theme.mdCodeBlockBorder ?? theme.mdHr),
|
|
81
68
|
link: rgbColor(theme.mdLink ?? theme.code),
|
|
82
69
|
linkText: rgbColor(theme.mdLinkText ?? theme.mdCode),
|
|
83
|
-
strong: rgbColor(theme.mdStrong ?? theme.mdHeading),
|
|
84
|
-
emph: rgbColor(theme.mdEmph ?? theme.mdHeading),
|
|
85
70
|
// diff/patch
|
|
86
71
|
diffAdded: rgbColor(theme.mdDiffAdded ?? theme.success),
|
|
87
72
|
diffRemoved: rgbColor(theme.mdDiffRemoved ?? theme.error),
|
|
@@ -99,8 +84,6 @@ export function extraColorizers() {
|
|
|
99
84
|
synOperator: rgbColor(theme.syntaxOperator ?? theme.mdCodeBlock),
|
|
100
85
|
synPunct: rgbColor(theme.syntaxPunctuation ?? theme.mdCodeBlock),
|
|
101
86
|
body: fallbackBody,
|
|
102
|
-
// Truecolor background band for fenced code blocks (per-line wrap).
|
|
103
|
-
codeBg: rgbBg(theme.mdCodeBlockBg ?? theme.background),
|
|
104
87
|
};
|
|
105
88
|
return _extra;
|
|
106
89
|
}
|
|
@@ -124,14 +107,65 @@ function decodeEntities(s) {
|
|
|
124
107
|
}
|
|
125
108
|
|
|
126
109
|
// ── Fenced code block rendering ─────────────────────────────────────────────
|
|
127
|
-
//
|
|
128
|
-
// Diff/patch languages
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
// languages fall back to
|
|
132
|
-
const CODE_BLOCK_INDENT = ' ';
|
|
110
|
+
// Gutter-indented, syntax-highlighted body with NO background band and no ```
|
|
111
|
+
// fences (codex/claude-code convention). Diff/patch languages (and bodies that
|
|
112
|
+
// look like unified diffs) keep the diff highlighter; other languages use
|
|
113
|
+
// cli-highlight (highlight.js) themed from our syntax* palette; unknown
|
|
114
|
+
// languages fall back to flat `mdCodeBlock` body color.
|
|
133
115
|
const DIFF_LANGS = new Set(['diff', 'patch', 'udiff', 'git-diff', 'gitdiff']);
|
|
134
116
|
|
|
117
|
+
// Fixed tab size for fenced-code rendering. The exact value is cosmetic; what
|
|
118
|
+
// matters for correctness is that NO raw \t byte survives into the rendered
|
|
119
|
+
// string. string-width counts a tab as ZERO cells ("Tabs are ignored by
|
|
120
|
+
// design"; \p{Control} is a zero-width cluster), so our wrap/row math believes
|
|
121
|
+
// a tab-bearing code line is narrower than it is. ink clips the transcript
|
|
122
|
+
// viewport at the measured height, but the terminal then EXPANDS the surviving
|
|
123
|
+
// tab to the next tab stop — adding physical columns/rows AFTER the clip, which
|
|
124
|
+
// makes a code line near the bottom edge bleed THROUGH into the prompt box on
|
|
125
|
+
// scroll-up. Expanding tabs to literal spaces here makes the measurement and
|
|
126
|
+
// the terminal agree, so the clip holds.
|
|
127
|
+
const CODE_TAB_SIZE = 2;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Normalize raw fenced-code source for terminal rendering:
|
|
131
|
+
* - CRLF / lone CR → LF (so wrap sees real logical lines);
|
|
132
|
+
* - TAB → spaces, column-aware to the next CODE_TAB_SIZE stop, so width math
|
|
133
|
+
* matches what the terminal draws and the viewport clip is not bypassed;
|
|
134
|
+
* - other C0 control chars (and DEL) except LF → single space, so stray
|
|
135
|
+
* control bytes cannot trigger terminal-side cursor moves / extra rows.
|
|
136
|
+
*/
|
|
137
|
+
function normalizeCodeText(text) {
|
|
138
|
+
const input = String(text ?? '');
|
|
139
|
+
if (input.length === 0) return input;
|
|
140
|
+
const normalizedEol = input.replace(/\r\n?/g, '\n');
|
|
141
|
+
let out = '';
|
|
142
|
+
let col = 0;
|
|
143
|
+
for (const ch of normalizedEol) {
|
|
144
|
+
if (ch === '\n') {
|
|
145
|
+
out += ch;
|
|
146
|
+
col = 0;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (ch === '\t') {
|
|
150
|
+
const advance = CODE_TAB_SIZE - (col % CODE_TAB_SIZE);
|
|
151
|
+
out += ' '.repeat(advance);
|
|
152
|
+
col += advance;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const cp = ch.codePointAt(0);
|
|
156
|
+
if (cp != null && (cp <= 0x1f || cp === 0x7f)) {
|
|
157
|
+
// Remaining C0 control / DEL: replace with a space so it cannot move the
|
|
158
|
+
// terminal cursor after ink has already accounted for the row.
|
|
159
|
+
out += ' ';
|
|
160
|
+
col += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
out += ch;
|
|
164
|
+
col += displayWidth(ch);
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
135
169
|
/** Wrap text to width, ANSI-aware (lockstep with table-layout hard wrap). */
|
|
136
170
|
function wrapTextToWidth(text, width, options) {
|
|
137
171
|
if (width <= 0) return [text];
|
|
@@ -153,10 +187,10 @@ function hardWrapAnsiLines(text, width) {
|
|
|
153
187
|
const out = [];
|
|
154
188
|
for (const softLine of wrapTextToWidth(input, max, { hard: true })) {
|
|
155
189
|
let rest = softLine;
|
|
156
|
-
while (rest.length > 0 &&
|
|
190
|
+
while (rest.length > 0 && displayWidth(rest) > max) {
|
|
157
191
|
let take = 1;
|
|
158
192
|
for (let i = 1; i <= rest.length; i++) {
|
|
159
|
-
if (
|
|
193
|
+
if (displayWidth(rest.slice(0, i)) <= max) take = i;
|
|
160
194
|
else break;
|
|
161
195
|
}
|
|
162
196
|
out.push(rest.slice(0, take));
|
|
@@ -167,24 +201,17 @@ function hardWrapAnsiLines(text, width) {
|
|
|
167
201
|
return out.length > 0 ? out : [''];
|
|
168
202
|
}
|
|
169
203
|
|
|
170
|
-
/** Wrap one logical code line (ANSI content
|
|
171
|
-
function
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
const segments = hardWrapAnsiLines(ansiContent, contentMax);
|
|
175
|
-
return segments.map((seg) => `${CODE_BLOCK_INDENT}${seg}`);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** Tint code lines without padding to terminal width (no trailing bg band). */
|
|
179
|
-
function tintCodeLines(lines, codeBg) {
|
|
180
|
-
return lines.map((line) => codeBg(line)).join(EOL);
|
|
204
|
+
/** Wrap one logical code line (ANSI content) to max visible width. */
|
|
205
|
+
function wrapCodeLine(ansiContent, maxLineWidth) {
|
|
206
|
+
const contentMax = Math.max(1, maxLineWidth);
|
|
207
|
+
return hardWrapAnsiLines(ansiContent, contentMax);
|
|
181
208
|
}
|
|
182
209
|
|
|
183
210
|
/** Reduce render width by a visible prefix (list marker, blockquote bar, etc.). */
|
|
184
211
|
function contentWidthAfterPrefix(width, prefix) {
|
|
185
212
|
const base = Number(width) || 0;
|
|
186
213
|
if (base <= 0) return 0;
|
|
187
|
-
return Math.max(8, base -
|
|
214
|
+
return Math.max(8, base - displayWidth(String(prefix ?? '')));
|
|
188
215
|
}
|
|
189
216
|
|
|
190
217
|
/** Normalize a fenced info-string to a bare lowercase language token. */
|
|
@@ -266,34 +293,81 @@ function colorizeDiffStatTrailer(line, c) {
|
|
|
266
293
|
.replace(/(\d+)(\s+deletions?\(-\))/g, (_, n, rest) => `${c.diffRemoved(n)}${c.diffContext(rest)}`);
|
|
267
294
|
}
|
|
268
295
|
|
|
269
|
-
function
|
|
296
|
+
function collectDiffBodyLines(text, c, bandWidth) {
|
|
270
297
|
const lines = [];
|
|
271
298
|
for (const line of String(text ?? '').split(EOL)) {
|
|
272
299
|
const colored = colorizeDiffLine(line, c);
|
|
273
|
-
lines.push(...
|
|
300
|
+
lines.push(...wrapCodeLine(colored, bandWidth));
|
|
301
|
+
}
|
|
302
|
+
return lines;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── cli-highlight (highlight.js) ────────────────────────────────────────────
|
|
306
|
+
const requireCliHighlight = createRequire(import.meta.url);
|
|
307
|
+
/** @type {{ highlight: typeof import('cli-highlight').highlight, supportsLanguage: typeof import('cli-highlight').supportsLanguage } | null} */
|
|
308
|
+
let cliHighlightModule = null;
|
|
309
|
+
|
|
310
|
+
function getCliHighlight() {
|
|
311
|
+
if (cliHighlightModule) return cliHighlightModule;
|
|
312
|
+
try {
|
|
313
|
+
const clihl = requireCliHighlight('cli-highlight');
|
|
314
|
+
cliHighlightModule = {
|
|
315
|
+
highlight: clihl.highlight,
|
|
316
|
+
supportsLanguage: clihl.supportsLanguage,
|
|
317
|
+
};
|
|
318
|
+
} catch {
|
|
319
|
+
cliHighlightModule = {
|
|
320
|
+
highlight: null,
|
|
321
|
+
supportsLanguage: () => false,
|
|
322
|
+
};
|
|
274
323
|
}
|
|
275
|
-
return
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const
|
|
280
|
-
js:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
324
|
+
return cliHighlightModule;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Internal family → highlight.js language id (when alias alone is insufficient). */
|
|
328
|
+
const FAMILY_TO_HLJS = {
|
|
329
|
+
js: 'javascript',
|
|
330
|
+
ts: 'typescript',
|
|
331
|
+
tsx: 'tsx',
|
|
332
|
+
jsx: 'jsx',
|
|
333
|
+
json: 'json',
|
|
334
|
+
sh: 'bash',
|
|
335
|
+
py: 'python',
|
|
336
|
+
css: 'css',
|
|
337
|
+
html: 'xml',
|
|
338
|
+
go: 'go',
|
|
339
|
+
rust: 'rust',
|
|
340
|
+
java: 'java',
|
|
341
|
+
c: 'cpp',
|
|
342
|
+
ruby: 'ruby',
|
|
343
|
+
sql: 'sql',
|
|
344
|
+
yaml: 'yaml',
|
|
345
|
+
toml: 'ini',
|
|
346
|
+
kotlin: 'kotlin',
|
|
347
|
+
swift: 'swift',
|
|
348
|
+
php: 'php',
|
|
349
|
+
csharp: 'csharp',
|
|
350
|
+
dockerfile: 'dockerfile',
|
|
351
|
+
protobuf: 'protobuf',
|
|
352
|
+
scala: 'scala',
|
|
353
|
+
dart: 'dart',
|
|
354
|
+
lua: 'lua',
|
|
355
|
+
perl: 'perl',
|
|
356
|
+
r: 'r',
|
|
357
|
+
objc: 'objectivec',
|
|
358
|
+
powershell: 'powershell',
|
|
359
|
+
makefile: 'makefile',
|
|
360
|
+
nginx: 'nginx',
|
|
361
|
+
ini: 'ini',
|
|
362
|
+
vim: 'vim',
|
|
363
|
+
haskell: 'haskell',
|
|
364
|
+
elixir: 'elixir',
|
|
365
|
+
clojure: 'clojure',
|
|
292
366
|
};
|
|
293
367
|
|
|
294
368
|
export const LANG_FAMILY = {
|
|
295
369
|
js: 'js', javascript: 'js', mjs: 'js', cjs: 'js',
|
|
296
|
-
ts: '
|
|
370
|
+
ts: 'ts', typescript: 'ts', jsx: 'jsx', tsx: 'tsx',
|
|
297
371
|
json: 'json', json5: 'json',
|
|
298
372
|
bash: 'sh', sh: 'sh', shell: 'sh', zsh: 'sh',
|
|
299
373
|
python: 'py', py: 'py',
|
|
@@ -308,96 +382,226 @@ export const LANG_FAMILY = {
|
|
|
308
382
|
sql: 'sql',
|
|
309
383
|
yaml: 'yaml', yml: 'yaml',
|
|
310
384
|
toml: 'toml',
|
|
385
|
+
kotlin: 'kotlin', kt: 'kotlin', kts: 'kotlin',
|
|
386
|
+
swift: 'swift',
|
|
387
|
+
php: 'php',
|
|
388
|
+
csharp: 'csharp', cs: 'csharp', 'c#': 'csharp',
|
|
389
|
+
dockerfile: 'dockerfile', docker: 'dockerfile',
|
|
390
|
+
graphql: 'graphql', gql: 'graphql',
|
|
391
|
+
protobuf: 'protobuf', proto: 'protobuf',
|
|
392
|
+
scala: 'scala',
|
|
393
|
+
dart: 'dart',
|
|
394
|
+
lua: 'lua',
|
|
395
|
+
perl: 'perl', pl: 'perl',
|
|
396
|
+
r: 'r', rl: 'r',
|
|
397
|
+
objc: 'objc', 'objective-c': 'objc', 'obj-c': 'objc',
|
|
398
|
+
powershell: 'powershell', ps1: 'powershell', ps: 'powershell', pwsh: 'powershell',
|
|
399
|
+
makefile: 'makefile', make: 'makefile',
|
|
400
|
+
nginx: 'nginx',
|
|
401
|
+
ini: 'ini',
|
|
402
|
+
vim: 'vim',
|
|
403
|
+
haskell: 'haskell', hs: 'haskell',
|
|
404
|
+
elixir: 'elixir', ex: 'elixir', exs: 'elixir',
|
|
405
|
+
clojure: 'clojure', clj: 'clojure',
|
|
311
406
|
};
|
|
312
407
|
|
|
313
|
-
|
|
314
|
-
const
|
|
408
|
+
const HIGHLIGHT_CACHE_MAX = 300;
|
|
409
|
+
const highlightCache = new Map();
|
|
315
410
|
|
|
316
|
-
/**
|
|
317
|
-
export function
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
411
|
+
/** @internal Test-only introspection for highlight LRU cache. */
|
|
412
|
+
export function _highlightCacheSizeForTests() {
|
|
413
|
+
return highlightCache.size;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let _hljsThemeVersion = -1;
|
|
417
|
+
let _hljsTheme = null;
|
|
418
|
+
|
|
419
|
+
/** Map highlight.js token classes to chalk truecolor fns from the active theme. */
|
|
420
|
+
function buildCliHighlightTheme(c) {
|
|
421
|
+
const plain = (s) => c.body(s);
|
|
422
|
+
return {
|
|
423
|
+
default: plain,
|
|
424
|
+
keyword: c.synKeyword,
|
|
425
|
+
built_in: c.synType,
|
|
426
|
+
type: c.synType,
|
|
427
|
+
literal: c.synKeyword,
|
|
428
|
+
number: c.synNumber,
|
|
429
|
+
regexp: c.synString,
|
|
430
|
+
string: c.synString,
|
|
431
|
+
subst: plain,
|
|
432
|
+
symbol: plain,
|
|
433
|
+
class: c.synType,
|
|
434
|
+
function: c.synFunction,
|
|
435
|
+
title: c.synFunction,
|
|
436
|
+
params: c.synVariable,
|
|
437
|
+
comment: c.synComment,
|
|
438
|
+
doctag: c.synComment,
|
|
439
|
+
meta: c.synComment,
|
|
440
|
+
section: c.synType,
|
|
441
|
+
tag: c.synPunct,
|
|
442
|
+
name: c.synFunction,
|
|
443
|
+
builtin: c.synType,
|
|
444
|
+
attr: c.synType,
|
|
445
|
+
attribute: c.synType,
|
|
446
|
+
variable: c.synVariable,
|
|
447
|
+
selector: c.synKeyword,
|
|
448
|
+
template: c.synVariable,
|
|
449
|
+
bullet: plain,
|
|
450
|
+
code: plain,
|
|
451
|
+
emphasis: plain,
|
|
452
|
+
strong: plain,
|
|
453
|
+
formula: plain,
|
|
454
|
+
link: plain,
|
|
455
|
+
quote: plain,
|
|
456
|
+
addition: c.synString,
|
|
457
|
+
deletion: c.synString,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function getCliHighlightTheme() {
|
|
462
|
+
const version = getThemeVersion();
|
|
463
|
+
if (_hljsTheme && _hljsThemeVersion === version) return _hljsTheme;
|
|
464
|
+
_hljsThemeVersion = version;
|
|
465
|
+
_hljsTheme = buildCliHighlightTheme(extraColorizers());
|
|
466
|
+
return _hljsTheme;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function hljsLanguageFromFamily(family) {
|
|
470
|
+
if (!family || family === 'md') return null;
|
|
471
|
+
const { supportsLanguage } = getCliHighlight();
|
|
472
|
+
const mapped = FAMILY_TO_HLJS[family];
|
|
473
|
+
if (mapped && supportsLanguage(mapped)) return mapped;
|
|
474
|
+
if (supportsLanguage(family)) return family;
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Resolve a fenced info-string to a highlight.js language id. */
|
|
479
|
+
function resolveHljsLanguage(lang) {
|
|
480
|
+
const normalized = normalizeLang(lang);
|
|
481
|
+
if (!normalized) return null;
|
|
482
|
+
const viaFamily = hljsLanguageFromFamily(LANG_FAMILY[normalized]);
|
|
483
|
+
if (viaFamily) return viaFamily;
|
|
484
|
+
const { supportsLanguage } = getCliHighlight();
|
|
485
|
+
return supportsLanguage(normalized) ? normalized : null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Highlight source with cli-highlight; returns null when language is unsupported
|
|
490
|
+
* or highlighting fails (caller falls back to flat body color).
|
|
491
|
+
*/
|
|
492
|
+
function highlightCodeText(text, hljsLang) {
|
|
493
|
+
const { highlight, supportsLanguage } = getCliHighlight();
|
|
494
|
+
if (!hljsLang || !supportsLanguage(hljsLang) || typeof highlight !== 'function') return null;
|
|
495
|
+
const src = String(text ?? '');
|
|
496
|
+
if (!src.trim()) return null;
|
|
497
|
+
if (!/[A-Za-z0-9]/.test(src)) return null;
|
|
498
|
+
const cacheKey = `${hljsLang}|${getThemeVersion()}|${src}`;
|
|
499
|
+
const cached = highlightCache.get(cacheKey);
|
|
500
|
+
if (cached !== undefined) {
|
|
501
|
+
highlightCache.delete(cacheKey);
|
|
502
|
+
highlightCache.set(cacheKey, cached);
|
|
503
|
+
return cached;
|
|
504
|
+
}
|
|
505
|
+
try {
|
|
506
|
+
const out = highlight(src, {
|
|
507
|
+
language: hljsLang,
|
|
508
|
+
theme: getCliHighlightTheme(),
|
|
509
|
+
ignoreIllegals: true,
|
|
510
|
+
});
|
|
511
|
+
if (highlightCache.size >= HIGHLIGHT_CACHE_MAX) {
|
|
512
|
+
const first = highlightCache.keys().next().value;
|
|
513
|
+
if (first !== undefined) highlightCache.delete(first);
|
|
350
514
|
}
|
|
351
|
-
|
|
515
|
+
highlightCache.set(cacheKey, out);
|
|
516
|
+
return out;
|
|
517
|
+
} catch {
|
|
518
|
+
return null;
|
|
352
519
|
}
|
|
353
|
-
if (last < line.length) out += c.body(line.slice(last));
|
|
354
|
-
return out;
|
|
355
520
|
}
|
|
356
521
|
|
|
357
|
-
|
|
522
|
+
/** Highlight a single line (tool expanded output); uses the same theme map. */
|
|
523
|
+
export function highlightCodeLine(line, family, c) {
|
|
524
|
+
const hljsLang = hljsLanguageFromFamily(family);
|
|
525
|
+
const highlighted = highlightCodeText(line, hljsLang);
|
|
526
|
+
if (highlighted != null) return highlighted;
|
|
527
|
+
return line ? c.body(line) : '';
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Highlight a multi-line block in one cli-highlight call; returns one ANSI string per line.
|
|
532
|
+
* On miss, each line is flat-colored with `c.body`.
|
|
533
|
+
*/
|
|
534
|
+
export function highlightCodeBlockToLines(text, family, c) {
|
|
535
|
+
const hljsLang = hljsLanguageFromFamily(family);
|
|
536
|
+
const raw = String(text ?? '');
|
|
537
|
+
const highlighted = hljsLang ? highlightCodeText(raw, hljsLang) : null;
|
|
538
|
+
if (highlighted != null) {
|
|
539
|
+
return highlighted.split(EOL);
|
|
540
|
+
}
|
|
541
|
+
return raw.split(EOL).map((line) => (line ? c.body(line) : ''));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function collectFlatBodyLines(text, codeBlock, bandWidth) {
|
|
358
545
|
const lines = [];
|
|
359
546
|
for (const line of String(text ?? '').split(EOL)) {
|
|
360
|
-
const colored =
|
|
361
|
-
lines.push(...
|
|
547
|
+
const colored = codeBlock(line);
|
|
548
|
+
lines.push(...wrapCodeLine(colored, bandWidth));
|
|
362
549
|
}
|
|
363
|
-
return
|
|
550
|
+
return lines;
|
|
364
551
|
}
|
|
365
552
|
|
|
553
|
+
function collectHighlightedBodyLines(text, hljsLang, bandWidth) {
|
|
554
|
+
const highlighted = highlightCodeText(text, hljsLang);
|
|
555
|
+
const { codeBlock } = colorizers();
|
|
556
|
+
const source = highlighted != null
|
|
557
|
+
? highlighted
|
|
558
|
+
: String(text ?? '').split(EOL).map(codeBlock).join(EOL);
|
|
559
|
+
const lines = [];
|
|
560
|
+
for (const line of source.split(EOL)) {
|
|
561
|
+
lines.push(...wrapCodeLine(line, bandWidth));
|
|
562
|
+
}
|
|
563
|
+
return lines;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** Left gutter that keeps code visually distinct from prose (no bg band). */
|
|
567
|
+
const CODE_GUTTER = ' ';
|
|
568
|
+
|
|
366
569
|
/**
|
|
367
|
-
* Render a fenced `code` token
|
|
368
|
-
*
|
|
570
|
+
* Render a fenced `code` token (codex/claude-code convention): an optional
|
|
571
|
+
* subtle language label row, then the wrapped, syntax/diff/flat-highlighted
|
|
572
|
+
* body. Every row is left-indented by a 2-col gutter. NO background band, no
|
|
573
|
+
* full-width padding, and no visible ``` fence lines.
|
|
369
574
|
*/
|
|
370
575
|
function renderCodeBlock(token, width = 0) {
|
|
371
576
|
const { codeBlock } = colorizers();
|
|
372
577
|
const c = extraColorizers();
|
|
373
578
|
const lang = normalizeLang(token.lang);
|
|
374
|
-
const text = decodeEntities(token.text ?? '');
|
|
375
|
-
const
|
|
579
|
+
const text = normalizeCodeText(decodeEntities(token.text ?? ''));
|
|
580
|
+
const renderWidth = Math.max(8, Number(width) || 80);
|
|
581
|
+
// Wrap content to the render width minus the left gutter so `gutter + content`
|
|
582
|
+
// never overruns the available width.
|
|
583
|
+
const contentWidth = Math.max(1, renderWidth - CODE_GUTTER.length);
|
|
376
584
|
|
|
377
|
-
let
|
|
585
|
+
let bodyLines;
|
|
378
586
|
if (DIFF_LANGS.has(lang) || (!lang && looksLikeUnifiedDiff(text))) {
|
|
379
|
-
|
|
587
|
+
bodyLines = collectDiffBodyLines(text, c, contentWidth);
|
|
380
588
|
} else {
|
|
589
|
+
const hljsLang = resolveHljsLanguage(lang);
|
|
381
590
|
const family = LANG_FAMILY[lang];
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// markdown highlighter); unknown langs fall back to the flat body color.
|
|
385
|
-
if (family && family !== 'md') {
|
|
386
|
-
body = renderHighlightedBody(text, family, c, bandWidth);
|
|
591
|
+
if (hljsLang && family !== 'md') {
|
|
592
|
+
bodyLines = collectHighlightedBodyLines(text, hljsLang, contentWidth);
|
|
387
593
|
} else {
|
|
388
|
-
|
|
389
|
-
const lines = [];
|
|
390
|
-
for (const line of text.split(EOL)) {
|
|
391
|
-
const colored = codeBlock(line);
|
|
392
|
-
lines.push(...wrapIndentedCodeLine(colored, bandWidth));
|
|
393
|
-
}
|
|
394
|
-
body = tintCodeLines(lines, c.codeBg);
|
|
594
|
+
bodyLines = collectFlatBodyLines(text, codeBlock, contentWidth);
|
|
395
595
|
}
|
|
396
596
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
597
|
+
// Indent every body row by the gutter (no bg band).
|
|
598
|
+
const indentedBody = bodyLines.map((line) => `${CODE_GUTTER}${line ?? ''}`);
|
|
599
|
+
// Language label: a subtle (syntax-comment colored) metadata row above the
|
|
600
|
+
// body. Its trimmed visible text is exactly the bare lang token, so consumers
|
|
601
|
+
// can detect the label by trimmed equality.
|
|
602
|
+
const langLine = lang ? `${CODE_GUTTER}${c.synComment(lang)}` : null;
|
|
603
|
+
const rows = [...(langLine ? [langLine] : []), ...indentedBody].join(EOL);
|
|
604
|
+
return `${rows}${rows ? EOL : ''}`;
|
|
401
605
|
}
|
|
402
606
|
|
|
403
607
|
function numberToLetter(n) {
|
|
@@ -445,41 +649,48 @@ function getListNumber(depth, orderedListNumber) {
|
|
|
445
649
|
* marked token switch (minus table / hyperlink deps).
|
|
446
650
|
*/
|
|
447
651
|
export function formatToken(token, listBaseIndent = 0, orderedListNumber = null, parent = null, width = 0, depth = 0) {
|
|
448
|
-
const { accent, codeBlock,
|
|
652
|
+
const { accent, codeBlock, hrLine } = colorizers();
|
|
449
653
|
const ex = extraColorizers();
|
|
450
654
|
switch (token.type) {
|
|
451
655
|
case 'blockquote': {
|
|
452
|
-
|
|
656
|
+
// Block structure carries no color (claude-code policy): a dim bar plus
|
|
657
|
+
// body-colored italic text. Color is reserved for inline codespan/link.
|
|
658
|
+
const bar = chalk.dim(BLOCKQUOTE_BAR);
|
|
453
659
|
const quotePrefix = `${BLOCKQUOTE_BAR} `;
|
|
454
660
|
const innerWidth = contentWidthAfterPrefix(width, quotePrefix);
|
|
455
661
|
const inner = (token.tokens ?? []).map((t) => formatToken(t, 0, null, null, innerWidth)).join('');
|
|
456
662
|
return inner
|
|
457
663
|
.split(EOL)
|
|
458
|
-
.map((line) =>
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
)
|
|
664
|
+
.map((line) => {
|
|
665
|
+
// Padded fenced-code blank rows are space-only; trim() would drop the quote bar.
|
|
666
|
+
if (displayWidth(stripAnsi(line)) === 0) return line;
|
|
667
|
+
return `${bar} ${chalk.italic(line)}`;
|
|
668
|
+
})
|
|
463
669
|
.join(EOL);
|
|
464
670
|
}
|
|
465
671
|
case 'code':
|
|
466
|
-
// Fenced block:
|
|
672
|
+
// Fenced block: flush-left wrapped body with syntax highlighting.
|
|
467
673
|
return renderCodeBlock(token, width);
|
|
468
674
|
case 'codespan':
|
|
469
|
-
// inline code
|
|
675
|
+
// inline code — accent color only (no background tint; a bg box behind
|
|
676
|
+
// inline spans reads as awkward against body text, matches claude-code's
|
|
677
|
+
// codespan = color-only treatment).
|
|
470
678
|
return accent(decodeEntities(token.text));
|
|
471
679
|
case 'em':
|
|
472
|
-
|
|
680
|
+
// Italic only — no color tint (matches codex/claude-code; a colored em
|
|
681
|
+
// clashes per-theme and reads loud against body prose).
|
|
682
|
+
return chalk.italic((token.tokens ?? []).map((t) => formatToken(t, 0, null, parent)).join(''));
|
|
473
683
|
case 'strong':
|
|
474
|
-
|
|
684
|
+
// Bold only — no color tint (stays body-colored, just heavier weight).
|
|
685
|
+
return chalk.bold((token.tokens ?? []).map((t) => formatToken(t, 0, null, parent)).join(''));
|
|
475
686
|
case 'heading':
|
|
476
687
|
switch (token.depth) {
|
|
477
688
|
case 1:
|
|
478
689
|
return (
|
|
479
|
-
chalk.bold.italic.underline(
|
|
690
|
+
chalk.bold.italic.underline((token.tokens ?? []).map((t) => formatToken(t)).join('')) + EOL + EOL
|
|
480
691
|
);
|
|
481
692
|
default: // h2+
|
|
482
|
-
return chalk.bold(
|
|
693
|
+
return chalk.bold((token.tokens ?? []).map((t) => formatToken(t)).join('')) + EOL + EOL;
|
|
483
694
|
}
|
|
484
695
|
case 'hr': {
|
|
485
696
|
// Span the available content width with a box-drawing rule. width is only
|
|
@@ -504,12 +715,12 @@ export function formatToken(token, listBaseIndent = 0, orderedListNumber = null,
|
|
|
504
715
|
const OSC8_OPEN = (url) => `\x1b]8;;${url}\x07`;
|
|
505
716
|
const OSC8_CLOSE = '\x1b]8;;\x07';
|
|
506
717
|
if (plain && plain !== href) {
|
|
507
|
-
const styledLabel = ex.linkText(
|
|
718
|
+
const styledLabel = ex.linkText(linkText);
|
|
508
719
|
return `${OSC8_OPEN(href)}${styledLabel}${OSC8_CLOSE}`;
|
|
509
720
|
}
|
|
510
721
|
// No distinct label (empty or equal to href): show the URL itself as the
|
|
511
722
|
// clickable, visible text.
|
|
512
|
-
return `${OSC8_OPEN(href)}${ex.link(
|
|
723
|
+
return `${OSC8_OPEN(href)}${ex.link(href)}${OSC8_CLOSE}`;
|
|
513
724
|
}
|
|
514
725
|
case 'list':
|
|
515
726
|
return token.items
|
|
@@ -598,7 +809,8 @@ function formatListItem(token, listBaseIndent, orderedListNumber, parent, depth
|
|
|
598
809
|
out += `${markerPrefix.trimEnd()}${EOL}`;
|
|
599
810
|
firstBlock = false;
|
|
600
811
|
}
|
|
601
|
-
|
|
812
|
+
// Pass outer `width` so the nested list subtracts only its own marker prefix once.
|
|
813
|
+
out += formatToken(child, nestedListIndent, null, token, width, depth + 1);
|
|
602
814
|
continue;
|
|
603
815
|
}
|
|
604
816
|
|