@ikyyofc/gemini-cli 1.0.0 → 1.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/index.js +193 -222
- package/package.json +1 -1
- package/src/input.js +88 -0
- package/src/renderer.js +97 -115
package/index.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// ═══════════════════════════════════════════════════════════════
|
|
3
|
-
// Gemini CLI v2 — AI Agent
|
|
4
|
-
// Native function calling · GEMINI.md context hierarchy
|
|
5
|
-
// Extension system · Custom commands · /memory · /ext
|
|
6
|
-
// ═══════════════════════════════════════════════════════════════
|
|
7
2
|
import readline from "readline";
|
|
8
3
|
import fs from "fs";
|
|
9
4
|
import path from "path";
|
|
@@ -15,7 +10,7 @@ import {
|
|
|
15
10
|
loadMemory, buildContextString, memoryShow, memoryAdd, ensureGlobalDir, GLOBAL_DIR
|
|
16
11
|
} from "./src/memory.js";
|
|
17
12
|
import {
|
|
18
|
-
loadExtensions,
|
|
13
|
+
loadExtensions, getExtensionContextDirs,
|
|
19
14
|
installExtension, uninstallExtension, enableExtension, listExtensions, updateExtension
|
|
20
15
|
} from "./src/extensions.js";
|
|
21
16
|
import {
|
|
@@ -23,340 +18,316 @@ import {
|
|
|
23
18
|
printUser, printAssistant,
|
|
24
19
|
printError, printInfo, printSuccess, printWarning
|
|
25
20
|
} from "./src/renderer.js";
|
|
21
|
+
import {
|
|
22
|
+
PasteTransform, restorePaste,
|
|
23
|
+
enableBracketedPaste, disableBracketedPaste
|
|
24
|
+
} from "./src/input.js";
|
|
26
25
|
|
|
27
26
|
// ─────────────────────────────────────────────────────────────────
|
|
28
27
|
// Bootstrap
|
|
29
28
|
// ─────────────────────────────────────────────────────────────────
|
|
30
29
|
ensureGlobalDir();
|
|
31
30
|
|
|
32
|
-
let extensions
|
|
33
|
-
let
|
|
34
|
-
let
|
|
35
|
-
let memoryContext = null; // concatenated string
|
|
31
|
+
let extensions = loadExtensions();
|
|
32
|
+
let memoryLoaded = [];
|
|
33
|
+
let memoryContext = null;
|
|
36
34
|
|
|
37
35
|
function reloadAll() {
|
|
38
|
-
extensions
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
memoryContext = buildContextString(memoryLoaded);
|
|
36
|
+
extensions = loadExtensions();
|
|
37
|
+
const extra = getExtensionContextDirs(extensions);
|
|
38
|
+
memoryLoaded = loadMemory(extra);
|
|
39
|
+
memoryContext = buildContextString(memoryLoaded);
|
|
43
40
|
}
|
|
44
41
|
reloadAll();
|
|
45
42
|
|
|
46
43
|
// ─────────────────────────────────────────────────────────────────
|
|
47
44
|
// State
|
|
48
45
|
// ─────────────────────────────────────────────────────────────────
|
|
49
|
-
let
|
|
50
|
-
let sessionSystem
|
|
51
|
-
let pendingFile
|
|
52
|
-
let
|
|
53
|
-
let
|
|
54
|
-
let agentMode
|
|
55
|
-
let autoApprove
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const parts = [memoryContext, sessionSystem].filter(Boolean);
|
|
60
|
-
return parts.length ? parts.join("\n\n") : null;
|
|
46
|
+
let history = [];
|
|
47
|
+
let sessionSystem = null;
|
|
48
|
+
let pendingFile = null;
|
|
49
|
+
let pendingPath = null;
|
|
50
|
+
let processing = false;
|
|
51
|
+
let agentMode = true;
|
|
52
|
+
let autoApprove = false;
|
|
53
|
+
|
|
54
|
+
function systemInstruction() {
|
|
55
|
+
return [memoryContext, sessionSystem].filter(Boolean).join("\n\n") || null;
|
|
61
56
|
}
|
|
62
57
|
|
|
63
58
|
// ─────────────────────────────────────────────────────────────────
|
|
64
|
-
// Readline
|
|
59
|
+
// Readline — pipe stdin through PasteTransform first
|
|
65
60
|
// ─────────────────────────────────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
enableBracketedPaste();
|
|
62
|
+
process.on("exit", disableBracketedPaste);
|
|
63
|
+
|
|
64
|
+
const pasteStream = new PasteTransform();
|
|
65
|
+
process.stdin.pipe(pasteStream);
|
|
66
|
+
|
|
67
|
+
const rl = readline.createInterface({
|
|
68
|
+
input: pasteStream,
|
|
69
|
+
output: process.stdout,
|
|
70
|
+
terminal: true,
|
|
71
|
+
historySize: 200,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
function prompt() {
|
|
75
|
+
const mode = agentMode ? chalk.hex("#4EC9B0")("agent") : chalk.dim("chat");
|
|
76
|
+
const yolo = autoApprove ? chalk.red(" yolo") : "";
|
|
77
|
+
const file = pendingFile ? chalk.yellow(` +${path.basename(pendingPath)}`) : "";
|
|
78
|
+
const turns = history.length ? chalk.dim(` ${Math.ceil(history.length/2)}t`) : "";
|
|
79
|
+
const mem = memoryLoaded.length ? chalk.dim(` m${memoryLoaded.length}`) : "";
|
|
80
|
+
|
|
81
|
+
rl.setPrompt(
|
|
82
|
+
"\n" +
|
|
83
|
+
chalk.dim(" ") + chalk.bold("❯") + " " +
|
|
84
|
+
chalk.dim("[") + mode + yolo + file + turns + mem + chalk.dim("]") +
|
|
85
|
+
" "
|
|
86
|
+
);
|
|
75
87
|
rl.prompt();
|
|
76
88
|
}
|
|
77
89
|
|
|
78
90
|
// ─────────────────────────────────────────────────────────────────
|
|
79
91
|
// File attachment
|
|
80
92
|
// ─────────────────────────────────────────────────────────────────
|
|
81
|
-
function
|
|
93
|
+
function attachFile(filePath) {
|
|
82
94
|
const p = path.resolve(filePath.trim().replace(/^['"]|['"]$/g, ""));
|
|
83
|
-
if (!fs.existsSync(p))
|
|
84
|
-
if (fs.statSync(p).size > 20*1024*1024)
|
|
85
|
-
pendingFile
|
|
86
|
-
|
|
87
|
-
printSuccess(`
|
|
88
|
-
printInfo("Will be sent with your next message.");
|
|
95
|
+
if (!fs.existsSync(p)) { printError(`not found: ${p}`); return; }
|
|
96
|
+
if (fs.statSync(p).size > 20*1024*1024) { printError("file too large (max 20MB)"); return; }
|
|
97
|
+
pendingFile = fs.readFileSync(p);
|
|
98
|
+
pendingPath = p;
|
|
99
|
+
printSuccess(`attached ${path.basename(p)}`);
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
// ─────────────────────────────────────────────────────────────────
|
|
92
|
-
//
|
|
103
|
+
// Send message
|
|
93
104
|
// ─────────────────────────────────────────────────────────────────
|
|
94
|
-
async function
|
|
105
|
+
async function send(rawInput) {
|
|
106
|
+
// restorePaste turns \x00 back into \n (from bracketed paste)
|
|
107
|
+
const userText = restorePaste(rawInput).trim();
|
|
108
|
+
if (!userText) return;
|
|
109
|
+
|
|
110
|
+
printUser(userText + (pendingFile ? chalk.dim(` [${path.basename(pendingPath)}]`) : ""));
|
|
111
|
+
|
|
112
|
+
if (agentMode) {
|
|
113
|
+
const res = await runAgentLoop(userText, history, {
|
|
114
|
+
systemInstruction: systemInstruction(),
|
|
115
|
+
autoApprove,
|
|
116
|
+
maxIterations: 40,
|
|
117
|
+
});
|
|
118
|
+
if (res?.finalResponse) {
|
|
119
|
+
history.push({ role: "user", content: userText });
|
|
120
|
+
history.push({ role: "assistant", content: res.finalResponse });
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
const { default: ora } = await import("ora");
|
|
124
|
+
const sp = ora({ text: "thinking…", spinner: "dots", color: "cyan", prefixText: " " }).start();
|
|
125
|
+
const msgs = [];
|
|
126
|
+
if (systemInstruction()) msgs.push({ role: "system", content: systemInstruction() });
|
|
127
|
+
msgs.push(...history, { role: "user", content: userText });
|
|
128
|
+
try {
|
|
129
|
+
const t0 = Date.now();
|
|
130
|
+
const reply = await chat(msgs, pendingFile || null);
|
|
131
|
+
sp.succeed(chalk.dim(`done (${((Date.now()-t0)/1000).toFixed(1)}s)`));
|
|
132
|
+
history.push({ role: "user", content: userText }, { role: "assistant", content: reply });
|
|
133
|
+
printAssistant(reply);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
sp.fail("failed");
|
|
136
|
+
printError(e.message);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
pendingFile = null;
|
|
141
|
+
pendingPath = null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─────────────────────────────────────────────────────────────────
|
|
145
|
+
// Commands
|
|
146
|
+
// ─────────────────────────────────────────────────────────────────
|
|
147
|
+
async function command(input) {
|
|
95
148
|
const tokens = input.trim().split(/\s+/);
|
|
96
149
|
const cmd = tokens[0].toLowerCase();
|
|
97
150
|
const arg = tokens.slice(1).join(" ").trim();
|
|
98
151
|
|
|
99
|
-
//
|
|
152
|
+
// /memory
|
|
100
153
|
if (cmd === "/memory") {
|
|
101
|
-
const sub = tokens[1]
|
|
102
|
-
if (sub === "show")
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!text) { printError("Usage: /memory add <text>"); return; }
|
|
110
|
-
const result = memoryAdd(text);
|
|
111
|
-
printSuccess(result);
|
|
112
|
-
reloadAll();
|
|
113
|
-
} else {
|
|
114
|
-
printInfo("Usage: /memory [show|reload|add <text>]");
|
|
115
|
-
}
|
|
154
|
+
const sub = tokens[1];
|
|
155
|
+
if (sub === "show") { console.log(memoryShow(memoryLoaded)); }
|
|
156
|
+
else if (sub === "reload") { reloadAll(); printSuccess(`reloaded — ${memoryLoaded.length} file(s)`); }
|
|
157
|
+
else if (sub === "add") {
|
|
158
|
+
const t = tokens.slice(2).join(" ");
|
|
159
|
+
if (!t) { printError("usage: /memory add <text>"); return; }
|
|
160
|
+
printSuccess(memoryAdd(t)); reloadAll();
|
|
161
|
+
} else { printInfo("/memory [show | reload | add <text>]"); }
|
|
116
162
|
return;
|
|
117
163
|
}
|
|
118
164
|
|
|
119
|
-
//
|
|
165
|
+
// /ext
|
|
120
166
|
if (cmd === "/ext") {
|
|
121
|
-
const sub
|
|
167
|
+
const sub = tokens[1];
|
|
122
168
|
const name = tokens[2];
|
|
123
|
-
|
|
124
169
|
if (sub === "list") {
|
|
125
170
|
const list = listExtensions();
|
|
126
|
-
if (!list.length) { printInfo("
|
|
171
|
+
if (!list.length) { printInfo("no extensions installed"); return; }
|
|
127
172
|
console.log("");
|
|
128
173
|
list.forEach(e => {
|
|
129
|
-
const
|
|
130
|
-
console.log(
|
|
131
|
-
` ${status} ` +
|
|
132
|
-
chalk.hex("#DCDCAA").bold(e.name) +
|
|
133
|
-
chalk.hex("#858585")(` v${e.version}`) +
|
|
134
|
-
(e.description ? chalk.hex("#858585")(` — ${e.description}`) : "")
|
|
135
|
-
);
|
|
174
|
+
const s = e.enabled ? chalk.green("✓") : chalk.dim("○");
|
|
175
|
+
console.log(` ${s} ${chalk.bold(e.name)} ${chalk.dim("v"+e.version)} ${chalk.dim(e.description)}`);
|
|
136
176
|
});
|
|
137
177
|
console.log("");
|
|
138
|
-
|
|
139
178
|
} else if (sub === "install") {
|
|
140
|
-
if (!arg.slice(arg.indexOf(" ")+1).trim() && !name) { printError("Usage: /ext install <path-or-git-url>"); return; }
|
|
141
179
|
const src = tokens.slice(2).join(" ");
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
180
|
+
if (!src) { printError("usage: /ext install <path-or-url>"); return; }
|
|
181
|
+
printInfo(`installing ${src}…`);
|
|
182
|
+
const r = await installExtension(src);
|
|
183
|
+
r.error ? printError(r.error) : (printSuccess(`installed: ${r.name}`), reloadAll());
|
|
147
184
|
} else if (sub === "uninstall") {
|
|
148
|
-
if (!name) { printError("
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
else { printSuccess(`Uninstalled: ${name}`); reloadAll(); }
|
|
152
|
-
|
|
185
|
+
if (!name) { printError("usage: /ext uninstall <name>"); return; }
|
|
186
|
+
const r = await uninstallExtension(name);
|
|
187
|
+
r.error ? printError(r.error) : (printSuccess(`removed: ${name}`), reloadAll());
|
|
153
188
|
} else if (sub === "enable") {
|
|
154
|
-
if (!name) { printError("
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
189
|
+
if (!name) { printError("usage: /ext enable <name>"); return; }
|
|
190
|
+
const r = enableExtension(name, true);
|
|
191
|
+
r.error ? printError(r.error) : (printSuccess(`enabled: ${name}`), reloadAll());
|
|
158
192
|
} else if (sub === "disable") {
|
|
159
|
-
if (!name) { printError("
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
193
|
+
if (!name) { printError("usage: /ext disable <name>"); return; }
|
|
194
|
+
const r = enableExtension(name, false);
|
|
195
|
+
r.error ? printError(r.error) : (printSuccess(`disabled: ${name}`), reloadAll());
|
|
163
196
|
} else if (sub === "update") {
|
|
164
|
-
if (!name) { printError("
|
|
165
|
-
printInfo(`
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
} else {
|
|
170
|
-
printInfo("Usage: /ext [list|install|uninstall|enable|disable|update] [name]");
|
|
171
|
-
}
|
|
197
|
+
if (!name) { printError("usage: /ext update <name>"); return; }
|
|
198
|
+
printInfo(`updating ${name}…`);
|
|
199
|
+
const r = await updateExtension(name);
|
|
200
|
+
r.error ? printError(r.error) : (printSuccess(`updated: ${name}`), reloadAll());
|
|
201
|
+
} else { printInfo("/ext [list | install | uninstall | enable | disable | update]"); }
|
|
172
202
|
return;
|
|
173
203
|
}
|
|
174
204
|
|
|
175
|
-
// ── Built-in commands ────────────────────────────────────────
|
|
176
205
|
switch (cmd) {
|
|
177
|
-
case "/help":
|
|
178
|
-
console.log(renderHelp(customCommands));
|
|
179
|
-
break;
|
|
206
|
+
case "/help": console.log(renderHelp()); break;
|
|
180
207
|
|
|
181
208
|
case "/new": case "/clear":
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
printInfo("Conversation cleared.");
|
|
209
|
+
history = []; pendingFile = null; pendingPath = null;
|
|
210
|
+
printInfo("conversation cleared");
|
|
185
211
|
break;
|
|
186
212
|
|
|
187
213
|
case "/file":
|
|
188
|
-
if (!arg) { printError("
|
|
189
|
-
|
|
190
|
-
break;
|
|
214
|
+
if (!arg) { printError("usage: /file <path>"); break; }
|
|
215
|
+
attachFile(arg); break;
|
|
191
216
|
|
|
192
217
|
case "/system":
|
|
193
|
-
if (!arg) { printInfo(`
|
|
194
|
-
sessionSystem = arg;
|
|
195
|
-
printSuccess("Session system instruction set.");
|
|
196
|
-
break;
|
|
218
|
+
if (!arg) { printInfo(`system: ${sessionSystem ?? "(none)"}`); break; }
|
|
219
|
+
sessionSystem = arg; printSuccess("system instruction set"); break;
|
|
197
220
|
|
|
198
221
|
case "/agent":
|
|
199
222
|
agentMode = !agentMode;
|
|
200
|
-
|
|
223
|
+
printInfo(`mode: ${agentMode ? chalk.hex("#4EC9B0")("agent") + " (tools on)" : chalk.dim("chat") + " (tools off)"}`);
|
|
201
224
|
break;
|
|
202
225
|
|
|
203
226
|
case "/yolo":
|
|
204
227
|
autoApprove = !autoApprove;
|
|
205
|
-
autoApprove ? printWarning("
|
|
228
|
+
autoApprove ? printWarning("yolo on — no confirmations") : printInfo("yolo off — confirmations restored");
|
|
206
229
|
break;
|
|
207
230
|
|
|
208
231
|
case "/history":
|
|
209
|
-
if (!
|
|
232
|
+
if (!history.length) { printInfo("no history"); break; }
|
|
210
233
|
console.log("");
|
|
211
|
-
|
|
212
|
-
const who
|
|
213
|
-
const body
|
|
214
|
-
|
|
215
|
-
console.log(chalk.hex("#858585")(` [${i+1}] `) + who + chalk.hex("#858585")(": ") + chalk.hex("#D4D4D4")(short));
|
|
234
|
+
history.forEach((m, i) => {
|
|
235
|
+
const who = m.role === "user" ? chalk.hex("#4A9EFF")("you") : chalk.hex("#4EC9B0")("gemini");
|
|
236
|
+
const body = String(m.content).replace(/\n/g," ").slice(0, 80);
|
|
237
|
+
console.log(chalk.dim(` [${i+1}] `) + who + chalk.dim(": ") + body);
|
|
216
238
|
});
|
|
217
239
|
console.log("");
|
|
218
240
|
break;
|
|
219
241
|
|
|
220
242
|
case "/export":
|
|
221
|
-
if (!arg) { printError("
|
|
222
|
-
fs.writeFileSync(path.resolve(arg), JSON.stringify({
|
|
223
|
-
|
|
243
|
+
if (!arg) { printError("usage: /export <file.json>"); break; }
|
|
244
|
+
fs.writeFileSync(path.resolve(arg), JSON.stringify({
|
|
245
|
+
exported_at: new Date().toISOString(),
|
|
246
|
+
mode: agentMode ? "agent" : "chat",
|
|
247
|
+
system: systemInstruction(),
|
|
248
|
+
messages: history
|
|
249
|
+
}, null, 2));
|
|
250
|
+
printSuccess(`exported to ${arg}`);
|
|
224
251
|
break;
|
|
225
252
|
|
|
226
|
-
case "/cwd":
|
|
227
|
-
printInfo(`Working directory: ${process.cwd()}`);
|
|
228
|
-
break;
|
|
253
|
+
case "/cwd": printInfo(process.cwd()); break;
|
|
229
254
|
|
|
230
255
|
case "/cd":
|
|
231
|
-
if (!arg) { printError("
|
|
232
|
-
try { process.chdir(path.resolve(arg));
|
|
256
|
+
if (!arg) { printError("usage: /cd <path>"); break; }
|
|
257
|
+
try { process.chdir(path.resolve(arg)); printInfo(process.cwd()); }
|
|
258
|
+
catch (e) { printError(e.message); }
|
|
233
259
|
break;
|
|
234
260
|
|
|
235
261
|
case "/model":
|
|
236
|
-
printInfo(
|
|
237
|
-
printInfo("
|
|
238
|
-
printInfo("
|
|
239
|
-
printInfo(
|
|
240
|
-
printInfo(`
|
|
241
|
-
printInfo(`Auto-approve : ${autoApprove ? "ON (YOLO)" : "OFF"}`);
|
|
242
|
-
printInfo(`Memory files : ${memoryLoaded.length} | Extensions: ${extensions.length}`);
|
|
243
|
-
printInfo(`Global config: ${GLOBAL_DIR}`);
|
|
262
|
+
printInfo(`model gemini-pro-latest`);
|
|
263
|
+
printInfo(`tools ${agentMode ? "on (native function calling)" : "off"}`);
|
|
264
|
+
printInfo(`yolo ${autoApprove ? "on" : "off"}`);
|
|
265
|
+
printInfo(`memory ${memoryLoaded.length} file(s) | extensions: ${extensions.length}`);
|
|
266
|
+
printInfo(`config ${GLOBAL_DIR}`);
|
|
244
267
|
break;
|
|
245
268
|
|
|
246
269
|
case "/exit": case "/quit":
|
|
247
|
-
printInfo("
|
|
248
|
-
rl.close(); process.exit(0);
|
|
249
|
-
break;
|
|
270
|
+
printInfo("bye"); rl.close(); process.exit(0); break;
|
|
250
271
|
|
|
251
|
-
default:
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const c = customCommands[customKey];
|
|
256
|
-
const msg = (c.prompt ?? "").replace("{{args}}", arg);
|
|
257
|
-
if (msg) await sendMessage(msg);
|
|
258
|
-
else printError(`Command "${customKey}" has no prompt defined.`);
|
|
272
|
+
default:
|
|
273
|
+
// Custom extension commands: /namespace:cmd <args>
|
|
274
|
+
if (cmd.slice(1).includes(":")) {
|
|
275
|
+
printError(`unknown command: ${cmd} — type /help`);
|
|
259
276
|
} else {
|
|
260
|
-
printError(`
|
|
277
|
+
printError(`unknown command: ${cmd} — type /help`);
|
|
261
278
|
}
|
|
262
|
-
}
|
|
263
279
|
}
|
|
264
280
|
}
|
|
265
281
|
|
|
266
|
-
// ─────────────────────────────────────────────────────────────────
|
|
267
|
-
// Send message
|
|
268
|
-
// ─────────────────────────────────────────────────────────────────
|
|
269
|
-
async function sendMessage(userText) {
|
|
270
|
-
if (!userText.trim()) return;
|
|
271
|
-
printUser(userText + (pendingFile ? chalk.hex("#DCDCAA")(` [📎 ${path.basename(pendingFilePath)}]`) : ""));
|
|
272
|
-
|
|
273
|
-
const sysInstruction = buildSystemInstruction();
|
|
274
|
-
|
|
275
|
-
if (agentMode) {
|
|
276
|
-
const result = await runAgentLoop(userText, conversationHistory, {
|
|
277
|
-
systemInstruction: sysInstruction,
|
|
278
|
-
autoApprove,
|
|
279
|
-
maxIterations: 40,
|
|
280
|
-
});
|
|
281
|
-
if (result?.finalResponse) {
|
|
282
|
-
conversationHistory.push({ role: "user", content: userText });
|
|
283
|
-
conversationHistory.push({ role: "assistant", content: result.finalResponse });
|
|
284
|
-
}
|
|
285
|
-
} else {
|
|
286
|
-
const { default: ora } = await import("ora");
|
|
287
|
-
const spinner = ora({ text: chalk.hex("#858585")(" Thinking…"), spinner: "dots12", color: "cyan", prefixText: " " }).start();
|
|
288
|
-
const msgs = [];
|
|
289
|
-
if (sysInstruction) msgs.push({ role: "system", content: sysInstruction });
|
|
290
|
-
msgs.push(...conversationHistory, { role: "user", content: userText });
|
|
291
|
-
try {
|
|
292
|
-
const t0 = Date.now();
|
|
293
|
-
const reply = await chat(msgs, pendingFile || null);
|
|
294
|
-
spinner.succeed(chalk.hex("#4EC9B0")("Done") + chalk.hex("#858585")(` (${((Date.now()-t0)/1000).toFixed(1)}s)`));
|
|
295
|
-
conversationHistory.push({ role: "user", content: userText }, { role: "assistant", content: reply });
|
|
296
|
-
printAssistant(reply);
|
|
297
|
-
} catch (err) {
|
|
298
|
-
spinner.fail(chalk.hex("#F44747")("Failed"));
|
|
299
|
-
printError(err.message);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
pendingFile = null; pendingFilePath = null;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
282
|
// ─────────────────────────────────────────────────────────────────
|
|
307
283
|
// Main
|
|
308
284
|
// ─────────────────────────────────────────────────────────────────
|
|
309
285
|
async function main() {
|
|
310
286
|
process.stdout.write("\x1Bc");
|
|
311
|
-
console.log(renderWelcome(memoryLoaded.length, extensions.length
|
|
287
|
+
console.log(renderWelcome(memoryLoaded.length, extensions.length));
|
|
312
288
|
|
|
313
|
-
// Parse flags
|
|
289
|
+
// Parse CLI flags
|
|
314
290
|
const argv = process.argv.slice(2);
|
|
315
291
|
const positional = [];
|
|
316
292
|
for (let i = 0; i < argv.length; i++) {
|
|
317
293
|
const a = argv[i];
|
|
318
|
-
if (a === "--system" && argv[i+1]) { sessionSystem = argv[++i];
|
|
319
|
-
else if (a === "--file" && argv[i+1]) {
|
|
320
|
-
else if (a === "--yolo") { autoApprove = true; printWarning("
|
|
294
|
+
if (a === "--system" && argv[i+1]) { sessionSystem = argv[++i]; }
|
|
295
|
+
else if (a === "--file" && argv[i+1]) { attachFile(argv[++i]); }
|
|
296
|
+
else if (a === "--yolo") { autoApprove = true; printWarning("yolo on"); }
|
|
321
297
|
else if (a === "--chat") { agentMode = false; }
|
|
322
298
|
else if (!a.startsWith("--")) { positional.push(a); }
|
|
323
299
|
}
|
|
324
300
|
|
|
325
|
-
// One-shot
|
|
301
|
+
// One-shot mode
|
|
326
302
|
if (positional.length > 0) {
|
|
327
|
-
await
|
|
303
|
+
await send(positional.join(" "));
|
|
328
304
|
process.exit(0);
|
|
329
305
|
}
|
|
330
306
|
|
|
331
|
-
|
|
332
|
-
printInfo(
|
|
333
|
-
`Mode: ${agentMode
|
|
334
|
-
? chalk.hex("#4EC9B0").bold("AGENT") + chalk.hex("#858585")(" (native function calling · filesystem · shell · web)")
|
|
335
|
-
: chalk.hex("#858585").bold("CHAT")
|
|
336
|
-
} | ${chalk.hex("#CE9178")("/agent")} toggle | ${chalk.hex("#CE9178")("/yolo")} skip confirms | ${chalk.hex("#CE9178")("/help")} commands`
|
|
337
|
-
);
|
|
338
|
-
if (memoryContext) {
|
|
339
|
-
printInfo(`Context: ${chalk.hex("#C586C0")(memoryLoaded.length + " GEMINI.md file(s) loaded")} — use ${chalk.hex("#CE9178")("/memory show")} to inspect`);
|
|
340
|
-
}
|
|
341
|
-
console.log("");
|
|
307
|
+
prompt();
|
|
342
308
|
|
|
343
|
-
|
|
309
|
+
rl.on("line", async raw => {
|
|
310
|
+
const input = restorePaste(raw).trim();
|
|
311
|
+
if (!input) { prompt(); return; }
|
|
312
|
+
if (processing) { printWarning("still thinking…"); prompt(); return; }
|
|
344
313
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (!input) { setPrompt(); return; }
|
|
348
|
-
if (isProcessing) { printWarning("Still processing…"); setPrompt(); return; }
|
|
349
|
-
isProcessing = true; rl.pause();
|
|
314
|
+
processing = true;
|
|
315
|
+
rl.pause();
|
|
350
316
|
try {
|
|
351
|
-
if (input.startsWith("/")) await
|
|
352
|
-
else await
|
|
317
|
+
if (input.startsWith("/")) await command(input);
|
|
318
|
+
else await send(raw); // pass raw so send() can restorePaste
|
|
353
319
|
} finally {
|
|
354
|
-
|
|
320
|
+
processing = false;
|
|
321
|
+
rl.resume();
|
|
322
|
+
prompt();
|
|
355
323
|
}
|
|
356
324
|
});
|
|
357
325
|
|
|
358
|
-
rl.on("close", () => { console.log("
|
|
359
|
-
rl.on("SIGINT", () => {
|
|
326
|
+
rl.on("close", () => { console.log(""); process.exit(0); });
|
|
327
|
+
rl.on("SIGINT", () => {
|
|
328
|
+
if (processing) { printWarning("press Ctrl+C again to force quit"); return; }
|
|
329
|
+
rl.close();
|
|
330
|
+
});
|
|
360
331
|
}
|
|
361
332
|
|
|
362
|
-
main().catch(
|
|
333
|
+
main().catch(e => { console.error(chalk.red("fatal:"), e.message); process.exit(1); });
|
package/package.json
CHANGED
package/src/input.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/input.js — Bracketed paste mode via Transform stream
|
|
2
|
+
//
|
|
3
|
+
// How it works:
|
|
4
|
+
// Terminal sends: \x1b[200~ <pasted text with \n> \x1b[201~
|
|
5
|
+
// We detect start/end, collect paste buffer, replace \n with \x00
|
|
6
|
+
// so readline treats it as ONE line, then restore \n in the handler.
|
|
7
|
+
//
|
|
8
|
+
import { Transform } from "stream";
|
|
9
|
+
|
|
10
|
+
const PASTE_START = "\x1b[200~";
|
|
11
|
+
const PASTE_END = "\x1b[201~";
|
|
12
|
+
|
|
13
|
+
export class PasteTransform extends Transform {
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this._pasting = false;
|
|
17
|
+
this._pasteBuf = "";
|
|
18
|
+
this._raw = ""; // accumulate raw bytes for sequence detection
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_transform(chunk, _enc, cb) {
|
|
22
|
+
this._raw += chunk.toString("utf8");
|
|
23
|
+
|
|
24
|
+
let out = "";
|
|
25
|
+
|
|
26
|
+
while (this._raw.length > 0) {
|
|
27
|
+
// ── Paste START ──────────────────────────────────────────
|
|
28
|
+
const si = this._raw.indexOf(PASTE_START);
|
|
29
|
+
if (!this._pasting && si !== -1) {
|
|
30
|
+
// pass through everything before the sequence
|
|
31
|
+
out += this._raw.slice(0, si);
|
|
32
|
+
this._pasting = true;
|
|
33
|
+
this._pasteBuf = "";
|
|
34
|
+
this._raw = this._raw.slice(si + PASTE_START.length);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Paste END ────────────────────────────────────────────
|
|
39
|
+
const ei = this._raw.indexOf(PASTE_END);
|
|
40
|
+
if (this._pasting && ei !== -1) {
|
|
41
|
+
// collect everything up to end marker
|
|
42
|
+
this._pasteBuf += this._raw.slice(0, ei);
|
|
43
|
+
this._raw = this._raw.slice(ei + PASTE_END.length);
|
|
44
|
+
this._pasting = false;
|
|
45
|
+
|
|
46
|
+
// Replace newlines with \x00 so readline sees ONE line
|
|
47
|
+
// (\x00 is a null byte readline won't split on)
|
|
48
|
+
const safe = this._pasteBuf.replace(/\r\n/g, "\x00").replace(/\n/g, "\x00");
|
|
49
|
+
out += safe + "\n"; // the trailing \n makes readline fire one line event
|
|
50
|
+
this._pasteBuf = "";
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Inside paste, no end yet — wait for more data ────────
|
|
55
|
+
if (this._pasting) {
|
|
56
|
+
this._pasteBuf += this._raw;
|
|
57
|
+
this._raw = "";
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Partial escape sequence at end — wait for more data ──
|
|
62
|
+
if (this._raw.startsWith("\x1b") && this._raw.length < PASTE_START.length) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Normal data ──────────────────────────────────────────
|
|
67
|
+
out += this._raw;
|
|
68
|
+
this._raw = "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (out) this.push(out);
|
|
72
|
+
cb();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_flush(cb) {
|
|
76
|
+
if (this._pasteBuf) this.push(this._pasteBuf);
|
|
77
|
+
cb();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Restore \x00 → \n in a line received from readline */
|
|
82
|
+
export function restorePaste(line) {
|
|
83
|
+
return line.replace(/\x00/g, "\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Enable/disable bracketed paste mode on the terminal */
|
|
87
|
+
export function enableBracketedPaste() { process.stdout.write("\x1b[?2004h"); }
|
|
88
|
+
export function disableBracketedPaste() { process.stdout.write("\x1b[?2004l"); }
|
package/src/renderer.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
// src/renderer.js —
|
|
1
|
+
// src/renderer.js — Clean, minimal terminal UI
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
|
|
4
4
|
// ─────────────────────────────────────────────────────────────────
|
|
5
5
|
// Syntax highlighting
|
|
6
6
|
// ─────────────────────────────────────────────────────────────────
|
|
7
7
|
const KW = {
|
|
8
|
-
js:
|
|
9
|
-
ts:
|
|
10
|
-
py:
|
|
11
|
-
go:
|
|
12
|
-
sh:
|
|
13
|
-
rs:
|
|
8
|
+
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,
|
|
9
|
+
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
|
+
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
|
+
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,
|
|
13
|
+
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
|
+
};
|
|
15
|
+
const LANGMAP = {
|
|
16
|
+
javascript:"js",js:"js",typescript:"ts",ts:"ts",
|
|
17
|
+
python:"py",py:"py",go:"go",golang:"go",
|
|
18
|
+
rust:"rs",rs:"rs",bash:"sh",sh:"sh",shell:"sh",zsh:"sh"
|
|
14
19
|
};
|
|
15
|
-
const LANGMAP = { javascript:"js",js:"js",typescript:"ts",ts:"ts",python:"py",py:"py",go:"go",golang:"go",rust:"rs",rs:"rs",bash:"sh",sh:"sh",shell:"sh",zsh:"sh" };
|
|
16
20
|
|
|
17
21
|
function highlight(code, lang = "") {
|
|
18
22
|
const l = LANGMAP[lang.toLowerCase()] || "";
|
|
@@ -20,17 +24,11 @@ function highlight(code, lang = "") {
|
|
|
20
24
|
const saved = [];
|
|
21
25
|
const save = s => { const id = `\x00${saved.length}\x00`; saved.push(s); return id; };
|
|
22
26
|
|
|
23
|
-
// Comments
|
|
24
27
|
r = r.replace(/(\/\/.*$|#.*$|\/\*[\s\S]*?\*\/)/gm, m => save(chalk.hex("#6A9955").italic(m)));
|
|
25
|
-
// Strings
|
|
26
28
|
r = r.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, m => save(chalk.hex("#CE9178")(m)));
|
|
27
|
-
// Keywords
|
|
28
29
|
if (KW[l]) r = r.replace(KW[l], m => save(chalk.hex("#569CD6")(m)));
|
|
29
|
-
// Numbers
|
|
30
30
|
r = r.replace(/\b(\d+\.?\d*)\b/g, m => save(chalk.hex("#B5CEA8")(m)));
|
|
31
|
-
// Function calls
|
|
32
31
|
r = r.replace(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=\()/g, m => save(chalk.hex("#DCDCAA")(m)));
|
|
33
|
-
// Restore
|
|
34
32
|
return r.replace(/\x00(\d+)\x00/g, (_, i) => saved[parseInt(i)]);
|
|
35
33
|
}
|
|
36
34
|
|
|
@@ -42,21 +40,20 @@ export function renderMarkdown(text) {
|
|
|
42
40
|
|
|
43
41
|
// Fenced code blocks
|
|
44
42
|
r = r.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => {
|
|
45
|
-
const hl = highlight(code.
|
|
46
|
-
const lbl = lang ? chalk.
|
|
47
|
-
const top = chalk.
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
return `\n${top}\n${body}\n${bot}\n`;
|
|
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`;
|
|
51
48
|
});
|
|
52
49
|
|
|
53
50
|
// Inline code
|
|
54
|
-
r = r.replace(/`([^`\n]+)`/g, (_, c) => chalk.hex("#CE9178")
|
|
51
|
+
r = r.replace(/`([^`\n]+)`/g, (_, c) => chalk.hex("#CE9178")(`\`${c}\``));
|
|
55
52
|
|
|
56
53
|
// Headers
|
|
57
|
-
r = r.replace(/^### (.+)$/gm, (_, t) =>
|
|
58
|
-
r = r.replace(/^## (.+)$/gm, (_, t) =>
|
|
59
|
-
r = r.replace(/^# (.+)$/gm, (_, t) =>
|
|
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));
|
|
60
57
|
|
|
61
58
|
// Bold / italic
|
|
62
59
|
r = r.replace(/\*\*\*(.+?)\*\*\*/g, (_, t) => chalk.bold.italic(t));
|
|
@@ -64,139 +61,124 @@ export function renderMarkdown(text) {
|
|
|
64
61
|
r = r.replace(/\*(.+?)\*/g, (_, t) => chalk.italic(t));
|
|
65
62
|
|
|
66
63
|
// Blockquotes
|
|
67
|
-
r = r.replace(/^> (.+)$/gm, (_, t) => chalk.
|
|
64
|
+
r = r.replace(/^> (.+)$/gm, (_, t) => chalk.dim("▌ ") + chalk.italic(t));
|
|
68
65
|
|
|
69
66
|
// Lists
|
|
70
|
-
r = r.replace(/^(\s*)[*\-+] (.+)$/gm, (_, i, t) => `${i}
|
|
71
|
-
r = r.replace(/^(\s*)(\d+)\. (.+)$/gm, (_, i, n, t) => `${i}${chalk.
|
|
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}`);
|
|
72
69
|
|
|
73
70
|
// HR
|
|
74
|
-
r = r.replace(/^---+$/gm, chalk.
|
|
75
|
-
|
|
76
|
-
// Links
|
|
77
|
-
r = r.replace(/\[(.+?)\]\((.+?)\)/g, (_, txt, url) =>
|
|
78
|
-
chalk.bold(txt) + " " + chalk.hex("#858585").dim(`(${url})`)
|
|
79
|
-
);
|
|
71
|
+
r = r.replace(/^---+$/gm, chalk.dim("─".repeat(48)));
|
|
80
72
|
|
|
81
73
|
return r;
|
|
82
74
|
}
|
|
83
75
|
|
|
84
76
|
// ─────────────────────────────────────────────────────────────────
|
|
85
|
-
//
|
|
77
|
+
// Message printers (clean, no heavy boxing)
|
|
86
78
|
// ─────────────────────────────────────────────────────────────────
|
|
87
|
-
const W = 60;
|
|
88
|
-
|
|
89
79
|
export function printUser(text) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
80
|
+
// Show multi-line messages cleanly
|
|
81
|
+
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"));
|
|
88
|
+
}
|
|
96
89
|
}
|
|
97
90
|
|
|
98
91
|
export function printAssistant(text) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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");
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
export function printError(msg) {
|
|
109
|
-
process.stdout.write(
|
|
103
|
+
process.stdout.write(chalk.red(" error ") + chalk.dim(msg) + "\n");
|
|
110
104
|
}
|
|
111
105
|
export function printInfo(msg) {
|
|
112
|
-
process.stdout.write(chalk.
|
|
106
|
+
process.stdout.write(chalk.dim(" info ") + msg + "\n");
|
|
113
107
|
}
|
|
114
108
|
export function printSuccess(msg) {
|
|
115
|
-
process.stdout.write(chalk.
|
|
109
|
+
process.stdout.write(chalk.green(" ✓ ") + msg + "\n");
|
|
116
110
|
}
|
|
117
111
|
export function printWarning(msg) {
|
|
118
|
-
process.stdout.write(chalk.
|
|
112
|
+
process.stdout.write(chalk.yellow(" warn ") + msg + "\n");
|
|
119
113
|
}
|
|
120
114
|
|
|
121
115
|
// ─────────────────────────────────────────────────────────────────
|
|
122
|
-
// Welcome
|
|
116
|
+
// Welcome
|
|
123
117
|
// ─────────────────────────────────────────────────────────────────
|
|
124
|
-
export function renderWelcome(memCount = 0, extCount = 0
|
|
118
|
+
export function renderWelcome(memCount = 0, extCount = 0) {
|
|
119
|
+
const stats = [
|
|
120
|
+
memCount ? `${memCount} context file${memCount > 1 ? "s" : ""}` : null,
|
|
121
|
+
extCount ? `${extCount} extension${extCount > 1 ? "s" : ""}` : null,
|
|
122
|
+
].filter(Boolean).join(" · ");
|
|
123
|
+
|
|
125
124
|
return [
|
|
126
125
|
"",
|
|
127
|
-
chalk.hex("#4EC9B0").bold("
|
|
128
|
-
|
|
129
|
-
chalk.
|
|
130
|
-
chalk.
|
|
131
|
-
chalk.hex("#DCDCAA").bold(" ╚██████╔╝███████╗██║ ╚═╝ ██║██║██║ ╚████║██║"),
|
|
132
|
-
chalk.hex("#858585") (" ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝"),
|
|
133
|
-
"",
|
|
134
|
-
chalk.hex("#858585")(" ╔═══════════════════════════════════════════════════════╗"),
|
|
135
|
-
chalk.hex("#858585")(" ║ ") + chalk.hex("#4EC9B0")(" AI Agent CLI") + chalk.hex("#858585")(" · ") + chalk.hex("#DCDCAA")("native function calling") + chalk.hex("#858585")(" · ") + chalk.hex("#C586C0")("Gemini Pro ") + chalk.hex("#858585")(" ║"),
|
|
136
|
-
chalk.hex("#858585")(" ╚═══════════════════════════════════════════════════════╝"),
|
|
137
|
-
"",
|
|
138
|
-
chalk.hex("#6A9955")(
|
|
139
|
-
` Context files: ${memCount} · Extensions: ${extCount} · Custom commands: ${cmdCount}`
|
|
140
|
-
),
|
|
141
|
-
chalk.hex("#858585")(` Type ${chalk.hex("#CE9178")("/help")} for all commands`),
|
|
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"),
|
|
142
130
|
"",
|
|
143
131
|
].join("\n");
|
|
144
132
|
}
|
|
145
133
|
|
|
146
134
|
// ─────────────────────────────────────────────────────────────────
|
|
147
|
-
// Help
|
|
135
|
+
// Help
|
|
148
136
|
// ─────────────────────────────────────────────────────────────────
|
|
149
137
|
export function renderHelp(customCommands = {}) {
|
|
150
|
-
const
|
|
151
|
-
["/agent", "Toggle AGENT (ReAct loop) ↔ CHAT mode"],
|
|
152
|
-
["/yolo", "Toggle auto-approve all tool actions"],
|
|
153
|
-
["/memory show", "Show all loaded GEMINI.md context files"],
|
|
154
|
-
["/memory reload", "Reload all GEMINI.md files from disk"],
|
|
155
|
-
["/memory add <text>", "Append text to ~/.gemini/GEMINI.md"],
|
|
156
|
-
["/ext list", "List installed extensions"],
|
|
157
|
-
["/ext install <src>", "Install extension (path or git URL)"],
|
|
158
|
-
["/ext uninstall <n>", "Uninstall extension by name"],
|
|
159
|
-
["/ext enable <name>", "Enable an extension"],
|
|
160
|
-
["/ext disable <name>", "Disable an extension"],
|
|
161
|
-
["/ext update <name>", "Pull latest from git source"],
|
|
162
|
-
["/file <path>", "Attach file to next message"],
|
|
163
|
-
["/system <text>", "Set session system instruction"],
|
|
164
|
-
["/history", "Show conversation turns"],
|
|
165
|
-
["/export <file>", "Export history to JSON"],
|
|
166
|
-
["/cd <path>", "Change working directory"],
|
|
167
|
-
["/cwd", "Show working directory"],
|
|
168
|
-
["/new /clear", "Reset conversation"],
|
|
169
|
-
["/model", "Show model & config info"],
|
|
170
|
-
["/help", "Show this help"],
|
|
171
|
-
["/exit /quit", "Exit Gemini"],
|
|
172
|
-
];
|
|
173
|
-
|
|
174
|
-
const lines = [
|
|
138
|
+
const section = (title, rows) => [
|
|
175
139
|
"",
|
|
176
|
-
chalk.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
chalk.hex("#CE9178")
|
|
180
|
-
chalk.hex("#858585")(desc.padEnd(36)) +
|
|
181
|
-
chalk.hex("#569CD6")("│")
|
|
140
|
+
chalk.dim(" " + title),
|
|
141
|
+
chalk.dim(" " + "─".repeat(48)),
|
|
142
|
+
...rows.map(([cmd, desc]) =>
|
|
143
|
+
" " + chalk.hex("#CE9178")(cmd.padEnd(26)) + chalk.dim(desc)
|
|
182
144
|
),
|
|
183
|
-
|
|
145
|
+
].join("\n");
|
|
146
|
+
|
|
147
|
+
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
|
+
]),
|
|
184
168
|
];
|
|
185
169
|
|
|
186
170
|
const cmds = Object.entries(customCommands);
|
|
187
171
|
if (cmds.length) {
|
|
188
|
-
lines.push(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
chalk.hex("#C586C0")(" │ ") +
|
|
192
|
-
chalk.hex("#DCDCAA").bold(("/" + key).padEnd(28)) +
|
|
193
|
-
chalk.hex("#858585")((cmd.description ?? "").padEnd(34)) +
|
|
194
|
-
chalk.hex("#C586C0")("│")
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
lines.push(chalk.hex("#C586C0")(" └───────────────────────────────────────────────────────────┘"));
|
|
172
|
+
lines.push(section("EXTENSION COMMANDS",
|
|
173
|
+
cmds.map(([k, c]) => ["/" + k, c.description ?? ""])
|
|
174
|
+
));
|
|
198
175
|
}
|
|
199
176
|
|
|
200
|
-
lines.push(
|
|
177
|
+
lines.push(
|
|
178
|
+
"",
|
|
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
|
+
""
|
|
182
|
+
);
|
|
201
183
|
return lines.join("\n");
|
|
202
|
-
}
|
|
184
|
+
}
|