@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 +60 -94
- package/index.js +38 -10
- package/package.json +6 -16
- package/src/agent.js +96 -77
- package/src/extensions.js +2 -2
- package/src/gemini-bak.js.js +119 -0
- package/src/gemini.js +1 -1
- package/src/memory.js +1 -1
- package/src/renderer.js +158 -156
- package/src/tools.js +3 -3
- package/src/utils/proxy.js +1 -0
- package/src/utils/spinner.js +79 -0
- package/utils/proxy-manager.js +0 -1
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
|
-
##
|
|
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" #
|
|
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 #
|
|
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
|
-
|
|
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
|
-
/
|
|
57
|
-
/
|
|
58
|
-
/
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
│
|
|
131
|
-
└──
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
├──
|
|
138
|
-
|
|
139
|
-
|
|
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",
|
|
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
|
|
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(
|
|
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(
|
|
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.
|
|
4
|
-
"description": "AI CLI
|
|
3
|
+
"version": "2.0.8",
|
|
4
|
+
"description": "AI Agent CLI — native function calling · GEMINI.md context · extensions",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
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
|
-
"
|
|
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
|
|
1
|
+
// src/agent.js — ReAct agent loop, native Gemini function calling
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
25
|
-
const messages
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
// ──
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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(
|
|
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
|
-
|
|
58
|
-
const
|
|
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
|
-
// ──
|
|
99
|
+
// ── Final answer — no more tool calls ─────────────────────
|
|
70
100
|
if (callParts.length === 0) {
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
// ──
|
|
84
|
-
|
|
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
|
-
|
|
90
|
-
chalk.hex("#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|