@thejeetsingh/kalcode 2.0.0 → 2.2.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/README.md +0 -4
- package/dist/bin/kalcode.d.ts +2 -0
- package/dist/bin/kalcode.js +12 -0
- package/dist/bin/kalcode.js.map +1 -0
- package/dist/src/agent/context.d.ts +6 -0
- package/dist/src/agent/context.js +60 -0
- package/dist/src/agent/context.js.map +1 -0
- package/dist/src/agent/history.d.ts +8 -0
- package/dist/src/agent/history.js +59 -0
- package/dist/src/agent/history.js.map +1 -0
- package/dist/src/agent/loop.d.ts +6 -0
- package/dist/src/agent/loop.js +235 -0
- package/dist/src/agent/loop.js.map +1 -0
- package/dist/src/agent/memory.d.ts +2 -0
- package/dist/src/agent/memory.js +27 -0
- package/dist/src/agent/memory.js.map +1 -0
- package/dist/src/agent/permissions.d.ts +5 -0
- package/dist/src/agent/permissions.js +66 -0
- package/dist/src/agent/permissions.js.map +1 -0
- package/dist/src/agent/text-tool-parser.d.ts +2 -0
- package/dist/src/agent/text-tool-parser.js +68 -0
- package/dist/src/agent/text-tool-parser.js.map +1 -0
- package/dist/src/api/client.d.ts +2 -0
- package/dist/src/api/client.js +86 -0
- package/dist/src/api/client.js.map +1 -0
- package/dist/src/api/stream-parser.d.ts +2 -0
- package/dist/src/api/stream-parser.js +97 -0
- package/dist/src/api/stream-parser.js.map +1 -0
- package/dist/src/config.d.ts +7 -0
- package/dist/src/config.js +52 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/constants.d.ts +25 -0
- package/{src/constants.ts → dist/src/constants.js} +17 -19
- package/dist/src/constants.js.map +1 -0
- package/dist/src/git/git.d.ts +15 -0
- package/dist/src/git/git.js +73 -0
- package/dist/src/git/git.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +415 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/proxy/server.d.ts +1 -0
- package/dist/src/proxy/server.js +92 -0
- package/dist/src/proxy/server.js.map +1 -0
- package/dist/src/tools/edit-file.d.ts +2 -0
- package/dist/src/tools/edit-file.js +88 -0
- package/dist/src/tools/edit-file.js.map +1 -0
- package/dist/src/tools/glob-tool.d.ts +2 -0
- package/dist/src/tools/glob-tool.js +52 -0
- package/dist/src/tools/glob-tool.js.map +1 -0
- package/dist/src/tools/grep.d.ts +2 -0
- package/dist/src/tools/grep.js +93 -0
- package/dist/src/tools/grep.js.map +1 -0
- package/dist/src/tools/list-directory.d.ts +2 -0
- package/dist/src/tools/list-directory.js +90 -0
- package/dist/src/tools/list-directory.js.map +1 -0
- package/dist/src/tools/read-file.d.ts +2 -0
- package/dist/src/tools/read-file.js +64 -0
- package/dist/src/tools/read-file.js.map +1 -0
- package/dist/src/tools/registry.d.ts +4 -0
- package/dist/src/tools/registry.js +32 -0
- package/dist/src/tools/registry.js.map +1 -0
- package/dist/src/tools/run-command.d.ts +2 -0
- package/dist/src/tools/run-command.js +98 -0
- package/dist/src/tools/run-command.js.map +1 -0
- package/dist/src/tools/write-file.d.ts +2 -0
- package/dist/src/tools/write-file.js +39 -0
- package/dist/src/tools/write-file.js.map +1 -0
- package/dist/src/types.d.ts +61 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/ui/input.d.ts +5 -0
- package/dist/src/ui/input.js +52 -0
- package/dist/src/ui/input.js.map +1 -0
- package/dist/src/ui/model-picker.d.ts +7 -0
- package/dist/src/ui/model-picker.js +70 -0
- package/dist/src/ui/model-picker.js.map +1 -0
- package/dist/src/ui/skills-picker.d.ts +2 -0
- package/dist/src/ui/skills-picker.js +95 -0
- package/dist/src/ui/skills-picker.js.map +1 -0
- package/dist/src/ui/skills.d.ts +14 -0
- package/dist/src/ui/skills.js +137 -0
- package/dist/src/ui/skills.js.map +1 -0
- package/dist/src/ui/spinner.d.ts +5 -0
- package/dist/src/ui/spinner.js +49 -0
- package/dist/src/ui/spinner.js.map +1 -0
- package/dist/src/ui/stream-renderer.d.ts +2 -0
- package/dist/src/ui/stream-renderer.js +66 -0
- package/dist/src/ui/stream-renderer.js.map +1 -0
- package/dist/src/ui/terminal.d.ts +24 -0
- package/dist/src/ui/terminal.js +272 -0
- package/dist/src/ui/terminal.js.map +1 -0
- package/package.json +16 -16
- package/api/health.ts +0 -10
- package/api/v1/chat/completions.ts +0 -59
- package/bin/kalcode.ts +0 -14
- package/src/agent/context.ts +0 -62
- package/src/agent/history.ts +0 -70
- package/src/agent/loop.ts +0 -282
- package/src/agent/memory.ts +0 -26
- package/src/agent/permissions.ts +0 -84
- package/src/agent/text-tool-parser.ts +0 -71
- package/src/api/client.ts +0 -110
- package/src/api/stream-parser.ts +0 -109
- package/src/config.ts +0 -61
- package/src/git/git.ts +0 -86
- package/src/index.ts +0 -403
- package/src/proxy/server.ts +0 -128
- package/src/tools/edit-file.ts +0 -97
- package/src/tools/glob-tool.ts +0 -59
- package/src/tools/grep.ts +0 -96
- package/src/tools/list-directory.ts +0 -101
- package/src/tools/read-file.ts +0 -71
- package/src/tools/registry.ts +0 -41
- package/src/tools/run-command.ts +0 -99
- package/src/tools/write-file.ts +0 -42
- package/src/types.ts +0 -68
- package/src/ui/input.ts +0 -60
- package/src/ui/model-picker.ts +0 -92
- package/src/ui/skills-picker.ts +0 -113
- package/src/ui/skills.ts +0 -152
- package/src/ui/spinner.ts +0 -56
- package/src/ui/stream-renderer.ts +0 -69
- package/src/ui/terminal.ts +0 -337
- package/tsconfig.json +0 -15
- package/vercel.json +0 -12
package/src/config.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import type { Config } from "./types.js";
|
|
5
|
-
import { DEFAULT_MODEL, AVAILABLE_MODELS } from "./constants.js";
|
|
6
|
-
|
|
7
|
-
const CONFIG_DIR = join(homedir(), ".kalcode");
|
|
8
|
-
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
9
|
-
|
|
10
|
-
const KNOWN_MODEL_IDS = new Set(AVAILABLE_MODELS.map(m => m.id));
|
|
11
|
-
|
|
12
|
-
export function loadConfig(): Config {
|
|
13
|
-
if (!existsSync(CONFIG_FILE)) {
|
|
14
|
-
return { apiKey: process.env.NVIDIA_NIM_KEY || "", model: DEFAULT_MODEL };
|
|
15
|
-
}
|
|
16
|
-
try {
|
|
17
|
-
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
18
|
-
const parsed = JSON.parse(raw);
|
|
19
|
-
let model = parsed.model || DEFAULT_MODEL;
|
|
20
|
-
|
|
21
|
-
// Migrate old OpenRouter model IDs
|
|
22
|
-
if (model.includes(":free") || model.includes("openrouter/")) {
|
|
23
|
-
model = DEFAULT_MODEL;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Ignore old OpenRouter keys
|
|
27
|
-
const apiKey = (parsed.apiKey && !parsed.apiKey.startsWith("sk-or-"))
|
|
28
|
-
? parsed.apiKey
|
|
29
|
-
: (process.env.NVIDIA_NIM_KEY || "");
|
|
30
|
-
return { apiKey, model };
|
|
31
|
-
} catch {
|
|
32
|
-
return { apiKey: "", model: DEFAULT_MODEL };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function saveConfig(config: Config): void {
|
|
37
|
-
if (!existsSync(CONFIG_DIR)) {
|
|
38
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function getApiKey(): string {
|
|
44
|
-
return loadConfig().apiKey;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function setApiKey(key: string): void {
|
|
48
|
-
const config = loadConfig();
|
|
49
|
-
config.apiKey = key;
|
|
50
|
-
saveConfig(config);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function getModel(): string {
|
|
54
|
-
return loadConfig().model;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function setModel(model: string): void {
|
|
58
|
-
const config = loadConfig();
|
|
59
|
-
config.model = model;
|
|
60
|
-
saveConfig(config);
|
|
61
|
-
}
|
package/src/git/git.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
|
|
4
|
-
export function isGitRepo(cwd: string = process.cwd()): boolean {
|
|
5
|
-
return existsSync(join(cwd, ".git"));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
async function run(args: string[], cwd?: string): Promise<{ ok: boolean; stdout: string; stderr: string }> {
|
|
9
|
-
try {
|
|
10
|
-
const proc = Bun.spawn(["git", ...args], {
|
|
11
|
-
stdout: "pipe",
|
|
12
|
-
stderr: "pipe",
|
|
13
|
-
cwd: cwd || process.cwd(),
|
|
14
|
-
});
|
|
15
|
-
const [stdoutBuf, stderrBuf] = await Promise.all([
|
|
16
|
-
new Response(proc.stdout).arrayBuffer(),
|
|
17
|
-
new Response(proc.stderr).arrayBuffer(),
|
|
18
|
-
]);
|
|
19
|
-
const exitCode = await proc.exited;
|
|
20
|
-
return {
|
|
21
|
-
ok: exitCode === 0,
|
|
22
|
-
stdout: new TextDecoder().decode(stdoutBuf).trim(),
|
|
23
|
-
stderr: new TextDecoder().decode(stderrBuf).trim(),
|
|
24
|
-
};
|
|
25
|
-
} catch (err) {
|
|
26
|
-
return { ok: false, stdout: "", stderr: String(err) };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function gitStatus(): Promise<string> {
|
|
31
|
-
const r = await run(["status", "--short"]);
|
|
32
|
-
return r.ok ? (r.stdout || "Clean working tree") : `git error: ${r.stderr}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export async function gitDiff(staged = false): Promise<string> {
|
|
36
|
-
const args = staged ? ["diff", "--staged"] : ["diff"];
|
|
37
|
-
const r = await run(args);
|
|
38
|
-
return r.ok ? (r.stdout || "No changes") : `git error: ${r.stderr}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function gitDiffSummary(): Promise<string> {
|
|
42
|
-
const r = await run(["diff", "--stat"]);
|
|
43
|
-
const rs = await run(["diff", "--staged", "--stat"]);
|
|
44
|
-
let out = "";
|
|
45
|
-
if (r.stdout) out += `Unstaged:\n${r.stdout}\n`;
|
|
46
|
-
if (rs.stdout) out += `Staged:\n${rs.stdout}`;
|
|
47
|
-
return out || "No changes";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function gitLog(count = 5): Promise<string> {
|
|
51
|
-
const r = await run(["log", `--oneline`, `-${count}`]);
|
|
52
|
-
return r.ok ? (r.stdout || "No commits yet") : `git error: ${r.stderr}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function gitCommit(message: string): Promise<{ ok: boolean; output: string }> {
|
|
56
|
-
// Stage all changes
|
|
57
|
-
await run(["add", "-A"]);
|
|
58
|
-
const r = await run(["commit", "-m", message]);
|
|
59
|
-
return { ok: r.ok, output: r.ok ? r.stdout : r.stderr };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function gitUndo(): Promise<{ ok: boolean; output: string }> {
|
|
63
|
-
// Check if there's a commit to undo
|
|
64
|
-
const log = await run(["log", "--oneline", "-1"]);
|
|
65
|
-
if (!log.ok || !log.stdout) {
|
|
66
|
-
return { ok: false, output: "No commits to undo" };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const r = await run(["reset", "--soft", "HEAD~1"]);
|
|
70
|
-
if (r.ok) {
|
|
71
|
-
// Also unstage
|
|
72
|
-
await run(["reset", "HEAD"]);
|
|
73
|
-
return { ok: true, output: `Undid: ${log.stdout}` };
|
|
74
|
-
}
|
|
75
|
-
return { ok: false, output: r.stderr };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function gitCurrentBranch(): Promise<string> {
|
|
79
|
-
const r = await run(["branch", "--show-current"]);
|
|
80
|
-
return r.ok ? r.stdout : "unknown";
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export async function hasPendingChanges(): Promise<boolean> {
|
|
84
|
-
const r = await run(["status", "--porcelain"]);
|
|
85
|
-
return r.ok && r.stdout.length > 0;
|
|
86
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
import { parseArgs } from "util";
|
|
2
|
-
import { writeFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import { VERSION, AVAILABLE_MODELS } from "./constants.js";
|
|
6
|
-
import { loadConfig, setApiKey, setModel, saveConfig } from "./config.js";
|
|
7
|
-
import { initHistory, clearHistory, getLastUserMessage, removeLastExchange } from "./agent/history.js";
|
|
8
|
-
import { runAgentLoop, setCompact, getCompact, setAskMode, getAskMode, interruptAgent } from "./agent/loop.js";
|
|
9
|
-
import { renderWelcome, renderHelp, renderError, renderSeparator, renderHints } from "./ui/terminal.js";
|
|
10
|
-
import { pickModel } from "./ui/model-picker.js";
|
|
11
|
-
import { createInput } from "./ui/input.js";
|
|
12
|
-
import { getSkills, searchSkills, renderSkillsList } from "./ui/skills.js";
|
|
13
|
-
import { setPermissionLevel, getPermissionLevel } from "./agent/permissions.js";
|
|
14
|
-
import { addFileToContext, dropFileFromContext, listContextFiles, clearContextFiles } from "./agent/context.js";
|
|
15
|
-
import { getMemoryFileName } from "./agent/memory.js";
|
|
16
|
-
import { isGitRepo, gitUndo, gitDiffSummary, gitLog, gitStatus, gitCommit } from "./git/git.js";
|
|
17
|
-
import * as readline from "readline";
|
|
18
|
-
|
|
19
|
-
const REPL_COMMANDS: { cmd: string; desc: string }[] = [
|
|
20
|
-
{ cmd: "/help", desc: "Show help" },
|
|
21
|
-
{ cmd: "/clear", desc: "Clear conversation + context" },
|
|
22
|
-
{ cmd: "/retry", desc: "Retry last message" },
|
|
23
|
-
{ cmd: "/model", desc: "Show/switch model" },
|
|
24
|
-
{ cmd: "/compact", desc: "Toggle compact output" },
|
|
25
|
-
{ cmd: "/ask", desc: "Toggle read-only mode" },
|
|
26
|
-
{ cmd: "/auto", desc: "Toggle auto-accept permissions" },
|
|
27
|
-
{ cmd: "/skills", desc: "List skills" },
|
|
28
|
-
{ cmd: "/add", desc: "Add file to context" },
|
|
29
|
-
{ cmd: "/drop", desc: "Remove file from context" },
|
|
30
|
-
{ cmd: "/files", desc: "List context files" },
|
|
31
|
-
{ cmd: "/diff", desc: "Show uncommitted changes" },
|
|
32
|
-
{ cmd: "/status", desc: "Git status" },
|
|
33
|
-
{ cmd: "/log", desc: "Recent commits" },
|
|
34
|
-
{ cmd: "/undo", desc: "Undo last commit" },
|
|
35
|
-
{ cmd: "/commit", desc: "Commit all changes" },
|
|
36
|
-
{ cmd: "/init", desc: "Create KALCODE.md" },
|
|
37
|
-
{ cmd: "/exit", desc: "Quit" },
|
|
38
|
-
{ cmd: "/quit", desc: "Quit" },
|
|
39
|
-
{ cmd: "/q", desc: "Quit" },
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
export async function main(): Promise<void> {
|
|
43
|
-
const { values, positionals } = parseArgs({
|
|
44
|
-
args: Bun.argv.slice(2),
|
|
45
|
-
options: {
|
|
46
|
-
help: { type: "boolean", short: "h" },
|
|
47
|
-
version: { type: "boolean", short: "v" },
|
|
48
|
-
model: { type: "string", short: "m" },
|
|
49
|
-
"set-key": { type: "boolean" },
|
|
50
|
-
"set-model": { type: "string" },
|
|
51
|
-
compact: { type: "boolean" },
|
|
52
|
-
"auto-accept": { type: "boolean" },
|
|
53
|
-
ask: { type: "boolean" },
|
|
54
|
-
},
|
|
55
|
-
allowPositionals: true,
|
|
56
|
-
strict: true,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
if (values.version) {
|
|
60
|
-
console.log(`kalcode v${VERSION}`);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (values.help) {
|
|
65
|
-
printHelp();
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (values["set-key"]) {
|
|
70
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
71
|
-
const key = await new Promise<string>((resolve) => {
|
|
72
|
-
rl.question(chalk.dim(" NVIDIA NIM API key: "), (ans) => { rl.close(); resolve(ans.trim()); });
|
|
73
|
-
});
|
|
74
|
-
if (key) { setApiKey(key); console.log(chalk.green(" ✓ Saved to ~/.kalcode/config.json")); }
|
|
75
|
-
else { console.log(chalk.yellow(" No key provided.")); }
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (values["set-model"]) {
|
|
80
|
-
setModel(values["set-model"]);
|
|
81
|
-
console.log(chalk.green(` ✓ Default model: ${values["set-model"]}`));
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const config = loadConfig();
|
|
86
|
-
if (values.model) config.model = values.model;
|
|
87
|
-
if (values.compact) setCompact(true);
|
|
88
|
-
if (values["auto-accept"]) setPermissionLevel("auto");
|
|
89
|
-
if (values.ask) setAskMode(true);
|
|
90
|
-
const proxyUrl = (process.env.KALCODE_PROXY_URL || "").trim();
|
|
91
|
-
const proxyMode = proxyUrl.length > 0;
|
|
92
|
-
|
|
93
|
-
// API key check (skip when using proxy mode)
|
|
94
|
-
if (!proxyMode && !config.apiKey) {
|
|
95
|
-
console.log("");
|
|
96
|
-
console.log(chalk.yellow(" No API key found."));
|
|
97
|
-
console.log(chalk.dim(" Get one at https://build.nvidia.com (NVIDIA NIM)"));
|
|
98
|
-
console.log("");
|
|
99
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
100
|
-
const key = await new Promise<string>((resolve) => {
|
|
101
|
-
rl.question(chalk.dim(" NVIDIA NIM API key: "), (ans) => { rl.close(); resolve(ans.trim()); });
|
|
102
|
-
});
|
|
103
|
-
if (!key) { console.log(chalk.red(" API key required.")); return; }
|
|
104
|
-
setApiKey(key);
|
|
105
|
-
config.apiKey = key;
|
|
106
|
-
console.log(chalk.green(" ✓ Saved."));
|
|
107
|
-
console.log("");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
await initHistory();
|
|
111
|
-
|
|
112
|
-
// Single-shot mode
|
|
113
|
-
if (positionals.length > 0) {
|
|
114
|
-
await runAgentLoop(config.apiKey, config.model, positionals.join(" "));
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ─── Interactive REPL ─────────────────────────────────────────────
|
|
119
|
-
renderWelcome(config.model);
|
|
120
|
-
|
|
121
|
-
// Status context
|
|
122
|
-
const statusParts: string[] = [];
|
|
123
|
-
const memFile = getMemoryFileName(process.cwd());
|
|
124
|
-
if (memFile) statusParts.push(`memory: ${memFile}`);
|
|
125
|
-
if (isGitRepo()) statusParts.push("git: repo detected");
|
|
126
|
-
if (proxyMode) statusParts.push("api: proxy");
|
|
127
|
-
if (statusParts.length > 0) {
|
|
128
|
-
console.log(chalk.dim(` ${statusParts.join(" · ")}`));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
renderSeparator();
|
|
132
|
-
renderHints();
|
|
133
|
-
|
|
134
|
-
// Ctrl+C handling
|
|
135
|
-
let agentRunning = false;
|
|
136
|
-
process.on("SIGINT", () => {
|
|
137
|
-
if (agentRunning) {
|
|
138
|
-
interruptAgent();
|
|
139
|
-
} else {
|
|
140
|
-
console.log(chalk.dim("\n Goodbye."));
|
|
141
|
-
process.exit(0);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const input = createInput(REPL_COMMANDS.map((c) => c.cmd));
|
|
146
|
-
|
|
147
|
-
while (true) {
|
|
148
|
-
let userInput: string;
|
|
149
|
-
try {
|
|
150
|
-
userInput = await input.prompt();
|
|
151
|
-
} catch {
|
|
152
|
-
await new Promise(r => setTimeout(r, 50));
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!userInput) continue;
|
|
157
|
-
|
|
158
|
-
if (userInput.startsWith("/")) {
|
|
159
|
-
const parts = userInput.split(/\s+/);
|
|
160
|
-
const rawCmd = parts[0]!.toLowerCase();
|
|
161
|
-
const arg = parts.slice(1).join(" ");
|
|
162
|
-
const commandNames = REPL_COMMANDS.map((c) => c.cmd);
|
|
163
|
-
|
|
164
|
-
if (rawCmd === "/") {
|
|
165
|
-
renderSlashCommandMenu(commandNames);
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
let cmd = rawCmd;
|
|
170
|
-
if (!commandNames.includes(rawCmd)) {
|
|
171
|
-
const matches = commandNames.filter((c) => c.startsWith(rawCmd));
|
|
172
|
-
if (matches.length === 1) {
|
|
173
|
-
cmd = matches[0]!;
|
|
174
|
-
console.log(chalk.dim(` → ${rawCmd} resolved to ${cmd}`));
|
|
175
|
-
} else if (matches.length > 1) {
|
|
176
|
-
renderSlashCommandMenu(matches);
|
|
177
|
-
continue;
|
|
178
|
-
} else {
|
|
179
|
-
console.log(chalk.dim(` Unknown command: ${rawCmd}. Type / for command list.`));
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
switch (cmd) {
|
|
185
|
-
case "/exit": case "/quit": case "/q":
|
|
186
|
-
console.log(chalk.dim(" Goodbye."));
|
|
187
|
-
input.close();
|
|
188
|
-
return;
|
|
189
|
-
|
|
190
|
-
case "/help":
|
|
191
|
-
renderHelp();
|
|
192
|
-
continue;
|
|
193
|
-
|
|
194
|
-
case "/clear":
|
|
195
|
-
clearHistory();
|
|
196
|
-
clearContextFiles();
|
|
197
|
-
console.log(chalk.dim(" Conversation cleared."));
|
|
198
|
-
continue;
|
|
199
|
-
|
|
200
|
-
case "/retry": {
|
|
201
|
-
const lastMsg = getLastUserMessage();
|
|
202
|
-
if (!lastMsg) { console.log(chalk.dim(" Nothing to retry.")); continue; }
|
|
203
|
-
removeLastExchange();
|
|
204
|
-
console.log(chalk.dim(` Retrying: ${lastMsg.slice(0, 60)}${lastMsg.length > 60 ? "…" : ""}`));
|
|
205
|
-
// Close readline before agent run so permission keypresses do not leak into prompt input.
|
|
206
|
-
input.close();
|
|
207
|
-
agentRunning = true;
|
|
208
|
-
await runAgentLoop(config.apiKey, config.model, lastMsg);
|
|
209
|
-
agentRunning = false;
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
case "/compact":
|
|
214
|
-
setCompact(!getCompact());
|
|
215
|
-
console.log(chalk.dim(` Compact: ${getCompact() ? "on" : "off"}`));
|
|
216
|
-
continue;
|
|
217
|
-
|
|
218
|
-
case "/ask":
|
|
219
|
-
setAskMode(!getAskMode());
|
|
220
|
-
console.log(chalk.dim(` Read-only mode: ${getAskMode() ? "on" : "off"}`));
|
|
221
|
-
continue;
|
|
222
|
-
|
|
223
|
-
case "/auto":
|
|
224
|
-
setPermissionLevel(getPermissionLevel() === "auto" ? "ask" : "auto");
|
|
225
|
-
console.log(chalk.dim(` Auto-accept: ${getPermissionLevel() === "auto" ? "on" : "off"}`));
|
|
226
|
-
continue;
|
|
227
|
-
|
|
228
|
-
case "/model":
|
|
229
|
-
if (arg) {
|
|
230
|
-
config.model = arg;
|
|
231
|
-
saveConfig(config);
|
|
232
|
-
console.log(chalk.green(` ✓ Model: ${arg}`));
|
|
233
|
-
} else {
|
|
234
|
-
// Close readline before opening raw-mode picker to avoid keypress buffering issues.
|
|
235
|
-
input.close();
|
|
236
|
-
const picked = await pickModel(AVAILABLE_MODELS, config.model);
|
|
237
|
-
if (picked && picked !== config.model) {
|
|
238
|
-
config.model = picked;
|
|
239
|
-
saveConfig(config);
|
|
240
|
-
console.log(chalk.green(` ✓ Switched to ${picked}`));
|
|
241
|
-
} else if (picked) {
|
|
242
|
-
console.log(chalk.dim(` Already using ${picked}`));
|
|
243
|
-
} else {
|
|
244
|
-
console.log(chalk.dim(" Cancelled."));
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
continue;
|
|
248
|
-
|
|
249
|
-
case "/skills": {
|
|
250
|
-
const skills = arg ? searchSkills(arg) : getSkills();
|
|
251
|
-
renderSkillsList(skills, arg);
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
case "/add":
|
|
256
|
-
if (!arg) { console.log(chalk.dim(" Usage: /add <file>")); continue; }
|
|
257
|
-
console.log(chalk.dim(` ${addFileToContext(arg)}`));
|
|
258
|
-
continue;
|
|
259
|
-
|
|
260
|
-
case "/drop":
|
|
261
|
-
if (!arg) { console.log(chalk.dim(" Usage: /drop <file>")); continue; }
|
|
262
|
-
console.log(chalk.dim(` ${dropFileFromContext(arg)}`));
|
|
263
|
-
continue;
|
|
264
|
-
|
|
265
|
-
case "/files": {
|
|
266
|
-
const files = listContextFiles();
|
|
267
|
-
if (files.length === 0) { console.log(chalk.dim(" No files in context. Use /add <file>")); }
|
|
268
|
-
else { files.forEach((f) => console.log(chalk.dim(` · ${f}`))); }
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
case "/diff":
|
|
273
|
-
if (!isGitRepo()) { console.log(chalk.dim(" Not a git repo.")); continue; }
|
|
274
|
-
console.log(chalk.dim(await gitDiffSummary()));
|
|
275
|
-
continue;
|
|
276
|
-
|
|
277
|
-
case "/status":
|
|
278
|
-
if (!isGitRepo()) { console.log(chalk.dim(" Not a git repo.")); continue; }
|
|
279
|
-
console.log(chalk.dim(await gitStatus()));
|
|
280
|
-
continue;
|
|
281
|
-
|
|
282
|
-
case "/log":
|
|
283
|
-
if (!isGitRepo()) { console.log(chalk.dim(" Not a git repo.")); continue; }
|
|
284
|
-
console.log(chalk.dim(await gitLog()));
|
|
285
|
-
continue;
|
|
286
|
-
|
|
287
|
-
case "/undo":
|
|
288
|
-
if (!isGitRepo()) { console.log(chalk.dim(" Not a git repo.")); continue; }
|
|
289
|
-
const undoResult = await gitUndo();
|
|
290
|
-
console.log(undoResult.ok ? chalk.green(` ✓ ${undoResult.output}`) : chalk.red(` ✗ ${undoResult.output}`));
|
|
291
|
-
continue;
|
|
292
|
-
|
|
293
|
-
case "/commit": {
|
|
294
|
-
if (!isGitRepo()) { console.log(chalk.dim(" Not a git repo.")); continue; }
|
|
295
|
-
const msg = arg || "kalcode changes";
|
|
296
|
-
const commitResult = await gitCommit(msg);
|
|
297
|
-
console.log(commitResult.ok ? chalk.green(` ✓ ${commitResult.output}`) : chalk.red(` ✗ ${commitResult.output}`));
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
case "/init": {
|
|
302
|
-
const path = join(process.cwd(), "KALCODE.md");
|
|
303
|
-
const template = `# Project Conventions
|
|
304
|
-
|
|
305
|
-
## Tech Stack
|
|
306
|
-
<!-- e.g., TypeScript, React, Node.js -->
|
|
307
|
-
|
|
308
|
-
## Architecture
|
|
309
|
-
<!-- Brief description of project structure -->
|
|
310
|
-
|
|
311
|
-
## Coding Style
|
|
312
|
-
<!-- e.g., Use functional components, prefer const, etc. -->
|
|
313
|
-
|
|
314
|
-
## Important Files
|
|
315
|
-
<!-- Key files the agent should know about -->
|
|
316
|
-
|
|
317
|
-
## Rules
|
|
318
|
-
<!-- Any rules the agent should follow -->
|
|
319
|
-
`;
|
|
320
|
-
writeFileSync(path, template);
|
|
321
|
-
console.log(chalk.green(" ✓ Created KALCODE.md — edit it with your project conventions"));
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
default:
|
|
326
|
-
console.log(chalk.dim(` Unknown command: ${cmd}. Type /help`));
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Close readline before agent run so permission keypresses do not leak into prompt input.
|
|
332
|
-
input.close();
|
|
333
|
-
agentRunning = true;
|
|
334
|
-
await runAgentLoop(config.apiKey, config.model, userInput);
|
|
335
|
-
agentRunning = false;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
input.close();
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function printHelp(): void {
|
|
342
|
-
console.log(`
|
|
343
|
-
${chalk.bold("kalcode")} — CLI coding agent powered by NVIDIA NIM
|
|
344
|
-
|
|
345
|
-
${chalk.bold("Usage:")}
|
|
346
|
-
kalcode [options] [prompt]
|
|
347
|
-
|
|
348
|
-
${chalk.bold("Options:")}
|
|
349
|
-
-h, --help Show this help
|
|
350
|
-
-v, --version Show version
|
|
351
|
-
-m, --model <id> Use a specific model
|
|
352
|
-
--set-key Set NVIDIA NIM API key
|
|
353
|
-
--set-model <id> Set default model
|
|
354
|
-
--compact Compact output
|
|
355
|
-
--auto-accept Skip permission prompts
|
|
356
|
-
--ask Read-only mode (no writes)
|
|
357
|
-
|
|
358
|
-
${chalk.bold("REPL commands:")}
|
|
359
|
-
/help Show help
|
|
360
|
-
/model [id] Show or switch model
|
|
361
|
-
/clear Clear conversation + context
|
|
362
|
-
/retry Retry last message
|
|
363
|
-
/compact Toggle compact output
|
|
364
|
-
/ask Toggle read-only mode
|
|
365
|
-
/auto Toggle auto-accept permissions
|
|
366
|
-
/skills [query] List skills (filter optional)
|
|
367
|
-
/add <file> Add file to context
|
|
368
|
-
/drop <file> Remove file from context
|
|
369
|
-
/files List context files
|
|
370
|
-
|
|
371
|
-
${chalk.bold("Git commands:")}
|
|
372
|
-
/diff Show uncommitted changes
|
|
373
|
-
/status Git status
|
|
374
|
-
/log Recent commits
|
|
375
|
-
/undo Undo last commit (soft reset)
|
|
376
|
-
/commit [msg] Commit all changes
|
|
377
|
-
|
|
378
|
-
${chalk.bold("Project:")}
|
|
379
|
-
/init Create KALCODE.md conventions file
|
|
380
|
-
/exit Quit
|
|
381
|
-
|
|
382
|
-
${chalk.bold("Models (NVIDIA NIM):")}
|
|
383
|
-
${AVAILABLE_MODELS.map(m => ` ${chalk.dim(m.name.padEnd(22))} ${m.id}`).join("\n")}
|
|
384
|
-
|
|
385
|
-
${chalk.bold("Examples:")}
|
|
386
|
-
kalcode "fix the bug in main.ts"
|
|
387
|
-
kalcode -m nvidia/llama-3.1-405b-instruct "explain this"
|
|
388
|
-
kalcode --ask "explain this codebase"
|
|
389
|
-
/ Show all slash commands
|
|
390
|
-
/he Auto-resolve to /help when unique
|
|
391
|
-
`);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function renderSlashCommandMenu(commands: string[]): void {
|
|
395
|
-
console.log("");
|
|
396
|
-
console.log(chalk.bold(" Slash commands"));
|
|
397
|
-
for (const cmd of commands) {
|
|
398
|
-
const def = REPL_COMMANDS.find((c) => c.cmd === cmd);
|
|
399
|
-
if (!def) continue;
|
|
400
|
-
console.log(` ${chalk.bold(cmd.padEnd(12))}${chalk.dim(def.desc)}`);
|
|
401
|
-
}
|
|
402
|
-
console.log("");
|
|
403
|
-
}
|
package/src/proxy/server.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { API_URL } from "../constants.js";
|
|
2
|
-
|
|
3
|
-
const PORT = parseInt(process.env.PORT || "8787", 10);
|
|
4
|
-
const HOST = process.env.HOST || "0.0.0.0";
|
|
5
|
-
const PROXY_TOKEN = process.env.KALCODE_PROXY_TOKEN || "";
|
|
6
|
-
const NVIDIA_NIM_KEY = process.env.NVIDIA_NIM_KEY || "";
|
|
7
|
-
const RATE_LIMIT_PER_MIN = Math.max(
|
|
8
|
-
1,
|
|
9
|
-
parseInt(process.env.KALCODE_PROXY_RATE_LIMIT_PER_MIN || "120", 10),
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
const requestTimestampsByClient = new Map<string, number[]>();
|
|
13
|
-
|
|
14
|
-
function json(status: number, payload: Record<string, unknown>): Response {
|
|
15
|
-
return new Response(JSON.stringify(payload), {
|
|
16
|
-
status,
|
|
17
|
-
headers: {
|
|
18
|
-
"Content-Type": "application/json",
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getClientId(req: Request): string {
|
|
24
|
-
const forwardedFor = req.headers.get("x-forwarded-for");
|
|
25
|
-
if (forwardedFor) return forwardedFor.split(",")[0]!.trim();
|
|
26
|
-
return "unknown";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function isRateLimited(clientId: string): boolean {
|
|
30
|
-
const now = Date.now();
|
|
31
|
-
const windowStart = now - 60_000;
|
|
32
|
-
const timestamps = requestTimestampsByClient.get(clientId) || [];
|
|
33
|
-
const filtered = timestamps.filter((ts) => ts >= windowStart);
|
|
34
|
-
|
|
35
|
-
if (filtered.length >= RATE_LIMIT_PER_MIN) {
|
|
36
|
-
requestTimestampsByClient.set(clientId, filtered);
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
filtered.push(now);
|
|
41
|
-
requestTimestampsByClient.set(clientId, filtered);
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getBearerToken(req: Request): string {
|
|
46
|
-
const auth = req.headers.get("authorization") || "";
|
|
47
|
-
const m = auth.match(/^Bearer\s+(.+)$/i);
|
|
48
|
-
return m?.[1]?.trim() || "";
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function proxyCompletions(req: Request): Promise<Response> {
|
|
52
|
-
if (!NVIDIA_NIM_KEY) {
|
|
53
|
-
return json(500, {
|
|
54
|
-
error: {
|
|
55
|
-
message: "Server missing NVIDIA_NIM_KEY.",
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (PROXY_TOKEN) {
|
|
61
|
-
const incomingToken = getBearerToken(req);
|
|
62
|
-
if (!incomingToken || incomingToken !== PROXY_TOKEN) {
|
|
63
|
-
return json(401, {
|
|
64
|
-
error: {
|
|
65
|
-
message: "Unauthorized. Invalid proxy token.",
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const clientId = getClientId(req);
|
|
72
|
-
if (isRateLimited(clientId)) {
|
|
73
|
-
return json(429, {
|
|
74
|
-
error: {
|
|
75
|
-
message: "Rate limit exceeded. Please retry shortly.",
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const body = await req.text();
|
|
81
|
-
const upstream = await fetch(API_URL, {
|
|
82
|
-
method: "POST",
|
|
83
|
-
headers: {
|
|
84
|
-
"Content-Type": "application/json",
|
|
85
|
-
Authorization: `Bearer ${NVIDIA_NIM_KEY}`,
|
|
86
|
-
},
|
|
87
|
-
body,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const headers = new Headers();
|
|
91
|
-
const contentType = upstream.headers.get("content-type");
|
|
92
|
-
if (contentType) headers.set("Content-Type", contentType);
|
|
93
|
-
const cacheControl = upstream.headers.get("cache-control");
|
|
94
|
-
if (cacheControl) headers.set("Cache-Control", cacheControl);
|
|
95
|
-
|
|
96
|
-
return new Response(upstream.body, {
|
|
97
|
-
status: upstream.status,
|
|
98
|
-
headers,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const server = Bun.serve({
|
|
103
|
-
hostname: HOST,
|
|
104
|
-
port: PORT,
|
|
105
|
-
async fetch(req: Request): Promise<Response> {
|
|
106
|
-
const url = new URL(req.url);
|
|
107
|
-
|
|
108
|
-
if (req.method === "GET" && url.pathname === "/health") {
|
|
109
|
-
return json(200, {
|
|
110
|
-
ok: true,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (req.method === "POST" && url.pathname === "/v1/chat/completions") {
|
|
115
|
-
return proxyCompletions(req);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return json(404, {
|
|
119
|
-
error: {
|
|
120
|
-
message: "Not found.",
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
console.log(
|
|
127
|
-
`kalcode proxy listening on http://${server.hostname}:${server.port} (rate ${RATE_LIMIT_PER_MIN}/min/client)`,
|
|
128
|
-
);
|