@ikyyofc/gemini-cli 1.0.3 → 1.0.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikyyofc/gemini-cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "AI CLI Agent powered by Gemini — your own terminal assistant",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/agent.js CHANGED
@@ -3,7 +3,7 @@ import chalk from "chalk";
3
3
  import ora from "ora";
4
4
  import { callGemini } from "./gemini.js";
5
5
  import { GEMINI_TOOLS, executeTool } from "./tools.js";
6
- import { printAssistant, printError, printWarning } from "./renderer.js";
6
+ import { printAssistant, printError, printWarning, printToolCall, printToolResult } from "./renderer.js";
7
7
 
8
8
  /**
9
9
  * Run the agent loop until the model gives a final text answer (no more tool calls).
@@ -68,15 +68,28 @@ export async function runAgentLoop(userMessage, history, {
68
68
  // ── No tool calls → final answer ────────────────────────
69
69
  if (callParts.length === 0) {
70
70
  const final = textParts.map(p => p.text).join("").trim();
71
+ // Close the tool section if it was opened
72
+ if (iteration > 1) {
73
+ process.stdout.write(
74
+ chalk.hex("#4A9EFF")(" ╰") +
75
+ chalk.hex("#555566")("─".repeat(47)) + "\n"
76
+ );
77
+ }
71
78
  if (final) printAssistant(final);
72
79
  return { finalResponse: final, iterations: iteration };
73
80
  }
74
81
 
75
82
  // ── Execute each tool call ───────────────────────────────
76
- // Append the model's turn (contains functionCall parts) to history
77
83
  messages.push({ role: "model", parts });
78
84
 
79
- // Build the functionResponse turn
85
+ // Print tool section header on first iteration
86
+ if (iteration === 1) {
87
+ process.stdout.write(
88
+ "\n" + chalk.hex("#4A9EFF")(" ╭─ working") +
89
+ chalk.hex("#555566")(" " + "─".repeat(36)) + "\n"
90
+ );
91
+ }
92
+
80
93
  const responseParts = [];
81
94
 
82
95
  for (const part of callParts) {
@@ -84,17 +97,13 @@ export async function runAgentLoop(userMessage, history, {
84
97
  printToolCall(name, args);
85
98
 
86
99
  const result = await executeTool(name, args ?? {}, { autoApprove });
87
- printToolResult(result, name);
100
+ printToolResult(result);
88
101
 
89
102
  responseParts.push({
90
- functionResponse: {
91
- name,
92
- response: result // the entire result object goes here
93
- }
103
+ functionResponse: { name, response: result }
94
104
  });
95
105
  }
96
106
 
97
- // Append user turn with all function responses
98
107
  messages.push({ role: "user", parts: responseParts });
99
108
  }
100
109
 
@@ -102,39 +111,6 @@ export async function runAgentLoop(userMessage, history, {
102
111
  return { finalResponse: null, iterations: iteration };
103
112
  }
104
113
 
105
- // ─────────────────────────────────────────────────────────────────
106
- // Terminal rendering helpers for tool calls
107
- // ─────────────────────────────────────────────────────────────────
108
- function printToolCall(name, args = {}) {
109
- const preview = Object.entries(args)
110
- .map(([k, v]) => {
111
- const s = String(v);
112
- return chalk.dim(k + ":") + (s.length > 60 ? s.slice(0, 60) + "…" : s);
113
- })
114
- .join(" ");
115
-
116
- process.stdout.write(
117
- " " + chalk.dim("run") + " " +
118
- chalk.hex("#569CD6")(name) +
119
- (preview ? chalk.dim(" " + preview) : "") + "\n"
120
- );
121
- }
122
-
123
- function printToolResult(result, name) {
124
- const text = typeof result === "object"
125
- ? (result.result ?? result.error ?? JSON.stringify(result))
126
- : String(result);
127
- const isErr = typeof result === "object" && result.error;
128
- const color = isErr ? chalk.red : chalk.dim;
129
- const lines = text.split("\n").slice(0, 12);
130
- const more = text.split("\n").length > 12
131
- ? chalk.dim(`\n … +${text.split("\n").length - 12} lines`) : "";
132
-
133
- process.stdout.write(
134
- " " + color(lines.join("\n ")) + more + "\n"
135
- );
136
- }
137
-
138
114
  function clearLine() {
139
115
  if (process.stdout.clearLine) { process.stdout.clearLine(0); process.stdout.cursorTo(0); }
140
116
  }
package/src/renderer.js CHANGED
@@ -1,6 +1,23 @@
1
- // src/renderer.js — Clean, minimal terminal UI
1
+ // src/renderer.js
2
2
  import chalk from "chalk";
3
3
 
4
+ // Accent colors
5
+ const C = {
6
+ blue: "#4A9EFF",
7
+ teal: "#1DB8A0",
8
+ dim: "#555566",
9
+ code: "#CE9178",
10
+ kw: "#569CD6",
11
+ str: "#CE9178",
12
+ comment: "#6A9955",
13
+ num: "#B5CEA8",
14
+ fn: "#DCDCAA",
15
+ green: "#4EC9B0",
16
+ yellow: "#E5C07B",
17
+ red: "#E06C75",
18
+ white: "#CDD6F4",
19
+ };
20
+
4
21
  // ─────────────────────────────────────────────────────────────────
5
22
  // Syntax highlighting
6
23
  // ─────────────────────────────────────────────────────────────────
@@ -9,13 +26,13 @@ const KW = {
9
26
  ts: /\b(const|let|var|function|return|if|else|for|while|switch|case|class|extends|import|export|default|async|await|try|catch|type|interface|enum|implements|declare|readonly|abstract|as|keyof|never|any|string|number|boolean|null|undefined|true|false)\b/g,
10
27
  py: /\b(def|class|return|if|elif|else|for|while|import|from|as|with|try|except|finally|raise|pass|break|continue|and|or|not|in|is|None|True|False|lambda|yield|global|async|await)\b/g,
11
28
  go: /\b(func|return|if|else|for|range|switch|var|const|type|struct|interface|import|package|defer|go|chan|map|make|new|nil|true|false)\b/g,
12
- sh: /\b(if|then|else|elif|fi|for|while|do|done|case|esac|function|return|exit|export|echo|local|source)\b/g,
29
+ sh: /\b(if|then|else|elif|fi|for|while|do|done|case|esac|function|return|exit|export|echo|local|source|cd|mkdir|rm|cp|mv)\b/g,
13
30
  rs: /\b(fn|let|mut|return|if|else|for|match|use|mod|pub|struct|enum|impl|trait|type|const|async|await|true|false|None|Some|Ok|Err)\b/g,
14
31
  };
15
32
  const LANGMAP = {
16
33
  javascript:"js",js:"js",typescript:"ts",ts:"ts",
17
34
  python:"py",py:"py",go:"go",golang:"go",
18
- rust:"rs",rs:"rs",bash:"sh",sh:"sh",shell:"sh",zsh:"sh"
35
+ rust:"rs",rs:"rs",bash:"sh",sh:"sh",shell:"sh",zsh:"sh",fish:"sh",
19
36
  };
20
37
 
21
38
  function highlight(code, lang = "") {
@@ -24,11 +41,11 @@ function highlight(code, lang = "") {
24
41
  const saved = [];
25
42
  const save = s => { const id = `\x00${saved.length}\x00`; saved.push(s); return id; };
26
43
 
27
- r = r.replace(/(\/\/.*$|#.*$|\/\*[\s\S]*?\*\/)/gm, m => save(chalk.hex("#6A9955").italic(m)));
28
- r = r.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, m => save(chalk.hex("#CE9178")(m)));
29
- if (KW[l]) r = r.replace(KW[l], m => save(chalk.hex("#569CD6")(m)));
30
- r = r.replace(/\b(\d+\.?\d*)\b/g, m => save(chalk.hex("#B5CEA8")(m)));
31
- r = r.replace(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=\()/g, m => save(chalk.hex("#DCDCAA")(m)));
44
+ r = r.replace(/(\/\/.*$|#.*$|\/\*[\s\S]*?\*\/)/gm, m => save(chalk.hex(C.comment).italic(m)));
45
+ r = r.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, m => save(chalk.hex(C.str)(m)));
46
+ if (KW[l]) r = r.replace(KW[l], m => save(chalk.hex(C.kw).bold(m)));
47
+ r = r.replace(/\b(\d+\.?\d*)\b/g, m => save(chalk.hex(C.num)(m)));
48
+ r = r.replace(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=\()/g, m => save(chalk.hex(C.fn)(m)));
32
49
  return r.replace(/\x00(\d+)\x00/g, (_, i) => saved[parseInt(i)]);
33
50
  }
34
51
 
@@ -38,22 +55,46 @@ function highlight(code, lang = "") {
38
55
  export function renderMarkdown(text) {
39
56
  let r = text;
40
57
 
41
- // Fenced code blocks
58
+ // Fenced code blocks — with line gutter
42
59
  r = r.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => {
43
- const hl = highlight(code.trimEnd(), lang);
44
- const lbl = lang ? chalk.dim(` ${lang} `) : "";
45
- const top = chalk.dim("┄".repeat(40)) + lbl;
46
- const body = hl.split("\n").map(l => " " + l).join("\n");
47
- return `\n ${top}\n${body}\n ${chalk.dim("┄".repeat(40))}\n`;
60
+ const trimmed = code.trimEnd();
61
+ const lines = trimmed.split("\n");
62
+ const hl = highlight(trimmed, lang);
63
+ const hlLines = hl.split("\n");
64
+ const lbl = lang
65
+ ? chalk.hex(C.blue).bold(` ${lang} `)
66
+ : "";
67
+ const gutterW = String(lines.length).length;
68
+
69
+ const header = chalk.hex(C.dim)(" ╭") +
70
+ chalk.hex(C.dim)("─".repeat(2)) +
71
+ lbl +
72
+ chalk.hex(C.dim)("─".repeat(Math.max(0, 40 - lang.length - 2))) +
73
+ "╮";
74
+ const footer = chalk.hex(C.dim)(" ╰" + "─".repeat(44) + "╯");
75
+ const body = hlLines.map((l, i) => {
76
+ const n = chalk.hex(C.dim)(String(i + 1).padStart(gutterW));
77
+ return chalk.hex(C.dim)(" │ ") + n + chalk.hex(C.dim)(" │ ") + l;
78
+ }).join("\n");
79
+
80
+ return `\n${header}\n${body}\n${footer}\n`;
48
81
  });
49
82
 
50
83
  // Inline code
51
- r = r.replace(/`([^`\n]+)`/g, (_, c) => chalk.hex("#CE9178")(`\`${c}\``));
84
+ r = r.replace(/`([^`\n]+)`/g, (_, c) =>
85
+ chalk.hex(C.code)("‹") + chalk.hex(C.code).bold(c) + chalk.hex(C.code)("›")
86
+ );
52
87
 
53
88
  // Headers
54
- r = r.replace(/^### (.+)$/gm, (_, t) => "\n" + chalk.bold(t));
55
- r = r.replace(/^## (.+)$/gm, (_, t) => "\n" + chalk.bold.underline(t));
56
- r = r.replace(/^# (.+)$/gm, (_, t) => "\n" + chalk.bold.underline(t));
89
+ r = r.replace(/^### (.+)$/gm, (_, t) =>
90
+ "\n" + chalk.hex(C.yellow).bold(" " + t)
91
+ );
92
+ r = r.replace(/^## (.+)$/gm, (_, t) =>
93
+ "\n" + chalk.hex(C.blue).bold(" ›› " + t)
94
+ );
95
+ r = r.replace(/^# (.+)$/gm, (_, t) =>
96
+ "\n" + chalk.hex(C.teal).bold(" ▸ " + t.toUpperCase())
97
+ );
57
98
 
58
99
  // Bold / italic
59
100
  r = r.replace(/\*\*\*(.+?)\*\*\*/g, (_, t) => chalk.bold.italic(t));
@@ -61,124 +102,225 @@ export function renderMarkdown(text) {
61
102
  r = r.replace(/\*(.+?)\*/g, (_, t) => chalk.italic(t));
62
103
 
63
104
  // Blockquotes
64
- r = r.replace(/^> (.+)$/gm, (_, t) => chalk.dim("▌ ") + chalk.italic(t));
105
+ r = r.replace(/^> (.+)$/gm, (_, t) =>
106
+ chalk.hex(C.blue)(" ▌ ") + chalk.hex(C.white).italic(t)
107
+ );
65
108
 
66
109
  // Lists
67
- r = r.replace(/^(\s*)[*\-+] (.+)$/gm, (_, i, t) => `${i} · ${t}`);
68
- r = r.replace(/^(\s*)(\d+)\. (.+)$/gm, (_, i, n, t) => `${i} ${chalk.bold(n+".")} ${t}`);
110
+ r = r.replace(/^(\s*)[*\-+] (.+)$/gm, (_, ind, t) =>
111
+ ind + chalk.hex(C.teal)(" ◆ ") + t
112
+ );
113
+ r = r.replace(/^(\s*)(\d+)\. (.+)$/gm, (_, ind, n, t) =>
114
+ ind + chalk.hex(C.blue)(" " + n + ". ") + t
115
+ );
69
116
 
70
117
  // HR
71
- r = r.replace(/^---+$/gm, chalk.dim("─".repeat(48)));
118
+ r = r.replace(/^---+$/gm,
119
+ chalk.hex(C.dim)(" " + "─".repeat(50))
120
+ );
72
121
 
73
122
  return r;
74
123
  }
75
124
 
76
125
  // ─────────────────────────────────────────────────────────────────
77
- // Message printers (clean, no heavy boxing)
126
+ // Message printers
78
127
  // ─────────────────────────────────────────────────────────────────
79
128
  export function printUser(text) {
80
- // Show multi-line messages cleanly
81
129
  const lines = text.split("\n");
82
- const prefix = chalk.hex("#4A9EFF").bold(" you ");
83
- if (lines.length === 1) {
84
- process.stdout.write("\n" + prefix + chalk.white(text) + "\n");
85
- } else {
86
- process.stdout.write("\n" + prefix + chalk.white(lines[0]) + "\n");
87
- lines.slice(1).forEach(l => process.stdout.write(" " + chalk.white(l) + "\n"));
130
+ const isMulti = lines.length > 1;
131
+
132
+ // Single line: compact
133
+ if (!isMulti) {
134
+ process.stdout.write(
135
+ "\n" +
136
+ chalk.hex(C.blue)(" ╸ ") +
137
+ chalk.hex(C.blue).bold("you ") +
138
+ chalk.hex(C.white)(text) +
139
+ "\n"
140
+ );
141
+ return;
88
142
  }
143
+
144
+ // Multi-line (pasted code): show with left bar
145
+ process.stdout.write(
146
+ "\n" +
147
+ chalk.hex(C.blue)(" ╸ ") +
148
+ chalk.hex(C.blue).bold("you") +
149
+ chalk.hex(C.dim)(` (${lines.length} lines)`) +
150
+ "\n"
151
+ );
152
+ lines.forEach(l =>
153
+ process.stdout.write(chalk.hex(C.blue)(" │ ") + chalk.hex(C.white)(l) + "\n")
154
+ );
155
+ process.stdout.write(chalk.hex(C.blue)(" ╹") + "\n");
89
156
  }
90
157
 
91
158
  export function printAssistant(text) {
92
- const label = chalk.hex("#4EC9B0").bold("gemini ");
93
- const lines = renderMarkdown(text).split("\n");
94
- process.stdout.write("\n");
95
- lines.forEach((line, i) => {
96
- const pre = i === 0 ? " " + label : " ";
97
- process.stdout.write(pre + line + "\n");
98
- });
99
- process.stdout.write("\n");
159
+ const rendered = renderMarkdown(text);
160
+ const lines = rendered.split("\n");
161
+
162
+ process.stdout.write(
163
+ "\n" +
164
+ chalk.hex(C.teal)(" ╸ ") +
165
+ chalk.hex(C.teal).bold("gemini") +
166
+ "\n"
167
+ );
168
+
169
+ lines.forEach(l =>
170
+ process.stdout.write(chalk.hex(C.teal)(" │ ") + l + "\n")
171
+ );
172
+
173
+ process.stdout.write(chalk.hex(C.teal)(" ╹") + "\n\n");
100
174
  }
101
175
 
176
+ // ─────────────────────────────────────────────────────────────────
177
+ // Status messages
178
+ // ─────────────────────────────────────────────────────────────────
102
179
  export function printError(msg) {
103
- process.stdout.write(chalk.red(" error ") + chalk.dim(msg) + "\n");
180
+ process.stdout.write(
181
+ "\n" +
182
+ chalk.hex(C.red)(" ✗ ") +
183
+ chalk.hex(C.red).bold("error ") +
184
+ chalk.white(msg) +
185
+ "\n\n"
186
+ );
104
187
  }
105
188
  export function printInfo(msg) {
106
- process.stdout.write(chalk.dim(" info ") + msg + "\n");
189
+ process.stdout.write(
190
+ chalk.hex(C.dim)(" · ") +
191
+ chalk.hex(C.white)(msg) +
192
+ "\n"
193
+ );
107
194
  }
108
195
  export function printSuccess(msg) {
109
- process.stdout.write(chalk.green(" ✓ ") + msg + "\n");
196
+ process.stdout.write(
197
+ chalk.hex(C.teal)(" ✓ ") +
198
+ chalk.hex(C.teal)(msg) +
199
+ "\n"
200
+ );
110
201
  }
111
202
  export function printWarning(msg) {
112
- process.stdout.write(chalk.yellow(" warn ") + msg + "\n");
203
+ process.stdout.write(
204
+ chalk.hex(C.yellow)(" ⚠ ") +
205
+ chalk.hex(C.yellow)(msg) +
206
+ "\n"
207
+ );
113
208
  }
114
209
 
115
210
  // ─────────────────────────────────────────────────────────────────
116
- // Welcome
211
+ // Tool call display (used by agent.js)
212
+ // ─────────────────────────────────────────────────────────────────
213
+ export function printToolCall(name, args = {}) {
214
+ const argStr = Object.entries(args)
215
+ .map(([k, v]) => {
216
+ const s = String(v);
217
+ return chalk.hex(C.dim)(k + ":") + chalk.hex(C.code)(s.length > 55 ? s.slice(0, 55) + "…" : s);
218
+ })
219
+ .join(" ");
220
+
221
+ process.stdout.write(
222
+ chalk.hex(C.blue)(" ├─ ") +
223
+ chalk.hex(C.blue).bold(name) +
224
+ (argStr ? " " + argStr : "") +
225
+ "\n"
226
+ );
227
+ }
228
+
229
+ export function printToolResult(result) {
230
+ const isErr = typeof result === "object" && result.error;
231
+ const text = typeof result === "object"
232
+ ? (result.result ?? result.error ?? JSON.stringify(result))
233
+ : String(result);
234
+ const color = isErr ? chalk.hex(C.red) : chalk.hex(C.dim);
235
+ const lines = text.split("\n");
236
+ const shown = lines.slice(0, 10);
237
+ const extra = lines.length > 10 ? chalk.hex(C.dim)(` +${lines.length - 10} more lines`) : "";
238
+
239
+ shown.forEach(l =>
240
+ process.stdout.write(chalk.hex(C.dim)(" │ ") + color(l) + "\n")
241
+ );
242
+ if (extra) process.stdout.write(" " + extra + "\n");
243
+ }
244
+
245
+ // ─────────────────────────────────────────────────────────────────
246
+ // Welcome screen
117
247
  // ─────────────────────────────────────────────────────────────────
118
248
  export function renderWelcome(memCount = 0, extCount = 0) {
119
249
  const stats = [
120
- memCount ? `${memCount} context file${memCount > 1 ? "s" : ""}` : null,
121
- extCount ? `${extCount} extension${extCount > 1 ? "s" : ""}` : null,
122
- ].filter(Boolean).join(" · ");
250
+ memCount ? `${memCount} context file${memCount > 1 ? "s" : ""}` : null,
251
+ extCount ? `${extCount} extension${extCount > 1 ? "s" : ""}` : null,
252
+ ].filter(Boolean);
253
+
254
+ const sep = chalk.hex(C.blue)(" " + "━".repeat(46));
123
255
 
124
256
  return [
125
257
  "",
126
- chalk.hex("#4EC9B0").bold(" Gemini CLI") +
127
- chalk.dim(" ─ AI Agent ─ native function calling"),
128
- stats ? chalk.dim(" " + stats) : "",
129
- chalk.dim(" /help for commands · paste code freely · /agent to toggle tools"),
258
+ sep,
259
+ chalk.hex(C.teal).bold(" Gemini CLI") +
260
+ chalk.hex(C.dim)(" ─ AI Agent ─ native function calling"),
261
+ sep,
262
+ stats.length
263
+ ? chalk.hex(C.dim)(" ") + chalk.hex(C.white)(stats.join(" · "))
264
+ : "",
265
+ chalk.hex(C.dim)(" /help") +
266
+ chalk.hex(C.dim)(" for commands · ") +
267
+ chalk.hex(C.dim)("/agent") +
268
+ chalk.hex(C.dim)(" to toggle tools"),
130
269
  "",
131
270
  ].join("\n");
132
271
  }
133
272
 
134
273
  // ─────────────────────────────────────────────────────────────────
135
- // Help
274
+ // Help screen
136
275
  // ─────────────────────────────────────────────────────────────────
137
276
  export function renderHelp(customCommands = {}) {
138
- const section = (title, rows) => [
139
- "",
140
- chalk.dim(" " + title),
141
- chalk.dim(" " + "─".repeat(48)),
142
- ...rows.map(([cmd, desc]) =>
143
- " " + chalk.hex("#CE9178")(cmd.padEnd(26)) + chalk.dim(desc)
144
- ),
145
- ].join("\n");
277
+ const sep = chalk.hex(C.dim)(" " + "─".repeat(50));
278
+
279
+ const row = (cmd, desc) =>
280
+ " " +
281
+ chalk.hex(C.blue).bold(cmd.padEnd(28)) +
282
+ chalk.hex(C.dim)(desc);
146
283
 
147
284
  const lines = [
148
- section("COMMANDS", [
149
- ["/agent", "Toggle agent (tools) ↔ chat mode"],
150
- ["/yolo", "Skip all tool confirmations"],
151
- ["/memory show", "Show loaded GEMINI.md files"],
152
- ["/memory reload", "Reload context from disk"],
153
- ["/memory add <text>", "Append to ~/.gemini/GEMINI.md"],
154
- ["/ext list", "List extensions"],
155
- ["/ext install <src>", "Install extension (path or git URL)"],
156
- ["/ext uninstall <n>", "Remove extension"],
157
- ["/ext enable/disable <n>", "Toggle extension"],
158
- ["/ext update <n>", "Pull latest (git)"],
159
- ["/file <path>", "Attach file to next message"],
160
- ["/system <text>", "Set system instruction"],
161
- ["/history", "Show conversation turns"],
162
- ["/export <file>", "Export history to JSON"],
163
- ["/cd <path>", "Change working directory"],
164
- ["/new /clear", "Reset conversation"],
165
- ["/model", "Show model info"],
166
- ["/exit /quit", "Exit"],
167
- ]),
285
+ "",
286
+ chalk.hex(C.teal).bold(" Commands"),
287
+ sep,
288
+ row("/agent", "Toggle agent mode (tools on/off)"),
289
+ row("/yolo", "Skip all tool confirmations"),
290
+ sep,
291
+ row("/memory show", "Show loaded GEMINI.md files"),
292
+ row("/memory reload", "Reload context from disk"),
293
+ row("/memory add <text>", "Append to ~/.gemini/GEMINI.md"),
294
+ sep,
295
+ row("/ext list", "List installed extensions"),
296
+ row("/ext install <src>", "Install from path or git URL"),
297
+ row("/ext uninstall <n>", "Remove extension"),
298
+ row("/ext enable <n>", "Enable extension"),
299
+ row("/ext disable <n>", "Disable extension"),
300
+ row("/ext update <n>", "Pull latest from git"),
301
+ sep,
302
+ row("/file <path>", "Attach file to next message"),
303
+ row("/system <text>", "Set system instruction"),
304
+ row("/history", "Show conversation turns"),
305
+ row("/export <file>", "Export history to JSON"),
306
+ row("/cd <path>", "Change working directory"),
307
+ row("/new /clear", "Reset conversation"),
308
+ row("/model", "Show model & config"),
309
+ row("/exit /quit", "Exit"),
168
310
  ];
169
311
 
170
312
  const cmds = Object.entries(customCommands);
171
313
  if (cmds.length) {
172
- lines.push(section("EXTENSION COMMANDS",
173
- cmds.map(([k, c]) => ["/" + k, c.description ?? ""])
174
- ));
314
+ lines.push("", chalk.hex(C.teal).bold(" Extension Commands"), sep);
315
+ cmds.forEach(([k, c]) => lines.push(row("/" + k, c.description ?? "")));
175
316
  }
176
317
 
177
318
  lines.push(
178
319
  "",
179
- chalk.dim(" Paste multi-line code freely — input is collected until you press Enter."),
180
- chalk.dim(" Ctrl+C cancel · Ctrl+D exit"),
181
- ""
320
+ chalk.hex(C.dim)(" Paste multi-line code freely — collected as one input."),
321
+ chalk.hex(C.dim)(" Ctrl+C cancel/exit · Ctrl+D exit"),
322
+ "",
182
323
  );
324
+
183
325
  return lines.join("\n");
184
326
  }