@ikyyofc/gemini-cli 2.0.9 → 3.0.1
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 +1 -1
- package/src/renderer.js +293 -241
package/package.json
CHANGED
package/src/renderer.js
CHANGED
|
@@ -1,328 +1,380 @@
|
|
|
1
|
-
// src/renderer.js
|
|
1
|
+
// src/renderer.js
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
|
|
4
4
|
const C = {
|
|
5
|
-
blue: "#4A9EFF",
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
|
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
|
+
// Word-wrap a PLAIN (no ANSI) string to maxW, returns array of lines
|
|
17
|
+
function wrapPlain(text, maxW) {
|
|
18
|
+
if (!text || text.length <= maxW) return [text || ""];
|
|
19
|
+
const words = text.split(" ");
|
|
20
|
+
const lines = [];
|
|
21
|
+
let cur = "";
|
|
22
|
+
for (const w of words) {
|
|
23
|
+
const candidate = cur ? cur + " " + w : w;
|
|
24
|
+
if (candidate.length <= maxW) { cur = candidate; continue; }
|
|
25
|
+
if (cur) lines.push(cur);
|
|
26
|
+
if (w.length > maxW) {
|
|
27
|
+
// hard-wrap single very long word (e.g. bare URL)
|
|
28
|
+
for (let i = 0; i < w.length; i += maxW) lines.push(w.slice(i, i + maxW));
|
|
29
|
+
cur = "";
|
|
30
|
+
} else { cur = w; }
|
|
31
|
+
}
|
|
32
|
+
if (cur) lines.push(cur);
|
|
33
|
+
return lines.length ? lines : [""];
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
// ─────────────────────────────────────────────────────────────────
|
|
24
|
-
// Syntax highlighting
|
|
37
|
+
// Syntax highlighting — applyToRaw prevents placeholder corruption
|
|
25
38
|
// ─────────────────────────────────────────────────────────────────
|
|
26
39
|
const KW = {
|
|
27
|
-
js
|
|
28
|
-
ts
|
|
29
|
-
py
|
|
30
|
-
go
|
|
31
|
-
sh
|
|
32
|
-
rs
|
|
33
|
-
json: null,
|
|
40
|
+
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,
|
|
41
|
+
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,
|
|
42
|
+
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,
|
|
43
|
+
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,
|
|
44
|
+
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,
|
|
45
|
+
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,
|
|
34
46
|
};
|
|
35
47
|
const LANGMAP = {
|
|
36
|
-
javascript:"js",
|
|
37
|
-
python:"py",
|
|
38
|
-
rust:"rs",
|
|
39
|
-
json:"json", jsonc:"json",
|
|
48
|
+
javascript:"js",js:"js",typescript:"ts",ts:"ts",
|
|
49
|
+
python:"py",py:"py",go:"go",golang:"go",
|
|
50
|
+
rust:"rs",rs:"rs",bash:"sh",sh:"sh",shell:"sh",zsh:"sh",fish:"sh",
|
|
40
51
|
};
|
|
41
52
|
|
|
53
|
+
function applyToRaw(str, re, fn) {
|
|
54
|
+
return str.split(/(\x00\d+\x00)/).map((p, i) => i % 2 === 0 ? p.replace(re, fn) : p).join("");
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
function highlight(code, lang = "") {
|
|
43
58
|
const l = LANGMAP[lang.toLowerCase()] || "";
|
|
44
|
-
if (!l
|
|
45
|
-
let r = code;
|
|
59
|
+
if (!l) return code;
|
|
46
60
|
const saved = [];
|
|
47
61
|
const save = s => { const id = `\x00${saved.length}\x00`; saved.push(s); return id; };
|
|
48
|
-
r =
|
|
49
|
-
r = r
|
|
50
|
-
|
|
51
|
-
r = r
|
|
52
|
-
r = r
|
|
53
|
-
|
|
62
|
+
let r = code;
|
|
63
|
+
r = applyToRaw(r, /(\/\/.*$|#.*$|\/\*[\s\S]*?\*\/)/gm, m => save(chalk.hex(C.comment).italic(m)));
|
|
64
|
+
r = applyToRaw(r, /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, m => save(chalk.hex(C.str)(m)));
|
|
65
|
+
if (KW[l]) { KW[l].lastIndex = 0; r = applyToRaw(r, KW[l], m => save(chalk.hex(C.kw).bold(m))); }
|
|
66
|
+
r = applyToRaw(r, /\b(\d+\.?\d*)\b/g, m => save(chalk.hex(C.num)(m)));
|
|
67
|
+
r = applyToRaw(r, /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=\()/g, m => save(chalk.hex(C.fn)(m)));
|
|
68
|
+
return r.replace(/\x00(\d+)\x00/g, (_, i) => saved[+i] ?? "");
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
// ─────────────────────────────────────────────────────────────────
|
|
57
|
-
// Markdown →
|
|
72
|
+
// Markdown → array of display lines (with wrapping baked in)
|
|
73
|
+
// Returns: Array<{ text: string, raw: boolean }>
|
|
74
|
+
// raw=true → ANSI already applied (code blocks), print as-is
|
|
75
|
+
// raw=false → plain text, will receive border + chalk in printBox
|
|
58
76
|
// ─────────────────────────────────────────────────────────────────
|
|
59
|
-
export function renderMarkdown(text) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
77
|
+
export function renderMarkdown(text, contentW) {
|
|
78
|
+
const cw = contentW ?? bw() - 2;
|
|
79
|
+
const out = []; // { text, raw }
|
|
80
|
+
|
|
81
|
+
const pushPlain = (s) => out.push({ text: s, raw: false });
|
|
82
|
+
const pushStyled = (s) => out.push({ text: s, raw: true });
|
|
83
|
+
const pushBlank = () => out.push({ text: "", raw: false });
|
|
84
|
+
|
|
85
|
+
// Normalise line endings
|
|
86
|
+
const src = text.replace(/\r\n/g, "\n").trimEnd();
|
|
87
|
+
|
|
88
|
+
// Split into tokens: code blocks vs everything else
|
|
89
|
+
const segments = src.split(/(```[\s\S]*?```)/g);
|
|
90
|
+
|
|
91
|
+
for (const seg of segments) {
|
|
92
|
+
// ── Fenced code block ──────────────────────────────────────
|
|
93
|
+
if (seg.startsWith("```")) {
|
|
94
|
+
const m = seg.match(/^```(\w*)\n?([\s\S]*?)```$/);
|
|
95
|
+
const lang = m?.[1] ?? "";
|
|
96
|
+
const code = m?.[2]?.trimEnd() ?? seg.slice(3);
|
|
97
|
+
const lines = code.split("\n");
|
|
98
|
+
const hl = highlight(code, lang);
|
|
99
|
+
const hlLines = hl.split("\n");
|
|
100
|
+
const gw = String(lines.length).length;
|
|
101
|
+
const bw2 = Math.min(cw + 2, tw() - 6);
|
|
102
|
+
const lbl = lang ? chalk.hex(C.blue).bold(` ${lang} `) : "";
|
|
103
|
+
const lblLen = lang ? lang.length + 2 : 0;
|
|
104
|
+
const dashes = Math.max(2, bw2 - lblLen - 1);
|
|
105
|
+
const maxCode = bw2 - gw - 4;
|
|
106
|
+
|
|
107
|
+
pushBlank();
|
|
108
|
+
pushStyled(chalk.hex(C.dim)("┌─") + lbl + chalk.hex(C.dimmer)("─".repeat(dashes)) + chalk.hex(C.dim)("┐"));
|
|
109
|
+
hlLines.forEach((hl_line, i) => {
|
|
110
|
+
const ln = chalk.hex(C.dimmer)(String(i + 1).padStart(gw));
|
|
111
|
+
const raw = lines[i] ?? "";
|
|
112
|
+
const disp = raw.length > maxCode ? hl_line.slice(0, maxCode * 3) + chalk.hex(C.dimmer)("…") : hl_line;
|
|
113
|
+
pushStyled(chalk.hex(C.dim)("│") + chalk.hex(C.dimmer)(" " + ln + " ╎ ") + disp);
|
|
114
|
+
});
|
|
115
|
+
pushStyled(chalk.hex(C.dim)("└" + "─".repeat(bw2 + 1) + "┘"));
|
|
116
|
+
pushBlank();
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Text segment — process line by line ───────────────────
|
|
121
|
+
for (const rawLine of seg.split("\n")) {
|
|
122
|
+
const line = rawLine;
|
|
123
|
+
|
|
124
|
+
// Blank line
|
|
125
|
+
if (!line.trim()) { pushBlank(); continue; }
|
|
126
|
+
|
|
127
|
+
// === Header === (non-standard but model uses it)
|
|
128
|
+
if (/^={3,}\s*.+\s*={3,}$/.test(line)) {
|
|
129
|
+
const t = line.replace(/^=+\s*/, "").replace(/\s*=+$/, "");
|
|
130
|
+
pushBlank();
|
|
131
|
+
pushStyled(chalk.hex(C.blue).bold.underline(t));
|
|
132
|
+
pushBlank();
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ATX headers
|
|
137
|
+
if (/^### /.test(line)) { pushStyled(chalk.hex(C.yellow).bold("◈ " + line.slice(4))); continue; }
|
|
138
|
+
if (/^## /.test(line)) { pushStyled(chalk.hex(C.blue).bold.underline(line.slice(3))); continue; }
|
|
139
|
+
if (/^# /.test(line)) { pushStyled(chalk.hex(C.teal).bold("◉ " + line.slice(2).toUpperCase())); continue; }
|
|
140
|
+
|
|
141
|
+
// HR
|
|
142
|
+
if (/^---+$/.test(line)) { pushStyled(chalk.hex(C.dimmer)("╌".repeat(Math.min(cw, 48)))); continue; }
|
|
143
|
+
|
|
144
|
+
// Blockquote
|
|
145
|
+
if (/^> /.test(line)) {
|
|
146
|
+
const t = line.slice(2);
|
|
147
|
+
wrapPlain(t, cw - 2).forEach((wl, i) =>
|
|
148
|
+
pushStyled(chalk.hex(C.purple)("▎ ") + chalk.hex(C.muted).italic(wl))
|
|
149
|
+
);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Unordered list
|
|
154
|
+
const ulM = line.match(/^(\s*)[*\-+] (.+)$/);
|
|
155
|
+
if (ulM) {
|
|
156
|
+
const [, ind, content] = ulM;
|
|
157
|
+
const indW = ind.length;
|
|
158
|
+
const itemW = cw - indW - 4; // "◆ " = 2 chars visible + 2 padding
|
|
159
|
+
const plain = stripInline(content);
|
|
160
|
+
wrapPlain(plain, itemW).forEach((wl, i) => {
|
|
161
|
+
if (i === 0)
|
|
162
|
+
pushStyled(ind + chalk.hex(C.teal)("◆ ") + chalk.hex(C.white)(applyInline(wl)));
|
|
163
|
+
else
|
|
164
|
+
pushStyled(ind + " " + chalk.hex(C.white)(applyInline(wl)));
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Ordered list
|
|
170
|
+
const olM = line.match(/^(\s*)(\d+)\. (.+)$/);
|
|
171
|
+
if (olM) {
|
|
172
|
+
const [, ind, num, content] = olM;
|
|
173
|
+
const markerW = num.length + 2;
|
|
174
|
+
const itemW = cw - ind.length - markerW;
|
|
175
|
+
const plain = stripInline(content);
|
|
176
|
+
wrapPlain(plain, itemW).forEach((wl, i) => {
|
|
177
|
+
if (i === 0)
|
|
178
|
+
pushStyled(ind + chalk.hex(C.blue)(chalk.bold(num + ".") + " ") + chalk.hex(C.white)(applyInline(wl)));
|
|
179
|
+
else
|
|
180
|
+
pushStyled(ind + " ".repeat(markerW) + chalk.hex(C.white)(applyInline(wl)));
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Plain paragraph — strip inline markup for wrapping, then re-apply
|
|
186
|
+
const plain = stripInline(line);
|
|
187
|
+
wrapPlain(plain, cw).forEach(wl =>
|
|
188
|
+
pushStyled(chalk.hex(C.white)(applyInline(wl)))
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
100
192
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
r = r.replace(/^(\s*)(\d+)\. (.+)$/gm, (_, i, n, t) => i + chalk.hex(C.blue)(" " + chalk.bold(n + ".") + " ") + chalk.hex(C.white)(t));
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
104
195
|
|
|
105
|
-
|
|
106
|
-
|
|
196
|
+
// Strip inline markdown to get plain text for accurate wrapping
|
|
197
|
+
function stripInline(s) {
|
|
198
|
+
return s
|
|
199
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // links → text only
|
|
200
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
201
|
+
.replace(/\*\*\*(.+?)\*\*\*/g, "$1")
|
|
202
|
+
.replace(/\*\*(.+?)\*\*/g, "$1")
|
|
203
|
+
.replace(/\*(.+?)\*/g, "$1")
|
|
204
|
+
.replace(/_(.+?)_/g, "$1");
|
|
205
|
+
}
|
|
107
206
|
|
|
108
|
-
|
|
207
|
+
// Apply inline markdown styling to already-wrapped line
|
|
208
|
+
function applyInline(s) {
|
|
209
|
+
return s
|
|
210
|
+
// Links → show "text (url truncated)" — URLs not clickable in terminal
|
|
211
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => {
|
|
212
|
+
const short = url.length > 40 ? url.slice(0, 40) + "…" : url;
|
|
213
|
+
return chalk.hex(C.teal)(text) + chalk.hex(C.dimmer)(" (" + short + ")");
|
|
214
|
+
})
|
|
215
|
+
.replace(/`([^`]+)`/g, (_, c) => chalk.bgHex("#2A2A3E")(chalk.hex(C.orange)(" " + c + " ")))
|
|
216
|
+
.replace(/\*\*\*(.+?)\*\*\*/g, (_, t) => chalk.bold.italic(t))
|
|
217
|
+
.replace(/\*\*(.+?)\*\*/g, (_, t) => chalk.bold(t))
|
|
218
|
+
.replace(/\*(.+?)\*/g, (_, t) => chalk.italic(t))
|
|
219
|
+
.replace(/_(.+?)_/g, (_, t) => chalk.italic(t));
|
|
109
220
|
}
|
|
110
221
|
|
|
111
222
|
// ─────────────────────────────────────────────────────────────────
|
|
112
|
-
//
|
|
223
|
+
// Box printer — all lines are pre-wrapped, just add border
|
|
113
224
|
// ─────────────────────────────────────────────────────────────────
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
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
|
-
}
|
|
225
|
+
function printBox(renderedLines, borderHex, label, extra = "") {
|
|
226
|
+
const W = bw();
|
|
227
|
+
const lblLen = vlen(label) + vlen(extra);
|
|
228
|
+
const dashes = Math.max(0, W - lblLen - 1);
|
|
130
229
|
|
|
131
230
|
process.stdout.write(
|
|
132
|
-
"\n" +
|
|
133
|
-
chalk.hex(C.
|
|
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")
|
|
231
|
+
"\n" + chalk.hex(borderHex)("╭─") + label +
|
|
232
|
+
chalk.hex(C.dim)("─".repeat(dashes)) + extra + "\n"
|
|
140
233
|
);
|
|
141
|
-
|
|
234
|
+
|
|
235
|
+
for (const { text, raw } of renderedLines) {
|
|
236
|
+
// raw lines (code blocks, headers with box-drawing) — print as-is
|
|
237
|
+
// non-raw lines — already plain/styled text, just add border
|
|
238
|
+
process.stdout.write(chalk.hex(borderHex)("│ ") + text + "\n");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
process.stdout.write(chalk.hex(borderHex)("╰" + "─".repeat(W + 1)) + "\n");
|
|
142
242
|
}
|
|
143
243
|
|
|
144
244
|
// ─────────────────────────────────────────────────────────────────
|
|
145
|
-
//
|
|
245
|
+
// Messages
|
|
146
246
|
// ─────────────────────────────────────────────────────────────────
|
|
147
|
-
export function
|
|
148
|
-
const W
|
|
149
|
-
const
|
|
150
|
-
const
|
|
247
|
+
export function printUser(text) {
|
|
248
|
+
const W = bw();
|
|
249
|
+
const inner = W - 2;
|
|
250
|
+
const rawLines = text.split("\n");
|
|
251
|
+
const multi = rawLines.length > 1;
|
|
252
|
+
const label = chalk.hex(C.blue).bold(" you ");
|
|
253
|
+
const extra = multi ? chalk.hex(C.muted)(` (${rawLines.length} lines) `) : "";
|
|
254
|
+
|
|
255
|
+
const lines = multi
|
|
256
|
+
? rawLines.flatMap(l => wrapPlain(l, inner).map(wl => ({ text: chalk.hex(C.white)(wl), raw: true })))
|
|
257
|
+
: wrapPlain(text, inner).map(wl => ({ text: chalk.hex(C.white)(wl), raw: true }));
|
|
258
|
+
|
|
259
|
+
printBox(lines, C.blue, label, extra);
|
|
260
|
+
}
|
|
151
261
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
);
|
|
262
|
+
export function printAssistant(text) {
|
|
263
|
+
const inner = bw() - 2;
|
|
264
|
+
const lines = renderMarkdown(text.trimEnd(), inner);
|
|
265
|
+
printBox(lines, C.teal, chalk.hex(C.teal).bold(" gemini "));
|
|
266
|
+
process.stdout.write("\n");
|
|
164
267
|
}
|
|
165
268
|
|
|
166
269
|
// ─────────────────────────────────────────────────────────────────
|
|
167
|
-
// Agent step
|
|
270
|
+
// Agent step blocks
|
|
168
271
|
// ─────────────────────────────────────────────────────────────────
|
|
169
|
-
export function printStepHeader(step
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
272
|
+
export function printStepHeader(step) {
|
|
273
|
+
const W = bw();
|
|
274
|
+
const label = chalk.hex(C.yellow).bold(" working ");
|
|
275
|
+
const num = step > 1 ? chalk.hex(C.dim)(` step ${step} `) : " ";
|
|
276
|
+
const trail = Math.max(2, W - 9 - vlen(num));
|
|
173
277
|
process.stdout.write(
|
|
174
|
-
"\n" +
|
|
175
|
-
chalk.hex(C.
|
|
278
|
+
"\n" + chalk.hex(C.yellow)("╭─") + label +
|
|
279
|
+
chalk.hex(C.dim)("─".repeat(trail)) + num + "\n"
|
|
176
280
|
);
|
|
177
281
|
}
|
|
178
282
|
|
|
179
283
|
export function printStepFooter() {
|
|
180
|
-
process.stdout.write(
|
|
181
|
-
chalk.hex(C.yellow)(" ╰" + "─".repeat(50)) + "\n"
|
|
182
|
-
);
|
|
284
|
+
process.stdout.write(chalk.hex(C.yellow)("╰" + "─".repeat(bw() + 1)) + "\n");
|
|
183
285
|
}
|
|
184
286
|
|
|
185
287
|
export function printToolCall(name, args = {}) {
|
|
186
|
-
const argStr = Object.entries(args)
|
|
187
|
-
.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
.join(" ");
|
|
192
|
-
|
|
288
|
+
const argStr = Object.entries(args).map(([k, v]) => {
|
|
289
|
+
const raw = String(v).replace(/\n/g, "↵");
|
|
290
|
+
const val = raw.length > 42 ? raw.slice(0, 42) + "…" : raw;
|
|
291
|
+
return chalk.hex(C.muted)(k + ":") + chalk.hex(C.orange)(val);
|
|
292
|
+
}).join(" ");
|
|
193
293
|
process.stdout.write(
|
|
194
|
-
chalk.hex(C.yellow)("
|
|
195
|
-
chalk.hex(C.blue).bold(name) +
|
|
294
|
+
chalk.hex(C.yellow)("├─ ") + chalk.hex(C.blue).bold(name) +
|
|
196
295
|
(argStr ? " " + argStr : "") + "\n"
|
|
197
296
|
);
|
|
198
297
|
}
|
|
199
298
|
|
|
200
299
|
export function printToolResult(result) {
|
|
300
|
+
const W = bw() - 5;
|
|
201
301
|
const isErr = typeof result === "object" && result.error;
|
|
202
302
|
const text = typeof result === "object"
|
|
203
303
|
? (result.result ?? result.error ?? JSON.stringify(result, null, 2))
|
|
204
304
|
: String(result);
|
|
205
305
|
const color = isErr ? chalk.hex(C.red) : chalk.hex(C.muted);
|
|
206
|
-
const border =
|
|
306
|
+
const border = chalk.hex(C.dimmer)("│ ");
|
|
207
307
|
const lines = text.split("\n");
|
|
208
|
-
const shown = lines.slice(0,
|
|
209
|
-
const extra = lines.length
|
|
210
|
-
|
|
308
|
+
const shown = lines.slice(0, 10);
|
|
309
|
+
const extra = lines.length - 10;
|
|
211
310
|
shown.forEach(l =>
|
|
212
|
-
process.stdout.write(border
|
|
311
|
+
process.stdout.write(border + color(l.length > W ? l.slice(0, W) + "…" : l) + "\n")
|
|
213
312
|
);
|
|
214
|
-
if (extra > 0)
|
|
215
|
-
process.stdout.write(border(" │ ") + chalk.hex(C.dim)(`… +${extra} more lines`) + "\n");
|
|
313
|
+
if (extra > 0) process.stdout.write(border + chalk.hex(C.dim)(`… +${extra} more lines`) + "\n");
|
|
216
314
|
}
|
|
217
315
|
|
|
218
316
|
// ─────────────────────────────────────────────────────────────────
|
|
219
|
-
// Status
|
|
317
|
+
// Status
|
|
220
318
|
// ─────────────────────────────────────────────────────────────────
|
|
221
|
-
export function
|
|
222
|
-
process.stdout.write(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
chalk.hex(C.muted)("thinking") +
|
|
226
|
-
(step > 1 ? chalk.hex(C.dim)(` · step ${step}`) : "") +
|
|
227
|
-
chalk.hex(C.dim)(" …") + "\n"
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
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
|
-
}
|
|
319
|
+
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"); }
|
|
320
|
+
export function printInfo(msg) { process.stdout.write(chalk.hex(C.dim)("· ") + chalk.hex(C.white)(msg) + "\n"); }
|
|
321
|
+
export function printSuccess(msg) { process.stdout.write(chalk.hex(C.teal)("✓ ") + chalk.hex(C.teal)(msg) + "\n"); }
|
|
322
|
+
export function printWarning(msg) { process.stdout.write(chalk.hex(C.yellow)("⚠ ") + chalk.hex(C.yellow)(msg) + "\n"); }
|
|
254
323
|
|
|
255
324
|
// ─────────────────────────────────────────────────────────────────
|
|
256
|
-
// Welcome
|
|
325
|
+
// Welcome & Help
|
|
257
326
|
// ─────────────────────────────────────────────────────────────────
|
|
258
327
|
export function renderWelcome(memCount = 0, extCount = 0) {
|
|
328
|
+
const W = bw();
|
|
259
329
|
const stats = [
|
|
260
330
|
memCount ? `${memCount} context file${memCount > 1 ? "s" : ""}` : null,
|
|
261
331
|
extCount ? `${extCount} extension${extCount > 1 ? "s" : ""}` : null,
|
|
262
332
|
].filter(Boolean).join(" · ");
|
|
263
|
-
|
|
264
333
|
return [
|
|
265
334
|
"",
|
|
266
|
-
chalk.hex(C.dim)("
|
|
267
|
-
chalk.hex(C.dim)("
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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"),
|
|
335
|
+
chalk.hex(C.dim)("┌" + "─".repeat(W + 1) + "┐"),
|
|
336
|
+
chalk.hex(C.dim)("│ ") + chalk.hex(C.teal).bold("Gemini") + chalk.hex(C.blue).bold(" CLI") +
|
|
337
|
+
chalk.hex(C.dim)(" ─ AI Agent ─ native function calling") + chalk.hex(C.dim)(" │"),
|
|
338
|
+
chalk.hex(C.dim)("└" + "─".repeat(W + 1) + "┘"),
|
|
339
|
+
stats ? chalk.hex(C.muted)(" " + stats) : "",
|
|
340
|
+
chalk.hex(C.dim)(" /help · /agent toggle · /yolo skip confirms"),
|
|
273
341
|
"",
|
|
274
342
|
].join("\n");
|
|
275
343
|
}
|
|
276
344
|
|
|
277
|
-
// ─────────────────────────────────────────────────────────────────
|
|
278
|
-
// Help
|
|
279
|
-
// ─────────────────────────────────────────────────────────────────
|
|
280
345
|
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
|
-
|
|
346
|
+
const sep = chalk.hex(C.dimmer)(" " + "─".repeat(Math.min(tw() - 4, 52)));
|
|
347
|
+
const row = (cmd, desc) => " " + chalk.hex(C.blue).bold(cmd.padEnd(24)) + chalk.hex(C.muted)(desc);
|
|
285
348
|
const lines = [
|
|
286
|
-
"",
|
|
287
|
-
|
|
288
|
-
sep,
|
|
289
|
-
row("/
|
|
290
|
-
row("/
|
|
291
|
-
sep,
|
|
292
|
-
row("/
|
|
293
|
-
row("/
|
|
294
|
-
row("/
|
|
295
|
-
|
|
296
|
-
row("/ext
|
|
297
|
-
row("/ext
|
|
298
|
-
row("/
|
|
299
|
-
row("/
|
|
300
|
-
row("/
|
|
301
|
-
|
|
302
|
-
row("/
|
|
303
|
-
row("/
|
|
304
|
-
row("/
|
|
305
|
-
row("/
|
|
306
|
-
row("/
|
|
307
|
-
|
|
308
|
-
row("/model", "Show model & config"),
|
|
309
|
-
row("/proxy [on|off]", "Proxy rotation status / toggle"),
|
|
310
|
-
row("/exit /quit", "Exit"),
|
|
311
|
-
sep,
|
|
349
|
+
"", chalk.hex(C.teal).bold(" Commands"), sep,
|
|
350
|
+
row("/agent", "Toggle agent ↔ chat mode"),
|
|
351
|
+
row("/yolo", "Skip all tool confirmations"), sep,
|
|
352
|
+
row("/memory show", "Show loaded GEMINI.md files"),
|
|
353
|
+
row("/memory reload", "Reload context from disk"),
|
|
354
|
+
row("/memory add <text>", "Append to ~/.gemini/GEMINI.md"), sep,
|
|
355
|
+
row("/ext list", "List extensions"),
|
|
356
|
+
row("/ext install <src>", "Install (path or git URL)"),
|
|
357
|
+
row("/ext uninstall <n>", "Uninstall extension"),
|
|
358
|
+
row("/ext enable <n>", "Enable extension"),
|
|
359
|
+
row("/ext disable <n>", "Disable extension"),
|
|
360
|
+
row("/ext update <n>", "Pull latest from git"), sep,
|
|
361
|
+
row("/file <path>", "Attach file to message"),
|
|
362
|
+
row("/system <text>", "Set system instruction"),
|
|
363
|
+
row("/history", "Show conversation turns"),
|
|
364
|
+
row("/export <file>", "Export history to JSON"),
|
|
365
|
+
row("/cd <path>", "Change working directory"),
|
|
366
|
+
row("/new /clear", "Reset conversation"),
|
|
367
|
+
row("/model", "Model & config info"),
|
|
368
|
+
row("/proxy [on|off]", "Proxy rotation status/toggle"),
|
|
369
|
+
row("/exit /quit", "Exit"), sep,
|
|
370
|
+
chalk.hex(C.dim)(" Ctrl+C interrupt · Ctrl+D exit"), "",
|
|
312
371
|
];
|
|
313
|
-
|
|
314
372
|
if (Object.keys(customCommands).length) {
|
|
315
|
-
lines.
|
|
316
|
-
|
|
317
|
-
|
|
373
|
+
lines.splice(-2, 0,
|
|
374
|
+
chalk.hex(C.purple).bold(" Extension Commands"), sep,
|
|
375
|
+
...Object.entries(customCommands).map(([k, c]) => row("/" + k, c.description ?? "")),
|
|
376
|
+
sep
|
|
318
377
|
);
|
|
319
|
-
lines.push(sep);
|
|
320
378
|
}
|
|
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
379
|
return lines.join("\n");
|
|
328
380
|
}
|