@ikyyofc/gemini-cli 4.0.2 → 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.2",
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,15 +134,14 @@ export async function runAgentLoop(userMessage, history, {
119
134
  return { finalResponse: textContent, iterations: iteration };
120
135
  }
121
136
 
122
- // ── Model reasoning show full, not preview ───────────────
137
+ // ── Model reasoning text alongside tool calls ──────────────
123
138
  if (textContent) {
124
- const lines = textContent.split("\n");
125
- lines.forEach((line, i) => {
139
+ textContent.split("\n").forEach((line, i) =>
126
140
  process.stdout.write(
127
141
  chalk.hex("#4A4A5E")(i === 0 ? " ┄ " : " ") +
128
142
  chalk.hex("#7A7A9A").italic(line) + "\n"
129
- );
130
- });
143
+ )
144
+ );
131
145
  }
132
146
 
133
147
  // ── Step block — fresh per iteration ──────────────────────
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
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": {