@ikyyofc/gemini-cli 3.0.9 → 4.0.0
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 +1 -1
- package/src/agent.js +13 -0
- package/src/tools.js +273 -0
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -20,8 +20,21 @@ function buildSystemPrompt(extra = "") {
|
|
|
20
20
|
const skills = loadSkills();
|
|
21
21
|
const skillsBlock = buildSkillsPrompt(skills);
|
|
22
22
|
|
|
23
|
+
// Current datetime injected directly — no tool needed
|
|
24
|
+
const now = new Date();
|
|
25
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
26
|
+
const datetime = now.toLocaleString("id-ID", {
|
|
27
|
+
timeZone: tz,
|
|
28
|
+
dateStyle: "full",
|
|
29
|
+
timeStyle: "long",
|
|
30
|
+
});
|
|
31
|
+
|
|
23
32
|
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.
|
|
24
33
|
|
|
34
|
+
## CURRENT TIME
|
|
35
|
+
${datetime} (${tz})
|
|
36
|
+
Unix timestamp: ${Math.floor(now.getTime() / 1000)}
|
|
37
|
+
|
|
25
38
|
## CORE RULE — NEVER ASK, ALWAYS ACT
|
|
26
39
|
|
|
27
40
|
You MUST use tools to complete tasks. You are NEVER allowed to:
|
package/src/tools.js
CHANGED
|
@@ -357,6 +357,89 @@ export const FUNCTION_DECLARATIONS = [
|
|
|
357
357
|
},
|
|
358
358
|
required: ["path", "permissions"]
|
|
359
359
|
}
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
// ── REAL-TIME TOOLS ──────────────────────────────────────────
|
|
363
|
+
{
|
|
364
|
+
name: "web_search",
|
|
365
|
+
description: "Search the web for current information, news, facts, or anything up-to-date. Use this when you need real-time or recent information.",
|
|
366
|
+
parameters: {
|
|
367
|
+
type: "OBJECT",
|
|
368
|
+
properties: {
|
|
369
|
+
query: { type: "STRING", description: "Search query" },
|
|
370
|
+
region: { type: "STRING", description: "Region code e.g. id-id, us-en, my-en (default: id-id)" }
|
|
371
|
+
},
|
|
372
|
+
required: ["query"]
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: "get_weather",
|
|
377
|
+
description: "Get current weather and forecast for any city or location.",
|
|
378
|
+
parameters: {
|
|
379
|
+
type: "OBJECT",
|
|
380
|
+
properties: {
|
|
381
|
+
location: { type: "STRING", description: "City name or location e.g. 'Jakarta', 'New York', 'Tokyo'" },
|
|
382
|
+
format: { type: "STRING", description: "simple (1 line) | full (detailed JSON) — default: simple" }
|
|
383
|
+
},
|
|
384
|
+
required: ["location"]
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "get_news",
|
|
389
|
+
description: "Get latest news headlines on any topic from around the web.",
|
|
390
|
+
parameters: {
|
|
391
|
+
type: "OBJECT",
|
|
392
|
+
properties: {
|
|
393
|
+
query: { type: "STRING", description: "Topic or keyword to search news for (e.g. 'AI', 'Indonesia', 'bitcoin')" },
|
|
394
|
+
language: { type: "STRING", description: "Language code: id, en, etc. (default: id)" },
|
|
395
|
+
limit: { type: "NUMBER", description: "Number of results (default: 10, max: 20)" }
|
|
396
|
+
},
|
|
397
|
+
required: ["query"]
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: "get_exchange_rate",
|
|
402
|
+
description: "Get current currency exchange rates. Supports all major currencies.",
|
|
403
|
+
parameters: {
|
|
404
|
+
type: "OBJECT",
|
|
405
|
+
properties: {
|
|
406
|
+
from: { type: "STRING", description: "Base currency code e.g. USD, IDR, EUR (default: USD)" },
|
|
407
|
+
to: { type: "STRING", description: "Target currency or comma-separated list e.g. 'IDR,EUR,JPY'" }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "get_stock",
|
|
413
|
+
description: "Get real-time or latest stock price and info for a ticker symbol.",
|
|
414
|
+
parameters: {
|
|
415
|
+
type: "OBJECT",
|
|
416
|
+
properties: {
|
|
417
|
+
symbol: { type: "STRING", description: "Stock ticker symbol e.g. AAPL, GOOGL, BBCA.JK, TLKM.JK" }
|
|
418
|
+
},
|
|
419
|
+
required: ["symbol"]
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "get_crypto",
|
|
424
|
+
description: "Get current cryptocurrency prices and market data.",
|
|
425
|
+
parameters: {
|
|
426
|
+
type: "OBJECT",
|
|
427
|
+
properties: {
|
|
428
|
+
coins: { type: "STRING", description: "Coin IDs comma-separated e.g. 'bitcoin,ethereum,solana'" },
|
|
429
|
+
currency: { type: "STRING", description: "Fiat currency for prices e.g. usd, idr (default: usd)" }
|
|
430
|
+
},
|
|
431
|
+
required: ["coins"]
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "get_ip_info",
|
|
436
|
+
description: "Get geolocation and network info for an IP address, or your own public IP if none given.",
|
|
437
|
+
parameters: {
|
|
438
|
+
type: "OBJECT",
|
|
439
|
+
properties: {
|
|
440
|
+
ip: { type: "STRING", description: "IP address to look up (leave empty for your own IP)" }
|
|
441
|
+
}
|
|
442
|
+
}
|
|
360
443
|
}
|
|
361
444
|
];
|
|
362
445
|
|
|
@@ -755,6 +838,196 @@ export async function executeTool(name, args = {}, { autoApprove = false } = {})
|
|
|
755
838
|
return { result: `chmod ${args.permissions} applied to ${p}` };
|
|
756
839
|
}
|
|
757
840
|
|
|
841
|
+
// ── REAL-TIME TOOLS ──────────────────────────────────────
|
|
842
|
+
case "web_search": {
|
|
843
|
+
const q = encodeURIComponent(args.query);
|
|
844
|
+
const reg = args.region ?? "id-id";
|
|
845
|
+
// DuckDuckGo Instant Answer API
|
|
846
|
+
const { stdout: ddg } = await execAsync(
|
|
847
|
+
`curl -sL --max-time 10 "https://api.duckduckgo.com/?q=${q}&format=json&no_redirect=1&no_html=1&skip_disambig=1"`
|
|
848
|
+
).catch(() => ({ stdout: "" }));
|
|
849
|
+
|
|
850
|
+
// Also get HTML search results via scraping (lite version)
|
|
851
|
+
const { stdout: html } = await execAsync(
|
|
852
|
+
`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`
|
|
853
|
+
).catch(() => ({ stdout: "" }));
|
|
854
|
+
|
|
855
|
+
let out = "";
|
|
856
|
+
try {
|
|
857
|
+
const data = JSON.parse(ddg);
|
|
858
|
+
if (data.AbstractText) out += `📖 ${data.AbstractText}\n Source: ${data.AbstractURL}\n\n`;
|
|
859
|
+
if (data.Answer) out += `💡 ${data.Answer}\n\n`;
|
|
860
|
+
if (data.RelatedTopics?.length) {
|
|
861
|
+
out += "Related:\n";
|
|
862
|
+
data.RelatedTopics.slice(0, 5).forEach(t => {
|
|
863
|
+
if (t.Text) out += ` · ${t.Text}\n`;
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
} catch {}
|
|
867
|
+
|
|
868
|
+
if (html.trim()) out += "\nSearch snippets:\n" + html.trim().split("\n").map(l => ` · ${l.trim()}`).join("\n");
|
|
869
|
+
|
|
870
|
+
return { result: out.trim() || "No results found. Try a different query." };
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
case "get_weather": {
|
|
874
|
+
const loc = encodeURIComponent(args.location);
|
|
875
|
+
if (args.format === "full") {
|
|
876
|
+
const { stdout } = await execAsync(
|
|
877
|
+
`curl -sL --max-time 10 "https://wttr.in/${loc}?format=j1"`
|
|
878
|
+
);
|
|
879
|
+
try {
|
|
880
|
+
const data = JSON.parse(stdout);
|
|
881
|
+
const current = data.current_condition[0];
|
|
882
|
+
const area = data.nearest_area[0];
|
|
883
|
+
const city = area.areaName[0].value;
|
|
884
|
+
const country = area.country[0].value;
|
|
885
|
+
return {
|
|
886
|
+
result: JSON.stringify({
|
|
887
|
+
location: `${city}, ${country}`,
|
|
888
|
+
temp_c: current.temp_C + "°C",
|
|
889
|
+
feels_like: current.FeelsLikeC + "°C",
|
|
890
|
+
humidity: current.humidity + "%",
|
|
891
|
+
wind_kmph: current.windspeedKmph + " km/h",
|
|
892
|
+
description: current.weatherDesc[0].value,
|
|
893
|
+
visibility: current.visibility + " km",
|
|
894
|
+
uv_index: current.uvIndex,
|
|
895
|
+
}, null, 2)
|
|
896
|
+
};
|
|
897
|
+
} catch {}
|
|
898
|
+
}
|
|
899
|
+
// Simple one-line format
|
|
900
|
+
const { stdout } = await execAsync(
|
|
901
|
+
`curl -sL --max-time 10 "https://wttr.in/${loc}?format=3"`
|
|
902
|
+
);
|
|
903
|
+
return { result: stdout.trim() || "Could not fetch weather." };
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
case "get_news": {
|
|
907
|
+
const q = encodeURIComponent(args.query);
|
|
908
|
+
const lang = args.language ?? "id";
|
|
909
|
+
const lim = Math.min(args.limit ?? 10, 20);
|
|
910
|
+
|
|
911
|
+
// Use HackerNews Algolia API for tech news, BBC/Reuters RSS for general
|
|
912
|
+
const results = [];
|
|
913
|
+
|
|
914
|
+
// Try GNews RSS (free, no key)
|
|
915
|
+
const { stdout: hn } = await execAsync(
|
|
916
|
+
`curl -sL --max-time 12 "https://hn.algolia.com/api/v1/search?query=${q}&tags=story&hitsPerPage=${lim}" 2>/dev/null`
|
|
917
|
+
).catch(() => ({ stdout: "" }));
|
|
918
|
+
|
|
919
|
+
try {
|
|
920
|
+
const data = JSON.parse(hn);
|
|
921
|
+
if (data.hits?.length) {
|
|
922
|
+
data.hits.forEach((h, i) => {
|
|
923
|
+
results.push(`${i+1}. ${h.title}\n 🔗 ${h.url || "https://news.ycombinator.com/item?id="+h.objectID}\n ⏱ ${h.created_at}`);
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
} catch {}
|
|
927
|
+
|
|
928
|
+
// Also try Google News RSS if lang is id
|
|
929
|
+
if (results.length < 5) {
|
|
930
|
+
const { stdout: rss } = await execAsync(
|
|
931
|
+
`curl -sL --max-time 12 "https://news.google.com/rss/search?q=${q}&hl=${lang}&gl=ID&ceid=ID:${lang}" | grep -oP '(?<=<title>)[^<]+' | grep -v "Google News" | head -${lim}`
|
|
932
|
+
).catch(() => ({ stdout: "" }));
|
|
933
|
+
if (rss.trim()) {
|
|
934
|
+
const headlines = rss.trim().split("\n").filter(Boolean);
|
|
935
|
+
headlines.forEach((h, i) => {
|
|
936
|
+
if (!results.find(r => r.includes(h)))
|
|
937
|
+
results.push(`${results.length+1}. ${h}`);
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return { result: results.slice(0, lim).join("\n\n") || "No news found." };
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
case "get_exchange_rate": {
|
|
946
|
+
const from = (args.from ?? "USD").toUpperCase();
|
|
947
|
+
const to = args.to ? args.to.toUpperCase() : "";
|
|
948
|
+
const url = to
|
|
949
|
+
? `https://api.frankfurter.app/latest?from=${from}&to=${to}`
|
|
950
|
+
: `https://api.frankfurter.app/latest?from=${from}`;
|
|
951
|
+
const { stdout } = await execAsync(`curl -sL --max-time 10 "${url}"`);
|
|
952
|
+
try {
|
|
953
|
+
const data = JSON.parse(stdout);
|
|
954
|
+
const rates = Object.entries(data.rates)
|
|
955
|
+
.map(([k, v]) => ` ${k}: ${v}`)
|
|
956
|
+
.join("\n");
|
|
957
|
+
return { result: `1 ${from} =\n${rates}\n\nDate: ${data.date}` };
|
|
958
|
+
} catch {
|
|
959
|
+
return { error: "Could not fetch exchange rates." };
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
case "get_stock": {
|
|
964
|
+
const sym = encodeURIComponent(args.symbol.toUpperCase());
|
|
965
|
+
const { stdout } = await execAsync(
|
|
966
|
+
`curl -sL --max-time 12 "https://query1.finance.yahoo.com/v8/finance/chart/${sym}?range=1d&interval=1d"`
|
|
967
|
+
);
|
|
968
|
+
try {
|
|
969
|
+
const data = JSON.parse(stdout);
|
|
970
|
+
const meta = data.chart.result[0].meta;
|
|
971
|
+
return {
|
|
972
|
+
result: JSON.stringify({
|
|
973
|
+
symbol: meta.symbol,
|
|
974
|
+
name: meta.longName ?? meta.shortName ?? meta.symbol,
|
|
975
|
+
price: meta.regularMarketPrice,
|
|
976
|
+
prev_close: meta.previousClose,
|
|
977
|
+
change: (meta.regularMarketPrice - meta.previousClose).toFixed(2),
|
|
978
|
+
change_pct: (((meta.regularMarketPrice - meta.previousClose) / meta.previousClose) * 100).toFixed(2) + "%",
|
|
979
|
+
currency: meta.currency,
|
|
980
|
+
exchange: meta.exchangeName,
|
|
981
|
+
market_state: meta.marketState,
|
|
982
|
+
volume: meta.regularMarketVolume,
|
|
983
|
+
}, null, 2)
|
|
984
|
+
};
|
|
985
|
+
} catch {
|
|
986
|
+
return { error: `Could not fetch stock data for ${args.symbol}.` };
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
case "get_crypto": {
|
|
991
|
+
const ids = encodeURIComponent(args.coins.toLowerCase().replace(/\s/g, ""));
|
|
992
|
+
const cur = (args.currency ?? "usd").toLowerCase();
|
|
993
|
+
const url = `https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=${cur}&include_24hr_change=true&include_market_cap=true&include_24hr_vol=true`;
|
|
994
|
+
const { stdout } = await execAsync(`curl -sL --max-time 12 "${url}"`);
|
|
995
|
+
try {
|
|
996
|
+
const data = JSON.parse(stdout);
|
|
997
|
+
const lines = Object.entries(data).map(([coin, info]) => {
|
|
998
|
+
const price = info[cur];
|
|
999
|
+
const change = info[`${cur}_24h_change`]?.toFixed(2);
|
|
1000
|
+
const mcap = info[`${cur}_market_cap`];
|
|
1001
|
+
return `${coin.toUpperCase()}: ${cur.toUpperCase()} ${price?.toLocaleString()} (24h: ${change}%) mcap: ${mcap?.toLocaleString()}`;
|
|
1002
|
+
});
|
|
1003
|
+
return { result: lines.join("\n") };
|
|
1004
|
+
} catch {
|
|
1005
|
+
return { error: "Could not fetch crypto prices." };
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
case "get_ip_info": {
|
|
1010
|
+
const target = args.ip ? `https://ipapi.co/${args.ip}/json/` : "https://ipapi.co/json/";
|
|
1011
|
+
const { stdout } = await execAsync(`curl -sL --max-time 10 "${target}"`);
|
|
1012
|
+
try {
|
|
1013
|
+
const d = JSON.parse(stdout);
|
|
1014
|
+
return {
|
|
1015
|
+
result: JSON.stringify({
|
|
1016
|
+
ip: d.ip,
|
|
1017
|
+
city: d.city,
|
|
1018
|
+
region: d.region,
|
|
1019
|
+
country: d.country_name,
|
|
1020
|
+
timezone: d.timezone,
|
|
1021
|
+
isp: d.org,
|
|
1022
|
+
lat: d.latitude,
|
|
1023
|
+
lon: d.longitude,
|
|
1024
|
+
}, null, 2)
|
|
1025
|
+
};
|
|
1026
|
+
} catch {
|
|
1027
|
+
return { error: "Could not fetch IP info." };
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
758
1031
|
default:
|
|
759
1032
|
return { error: `Unknown tool: ${name}` };
|
|
760
1033
|
}
|