@gilangranggaid/kilua-agent 1.0.0 → 1.0.1
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 +144 -25
- 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 both OpenAI & Anthropic (9router) protocols.
|
|
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,20 +130,42 @@ 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)
|
|
112
|
-
|
|
136
|
+
console.log("1) 9Router / Claude Code Proxy (Port 20128)");
|
|
137
|
+
console.log("2) TokenHarbor / OpenAI Compatible");
|
|
138
|
+
console.log("3) Custom Provider\n");
|
|
139
|
+
|
|
140
|
+
const choice = (await promptQuestion(rl, "Pilihan [1/2/3] (1): ")).trim() || "1";
|
|
113
141
|
let settings = {};
|
|
114
142
|
|
|
115
|
-
if (choice === "
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
143
|
+
if (choice === "1") {
|
|
144
|
+
const defaultUrl = claudeCfg?.base_url || "http://127.0.0.1:20128/v1";
|
|
145
|
+
const defaultKey = claudeCfg?.api_key || "";
|
|
146
|
+
const defaultModel = claudeCfg?.model || "harobor";
|
|
147
|
+
|
|
148
|
+
const baseUrl = (await promptQuestion(rl, `Base URL (${defaultUrl}): `)).trim() || defaultUrl;
|
|
149
|
+
const key = (await promptQuestion(rl, `API Key/Token (${defaultKey ? defaultKey.slice(0, 10) + "..." : "kosongkan jika sama"}): `)).trim() || defaultKey;
|
|
150
|
+
const model = (await promptQuestion(rl, `Model (${defaultModel}): `)).trim() || defaultModel;
|
|
151
|
+
|
|
152
|
+
settings = {
|
|
153
|
+
provider: "anthropic",
|
|
154
|
+
base_url: baseUrl.replace(/\/+$/, ""),
|
|
155
|
+
api_key: key,
|
|
156
|
+
model
|
|
157
|
+
};
|
|
158
|
+
} else if (choice === "2") {
|
|
120
159
|
const baseUrl = (await promptQuestion(rl, "Base URL (https://tokenharbor.ai/v1): ")).trim() || "https://tokenharbor.ai/v1";
|
|
121
160
|
const key = (await promptQuestion(rl, "API Key: ")).trim();
|
|
122
161
|
const model = (await promptQuestion(rl, "Model (deepseek-v4.1-flash:free): ")).trim() || "deepseek-v4.1-flash:free";
|
|
123
162
|
settings = { provider: "openai", api_key: key, base_url: baseUrl.replace(/\/+$/, ""), model };
|
|
163
|
+
} else {
|
|
164
|
+
const provider = (await promptQuestion(rl, "Tipe API [openai/anthropic] (openai): ")).trim() || "openai";
|
|
165
|
+
const baseUrl = (await promptQuestion(rl, "Base URL: ")).trim();
|
|
166
|
+
const key = (await promptQuestion(rl, "API Key: ")).trim();
|
|
167
|
+
const model = (await promptQuestion(rl, "Model: ")).trim();
|
|
168
|
+
settings = { provider, api_key: key, base_url: baseUrl.replace(/\/+$/, ""), model };
|
|
124
169
|
}
|
|
125
170
|
|
|
126
171
|
saveSettings(settings);
|
|
@@ -129,9 +174,74 @@ async function runInteractiveConfig() {
|
|
|
129
174
|
return settings;
|
|
130
175
|
}
|
|
131
176
|
|
|
132
|
-
|
|
177
|
+
// Loop untuk Anthropic / 9router protocol (/v1/messages)
|
|
178
|
+
async function runAnthropicLoop(settings, convo) {
|
|
179
|
+
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.`;
|
|
180
|
+
|
|
181
|
+
for (let step = 0; step < 12; step++) {
|
|
182
|
+
process.stdout.write("\r\x1b[2K\x1b[35m[KILUA Berpikir...]\x1b[0m ");
|
|
183
|
+
let res;
|
|
184
|
+
try {
|
|
185
|
+
res = await fetch(`${settings.base_url}/messages`, {
|
|
186
|
+
method: "POST",
|
|
187
|
+
headers: {
|
|
188
|
+
"Content-Type": "application/json",
|
|
189
|
+
"x-api-key": settings.api_key,
|
|
190
|
+
Authorization: `Bearer ${settings.api_key}`,
|
|
191
|
+
"anthropic-version": "2023-06-01"
|
|
192
|
+
},
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
model: settings.model,
|
|
195
|
+
max_tokens: 4096,
|
|
196
|
+
system: SYSTEM,
|
|
197
|
+
messages: convo,
|
|
198
|
+
tools: TOOLS_ANTHROPIC
|
|
199
|
+
})
|
|
200
|
+
});
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.error("\n[!] Connection error:", e.message);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!res.ok) {
|
|
207
|
+
const err = await res.json().catch(() => ({}));
|
|
208
|
+
console.error(`\n[!] Error (${res.status}):`, err.error?.message || err.message || res.statusText);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const data = await res.json();
|
|
213
|
+
const blocks = data.content || [];
|
|
214
|
+
const textBlock = blocks.find(b => b.type === "text");
|
|
215
|
+
if (textBlock?.text) {
|
|
216
|
+
process.stdout.write(`\r\x1b[2KKILUA > ${textBlock.text}\n`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
convo.push({ role: "assistant", content: blocks });
|
|
220
|
+
|
|
221
|
+
const toolUseBlocks = blocks.filter(b => b.type === "tool_use");
|
|
222
|
+
if (!toolUseBlocks.length) break;
|
|
223
|
+
|
|
224
|
+
const toolResultContent = [];
|
|
225
|
+
for (const tu of toolUseBlocks) {
|
|
226
|
+
const name = tu.name;
|
|
227
|
+
const args = tu.input || {};
|
|
228
|
+
console.log(`\x1b[36m⚡ Tool: ${name}\x1b[0m(${JSON.stringify(args)})`);
|
|
229
|
+
const output = executeLocalTool(name, args);
|
|
230
|
+
console.log(`\x1b[90m↳ ${output.slice(0, 140).replace(/\n/g, " ")}${output.length > 140 ? "..." : ""}\x1b[0m`);
|
|
231
|
+
toolResultContent.push({
|
|
232
|
+
type: "tool_result",
|
|
233
|
+
tool_use_id: tu.id,
|
|
234
|
+
content: output
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
convo.push({ role: "user", content: toolResultContent });
|
|
238
|
+
}
|
|
239
|
+
console.log();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Loop untuk OpenAI / TokenHarbor protocol (/v1/chat/completions)
|
|
243
|
+
async function runOpenAiLoop(settings, convo) {
|
|
133
244
|
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
|
-
convo.push({ role: "user", content: userPrompt });
|
|
135
245
|
|
|
136
246
|
for (let step = 0; step < 12; step++) {
|
|
137
247
|
process.stdout.write("\r\x1b[2K\x1b[35m[KILUA Berpikir...]\x1b[0m ");
|
|
@@ -140,7 +250,7 @@ async function runAgent(settings, userPrompt, convo) {
|
|
|
140
250
|
res = await fetch(`${settings.base_url}/chat/completions`, {
|
|
141
251
|
method: "POST",
|
|
142
252
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${settings.api_key}` },
|
|
143
|
-
body: JSON.stringify({ model: settings.model, messages: [{ role: "system", content: SYSTEM }, ...convo], tools:
|
|
253
|
+
body: JSON.stringify({ model: settings.model, messages: [{ role: "system", content: SYSTEM }, ...convo], tools: TOOLS_OPENAI, tool_choice: "auto" })
|
|
144
254
|
});
|
|
145
255
|
} catch (e) {
|
|
146
256
|
console.error("\n[!] Connection error:", e.message);
|
|
@@ -176,6 +286,17 @@ async function runAgent(settings, userPrompt, convo) {
|
|
|
176
286
|
console.log();
|
|
177
287
|
}
|
|
178
288
|
|
|
289
|
+
async function runAgent(settings, userPrompt, convo) {
|
|
290
|
+
const isAnthropic = settings.provider === "anthropic" || settings.base_url.includes(":20128") || settings.model === "harobor";
|
|
291
|
+
if (isAnthropic) {
|
|
292
|
+
convo.push({ role: "user", content: userPrompt });
|
|
293
|
+
await runAnthropicLoop(settings, convo);
|
|
294
|
+
} else {
|
|
295
|
+
convo.push({ role: "user", content: userPrompt });
|
|
296
|
+
await runOpenAiLoop(settings, convo);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
179
300
|
async function main() {
|
|
180
301
|
const args = process.argv.slice(2);
|
|
181
302
|
const oneShot = args.join(" ").trim();
|
|
@@ -186,19 +307,17 @@ async function main() {
|
|
|
186
307
|
}
|
|
187
308
|
|
|
188
309
|
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
310
|
|
|
311
|
+
// Auto-import dari Claude Code jika settings kilua belum ada
|
|
199
312
|
if (!settings.api_key) {
|
|
200
|
-
|
|
201
|
-
|
|
313
|
+
const claudeCfg = readClaudeSettings();
|
|
314
|
+
if (claudeCfg) {
|
|
315
|
+
settings = claudeCfg;
|
|
316
|
+
saveSettings(settings);
|
|
317
|
+
} else {
|
|
318
|
+
console.log("\x1b[33m[!] Belum ada konfigurasi AI key.\x1b[0m");
|
|
319
|
+
settings = await runInteractiveConfig();
|
|
320
|
+
}
|
|
202
321
|
}
|
|
203
322
|
|
|
204
323
|
const convo = [];
|
|
@@ -209,8 +328,8 @@ async function main() {
|
|
|
209
328
|
|
|
210
329
|
console.log("\x1b[1m\x1b[35m=== KILUA AI CLI AGENT ===\x1b[0m");
|
|
211
330
|
console.log(`CWD : \x1b[33m${process.cwd()}\x1b[0m`);
|
|
212
|
-
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
|
|
331
|
+
console.log(`Provider: \x1b[32m${settings.base_url}\x1b[0m (${settings.model}) [${settings.provider || "openai"}]`);
|
|
332
|
+
console.log(`Config : \x1b[90m${SETTINGS_PATH}\x1b[0m (ketik 'config' untuk ganti)\n`);
|
|
214
333
|
|
|
215
334
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
216
335
|
const ask = () => {
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gilangranggaid/kilua-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
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",
|