@ikyyofc/gemini-cli 2.0.9 → 3.0.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/renderer.js +168 -233
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikyyofc/gemini-cli",
3
- "version": "2.0.9",
3
+ "version": "3.0.0",
4
4
  "description": "AI Agent CLI — native function calling · GEMINI.md context · extensions",
5
5
  "type": "module",
6
6
  "bin": { "gemini": "./index.js" },
package/src/renderer.js CHANGED
@@ -1,328 +1,263 @@
1
- // src/renderer.js — Terminal UI
1
+ // src/renderer.js
2
2
  import chalk from "chalk";
3
3
 
4
4
  const C = {
5
- blue: "#4A9EFF",
6
- teal: "#00D4AA",
7
- purple: "#C586FF",
8
- yellow: "#FFD080",
9
- orange: "#FF9060",
10
- red: "#FF5F7E",
11
- green: "#50FA7B",
12
- dim: "#4A4A5E",
13
- dimmer: "#32323E",
14
- muted: "#7A7A9A",
15
- white: "#E8E8F0",
16
- kw: "#79B8FF",
17
- str: "#F0A070",
18
- comment: "#6A9955",
19
- num: "#B5CEA8",
20
- fn: "#FFD080",
5
+ blue: "#4A9EFF", teal: "#00D4AA", purple: "#C586FF",
6
+ yellow: "#FFD080", orange:"#FF9060", red: "#FF5F7E",
7
+ dim: "#4A4A5E", dimmer:"#32323E", muted: "#7A7A9A",
8
+ white: "#E8E8F0", kw: "#79B8FF", str: "#F0A070",
9
+ comment: "#6A9955", num: "#B5CEA8", fn: "#FFD080",
21
10
  };
22
11
 
23
- // ─────────────────────────────────────────────────────────────────
24
- // Syntax highlighting
25
- // ─────────────────────────────────────────────────────────────────
12
+ const tw = () => Math.min(process.stdout.columns || 72, 84);
13
+ const bw = () => Math.min(tw() - 4, 68);
14
+ const vlen = s => s.replace(/\x1b\[[0-9;]*m/g, "").length;
15
+
16
+ function wrapText(text, maxW) {
17
+ if (vlen(text) <= maxW) return [text];
18
+ const words = text.split(" ");
19
+ const lines = [];
20
+ let cur = "";
21
+ for (const w of words) {
22
+ const adding = cur ? cur + " " + w : w;
23
+ if (vlen(adding) <= maxW) { cur = adding; continue; }
24
+ if (cur) lines.push(cur);
25
+ if (vlen(w) > maxW) {
26
+ for (let i = 0; i < w.length; i += maxW) lines.push(w.slice(i, i + maxW));
27
+ cur = "";
28
+ } else { cur = w; }
29
+ }
30
+ if (cur) lines.push(cur);
31
+ return lines.length ? lines : [""];
32
+ }
33
+
34
+ // ─── Syntax highlight ────────────────────────────────────────────
35
+ // FIX: applyToRaw() ensures the number regex never matches digits
36
+ // inside existing \x00N\x00 placeholder markers (the root cause of
37
+ // garbage numbers appearing in rendered code).
26
38
  const KW = {
27
39
  js: /\b(const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|new|this|class|extends|import|export|default|async|await|try|catch|finally|throw|typeof|instanceof|of|in|null|undefined|true|false|void|delete|yield|from|static|super)\b/g,
28
40
  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,
29
41
  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,
30
42
  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,
31
- 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|sudo|apt|npm|pip|git)\b/g,
43
+ 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|sudo|npm|pip|git)\b/g,
32
44
  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,
33
- json: null,
34
45
  };
35
46
  const LANGMAP = {
36
47
  javascript:"js", js:"js", typescript:"ts", ts:"ts",
37
48
  python:"py", py:"py", go:"go", golang:"go",
38
49
  rust:"rs", rs:"rs", bash:"sh", sh:"sh", shell:"sh", zsh:"sh", fish:"sh",
39
- json:"json", jsonc:"json",
40
50
  };
41
51
 
52
+ function applyToRaw(str, re, fn) {
53
+ return str.split(/(\x00\d+\x00)/).map((p, i) =>
54
+ i % 2 === 0 ? p.replace(re, fn) : p
55
+ ).join("");
56
+ }
57
+
42
58
  function highlight(code, lang = "") {
43
59
  const l = LANGMAP[lang.toLowerCase()] || "";
44
- if (!l || l === "json") return code;
45
- let r = code;
60
+ if (!l) return code;
46
61
  const saved = [];
47
- const save = s => { const id = `\x00${saved.length}\x00`; saved.push(s); return id; };
48
- r = r.replace(/(\/\/.*$|#.*$|\/\*[\s\S]*?\*\/)/gm, m => save(chalk.hex(C.comment).italic(m)));
49
- r = r.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, m => save(chalk.hex(C.str)(m)));
50
- if (KW[l]) r = r.replace(KW[l], m => save(chalk.hex(C.kw).bold(m)));
51
- r = r.replace(/\b(\d+\.?\d*)\b/g, m => save(chalk.hex(C.num)(m)));
52
- r = r.replace(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=\()/g, m => save(chalk.hex(C.fn)(m)));
53
- return r.replace(/\x00(\d+)\x00/g, (_, i) => saved[+i]);
62
+ const save = s => { const id = `\x00${saved.length}\x00`; saved.push(s); return id; };
63
+ let r = code;
64
+ r = applyToRaw(r, /(\/\/.*$|#.*$|\/\*[\s\S]*?\*\/)/gm, m => save(chalk.hex(C.comment).italic(m)));
65
+ r = applyToRaw(r, /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, m => save(chalk.hex(C.str)(m)));
66
+ if (KW[l]) { KW[l].lastIndex = 0; r = applyToRaw(r, KW[l], m => save(chalk.hex(C.kw).bold(m))); }
67
+ r = applyToRaw(r, /\b(\d+\.?\d*)\b/g, m => save(chalk.hex(C.num)(m)));
68
+ r = applyToRaw(r, /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=\()/g, m => save(chalk.hex(C.fn)(m)));
69
+ return r.replace(/\x00(\d+)\x00/g, (_, i) => saved[+i] ?? "");
54
70
  }
55
71
 
56
- // ─────────────────────────────────────────────────────────────────
57
- // Markdown terminal
58
- // ─────────────────────────────────────────────────────────────────
59
- export function renderMarkdown(text) {
72
+ // ─── Markdown renderer ───────────────────────────────────────────
73
+ export function renderMarkdown(text, contentW) {
74
+ const cw = contentW ?? bw() - 2;
60
75
  let r = text;
61
76
 
62
- // Code blocks
63
77
  r = r.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => {
64
78
  const trimmed = code.trimEnd();
65
79
  const lines = trimmed.split("\n");
66
80
  const hl = highlight(trimmed, lang);
67
81
  const hlLines = hl.split("\n");
68
82
  const gw = String(lines.length).length;
69
- const label = lang ? chalk.hex(C.blue).bold(` ${lang} `) + chalk.hex(C.dimmer)("─".repeat(Math.max(0, 36 - lang.length))) : chalk.hex(C.dimmer)("─".repeat(38));
70
-
71
- const top = chalk.hex(C.dim)(" ┌─") + label + chalk.hex(C.dim)("┐");
72
- const bot = chalk.hex(C.dim)(" └" + "─".repeat(40) + "┘");
73
- const body = hlLines.map((l, i) => {
74
- const ln = chalk.hex(C.dimmer)(String(i + 1).padStart(gw));
75
- return chalk.hex(C.dim)("") + chalk.hex(C.dimmer)(" " + ln + "") + l;
83
+ const bw2 = Math.min(cw + 2, tw() - 6);
84
+ const lbl = lang ? chalk.hex(C.blue).bold(` ${lang} `) : "";
85
+ const lblLen = lang ? lang.length + 2 : 0;
86
+ const dashes = Math.max(2, bw2 - lblLen - 1);
87
+ const maxCode = bw2 - gw - 4;
88
+
89
+ const top = chalk.hex(C.dim)("┌─") + lbl + chalk.hex(C.dimmer)("".repeat(dashes)) + chalk.hex(C.dim)("");
90
+ const bot = chalk.hex(C.dim)("└" + "─".repeat(bw2 + 1) + "┘");
91
+ const body = hlLines.map((hl_line, i) => {
92
+ const ln = chalk.hex(C.dimmer)(String(i + 1).padStart(gw));
93
+ const raw = lines[i] ?? "";
94
+ const disp = raw.length > maxCode ? hl_line.slice(0, maxCode * 3) + chalk.hex(C.dimmer)("…") : hl_line;
95
+ return chalk.hex(C.dim)("│") + chalk.hex(C.dimmer)(" " + ln + " ╎ ") + disp;
76
96
  }).join("\n");
77
97
 
78
98
  return `\n${top}\n${body}\n${bot}\n`;
79
99
  });
80
100
 
81
- // Inline code
82
- r = r.replace(/`([^`\n]+)`/g, (_, c) =>
83
- chalk.bgHex("#2A2A3E")(chalk.hex(C.orange)(" " + c + " "))
84
- );
85
-
86
- // Headers
87
- r = r.replace(/^### (.+)$/gm, (_, t) => "\n" + chalk.hex(C.yellow).bold(" ◈ " + t));
88
- r = r.replace(/^## (.+)$/gm, (_, t) => "\n" + chalk.hex(C.blue).bold.underline(" " + t));
89
- r = r.replace(/^# (.+)$/gm, (_, t) => "\n" + chalk.hex(C.teal).bold(" ◉ " + t.toUpperCase()));
90
-
91
- // Bold / italic
101
+ r = r.replace(/`([^`\n]+)`/g, (_, c) => chalk.bgHex("#2A2A3E")(chalk.hex(C.orange)(" " + c + " ")));
102
+ r = r.replace(/^### (.+)$/gm, (_, t) => "\n" + chalk.hex(C.yellow).bold("◈ " + t));
103
+ r = r.replace(/^## (.+)$/gm, (_, t) => "\n" + chalk.hex(C.blue).bold.underline(t));
104
+ r = r.replace(/^# (.+)$/gm, (_, t) => "\n" + chalk.hex(C.teal).bold("◉ " + t.toUpperCase()));
92
105
  r = r.replace(/\*\*\*(.+?)\*\*\*/g, (_, t) => chalk.bold.italic(t));
93
106
  r = r.replace(/\*\*(.+?)\*\*/g, (_, t) => chalk.bold(t));
94
107
  r = r.replace(/\*(.+?)\*/g, (_, t) => chalk.italic(t));
108
+ r = r.replace(/^> (.+)$/gm, (_, t) => chalk.hex(C.purple)("▎ ") + chalk.hex(C.muted).italic(t));
109
+ r = r.replace(/^(\s*)[*\-+] (.+)$/gm, (_, i, t) => i + chalk.hex(C.teal)("◆ ") + chalk.hex(C.white)(t));
110
+ r = r.replace(/^(\s*)(\d+)\. (.+)$/gm, (_, i, n, t) => i + chalk.hex(C.blue)(chalk.bold(n + ".") + " ") + chalk.hex(C.white)(t));
111
+ r = r.replace(/^---+$/gm, chalk.hex(C.dimmer)("╌".repeat(Math.min(cw, 48))));
112
+ return r;
113
+ }
95
114
 
96
- // Blockquote
97
- r = r.replace(/^> (.+)$/gm, (_, t) =>
98
- chalk.hex(C.purple)(" ▎ ") + chalk.hex(C.muted).italic(t)
99
- );
115
+ // ─── Box printer ─────────────────────────────────────────────────
116
+ function printBox(contentLines, borderHex, label, extra = "") {
117
+ const W = bw();
118
+ const inner = W - 2;
119
+ const lblLen = vlen(label) + vlen(extra);
120
+ const dashes = Math.max(0, W - lblLen - 1);
100
121
 
101
- // Lists
102
- r = r.replace(/^(\s*)[*\-+] (.+)$/gm, (_, i, t) => i + chalk.hex(C.teal)("") + chalk.hex(C.white)(t));
103
- r = r.replace(/^(\s*)(\d+)\. (.+)$/gm, (_, i, n, t) => i + chalk.hex(C.blue)(" " + chalk.bold(n + ".") + " ") + chalk.hex(C.white)(t));
122
+ process.stdout.write(
123
+ "\n" + chalk.hex(borderHex)("╭─") + label +
124
+ chalk.hex(C.dim)("".repeat(dashes)) + extra + "\n"
125
+ );
104
126
 
105
- // HR
106
- r = r.replace(/^---+$/gm, chalk.hex(C.dimmer)(" " + "╌".repeat(48)));
127
+ for (const line of contentLines) {
128
+ if (line.includes("\x1b[")) {
129
+ // Already styled (code blocks, headers) — print as-is
130
+ process.stdout.write(chalk.hex(borderHex)("│ ") + line + "\n");
131
+ } else {
132
+ wrapText(line || " ", inner).forEach(wl =>
133
+ process.stdout.write(chalk.hex(borderHex)("│ ") + wl + "\n")
134
+ );
135
+ }
136
+ }
107
137
 
108
- return r;
138
+ process.stdout.write(chalk.hex(borderHex)("╰" + "─".repeat(W + 1)) + "\n");
109
139
  }
110
140
 
111
- // ─────────────────────────────────────────────────────────────────
112
- // User message
113
- // ─────────────────────────────────────────────────────────────────
141
+ // ─── Messages ────────────────────────────────────────────────────
114
142
  export function printUser(text) {
115
- const lines = text.split("\n");
116
- const isMulti = lines.length > 1;
117
- const W = 50;
118
-
119
- if (!isMulti) {
120
- process.stdout.write(
121
- "\n" +
122
- chalk.hex(C.blue)(" ╭─") +
123
- chalk.hex(C.blue).bold(" you ") +
124
- chalk.hex(C.dim)("─".repeat(W - 5)) + "\n" +
125
- chalk.hex(C.blue)(" │ ") + chalk.hex(C.white)(text) + "\n" +
126
- chalk.hex(C.blue)(" ╰" + "─".repeat(W + 2)) + "\n"
127
- );
128
- return;
129
- }
130
-
131
- process.stdout.write(
132
- "\n" +
133
- chalk.hex(C.blue)(" ╭─") +
134
- chalk.hex(C.blue).bold(" you ") +
135
- chalk.hex(C.dim)("─".repeat(W - 5)) +
136
- chalk.hex(C.dim)(` (${lines.length} lines)`) + "\n"
137
- );
138
- lines.forEach(l =>
139
- process.stdout.write(chalk.hex(C.blue)(" │ ") + chalk.hex(C.white)(l) + "\n")
140
- );
141
- process.stdout.write(chalk.hex(C.blue)(" ╰" + "─".repeat(W + 2)) + "\n");
143
+ const lines = text.split("\n");
144
+ const multi = lines.length > 1;
145
+ const label = chalk.hex(C.blue).bold(" you ");
146
+ const extra = multi ? chalk.hex(C.muted)(` (${lines.length} lines) `) : "";
147
+ printBox(lines, C.blue, label, extra);
142
148
  }
143
149
 
144
- // ─────────────────────────────────────────────────────────────────
145
- // Gemini response
146
- // ─────────────────────────────────────────────────────────────────
147
150
  export function printAssistant(text) {
148
- const W = 50;
149
- const rendered = renderMarkdown(text).trimEnd();
150
- const lines = rendered.split("\n");
151
-
152
- process.stdout.write(
153
- "\n" +
154
- chalk.hex(C.teal)(" ╭─") +
155
- chalk.hex(C.teal).bold(" gemini ") +
156
- chalk.hex(C.dim)("─".repeat(W - 8)) + "\n"
157
- );
158
- lines.forEach(l =>
159
- process.stdout.write(chalk.hex(C.teal)(" │") + " " + l + "\n")
160
- );
161
- process.stdout.write(
162
- chalk.hex(C.teal)(" ╰" + "─".repeat(W + 2)) + "\n\n"
163
- );
151
+ const inner = bw() - 2;
152
+ const rendered = renderMarkdown(text.trimEnd(), inner);
153
+ printBox(rendered.split("\n"), C.teal, chalk.hex(C.teal).bold(" gemini "));
154
+ process.stdout.write("\n");
164
155
  }
165
156
 
166
- // ─────────────────────────────────────────────────────────────────
167
- // Agent step block — one block per iteration, fresh each time
168
- // ─────────────────────────────────────────────────────────────────
169
- export function printStepHeader(step, label = "working") {
170
- const tag = chalk.hex(C.yellow).bold(` ${label} `);
171
- const num = step > 1 ? chalk.hex(C.dim)(` step ${step}`) : "";
172
- const trail = chalk.hex(C.dim)("─".repeat(Math.max(2, 40 - label.length)));
157
+ // ─── Agent step blocks ───────────────────────────────────────────
158
+ export function printStepHeader(step) {
159
+ const W = bw();
160
+ const label = chalk.hex(C.yellow).bold(" working ");
161
+ const num = step > 1 ? chalk.hex(C.dim)(` step ${step} `) : " ";
162
+ const trail = Math.max(2, W - 9 - vlen(num));
173
163
  process.stdout.write(
174
- "\n" +
175
- chalk.hex(C.yellow)(" ╭─") + tag + trail + num + "\n"
164
+ "\n" + chalk.hex(C.yellow)("╭─") + label +
165
+ chalk.hex(C.dim)("".repeat(trail)) + num + "\n"
176
166
  );
177
167
  }
178
168
 
179
169
  export function printStepFooter() {
180
- process.stdout.write(
181
- chalk.hex(C.yellow)(" ╰" + "─".repeat(50)) + "\n"
182
- );
170
+ process.stdout.write(chalk.hex(C.yellow)("╰" + "─".repeat(bw() + 1)) + "\n");
183
171
  }
184
172
 
185
173
  export function printToolCall(name, args = {}) {
186
- const argStr = Object.entries(args)
187
- .map(([k, v]) => {
188
- const s = String(v).replace(/\n/g, "");
189
- return chalk.hex(C.muted)(k + ":") + chalk.hex(C.orange)(s.length > 50 ? s.slice(0, 50) + "…" : s);
190
- })
191
- .join(" ");
192
-
174
+ const argStr = Object.entries(args).map(([k, v]) => {
175
+ const raw = String(v).replace(/\n/g, "↵");
176
+ const val = vlen(raw) > 42 ? raw.slice(0, 42) + "" : raw;
177
+ return chalk.hex(C.muted)(k + ":") + chalk.hex(C.orange)(val);
178
+ }).join(" ");
193
179
  process.stdout.write(
194
- chalk.hex(C.yellow)(" ├─ ") +
195
- chalk.hex(C.blue).bold(name) +
180
+ chalk.hex(C.yellow)("├─ ") + chalk.hex(C.blue).bold(name) +
196
181
  (argStr ? " " + argStr : "") + "\n"
197
182
  );
198
183
  }
199
184
 
200
185
  export function printToolResult(result) {
186
+ const W = bw() - 5;
201
187
  const isErr = typeof result === "object" && result.error;
202
188
  const text = typeof result === "object"
203
189
  ? (result.result ?? result.error ?? JSON.stringify(result, null, 2))
204
190
  : String(result);
205
191
  const color = isErr ? chalk.hex(C.red) : chalk.hex(C.muted);
206
- const border = isErr ? chalk.hex(C.red) : chalk.hex(C.dimmer);
192
+ const border = chalk.hex(C.dimmer)("│ ");
207
193
  const lines = text.split("\n");
208
- const shown = lines.slice(0, 12);
209
- const extra = lines.length > 12 ? lines.length - 12 : 0;
210
-
194
+ const shown = lines.slice(0, 10);
195
+ const extra = lines.length - 10;
211
196
  shown.forEach(l =>
212
- process.stdout.write(border(" │ ") + color(l) + "\n")
197
+ process.stdout.write(border + color(l.length > W ? l.slice(0, W) + "…" : l) + "\n")
213
198
  );
214
- if (extra > 0)
215
- process.stdout.write(border(" │ ") + chalk.hex(C.dim)(`… +${extra} more lines`) + "\n");
199
+ if (extra > 0) process.stdout.write(border + chalk.hex(C.dim)(`… +${extra} more lines`) + "\n");
216
200
  }
217
201
 
218
- // ─────────────────────────────────────────────────────────────────
219
- // Status messages
220
- // ─────────────────────────────────────────────────────────────────
221
- export function printThinking(step) {
222
- process.stdout.write(
223
- "\n" +
224
- chalk.hex(C.dim)(" ◌ ") +
225
- chalk.hex(C.muted)("thinking") +
226
- (step > 1 ? chalk.hex(C.dim)(` · step ${step}`) : "") +
227
- chalk.hex(C.dim)(" …") + "\n"
228
- );
229
- }
202
+ // ─── Status ──────────────────────────────────────────────────────
203
+ export function printError(msg) { process.stdout.write("\n" + chalk.hex(C.red)("╳ ") + chalk.hex(C.red).bold("error ") + chalk.hex(C.muted)(msg) + "\n\n"); }
204
+ export function printInfo(msg) { process.stdout.write(chalk.hex(C.dim)("· ") + chalk.hex(C.white)(msg) + "\n"); }
205
+ export function printSuccess(msg) { process.stdout.write(chalk.hex(C.teal)("✓ ") + chalk.hex(C.teal)(msg) + "\n"); }
206
+ export function printWarning(msg) { process.stdout.write(chalk.hex(C.yellow)("⚠ ") + chalk.hex(C.yellow)(msg) + "\n"); }
230
207
 
231
- export function printError(msg) {
232
- process.stdout.write(
233
- "\n" +
234
- chalk.hex(C.red)(" ╳ ") +
235
- chalk.hex(C.red).bold("error") +
236
- chalk.hex(C.muted)(" " + msg) + "\n\n"
237
- );
238
- }
239
- export function printInfo(msg) {
240
- process.stdout.write(
241
- chalk.hex(C.dim)(" · ") + chalk.hex(C.white)(msg) + "\n"
242
- );
243
- }
244
- export function printSuccess(msg) {
245
- process.stdout.write(
246
- chalk.hex(C.teal)(" ✓ ") + chalk.hex(C.teal)(msg) + "\n"
247
- );
248
- }
249
- export function printWarning(msg) {
250
- process.stdout.write(
251
- chalk.hex(C.yellow)(" ⚠ ") + chalk.hex(C.yellow)(msg) + "\n"
252
- );
253
- }
254
-
255
- // ─────────────────────────────────────────────────────────────────
256
- // Welcome
257
- // ─────────────────────────────────────────────────────────────────
208
+ // ─── Welcome ─────────────────────────────────────────────────────
258
209
  export function renderWelcome(memCount = 0, extCount = 0) {
210
+ const W = bw();
259
211
  const stats = [
260
212
  memCount ? `${memCount} context file${memCount > 1 ? "s" : ""}` : null,
261
213
  extCount ? `${extCount} extension${extCount > 1 ? "s" : ""}` : null,
262
214
  ].filter(Boolean).join(" · ");
263
-
264
215
  return [
265
216
  "",
266
- chalk.hex(C.dim)(" ┌─────────────────────────────────────────────────┐"),
267
- chalk.hex(C.dim)(" ") + " " + chalk.hex(C.teal).bold("Gemini") + chalk.hex(C.blue).bold(" CLI") + chalk.hex(C.dim)(" ─ AI Agent ─ native function calling ") + chalk.hex(C.dim)("│"),
268
- chalk.hex(C.dim)(" └─────────────────────────────────────────────────┘"),
269
- stats
270
- ? chalk.hex(C.dim)(" ") + chalk.hex(C.muted)(stats)
271
- : "",
272
- chalk.hex(C.dim)(" ") + chalk.hex(C.dim)("/help") + chalk.hex(C.dim)(" · ") + chalk.hex(C.dim)("/agent") + chalk.hex(C.dim)(" toggle tools · ") + chalk.hex(C.dim)("/yolo") + chalk.hex(C.dim)(" skip confirms"),
217
+ chalk.hex(C.dim)("" + "─".repeat(W + 1) + "┐"),
218
+ chalk.hex(C.dim)(" ") + chalk.hex(C.teal).bold("Gemini") + chalk.hex(C.blue).bold(" CLI") +
219
+ chalk.hex(C.dim)(" ─ AI Agent ─ native function calling") + chalk.hex(C.dim)(" │"),
220
+ chalk.hex(C.dim)("└" + "─".repeat(W + 1) + "┘"),
221
+ stats ? chalk.hex(C.muted)(" " + stats) : "",
222
+ chalk.hex(C.dim)(" /help · /agent toggle · /yolo skip confirms"),
273
223
  "",
274
224
  ].join("\n");
275
225
  }
276
226
 
277
- // ─────────────────────────────────────────────────────────────────
278
- // Help
279
- // ─────────────────────────────────────────────────────────────────
227
+ // ─── Help ────────────────────────────────────────────────────────
280
228
  export function renderHelp(customCommands = {}) {
281
- const sep = chalk.hex(C.dimmer)(" " + "─".repeat(52));
282
- const row = (cmd, desc) =>
283
- " " + chalk.hex(C.blue).bold(cmd.padEnd(26)) + chalk.hex(C.muted)(desc);
284
-
229
+ const sep = chalk.hex(C.dimmer)(" " + "─".repeat(Math.min(tw() - 4, 52)));
230
+ const row = (cmd, desc) => " " + chalk.hex(C.blue).bold(cmd.padEnd(24)) + chalk.hex(C.muted)(desc);
285
231
  const lines = [
286
- "",
287
- chalk.hex(C.teal).bold(" Commands"),
288
- sep,
289
- row("/agent", "Toggle agent mode ↔ chat mode"),
290
- row("/yolo", "Skip all tool confirmations"),
291
- sep,
292
- row("/memory show", "Show loaded GEMINI.md context files"),
293
- row("/memory reload", "Reload all context from disk"),
294
- row("/memory add <text>", "Append text to ~/.gemini/GEMINI.md"),
295
- sep,
296
- row("/ext list", "List installed extensions"),
297
- row("/ext install <src>", "Install from local path or git URL"),
298
- row("/ext uninstall <n>", "Uninstall extension"),
299
- row("/ext enable/disable", "Toggle extension on/off"),
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 session system instruction"),
304
- row("/history", "Show conversation turns"),
305
- row("/export <file.json>", "Export conversation to JSON"),
306
- row("/cd <path>", "Change working directory"),
307
- row("/new /clear", "Reset conversation"),
308
- row("/model", "Show model & config"),
309
- row("/proxy [on|off]", "Proxy rotation status / toggle"),
310
- row("/exit /quit", "Exit"),
311
- sep,
232
+ "", chalk.hex(C.teal).bold(" Commands"), sep,
233
+ row("/agent", "Toggle agent ↔ chat mode"),
234
+ row("/yolo", "Skip all tool confirmations"), sep,
235
+ row("/memory show", "Show loaded GEMINI.md files"),
236
+ row("/memory reload", "Reload context from disk"),
237
+ row("/memory add <text>", "Append to ~/.gemini/GEMINI.md"), sep,
238
+ row("/ext list", "List extensions"),
239
+ row("/ext install <src>", "Install (path or git URL)"),
240
+ row("/ext uninstall <n>", "Uninstall extension"),
241
+ row("/ext enable <n>", "Enable extension"),
242
+ row("/ext disable <n>", "Disable extension"),
243
+ row("/ext update <n>", "Pull latest from git"), sep,
244
+ row("/file <path>", "Attach file to message"),
245
+ row("/system <text>", "Set system instruction"),
246
+ row("/history", "Show conversation turns"),
247
+ row("/export <file>", "Export history to JSON"),
248
+ row("/cd <path>", "Change working directory"),
249
+ row("/new /clear", "Reset conversation"),
250
+ row("/model", "Model & config info"),
251
+ row("/proxy [on|off]", "Proxy rotation status/toggle"),
252
+ row("/exit /quit", "Exit"), sep,
253
+ chalk.hex(C.dim)(" Ctrl+C interrupt · Ctrl+D exit"), "",
312
254
  ];
313
-
314
255
  if (Object.keys(customCommands).length) {
315
- lines.push(chalk.hex(C.purple).bold(" Extension Commands"), sep);
316
- Object.entries(customCommands).forEach(([k, c]) =>
317
- lines.push(row("/" + k, c.description ?? ""))
256
+ lines.splice(-2, 0,
257
+ chalk.hex(C.purple).bold(" Extension Commands"), sep,
258
+ ...Object.entries(customCommands).map(([k, c]) => row("/" + k, c.description ?? "")),
259
+ sep
318
260
  );
319
- lines.push(sep);
320
261
  }
321
-
322
- lines.push(
323
- chalk.hex(C.dim)(" Paste multi-line code freely."),
324
- chalk.hex(C.dim)(" Ctrl+C interrupt · Ctrl+D exit"),
325
- "",
326
- );
327
262
  return lines.join("\n");
328
263
  }