@ikyyofc/gemini-cli 2.0.6 → 2.0.8

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/README.md CHANGED
@@ -3,17 +3,28 @@
3
3
  > AI Agent CLI — native function calling · GEMINI.md context · extension system
4
4
 
5
5
  ```
6
- ██████╗ ███████╗███╗ ███╗██╗███╗ ██╗██╗
7
- ██╔════╝ ██╔════╝████╗ ████║██║████╗ ██║██║
8
- ██║ ███╗█████╗ ██╔████╔██║██║██╔██╗ ██║██║
9
- ██║ ██║██╔══╝ ██║╚██╔╝██║██║██║╚██╗██║██║
10
- ╚██████╔╝███████╗██║ ╚═╝ ██║██║██║ ╚████║██║
11
- ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝
6
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
7
+ Gemini CLI ─ AI Agent ─ native function calling
8
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12
9
  ```
13
10
 
11
+ Gemini CLI adalah asisten terminal bertenaga AI yang menggunakan model Gemini dari Google. CLI ini bukan sekadar antarmuka chat biasa, melainkan sebuah **AI Agent** yang dapat berinteraksi langsung dengan sistem file dan environment lokal Anda menggunakan *native function calling*.
12
+
14
13
  ---
15
14
 
16
- ## Instalasi
15
+ ## 📚 Dokumentasi Lengkap
16
+
17
+ Untuk detail lebih lanjut mengenai arsitektur dan fitur spesifik, silakan baca dokumentasi berikut:
18
+
19
+ - [**Architecture Overview**](./docs/ARCHITECTURE.md) - Penjelasan tentang ReAct loop dan cara kerja agent.
20
+ - [**API Reference**](./docs/API.md) - Referensi fungsi utama (`callGemini`, `chat`).
21
+ - [**Tools (Function Calling)**](./docs/TOOLS.md) - Daftar lengkap tool yang tersedia untuk agent (baca file, jalankan shell, dll).
22
+ - [**Extensions System**](./docs/EXTENSIONS.md) - Cara membuat dan mengelola ekstensi serta custom commands.
23
+ - [**Memory & Context**](./docs/MEMORY.md) - Panduan menggunakan `GEMINI.md` untuk memberikan konteks pada agent.
24
+
25
+ ---
26
+
27
+ ## 🚀 Instalasi
17
28
 
18
29
  ```bash
19
30
  npm install
@@ -23,118 +34,73 @@ npm link # optional: pakai sebagai `gemini` di terminal
23
34
 
24
35
  ---
25
36
 
26
- ## Penggunaan
37
+ ## 💻 Penggunaan
38
+
39
+ Anda dapat menggunakan Gemini CLI dalam mode interaktif (REPL) atau mode *one-shot* langsung dari terminal.
27
40
 
28
41
  ```bash
29
- gemini # interactive agent
30
- gemini "buatkan REST API di ./api" # one-shot
31
- gemini --system "Kamu senior backend engineer"
32
- gemini --file ./app.js "jelaskan kode ini"
33
- gemini --yolo "refactor semua file di src/"
34
- gemini --chat # plain chat tanpa tools
42
+ gemini # Masuk ke mode interactive agent
43
+ gemini "buatkan REST API di ./api" # One-shot task
44
+ gemini --system "Kamu senior backend engineer" # Set system prompt
45
+ gemini --file ./app.js "jelaskan kode ini" # Lampirkan file
46
+ gemini --yolo "refactor semua file di src/" # Skip semua konfirmasi tool (HATI-HATI)
47
+ gemini --chat # Plain chat tanpa tools (bukan agent)
35
48
  ```
36
49
 
37
- ---
38
-
39
- ## GEMINI.md — Context Files
50
+ ### Interactive Commands
40
51
 
41
- Buat `GEMINI.md` di lokasi berikut (dimuat hierarki, seperti Gemini CLI asli):
52
+ Saat berada di dalam mode interaktif, Anda dapat menggunakan perintah berikut:
42
53
 
43
- | Lokasi | Scope |
44
- |--------|-------|
45
- | `~/.gemini/GEMINI.md` | Global semua project |
46
- | `./GEMINI.md` | Project root (sampai `.git`) |
47
-
48
- Support import antar file:
49
- ```md
50
- @./components/style.md
51
- @../shared/conventions.md
52
- ```
53
-
54
- **Commands:**
55
54
  ```
56
- /memory show tampilkan semua context yang dimuat
57
- /memory reload reload dari disk
58
- /memory add <text> append ke ~/.gemini/GEMINI.md
55
+ /agent Toggle agent mode (tools on/off)
56
+ /yolo Skip all tool confirmations
57
+ /file <path> Attach file to next message
58
+ /system <text> → Set system instruction
59
+ /history → Show conversation turns
60
+ /export <file> → Export history to JSON
61
+ /cd <path> → Change working directory
62
+ /cwd → Show current working directory
63
+ /new /clear → Reset conversation
64
+ /model → Show model & config
65
+ /help → Show help
66
+ /exit /quit → Exit
59
67
  ```
60
68
 
61
69
  ---
62
70
 
63
- ## Extensions
64
-
65
- Manifest: `~/.gemini/extensions/<name>/gemini-extension.json`
66
-
67
- ```json
68
- {
69
- "name": "my-ext",
70
- "version": "1.0.0",
71
- "description": "...",
72
- "contextFileName": "GEMINI.md",
73
- "enabled": true,
74
- "commands": {
75
- "do-thing": {
76
- "description": "Does a thing",
77
- "prompt": "Do this: {{args}}"
78
- }
79
- }
80
- }
81
- ```
71
+ ## 🧠 Fitur Utama
82
72
 
83
- **Commands:**
84
- ```
85
- /ext list
86
- /ext install /path/to/ext
87
- /ext install https://github.com/user/repo
88
- /ext uninstall <name>
89
- /ext enable / disable <name>
90
- /ext update <name>
91
- ```
92
-
93
- Custom commands dipanggil: `/code-reviewer:review ./src/app.js`
73
+ ### 1. Native Function Calling (Tools)
74
+ Agent dapat membaca file, menulis file, menjalankan perintah shell, dan mencari file secara mandiri untuk menyelesaikan tugas yang Anda berikan. [Baca selengkapnya](./docs/TOOLS.md).
94
75
 
95
- ---
76
+ ### 2. Hierarchical Context (`GEMINI.md`)
77
+ Anda dapat memberikan instruksi spesifik proyek atau global menggunakan file `GEMINI.md`. Agent akan memuat konteks ini secara otomatis. [Baca selengkapnya](./docs/MEMORY.md).
96
78
 
97
- ## Tools (Native Function Calling)
98
-
99
- | Tool | Aksi |
100
- |------|------|
101
- | `read_file` | Baca file |
102
- | `write_file` | Tulis/overwrite file |
103
- | `patch_file` | Edit bagian spesifik |
104
- | `append_file` | Append ke file |
105
- | `list_dir` | List direktori |
106
- | `find_files` | Cari file (glob) |
107
- | `search_in_files` | Grep dalam file |
108
- | `run_shell` | Jalankan shell command |
109
- | `create_dir` | Buat direktori |
110
- | `delete_file` | Hapus file |
111
- | `move_file` | Pindah/rename |
112
- | `get_env` | Info environment |
113
- | `read_url` | Fetch URL / API |
79
+ ### 3. Extension System
80
+ Perluas kemampuan CLI dengan membuat ekstensi yang berisi custom commands dan konteks tambahan. [Baca selengkapnya](./docs/EXTENSIONS.md).
114
81
 
115
82
  ---
116
83
 
117
- ## Struktur
84
+ ## 📂 Struktur Direktori
118
85
 
119
86
  ```
120
87
  gemini-cli/
121
88
  ├── index.js ← CLI entry + REPL + commands
122
- ├── GEMINI.md ← Project context (auto-loaded)
123
89
  ├── package.json
90
+ ├── docs/ ← Dokumentasi lengkap
124
91
  ├── src/
125
92
  │ ├── gemini.js ← API client (native function calling)
126
93
  │ ├── tools.js ← functionDeclarations + executor
127
94
  │ ├── agent.js ← ReAct loop
128
95
  │ ├── memory.js ← GEMINI.md hierarchy loader
129
96
  │ ├── extensions.js ← Extension manager
130
- └── renderer.js ← Terminal UI + markdown
131
- └── extensions/
132
- └── code-reviewer/
133
- ├── gemini-extension.json
134
- └── GEMINI.md
135
-
136
- ~/.gemini/ ← Global config dir
137
- ├── GEMINI.md
138
- ├── extensions/
139
- └── commands/
140
- ```
97
+ ├── renderer.js ← Terminal UI + markdown
98
+ └── input.js ← Bracketed paste via Transform stream
99
+ └── utils/
100
+ └── proxy-manager.js ← Global proxy manager
101
+
102
+ ~/.gemini/ ← Global config dir (dibuat otomatis)
103
+ ├── GEMINI.md ← Global context
104
+ ├── extensions/ ← Folder instalasi ekstensi
105
+ └── commands/ ← Global custom commands
106
+ ```
package/index.js CHANGED
@@ -22,11 +22,14 @@ import {
22
22
  PasteTransform, restorePaste,
23
23
  enableBracketedPaste, disableBracketedPaste
24
24
  } from "./src/input.js";
25
+ import { setupGlobalProxy, proxyStatus, setProxyEnabled } from "./src/utils/proxy.js";
26
+ import { Spinner } from "./src/utils/spinner.js";
25
27
 
26
28
  // ─────────────────────────────────────────────────────────────────
27
29
  // Bootstrap
28
30
  // ─────────────────────────────────────────────────────────────────
29
31
  ensureGlobalDir();
32
+ setupGlobalProxy(); // aktifkan rotasi proxy sebelum request apapun
30
33
 
31
34
  let extensions = loadExtensions();
32
35
  let memoryLoaded = [];
@@ -81,13 +84,20 @@ function cleanup() {
81
84
  disableBracketedPaste();
82
85
  try { if (process.stdin.isTTY) process.stdin.setRawMode(false); } catch {}
83
86
  }
84
- process.on("exit", cleanup);
87
+ process.on("exit", cleanup);
85
88
  process.on("SIGTERM", () => { cleanup(); process.exit(0); });
86
89
 
90
+ // When terminal is resized (or focus returns on some terminals),
91
+ // only redraw prompt if we are NOT currently processing
92
+ process.on("SIGWINCH", () => { if (!processing) showPrompt(); });
93
+
87
94
  // ─────────────────────────────────────────────────────────────────
88
95
  // Prompt
89
96
  // ─────────────────────────────────────────────────────────────────
90
97
  function showPrompt() {
98
+ // Never redraw prompt while a request is in flight
99
+ if (processing) return;
100
+
91
101
  const mode = agentMode ? chalk.hex("#4EC9B0")("agent") : chalk.dim("chat");
92
102
  const yolo = autoApprove ? chalk.red(" yolo") : "";
93
103
  const file = pendingFile ? chalk.yellow(` +${path.basename(pendingPath)}`) : "";
@@ -127,29 +137,25 @@ async function send(rawLine) {
127
137
  const res = await runAgentLoop(userText, history, {
128
138
  systemInstruction: sysInstruction(),
129
139
  autoApprove,
130
- maxIterations: 5000,
131
140
  });
132
141
  if (res?.finalResponse) {
133
142
  history.push({ role: "user", content: userText });
134
143
  history.push({ role: "assistant", content: res.finalResponse });
135
144
  }
136
145
  } else {
137
- const { default: ora } = await import("ora");
138
- const sp = ora({
139
- text: "thinking…", spinner: "dots", color: "cyan",
140
- prefixText: " ", discardStdin: false
141
- }).start();
146
+ const sp = new Spinner();
142
147
  const msgs = [];
143
148
  if (sysInstruction()) msgs.push({ role: "system", content: sysInstruction() });
144
149
  msgs.push(...history, { role: "user", content: userText });
150
+ sp.start("thinking…", "#4A9EFF");
145
151
  try {
146
152
  const t0 = Date.now();
147
153
  const reply = await chat(msgs, pendingFile || null);
148
- sp.succeed(chalk.dim(`done (${((Date.now()-t0)/1000).toFixed(1)}s)`));
154
+ sp.succeed(`done ${((Date.now()-t0)/1000).toFixed(1)}s`);
149
155
  history.push({ role: "user", content: userText }, { role: "assistant", content: reply });
150
156
  printAssistant(reply);
151
157
  } catch (e) {
152
- sp.fail("failed");
158
+ sp.fail(e.message);
153
159
  printError(e.message);
154
160
  }
155
161
  }
@@ -275,13 +281,35 @@ async function handleCommand(input) {
275
281
  catch (e) { printError(e.message); }
276
282
  break;
277
283
 
278
- case "/model":
284
+ case "/model": {
285
+ const px = proxyStatus();
279
286
  printInfo(`model gemini-pro-latest`);
280
287
  printInfo(`tools ${agentMode ? "on (native function calling)" : "off"}`);
281
288
  printInfo(`yolo ${autoApprove ? "on" : "off"}`);
282
289
  printInfo(`memory ${memoryLoaded.length} file(s) · extensions: ${extensions.length}`);
290
+ printInfo(`proxy ${px.available}/${px.total} available · ${px.blocked} blocked · ${px.enabled ? "on" : "off"}`);
283
291
  printInfo(`config ${GLOBAL_DIR}`);
284
292
  break;
293
+ }
294
+
295
+ case "/proxy": {
296
+ const px = proxyStatus();
297
+ const sub = tokens[1];
298
+ if (sub === "off") {
299
+ setProxyEnabled(false);
300
+ printInfo("proxy rotation off — direct connection");
301
+ } else if (sub === "on") {
302
+ setProxyEnabled(true);
303
+ printSuccess("proxy rotation on");
304
+ } else {
305
+ // Show status
306
+ printInfo(`status ${px.enabled ? "on" : "off"}`);
307
+ printInfo(`available ${px.available} / ${px.total}`);
308
+ printInfo(`blocked ${px.blocked} (auto-unblock setelah 15 menit)`);
309
+ printInfo(`usage /proxy on | /proxy off`);
310
+ }
311
+ break;
312
+ }
285
313
 
286
314
  case "/exit": case "/quit":
287
315
  cleanup(); console.log(""); process.exit(0); break;
package/package.json CHANGED
@@ -1,25 +1,15 @@
1
1
  {
2
2
  "name": "@ikyyofc/gemini-cli",
3
- "version": "2.0.6",
4
- "description": "AI CLI Agent powered by Gemini your own terminal assistant",
3
+ "version": "2.0.8",
4
+ "description": "AI Agent CLI native function calling · GEMINI.md context · extensions",
5
5
  "type": "module",
6
- "main": "index.js",
7
- "bin": {
8
- "gemini": "./index.js"
9
- },
10
- "scripts": {
11
- "start": "node index.js",
12
- "dev": "node --watch index.js"
13
- },
6
+ "bin": { "gemini": "./index.js" },
7
+ "scripts": { "start": "node index.js" },
14
8
  "dependencies": {
15
9
  "axios": "^1.7.9",
16
10
  "chalk": "^5.3.0",
17
- "cli-spinners": "^2.9.2",
18
11
  "file-type": "^19.6.0",
19
12
  "ora": "^8.1.1",
20
- "readline": "^1.3.0",
21
- "marked": "^12.0.0",
22
- "marked-terminal": "^7.1.0",
23
- "minimist": "^1.2.8"
13
+ "toml": "^3.0.0"
24
14
  }
25
- }
15
+ }
package/src/agent.js CHANGED
@@ -1,117 +1,136 @@
1
- // src/agent.js — ReAct agent loop using NATIVE Gemini function calling
1
+ // src/agent.js — ReAct agent loop, native Gemini function calling
2
2
  import chalk from "chalk";
3
- import ora from "ora";
4
- import { callGemini } from "./gemini.js";
5
- import { GEMINI_TOOLS, executeTool } from "./tools.js";
6
- import { printAssistant, printError, printWarning, printToolCall, printToolResult } from "./renderer.js";
7
-
8
- /**
9
- * Run the agent loop until the model gives a final text answer (no more tool calls).
10
- *
11
- * Native function calling flow:
12
- * 1. Send contents + tools (functionDeclarations) to Gemini
13
- * 2. Response parts may contain: { functionCall: { name, args } }
14
- * 3. Execute the tool → get result
15
- * 4. Append model turn (with functionCall) + user turn (with functionResponse)
16
- * 5. Repeat until response has only text parts → done
17
- */
3
+ import { callGemini } from "./gemini.js";
4
+ import { GEMINI_TOOLS, FUNCTION_DECLARATIONS, executeTool } from "./tools.js";
5
+ import { Spinner } from "./utils/spinner.js";
6
+ import {
7
+ printAssistant, printError, printWarning,
8
+ printToolCall, printToolResult,
9
+ printStepHeader, printStepFooter,
10
+ } from "./renderer.js";
11
+
12
+ const TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
13
+
14
+ // ─────────────────────────────────────────────────────────────────
15
+ // System prompt
16
+ // ─────────────────────────────────────────────────────────────────
17
+ function buildSystemPrompt(extra = "") {
18
+ const toolList = FUNCTION_DECLARATIONS.map(t => `- ${t.name}: ${t.description}`).join("\n");
19
+ return `You are an autonomous AI coding agent running in the user's terminal. You have full access to their filesystem and shell through tools.
20
+
21
+ ## CORE RULE — NEVER ASK, ALWAYS ACT
22
+
23
+ You MUST use tools to complete tasks. You are NEVER allowed to:
24
+ - Ask the user to run a command themselves
25
+ - Ask the user to write or edit any code themselves
26
+ - Say "you can run...", "try running...", "create a file...", "add this code to..."
27
+ - Give instructions instead of taking action
28
+
29
+ If you need to run something → use run_shell.
30
+ If you need to read a file → use read_file first.
31
+ If you need to create/edit a file → use write_file or patch_file.
32
+ If something fails → diagnose and retry automatically, never ask user.
33
+
34
+ ## WORKFLOW
35
+ 1. EXPLORE — read existing structure/files before acting
36
+ 2. ACT — use tools to complete the task fully
37
+ 3. VERIFY — run/test after changes to confirm it works
38
+ 4. REPORT — brief summary of what was done
39
+
40
+ ## TOOLS
41
+ ${toolList}
42
+
43
+ ## RULES
44
+ - Always read files before editing them
45
+ - Use patch_file for targeted edits (safer than full rewrite)
46
+ - Verify code works by running it after changes
47
+ - If a command fails, fix it yourself and retry
48
+ - Only ask the user if the request is genuinely ambiguous
49
+ - Current working directory: ${process.cwd()}
50
+ - Platform: ${process.platform}
51
+
52
+ ${extra ? `## EXTRA INSTRUCTIONS\n${extra}` : ""}`.trim();
53
+ }
54
+
55
+ // ─────────────────────────────────────────────────────────────────
56
+ // Agent loop
57
+ // ─────────────────────────────────────────────────────────────────
18
58
  export async function runAgentLoop(userMessage, history, {
19
59
  systemInstruction = null,
20
60
  autoApprove = false,
21
- maxIterations = 5000,
22
61
  } = {}) {
23
62
 
24
- // Build initial message list
25
- const messages = [
26
- ...history,
27
- { role: "user", content: userMessage }
28
- ];
63
+ const fullSystem = buildSystemPrompt(systemInstruction ?? "");
64
+ const messages = [...history, { role: "user", content: userMessage }];
65
+ const spinner = new Spinner();
66
+ const deadline = Date.now() + TIMEOUT_MS;
67
+ let iteration = 0;
29
68
 
30
- let iteration = 0;
69
+ while (true) {
70
+ if (Date.now() > deadline) {
71
+ spinner.stop();
72
+ printWarning("timeout: 10 menit tercapai, agent dihentikan");
73
+ return { finalResponse: null, iterations: iteration };
74
+ }
31
75
 
32
- while (iteration < maxIterations) {
33
76
  iteration++;
34
77
 
35
- // ── Call Gemini API with tools ──────────────────────────
36
- const spinner = ora({
37
- text: chalk.dim(iteration === 1 ? "thinking…" : `step ${iteration}…`),
38
- spinner: "dots",
39
- color: "cyan",
40
- prefixText: " ",
41
- discardStdin: false, // prevent ora from pausing stdin after stop
42
- }).start();
78
+ // ── Animated spinner while waiting for LLM ─────────────────
79
+ spinner.start(
80
+ iteration === 1 ? "thinking…" : `thinking… (step ${iteration})`,
81
+ "#4A9EFF"
82
+ );
43
83
 
44
84
  let parts;
45
85
  try {
46
- const res = await callGemini({ messages, tools: GEMINI_TOOLS, systemInstruction });
86
+ const res = await callGemini({ messages, tools: GEMINI_TOOLS, systemInstruction: fullSystem });
47
87
  parts = res.parts;
48
- spinner.stop();
49
- clearLine();
88
+ spinner.stop(); // clear line silently
50
89
  } catch (err) {
51
- spinner.fail(chalk.hex("#F44747")("API error"));
90
+ spinner.fail(err.message);
52
91
  printError(err.message);
53
- printError(JSON.stringify(err.response.data, null, 2));
54
92
  return null;
55
93
  }
56
94
 
57
- // ── Separate text parts from functionCall parts ─────────
58
- const textParts = parts.filter(p => p.text != null);
59
- const callParts = parts.filter(p => p.functionCall != null);
60
-
61
- // Print any accompanying text (model thinking aloud)
95
+ const textParts = parts.filter(p => p.text != null);
96
+ const callParts = parts.filter(p => p.functionCall != null);
62
97
  const textContent = textParts.map(p => p.text).join("").trim();
63
- if (textContent && callParts.length > 0) {
64
- process.stdout.write(
65
- chalk.dim(" … " + textContent.replace(/\n/g, "\n ")) + "\n"
66
- );
67
- }
68
98
 
69
- // ── No tool calls final answer ────────────────────────
99
+ // ── Final answer no more tool calls ─────────────────────
70
100
  if (callParts.length === 0) {
71
- const final = textParts.map(p => p.text).join("").trim();
72
- // Close the tool section if it was opened
73
- if (iteration > 1) {
74
- process.stdout.write(
75
- chalk.hex("#4A9EFF")(" ╰") +
76
- chalk.hex("#555566")("─".repeat(47)) + "\n"
77
- );
78
- }
79
- if (final) printAssistant(final);
80
- return { finalResponse: final, iterations: iteration };
101
+ if (textContent) printAssistant(textContent);
102
+ return { finalResponse: textContent, iterations: iteration };
81
103
  }
82
104
 
83
- // ── Execute each tool call ───────────────────────────────
84
- messages.push({ role: "model", parts });
85
-
86
- // Print tool section header on first iteration
87
- if (iteration === 1) {
105
+ // ── Model reasoning text (before tool block) ───────────────
106
+ if (textContent) {
88
107
  process.stdout.write(
89
- "\n" + chalk.hex("#4A9EFF")(" ╭─ working") +
90
- chalk.hex("#555566")(" " + "─".repeat(36)) + "\n"
108
+ chalk.hex("#4A4A5E")(" ") +
109
+ chalk.hex("#7A7A9A").italic(textContent.split("\n")[0].slice(0, 72)) +
110
+ "\n"
91
111
  );
92
112
  }
93
113
 
94
- const responseParts = [];
114
+ // ── Step block — fresh per iteration ──────────────────────
115
+ printStepHeader(iteration);
116
+ messages.push({ role: "model", parts });
95
117
 
118
+ const responseParts = [];
96
119
  for (const part of callParts) {
97
120
  const { name, args } = part.functionCall;
98
- printToolCall(name, args);
121
+
122
+ // Animate the tool name while it runs
123
+ spinner.start(name + "…", "#FFD080");
124
+ printToolCall(name, args ?? {});
125
+ spinner.stop();
99
126
 
100
127
  const result = await executeTool(name, args ?? {}, { autoApprove });
101
128
  printToolResult(result);
102
129
 
103
- responseParts.push({
104
- functionResponse: { name, response: result }
105
- });
130
+ responseParts.push({ functionResponse: { name, response: result } });
106
131
  }
107
132
 
133
+ printStepFooter();
108
134
  messages.push({ role: "user", parts: responseParts });
109
135
  }
110
-
111
- printWarning(`Max iterations (${maxIterations}) reached.`);
112
- return { finalResponse: null, iterations: iteration };
113
- }
114
-
115
- function clearLine() {
116
- if (process.stdout.clearLine) { process.stdout.clearLine(0); process.stdout.cursorTo(0); }
117
136
  }
package/src/extensions.js CHANGED
@@ -67,7 +67,7 @@ export function getExtensionContextDirs(extensions) {
67
67
  return extensions.map(e => e.path);
68
68
  }
69
69
 
70
- /** Build all custom commands from: extensions + ~/.gemini/commands/** */
70
+ /** Build all custom commands from extensions + ~/.gemini/commands/** */
71
71
  export async function loadCustomCommands(extensions) {
72
72
  const cmds = {}; // "namespace:name" → { description, prompt, source }
73
73
 
@@ -192,4 +192,4 @@ export async function updateExtension(name) {
192
192
  } catch (e) {
193
193
  return { error: e.message };
194
194
  }
195
- }
195
+ }