@sns-myagent/cli 0.3.5 → 0.3.6

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.
@@ -1,189 +1,76 @@
1
1
  /**
2
- * Command palette component for SNS-MyAgent terminal UI.
3
- *
4
- * Fuzzy-searchable command overlay with gradient styling.
5
- * Triggered by Ctrl+P or typing "/" in the input.
2
+ * Command palette fuzzy command overlay.
3
+ * Minimal: cyan highlight, dim for secondary. No gradient.
6
4
  */
5
+
7
6
  import chalk from "chalk";
8
- import gradient from "gradient-string";
9
- import { BRAND_GRADIENT, ACCENT_GRADIENT } from "../ui/colors.js";
10
7
 
11
8
  export interface PaletteCommand {
12
- /** Command name (e.g., "/help"). */
13
- name: string;
14
- /** Short description. */
15
- description: string;
16
- /** Category for grouping. */
17
- category: string;
18
- /** Keyboard shortcut (optional). */
19
- shortcut?: string;
20
- /** Whether command is enabled. */
21
- enabled?: boolean;
9
+ name: string;
10
+ description: string;
11
+ category: string;
12
+ shortcut?: string;
13
+ enabled?: boolean;
22
14
  }
23
15
 
24
16
  export interface CommandPaletteOptions {
25
- /** Available commands. */
26
- commands: PaletteCommand[];
27
- /** Search query filter. */
28
- query?: string;
29
- /** Currently highlighted index. */
30
- highlighted?: number;
31
- /** Width of the palette. */
32
- width?: number;
17
+ commands: PaletteCommand[];
18
+ query?: string;
19
+ highlighted?: number;
20
+ width?: number;
33
21
  }
34
22
 
35
- // ── Pre-built command lists ──
36
-
37
23
  export const CHAT_COMMANDS: PaletteCommand[] = [
38
- { name: "/help", description: "Show available commands", category: "General", shortcut: "?" },
39
- { name: "/clear", description: "Clear screen", category: "General", shortcut: "⌘K" },
40
- { name: "/exit", description: "Quit the chat", category: "General", shortcut: "⌘Q" },
41
- { name: "/model", description: "Show/switch model", category: "Config" },
42
- { name: "/history", description: "Show conversation history", category: "General" },
43
- { name: "/memory", description: "Recall from memory", category: "Memory" },
44
- { name: "/skills", description: "List available skills", category: "Extensibility" },
45
- { name: "/plugins", description: "List installed plugins", category: "Extensibility" },
46
- { name: "/mcp", description: "MCP server management", category: "Extensibility" },
47
- { name: "/theme", description: "Change terminal theme", category: "Config" },
48
- { name: "/bench", description: "Run benchmarks", category: "Debug" },
49
- { name: "/debug", description: "Debug info", category: "Debug" },
24
+ { name: "/help", description: "Show available commands", category: "General", shortcut: "?" },
25
+ { name: "/clear", description: "Clear screen", category: "General", shortcut: "⌘K" },
26
+ { name: "/exit", description: "Quit the chat", category: "General", shortcut: "⌘Q" },
27
+ { name: "/model", description: "Show/switch model", category: "Config" },
28
+ { name: "/history", description: "Show conversation history", category: "General" },
29
+ { name: "/memory", description: "Recall from memory", category: "Memory" },
30
+ { name: "/skills", description: "List available skills", category: "Extensibility" },
31
+ { name: "/plugins", description: "List installed plugins", category: "Extensibility" },
32
+ { name: "/mcp", description: "MCP server management", category: "Extensibility" },
33
+ { name: "/theme", description: "Change terminal theme", category: "Config" },
34
+ { name: "/bench", description: "Run benchmarks", category: "Debug" },
35
+ { name: "/debug", description: "Debug info", category: "Debug" },
50
36
  ];
51
37
 
52
- // ── Fuzzy match ──
53
-
54
- function fuzzyMatch(query: string, text: string): number {
55
- if (!query) return 1;
56
- const q = query.toLowerCase();
57
- const t = text.toLowerCase();
58
-
59
- // Exact prefix match — highest score
60
- if (t.startsWith(q)) return 1;
61
-
62
- // Subsequence match
63
- let qi = 0;
64
- let score = 0;
65
- let consecutive = 0;
66
- for (let ti = 0; ti < t.length && qi < q.length; ti++) {
67
- if (t[ti] === q[qi]) {
68
- qi++;
69
- consecutive++;
70
- score += consecutive; // bonus for consecutive chars
71
- } else {
72
- consecutive = 0;
73
- }
74
- }
75
-
76
- return qi === q.length ? score / (q.length * q.length) : 0;
77
- }
78
-
79
- // ── Renderer ──
80
-
81
- /**
82
- * Render the command palette as a bordered overlay.
83
- */
84
38
  export function renderCommandPalette(opts: CommandPaletteOptions): string {
85
- const cols = process.stdout.columns ?? 80;
86
- const width = Math.min(opts.width ?? cols - 4, 64);
87
- const grad = gradient(BRAND_GRADIENT);
88
- const accentGrad = gradient(ACCENT_GRADIENT);
89
-
90
- // Filter and score commands
91
- const query = opts.query ?? "";
92
- const scored = opts.commands
93
- .map(cmd => ({
94
- cmd,
95
- score: fuzzyMatch(query, cmd.name) + fuzzyMatch(query, cmd.description) * 0.5,
96
- }))
97
- .filter(s => s.score > 0)
98
- .sort((a, b) => b.score - a.score)
99
- .slice(0, 12);
100
-
101
- const highlighted = opts.highlighted ?? 0;
102
-
103
- // Group by category
104
- const groups = new Map<string, typeof scored>();
105
- for (const item of scored) {
106
- const cat = item.cmd.category;
107
- if (!groups.has(cat)) groups.set(cat, []);
108
- groups.get(cat)!.push(item);
109
- }
110
-
111
- const lines: string[] = [];
112
- const inner = width - 4;
113
-
114
- // ── Header ──
115
- const searchIcon = "🔍";
116
- const headerText = `${searchIcon} ${accentGrad("Command Palette")}`;
117
- const fill = "─".repeat(Math.max(0, inner - 22));
118
- lines.push(grad("╭─") + headerText + grad(fill + "─╮"));
119
-
120
- // ── Search bar ──
121
- const searchDisplay = query ? chalk.white(query) + chalk.dim("") : chalk.dim("Type to search...");
122
- const searchFill = " ".repeat(Math.max(0, inner - searchDisplay.length + 8));
123
- lines.push(grad("│") + ` ${searchDisplay}${searchFill}` + grad("│"));
124
-
125
- // ── Separator ──
126
- lines.push(grad("├") + "─".repeat(inner) + grad("┤"));
127
-
128
- // ── Commands ──
129
- let globalIdx = 0;
130
- for (const [category, items] of groups) {
131
- // Category header
132
- const catText = chalk.dim(` ${category.toUpperCase()} `);
133
- const catFill = " ".repeat(Math.max(0, inner - category.length - 2));
134
- lines.push(grad("│") + catText + catFill + grad("│"));
135
-
136
- for (const { cmd } of items) {
137
- const isSelected = globalIdx === highlighted;
138
- const nameStr = isSelected
139
- ? chalk.bgCyan.black(` ${cmd.name} `)
140
- : chalk.cyan(cmd.name);
141
- const descStr = chalk.dim(cmd.description);
142
- const shortcutStr = cmd.shortcut ? chalk.dim(` [${cmd.shortcut}]`) : "";
143
-
144
- const line = ` ${nameStr} ${descStr}${shortcutStr}`;
145
- const padding = " ".repeat(Math.max(0, inner - visibleLen(line) + 4));
146
-
147
- if (isSelected) {
148
- lines.push(grad("│") + chalk.bgHex("#1a1a2e")(line) + padding + grad("│"));
149
- } else {
150
- lines.push(grad("│") + line + padding + grad("│"));
151
- }
152
-
153
- globalIdx++;
154
- }
155
- }
156
-
157
- // ── Footer ──
158
- const footer = chalk.dim(" ↑↓ navigate ⏎ select esc close ");
159
- const footerFill = " ".repeat(Math.max(0, inner - footer.length + 4));
160
- lines.push(grad("├") + "─".repeat(inner) + grad("┤"));
161
- lines.push(grad("│") + footer + footerFill + grad("│"));
162
- lines.push(grad("╰") + "─".repeat(inner) + grad("╯"));
163
-
164
- return lines.join("\n");
165
- }
166
-
167
- /**
168
- * Get filtered commands for a query.
169
- */
170
- export function filterCommands(
171
- commands: PaletteCommand[],
172
- query: string,
173
- ): PaletteCommand[] {
174
- return commands
175
- .map(cmd => ({
176
- cmd,
177
- score: fuzzyMatch(query, cmd.name) + fuzzyMatch(query, cmd.description) * 0.5,
178
- }))
179
- .filter(s => s.score > 0)
180
- .sort((a, b) => b.score - a.score)
181
- .map(s => s.cmd);
182
- }
183
-
184
- // ── Helpers ──
185
-
186
- function visibleLen(str: string): number {
187
- // Strip ANSI escape sequences for length calculation
188
- return str.replace(/\x1b\[[0-9;]*m/g, "").length;
189
- }
39
+ const { commands, query = "", highlighted = 0, width = 60 } = opts;
40
+ const filtered = commands.filter(
41
+ (c) => c.enabled !== false &&
42
+ (c.name.toLowerCase().includes(query.toLowerCase()) ||
43
+ c.description.toLowerCase().includes(query.toLowerCase()) ||
44
+ c.category.toLowerCase().includes(query.toLowerCase()))
45
+ );
46
+
47
+ const cols = process.stdout.columns ?? 80;
48
+ const width2 = Math.min(opts.width ?? cols, cols) - 4;
49
+
50
+ const lines: string[] = [];
51
+
52
+ // Header
53
+ const search = chalk.cyan("? ") + query + chalk.dim(" (↑↓ navigate, Enter select, Esc cancel)");
54
+ lines.push(chalk.dim("─".repeat(60)));
55
+ lines.push(` ${chalk.cyan("?")} ${query}${chalk.dim(" (↑↓ Enter Esc)")}`);
56
+ lines.push("");
57
+
58
+ if (filtered.length === 0) {
59
+ lines.push(chalk.dim(" No commands match"));
60
+ } else {
61
+ filtered.forEach((cmd, idx) => {
62
+ const isActive = idx === highlighted;
63
+ const prefix = isActive ? chalk.cyan("► ") : " ";
64
+ const name = isActive ? chalk.cyan.bold(cmd.name) : chalk.cyan(cmd.name);
65
+ const desc = chalk.dim(cmd.description);
66
+ const cat = chalk.dim(` [${cmd.category}]`);
67
+ const shortcut = cmd.shortcut ? chalk.dim(` ${cmd.shortcut}`) : "";
68
+ lines.push(`${prefix}${name}${cat}${shortcut}`);
69
+ });
70
+ }
71
+
72
+ lines.push("");
73
+ lines.push(chalk.dim("─".repeat(60)));
74
+
75
+ return lines.join("\n");
76
+ }
package/src/tui/index.ts CHANGED
@@ -14,4 +14,4 @@ export * from "./width-aware-text";
14
14
  export * from "./splash";
15
15
  export * from "./chat-blocks";
16
16
  export * from "./chat-ui";
17
- export { renderCommandPalette, filterCommands, CHAT_COMMANDS, type PaletteCommand, type CommandPaletteOptions } from "./command-palette";
17
+ export { renderCommandPalette, CHAT_COMMANDS, type PaletteCommand, type CommandPaletteOptions } from "./command-palette";
package/src/tui/splash.ts CHANGED
@@ -1,130 +1,86 @@
1
1
  /**
2
- * SNS-MyAgent splash screen branded gradient banner + system info blocks.
3
- * Premium terminal UI with gradient logo, rounded box, and info panel.
2
+ * SNS-MyAgent splash — minimal banner + info block.
3
+ * Single accent (cyan) for brand, default terminal text everywhere else.
4
4
  */
5
5
  import chalk from "chalk";
6
- import gradient from "gradient-string";
7
- import boxen from "boxen";
8
6
  import { readFileSync } from "node:fs";
9
7
  import { dirname, resolve } from "node:path";
10
8
  import { fileURLToPath } from "node:url";
11
9
 
12
- const BANNER_ART = `
13
- ███╗ ██╗███████╗██╗ ██╗██╗ ██╗███████╗
14
- ████╗ ██║██╔════╝╚██╗██╔╝██║ ██║██╔════╝
15
- ██╔██╗ ██║█████╗ ╚███╔╝ ██║ ██║███████╗
16
- ██║╚██╗██║██╔══╝ ██╔██╗ ██║ ██║╚════██║
17
- ██║ ╚████║███████╗██╔╝ ╚██╗╚██████╔╝███████║
18
- ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝`;
19
-
20
- const SUBTITLE = "My-Agent • SnsAgent CLI";
21
-
22
- const SNY_GRADIENT = ["#00d2ff", "#7b2ff7", "#ff6b9d"];
23
- const ACCENT_GRADIENT = ["#7b2ff7", "#00d2ff"];
10
+ const SUBTITLE = "snsagent — coding agent CLI";
24
11
 
25
12
  function readVersion(): string {
26
- try {
27
- // Strategy 1: import.meta.url relative path (works in most bundler/ts-node setups)
28
- const here = dirname(fileURLToPath(import.meta.url));
29
- const candidates = [
30
- resolve(here, "..", "..", "package.json"), // src/tui/ → root
31
- resolve(here, "..", "package.json"), // dist/tui/ → dist/ (fallback)
32
- resolve(here, "..", "..", "dist", "package.json"), // flat dist
33
- ];
34
-
35
- for (const pkgPath of candidates) {
36
- try {
37
- const raw = readFileSync(pkgPath, "utf8");
38
- const pkg = JSON.parse(raw) as { version?: string };
39
- if (pkg.version && pkg.version !== "0.0.0") return pkg.version;
40
- } catch { /* try next */ }
41
- }
42
-
43
- // Strategy 2: Walk up from CWD looking for package.json with matching name
44
- let dir = process.cwd();
45
- for (let i = 0; i < 5; i++) {
46
- try {
47
- const raw = readFileSync(resolve(dir, "package.json"), "utf8");
48
- const pkg = JSON.parse(raw) as { name?: string; version?: string };
49
- if (pkg.name === "@sns-myagent/cli" && pkg.version) return pkg.version;
50
- } catch { /* continue */ }
51
- const parent = dirname(dir);
52
- if (parent === dir) break;
53
- dir = parent;
54
- }
55
-
56
- return "0.0.0";
57
- } catch {
58
- return "0.0.0";
59
- }
13
+ try {
14
+ const here = dirname(fileURLToPath(import.meta.url));
15
+ const candidates = [
16
+ resolve(here, "..", "..", "package.json"),
17
+ resolve(here, "..", "package.json"),
18
+ resolve(here, "..", "..", "dist", "package.json"),
19
+ ];
20
+ for (const pkgPath of candidates) {
21
+ try {
22
+ const raw = readFileSync(pkgPath, "utf8");
23
+ const pkg = JSON.parse(raw) as { version?: string };
24
+ if (pkg.version && pkg.version !== "0.0.0") return pkg.version;
25
+ } catch {}
26
+ }
27
+ let dir = process.cwd();
28
+ for (let i = 0; i < 5; i++) {
29
+ try {
30
+ const raw = readFileSync(resolve(dir, "package.json"), "utf8");
31
+ const pkg = JSON.parse(raw) as { name?: string; version?: string };
32
+ if (pkg.name === "@sns-myagent/cli" && pkg.version) return pkg.version;
33
+ } catch {}
34
+ const parent = dirname(dir);
35
+ if (parent === dir) break;
36
+ dir = parent;
37
+ }
38
+ return "0.0.0";
39
+ } catch {
40
+ return "0.0.0";
41
+ }
60
42
  }
61
43
 
62
44
  export interface SplashInfo {
63
- model?: string;
64
- provider?: string;
65
- cwd?: string;
66
- platform?: string;
67
- nodeVersion?: string;
68
- }
69
-
70
- function makeInfoBlocks(info: SplashInfo, width: number): string {
71
- const inner = Math.min(width - 4, 56);
72
- const sep = chalk.dim("─".repeat(inner));
73
-
74
- const rows: string[] = [];
75
- const kv = (k: string, v: string) =>
76
- ` ${chalk.dim(k.padEnd(12))}${chalk.white(v)}`;
77
-
78
- rows.push(sep);
79
- if (info.model) rows.push(kv("Model", `${info.provider ?? "unknown"}/${info.model}`));
80
- if (info.cwd) rows.push(kv("Working Dir", info.cwd));
81
- if (info.platform) rows.push(kv("Platform", info.platform));
82
- rows.push(kv("Version", readVersion()));
83
- rows.push(sep);
84
-
85
- const hint = chalk.dim(" Type your message to start chatting.");
86
- const exit = chalk.dim(" /exit or Ctrl+C to quit.");
87
- rows.push(hint);
88
- rows.push(exit);
89
-
90
- return rows.join("\n");
45
+ model?: string;
46
+ provider?: string;
47
+ cwd?: string;
48
+ platform?: string;
49
+ nodeVersion?: string;
91
50
  }
92
51
 
93
52
  export function renderSplash(info: SplashInfo = {}): string {
94
- const cols = process.stdout.columns ?? 80;
95
- const bannerWidth = Math.min(cols - 2, 64);
96
-
97
- // Gradient banner
98
- const bannerLines = BANNER_ART.split("\n").filter(l => l.trim().length > 0);
99
- const coloredBanner = bannerLines
100
- .map(l => gradient(SNY_GRADIENT).multiline(l))
101
- .join("\n");
102
-
103
- // Subtitle
104
- const subtitle = gradient(ACCENT_GRADIENT)(SUBTITLE);
105
-
106
- // Info block
107
- const infoBlock = makeInfoBlocks(info, bannerWidth);
108
-
109
- // Wrap in boxen
110
- const content = `${coloredBanner}\n${subtitle}\n${infoBlock}`;
111
-
112
- const box = boxen(content, {
113
- padding: { top: 1, bottom: 1, left: 2, right: 2 },
114
- margin: { top: 0, bottom: 0, left: 1, right: 1 },
115
- borderStyle: "round",
116
- borderColor: "cyan",
117
- width: bannerWidth,
118
- textAlignment: "center",
119
- });
120
-
121
- return box;
53
+ const cols = process.stdout.columns ?? 80;
54
+ const width = Math.min(cols - 4, 72);
55
+
56
+ // Single brand mark, no rainbow
57
+ const brandLine = `\n ${chalk.cyan.bold("MY")} ${chalk.bold("snsagent")} ${chalk.dim(`v${readVersion()}`)}\n`;
58
+ const subLine = ` ${chalk.dim(SUBTITLE)}\n`;
59
+
60
+ // Separator
61
+ const sep = chalk.dim("─".repeat(Math.min(width - 4, 60)));
62
+
63
+ // Info rows: key dim, value default
64
+ const rows: string[] = [sep];
65
+ const kv = (k: string, v: string) =>
66
+ ` ${chalk.dim(k.padEnd(12))}${v}`;
67
+ if (info.model) rows.push(kv("Model", `${info.provider ?? "unknown"}/${info.model}`));
68
+ if (info.cwd) rows.push(kv("Working Dir", info.cwd));
69
+ if (info.platform) rows.push(kv("Platform", info.platform));
70
+ rows.push(kv("Version", readVersion()));
71
+ rows.push(sep);
72
+
73
+ // Hints
74
+ rows.push(` ${chalk.dim("Type your message to start chatting.")}`);
75
+ rows.push(` ${chalk.dim("/exit or Ctrl+C to quit.")}`);
76
+
77
+ return brandLine + subLine + "\n" + rows.join("\n") + "\n";
122
78
  }
123
79
 
124
80
  export function renderInlineHeader(info: SplashInfo = {}): string {
125
- const model = info.model
126
- ? chalk.cyan(`${info.provider ?? "?"}/${info.model}`)
127
- : chalk.dim("no model");
128
- const ver = chalk.dim(`v${readVersion()}`);
129
- return `\n ${gradient(SNY_GRADIENT)("SnsAgent")} ${ver} ${chalk.dim("│")} ${model}\n`;
81
+ const model = info.model
82
+ ? chalk.cyan(`${info.provider ?? "?"}/${info.model}`)
83
+ : chalk.dim("no model");
84
+ const ver = chalk.dim(`v${readVersion()}`);
85
+ return `\n ${chalk.cyan.bold("MY")} ${chalk.bold("snsagent")} ${ver} ${chalk.dim("│")} ${model}\n`;
130
86
  }
@@ -1,130 +1,94 @@
1
1
  /**
2
- * Premium error display component for SNS-MyAgent terminal UI.
3
- *
4
- * Renders errors in styled bordered blocks with gradient accents.
5
- * Supports error hierarchy: main error + optional cause chain.
2
+ * Error display minimal, single accent, no gradient.
3
+ * Severity controls the icon color (red/yellow/cyan) but everything else
4
+ * stays dim + default text.
6
5
  */
7
- import chalk from "chalk";
8
- import gradient from "gradient-string";
9
- import boxen from "boxen";
10
- import { BRAND_GRADIENT, ROLE_HEX } from "./colors.js";
11
6
 
12
- const ERROR_GRADIENT = [ROLE_HEX.error, "#cc0000"] as const;
13
- const WARN_GRADIENT = ["#ffd700", "#ff8c00"] as const;
7
+ import chalk from "chalk";
14
8
 
15
9
  export type ErrorSeverity = "error" | "warning" | "info";
16
10
 
17
11
  export interface ErrorDisplayOptions {
18
- /** Error title / short description. */
19
- title: string;
20
- /** Detailed error message. */
21
- message: string;
22
- /** Error code (e.g., "E001", "HTTP_404"). */
23
- code?: string;
24
- /** Severity level. */
25
- severity?: ErrorSeverity;
26
- /** Stack trace (shown collapsed). */
27
- stack?: string;
28
- /** Suggestion for fixing the error. */
29
- suggestion?: string;
30
- /** Additional context lines. */
31
- context?: string[];
12
+ title: string;
13
+ message: string;
14
+ code?: string;
15
+ severity?: ErrorSeverity;
16
+ stack?: string;
17
+ suggestion?: string;
18
+ context?: string[];
32
19
  }
33
20
 
34
- function getSeverityColors(severity: ErrorSeverity) {
35
- switch (severity) {
36
- case "error":
37
- return { border: "red" as const, gradient: [...ERROR_GRADIENT], icon: "✗", label: "ERROR" };
38
- case "warning":
39
- return { border: "yellow" as const, gradient: [...WARN_GRADIENT], icon: "⚠", label: "WARNING" };
40
- case "info":
41
- return { border: "cyan" as const, gradient: ["#00d2ff", "#7b2ff7"] as [string, string], icon: "ℹ", label: "INFO" };
42
- }
21
+ function getSeverityMeta(severity: ErrorSeverity) {
22
+ switch (severity) {
23
+ case "error":
24
+ return { icon: "✗", label: "ERROR", color: chalk.red.bold };
25
+ case "warning":
26
+ return { icon: "⚠", label: "WARNING", color: chalk.yellow.bold };
27
+ case "info":
28
+ return { icon: "", label: "INFO", color: chalk.cyan.bold };
29
+ }
43
30
  }
44
31
 
45
- /**
46
- * Render an error display block.
47
- */
48
32
  export function renderErrorDisplay(opts: ErrorDisplayOptions): string {
49
- const severity = opts.severity ?? "error";
50
- const colors = getSeverityColors(severity);
51
- const grad = gradient(colors.gradient);
52
- const cols = process.stdout.columns ?? 80;
53
- const width = Math.min(cols - 2, 72);
33
+ const severity = opts.severity ?? "error";
34
+ const meta = getSeverityMeta(severity);
35
+ const cols = process.stdout.columns ?? 80;
36
+ const width = Math.min(cols - 2, 72);
54
37
 
55
- const lines: string[] = [];
38
+ const border = "─".repeat(width - 2);
56
39
 
57
- // ── Header ──
58
- const headerIcon = severity === "error"
59
- ? chalk.red.bold(colors.icon)
60
- : severity === "warning"
61
- ? chalk.yellow.bold(colors.icon)
62
- : chalk.cyan(colors.icon);
63
- const headerLabel = grad(` ${colors.label} `);
64
- const codeStr = opts.code ? chalk.dim(` [${opts.code}]`) : "";
65
- lines.push(`${headerIcon} ${headerLabel}${codeStr}`);
40
+ const lines: string[] = [];
41
+ lines.push(chalk.dim(border));
66
42
 
67
- // ── Title ──
68
- lines.push("");
69
- lines.push(chalk.bold.white(opts.title));
43
+ // Header
44
+ const codeStr = opts.code ? chalk.dim(` [${opts.code}]`) : "";
45
+ lines.push(`${meta.color(meta.icon)} ${meta.color(meta.label)}${codeStr}`);
46
+ lines.push("");
47
+ lines.push(chalk.bold(opts.title));
48
+ lines.push("");
70
49
 
71
- // ── Message ──
72
- lines.push("");
73
- const msgLines = opts.message.split("\n");
74
- for (const line of msgLines) {
75
- lines.push(chalk.white(line));
76
- }
50
+ // Message
51
+ for (const line of opts.message.split("\n")) {
52
+ lines.push(line);
53
+ }
77
54
 
78
- // ── Context lines ──
79
- if (opts.context && opts.context.length > 0) {
80
- lines.push("");
81
- lines.push(chalk.dim("Context:"));
82
- for (const ctx of opts.context.slice(0, 5)) {
83
- lines.push(chalk.dim(` ${ctx}`));
84
- }
85
- if (opts.context.length > 5) {
86
- lines.push(chalk.dim(` ... +${opts.context.length - 5} more`));
87
- }
88
- }
55
+ // Context
56
+ if (opts.context && opts.context.length > 0) {
57
+ lines.push("");
58
+ lines.push(chalk.dim("Context:"));
59
+ for (const ctx of opts.context.slice(0, 5)) {
60
+ lines.push(chalk.dim(` ${ctx}`));
61
+ }
62
+ if (opts.context.length > 5) {
63
+ lines.push(chalk.dim(` ... +${opts.context.length - 5} more`));
64
+ }
65
+ }
89
66
 
90
- // ── Suggestion ──
91
- if (opts.suggestion) {
92
- lines.push("");
93
- lines.push(`${chalk.green("💡")} ${chalk.green.bold("Suggestion:")}`);
94
- lines.push(chalk.green(` ${opts.suggestion}`));
95
- }
67
+ // Suggestion
68
+ if (opts.suggestion) {
69
+ lines.push("");
70
+ lines.push(`${chalk.cyan("")} ${chalk.bold("Suggestion:")}`);
71
+ lines.push(` ${opts.suggestion}`);
72
+ }
96
73
 
97
- // ── Stack trace (collapsed preview) ──
98
- if (opts.stack) {
99
- lines.push("");
100
- const stackLines = opts.stack.split("\n").slice(0, 3);
101
- lines.push(chalk.dim("Stack trace (first 3 lines):"));
102
- for (const sl of stackLines) {
103
- lines.push(chalk.dim(` ${sl.trim()}`));
104
- }
105
- }
74
+ // Stack preview
75
+ if (opts.stack) {
76
+ lines.push("");
77
+ const stackLines = opts.stack.split("\n").slice(0, 3);
78
+ lines.push(chalk.dim("Stack (first 3):"));
79
+ for (const sl of stackLines) {
80
+ lines.push(chalk.dim(` ${sl.trim()}`));
81
+ }
82
+ }
106
83
 
107
- // ── Render in boxen ──
108
- const content = lines.join("\n");
109
- return boxen(content, {
110
- padding: { top: 1, bottom: 1, left: 2, right: 2 },
111
- margin: { top: 0, bottom: 1, left: 0, right: 0 },
112
- borderStyle: "round",
113
- borderColor: colors.border,
114
- width,
115
- });
84
+ lines.push(chalk.dim(border));
85
+ return lines.join("\n");
116
86
  }
117
87
 
118
- /**
119
- * Quick error render — wraps a simple error message in a styled block.
120
- */
121
88
  export function renderQuickError(title: string, message: string): string {
122
- return renderErrorDisplay({ title, message, severity: "error" });
89
+ return renderErrorDisplay({ title, message, severity: "error" });
123
90
  }
124
91
 
125
- /**
126
- * Render a warning display.
127
- */
128
92
  export function renderWarning(title: string, message: string, suggestion?: string): string {
129
- return renderErrorDisplay({ title, message, severity: "warning", suggestion });
93
+ return renderErrorDisplay({ title, message, severity: "warning", suggestion });
130
94
  }