@ikyyofc/gemini-cli 4.0.7 → 4.0.9
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 +9 -51
- package/package.json +1 -1
- package/src/agent.js +60 -80
package/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "fs";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
|
|
7
|
-
import { chat
|
|
7
|
+
import { chat } from "./src/gemini.js";
|
|
8
8
|
import { runAgentLoop } from "./src/agent.js";
|
|
9
9
|
import {
|
|
10
10
|
loadMemory, buildContextString, memoryShow, memoryAdd, ensureGlobalDir, GLOBAL_DIR
|
|
@@ -163,62 +163,20 @@ async function send(rawLine) {
|
|
|
163
163
|
history.push({ role: "assistant", content: res.finalResponse });
|
|
164
164
|
}
|
|
165
165
|
} else {
|
|
166
|
-
// Chat mode — streaming
|
|
166
|
+
// Chat mode — non-streaming
|
|
167
|
+
const sp = new Spinner();
|
|
167
168
|
const msgs = [];
|
|
168
169
|
if (sysInstruction()) msgs.push({ role: "system", content: sysInstruction() });
|
|
169
170
|
msgs.push(...history, { role: "user", content: userText });
|
|
170
|
-
|
|
171
|
-
const W = bw();
|
|
172
|
-
const prefix = chalk.hex("#00D4AA")(" │ ");
|
|
173
|
-
let started = false;
|
|
174
|
-
let fullText = "";
|
|
175
|
-
let thinkText = "";
|
|
176
|
-
|
|
177
|
-
const sp = new Spinner();
|
|
178
171
|
sp.start("thinking…", "#4A9EFF");
|
|
179
|
-
|
|
180
172
|
try {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
},
|
|
187
|
-
onText: (chunk) => {
|
|
188
|
-
if (!started) {
|
|
189
|
-
sp.stop();
|
|
190
|
-
// Show thinking first if any
|
|
191
|
-
if (thinkText.trim()) {
|
|
192
|
-
process.stdout.write(
|
|
193
|
-
"\n" + chalk.hex("#2A2A40")(" ╭─ thinking " + "─".repeat(Math.max(2, W - 13))) + "\n"
|
|
194
|
-
);
|
|
195
|
-
thinkText.trim().split("\n").forEach(line =>
|
|
196
|
-
process.stdout.write(chalk.hex("#2A2A40")(" │ ") + chalk.hex("#4A4A6A").italic(line) + "\n")
|
|
197
|
-
);
|
|
198
|
-
process.stdout.write(chalk.hex("#2A2A40")(" ╰" + "─".repeat(W + 1)) + "\n\n");
|
|
199
|
-
}
|
|
200
|
-
process.stdout.write(
|
|
201
|
-
"\n" + chalk.hex("#00D4AA")(" ╭─") +
|
|
202
|
-
chalk.hex("#00D4AA").bold(" gemini ") +
|
|
203
|
-
chalk.hex("#4A4A5E")("─".repeat(Math.max(0, W - 10))) + "\n"
|
|
204
|
-
);
|
|
205
|
-
started = true;
|
|
206
|
-
}
|
|
207
|
-
fullText += chunk;
|
|
208
|
-
process.stdout.write(prefix + chunk.replace(/\n/g, "\n" + prefix));
|
|
209
|
-
},
|
|
210
|
-
onDone: () => {
|
|
211
|
-
if (!started) sp.stop();
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
if (started) {
|
|
216
|
-
process.stdout.write("\n" + chalk.hex("#00D4AA")(" ╰" + "─".repeat(W + 1)) + "\n\n");
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
history.push({ role: "user", content: userText }, { role: "assistant", content: fullText });
|
|
173
|
+
const t0 = Date.now();
|
|
174
|
+
const reply = await chat(msgs, pendingFile || null);
|
|
175
|
+
sp.succeed(`done ${((Date.now()-t0)/1000).toFixed(1)}s`);
|
|
176
|
+
history.push({ role: "user", content: userText }, { role: "assistant", content: reply });
|
|
177
|
+
printAssistant(reply);
|
|
220
178
|
} catch (e) {
|
|
221
|
-
sp.
|
|
179
|
+
sp.fail(e.message);
|
|
222
180
|
printError(e.message);
|
|
223
181
|
}
|
|
224
182
|
}
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// src/agent.js — ReAct agent loop with
|
|
1
|
+
// src/agent.js — ReAct agent loop with auto skill injection
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { callGemini
|
|
3
|
+
import { callGemini } from "./gemini.js";
|
|
4
4
|
import { GEMINI_TOOLS, FUNCTION_DECLARATIONS, executeTool } from "./tools.js";
|
|
5
5
|
import { Spinner } from "./utils/spinner.js";
|
|
6
|
-
import { loadSkills
|
|
6
|
+
import { loadSkills } from "./skills.js";
|
|
7
7
|
import {
|
|
8
8
|
printAssistant, printError, printWarning,
|
|
9
9
|
printToolCall, printToolResult,
|
|
@@ -13,18 +13,47 @@ import {
|
|
|
13
13
|
const TIMEOUT_MS = 10 * 60 * 1000;
|
|
14
14
|
|
|
15
15
|
// ─────────────────────────────────────────────────────────────────
|
|
16
|
-
//
|
|
16
|
+
// Auto-inject relevant skill content into conversation
|
|
17
|
+
// This happens BEFORE the first LLM call — model has no choice
|
|
18
|
+
// but to see and follow the skill instructions.
|
|
17
19
|
// ─────────────────────────────────────────────────────────────────
|
|
18
|
-
function
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
function injectSkills(userMessage, messages) {
|
|
21
|
+
const skills = loadSkills();
|
|
22
|
+
if (!skills.length) return messages;
|
|
23
|
+
|
|
24
|
+
const query = userMessage.toLowerCase();
|
|
25
|
+
const words = query.split(/\s+/).filter(w => w.length > 3);
|
|
26
|
+
|
|
27
|
+
// Score each skill by keyword match
|
|
28
|
+
const scored = skills.map(skill => {
|
|
29
|
+
const hay = (skill.name + " " + skill.slug + " " + skill.content.slice(0, 400)).toLowerCase();
|
|
30
|
+
const hits = words.filter(w => hay.includes(w)).length;
|
|
31
|
+
return { skill, hits };
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Use matched skills, or ALL skills if none matched (task is ambiguous)
|
|
35
|
+
const matched = scored.filter(s => s.hits > 0).map(s => s.skill);
|
|
36
|
+
const toInject = matched.length > 0 ? matched : skills;
|
|
37
|
+
|
|
38
|
+
const block = toInject.map(s =>
|
|
39
|
+
`=== SKILL: ${s.name} ===\n${s.content.trim()}`
|
|
40
|
+
).join("\n\n---\n\n");
|
|
41
|
+
|
|
42
|
+
// Replace the last user message with skill context prepended
|
|
43
|
+
const last = messages[messages.length - 1];
|
|
44
|
+
const newLast = {
|
|
45
|
+
role: "user",
|
|
46
|
+
content: `[SKILLS TO APPLY FOR THIS TASK]\n\n${block}\n\n[END SKILLS]\n\nTask: ${last.content ?? userMessage}`
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return [...messages.slice(0, -1), newLast];
|
|
50
|
+
}
|
|
27
51
|
|
|
52
|
+
// ─────────────────────────────────────────────────────────────────
|
|
53
|
+
// System prompt — clean, no skill listing
|
|
54
|
+
// ─────────────────────────────────────────────────────────────────
|
|
55
|
+
function buildSystemPrompt(extra = "") {
|
|
56
|
+
const toolList = FUNCTION_DECLARATIONS.map(t => `- ${t.name}: ${t.description}`).join("\n");
|
|
28
57
|
const now = new Date();
|
|
29
58
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
30
59
|
const datetime = now.toLocaleString("id-ID", { timeZone: tz, dateStyle: "full", timeStyle: "long" });
|
|
@@ -38,30 +67,21 @@ ${datetime} (${tz})
|
|
|
38
67
|
Use tools to complete every task. Never instruct the user to do anything themselves.
|
|
39
68
|
|
|
40
69
|
## WORKFLOW
|
|
41
|
-
1.
|
|
42
|
-
2.
|
|
43
|
-
3.
|
|
44
|
-
4.
|
|
45
|
-
5. REPORT — brief summary
|
|
70
|
+
1. EXPLORE — understand the project structure
|
|
71
|
+
2. ACT — complete the task fully using tools
|
|
72
|
+
3. VERIFY — test after changes
|
|
73
|
+
4. REPORT — brief summary
|
|
46
74
|
|
|
47
75
|
## TOOLS
|
|
48
76
|
${toolList}
|
|
49
77
|
|
|
50
|
-
${skillIndex ? `## AVAILABLE SKILLS
|
|
51
|
-
You have skills installed. ALWAYS check if a skill is relevant before starting a task.
|
|
52
|
-
If relevant, read it first: read_file("${SKILLS_DIR}/<slug>/SKILL.md")
|
|
53
|
-
|
|
54
|
-
Installed:
|
|
55
|
-
${skillIndex}` : ""}
|
|
56
|
-
|
|
57
78
|
## RULES
|
|
58
|
-
- Always read files before editing
|
|
79
|
+
- Always read files before editing them
|
|
59
80
|
- Use patch_file for targeted edits
|
|
60
81
|
- Verify code works after changes
|
|
61
82
|
- If a command fails, diagnose and retry
|
|
62
83
|
- Platform: ${process.platform} | CWD: ${process.cwd()}
|
|
63
|
-
|
|
64
|
-
${extra ? `## EXTRA\n${extra}` : ""}`.trim();
|
|
84
|
+
${extra ? `\n## EXTRA\n${extra}` : ""}`.trim();
|
|
65
85
|
}
|
|
66
86
|
|
|
67
87
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -87,10 +107,14 @@ export async function runAgentLoop(userMessage, history, {
|
|
|
87
107
|
} = {}) {
|
|
88
108
|
|
|
89
109
|
const fullSystem = buildSystemPrompt(systemInstruction ?? "");
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
110
|
+
|
|
111
|
+
// Build messages and auto-inject relevant skills
|
|
112
|
+
let messages = [...history, { role: "user", content: userMessage }];
|
|
113
|
+
messages = injectSkills(userMessage, messages);
|
|
114
|
+
|
|
115
|
+
const spinner = new Spinner();
|
|
116
|
+
const deadline = Date.now() + TIMEOUT_MS;
|
|
117
|
+
let iteration = 0;
|
|
94
118
|
|
|
95
119
|
while (true) {
|
|
96
120
|
if (Date.now() > deadline) {
|
|
@@ -125,16 +149,13 @@ export async function runAgentLoop(userMessage, history, {
|
|
|
125
149
|
// Show thinking
|
|
126
150
|
printThinkBlock(thinkContent);
|
|
127
151
|
|
|
128
|
-
// ── Final answer
|
|
152
|
+
// ── Final answer ───────────────────────────────────────────
|
|
129
153
|
if (callParts.length === 0) {
|
|
130
|
-
if (textContent)
|
|
131
|
-
// Stream the final answer for better UX
|
|
132
|
-
await streamFinalAnswer(messages, fullSystem, thinkContent);
|
|
133
|
-
}
|
|
154
|
+
if (textContent) printAssistant(textContent);
|
|
134
155
|
return { finalResponse: textContent, iterations: iteration };
|
|
135
156
|
}
|
|
136
157
|
|
|
137
|
-
//
|
|
158
|
+
// Reasoning text before tool block
|
|
138
159
|
if (textContent) {
|
|
139
160
|
textContent.split("\n").forEach((line, i) =>
|
|
140
161
|
process.stdout.write(
|
|
@@ -145,7 +166,7 @@ export async function runAgentLoop(userMessage, history, {
|
|
|
145
166
|
}
|
|
146
167
|
|
|
147
168
|
// ── Tool calls ─────────────────────────────────────────────
|
|
148
|
-
//
|
|
169
|
+
// Push ALL parts including thinking into history — maintains context continuity
|
|
149
170
|
messages.push({ role: "model", parts });
|
|
150
171
|
|
|
151
172
|
printStepHeader(iteration);
|
|
@@ -164,44 +185,3 @@ export async function runAgentLoop(userMessage, history, {
|
|
|
164
185
|
messages.push({ role: "user", parts: responseParts });
|
|
165
186
|
}
|
|
166
187
|
}
|
|
167
|
-
|
|
168
|
-
// ─────────────────────────────────────────────────────────────────
|
|
169
|
-
// Stream the final answer with live typing effect
|
|
170
|
-
// ─────────────────────────────────────────────────────────────────
|
|
171
|
-
async function streamFinalAnswer(messages, systemInstruction, prevThink) {
|
|
172
|
-
const W = bw();
|
|
173
|
-
const prefix = chalk.hex("#00D4AA")(" │ ");
|
|
174
|
-
let started = false;
|
|
175
|
-
let thinkAcc = "";
|
|
176
|
-
|
|
177
|
-
const printHeader = () => {
|
|
178
|
-
process.stdout.write(
|
|
179
|
-
"\n" + chalk.hex("#00D4AA")(" ╭─") +
|
|
180
|
-
chalk.hex("#00D4AA").bold(" gemini ") +
|
|
181
|
-
chalk.hex("#4A4A5E")("─".repeat(Math.max(0, W - 10))) + "\n"
|
|
182
|
-
);
|
|
183
|
-
started = true;
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
await callGeminiStream({
|
|
187
|
-
messages,
|
|
188
|
-
systemInstruction,
|
|
189
|
-
onThought: (chunk) => {
|
|
190
|
-
thinkAcc += chunk;
|
|
191
|
-
},
|
|
192
|
-
onText: (chunk) => {
|
|
193
|
-
if (!started) printHeader();
|
|
194
|
-
process.stdout.write(prefix + chunk.replace(/\n/g, "\n" + prefix));
|
|
195
|
-
},
|
|
196
|
-
onDone: (parts) => {
|
|
197
|
-
// Show thinking if new thinking arrived in final answer
|
|
198
|
-
if (thinkAcc.trim() && thinkAcc !== prevThink) {
|
|
199
|
-
printThinkBlock(thinkAcc);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
if (started) {
|
|
205
|
-
process.stdout.write("\n" + chalk.hex("#00D4AA")(" ╰" + "─".repeat(W + 1)) + "\n\n");
|
|
206
|
-
}
|
|
207
|
-
}
|