@gilangranggaid/kilua-agent 1.0.0 → 1.0.2
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/bin/kilua.mjs +171 -27
- package/package.json +3 -3
package/bin/kilua.mjs
CHANGED
|
@@ -5,9 +5,10 @@ import path from "node:path";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { execSync } from "node:child_process";
|
|
7
7
|
|
|
8
|
-
// ponytail: single
|
|
8
|
+
// ponytail: single-file agent CLI supporting OpenAI, Gemini OpenAI endpoint & Anthropic/9router.
|
|
9
9
|
const KILUA_DIR = path.join(os.homedir(), ".kilua");
|
|
10
10
|
const SETTINGS_PATH = path.join(KILUA_DIR, "settings.json");
|
|
11
|
+
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
|
|
11
12
|
|
|
12
13
|
function ensureKiluaDir() {
|
|
13
14
|
if (!fs.existsSync(KILUA_DIR)) fs.mkdirSync(KILUA_DIR, { recursive: true });
|
|
@@ -23,7 +24,23 @@ function saveSettings(settings) {
|
|
|
23
24
|
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf8");
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
function readClaudeSettings() {
|
|
28
|
+
try {
|
|
29
|
+
const raw = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf8"));
|
|
30
|
+
const env = raw.env || {};
|
|
31
|
+
if (env.ANTHROPIC_BASE_URL || env.ANTHROPIC_AUTH_TOKEN) {
|
|
32
|
+
return {
|
|
33
|
+
base_url: env.ANTHROPIC_BASE_URL || "http://127.0.0.1:20128/v1",
|
|
34
|
+
api_key: env.ANTHROPIC_AUTH_TOKEN || "",
|
|
35
|
+
model: env.ANTHROPIC_DEFAULT_OPUS_MODEL || env.ANTHROPIC_DEFAULT_SONNET_MODEL || "harobor",
|
|
36
|
+
provider: "anthropic"
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
} catch {}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const TOOLS_OPENAI = [
|
|
27
44
|
{
|
|
28
45
|
type: "function",
|
|
29
46
|
function: {
|
|
@@ -66,6 +83,12 @@ const TOOLS = [
|
|
|
66
83
|
}
|
|
67
84
|
];
|
|
68
85
|
|
|
86
|
+
const TOOLS_ANTHROPIC = TOOLS_OPENAI.map(t => ({
|
|
87
|
+
name: t.function.name,
|
|
88
|
+
description: t.function.description,
|
|
89
|
+
input_schema: t.function.parameters
|
|
90
|
+
}));
|
|
91
|
+
|
|
69
92
|
function executeLocalTool(name, args) {
|
|
70
93
|
const cwd = process.cwd();
|
|
71
94
|
try {
|
|
@@ -107,40 +130,151 @@ function promptQuestion(rl, query) {
|
|
|
107
130
|
|
|
108
131
|
async function runInteractiveConfig() {
|
|
109
132
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
133
|
+
const claudeCfg = readClaudeSettings();
|
|
134
|
+
|
|
110
135
|
console.log("\x1b[35m=== KILUA CONFIG ===\x1b[0m\nPath: " + SETTINGS_PATH);
|
|
111
|
-
console.log("1) TokenHarbor / 9Router /
|
|
112
|
-
|
|
136
|
+
console.log("1) TokenHarbor / 9Router / OpenRouter (OpenAI-compatible)");
|
|
137
|
+
console.log("2) Google Gemini (AI Studio Key)");
|
|
138
|
+
console.log("3) 9Router Local / Claude Code Proxy (Port 20128)\n");
|
|
139
|
+
|
|
140
|
+
const choice = (await promptQuestion(rl, "Pilihan [1/2/3] (default 1): ")).trim() || "1";
|
|
113
141
|
let settings = {};
|
|
114
142
|
|
|
115
143
|
if (choice === "2") {
|
|
116
|
-
const key = (await promptQuestion(rl, "GEMINI_API_KEY: ")).trim();
|
|
117
|
-
const model = (await promptQuestion(rl, "Model (gemini-2.5-flash): ")).trim() || "gemini-2.5-flash";
|
|
118
|
-
settings = {
|
|
144
|
+
const key = (await promptQuestion(rl, "Masukkan GEMINI_API_KEY: ")).trim();
|
|
145
|
+
const model = (await promptQuestion(rl, "Nama Model (gemini-2.5-flash): ")).trim() || "gemini-2.5-flash";
|
|
146
|
+
settings = {
|
|
147
|
+
provider: "openai",
|
|
148
|
+
base_url: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
149
|
+
api_key: key,
|
|
150
|
+
model
|
|
151
|
+
};
|
|
152
|
+
} else if (choice === "3") {
|
|
153
|
+
const defaultUrl = claudeCfg?.base_url || "http://127.0.0.1:20128/v1";
|
|
154
|
+
const defaultKey = claudeCfg?.api_key || "";
|
|
155
|
+
const defaultModel = claudeCfg?.model || "harobor";
|
|
156
|
+
|
|
157
|
+
const baseUrl = (await promptQuestion(rl, `Base URL (${defaultUrl}): `)).trim() || defaultUrl;
|
|
158
|
+
const key = (await promptQuestion(rl, `API Key/Token (${defaultKey ? defaultKey.slice(0, 10) + "..." : "masukkan token"}): `)).trim() || defaultKey;
|
|
159
|
+
const model = (await promptQuestion(rl, `Model (${defaultModel}): `)).trim() || defaultModel;
|
|
160
|
+
|
|
161
|
+
settings = {
|
|
162
|
+
provider: "anthropic",
|
|
163
|
+
base_url: baseUrl.replace(/\/+$/, ""),
|
|
164
|
+
api_key: key,
|
|
165
|
+
model
|
|
166
|
+
};
|
|
119
167
|
} else {
|
|
168
|
+
console.log("Contoh Base URL: https://tokenharbor.ai/v1 atau https://api.9router.com/v1");
|
|
120
169
|
const baseUrl = (await promptQuestion(rl, "Base URL (https://tokenharbor.ai/v1): ")).trim() || "https://tokenharbor.ai/v1";
|
|
121
170
|
const key = (await promptQuestion(rl, "API Key: ")).trim();
|
|
122
171
|
const model = (await promptQuestion(rl, "Model (deepseek-v4.1-flash:free): ")).trim() || "deepseek-v4.1-flash:free";
|
|
123
|
-
settings = {
|
|
172
|
+
settings = {
|
|
173
|
+
provider: "openai",
|
|
174
|
+
api_key: key,
|
|
175
|
+
base_url: baseUrl.replace(/\/+$/, ""),
|
|
176
|
+
model
|
|
177
|
+
};
|
|
124
178
|
}
|
|
125
179
|
|
|
126
180
|
saveSettings(settings);
|
|
127
181
|
rl.close();
|
|
128
|
-
console.log("\x1b[32m✓
|
|
182
|
+
console.log("\x1b[32m✓ Pengaturan disimpan di " + SETTINGS_PATH + "\x1b[0m\n");
|
|
129
183
|
return settings;
|
|
130
184
|
}
|
|
131
185
|
|
|
132
|
-
|
|
186
|
+
// Loop untuk Anthropic / 9router local (/v1/messages)
|
|
187
|
+
async function runAnthropicLoop(settings, convo) {
|
|
133
188
|
const SYSTEM = `Kamu KILUA, AI Coding Agent di terminal lokal. Folder: ${process.cwd()}. Gunakan tools (read_file, write_file, edit_file, list_dir, run_cmd) untuk inspeksi dan eksekusi tugas coding mandiri. Respon ringkas to the point.`;
|
|
134
|
-
|
|
189
|
+
|
|
190
|
+
let endpoint = settings.base_url.replace(/\/+$/, "");
|
|
191
|
+
if (!endpoint.endsWith("/messages")) endpoint += "/messages";
|
|
135
192
|
|
|
136
193
|
for (let step = 0; step < 12; step++) {
|
|
137
194
|
process.stdout.write("\r\x1b[2K\x1b[35m[KILUA Berpikir...]\x1b[0m ");
|
|
138
195
|
let res;
|
|
139
196
|
try {
|
|
140
|
-
res = await fetch(
|
|
197
|
+
res = await fetch(endpoint, {
|
|
141
198
|
method: "POST",
|
|
142
|
-
headers: {
|
|
143
|
-
|
|
199
|
+
headers: {
|
|
200
|
+
"Content-Type": "application/json",
|
|
201
|
+
"x-api-key": settings.api_key,
|
|
202
|
+
Authorization: `Bearer ${settings.api_key}`,
|
|
203
|
+
"anthropic-version": "2023-06-01"
|
|
204
|
+
},
|
|
205
|
+
body: JSON.stringify({
|
|
206
|
+
model: settings.model,
|
|
207
|
+
max_tokens: 4096,
|
|
208
|
+
system: SYSTEM,
|
|
209
|
+
messages: convo,
|
|
210
|
+
tools: TOOLS_ANTHROPIC
|
|
211
|
+
})
|
|
212
|
+
});
|
|
213
|
+
} catch (e) {
|
|
214
|
+
console.error("\n[!] Connection error:", e.message);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!res.ok) {
|
|
219
|
+
const err = await res.json().catch(() => ({}));
|
|
220
|
+
console.error(`\n[!] Error (${res.status}):`, err.error?.message || err.message || res.statusText);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const data = await res.json();
|
|
225
|
+
const blocks = data.content || [];
|
|
226
|
+
const textBlock = blocks.find(b => b.type === "text");
|
|
227
|
+
if (textBlock?.text) {
|
|
228
|
+
process.stdout.write(`\r\x1b[2KKILUA > ${textBlock.text}\n`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
convo.push({ role: "assistant", content: blocks });
|
|
232
|
+
|
|
233
|
+
const toolUseBlocks = blocks.filter(b => b.type === "tool_use");
|
|
234
|
+
if (!toolUseBlocks.length) break;
|
|
235
|
+
|
|
236
|
+
const toolResultContent = [];
|
|
237
|
+
for (const tu of toolUseBlocks) {
|
|
238
|
+
const name = tu.name;
|
|
239
|
+
const args = tu.input || {};
|
|
240
|
+
console.log(`\x1b[36m⚡ Tool: ${name}\x1b[0m(${JSON.stringify(args)})`);
|
|
241
|
+
const output = executeLocalTool(name, args);
|
|
242
|
+
console.log(`\x1b[90m↳ ${output.slice(0, 140).replace(/\n/g, " ")}${output.length > 140 ? "..." : ""}\x1b[0m`);
|
|
243
|
+
toolResultContent.push({
|
|
244
|
+
type: "tool_result",
|
|
245
|
+
tool_use_id: tu.id,
|
|
246
|
+
content: output
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
convo.push({ role: "user", content: toolResultContent });
|
|
250
|
+
}
|
|
251
|
+
console.log();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Loop untuk OpenAI / TokenHarbor / Gemini OpenAI protocol
|
|
255
|
+
async function runOpenAiLoop(settings, convo) {
|
|
256
|
+
const SYSTEM = `Kamu KILUA, AI Coding Agent di terminal lokal. Folder: ${process.cwd()}. Gunakan tools (read_file, write_file, edit_file, list_dir, run_cmd) untuk inspeksi dan eksekusi tugas coding mandiri. Respon ringkas to the point.`;
|
|
257
|
+
|
|
258
|
+
let endpoint = settings.base_url.replace(/\/+$/, "");
|
|
259
|
+
if (endpoint.includes("generativelanguage.googleapis.com") && !endpoint.includes("/openai")) {
|
|
260
|
+
endpoint += "/openai";
|
|
261
|
+
}
|
|
262
|
+
if (!endpoint.endsWith("/chat/completions")) {
|
|
263
|
+
endpoint += "/chat/completions";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
for (let step = 0; step < 12; step++) {
|
|
267
|
+
process.stdout.write("\r\x1b[2K\x1b[35m[KILUA Berpikir...]\x1b[0m ");
|
|
268
|
+
let res;
|
|
269
|
+
try {
|
|
270
|
+
res = await fetch(endpoint, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: {
|
|
273
|
+
"Content-Type": "application/json",
|
|
274
|
+
Authorization: `Bearer ${settings.api_key}`,
|
|
275
|
+
"x-goog-api-key": settings.api_key
|
|
276
|
+
},
|
|
277
|
+
body: JSON.stringify({ model: settings.model, messages: [{ role: "system", content: SYSTEM }, ...convo], tools: TOOLS_OPENAI, tool_choice: "auto" })
|
|
144
278
|
});
|
|
145
279
|
} catch (e) {
|
|
146
280
|
console.error("\n[!] Connection error:", e.message);
|
|
@@ -176,6 +310,17 @@ async function runAgent(settings, userPrompt, convo) {
|
|
|
176
310
|
console.log();
|
|
177
311
|
}
|
|
178
312
|
|
|
313
|
+
async function runAgent(settings, userPrompt, convo) {
|
|
314
|
+
const isAnthropic = settings.provider === "anthropic" || settings.base_url.includes(":20128") || settings.model === "harobor";
|
|
315
|
+
if (isAnthropic) {
|
|
316
|
+
convo.push({ role: "user", content: userPrompt });
|
|
317
|
+
await runAnthropicLoop(settings, convo);
|
|
318
|
+
} else {
|
|
319
|
+
convo.push({ role: "user", content: userPrompt });
|
|
320
|
+
await runOpenAiLoop(settings, convo);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
179
324
|
async function main() {
|
|
180
325
|
const args = process.argv.slice(2);
|
|
181
326
|
const oneShot = args.join(" ").trim();
|
|
@@ -186,19 +331,17 @@ async function main() {
|
|
|
186
331
|
}
|
|
187
332
|
|
|
188
333
|
let settings = loadSettings();
|
|
189
|
-
if (process.env.TOKEN_HARBOR_API_KEY) {
|
|
190
|
-
settings.api_key = process.env.TOKEN_HARBOR_API_KEY;
|
|
191
|
-
settings.base_url = process.env.TOKEN_HARBOR_BASE_URL || "https://tokenharbor.ai/v1";
|
|
192
|
-
settings.model = process.env.TOKEN_HARBOR_MODEL || "deepseek-v4.1-flash:free";
|
|
193
|
-
} else if (process.env.KILUA_API_KEY) {
|
|
194
|
-
settings.api_key = process.env.KILUA_API_KEY;
|
|
195
|
-
settings.base_url = process.env.KILUA_BASE_URL || settings.base_url || "https://tokenharbor.ai/v1";
|
|
196
|
-
settings.model = process.env.KILUA_MODEL || settings.model || "deepseek-v4.1-flash:free";
|
|
197
|
-
}
|
|
198
334
|
|
|
335
|
+
// Auto-import dari Claude Code jika settings kilua belum ada
|
|
199
336
|
if (!settings.api_key) {
|
|
200
|
-
|
|
201
|
-
|
|
337
|
+
const claudeCfg = readClaudeSettings();
|
|
338
|
+
if (claudeCfg) {
|
|
339
|
+
settings = claudeCfg;
|
|
340
|
+
saveSettings(settings);
|
|
341
|
+
} else {
|
|
342
|
+
console.log("\x1b[33m[!] Belum ada konfigurasi AI key.\x1b[0m");
|
|
343
|
+
settings = await runInteractiveConfig();
|
|
344
|
+
}
|
|
202
345
|
}
|
|
203
346
|
|
|
204
347
|
const convo = [];
|
|
@@ -210,9 +353,9 @@ async function main() {
|
|
|
210
353
|
console.log("\x1b[1m\x1b[35m=== KILUA AI CLI AGENT ===\x1b[0m");
|
|
211
354
|
console.log(`CWD : \x1b[33m${process.cwd()}\x1b[0m`);
|
|
212
355
|
console.log(`Provider: \x1b[32m${settings.base_url}\x1b[0m (${settings.model})`);
|
|
213
|
-
console.log(`Config : \x1b[90m${SETTINGS_PATH}\x1b[0m (ketik 'config' untuk ganti
|
|
356
|
+
console.log(`Config : \x1b[90m${SETTINGS_PATH}\x1b[0m (ketik 'config' untuk ganti)\n`);
|
|
214
357
|
|
|
215
|
-
|
|
358
|
+
let rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
216
359
|
const ask = () => {
|
|
217
360
|
rl.question("\x1b[1mKAMU > \x1b[0m", async input => {
|
|
218
361
|
const q = input.trim();
|
|
@@ -222,6 +365,7 @@ async function main() {
|
|
|
222
365
|
rl.close();
|
|
223
366
|
await runInteractiveConfig();
|
|
224
367
|
settings = loadSettings();
|
|
368
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
225
369
|
return ask();
|
|
226
370
|
}
|
|
227
371
|
await runAgent(settings, q, convo);
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gilangranggaid/kilua-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Autonomous AI Coding Agent for your terminal and VS Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/kilua.mjs",
|
|
7
7
|
"bin": {
|
|
8
|
-
"kilua": "bin/kilua.mjs",
|
|
9
|
-
"kilua-agent": "bin/kilua.mjs"
|
|
8
|
+
"kilua": "./bin/kilua.mjs",
|
|
9
|
+
"kilua-agent": "./bin/kilua.mjs"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"bin",
|