@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.
Files changed (3) hide show
  1. package/index.js +9 -51
  2. package/package.json +1 -1
  3. 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, callGeminiStream } from "./src/gemini.js";
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
- await callGeminiStream({
182
- messages: msgs,
183
- fileBuffer: pendingFile || null,
184
- onThought: (chunk) => {
185
- thinkText += chunk;
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.stop();
179
+ sp.fail(e.message);
222
180
  printError(e.message);
223
181
  }
224
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikyyofc/gemini-cli",
3
- "version": "4.0.7",
3
+ "version": "4.0.9",
4
4
  "description": "AI Agent CLI — native function calling · GEMINI.md context · extensions",
5
5
  "type": "module",
6
6
  "bin": { "gemini": "./index.js" },
package/src/agent.js CHANGED
@@ -1,9 +1,9 @@
1
- // src/agent.js — ReAct agent loop with streaming final answer
1
+ // src/agent.js — ReAct agent loop with auto skill injection
2
2
  import chalk from "chalk";
3
- import { callGemini, callGeminiStream } from "./gemini.js";
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, SKILLS_DIR } from "./skills.js";
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
- // System prompt
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 buildSystemPrompt(extra = "") {
19
- const toolList = FUNCTION_DECLARATIONS.map(t => `- ${t.name}: ${t.description}`).join("\n");
20
- const skills = loadSkills();
21
- const skillIndex = skills.length
22
- ? skills.map(s => {
23
- const firstLine = s.content.split("\n").find(l => l.trim() && !l.startsWith("#")) ?? "";
24
- return ` - ${s.slug}: ${firstLine.slice(0, 80)}`;
25
- }).join("\n")
26
- : null;
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. CHECK SKILLS if skills are available, read the relevant one FIRST before starting
42
- 2. EXPLOREunderstand the project structure
43
- 3. ACTcomplete the task fully using tools
44
- 4. VERIFYtest after changes
45
- 5. REPORT — brief summary
70
+ 1. EXPLOREunderstand the project structure
71
+ 2. ACTcomplete the task fully using tools
72
+ 3. VERIFYtest after changes
73
+ 4. REPORTbrief 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
- const messages = [...history, { role: "user", content: userMessage }];
91
- const spinner = new Spinner();
92
- const deadline = Date.now() + TIMEOUT_MS;
93
- let iteration = 0;
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 — stream it ───────────────────────────────
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
- // Show reasoning text before tool block
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
- // Include ALL parts (thinking + text + calls) in history for continuity
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
- }