@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.
@@ -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
+ }
package/vercel.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "rewrites": [
3
+ {
4
+ "source": "/v1/chat/completions",
5
+ "destination": "/api/v1/chat/completions"
6
+ },
7
+ {
8
+ "source": "/health",
9
+ "destination": "/api/health"
10
+ }
11
+ ]
12
+ }