@ikyyofc/gemini-cli 4.0.1 → 4.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikyyofc/gemini-cli",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
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
@@ -7,7 +7,7 @@ import { loadSkills, buildSkillsPrompt } from "./skills.js";
7
7
  import {
8
8
  printAssistant, printError, printWarning,
9
9
  printToolCall, printToolResult,
10
- printStepHeader, printStepFooter,
10
+ printStepHeader, printStepFooter, bw,
11
11
  } from "./renderer.js";
12
12
 
13
13
  const TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
@@ -109,9 +109,24 @@ export async function runAgentLoop(userMessage, history, {
109
109
  return null;
110
110
  }
111
111
 
112
- const textParts = parts.filter(p => p.text != null);
113
- const callParts = parts.filter(p => p.functionCall != null);
114
- const textContent = textParts.map(p => p.text).join("").trim();
112
+ // Separate: thought parts (p.thought===true), text parts, tool call parts
113
+ const thoughtParts = parts.filter(p => p.thought === true && p.text);
114
+ const textParts = parts.filter(p => p.text != null && !p.thought);
115
+ const callParts = parts.filter(p => p.functionCall != null);
116
+ const textContent = textParts.map(p => p.text).join("").trim();
117
+ const thinkContent = thoughtParts.map(p => p.text).join("").trim();
118
+
119
+ // ── Show thinking (thought parts) ─────────────────────────
120
+ if (thinkContent) {
121
+ process.stdout.write(
122
+ "\n" + chalk.hex("#3A3A4E")(" ╭─ thinking ") +
123
+ chalk.hex("#3A3A4E")("─".repeat(Math.max(2, bw() - 13))) + "\n"
124
+ );
125
+ thinkContent.split("\n").forEach(line =>
126
+ process.stdout.write(chalk.hex("#3A3A4E")(" │ ") + chalk.hex("#5A5A7A").italic(line) + "\n")
127
+ );
128
+ process.stdout.write(chalk.hex("#3A3A4E")(" ╰" + "─".repeat(bw() - 1)) + "\n\n");
129
+ }
115
130
 
116
131
  // ── Final answer — no more tool calls ─────────────────────
117
132
  if (callParts.length === 0) {
@@ -119,12 +134,13 @@ export async function runAgentLoop(userMessage, history, {
119
134
  return { finalResponse: textContent, iterations: iteration };
120
135
  }
121
136
 
122
- // ── Model reasoning text (before tool block) ───────────────
137
+ // ── Model reasoning text alongside tool calls ──────────────
123
138
  if (textContent) {
124
- process.stdout.write(
125
- chalk.hex("#4A4A5E")(" ┄ ") +
126
- chalk.hex("#7A7A9A").italic(textContent.split("\n")[0].slice(0, 72)) +
127
- "\n"
139
+ textContent.split("\n").forEach((line, i) =>
140
+ process.stdout.write(
141
+ chalk.hex("#4A4A5E")(i === 0 ? "" : " ") +
142
+ chalk.hex("#7A7A9A").italic(line) + "\n"
143
+ )
128
144
  );
129
145
  }
130
146
 
package/src/renderer.js CHANGED
@@ -9,8 +9,8 @@ const C = {
9
9
  comment: "#6A9955", num: "#B5CEA8", fn: "#FFD080",
10
10
  };
11
11
 
12
- const tw = () => Math.min(process.stdout.columns || 72, 84);
13
- const bw = () => Math.min(tw() - 4, 68);
12
+ export const tw = () => Math.min(process.stdout.columns || 72, 84);
13
+ export const bw = () => Math.min(tw() - 4, 68);
14
14
  const vlen = s => s.replace(/\x1b\[[0-9;]*m/g, "").length;
15
15
 
16
16
  // Word-wrap a PLAIN (no ANSI) string to maxW, returns array of lines
@@ -285,32 +285,38 @@ export function printStepFooter() {
285
285
  }
286
286
 
287
287
  export function printToolCall(name, args = {}) {
288
- const argStr = Object.entries(args).map(([k, v]) => {
289
- const raw = String(v).replace(/\n/g, "↵");
290
- const val = raw.length > 42 ? raw.slice(0, 42) + "…" : raw;
291
- return chalk.hex(C.muted)(k + ":") + chalk.hex(C.orange)(val);
292
- }).join(" ");
288
+ // Tool name
293
289
  process.stdout.write(
294
- chalk.hex(C.yellow)("├─ ") + chalk.hex(C.blue).bold(name) +
295
- (argStr ? " " + argStr : "") + "\n"
290
+ chalk.hex(C.yellow)("├─ ") + chalk.hex(C.blue).bold(name) + "\n"
296
291
  );
292
+ // Each arg on its own line, full value, no truncation
293
+ Object.entries(args).forEach(([k, v]) => {
294
+ const raw = String(v);
295
+ const lines = raw.split("\n");
296
+ process.stdout.write(
297
+ chalk.hex(C.dimmer)("│ ") +
298
+ chalk.hex(C.muted)(k + ": ") +
299
+ chalk.hex(C.orange)(lines[0]) + "\n"
300
+ );
301
+ // Multi-line values (e.g. file content passed as arg)
302
+ lines.slice(1).forEach(l =>
303
+ process.stdout.write(chalk.hex(C.dimmer)("│ ") + chalk.hex(C.orange)(l) + "\n")
304
+ );
305
+ });
297
306
  }
298
307
 
299
308
  export function printToolResult(result) {
300
- const W = bw() - 5;
301
309
  const isErr = typeof result === "object" && result.error;
302
310
  const text = typeof result === "object"
303
311
  ? (result.result ?? result.error ?? JSON.stringify(result, null, 2))
304
312
  : String(result);
305
313
  const color = isErr ? chalk.hex(C.red) : chalk.hex(C.muted);
306
314
  const border = chalk.hex(C.dimmer)("│ ");
307
- const lines = text.split("\n");
308
- const shown = lines.slice(0, 10);
309
- const extra = lines.length - 10;
310
- shown.forEach(l =>
311
- process.stdout.write(border + color(l.length > W ? l.slice(0, W) + "…" : l) + "\n")
315
+
316
+ // Print ALL lines — no limit, no truncation
317
+ text.split("\n").forEach(l =>
318
+ process.stdout.write(border + color(l) + "\n")
312
319
  );
313
- if (extra > 0) process.stdout.write(border + chalk.hex(C.dim)(`… +${extra} more lines`) + "\n");
314
320
  }
315
321
 
316
322
  // ─────────────────────────────────────────────────────────────────
package/src/tools.js CHANGED
@@ -668,25 +668,48 @@ export async function executeTool(name, args = {}, { autoApprove = false } = {})
668
668
  // ── Real-time ─────────────────────────────────────────
669
669
  case "web_search": {
670
670
  const q = encodeURIComponent(args.query);
671
+
672
+ // 1. DuckDuckGo Instant Answer API (best for facts/definitions)
671
673
  const { stdout: ddg } = await execAsync(
672
- `curl -sL --max-time 10 "https://api.duckduckgo.com/?q=${q}&format=json&no_redirect=1&no_html=1&skip_disambig=1"`
674
+ `curl -sL --max-time 10 -H "User-Agent: Mozilla/5.0" "https://api.duckduckgo.com/?q=${q}&format=json&no_redirect=1&no_html=1&skip_disambig=1"`
673
675
  ).catch(() => ({ stdout: "" }));
674
- const { stdout: html } = await execAsync(
675
- `curl -sL --max-time 10 -H "User-Agent: Mozilla/5.0" "https://html.duckduckgo.com/html/?q=${q}" | grep -oP '(?<=class="result__snippet">)[^<]+' | head -8`
676
+
677
+ // 2. DuckDuckGo Lite HTML (more reliable for general search)
678
+ const { stdout: lite } = await execAsync(
679
+ `curl -sL --max-time 12 -H "User-Agent: Mozilla/5.0 (Linux; Android 10)" -H "Accept-Language: id,en" "https://lite.duckduckgo.com/lite/?q=${q}&kl=id-id" | grep -A1 'class="result-snippet"' | grep -v "result-snippet" | sed 's/<[^>]*>//g' | sed 's/^[[:space:]]*//' | grep -v '^$' | head -12`
680
+ ).catch(() => ({ stdout: "" }));
681
+
682
+ // 3. Also grab result titles from lite
683
+ const { stdout: titles } = await execAsync(
684
+ `curl -sL --max-time 12 -H "User-Agent: Mozilla/5.0 (Linux; Android 10)" "https://lite.duckduckgo.com/lite/?q=${q}&kl=id-id" | grep -oP '(?<=<a class="result-link"[^>]*>)[^<]+' | head -8`
676
685
  ).catch(() => ({ stdout: "" }));
677
686
 
678
687
  let out = "";
688
+
689
+ // Parse instant answer
679
690
  try {
680
691
  const d = JSON.parse(ddg);
681
- if (d.AbstractText) out += `${d.AbstractText}\nSource: ${d.AbstractURL}\n\n`;
682
- if (d.Answer) out += `Answer: ${d.Answer}\n\n`;
692
+ if (d.AbstractText) out += `📖 ${d.AbstractText}\n → ${d.AbstractURL}\n\n`;
693
+ if (d.Answer) out += `💡 ${d.Answer}\n\n`;
694
+ if (d.Definition) out += `📝 ${d.Definition}\n\n`;
683
695
  if (d.RelatedTopics?.length) {
684
- out += "Related:\n";
685
- d.RelatedTopics.slice(0, 5).forEach(t => { if (t.Text) out += ` · ${t.Text}\n`; });
696
+ const related = d.RelatedTopics.slice(0, 4).map(t => t.Text).filter(Boolean);
697
+ if (related.length) out += "Related:\n" + related.map(t => ` · ${t}`).join("\n") + "\n\n";
686
698
  }
687
699
  } catch {}
688
- if (html.trim()) out += "\nSnippets:\n" + html.trim().split("\n").map(l => ` · ${l.trim()}`).join("\n");
689
- return { result: out.trim() || "No results found." };
700
+
701
+ // Add titles + snippets from lite
702
+ const titleList = titles.trim().split("\n").filter(Boolean);
703
+ const snippetList = lite.trim().split("\n").filter(Boolean);
704
+ if (titleList.length) {
705
+ out += "Search Results:\n";
706
+ titleList.forEach((title, i) => {
707
+ out += ` ${i+1}. ${title.trim()}\n`;
708
+ if (snippetList[i]) out += ` ${snippetList[i].trim()}\n`;
709
+ });
710
+ }
711
+
712
+ return { result: out.trim() || "No results found. Try rephrasing the query." };
690
713
  }
691
714
 
692
715
  case "get_weather": {