@ikyyofc/gemini-cli 1.0.0 → 1.0.2
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 +225 -224
- package/package.json +1 -1
- package/src/input.js +60 -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,346 @@ import {
|
|
|
23
18
|
printUser, printAssistant,
|
|
24
19
|
printError, printInfo, printSuccess, printWarning
|
|
25
20
|
} from "./src/renderer.js";
|
|
21
|
+
import {
|
|
22
|
+
enableBracketedPaste, disableBracketedPaste, setupPaste
|
|
23
|
+
} from "./src/input.js";
|
|
26
24
|
|
|
27
25
|
// ─────────────────────────────────────────────────────────────────
|
|
28
26
|
// Bootstrap
|
|
29
27
|
// ─────────────────────────────────────────────────────────────────
|
|
30
28
|
ensureGlobalDir();
|
|
31
29
|
|
|
32
|
-
let extensions
|
|
33
|
-
let
|
|
34
|
-
let
|
|
35
|
-
let memoryContext = null; // concatenated string
|
|
30
|
+
let extensions = loadExtensions();
|
|
31
|
+
let memoryLoaded = [];
|
|
32
|
+
let memoryContext = null;
|
|
36
33
|
|
|
37
34
|
function reloadAll() {
|
|
38
|
-
extensions
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
memoryLoaded = loadMemory(extraDirs);
|
|
42
|
-
memoryContext = buildContextString(memoryLoaded);
|
|
35
|
+
extensions = loadExtensions();
|
|
36
|
+
memoryLoaded = loadMemory(getExtensionContextDirs(extensions));
|
|
37
|
+
memoryContext = buildContextString(memoryLoaded);
|
|
43
38
|
}
|
|
44
39
|
reloadAll();
|
|
45
40
|
|
|
46
41
|
// ─────────────────────────────────────────────────────────────────
|
|
47
42
|
// State
|
|
48
43
|
// ─────────────────────────────────────────────────────────────────
|
|
49
|
-
let
|
|
50
|
-
let sessionSystem
|
|
51
|
-
let pendingFile
|
|
52
|
-
let
|
|
53
|
-
let
|
|
54
|
-
let agentMode
|
|
55
|
-
let autoApprove
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
let history = [];
|
|
45
|
+
let sessionSystem = null;
|
|
46
|
+
let pendingFile = null;
|
|
47
|
+
let pendingPath = null;
|
|
48
|
+
let processing = false;
|
|
49
|
+
let agentMode = true;
|
|
50
|
+
let autoApprove = false;
|
|
51
|
+
|
|
52
|
+
function sysInstruction() {
|
|
53
|
+
return [memoryContext, sessionSystem].filter(Boolean).join("\n\n") || null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─────────────────────────────────────────────────────────────────
|
|
57
|
+
// Readline — directly on process.stdin, NO transform stream
|
|
58
|
+
// ─────────────────────────────────────────────────────────────────
|
|
59
|
+
const rl = readline.createInterface({
|
|
60
|
+
input: process.stdin,
|
|
61
|
+
output: process.stdout,
|
|
62
|
+
terminal: true,
|
|
63
|
+
historySize: 200,
|
|
64
|
+
crlfDelay: Infinity,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Attach paste interception (keypress-based, not Transform-based)
|
|
68
|
+
enableBracketedPaste();
|
|
69
|
+
setupPaste(rl);
|
|
70
|
+
|
|
71
|
+
// Clean up on any exit
|
|
72
|
+
function cleanup() {
|
|
73
|
+
disableBracketedPaste();
|
|
74
|
+
if (process.stdin.isTTY) {
|
|
75
|
+
try { process.stdin.setRawMode(false); } catch {}
|
|
76
|
+
}
|
|
61
77
|
}
|
|
78
|
+
process.on("exit", cleanup);
|
|
79
|
+
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
62
80
|
|
|
63
81
|
// ─────────────────────────────────────────────────────────────────
|
|
64
|
-
//
|
|
82
|
+
// Prompt
|
|
65
83
|
// ─────────────────────────────────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
function showPrompt() {
|
|
85
|
+
const mode = agentMode ? chalk.hex("#4EC9B0")("agent") : chalk.dim("chat");
|
|
86
|
+
const yolo = autoApprove ? chalk.red(" yolo") : "";
|
|
87
|
+
const file = pendingFile ? chalk.yellow(` +${path.basename(pendingPath)}`) : "";
|
|
88
|
+
const turns = history.length ? chalk.dim(` ${Math.ceil(history.length/2)}t`) : "";
|
|
89
|
+
const mem = memoryLoaded.length ? chalk.dim(` m${memoryLoaded.length}`) : "";
|
|
90
|
+
|
|
91
|
+
rl.setPrompt(
|
|
92
|
+
"\n " + chalk.bold("❯") + " " +
|
|
93
|
+
chalk.dim("[") + mode + yolo + file + turns + mem + chalk.dim("]") + " "
|
|
94
|
+
);
|
|
95
|
+
rl.prompt(true);
|
|
76
96
|
}
|
|
77
97
|
|
|
78
98
|
// ─────────────────────────────────────────────────────────────────
|
|
79
99
|
// File attachment
|
|
80
100
|
// ─────────────────────────────────────────────────────────────────
|
|
81
|
-
function
|
|
82
|
-
const p = path.resolve(
|
|
83
|
-
if (!fs.existsSync(p))
|
|
84
|
-
if (fs.statSync(p).size > 20*1024*1024) { printError("
|
|
85
|
-
pendingFile
|
|
86
|
-
|
|
87
|
-
printSuccess(`
|
|
88
|
-
|
|
101
|
+
function attachFile(fp) {
|
|
102
|
+
const p = path.resolve(fp.trim().replace(/^['"]|['"]$/g, ""));
|
|
103
|
+
if (!fs.existsSync(p)) { printError(`not found: ${p}`); return; }
|
|
104
|
+
if (fs.statSync(p).size > 20*1024*1024) { printError("file too large (max 20MB)"); return; }
|
|
105
|
+
pendingFile = fs.readFileSync(p);
|
|
106
|
+
pendingPath = p;
|
|
107
|
+
printSuccess(`attached ${path.basename(p)}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─────────────────────────────────────────────────────────────────
|
|
111
|
+
// Send message
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────
|
|
113
|
+
async function send(userText) {
|
|
114
|
+
if (!userText.trim()) return;
|
|
115
|
+
|
|
116
|
+
printUser(userText + (pendingFile ? chalk.dim(` [${path.basename(pendingPath)}]`) : ""));
|
|
117
|
+
|
|
118
|
+
if (agentMode) {
|
|
119
|
+
const res = await runAgentLoop(userText, history, {
|
|
120
|
+
systemInstruction: sysInstruction(),
|
|
121
|
+
autoApprove,
|
|
122
|
+
maxIterations: 40,
|
|
123
|
+
});
|
|
124
|
+
if (res?.finalResponse) {
|
|
125
|
+
history.push({ role: "user", content: userText });
|
|
126
|
+
history.push({ role: "assistant", content: res.finalResponse });
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
const { default: ora } = await import("ora");
|
|
130
|
+
const sp = ora({ text: "thinking…", spinner: "dots", color: "cyan", prefixText: " " }).start();
|
|
131
|
+
const msgs = [];
|
|
132
|
+
if (sysInstruction()) msgs.push({ role: "system", content: sysInstruction() });
|
|
133
|
+
msgs.push(...history, { role: "user", content: userText });
|
|
134
|
+
try {
|
|
135
|
+
const t0 = Date.now();
|
|
136
|
+
const reply = await chat(msgs, pendingFile || null);
|
|
137
|
+
sp.succeed(chalk.dim(`done (${((Date.now()-t0)/1000).toFixed(1)}s)`));
|
|
138
|
+
history.push({ role: "user", content: userText }, { role: "assistant", content: reply });
|
|
139
|
+
printAssistant(reply);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
sp.fail("failed");
|
|
142
|
+
printError(e.message);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
pendingFile = null;
|
|
147
|
+
pendingPath = null;
|
|
89
148
|
}
|
|
90
149
|
|
|
91
150
|
// ─────────────────────────────────────────────────────────────────
|
|
92
|
-
//
|
|
151
|
+
// Commands
|
|
93
152
|
// ─────────────────────────────────────────────────────────────────
|
|
94
153
|
async function handleCommand(input) {
|
|
95
154
|
const tokens = input.trim().split(/\s+/);
|
|
96
155
|
const cmd = tokens[0].toLowerCase();
|
|
97
156
|
const arg = tokens.slice(1).join(" ").trim();
|
|
98
157
|
|
|
99
|
-
// ── /memory ─────────────────────────────────────────────────
|
|
100
158
|
if (cmd === "/memory") {
|
|
101
|
-
const sub = tokens[1]
|
|
102
|
-
if
|
|
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
|
-
}
|
|
159
|
+
const sub = tokens[1];
|
|
160
|
+
if (sub === "show") { console.log(memoryShow(memoryLoaded)); }
|
|
161
|
+
else if (sub === "reload") { reloadAll(); printSuccess(`reloaded — ${memoryLoaded.length} file(s)`); }
|
|
162
|
+
else if (sub === "add") {
|
|
163
|
+
const t = tokens.slice(2).join(" ");
|
|
164
|
+
if (!t) { printError("usage: /memory add <text>"); return; }
|
|
165
|
+
printSuccess(memoryAdd(t)); reloadAll();
|
|
166
|
+
} else { printInfo("/memory [show | reload | add <text>]"); }
|
|
116
167
|
return;
|
|
117
168
|
}
|
|
118
169
|
|
|
119
|
-
// ── /ext ────────────────────────────────────────────────────
|
|
120
170
|
if (cmd === "/ext") {
|
|
121
|
-
const sub
|
|
171
|
+
const sub = tokens[1];
|
|
122
172
|
const name = tokens[2];
|
|
123
|
-
|
|
124
173
|
if (sub === "list") {
|
|
125
174
|
const list = listExtensions();
|
|
126
|
-
if (!list.length) { printInfo("
|
|
175
|
+
if (!list.length) { printInfo("no extensions installed"); return; }
|
|
127
176
|
console.log("");
|
|
128
177
|
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
|
-
);
|
|
178
|
+
const s = e.enabled ? chalk.green("✓") : chalk.dim("○");
|
|
179
|
+
console.log(` ${s} ${chalk.bold(e.name)} ${chalk.dim("v"+e.version)} ${chalk.dim(e.description)}`);
|
|
136
180
|
});
|
|
137
181
|
console.log("");
|
|
138
|
-
|
|
139
182
|
} else if (sub === "install") {
|
|
140
|
-
if (!arg.slice(arg.indexOf(" ")+1).trim() && !name) { printError("Usage: /ext install <path-or-git-url>"); return; }
|
|
141
183
|
const src = tokens.slice(2).join(" ");
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
184
|
+
if (!src) { printError("usage: /ext install <path-or-url>"); return; }
|
|
185
|
+
printInfo(`installing…`);
|
|
186
|
+
const r = await installExtension(src);
|
|
187
|
+
r.error ? printError(r.error) : (printSuccess(`installed: ${r.name}`), reloadAll());
|
|
147
188
|
} else if (sub === "uninstall") {
|
|
148
|
-
if (!name) { printError("
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
else { printSuccess(`Uninstalled: ${name}`); reloadAll(); }
|
|
152
|
-
|
|
189
|
+
if (!name) { printError("usage: /ext uninstall <n>"); return; }
|
|
190
|
+
const r = await uninstallExtension(name);
|
|
191
|
+
r.error ? printError(r.error) : (printSuccess(`removed: ${name}`), reloadAll());
|
|
153
192
|
} else if (sub === "enable") {
|
|
154
|
-
if (!name) { printError("
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
193
|
+
if (!name) { printError("usage: /ext enable <n>"); return; }
|
|
194
|
+
const r = enableExtension(name, true);
|
|
195
|
+
r.error ? printError(r.error) : (printSuccess(`enabled: ${name}`), reloadAll());
|
|
158
196
|
} else if (sub === "disable") {
|
|
159
|
-
if (!name) { printError("
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
197
|
+
if (!name) { printError("usage: /ext disable <n>"); return; }
|
|
198
|
+
const r = enableExtension(name, false);
|
|
199
|
+
r.error ? printError(r.error) : (printSuccess(`disabled: ${name}`), reloadAll());
|
|
163
200
|
} 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
|
-
}
|
|
201
|
+
if (!name) { printError("usage: /ext update <n>"); return; }
|
|
202
|
+
printInfo(`updating ${name}…`);
|
|
203
|
+
const r = await updateExtension(name);
|
|
204
|
+
r.error ? printError(r.error) : (printSuccess(`updated: ${name}`), reloadAll());
|
|
205
|
+
} else { printInfo("/ext [list | install | uninstall | enable | disable | update]"); }
|
|
172
206
|
return;
|
|
173
207
|
}
|
|
174
208
|
|
|
175
|
-
// ── Built-in commands ────────────────────────────────────────
|
|
176
209
|
switch (cmd) {
|
|
177
|
-
case "/help":
|
|
178
|
-
console.log(renderHelp(customCommands));
|
|
179
|
-
break;
|
|
210
|
+
case "/help": console.log(renderHelp()); break;
|
|
180
211
|
|
|
181
212
|
case "/new": case "/clear":
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
printInfo("Conversation cleared.");
|
|
213
|
+
history = []; pendingFile = null; pendingPath = null;
|
|
214
|
+
printInfo("conversation cleared");
|
|
185
215
|
break;
|
|
186
216
|
|
|
187
217
|
case "/file":
|
|
188
|
-
if (!arg) { printError("
|
|
189
|
-
|
|
190
|
-
break;
|
|
218
|
+
if (!arg) { printError("usage: /file <path>"); break; }
|
|
219
|
+
attachFile(arg); break;
|
|
191
220
|
|
|
192
221
|
case "/system":
|
|
193
|
-
if (!arg) { printInfo(`
|
|
194
|
-
sessionSystem = arg;
|
|
195
|
-
printSuccess("Session system instruction set.");
|
|
196
|
-
break;
|
|
222
|
+
if (!arg) { printInfo(`system: ${sessionSystem ?? "(none)"}`); break; }
|
|
223
|
+
sessionSystem = arg; printSuccess("system instruction set"); break;
|
|
197
224
|
|
|
198
225
|
case "/agent":
|
|
199
226
|
agentMode = !agentMode;
|
|
200
|
-
|
|
227
|
+
printInfo(`mode → ${agentMode ? chalk.hex("#4EC9B0")("agent") : chalk.dim("chat")}`);
|
|
201
228
|
break;
|
|
202
229
|
|
|
203
230
|
case "/yolo":
|
|
204
231
|
autoApprove = !autoApprove;
|
|
205
|
-
autoApprove
|
|
232
|
+
autoApprove
|
|
233
|
+
? printWarning("yolo on — all tool actions auto-approved")
|
|
234
|
+
: printInfo("yolo off — confirmations restored");
|
|
206
235
|
break;
|
|
207
236
|
|
|
208
237
|
case "/history":
|
|
209
|
-
if (!
|
|
238
|
+
if (!history.length) { printInfo("no history"); break; }
|
|
210
239
|
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));
|
|
240
|
+
history.forEach((m, i) => {
|
|
241
|
+
const who = m.role === "user" ? chalk.hex("#4A9EFF")("you") : chalk.hex("#4EC9B0")("gemini");
|
|
242
|
+
const body = String(m.content).replace(/\n/g, " ").slice(0, 90);
|
|
243
|
+
console.log(` ${chalk.dim("["+( i+1)+"]")} ${who} ${chalk.dim(body)}`);
|
|
216
244
|
});
|
|
217
245
|
console.log("");
|
|
218
246
|
break;
|
|
219
247
|
|
|
220
248
|
case "/export":
|
|
221
|
-
if (!arg) { printError("
|
|
222
|
-
fs.writeFileSync(path.resolve(arg), JSON.stringify({
|
|
223
|
-
|
|
249
|
+
if (!arg) { printError("usage: /export <file.json>"); break; }
|
|
250
|
+
fs.writeFileSync(path.resolve(arg), JSON.stringify({
|
|
251
|
+
exported_at: new Date().toISOString(),
|
|
252
|
+
mode: agentMode ? "agent" : "chat",
|
|
253
|
+
system: sysInstruction(),
|
|
254
|
+
messages: history,
|
|
255
|
+
}, null, 2));
|
|
256
|
+
printSuccess(`exported → ${arg}`);
|
|
224
257
|
break;
|
|
225
258
|
|
|
226
259
|
case "/cwd":
|
|
227
|
-
printInfo(
|
|
228
|
-
break;
|
|
260
|
+
printInfo(process.cwd()); break;
|
|
229
261
|
|
|
230
262
|
case "/cd":
|
|
231
|
-
if (!arg) { printError("
|
|
232
|
-
try { process.chdir(path.resolve(arg));
|
|
263
|
+
if (!arg) { printError("usage: /cd <path>"); break; }
|
|
264
|
+
try { process.chdir(path.resolve(arg)); printInfo(process.cwd()); }
|
|
265
|
+
catch (e) { printError(e.message); }
|
|
233
266
|
break;
|
|
234
267
|
|
|
235
268
|
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}`);
|
|
269
|
+
printInfo(`model gemini-pro-latest`);
|
|
270
|
+
printInfo(`tools ${agentMode ? "on (native function calling)" : "off"}`);
|
|
271
|
+
printInfo(`yolo ${autoApprove ? "on" : "off"}`);
|
|
272
|
+
printInfo(`memory ${memoryLoaded.length} file(s) · extensions: ${extensions.length}`);
|
|
273
|
+
printInfo(`config ${GLOBAL_DIR}`);
|
|
244
274
|
break;
|
|
245
275
|
|
|
246
276
|
case "/exit": case "/quit":
|
|
247
|
-
|
|
248
|
-
|
|
277
|
+
cleanup();
|
|
278
|
+
printInfo("bye");
|
|
279
|
+
process.exit(0);
|
|
249
280
|
break;
|
|
250
281
|
|
|
251
|
-
default:
|
|
252
|
-
|
|
253
|
-
const customKey = cmd.slice(1); // strip leading /
|
|
254
|
-
if (customKey.includes(":") && customCommands[customKey]) {
|
|
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.`);
|
|
259
|
-
} else {
|
|
260
|
-
printError(`Unknown command: ${cmd} (type /help)`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
282
|
+
default:
|
|
283
|
+
printError(`unknown command: ${cmd} — /help for list`);
|
|
263
284
|
}
|
|
264
285
|
}
|
|
265
286
|
|
|
266
287
|
// ─────────────────────────────────────────────────────────────────
|
|
267
|
-
//
|
|
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
|
-
// ─────────────────────────────────────────────────────────────────
|
|
307
|
-
// Main
|
|
288
|
+
// Main REPL loop
|
|
308
289
|
// ─────────────────────────────────────────────────────────────────
|
|
309
290
|
async function main() {
|
|
310
291
|
process.stdout.write("\x1Bc");
|
|
311
|
-
console.log(renderWelcome(memoryLoaded.length, extensions.length
|
|
292
|
+
console.log(renderWelcome(memoryLoaded.length, extensions.length));
|
|
312
293
|
|
|
313
294
|
// Parse flags
|
|
314
295
|
const argv = process.argv.slice(2);
|
|
315
296
|
const positional = [];
|
|
316
297
|
for (let i = 0; i < argv.length; i++) {
|
|
317
298
|
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("
|
|
321
|
-
else if (a === "--chat") { agentMode
|
|
299
|
+
if (a === "--system" && argv[i+1]) { sessionSystem = argv[++i]; }
|
|
300
|
+
else if (a === "--file" && argv[i+1]) { attachFile(argv[++i]); }
|
|
301
|
+
else if (a === "--yolo") { autoApprove = true; printWarning("yolo on"); }
|
|
302
|
+
else if (a === "--chat") { agentMode = false; }
|
|
322
303
|
else if (!a.startsWith("--")) { positional.push(a); }
|
|
323
304
|
}
|
|
324
305
|
|
|
325
|
-
// One-shot
|
|
306
|
+
// One-shot mode
|
|
326
307
|
if (positional.length > 0) {
|
|
327
|
-
await
|
|
308
|
+
await send(positional.join(" "));
|
|
309
|
+
cleanup();
|
|
328
310
|
process.exit(0);
|
|
329
311
|
}
|
|
330
312
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
console.log("");
|
|
313
|
+
showPrompt();
|
|
314
|
+
|
|
315
|
+
rl.on("line", async (input) => {
|
|
316
|
+
// If already processing, just show prompt again — do NOT block or pause stream
|
|
317
|
+
if (processing) {
|
|
318
|
+
printWarning("still thinking…");
|
|
319
|
+
showPrompt();
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
342
322
|
|
|
343
|
-
|
|
323
|
+
const text = input.trim();
|
|
324
|
+
if (!text) { showPrompt(); return; }
|
|
344
325
|
|
|
345
|
-
|
|
346
|
-
const input = line.trim();
|
|
347
|
-
if (!input) { setPrompt(); return; }
|
|
348
|
-
if (isProcessing) { printWarning("Still processing…"); setPrompt(); return; }
|
|
349
|
-
isProcessing = true; rl.pause();
|
|
326
|
+
processing = true;
|
|
350
327
|
try {
|
|
351
|
-
if (
|
|
352
|
-
else
|
|
328
|
+
if (text.startsWith("/")) await handleCommand(text);
|
|
329
|
+
else await send(input); // preserve original (may have \n from paste)
|
|
330
|
+
} catch (e) {
|
|
331
|
+
printError(e.message);
|
|
353
332
|
} finally {
|
|
354
|
-
|
|
333
|
+
processing = false;
|
|
334
|
+
showPrompt();
|
|
355
335
|
}
|
|
356
336
|
});
|
|
357
337
|
|
|
358
|
-
|
|
359
|
-
rl.on("
|
|
338
|
+
// 'close' fires on Ctrl+D — exit gracefully
|
|
339
|
+
rl.on("close", () => {
|
|
340
|
+
cleanup();
|
|
341
|
+
console.log("");
|
|
342
|
+
process.exit(0);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// SIGINT (Ctrl+C) — cancel current operation or exit
|
|
346
|
+
rl.on("SIGINT", () => {
|
|
347
|
+
if (processing) {
|
|
348
|
+
process.stdout.write("\n");
|
|
349
|
+
printWarning("ctrl+c again or /exit to quit");
|
|
350
|
+
showPrompt();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
// Not processing — treat as exit intent
|
|
354
|
+
cleanup();
|
|
355
|
+
console.log("");
|
|
356
|
+
process.exit(0);
|
|
357
|
+
});
|
|
360
358
|
}
|
|
361
359
|
|
|
362
|
-
main().catch(
|
|
360
|
+
main().catch(e => {
|
|
361
|
+
console.error(chalk.red("fatal:"), e.message);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
});
|
package/package.json
CHANGED
package/src/input.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/input.js — Bracketed paste via keypress interception
|
|
2
|
+
//
|
|
3
|
+
// No Transform stream. We intercept keypress events directly:
|
|
4
|
+
// \x1b[200~ → enter paste mode, start buffering lines
|
|
5
|
+
// \x1b[201~ → end paste mode, emit full buffer as one line
|
|
6
|
+
//
|
|
7
|
+
import readline from "readline";
|
|
8
|
+
|
|
9
|
+
export function enableBracketedPaste() { process.stdout.write("\x1b[?2004h"); }
|
|
10
|
+
export function disableBracketedPaste() { process.stdout.write("\x1b[?2004l"); }
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Attach bracketed paste interception to an existing readline interface.
|
|
14
|
+
* readline must be created BEFORE calling this.
|
|
15
|
+
*/
|
|
16
|
+
export function setupPaste(rl) {
|
|
17
|
+
// Make stdin emit 'keypress' events (readline may have already done this)
|
|
18
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
19
|
+
|
|
20
|
+
let pasting = false;
|
|
21
|
+
let pasteBuf = [];
|
|
22
|
+
|
|
23
|
+
// Watch for paste start/end escape sequences
|
|
24
|
+
const onKeypress = (_str, key) => {
|
|
25
|
+
if (!key) return;
|
|
26
|
+
const seq = key.sequence ?? "";
|
|
27
|
+
|
|
28
|
+
if (seq === "\x1b[200~") {
|
|
29
|
+
pasting = true;
|
|
30
|
+
pasteBuf = [];
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (seq === "\x1b[201~") {
|
|
35
|
+
pasting = false;
|
|
36
|
+
const full = pasteBuf.join("\n");
|
|
37
|
+
pasteBuf = [];
|
|
38
|
+
// Fire the complete paste as one synthetic 'line' event
|
|
39
|
+
rl.emit("line", full);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
process.stdin.on("keypress", onKeypress);
|
|
44
|
+
|
|
45
|
+
// Patch rl.emit to swallow 'line' events while pasting
|
|
46
|
+
// (readline fires one per \n inside the paste — we buffer them)
|
|
47
|
+
const _emit = rl.emit.bind(rl);
|
|
48
|
+
rl.emit = function (event, ...args) {
|
|
49
|
+
if (event === "line" && pasting) {
|
|
50
|
+
pasteBuf.push(args[0] ?? "");
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return _emit(event, ...args);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
process.stdin.off("keypress", onKeypress);
|
|
58
|
+
rl.emit = _emit;
|
|
59
|
+
};
|
|
60
|
+
}
|
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
|
+
}
|