whale-code 6.4.0 → 6.5.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/bin/swagmanager-mcp.js +7 -0
- package/dist/cli/app.js +30 -2
- package/dist/cli/chat/ChatApp.d.ts +4 -4
- package/dist/cli/chat/ChatApp.js +114 -44
- package/dist/cli/chat/ChatInput.d.ts +13 -6
- package/dist/cli/chat/ChatInput.js +433 -89
- package/dist/cli/chat/MemoryManager.d.ts +15 -0
- package/dist/cli/chat/MemoryManager.js +61 -0
- package/dist/cli/chat/MessageList.d.ts +8 -0
- package/dist/cli/chat/MessageList.js +1 -1
- package/dist/cli/chat/NodeManager.d.ts +30 -0
- package/dist/cli/chat/NodeManager.js +89 -0
- package/dist/cli/chat/NodeSelector.d.ts +19 -0
- package/dist/cli/chat/NodeSelector.js +37 -0
- package/dist/cli/chat/PlanApproval.d.ts +17 -0
- package/dist/cli/chat/PlanApproval.js +82 -0
- package/dist/cli/chat/SessionManager.d.ts +16 -0
- package/dist/cli/chat/SessionManager.js +43 -0
- package/dist/cli/chat/SlashMenu.d.ts +38 -0
- package/dist/cli/chat/SlashMenu.js +208 -0
- package/dist/cli/chat/StatusBar.d.ts +16 -0
- package/dist/cli/chat/StatusBar.js +22 -0
- package/dist/cli/chat/ThemeSelector.d.ts +14 -0
- package/dist/cli/chat/ThemeSelector.js +29 -0
- package/dist/cli/chat/ToolIndicator.d.ts +8 -0
- package/dist/cli/chat/ToolIndicator.js +33 -9
- package/dist/cli/chat/hooks/useAgentLoop.d.ts +2 -1
- package/dist/cli/chat/hooks/useAgentLoop.js +22 -17
- package/dist/cli/chat/hooks/useSlashCommands.d.ts +19 -0
- package/dist/cli/chat/hooks/useSlashCommands.js +254 -15
- package/dist/cli/commands/config-cmd.js +4 -25
- package/dist/cli/commands/db.d.ts +13 -0
- package/dist/cli/commands/db.js +243 -0
- package/dist/cli/commands/doctor.js +6 -9
- package/dist/cli/commands/mcp.js +1 -20
- package/dist/cli/services/agent-events.d.ts +22 -1
- package/dist/cli/services/agent-events.js +9 -0
- package/dist/cli/services/agent-loop.js +66 -2
- package/dist/cli/services/agent-worker-base.js +21 -6
- package/dist/cli/services/api-retry.d.ts +25 -0
- package/dist/cli/services/api-retry.js +91 -0
- package/dist/cli/services/auth-service.d.ts +1 -1
- package/dist/cli/services/auth-service.js +40 -19
- package/dist/cli/services/background-processes.js +26 -2
- package/dist/cli/services/config-store.d.ts +13 -1
- package/dist/cli/services/config-store.js +116 -13
- package/dist/cli/services/format-server-response.js +12 -6
- package/dist/cli/services/ink-resize-fix.d.ts +18 -0
- package/dist/cli/services/ink-resize-fix.js +66 -0
- package/dist/cli/services/interactive-tools.d.ts +14 -0
- package/dist/cli/services/interactive-tools.js +47 -2
- package/dist/cli/services/keybinding-manager.js +1 -1
- package/dist/cli/services/local-tools.js +35 -2
- package/dist/cli/services/server-tools.js +175 -3
- package/dist/cli/services/subagent.js +15 -3
- package/dist/cli/services/system-prompt.js +5 -3
- package/dist/cli/services/task-decomposer.d.ts +35 -0
- package/dist/cli/services/task-decomposer.js +199 -0
- package/dist/cli/services/team-lead.d.ts +18 -0
- package/dist/cli/services/team-lead.js +80 -0
- package/dist/cli/services/teammate.js +5 -5
- package/dist/cli/services/telemetry.d.ts +8 -2
- package/dist/cli/services/telemetry.js +116 -92
- package/dist/cli/services/tools/agent-tools.d.ts +1 -0
- package/dist/cli/services/tools/agent-tools.js +50 -4
- package/dist/cli/services/tools/file-ops.d.ts +2 -0
- package/dist/cli/services/tools/file-ops.js +71 -19
- package/dist/cli/services/tools/shell-exec.js +22 -12
- package/dist/cli/shared/Theme.d.ts +1 -2
- package/dist/cli/shared/Theme.js +1 -1
- package/dist/cli/shared/WhaleBanner.d.ts +4 -1
- package/dist/cli/shared/WhaleBanner.js +12 -8
- package/dist/cli/shared/markdown.d.ts +5 -4
- package/dist/cli/shared/markdown.js +376 -334
- package/dist/cli/shared/theme-manager.d.ts +27 -0
- package/dist/cli/shared/theme-manager.js +178 -0
- package/dist/cli/shared/theme-presets.d.ts +16 -0
- package/dist/cli/shared/theme-presets.js +265 -0
- package/dist/index.js +0 -51
- package/dist/node/adapters/imessage.d.ts +10 -0
- package/dist/node/adapters/imessage.js +45 -6
- package/dist/node/cli.js +459 -8
- package/dist/node/config.d.ts +17 -0
- package/dist/node/gateway-client.d.ts +55 -0
- package/dist/node/gateway-client.js +201 -0
- package/dist/node/portal/clipboard.d.ts +28 -0
- package/dist/node/portal/clipboard.js +183 -0
- package/dist/node/portal/discovery.d.ts +29 -0
- package/dist/node/portal/discovery.js +61 -0
- package/dist/node/portal/forward.d.ts +30 -0
- package/dist/node/portal/forward.js +90 -0
- package/dist/node/portal/index.d.ts +47 -0
- package/dist/node/portal/index.js +250 -0
- package/dist/node/portal/multiplexer.d.ts +48 -0
- package/dist/node/portal/multiplexer.js +207 -0
- package/dist/node/portal/permissions.d.ts +36 -0
- package/dist/node/portal/permissions.js +131 -0
- package/dist/node/portal/protocol.d.ts +140 -0
- package/dist/node/portal/protocol.js +193 -0
- package/dist/node/portal/screen.d.ts +18 -0
- package/dist/node/portal/screen.js +93 -0
- package/dist/node/portal/session.d.ts +68 -0
- package/dist/node/portal/session.js +127 -0
- package/dist/node/portal/shell.d.ts +26 -0
- package/dist/node/portal/shell.js +142 -0
- package/dist/node/portal/stream.d.ts +43 -0
- package/dist/node/portal/stream.js +90 -0
- package/dist/node/portal/transfer.d.ts +33 -0
- package/dist/node/portal/transfer.js +231 -0
- package/dist/node/portal/ui.d.ts +16 -0
- package/dist/node/portal/ui.js +148 -0
- package/dist/node/remote-desktop/compile-helper.d.ts +13 -0
- package/dist/node/remote-desktop/compile-helper.js +73 -0
- package/dist/node/remote-desktop/index.d.ts +67 -0
- package/dist/node/remote-desktop/index.js +220 -0
- package/dist/node/remote-desktop/protocol.d.ts +96 -0
- package/dist/node/remote-desktop/protocol.js +67 -0
- package/dist/node/runtime.d.ts +8 -1
- package/dist/node/runtime.js +117 -9
- package/dist/server/handlers/__test-utils__/test-db.d.ts +25 -0
- package/dist/server/handlers/__test-utils__/test-db.js +128 -0
- package/dist/server/handlers/api-keys.js +26 -2
- package/dist/server/handlers/browser.d.ts +0 -4
- package/dist/server/handlers/browser.js +0 -46
- package/dist/server/handlers/catalog.js +37 -14
- package/dist/server/handlers/clickhouse.d.ts +10 -0
- package/dist/server/handlers/clickhouse.js +215 -0
- package/dist/server/handlers/comms.d.ts +308 -4
- package/dist/server/handlers/comms.js +444 -11
- package/dist/server/handlers/creations.js +1 -1
- package/dist/server/handlers/crm.d.ts +54 -8
- package/dist/server/handlers/crm.js +353 -68
- package/dist/server/handlers/embeddings.js +3 -3
- package/dist/server/handlers/enrichment.js +39 -55
- package/dist/server/handlers/inventory.js +1 -1
- package/dist/server/handlers/kali.d.ts +9 -1
- package/dist/server/handlers/kali.js +50 -1
- package/dist/server/handlers/media.d.ts +8 -0
- package/dist/server/handlers/media.js +902 -0
- package/dist/server/handlers/meta-ads.js +6 -3
- package/dist/server/handlers/nodes.d.ts +2 -0
- package/dist/server/handlers/nodes.js +331 -40
- package/dist/server/handlers/operations.d.ts +4 -6
- package/dist/server/handlers/operations.js +99 -38
- package/dist/server/handlers/platform.js +224 -107
- package/dist/server/handlers/remove-bg.d.ts +6 -0
- package/dist/server/handlers/remove-bg.js +96 -0
- package/dist/server/handlers/storefront.d.ts +6 -0
- package/dist/server/handlers/storefront.js +477 -0
- package/dist/server/handlers/supply-chain.js +21 -3
- package/dist/server/handlers/workflow-steps.js +87 -31
- package/dist/server/handlers/workflows.js +4 -1
- package/dist/server/index.js +334 -88
- package/dist/server/lib/clickhouse-buffer.d.ts +48 -0
- package/dist/server/lib/clickhouse-buffer.js +175 -0
- package/dist/server/lib/clickhouse-client.d.ts +112 -0
- package/dist/server/lib/clickhouse-client.js +141 -0
- package/dist/server/lib/coa-renderer.d.ts +91 -0
- package/dist/server/lib/coa-renderer.js +411 -0
- package/dist/server/lib/compaction-service.js +45 -1
- package/dist/server/lib/pdf-renderer.d.ts +143 -0
- package/dist/server/lib/pdf-renderer.js +867 -0
- package/dist/server/lib/react-pdf-layout.d.ts +40 -0
- package/dist/server/lib/react-pdf-layout.js +437 -0
- package/dist/server/lib/server-agent-loop.d.ts +2 -0
- package/dist/server/lib/server-agent-loop.js +61 -15
- package/dist/server/lib/server-subagent.d.ts +3 -0
- package/dist/server/lib/server-subagent.js +7 -4
- package/dist/server/lib/supabase-client.js +51 -3
- package/dist/server/lib/template-resolver.js +14 -4
- package/dist/server/lib/utils.js +15 -0
- package/dist/server/local-agent-gateway.d.ts +44 -0
- package/dist/server/local-agent-gateway.js +389 -49
- package/dist/server/providers/anthropic.js +12 -2
- package/dist/server/providers/gemini.js +17 -2
- package/dist/server/proxy-handlers.js +151 -0
- package/dist/server/tool-router.d.ts +2 -2
- package/dist/server/tool-router.js +25 -35
- package/dist/shared/agent-core.d.ts +5 -2
- package/dist/shared/agent-core.js +30 -4
- package/dist/shared/api-client.js +54 -3
- package/dist/shared/sse-parser.d.ts +1 -1
- package/dist/shared/sse-parser.js +5 -2
- package/dist/shared/tool-dispatch.js +1 -1
- package/package.json +16 -10
- package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +0 -11
- package/dist/server/handlers/__test-utils__/mock-supabase.js +0 -393
|
@@ -1,39 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Markdown rendering —
|
|
2
|
+
* Markdown rendering — polished terminal output with shiki syntax highlighting
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Uses marked + marked-terminal
|
|
4
|
+
* Uses shiki for VS Code-quality syntax highlighting.
|
|
5
|
+
* All chalk instances are rebuilt on theme switch via rebuildMarkdownRenderer().
|
|
6
|
+
* Uses marked + marked-terminal for markdown structure.
|
|
7
7
|
*/
|
|
8
8
|
import { Marked } from "marked";
|
|
9
9
|
import { markedTerminal } from "marked-terminal";
|
|
10
10
|
import chalk from "chalk";
|
|
11
|
-
import { createRequire } from "module";
|
|
12
11
|
import { colors } from "./Theme.js";
|
|
12
|
+
import { highlightCode, setRebuildCallback } from "./theme-manager.js";
|
|
13
13
|
import { diffWords } from "diff";
|
|
14
14
|
// Note: chalk.level is auto-detected by supports-color.
|
|
15
15
|
// Apple_Terminal → level 2 (256-color), iTerm.app v3+ → level 3 (24-bit).
|
|
16
16
|
// Do NOT force level 3 — Terminal.app can't render 24-bit codes (shows gray).
|
|
17
|
-
const require = createRequire(import.meta.url);
|
|
18
|
-
const { highlight } = require("cli-highlight");
|
|
19
17
|
// ============================================================================
|
|
20
|
-
//
|
|
18
|
+
// Palette factory — rebuilt on theme switch
|
|
21
19
|
// ============================================================================
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
function buildPalette() {
|
|
21
|
+
return {
|
|
22
|
+
systemBlue: chalk.hex(colors.brand),
|
|
23
|
+
systemCyan: chalk.hex(colors.info),
|
|
24
|
+
systemPink: chalk.hex(colors.pink),
|
|
25
|
+
systemPurple: chalk.hex(colors.purple),
|
|
26
|
+
systemIndigo: chalk.hex(colors.indigo),
|
|
27
|
+
systemGreen: chalk.hex(colors.success),
|
|
28
|
+
systemMint: chalk.hex(colors.mint),
|
|
29
|
+
systemRed: chalk.hex(colors.error),
|
|
30
|
+
systemOrange: chalk.hex(colors.warning),
|
|
31
|
+
text: chalk.hex(colors.text),
|
|
32
|
+
secondary: chalk.hex(colors.secondary),
|
|
33
|
+
tertiary: chalk.hex(colors.tertiary),
|
|
34
|
+
separator: chalk.hex(colors.separator),
|
|
35
|
+
lavender: chalk.hex(colors.lavender),
|
|
36
|
+
roseGold: chalk.hex(colors.roseGold),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
let p = buildPalette();
|
|
37
40
|
// ============================================================================
|
|
38
41
|
// Terminal width helpers — single source of truth for layout widths
|
|
39
42
|
// ============================================================================
|
|
@@ -49,20 +52,6 @@ export function toolContentWidth() {
|
|
|
49
52
|
return termWidth() - 6;
|
|
50
53
|
}
|
|
51
54
|
// ============================================================================
|
|
52
|
-
// console.warn suppression — safe in single-threaded Node.js
|
|
53
|
-
// ============================================================================
|
|
54
|
-
/** Suppress console.warn during a synchronous function call. */
|
|
55
|
-
function withSuppressedWarnings(fn) {
|
|
56
|
-
const orig = console.warn;
|
|
57
|
-
console.warn = () => { };
|
|
58
|
-
try {
|
|
59
|
-
return fn();
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
console.warn = orig;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// ============================================================================
|
|
66
55
|
// OSC 8 hyperlinks — only in terminals that support them
|
|
67
56
|
// ============================================================================
|
|
68
57
|
/** Detect if terminal supports OSC 8 clickable hyperlinks */
|
|
@@ -83,37 +72,37 @@ function hyperlink(url, text) {
|
|
|
83
72
|
// mailto: → clean email address, no protocol prefix, no OSC 8
|
|
84
73
|
if (url.startsWith("mailto:")) {
|
|
85
74
|
const email = text || url.slice(7);
|
|
86
|
-
return systemCyan(email);
|
|
75
|
+
return p.systemCyan(email);
|
|
87
76
|
}
|
|
88
77
|
// tel: → clean phone number
|
|
89
78
|
if (url.startsWith("tel:")) {
|
|
90
|
-
return systemCyan(text || url.slice(4));
|
|
79
|
+
return p.systemCyan(text || url.slice(4));
|
|
91
80
|
}
|
|
92
81
|
const display = text || url;
|
|
93
82
|
// OSC 8 clickable links — only where terminal supports them
|
|
94
83
|
if (supportsOsc8) {
|
|
95
|
-
return `\x1B]8;;${url}\x07${systemCyan.underline(display)}\x1B]8;;\x07`;
|
|
84
|
+
return `\x1B]8;;${url}\x07${p.systemCyan.underline(display)}\x1B]8;;\x07`;
|
|
96
85
|
}
|
|
97
86
|
// Fallback: colored underlined text (no escape sequences)
|
|
98
|
-
return systemCyan.underline(display);
|
|
87
|
+
return p.systemCyan.underline(display);
|
|
99
88
|
}
|
|
100
89
|
// ============================================================================
|
|
101
90
|
// Path helpers
|
|
102
91
|
// ============================================================================
|
|
103
92
|
/** Shorten a file path for code block headers */
|
|
104
93
|
function shortenPathForHeader(fullPath, maxLen = 40) {
|
|
105
|
-
let
|
|
94
|
+
let fp = fullPath;
|
|
106
95
|
const cwd = process.cwd();
|
|
107
96
|
const home = process.env.HOME || "";
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
else if (
|
|
111
|
-
|
|
112
|
-
else if (home &&
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
return
|
|
116
|
-
const parts =
|
|
97
|
+
if (fp.startsWith(cwd + "/"))
|
|
98
|
+
fp = fp.slice(cwd.length + 1);
|
|
99
|
+
else if (fp.startsWith(cwd))
|
|
100
|
+
fp = fp.slice(cwd.length);
|
|
101
|
+
else if (home && fp.startsWith(home))
|
|
102
|
+
fp = "~" + fp.slice(home.length);
|
|
103
|
+
if (fp.length <= maxLen)
|
|
104
|
+
return fp;
|
|
105
|
+
const parts = fp.split("/");
|
|
117
106
|
const file = parts.pop();
|
|
118
107
|
if (file.length >= maxLen - 4)
|
|
119
108
|
return "…/" + file.slice(-(maxLen - 4));
|
|
@@ -121,64 +110,6 @@ function shortenPathForHeader(fullPath, maxLen = 40) {
|
|
|
121
110
|
return parent ? "…/" + parent + "/" + file : "…/" + file;
|
|
122
111
|
}
|
|
123
112
|
// ============================================================================
|
|
124
|
-
// Syntax highlighting — purples / blues / pinks
|
|
125
|
-
// ============================================================================
|
|
126
|
-
const appleTheme = {
|
|
127
|
-
// Keywords — pink bold
|
|
128
|
-
keyword: systemPink.bold,
|
|
129
|
-
built_in: systemPurple,
|
|
130
|
-
type: systemCyan,
|
|
131
|
-
literal: systemIndigo,
|
|
132
|
-
number: systemMint,
|
|
133
|
-
regexp: systemPink,
|
|
134
|
-
// Strings — lavender
|
|
135
|
-
string: lavender,
|
|
136
|
-
subst: systemCyan,
|
|
137
|
-
symbol: systemPurple,
|
|
138
|
-
// Functions & classes — blue
|
|
139
|
-
class: systemCyan.bold,
|
|
140
|
-
function: systemBlue,
|
|
141
|
-
title: systemBlue.bold,
|
|
142
|
-
params: roseGold,
|
|
143
|
-
// Comments — tertiary italic
|
|
144
|
-
comment: tertiary.italic,
|
|
145
|
-
doctag: secondary.italic,
|
|
146
|
-
meta: systemIndigo,
|
|
147
|
-
"meta-keyword": systemPink,
|
|
148
|
-
"meta-string": lavender,
|
|
149
|
-
// Tags (HTML/JSX)
|
|
150
|
-
tag: systemPink,
|
|
151
|
-
name: systemCyan,
|
|
152
|
-
attr: systemPurple,
|
|
153
|
-
attribute: systemPurple,
|
|
154
|
-
// Variables & properties
|
|
155
|
-
variable: systemCyan,
|
|
156
|
-
property: systemBlue,
|
|
157
|
-
// Diff
|
|
158
|
-
addition: systemGreen,
|
|
159
|
-
deletion: systemRed,
|
|
160
|
-
// Lists & markup
|
|
161
|
-
bullet: systemPurple,
|
|
162
|
-
code: systemPink,
|
|
163
|
-
emphasis: chalk.italic,
|
|
164
|
-
strong: chalk.bold,
|
|
165
|
-
link: systemCyan.underline,
|
|
166
|
-
quote: secondary.italic,
|
|
167
|
-
// Selectors (CSS)
|
|
168
|
-
"selector-tag": systemPink,
|
|
169
|
-
"selector-id": systemBlue,
|
|
170
|
-
"selector-class": systemPurple,
|
|
171
|
-
"selector-pseudo": systemCyan,
|
|
172
|
-
"selector-attr": lavender,
|
|
173
|
-
// Template
|
|
174
|
-
"template-tag": systemPink,
|
|
175
|
-
"template-variable": systemCyan,
|
|
176
|
-
// JSON property names — purple
|
|
177
|
-
section: systemPurple,
|
|
178
|
-
// Fallback
|
|
179
|
-
default: (s) => s,
|
|
180
|
-
};
|
|
181
|
-
// ============================================================================
|
|
182
113
|
// Financial coloring
|
|
183
114
|
// ============================================================================
|
|
184
115
|
function colorizeFinancials(str) {
|
|
@@ -186,72 +117,150 @@ function colorizeFinancials(str) {
|
|
|
186
117
|
// NOTE: URL hyperlinking is handled by marked's GFM autolink + link/href handlers.
|
|
187
118
|
// Do NOT add URL patterns here — it causes links to render 3-5x.
|
|
188
119
|
// Negative dollar amounts → red (-$1,234.56) — require digit after $
|
|
189
|
-
.replace(/(-\$\d[\d,]*\.?\d*)/g, (m) => systemRed(m))
|
|
120
|
+
.replace(/(-\$\d[\d,]*\.?\d*)/g, (m) => p.systemRed(m))
|
|
190
121
|
// Positive dollar amounts → green ($1,234.56) — require digit after $
|
|
191
|
-
.replace(/((?:^|[^-])\$\d[\d,]*\.?\d*)/g, (m) => systemGreen(m))
|
|
122
|
+
.replace(/((?:^|[^-])\$\d[\d,]*\.?\d*)/g, (m) => p.systemGreen(m))
|
|
192
123
|
// Negative percentages → red
|
|
193
|
-
.replace(/(-\d+\.?\d*%)/g, (m) => systemRed(m))
|
|
124
|
+
.replace(/(-\d+\.?\d*%)/g, (m) => p.systemRed(m))
|
|
194
125
|
// Positive percentages → cyan
|
|
195
|
-
.replace(/((?:^|[^-])\d+\.?\d*%)/g, (m) => systemCyan(m))
|
|
126
|
+
.replace(/((?:^|[^-])\d+\.?\d*%)/g, (m) => p.systemCyan(m))
|
|
196
127
|
// Explicit positive → green
|
|
197
|
-
.replace(/(\+\$?[\d,]+\.?\d*)/g, (m) => systemGreen(m))
|
|
128
|
+
.replace(/(\+\$?[\d,]+\.?\d*)/g, (m) => p.systemGreen(m))
|
|
198
129
|
// Financial-specific words → green (no generic words like running/ready/started)
|
|
199
|
-
.replace(/\b(profit|revenue|gain|in stock|available)\b/gi, (m) => systemGreen(m))
|
|
130
|
+
.replace(/\b(profit|revenue|gain|in stock|available)\b/gi, (m) => p.systemGreen(m))
|
|
200
131
|
// Financial/status negatives → red
|
|
201
|
-
.replace(/\b(loss|deduction|deficit|expense|out of stock|low stock|overdue|expired|cancelled|failed|error|crashed|EADDRINUSE|ENOENT)\b/gi, (m) => systemRed(m));
|
|
132
|
+
.replace(/\b(loss|deduction|deficit|expense|out of stock|low stock|overdue|expired|cancelled|failed|error|crashed|EADDRINUSE|ENOENT)\b/gi, (m) => p.systemRed(m));
|
|
202
133
|
}
|
|
203
134
|
// ============================================================================
|
|
204
|
-
// Markdown renderer
|
|
135
|
+
// Markdown renderer factory — rebuilt on theme switch
|
|
205
136
|
// ============================================================================
|
|
206
|
-
|
|
207
|
-
const md = new Marked();
|
|
208
|
-
md.use(markedTerminal({
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
137
|
+
function buildMarkedInstance() {
|
|
138
|
+
const md = new Marked();
|
|
139
|
+
md.use(markedTerminal({
|
|
140
|
+
// Headings — bold blue
|
|
141
|
+
firstHeading: p.systemBlue.bold,
|
|
142
|
+
heading: p.systemBlue.bold,
|
|
143
|
+
// Inline
|
|
144
|
+
codespan: p.systemPink,
|
|
145
|
+
strong: p.text.bold,
|
|
146
|
+
em: p.lavender.italic,
|
|
147
|
+
// Blocks
|
|
148
|
+
blockquote: p.secondary.italic,
|
|
149
|
+
paragraph: (body) => colorizeFinancials(body),
|
|
150
|
+
hr: () => p.separator("─".repeat(50)),
|
|
151
|
+
// Links — OSC 8 clickable (single source of truth for URL rendering)
|
|
152
|
+
// NOTE: Only use `link` handler, NOT `href` — having both causes double-hyperlinking
|
|
153
|
+
link: (href, _title, text) => hyperlink(href, text !== href ? text : undefined),
|
|
154
|
+
// Lists — purple bullets, financial-aware
|
|
155
|
+
list: (body, ordered) => {
|
|
156
|
+
if (ordered) {
|
|
157
|
+
let n = 0;
|
|
158
|
+
return body.replace(/^\* /gm, () => {
|
|
159
|
+
n++;
|
|
160
|
+
return `${p.systemIndigo(String(n) + ".")} `;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return body.replace(/^\* /gm, `${p.systemPurple("●")} `);
|
|
164
|
+
},
|
|
165
|
+
listitem: (itemText) => {
|
|
166
|
+
return colorizeFinancials(itemText);
|
|
167
|
+
},
|
|
168
|
+
// Layout — adapt to terminal width
|
|
169
|
+
reflowText: false,
|
|
170
|
+
showSectionPrefix: false,
|
|
171
|
+
width: Math.min(120, contentWidth()),
|
|
172
|
+
tab: 2,
|
|
173
|
+
}));
|
|
174
|
+
// Register chart + table + code block extensions
|
|
175
|
+
md.use({
|
|
176
|
+
renderer: {
|
|
177
|
+
code(token) {
|
|
178
|
+
const rawLang = token.lang || "";
|
|
179
|
+
const code = token.text;
|
|
180
|
+
// Parse lang:subtitle (e.g. "typescript:src/foo.ts")
|
|
181
|
+
const colonIdx = rawLang.indexOf(":");
|
|
182
|
+
const lang = colonIdx > 0 ? rawLang.slice(0, colonIdx) : rawLang;
|
|
183
|
+
const subtitle = colonIdx > 0 ? rawLang.slice(colonIdx + 1) : "";
|
|
184
|
+
if (lang === "chart" || lang === "bar") {
|
|
185
|
+
return renderBarChart(code);
|
|
186
|
+
}
|
|
187
|
+
if (lang === "diff") {
|
|
188
|
+
return renderDiff(code);
|
|
189
|
+
}
|
|
190
|
+
{
|
|
191
|
+
// Command output mode: bash without subtitle = run_command output
|
|
192
|
+
// → no line numbers, wider content area, clean indent
|
|
193
|
+
const isCommandOutput = (lang === "bash" || lang === "terminal") && !subtitle;
|
|
194
|
+
const highlightLang = lang === "terminal" ? "bash" : lang;
|
|
195
|
+
// Build header: ── lang ── subtitle ──────
|
|
196
|
+
const cw = contentWidth();
|
|
197
|
+
const headerWidth = Math.max(20, cw - 6);
|
|
198
|
+
const displayLang = isCommandOutput ? "bash" : lang;
|
|
199
|
+
let header;
|
|
200
|
+
if (displayLang && subtitle) {
|
|
201
|
+
const shortSub = shortenPathForHeader(subtitle, headerWidth - displayLang.length - 10);
|
|
202
|
+
const pad = Math.max(2, headerWidth - displayLang.length - shortSub.length - 6);
|
|
203
|
+
header = p.separator(" ── ") + p.tertiary(displayLang) + p.separator(" ── ") + p.secondary(shortSub) + p.separator(` ${"─".repeat(pad)}`);
|
|
204
|
+
}
|
|
205
|
+
else if (displayLang) {
|
|
206
|
+
const pad = Math.max(2, headerWidth - displayLang.length - 3);
|
|
207
|
+
header = p.separator(" ── ") + p.tertiary(displayLang) + p.separator(` ${"─".repeat(pad)}`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
header = p.separator(" ──" + "─".repeat(headerWidth - 2));
|
|
211
|
+
}
|
|
212
|
+
// Calculate max line width to prevent wrapping
|
|
213
|
+
const lineCount = code.split("\n").length;
|
|
214
|
+
const gutterW = isCommandOutput ? 0 : String(lineCount).length;
|
|
215
|
+
const gutterOverhead = isCommandOutput ? 4 : (2 + gutterW + 3); // " " or " 123 │ "
|
|
216
|
+
const maxLineWidth = Math.max(20, cw - gutterOverhead - 2);
|
|
217
|
+
// Pre-truncate lines BEFORE highlighting (avoids cutting ANSI codes)
|
|
218
|
+
const truncatedCode = code.split("\n").map((line) => {
|
|
219
|
+
if (line.length > maxLineWidth) {
|
|
220
|
+
return line.slice(0, maxLineWidth - 1) + "…";
|
|
221
|
+
}
|
|
222
|
+
return line;
|
|
223
|
+
}).join("\n");
|
|
224
|
+
// Highlight with shiki
|
|
225
|
+
const highlighted = highlightLang
|
|
226
|
+
? highlightCode(truncatedCode, highlightLang)
|
|
227
|
+
: truncatedCode;
|
|
228
|
+
const hLines = highlighted.split("\n");
|
|
229
|
+
if (isCommandOutput) {
|
|
230
|
+
// Command output: no line numbers, 4-space indent
|
|
231
|
+
const body = hLines.map(l => " " + l).join("\n");
|
|
232
|
+
return "\n" + header + "\n" + body + "\n";
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Code with line numbers + gutter
|
|
236
|
+
const numbered = hLines.map((l, i) => {
|
|
237
|
+
const num = p.tertiary(String(i + 1).padStart(gutterW));
|
|
238
|
+
return " " + num + p.separator(" │ ") + l;
|
|
239
|
+
}).join("\n");
|
|
240
|
+
return "\n" + header + "\n" + numbered + "\n";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
table(token) {
|
|
245
|
+
return renderTable(token);
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
return md;
|
|
250
|
+
}
|
|
251
|
+
let md = buildMarkedInstance();
|
|
247
252
|
// ============================================================================
|
|
248
253
|
// Diff renderer — background colors + word-level diff (Claude Code parity)
|
|
249
254
|
// ============================================================================
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
function buildDiffStyles() {
|
|
256
|
+
return {
|
|
257
|
+
diffAddedBg: chalk.bgHex(colors.diffAddedBg).white,
|
|
258
|
+
diffRemovedBg: chalk.bgHex(colors.diffRemovedBg).white,
|
|
259
|
+
diffWordAdded: chalk.bgHex(colors.diffWordAdded).whiteBright.bold,
|
|
260
|
+
diffWordRemoved: chalk.bgHex(colors.diffWordRemoved).whiteBright.bold,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
let d = buildDiffStyles();
|
|
255
264
|
/** Compute word-level diff between two lines using the `diff` library. */
|
|
256
265
|
function wordDiff(oldLine, newLine) {
|
|
257
266
|
const changes = diffWords(oldLine, newLine);
|
|
@@ -353,16 +362,16 @@ function renderDiff(code) {
|
|
|
353
362
|
const wd = wordDiff(r.content, a.content);
|
|
354
363
|
// Removed line with word highlights
|
|
355
364
|
const rPad = Math.max(0, tw - rPrefix.length - r.content.length);
|
|
356
|
-
out.push(diffRemovedBg(rPrefix) + renderSegments(wd.old, diffWordRemoved, diffRemovedBg) + diffRemovedBg(" ".repeat(rPad)));
|
|
365
|
+
out.push(d.diffRemovedBg(rPrefix) + renderSegments(wd.old, d.diffWordRemoved, d.diffRemovedBg) + d.diffRemovedBg(" ".repeat(rPad)));
|
|
357
366
|
// Added line with word highlights
|
|
358
367
|
const aPad = Math.max(0, tw - aPrefix.length - a.content.length);
|
|
359
|
-
out.push(diffAddedBg(aPrefix) + renderSegments(wd.new, diffWordAdded, diffAddedBg) + diffAddedBg(" ".repeat(aPad)));
|
|
368
|
+
out.push(d.diffAddedBg(aPrefix) + renderSegments(wd.new, d.diffWordAdded, d.diffAddedBg) + d.diffAddedBg(" ".repeat(aPad)));
|
|
360
369
|
}
|
|
361
370
|
else {
|
|
362
371
|
// Unpaired remove
|
|
363
372
|
const raw = rPrefix + r.content;
|
|
364
373
|
const pad = Math.max(0, tw - raw.length);
|
|
365
|
-
out.push(diffRemovedBg(raw + " ".repeat(pad)));
|
|
374
|
+
out.push(d.diffRemovedBg(raw + " ".repeat(pad)));
|
|
366
375
|
}
|
|
367
376
|
}
|
|
368
377
|
// Unpaired adds
|
|
@@ -371,7 +380,7 @@ function renderDiff(code) {
|
|
|
371
380
|
const prefix = `${String(a.lineNo).padStart(gutterW)} + `;
|
|
372
381
|
const raw = prefix + a.content;
|
|
373
382
|
const pad = Math.max(0, tw - raw.length);
|
|
374
|
-
out.push(diffAddedBg(raw + " ".repeat(pad)));
|
|
383
|
+
out.push(d.diffAddedBg(raw + " ".repeat(pad)));
|
|
375
384
|
}
|
|
376
385
|
continue;
|
|
377
386
|
}
|
|
@@ -380,12 +389,12 @@ function renderDiff(code) {
|
|
|
380
389
|
const prefix = `${String(seg.lineNo).padStart(gutterW)} + `;
|
|
381
390
|
const raw = prefix + seg.content;
|
|
382
391
|
const pad = Math.max(0, tw - raw.length);
|
|
383
|
-
out.push(diffAddedBg(raw + " ".repeat(pad)));
|
|
392
|
+
out.push(d.diffAddedBg(raw + " ".repeat(pad)));
|
|
384
393
|
i++;
|
|
385
394
|
continue;
|
|
386
395
|
}
|
|
387
396
|
// Context line — dim line number, plain content, no background
|
|
388
|
-
const prefix = tertiary(`${String(seg.lineNo).padStart(gutterW)} `);
|
|
397
|
+
const prefix = p.tertiary(`${String(seg.lineNo).padStart(gutterW)} `);
|
|
389
398
|
out.push(prefix + seg.content);
|
|
390
399
|
i++;
|
|
391
400
|
}
|
|
@@ -394,16 +403,19 @@ function renderDiff(code) {
|
|
|
394
403
|
// ============================================================================
|
|
395
404
|
// Bar chart renderer — ```chart code blocks
|
|
396
405
|
// ============================================================================
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
406
|
+
function buildBarGradient() {
|
|
407
|
+
return [
|
|
408
|
+
chalk.hex(colors.purple),
|
|
409
|
+
chalk.hex(colors.indigo),
|
|
410
|
+
chalk.hex(colors.brand),
|
|
411
|
+
chalk.hex(colors.info),
|
|
412
|
+
chalk.hex(colors.teal),
|
|
413
|
+
chalk.hex(colors.success),
|
|
414
|
+
chalk.hex(colors.warning),
|
|
415
|
+
chalk.hex(colors.pink),
|
|
416
|
+
];
|
|
417
|
+
}
|
|
418
|
+
let barGradient = buildBarGradient();
|
|
407
419
|
function renderBarChart(code) {
|
|
408
420
|
const lines = code.trim().split("\n").filter(l => l.trim());
|
|
409
421
|
// Optional title — first line without "label: number" pattern
|
|
@@ -428,41 +440,64 @@ function renderBarChart(code) {
|
|
|
428
440
|
if (entries.length === 0)
|
|
429
441
|
return code;
|
|
430
442
|
const maxVal = Math.max(...entries.map(e => e.value));
|
|
431
|
-
const maxLabel = Math.max(...entries.map(e => e.label.length));
|
|
432
443
|
const maxRaw = Math.max(...entries.map(e => e.raw.length));
|
|
433
444
|
const cw = contentWidth();
|
|
434
|
-
|
|
445
|
+
// Responsive label width — truncate labels to fit
|
|
446
|
+
const idealLabelW = Math.max(...entries.map(e => e.label.length));
|
|
447
|
+
// Fixed overhead: indent(2) + gap(2) + gap(2) + value(maxRaw) + safety(2)
|
|
448
|
+
const fixedOverhead = 2 + 2 + 2 + maxRaw + 2;
|
|
449
|
+
const spaceForLabelAndBar = cw - fixedOverhead;
|
|
450
|
+
// Allocate: label gets up to half, bar gets the rest (min 4)
|
|
451
|
+
const maxLabelW = Math.min(idealLabelW, Math.max(6, Math.floor(spaceForLabelAndBar * 0.4)));
|
|
452
|
+
const barWidth = Math.max(4, spaceForLabelAndBar - maxLabelW);
|
|
453
|
+
// If even 4-char bars don't fit, fall back to compact list (no bars)
|
|
454
|
+
const compact = spaceForLabelAndBar < 12;
|
|
435
455
|
const out = [];
|
|
436
456
|
if (title) {
|
|
437
|
-
|
|
457
|
+
const displayTitle = title.length > cw - 4 ? title.slice(0, cw - 5) + "…" : title;
|
|
458
|
+
out.push(` ${p.systemBlue.bold(displayTitle)}`);
|
|
438
459
|
out.push("");
|
|
439
460
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
461
|
+
function colorVal(raw) {
|
|
462
|
+
return raw.includes("$") ? p.systemGreen(raw)
|
|
463
|
+
: raw.includes("%") ? p.systemCyan(raw)
|
|
464
|
+
: p.systemMint(raw);
|
|
465
|
+
}
|
|
466
|
+
if (compact) {
|
|
467
|
+
// Compact mode: just "label value" with color dot
|
|
468
|
+
for (let i = 0; i < entries.length; i++) {
|
|
469
|
+
const e = entries[i];
|
|
470
|
+
const color = barGradient[i % barGradient.length];
|
|
471
|
+
const labelStr = e.label.length > cw - maxRaw - 8
|
|
472
|
+
? e.label.slice(0, cw - maxRaw - 9) + "…"
|
|
473
|
+
: e.label;
|
|
474
|
+
out.push(` ${color("●")} ${p.secondary(labelStr)} ${colorVal(e.raw)}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
for (let i = 0; i < entries.length; i++) {
|
|
479
|
+
const e = entries[i];
|
|
480
|
+
const ratio = maxVal > 0 ? e.value / maxVal : 0;
|
|
481
|
+
const filled = Math.round(ratio * barWidth);
|
|
482
|
+
const color = barGradient[i % barGradient.length];
|
|
483
|
+
const labelStr = e.label.length > maxLabelW
|
|
484
|
+
? e.label.slice(0, maxLabelW - 1) + "…"
|
|
485
|
+
: e.label;
|
|
486
|
+
const label = p.secondary(labelStr.padStart(maxLabelW));
|
|
487
|
+
const bar = color("█".repeat(filled)) + chalk.hex("#2C2C2E")("░".repeat(barWidth - filled));
|
|
488
|
+
const val = colorVal(e.raw.padStart(maxRaw));
|
|
489
|
+
out.push(` ${label} ${bar} ${val}`);
|
|
490
|
+
}
|
|
453
491
|
}
|
|
454
492
|
return "\n" + out.join("\n") + "\n";
|
|
455
493
|
}
|
|
456
494
|
function getRowTone(cells) {
|
|
457
495
|
for (const cell of cells) {
|
|
458
496
|
const t = cell.trim();
|
|
459
|
-
// Negative financial → red row
|
|
460
497
|
if (/^-\$[\d,]+/.test(t) || /^-\d+\.?\d*%/.test(t))
|
|
461
498
|
return "negative";
|
|
462
|
-
// Explicit positive delta → green row
|
|
463
499
|
if (/^\+\d/.test(t) || /^\+\$/.test(t))
|
|
464
500
|
return "positive";
|
|
465
|
-
// Status badges
|
|
466
501
|
if (/^`?[✕✗]/.test(t) || /cancelled|failed|rejected|error|out of stock|low stock/i.test(t))
|
|
467
502
|
return "negative";
|
|
468
503
|
if (/^`?[✓●]/.test(t) || /completed|received|approved|active|success|paid|published/i.test(t))
|
|
@@ -470,72 +505,90 @@ function getRowTone(cells) {
|
|
|
470
505
|
}
|
|
471
506
|
return "neutral";
|
|
472
507
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
508
|
+
/** Detect if a column is numeric (for right-alignment) */
|
|
509
|
+
function isNumericColumn(rows, colIndex) {
|
|
510
|
+
let numericCount = 0;
|
|
511
|
+
let total = 0;
|
|
512
|
+
for (const row of rows) {
|
|
513
|
+
const val = (row[colIndex] || "").trim();
|
|
514
|
+
if (!val)
|
|
515
|
+
continue;
|
|
516
|
+
total++;
|
|
517
|
+
if (/^[+\-]?\$?[\d,]+\.?\d*%?$/.test(val))
|
|
518
|
+
numericCount++;
|
|
519
|
+
}
|
|
520
|
+
return total > 0 && numericCount / total > 0.5;
|
|
521
|
+
}
|
|
522
|
+
/** Blend two hex colors by ratio (0 = colorA, 1 = colorB) */
|
|
523
|
+
function blendHex(a, b, ratio) {
|
|
524
|
+
const parse = (h) => {
|
|
525
|
+
const c = h.replace("#", "");
|
|
526
|
+
return [parseInt(c.slice(0, 2), 16), parseInt(c.slice(2, 4), 16), parseInt(c.slice(4, 6), 16)];
|
|
527
|
+
};
|
|
528
|
+
const [ar, ag, ab] = parse(a);
|
|
529
|
+
const [br, bg, bb] = parse(b);
|
|
530
|
+
const r = Math.round(ar + (br - ar) * ratio);
|
|
531
|
+
const g = Math.round(ag + (bg - ag) * ratio);
|
|
532
|
+
const bv = Math.round(ab + (bb - ab) * ratio);
|
|
533
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${bv.toString(16).padStart(2, "0")}`;
|
|
534
|
+
}
|
|
535
|
+
function colorizeCell(val, isHeader) {
|
|
477
536
|
const trimmed = val.trim();
|
|
478
537
|
if (!trimmed)
|
|
479
|
-
return text("");
|
|
538
|
+
return p.text("");
|
|
480
539
|
if (isHeader)
|
|
481
|
-
return
|
|
540
|
+
return p.systemBlue.bold(trimmed);
|
|
482
541
|
// Badge format: `✓ status` or `◆ status` or `○ status` or `✕ status`
|
|
483
542
|
const badgeMatch = trimmed.match(/^`([✓●◆○✕◦])\s+(.+)`$/);
|
|
484
543
|
if (badgeMatch) {
|
|
485
544
|
const [, icon, label] = badgeMatch;
|
|
486
545
|
if (icon === "✓" || icon === "●")
|
|
487
|
-
return systemGreen(`${icon} ${label}`);
|
|
546
|
+
return p.systemGreen(`${icon} ${label}`);
|
|
488
547
|
if (icon === "◆")
|
|
489
|
-
return systemCyan(`${icon} ${label}`);
|
|
548
|
+
return p.systemCyan(`${icon} ${label}`);
|
|
490
549
|
if (icon === "○")
|
|
491
|
-
return systemOrange(`${icon} ${label}`);
|
|
550
|
+
return p.systemOrange(`${icon} ${label}`);
|
|
492
551
|
if (icon === "✕")
|
|
493
|
-
return systemRed(`${icon} ${label}`);
|
|
494
|
-
return secondary(`${icon} ${label}`);
|
|
552
|
+
return p.systemRed(`${icon} ${label}`);
|
|
553
|
+
return p.secondary(`${icon} ${label}`);
|
|
495
554
|
}
|
|
496
|
-
// Inline code (UUID, SKU, transfer number)
|
|
555
|
+
// Inline code (UUID, SKU, transfer number)
|
|
497
556
|
if (trimmed.startsWith("`") && trimmed.endsWith("`")) {
|
|
498
|
-
return systemPurple(trimmed.slice(1, -1));
|
|
557
|
+
return p.systemPurple(trimmed.slice(1, -1));
|
|
499
558
|
}
|
|
500
559
|
// Bold text
|
|
501
560
|
if (trimmed.startsWith("**") && trimmed.endsWith("**")) {
|
|
502
|
-
return text.bold(trimmed.slice(2, -2));
|
|
561
|
+
return p.text.bold(trimmed.slice(2, -2));
|
|
503
562
|
}
|
|
504
563
|
// Negative values → red
|
|
505
564
|
if (/^-\$?[\d,]+\.?\d*$/.test(trimmed) || /^-\d+\.?\d*%$/.test(trimmed)) {
|
|
506
|
-
return systemRed(trimmed);
|
|
565
|
+
return p.systemRed(trimmed);
|
|
507
566
|
}
|
|
508
567
|
// Positive financial → green
|
|
509
568
|
if (/^\+?\$[\d,]+\.?\d*$/.test(trimmed) || /^\$[\d,]+\.?\d*$/.test(trimmed)) {
|
|
510
|
-
return systemGreen(trimmed);
|
|
569
|
+
return p.systemGreen(trimmed);
|
|
511
570
|
}
|
|
512
571
|
// Percentages → cyan
|
|
513
572
|
if (/^\d+\.?\d*%$/.test(trimmed)) {
|
|
514
|
-
return systemCyan(trimmed);
|
|
573
|
+
return p.systemCyan(trimmed);
|
|
515
574
|
}
|
|
516
575
|
// Plain numbers → mint
|
|
517
576
|
if (/^[\d,]+\.?\d*$/.test(trimmed)) {
|
|
518
|
-
return systemMint(trimmed);
|
|
577
|
+
return p.systemMint(trimmed);
|
|
519
578
|
}
|
|
520
579
|
// Status words
|
|
521
580
|
if (/^(active|success|complete|approved|in stock|available)/i.test(trimmed)) {
|
|
522
|
-
return systemGreen(trimmed);
|
|
581
|
+
return p.systemGreen(trimmed);
|
|
523
582
|
}
|
|
524
583
|
if (/^(inactive|error|failed|cancelled|out of stock|low|overdue|expired)/i.test(trimmed)) {
|
|
525
|
-
return systemRed(trimmed);
|
|
584
|
+
return p.systemRed(trimmed);
|
|
526
585
|
}
|
|
527
586
|
if (/^(pending|draft|processing)/i.test(trimmed)) {
|
|
528
|
-
return systemOrange(trimmed);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (rowTone === "positive")
|
|
532
|
-
return rowBgPositive(text(trimmed));
|
|
533
|
-
if (rowTone === "negative")
|
|
534
|
-
return rowBgNegative(text(trimmed));
|
|
535
|
-
return text(trimmed);
|
|
587
|
+
return p.systemOrange(trimmed);
|
|
588
|
+
}
|
|
589
|
+
return p.text(trimmed);
|
|
536
590
|
}
|
|
537
591
|
function renderTable(token) {
|
|
538
|
-
// Extract cell text from token — handles both inline tokens and plain text
|
|
539
592
|
function getCellText(cell) {
|
|
540
593
|
if (!cell)
|
|
541
594
|
return "";
|
|
@@ -552,136 +605,125 @@ function renderTable(token) {
|
|
|
552
605
|
const rows = (token.rows || []).map((row) => row.map((cell) => getCellText(cell)));
|
|
553
606
|
if (headers.length === 0)
|
|
554
607
|
return "";
|
|
555
|
-
// Responsive column widths based on terminal width
|
|
556
608
|
const cw = contentWidth();
|
|
557
609
|
const N = headers.length;
|
|
558
|
-
//
|
|
559
|
-
|
|
610
|
+
// ── Check if box table can fit ──
|
|
611
|
+
// Minimum box table width: indent(2) + borders(N+1) + N * (minCol + 2 padding)
|
|
612
|
+
const MIN_COL = 3;
|
|
613
|
+
const minBoxWidth = 2 + (N + 1) + N * (MIN_COL + 2);
|
|
614
|
+
if (minBoxWidth > cw) {
|
|
615
|
+
// ── Card layout — graceful fallback for narrow terminals ──
|
|
616
|
+
return renderTableCards(headers, rows, cw);
|
|
617
|
+
}
|
|
618
|
+
// ── Box table layout ──
|
|
619
|
+
const overhead = 2 + 1 + (N - 1) + 1 + (N * 2);
|
|
560
620
|
const availableForContent = cw - overhead;
|
|
561
|
-
|
|
562
|
-
const minCol = cw < 70 ? 3 : cw < 90 ? 4 : 6;
|
|
621
|
+
const minCol = cw < 70 ? MIN_COL : cw < 90 ? 5 : 6;
|
|
563
622
|
const maxPerCol = Math.max(minCol, Math.floor(availableForContent / N));
|
|
623
|
+
// Detect numeric columns for right-alignment
|
|
624
|
+
const numericCols = headers.map((_, i) => isNumericColumn(rows, i));
|
|
625
|
+
// Size columns: fit content up to maxPerCol, respect minCol
|
|
564
626
|
const colWidths = headers.map((h, i) => {
|
|
565
627
|
const dataMax = rows.reduce((max, row) => Math.max(max, (row[i] || "").length), 0);
|
|
566
628
|
return Math.min(maxPerCol, Math.max(minCol, h.length, dataMax) + 2);
|
|
567
629
|
});
|
|
568
|
-
|
|
630
|
+
// Double-check total width won't overflow (defensive)
|
|
631
|
+
const totalWidth = 2 + 1 + colWidths.reduce((s, w) => s + w + 2, 0) + (N - 1) + 1;
|
|
632
|
+
if (totalWidth > cw) {
|
|
633
|
+
// Shrink largest columns until we fit
|
|
634
|
+
let excess = totalWidth - cw;
|
|
635
|
+
while (excess > 0) {
|
|
636
|
+
const maxIdx = colWidths.indexOf(Math.max(...colWidths));
|
|
637
|
+
if (colWidths[maxIdx] <= MIN_COL)
|
|
638
|
+
break; // can't shrink further
|
|
639
|
+
colWidths[maxIdx]--;
|
|
640
|
+
excess--;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// ── Colors ──
|
|
644
|
+
const borderColor = blendHex(colors.separator, colors.brand, 0.25);
|
|
645
|
+
const border = chalk.hex(borderColor);
|
|
646
|
+
const headerBg = chalk.bgHex(blendHex(colors.panel, colors.brand, 0.15));
|
|
647
|
+
const evenRowBg = chalk.bgHex(blendHex(colors.panel, colors.text, 0.04));
|
|
648
|
+
const oddRowBg = chalk.bgHex(colors.panel);
|
|
649
|
+
const positiveBg = chalk.bgHex(blendHex(colors.panel, colors.success, 0.12));
|
|
650
|
+
const negativeBg = chalk.bgHex(blendHex(colors.panel, colors.error, 0.12));
|
|
569
651
|
const out = [];
|
|
570
|
-
// Top border
|
|
652
|
+
// Top border
|
|
571
653
|
out.push(border(" ╭" + colWidths.map((w) => "─".repeat(w + 2)).join("┬") + "╮"));
|
|
572
|
-
// Header row
|
|
573
|
-
const
|
|
654
|
+
// Header row
|
|
655
|
+
const hdrCells = headers.map((h, i) => {
|
|
574
656
|
const display = h.length > colWidths[i] ? h.slice(0, colWidths[i] - 1) + "…" : h;
|
|
575
|
-
|
|
657
|
+
const padded = numericCols[i] ? display.padStart(colWidths[i]) : display.padEnd(colWidths[i]);
|
|
658
|
+
return headerBg(" " + chalk.hex(colors.brand).bold(padded) + " ");
|
|
576
659
|
}).join(border("│"));
|
|
577
|
-
out.push(border(" │") +
|
|
578
|
-
// Header
|
|
579
|
-
out.push(border(" ├" + colWidths.map((w) => "
|
|
580
|
-
// Data rows
|
|
581
|
-
for (
|
|
660
|
+
out.push(border(" │") + hdrCells + border("│"));
|
|
661
|
+
// Header divider
|
|
662
|
+
out.push(border(" ├" + colWidths.map((w) => "━".repeat(w + 2)).join("┿") + "┤"));
|
|
663
|
+
// Data rows
|
|
664
|
+
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
|
|
665
|
+
const row = rows[rowIdx];
|
|
582
666
|
const tone = getRowTone(row);
|
|
667
|
+
const rowBg = tone === "positive" ? positiveBg
|
|
668
|
+
: tone === "negative" ? negativeBg
|
|
669
|
+
: rowIdx % 2 === 0 ? evenRowBg : oddRowBg;
|
|
583
670
|
const cells = headers.map((_, i) => {
|
|
584
671
|
const raw = row[i] || "";
|
|
585
672
|
const display = raw.length > colWidths[i] ? raw.slice(0, colWidths[i] - 1) + "…" : raw;
|
|
586
|
-
const colored = colorizeCell(display, false
|
|
673
|
+
const colored = colorizeCell(display, false);
|
|
587
674
|
const extraPad = Math.max(0, colWidths[i] - display.length);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (tone === "negative")
|
|
593
|
-
return rowBgNegative(cellContent);
|
|
594
|
-
return cellContent;
|
|
675
|
+
if (numericCols[i]) {
|
|
676
|
+
return rowBg(" ".repeat(extraPad) + " " + colored + " ");
|
|
677
|
+
}
|
|
678
|
+
return rowBg(" " + colored + " ".repeat(extraPad) + " ");
|
|
595
679
|
}).join(border("│"));
|
|
596
680
|
out.push(border(" │") + cells + border("│"));
|
|
597
681
|
}
|
|
598
|
-
// Bottom border
|
|
682
|
+
// Bottom border
|
|
599
683
|
out.push(border(" ╰" + colWidths.map((w) => "─".repeat(w + 2)).join("┴") + "╯"));
|
|
600
684
|
return "\n" + out.join("\n") + "\n";
|
|
601
685
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const gutterOverhead = isCommandOutput ? 4 : (2 + gutterW + 3); // " " or " 123 │ "
|
|
644
|
-
const maxLineWidth = Math.max(20, cw - gutterOverhead - 2);
|
|
645
|
-
// Pre-truncate lines BEFORE highlighting (avoids cutting ANSI codes)
|
|
646
|
-
const truncatedCode = code.split("\n").map((line) => {
|
|
647
|
-
if (line.length > maxLineWidth) {
|
|
648
|
-
return line.slice(0, maxLineWidth - 1) + "…";
|
|
649
|
-
}
|
|
650
|
-
return line;
|
|
651
|
-
}).join("\n");
|
|
652
|
-
let highlighted;
|
|
653
|
-
if (highlightLang) {
|
|
654
|
-
try {
|
|
655
|
-
highlighted = withSuppressedWarnings(() => highlight(truncatedCode, { language: highlightLang, ignoreIllegals: true, theme: appleTheme }));
|
|
656
|
-
}
|
|
657
|
-
catch {
|
|
658
|
-
highlighted = truncatedCode;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
else {
|
|
662
|
-
highlighted = truncatedCode;
|
|
663
|
-
}
|
|
664
|
-
const hLines = highlighted.split("\n");
|
|
665
|
-
if (isCommandOutput) {
|
|
666
|
-
// Command output: no line numbers, 4-space indent
|
|
667
|
-
const body = hLines.map(l => " " + l).join("\n");
|
|
668
|
-
return "\n" + header + "\n" + body + "\n";
|
|
669
|
-
}
|
|
670
|
-
else {
|
|
671
|
-
// Code with line numbers + gutter
|
|
672
|
-
const numbered = hLines.map((l, i) => {
|
|
673
|
-
const num = tertiary(String(i + 1).padStart(gutterW));
|
|
674
|
-
return " " + num + separator(" │ ") + l;
|
|
675
|
-
}).join("\n");
|
|
676
|
-
return "\n" + header + "\n" + numbered + "\n";
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
},
|
|
680
|
-
table(token) {
|
|
681
|
-
return renderTable(token);
|
|
682
|
-
},
|
|
683
|
-
},
|
|
684
|
-
});
|
|
686
|
+
/** Card layout fallback — renders each row as a key: value block. Never overflows. */
|
|
687
|
+
function renderTableCards(headers, rows, cw) {
|
|
688
|
+
const border = chalk.hex(blendHex(colors.separator, colors.brand, 0.25));
|
|
689
|
+
const out = [];
|
|
690
|
+
const innerW = Math.max(8, cw - 6); // indent(2) + border(2) + padding(2)
|
|
691
|
+
const divider = border(" " + "─".repeat(innerW + 2));
|
|
692
|
+
// Find longest header for label alignment
|
|
693
|
+
const maxHdrLen = Math.min(Math.max(...headers.map(h => h.length)), Math.floor(innerW * 0.4));
|
|
694
|
+
out.push(divider);
|
|
695
|
+
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
|
|
696
|
+
const row = rows[rowIdx];
|
|
697
|
+
if (rowIdx > 0)
|
|
698
|
+
out.push(divider);
|
|
699
|
+
for (let i = 0; i < headers.length; i++) {
|
|
700
|
+
const hdr = headers[i].length > maxHdrLen
|
|
701
|
+
? headers[i].slice(0, maxHdrLen - 1) + "…"
|
|
702
|
+
: headers[i];
|
|
703
|
+
const val = row[i] || "";
|
|
704
|
+
const maxValLen = innerW - maxHdrLen - 3; // " label value"
|
|
705
|
+
const displayVal = val.length > maxValLen
|
|
706
|
+
? val.slice(0, maxValLen - 1) + "…"
|
|
707
|
+
: val;
|
|
708
|
+
const labelStr = p.tertiary(hdr.padEnd(maxHdrLen));
|
|
709
|
+
const valStr = colorizeCell(displayVal, false);
|
|
710
|
+
out.push(` ${labelStr} ${valStr}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
out.push(divider);
|
|
714
|
+
return "\n" + out.join("\n") + "\n";
|
|
715
|
+
}
|
|
716
|
+
// ============================================================================
|
|
717
|
+
// Rebuild — called on theme switch to refresh all chalk instances
|
|
718
|
+
// ============================================================================
|
|
719
|
+
export function rebuildMarkdownRenderer() {
|
|
720
|
+
p = buildPalette();
|
|
721
|
+
d = buildDiffStyles();
|
|
722
|
+
barGradient = buildBarGradient();
|
|
723
|
+
md = buildMarkedInstance();
|
|
724
|
+
}
|
|
725
|
+
// Register rebuild callback with theme manager
|
|
726
|
+
setRebuildCallback(rebuildMarkdownRenderer);
|
|
685
727
|
// ============================================================================
|
|
686
728
|
// Streaming fence closure — state-tracking approach
|
|
687
729
|
// ============================================================================
|