@thejeetsingh/kalcode 2.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/LICENSE +21 -0
- package/README.md +142 -0
- package/api/health.ts +10 -0
- package/api/v1/chat/completions.ts +59 -0
- package/bin/kalcode.ts +14 -0
- package/package.json +56 -0
- package/src/agent/context.ts +62 -0
- package/src/agent/history.ts +70 -0
- package/src/agent/loop.ts +282 -0
- package/src/agent/memory.ts +26 -0
- package/src/agent/permissions.ts +84 -0
- package/src/agent/text-tool-parser.ts +71 -0
- package/src/api/client.ts +110 -0
- package/src/api/stream-parser.ts +109 -0
- package/src/config.ts +61 -0
- package/src/constants.ts +58 -0
- package/src/git/git.ts +86 -0
- package/src/index.ts +403 -0
- package/src/proxy/server.ts +128 -0
- package/src/tools/edit-file.ts +97 -0
- package/src/tools/glob-tool.ts +59 -0
- package/src/tools/grep.ts +96 -0
- package/src/tools/list-directory.ts +101 -0
- package/src/tools/read-file.ts +71 -0
- package/src/tools/registry.ts +41 -0
- package/src/tools/run-command.ts +99 -0
- package/src/tools/write-file.ts +42 -0
- package/src/types.ts +68 -0
- package/src/ui/input.ts +60 -0
- package/src/ui/model-picker.ts +92 -0
- package/src/ui/skills-picker.ts +113 -0
- package/src/ui/skills.ts +152 -0
- package/src/ui/spinner.ts +56 -0
- package/src/ui/stream-renderer.ts +69 -0
- package/src/ui/terminal.ts +337 -0
- package/tsconfig.json +15 -0
- package/vercel.json +12 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { TokenUsage } from "../types.js";
|
|
3
|
+
import { VERSION } from "../constants.js";
|
|
4
|
+
|
|
5
|
+
// ─── Claude Code–style color palette (minimal, semantic) ────────────
|
|
6
|
+
const DIM = chalk.dim;
|
|
7
|
+
const BOLD = chalk.bold;
|
|
8
|
+
const RED = chalk.red;
|
|
9
|
+
const YELLOW = chalk.yellow;
|
|
10
|
+
const GREEN = chalk.green;
|
|
11
|
+
const CYAN = chalk.cyan;
|
|
12
|
+
|
|
13
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
function stripAnsi(str: string): string {
|
|
16
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function visibleLength(str: string): number {
|
|
20
|
+
return stripAnsi(str).length;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function padRight(str: string, width: number): string {
|
|
24
|
+
const vis = visibleLength(str);
|
|
25
|
+
if (vis >= width) return str;
|
|
26
|
+
return str + " ".repeat(width - vis);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getWidth(): number {
|
|
30
|
+
return Math.min(88, Math.max(60, process.stdout.columns || 80));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function shortModelName(modelId: string): string {
|
|
34
|
+
const parts = modelId.split("/");
|
|
35
|
+
let name = parts.length > 1 ? parts[1]! : modelId;
|
|
36
|
+
// Strip common verbose suffixes for compact display
|
|
37
|
+
name = name
|
|
38
|
+
.replace(/-instruct(?:-\d+)?$/, "")
|
|
39
|
+
.replace(/-chat$/, "");
|
|
40
|
+
if (name.length > 28) name = name.slice(0, 28) + "…";
|
|
41
|
+
return name;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function shortenPath(p: string): string {
|
|
45
|
+
const home = process.env.HOME || "";
|
|
46
|
+
if (home && p.startsWith(home)) return "~" + p.slice(home.length);
|
|
47
|
+
return p;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function truncLine(line: string, max = 120): string {
|
|
51
|
+
return line.length > max ? line.slice(0, max) + "…" : line;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function truncPath(p: string, max: number): string {
|
|
55
|
+
if (p.length <= max) return p;
|
|
56
|
+
// Keep the last components that fit
|
|
57
|
+
const parts = p.split("/");
|
|
58
|
+
let result = parts[parts.length - 1]!;
|
|
59
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
60
|
+
const candidate = parts[i] + "/" + result;
|
|
61
|
+
if (candidate.length + 2 > max) break;
|
|
62
|
+
result = candidate;
|
|
63
|
+
}
|
|
64
|
+
return "…/" + result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Welcome Screen (Claude Code style boxed panel) ─────────────────
|
|
68
|
+
|
|
69
|
+
export function renderWelcome(model: string): void {
|
|
70
|
+
const w = getWidth();
|
|
71
|
+
const midCol = Math.floor(w * 0.50);
|
|
72
|
+
const leftW = midCol - 1;
|
|
73
|
+
const rightW = w - midCol - 2;
|
|
74
|
+
|
|
75
|
+
// Top border with version embedded: ┌─ kalcode v2.0.0 ──...──┐
|
|
76
|
+
const vTag = ` kalcode v${VERSION} `;
|
|
77
|
+
const topFill = w - 2 - vTag.length - 1;
|
|
78
|
+
console.log("");
|
|
79
|
+
console.log(`┌─${vTag}${"─".repeat(Math.max(0, topFill))}┐`);
|
|
80
|
+
|
|
81
|
+
const leftLines: string[] = [
|
|
82
|
+
"",
|
|
83
|
+
` ${BOLD("Welcome!")}`,
|
|
84
|
+
"",
|
|
85
|
+
` ${DIM("┌──┐")}`,
|
|
86
|
+
` ${DIM("│")}${CYAN("kc")}${DIM("│")}`,
|
|
87
|
+
` ${DIM("└──┘")}`,
|
|
88
|
+
"",
|
|
89
|
+
` ${shortModelName(model)} ${DIM("· NVIDIA NIM")}`,
|
|
90
|
+
` ${DIM(truncPath(shortenPath(process.cwd()), leftW - 4))}`,
|
|
91
|
+
"",
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const rightLines: string[] = [
|
|
95
|
+
"",
|
|
96
|
+
` ${YELLOW("Tips for getting started")}`,
|
|
97
|
+
` Run ${BOLD("/init")} to create a KALCODE.md file`,
|
|
98
|
+
` with instructions for kalcode`,
|
|
99
|
+
"",
|
|
100
|
+
` ${YELLOW("Recent activity")}`,
|
|
101
|
+
` ${DIM("No recent activity")}`,
|
|
102
|
+
"",
|
|
103
|
+
"",
|
|
104
|
+
"",
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const rows = Math.max(leftLines.length, rightLines.length);
|
|
108
|
+
for (let i = 0; i < rows; i++) {
|
|
109
|
+
const l = padRight(leftLines[i] || "", leftW);
|
|
110
|
+
const r = padRight(rightLines[i] || "", rightW);
|
|
111
|
+
console.log(`│${l}│${r}│`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(`└${"─".repeat(leftW)}┴${"─".repeat(rightW)}┘`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Separator + Hints (shown between welcome and prompt) ───────────
|
|
118
|
+
|
|
119
|
+
export function renderSeparator(): void {
|
|
120
|
+
console.log("");
|
|
121
|
+
console.log(DIM("─".repeat(getWidth())));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function renderHints(): void {
|
|
125
|
+
const left = DIM(" ? for shortcuts");
|
|
126
|
+
const right = DIM("/help for commands /model to switch model");
|
|
127
|
+
const w = getWidth();
|
|
128
|
+
const gap = Math.max(2, w - visibleLength(left) - visibleLength(right));
|
|
129
|
+
console.log(left + " ".repeat(gap) + right);
|
|
130
|
+
console.log("");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── Status line (no-op; state is shown in welcome + hints) ─────────
|
|
134
|
+
|
|
135
|
+
export function renderStatusLine(_opts: {
|
|
136
|
+
model: string;
|
|
137
|
+
askMode: boolean;
|
|
138
|
+
autoAccept: boolean;
|
|
139
|
+
compact: boolean;
|
|
140
|
+
}): void {
|
|
141
|
+
// Intentionally empty — status context is in the welcome box
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── Help (Claude Code style boxed panel) ───────────────────────────
|
|
145
|
+
|
|
146
|
+
export function renderHelp(): void {
|
|
147
|
+
const w = getWidth();
|
|
148
|
+
const inner = w - 2;
|
|
149
|
+
|
|
150
|
+
// Top border
|
|
151
|
+
const hTag = " Commands ";
|
|
152
|
+
const hFill = inner - hTag.length - 1;
|
|
153
|
+
console.log("");
|
|
154
|
+
console.log(`┌─${hTag}${"─".repeat(Math.max(0, hFill))}┐`);
|
|
155
|
+
|
|
156
|
+
const sections: [string, [string, string][]][] = [
|
|
157
|
+
["Session", [
|
|
158
|
+
["/help", "Show this help"],
|
|
159
|
+
["/clear", "Clear conversation"],
|
|
160
|
+
["/retry", "Retry last message"],
|
|
161
|
+
["/model", "Switch model (TUI picker)"],
|
|
162
|
+
["/compact", "Toggle compact output"],
|
|
163
|
+
["/ask", "Toggle read-only mode"],
|
|
164
|
+
["/auto", "Toggle auto-accept"],
|
|
165
|
+
["/skills", "List available skills"],
|
|
166
|
+
["/", "Show slash commands"],
|
|
167
|
+
["/exit", "Quit"],
|
|
168
|
+
]],
|
|
169
|
+
["Context", [
|
|
170
|
+
["/add <f>", "Add file to context"],
|
|
171
|
+
["/drop <f>", "Remove file from context"],
|
|
172
|
+
["/files", "List context files"],
|
|
173
|
+
]],
|
|
174
|
+
["Git", [
|
|
175
|
+
["/diff", "Show uncommitted changes"],
|
|
176
|
+
["/status", "Git status"],
|
|
177
|
+
["/log", "Recent commits"],
|
|
178
|
+
["/undo", "Undo last commit"],
|
|
179
|
+
["/commit", "Commit all changes"],
|
|
180
|
+
["/init", "Create KALCODE.md"],
|
|
181
|
+
]],
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
for (const [title, cmds] of sections) {
|
|
185
|
+
const titleLine = ` ${BOLD(title)}`;
|
|
186
|
+
console.log(`│${padRight(titleLine, inner)}│`);
|
|
187
|
+
for (const [cmd, desc] of cmds) {
|
|
188
|
+
const line = ` ${BOLD(cmd.padEnd(14))}${DIM(desc)}`;
|
|
189
|
+
console.log(`│${padRight(line, inner)}│`);
|
|
190
|
+
}
|
|
191
|
+
console.log(`│${" ".repeat(inner)}│`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(`└${"─".repeat(inner)}┘`);
|
|
195
|
+
console.log("");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ─── Tool calls (Claude Code style ● marker + ⎿ continuation) ──────
|
|
199
|
+
|
|
200
|
+
export function renderToolCall(
|
|
201
|
+
name: string,
|
|
202
|
+
args: Record<string, unknown>,
|
|
203
|
+
): void {
|
|
204
|
+
const display = toolDisplayName(name);
|
|
205
|
+
const summary = formatToolSummary(name, args);
|
|
206
|
+
console.log("");
|
|
207
|
+
console.log(
|
|
208
|
+
summary
|
|
209
|
+
? ` ${CYAN("●")} ${BOLD(display)} ${DIM(summary)}`
|
|
210
|
+
: ` ${CYAN("●")} ${BOLD(display)}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function renderToolResult(
|
|
215
|
+
name: string,
|
|
216
|
+
result: string,
|
|
217
|
+
compact: boolean,
|
|
218
|
+
): void {
|
|
219
|
+
const lines = result.split("\n").filter((l) => l.length > 0);
|
|
220
|
+
if (lines.length === 0) return;
|
|
221
|
+
|
|
222
|
+
const cont = ` ${DIM("⎿")} `;
|
|
223
|
+
|
|
224
|
+
if (name === "writeFile" || name === "editFile") {
|
|
225
|
+
console.log(`${cont}${DIM(truncLine(lines[0]!))}`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const maxLines = compact ? 4 : 8;
|
|
230
|
+
const visible =
|
|
231
|
+
lines.length <= maxLines
|
|
232
|
+
? lines
|
|
233
|
+
: [...lines.slice(0, maxLines - 1), `… ${lines.length - maxLines + 1} more lines`];
|
|
234
|
+
|
|
235
|
+
for (const line of visible) {
|
|
236
|
+
console.log(`${cont}${DIM(truncLine(line))}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── Model info ─────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
export function renderModelInfo(
|
|
243
|
+
current: string,
|
|
244
|
+
available: { id: string; name: string; params: string }[],
|
|
245
|
+
): void {
|
|
246
|
+
console.log("");
|
|
247
|
+
console.log(` Current: ${BOLD(shortModelName(current))}`);
|
|
248
|
+
console.log("");
|
|
249
|
+
for (const m of available) {
|
|
250
|
+
const active = m.id === current;
|
|
251
|
+
const marker = active ? GREEN("●") : DIM("○");
|
|
252
|
+
const name = active ? BOLD(m.name) : m.name;
|
|
253
|
+
const params = DIM(`(${m.params})`);
|
|
254
|
+
console.log(` ${marker} ${name} ${params}`);
|
|
255
|
+
}
|
|
256
|
+
console.log("");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ─── Misc renders ───────────────────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
export function renderError(message: string): void {
|
|
262
|
+
console.log(` ${RED("✗")} ${RED(message)}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function renderUsage(usage: TokenUsage | null): void {
|
|
266
|
+
if (!usage) return;
|
|
267
|
+
const { prompt_tokens, completion_tokens, total_tokens } = usage;
|
|
268
|
+
console.log(
|
|
269
|
+
DIM(`\n ${total_tokens} tokens · ${prompt_tokens} in · ${completion_tokens} out`),
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function renderThinking(): void {
|
|
274
|
+
process.stdout.write(DIM(" thinking…"));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function clearThinking(): void {
|
|
278
|
+
process.stdout.write("\r\x1b[2K");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function renderRetryWait(attempt: number, waitSec: number): void {
|
|
282
|
+
process.stdout.write(
|
|
283
|
+
`\r\x1b[2K ${YELLOW("⏳")} ${DIM(`Rate limited — retry ${attempt} in ${waitSec}s`)}`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function renderInterrupted(): void {
|
|
288
|
+
console.log("");
|
|
289
|
+
console.log(` ${YELLOW("⚠")} ${YELLOW("Interrupted")}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ─── Private helpers ────────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
function toolDisplayName(name: string): string {
|
|
295
|
+
switch (name) {
|
|
296
|
+
case "readFile":
|
|
297
|
+
return "Read";
|
|
298
|
+
case "writeFile":
|
|
299
|
+
return "Write";
|
|
300
|
+
case "editFile":
|
|
301
|
+
return "Edit";
|
|
302
|
+
case "runCommand":
|
|
303
|
+
return "Bash";
|
|
304
|
+
case "grep":
|
|
305
|
+
return "Grep";
|
|
306
|
+
case "glob":
|
|
307
|
+
return "Glob";
|
|
308
|
+
case "listDirectory":
|
|
309
|
+
return "List";
|
|
310
|
+
default:
|
|
311
|
+
return name;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function formatToolSummary(
|
|
316
|
+
name: string,
|
|
317
|
+
args: Record<string, unknown>,
|
|
318
|
+
): string {
|
|
319
|
+
switch (name) {
|
|
320
|
+
case "readFile":
|
|
321
|
+
case "writeFile":
|
|
322
|
+
case "editFile":
|
|
323
|
+
return String(args.filePath || "");
|
|
324
|
+
case "runCommand": {
|
|
325
|
+
const cmd = String(args.command || "");
|
|
326
|
+
return cmd.length > 80 ? cmd.slice(0, 80) + "…" : cmd;
|
|
327
|
+
}
|
|
328
|
+
case "grep":
|
|
329
|
+
return `"${args.pattern}" in ${args.path || "."}`;
|
|
330
|
+
case "glob":
|
|
331
|
+
return String(args.pattern || "");
|
|
332
|
+
case "listDirectory":
|
|
333
|
+
return String(args.dirPath || ".");
|
|
334
|
+
default:
|
|
335
|
+
return "";
|
|
336
|
+
}
|
|
337
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": ".",
|
|
12
|
+
"types": ["bun"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*", "bin/**/*", "api/**/*"]
|
|
15
|
+
}
|