@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 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, loadCustomCommands, getExtensionContextDirs,
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 = loadExtensions();
33
- let customCommands = {}; // populated after extensions load
34
- let memoryLoaded = []; // { file, content }[]
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 = loadExtensions();
39
- customCommands = {}; // TODO: loadCustomCommands(extensions) — needs top-level await
40
- const extraDirs = getExtensionContextDirs(extensions);
41
- memoryLoaded = loadMemory(extraDirs);
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 conversationHistory = [];
50
- let sessionSystem = null; // from /system command or --system flag
51
- let pendingFile = null;
52
- let pendingFilePath = null;
53
- let isProcessing = false;
54
- let agentMode = true;
55
- let autoApprove = false;
56
-
57
- /** Full system instruction: memory context + session system */
58
- function buildSystemInstruction() {
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
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true, historySize: 200 });
67
-
68
- function setPrompt() {
69
- const mode = agentMode ? chalk.hex("#4EC9B0")(" ⚡agent") : chalk.hex("#858585")(" 💬chat");
70
- const yolo = autoApprove ? chalk.hex("#F44747")(" YOLO") : "";
71
- const fTag = pendingFile ? chalk.hex("#DCDCAA")(` 📎 ${path.basename(pendingFilePath)}`) : "";
72
- const turns = conversationHistory.length > 0 ? chalk.hex("#858585")(` [${Math.ceil(conversationHistory.length/2)}t]`) : "";
73
- const mem = memoryLoaded.length ? chalk.hex("#C586C0")(` [mem:${memoryLoaded.length}]`) : "";
74
- rl.setPrompt("\n " + chalk.hex("#569CD6").bold("❯") + mode + yolo + fTag + turns + mem + chalk.hex("#D4D4D4")(" "));
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 loadFile(filePath) {
93
+ function attachFile(filePath) {
82
94
  const p = path.resolve(filePath.trim().replace(/^['"]|['"]$/g, ""));
83
- if (!fs.existsSync(p)) { printError(`File not found: ${p}`); return; }
84
- if (fs.statSync(p).size > 20*1024*1024) { printError("File too large (max 20 MB)"); return; }
85
- pendingFile = fs.readFileSync(p);
86
- pendingFilePath = p;
87
- printSuccess(`Attached: ${path.basename(p)} (${(fs.statSync(p).size/1024).toFixed(1)} KB)`);
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
- // Command router
103
+ // Send message
93
104
  // ─────────────────────────────────────────────────────────────────
94
- async function handleCommand(input) {
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
- // ── /memory ─────────────────────────────────────────────────
152
+ // /memory
100
153
  if (cmd === "/memory") {
101
- const sub = tokens[1]?.toLowerCase();
102
- if (sub === "show") {
103
- console.log(memoryShow(memoryLoaded));
104
- } else if (sub === "reload") {
105
- reloadAll();
106
- printSuccess(`Reloaded. ${memoryLoaded.length} context file(s) loaded.`);
107
- } else if (sub === "add") {
108
- const text = tokens.slice(2).join(" ");
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
- // ── /ext ────────────────────────────────────────────────────
165
+ // /ext
120
166
  if (cmd === "/ext") {
121
- const sub = tokens[1]?.toLowerCase();
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("No extensions installed. Use /ext install <source>"); return; }
171
+ if (!list.length) { printInfo("no extensions installed"); return; }
127
172
  console.log("");
128
173
  list.forEach(e => {
129
- const status = e.enabled ? chalk.hex("#4EC9B0")("✔ enabled") : chalk.hex("#858585")(" disabled");
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
- printInfo(`Installing: ${src} …`);
143
- const res = await installExtension(src);
144
- if (res.error) printError(res.error);
145
- else { printSuccess(`Installed: ${res.name} (${res.path})`); reloadAll(); }
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("Usage: /ext uninstall <name>"); return; }
149
- const res = await uninstallExtension(name);
150
- if (res.error) printError(res.error);
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("Usage: /ext enable <name>"); return; }
155
- const res = enableExtension(name, true);
156
- if (res.error) printError(res.error); else { printSuccess(`Enabled: ${name}`); reloadAll(); }
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("Usage: /ext disable <name>"); return; }
160
- const res = enableExtension(name, false);
161
- if (res.error) printError(res.error); else { printSuccess(`Disabled: ${name}`); reloadAll(); }
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("Usage: /ext update <name>"); return; }
165
- printInfo(`Updating ${name}…`);
166
- const res = await updateExtension(name);
167
- if (res.error) printError(res.error); else { printSuccess(`Updated: ${name}`); reloadAll(); }
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
- conversationHistory = [];
183
- pendingFile = null; pendingFilePath = null;
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("Usage: /file <path>"); break; }
189
- loadFile(arg);
190
- break;
214
+ if (!arg) { printError("usage: /file <path>"); break; }
215
+ attachFile(arg); break;
191
216
 
192
217
  case "/system":
193
- if (!arg) { printInfo(`System: ${sessionSystem ?? "(none)"}`); break; }
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
- printSuccess(`Mode: ${agentMode ? chalk.hex("#4EC9B0").bold("AGENT") + " (native function calling loop)" : chalk.hex("#858585").bold("CHAT") + " (plain conversation)"}`);
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("YOLO ONall tool actions auto-approved.") : printSuccess("YOLO OFF — confirmations restored.");
228
+ autoApprove ? printWarning("yolo onno confirmations") : printInfo("yolo off — confirmations restored");
206
229
  break;
207
230
 
208
231
  case "/history":
209
- if (!conversationHistory.length) { printInfo("No history."); break; }
232
+ if (!history.length) { printInfo("no history"); break; }
210
233
  console.log("");
211
- conversationHistory.forEach((m, i) => {
212
- const who = m.role === "user" ? chalk.hex("#569CD6")("You") : chalk.hex("#4EC9B0")("Gemini");
213
- const body = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
214
- const short = body.slice(0, 100).replace(/\n/g, " ");
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("Usage: /export <file.json>"); break; }
222
- fs.writeFileSync(path.resolve(arg), JSON.stringify({ exported_at: new Date().toISOString(), mode: agentMode ? "agent":"chat", system: buildSystemInstruction(), messages: conversationHistory }, null, 2));
223
- printSuccess(`Exported to ${arg}`);
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("Usage: /cd <path>"); break; }
232
- try { process.chdir(path.resolve(arg)); printSuccess(`Changed to: ${process.cwd()}`); } catch (e) { printError(e.message); }
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("Model : gemini-pro-latest");
237
- printInfo("Provider : Gemini (Firebase Cloud Function)");
238
- printInfo("Tool calling : NATIVE (functionDeclarations / functionCall / functionResponse)");
239
- printInfo("Thinking : HIGH | Temperature: 0 | Safety: BLOCK_NONE");
240
- printInfo(`Agent mode : ${agentMode ? "ON" : "OFF"}`);
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("Goodbye! 👋");
248
- rl.close(); process.exit(0);
249
- break;
270
+ printInfo("bye"); rl.close(); process.exit(0); break;
250
271
 
251
- default: {
252
- // Check if it's a custom command: /namespace:cmd [args]
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.`);
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(`Unknown command: ${cmd} (type /help)`);
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, Object.keys(customCommands).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]; printSuccess(`System: "${sessionSystem}"`); }
319
- else if (a === "--file" && argv[i+1]) { loadFile(argv[++i]); }
320
- else if (a === "--yolo") { autoApprove = true; printWarning("YOLO mode ON."); }
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 sendMessage(positional.join(" "));
303
+ await send(positional.join(" "));
328
304
  process.exit(0);
329
305
  }
330
306
 
331
- // Print mode info
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
- setPrompt();
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
- rl.on("line", async line => {
346
- const input = line.trim();
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 handleCommand(input);
352
- else await sendMessage(input);
317
+ if (input.startsWith("/")) await command(input);
318
+ else await send(raw); // pass raw so send() can restorePaste
353
319
  } finally {
354
- isProcessing = false; rl.resume(); setPrompt();
320
+ processing = false;
321
+ rl.resume();
322
+ prompt();
355
323
  }
356
324
  });
357
325
 
358
- rl.on("close", () => { console.log("\n" + chalk.hex("#858585")(" Session ended.\n")); process.exit(0); });
359
- rl.on("SIGINT", () => { if (isProcessing) { printWarning("Ctrl+C again to force quit."); return; } rl.close(); });
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(err => { console.error(chalk.red("\n Fatal:"), err.message); process.exit(1); });
333
+ main().catch(e => { console.error(chalk.red("fatal:"), e.message); process.exit(1); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikyyofc/gemini-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "AI CLI Agent powered by Gemini — your own terminal assistant",
5
5
  "type": "module",
6
6
  "main": "index.js",
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 — Terminal rendering (markdown, syntax, boxes, UI)
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: /\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|get|set)\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|read|source|alias)\b/g,
13
- rs: /\b(fn|let|mut|return|if|else|for|match|use|mod|pub|struct|enum|impl|trait|type|const|static|async|await|true|false|None|Some|Ok|Err)\b/g,
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.trim(), lang);
46
- const lbl = lang ? chalk.hex("#858585")(` ${lang} `) : "";
47
- const top = chalk.hex("#3C3C3C")("┌" + "─".repeat(58)) + lbl;
48
- const bot = chalk.hex("#3C3C3C")("" + "".repeat(59));
49
- const body = hl.split("\n").map(l => chalk.hex("#3C3C3C")("│ ") + l).join("\n");
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").bgHex("#2A2A2A")(` ${c} `));
51
+ r = r.replace(/`([^`\n]+)`/g, (_, c) => chalk.hex("#CE9178")(`\`${c}\``));
55
52
 
56
53
  // Headers
57
- r = r.replace(/^### (.+)$/gm, (_, t) => chalk.hex("#DCDCAA").bold(` ◆ ${t}`));
58
- r = r.replace(/^## (.+)$/gm, (_, t) => chalk.hex("#4EC9B0").bold(`\n ◈ ${t}`));
59
- r = r.replace(/^# (.+)$/gm, (_, t) => chalk.hex("#569CD6").bold.underline(`\n ${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.hex("#6A9955")(` ▌ ${t}`));
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}${chalk.hex("#569CD6")("◆")} ${t}`);
71
- r = r.replace(/^(\s*)(\d+)\. (.+)$/gm, (_, i, n, t) => `${i}${chalk.hex("#569CD6").bold(n+".")} ${t}`);
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.hex("#3C3C3C")("─".repeat(58)));
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
- // Box printers
77
+ // Message printers (clean, no heavy boxing)
86
78
  // ─────────────────────────────────────────────────────────────────
87
- const W = 60;
88
-
89
79
  export function printUser(text) {
90
- process.stdout.write(
91
- "\n" +
92
- chalk.hex("#569CD6").bold(" ╭─ You " + "─".repeat(W - 8)) + "\n" +
93
- chalk.hex("#569CD6")(" │ ") + chalk.white(text) + "\n" +
94
- chalk.hex("#569CD6")(" ╰" + "─".repeat(W)) + "\n"
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
- process.stdout.write(
100
- "\n" + chalk.hex("#4EC9B0").bold(" ╭─ Gemini " + "─".repeat(W - 10)) + "\n"
101
- );
102
- renderMarkdown(text).split("\n").forEach(line =>
103
- process.stdout.write(chalk.hex("#4EC9B0")(" ") + line + "\n")
104
- );
105
- process.stdout.write(chalk.hex("#4EC9B0")(" ╰" + "─".repeat(W)) + "\n\n");
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("\n" + chalk.hex("#F44747")(" ") + chalk.hex("#CE9178")(msg) + "\n\n");
103
+ process.stdout.write(chalk.red(" error ") + chalk.dim(msg) + "\n");
110
104
  }
111
105
  export function printInfo(msg) {
112
- process.stdout.write(chalk.hex("#858585")(" ") + chalk.hex("#6A9955")(msg) + "\n");
106
+ process.stdout.write(chalk.dim(" info ") + msg + "\n");
113
107
  }
114
108
  export function printSuccess(msg) {
115
- process.stdout.write(chalk.hex("#4EC9B0")(" ") + chalk.hex("#4EC9B0")(msg) + "\n");
109
+ process.stdout.write(chalk.green(" ") + msg + "\n");
116
110
  }
117
111
  export function printWarning(msg) {
118
- process.stdout.write(chalk.hex("#DCDCAA")(" ") + chalk.hex("#DCDCAA")(msg) + "\n");
112
+ process.stdout.write(chalk.yellow(" warn ") + msg + "\n");
119
113
  }
120
114
 
121
115
  // ─────────────────────────────────────────────────────────────────
122
- // Welcome screen
116
+ // Welcome
123
117
  // ─────────────────────────────────────────────────────────────────
124
- export function renderWelcome(memCount = 0, extCount = 0, cmdCount = 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
- chalk.hex("#4EC9B0").bold(" ██╔════╝ ██╔════╝████╗ ████║██║████╗ ██║██║"),
129
- chalk.hex("#569CD6").bold(" ██║ ███╗█████╗ ██╔████╔██║██║██╔██╗ ██║██║"),
130
- chalk.hex("#569CD6").bold(" ██║ ██║██╔══╝ ██║╚██╔╝██║██║██║╚██╗██║██║"),
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 screen
135
+ // Help
148
136
  // ─────────────────────────────────────────────────────────────────
149
137
  export function renderHelp(customCommands = {}) {
150
- const builtIn = [
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.hex("#569CD6").bold(" ┌─ BUILT-IN COMMANDS ─────────────────────────────────────┐"),
177
- ...builtIn.map(([cmd, desc]) =>
178
- chalk.hex("#569CD6")(" │ ") +
179
- chalk.hex("#CE9178").bold(cmd.padEnd(26)) +
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
- chalk.hex("#569CD6")(" └────────────────────────────────────────────────────────┘"),
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("", chalk.hex("#C586C0").bold(" ┌─ CUSTOM COMMANDS ─────────────────────────────────────────┐"));
189
- cmds.forEach(([key, cmd]) => {
190
- lines.push(
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("", chalk.hex("#6A9955")(" Tip: /yolo skips all confirmations · Ctrl+C to cancel · Ctrl+D to exit"), "");
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
+ }