@alia-codea/cli 1.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/dist/index.js ADDED
@@ -0,0 +1,878 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ config,
4
+ createSession,
5
+ getSession,
6
+ getSessions,
7
+ saveSession,
8
+ streamChat
9
+ } from "./chunk-SVPL4GNV.js";
10
+
11
+ // src/index.ts
12
+ import { Command } from "commander";
13
+
14
+ // src/commands/repl.ts
15
+ import * as readline2 from "readline";
16
+ import chalk3 from "chalk";
17
+
18
+ // src/tools/executor.ts
19
+ import * as fs from "fs/promises";
20
+ import * as path from "path";
21
+ import { exec } from "child_process";
22
+ import { promisify } from "util";
23
+ import chalk from "chalk";
24
+ var execAsync = promisify(exec);
25
+ async function executeTool(name, args) {
26
+ try {
27
+ switch (name) {
28
+ case "read_file":
29
+ return await readFile2(args.path);
30
+ case "write_file":
31
+ return await writeFile2(args.path, args.content);
32
+ case "edit_file":
33
+ return await editFile(args.path, args.old_text, args.new_text);
34
+ case "list_files":
35
+ return await listFiles(args.path, args.recursive);
36
+ case "search_files":
37
+ return await searchFiles(args.pattern, args.path, args.file_pattern);
38
+ case "run_command":
39
+ return await runCommand(args.command, args.cwd);
40
+ default:
41
+ return { success: false, result: `Unknown tool: ${name}` };
42
+ }
43
+ } catch (error) {
44
+ return { success: false, result: error.message };
45
+ }
46
+ }
47
+ async function readFile2(filePath) {
48
+ const absolutePath = path.resolve(process.cwd(), filePath);
49
+ const content = await fs.readFile(absolutePath, "utf-8");
50
+ return { success: true, result: content };
51
+ }
52
+ async function writeFile2(filePath, content) {
53
+ const absolutePath = path.resolve(process.cwd(), filePath);
54
+ await fs.mkdir(path.dirname(absolutePath), { recursive: true });
55
+ await fs.writeFile(absolutePath, content, "utf-8");
56
+ return { success: true, result: `File written: ${filePath}` };
57
+ }
58
+ async function editFile(filePath, oldText, newText) {
59
+ const absolutePath = path.resolve(process.cwd(), filePath);
60
+ const content = await fs.readFile(absolutePath, "utf-8");
61
+ if (!content.includes(oldText)) {
62
+ return { success: false, result: `Text not found in file: "${oldText.slice(0, 50)}..."` };
63
+ }
64
+ const newContent = content.replace(oldText, newText);
65
+ await fs.writeFile(absolutePath, newContent, "utf-8");
66
+ return { success: true, result: `File edited: ${filePath}` };
67
+ }
68
+ async function listFiles(dirPath = ".", recursive = false) {
69
+ const absolutePath = path.resolve(process.cwd(), dirPath);
70
+ if (recursive) {
71
+ const files = [];
72
+ async function walk(dir) {
73
+ const entries = await fs.readdir(dir, { withFileTypes: true });
74
+ for (const entry of entries) {
75
+ const fullPath = path.join(dir, entry.name);
76
+ const relativePath = path.relative(absolutePath, fullPath);
77
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") {
78
+ continue;
79
+ }
80
+ if (entry.isDirectory()) {
81
+ files.push(relativePath + "/");
82
+ await walk(fullPath);
83
+ } else {
84
+ files.push(relativePath);
85
+ }
86
+ }
87
+ }
88
+ await walk(absolutePath);
89
+ return { success: true, result: files.join("\n") };
90
+ } else {
91
+ const entries = await fs.readdir(absolutePath, { withFileTypes: true });
92
+ const files = entries.map((e) => e.name + (e.isDirectory() ? "/" : ""));
93
+ return { success: true, result: files.join("\n") };
94
+ }
95
+ }
96
+ async function searchFiles(pattern, dirPath = ".", filePattern) {
97
+ const absolutePath = path.resolve(process.cwd(), dirPath);
98
+ const regex = new RegExp(pattern, "gi");
99
+ const results = [];
100
+ async function searchDir(dir) {
101
+ const entries = await fs.readdir(dir, { withFileTypes: true });
102
+ for (const entry of entries) {
103
+ const fullPath = path.join(dir, entry.name);
104
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") {
105
+ continue;
106
+ }
107
+ if (entry.isDirectory()) {
108
+ await searchDir(fullPath);
109
+ } else {
110
+ if (filePattern) {
111
+ const ext = path.extname(entry.name);
112
+ const patternExt = filePattern.replace("*", "");
113
+ if (ext !== patternExt && !entry.name.match(filePattern.replace("*", ".*"))) {
114
+ continue;
115
+ }
116
+ }
117
+ try {
118
+ const content = await fs.readFile(fullPath, "utf-8");
119
+ const lines = content.split("\n");
120
+ lines.forEach((line, index) => {
121
+ if (regex.test(line)) {
122
+ const relativePath = path.relative(absolutePath, fullPath);
123
+ results.push(`${relativePath}:${index + 1}: ${line.trim()}`);
124
+ }
125
+ });
126
+ } catch {
127
+ }
128
+ }
129
+ }
130
+ }
131
+ await searchDir(absolutePath);
132
+ if (results.length === 0) {
133
+ return { success: true, result: "No matches found." };
134
+ }
135
+ return { success: true, result: results.slice(0, 100).join("\n") + (results.length > 100 ? `
136
+ ... and ${results.length - 100} more` : "") };
137
+ }
138
+ async function runCommand(command, cwd) {
139
+ const workingDir = cwd ? path.resolve(process.cwd(), cwd) : process.cwd();
140
+ try {
141
+ const { stdout, stderr } = await execAsync(command, {
142
+ cwd: workingDir,
143
+ maxBuffer: 1024 * 1024,
144
+ timeout: 6e4
145
+ });
146
+ const output = stdout + (stderr ? `
147
+ Stderr:
148
+ ${stderr}` : "");
149
+ return { success: true, result: output || "Command completed successfully." };
150
+ } catch (error) {
151
+ return {
152
+ success: false,
153
+ result: error.stdout + (error.stderr ? `
154
+ Stderr:
155
+ ${error.stderr}` : "") || error.message
156
+ };
157
+ }
158
+ }
159
+ function formatToolCall(name, args) {
160
+ const labels = {
161
+ read_file: "Reading file",
162
+ write_file: "Writing file",
163
+ edit_file: "Editing file",
164
+ list_files: "Listing files",
165
+ search_files: "Searching files",
166
+ run_command: "Running command"
167
+ };
168
+ const label = labels[name] || name;
169
+ const argStr = Object.entries(args).map(([k, v]) => `${k}: ${typeof v === "string" ? v.slice(0, 50) : v}`).join(", ");
170
+ return `${chalk.cyan("\u2192")} ${chalk.bold(label)}: ${chalk.gray(argStr)}`;
171
+ }
172
+
173
+ // src/utils/ui.ts
174
+ import chalk2 from "chalk";
175
+ import * as readline from "readline";
176
+ function printTips() {
177
+ console.log(chalk2.white("Tips for getting started:"));
178
+ console.log(chalk2.gray("1. Ask questions, edit files, or run commands."));
179
+ console.log(chalk2.gray("2. Be specific for the best results."));
180
+ console.log(chalk2.gray("3. ") + chalk2.cyan("/help") + chalk2.gray(" for more information."));
181
+ console.log();
182
+ }
183
+ function printPrompt() {
184
+ process.stdout.write(chalk2.cyan("\u276F "));
185
+ }
186
+ function printToolExecution(tool, description) {
187
+ const boxWidth = Math.min(process.stdout.columns || 80, 80);
188
+ const content = `${chalk2.bold(tool)} ${description}`;
189
+ const paddedContent = ` \u2190 ${content} `.padEnd(boxWidth - 4);
190
+ console.log();
191
+ console.log(chalk2.gray("\u250C" + "\u2500".repeat(boxWidth - 2) + "\u2510"));
192
+ console.log(chalk2.gray("\u2502") + paddedContent + chalk2.gray("\u2502"));
193
+ console.log(chalk2.gray("\u2514" + "\u2500".repeat(boxWidth - 2) + "\u2518"));
194
+ }
195
+ function printToolResult(success, result) {
196
+ const status = success ? chalk2.green("\u2713") : chalk2.red("\u2717");
197
+ const preview = result.slice(0, 100).replace(/\n/g, " ");
198
+ console.log(` ${status} ${chalk2.gray(preview)}${result.length > 100 ? "..." : ""}`);
199
+ console.log();
200
+ }
201
+ var statusInterval = null;
202
+ var startTime = 0;
203
+ function showThinkingStatus(message) {
204
+ startTime = Date.now();
205
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
206
+ let frameIndex = 0;
207
+ statusInterval = setInterval(() => {
208
+ const elapsed = Math.floor((Date.now() - startTime) / 1e3);
209
+ const frame = frames[frameIndex % frames.length];
210
+ frameIndex++;
211
+ readline.clearLine(process.stdout, 0);
212
+ readline.cursorTo(process.stdout, 0);
213
+ process.stdout.write(
214
+ chalk2.cyan(frame) + " " + chalk2.bold(message) + chalk2.gray(` (esc to cancel, ${elapsed}s)`)
215
+ );
216
+ }, 80);
217
+ }
218
+ function hideThinkingStatus() {
219
+ if (statusInterval) {
220
+ clearInterval(statusInterval);
221
+ statusInterval = null;
222
+ readline.clearLine(process.stdout, 0);
223
+ readline.cursorTo(process.stdout, 0);
224
+ }
225
+ }
226
+ function printStatusBar(cwd, model, contextPercent) {
227
+ const width = process.stdout.columns || 80;
228
+ const cwdPart = chalk2.cyan(shortenPath(cwd));
229
+ const modelPart = chalk2.magenta(`${model} (${contextPercent}% context left)`);
230
+ const padding = width - stripAnsi(cwdPart).length - stripAnsi(modelPart).length - 4;
231
+ const spacer = " ".repeat(Math.max(padding, 2));
232
+ console.log();
233
+ console.log(chalk2.gray("\u2500".repeat(width)));
234
+ console.log(`${cwdPart}${spacer}${modelPart}`);
235
+ }
236
+ function shortenPath(p) {
237
+ const home = process.env.HOME || "";
238
+ if (p.startsWith(home)) {
239
+ return "~" + p.slice(home.length);
240
+ }
241
+ return p;
242
+ }
243
+ function stripAnsi(str) {
244
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
245
+ }
246
+ function printAssistantPrefix() {
247
+ process.stdout.write(chalk2.magenta("\u2726 "));
248
+ }
249
+ function printError(message) {
250
+ console.log(chalk2.red("\u2717 Error: ") + message);
251
+ }
252
+ function printSuccess(message) {
253
+ console.log(chalk2.green("\u2713 ") + message);
254
+ }
255
+ function printInfo(message) {
256
+ console.log(chalk2.blue("\u2139 ") + message);
257
+ }
258
+
259
+ // src/utils/context.ts
260
+ import * as fs2 from "fs/promises";
261
+ import * as path2 from "path";
262
+ import { exec as exec2 } from "child_process";
263
+ import { promisify as promisify2 } from "util";
264
+ var execAsync2 = promisify2(exec2);
265
+ function buildSystemMessage(model, codebaseContext) {
266
+ let systemMessage = `You are Codea, an expert AI coding assistant created by Alia. You help developers write, debug, refactor, and understand code directly in their terminal.
267
+
268
+ ## Core Principles
269
+ - Be concise and direct. Avoid unnecessary explanations unless asked.
270
+ - Write clean, idiomatic, well-structured code following best practices.
271
+ - Consider edge cases, error handling, and security implications.
272
+ - Match the existing code style and conventions of the project.
273
+
274
+ ## Tools Available
275
+ You have powerful tools to interact with the user's workspace:
276
+
277
+ - **read_file**: Read file contents. Use to understand existing code before making changes.
278
+ - **write_file**: Create new files or completely rewrite existing ones.
279
+ - **edit_file**: Make precise, targeted changes to existing files. Preferred for small modifications.
280
+ - **list_files**: Explore directory structure. Use to understand project layout.
281
+ - **search_files**: Find text/patterns across the codebase. Great for finding usages, definitions, etc.
282
+ - **run_command**: Execute shell commands (build, test, git, npm, etc.)
283
+
284
+ ## Best Practices
285
+ 1. **Read before writing**: Always read relevant files before modifying them.
286
+ 2. **Minimal changes**: Make the smallest change necessary to accomplish the task.
287
+ 3. **Preserve style**: Match existing formatting, naming conventions, and patterns.
288
+ 4. **Explain when helpful**: For complex changes, briefly explain the approach.
289
+
290
+ ## Response Style
291
+ - Use markdown for formatting code blocks, lists, and emphasis.
292
+ - For code explanations, be thorough but focused.
293
+ - For code changes, be precise and action-oriented.
294
+ - If unsure about requirements, ask clarifying questions.`;
295
+ if (codebaseContext) {
296
+ systemMessage += `
297
+
298
+ ## Current Codebase Context
299
+ ${codebaseContext}`;
300
+ }
301
+ return systemMessage;
302
+ }
303
+ async function getCodebaseContext() {
304
+ const cwd = process.cwd();
305
+ const contextParts = [];
306
+ try {
307
+ const pkgPath = path2.join(cwd, "package.json");
308
+ const pkgContent = await fs2.readFile(pkgPath, "utf-8");
309
+ const pkg = JSON.parse(pkgContent);
310
+ contextParts.push(`Project: ${pkg.name || "Unknown"} (${pkg.description || "No description"})`);
311
+ if (pkg.dependencies) {
312
+ const deps = Object.keys(pkg.dependencies).slice(0, 10);
313
+ contextParts.push(`Dependencies: ${deps.join(", ")}${Object.keys(pkg.dependencies).length > 10 ? "..." : ""}`);
314
+ }
315
+ } catch {
316
+ }
317
+ try {
318
+ const { stdout: branch } = await execAsync2("git branch --show-current", { cwd });
319
+ contextParts.push(`Git branch: ${branch.trim()}`);
320
+ const { stdout: status } = await execAsync2("git status --porcelain", { cwd });
321
+ const changedFiles = status.trim().split("\n").filter(Boolean).length;
322
+ if (changedFiles > 0) {
323
+ contextParts.push(`Uncommitted changes: ${changedFiles} files`);
324
+ }
325
+ } catch {
326
+ }
327
+ try {
328
+ const files = await getRelevantFiles(cwd);
329
+ if (files.length > 0) {
330
+ contextParts.push(`
331
+ Key files:
332
+ ${files.map((f) => `- ${f}`).join("\n")}`);
333
+ }
334
+ } catch {
335
+ }
336
+ return contextParts.join("\n");
337
+ }
338
+ async function getRelevantFiles(dir, maxFiles = 20) {
339
+ const relevantFiles = [];
340
+ const relevantExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".md"];
341
+ const ignoreDirs = ["node_modules", ".git", "dist", "build", "__pycache__", ".next", "coverage"];
342
+ async function walk(currentDir, depth = 0) {
343
+ if (depth > 3 || relevantFiles.length >= maxFiles) return;
344
+ try {
345
+ const entries = await fs2.readdir(currentDir, { withFileTypes: true });
346
+ for (const entry of entries) {
347
+ if (relevantFiles.length >= maxFiles) break;
348
+ if (ignoreDirs.includes(entry.name)) continue;
349
+ const fullPath = path2.join(currentDir, entry.name);
350
+ const relativePath = path2.relative(dir, fullPath);
351
+ if (entry.isDirectory()) {
352
+ await walk(fullPath, depth + 1);
353
+ } else {
354
+ const ext = path2.extname(entry.name);
355
+ if (relevantExtensions.includes(ext) || entry.name === "README.md" || entry.name === "package.json") {
356
+ relevantFiles.push(relativePath);
357
+ }
358
+ }
359
+ }
360
+ } catch {
361
+ }
362
+ }
363
+ await walk(dir);
364
+ return relevantFiles;
365
+ }
366
+
367
+ // src/commands/repl.ts
368
+ async function startRepl(options) {
369
+ const session = createSession();
370
+ const messages = [];
371
+ let isProcessing = false;
372
+ let contextUsed = 0;
373
+ const maxContext = 128e3;
374
+ printTips();
375
+ let codebaseContext = "";
376
+ if (options.context !== false) {
377
+ printInfo("Analyzing codebase...");
378
+ codebaseContext = await getCodebaseContext();
379
+ if (codebaseContext) {
380
+ printInfo(`Loaded context from ${codebaseContext.split("\n").length} files`);
381
+ }
382
+ }
383
+ const rl = readline2.createInterface({
384
+ input: process.stdin,
385
+ output: process.stdout,
386
+ terminal: true
387
+ });
388
+ rl.on("SIGINT", () => {
389
+ if (isProcessing) {
390
+ isProcessing = false;
391
+ hideThinkingStatus();
392
+ console.log(chalk3.yellow("\nCancelled."));
393
+ printPrompt();
394
+ } else {
395
+ console.log(chalk3.gray("\nGoodbye!"));
396
+ process.exit(0);
397
+ }
398
+ });
399
+ const askQuestion = () => {
400
+ printPrompt();
401
+ rl.question("", async (input) => {
402
+ const trimmed = input.trim();
403
+ if (!trimmed) {
404
+ askQuestion();
405
+ return;
406
+ }
407
+ if (trimmed.startsWith("/")) {
408
+ await handleSlashCommand(trimmed, messages, session, options);
409
+ askQuestion();
410
+ return;
411
+ }
412
+ messages.push({ role: "user", content: trimmed });
413
+ isProcessing = true;
414
+ const systemMessage = buildSystemMessage(options.model, codebaseContext);
415
+ await processConversation(messages, systemMessage, options.model, () => isProcessing);
416
+ isProcessing = false;
417
+ session.messages = messages.map((m) => ({ role: m.role, content: m.content }));
418
+ session.title = messages[0]?.content.slice(0, 50) || "New conversation";
419
+ session.updatedAt = Date.now();
420
+ saveSession(session);
421
+ contextUsed = Math.min(95, Math.floor(messages.reduce((acc, m) => acc + m.content.length, 0) / maxContext * 100));
422
+ printStatusBar(process.cwd(), getModelDisplayName(options.model), 100 - contextUsed);
423
+ askQuestion();
424
+ });
425
+ };
426
+ askQuestion();
427
+ }
428
+ async function processConversation(messages, systemMessage, model, isActive) {
429
+ while (isActive()) {
430
+ console.log();
431
+ printAssistantPrefix();
432
+ let fullContent = "";
433
+ let toolCalls;
434
+ showThinkingStatus("Thinking");
435
+ try {
436
+ await streamChat(messages, systemMessage, model, {
437
+ onContent: (content) => {
438
+ if (!isActive()) return;
439
+ hideThinkingStatus();
440
+ process.stdout.write(content);
441
+ fullContent += content;
442
+ },
443
+ onToolCall: (tc) => {
444
+ },
445
+ onDone: (content, tcs) => {
446
+ hideThinkingStatus();
447
+ toolCalls = tcs;
448
+ },
449
+ onError: (error) => {
450
+ hideThinkingStatus();
451
+ printError(error.message);
452
+ }
453
+ });
454
+ } catch (error) {
455
+ hideThinkingStatus();
456
+ printError(error.message);
457
+ break;
458
+ }
459
+ if (!isActive()) break;
460
+ if (toolCalls && toolCalls.length > 0) {
461
+ messages.push({
462
+ role: "assistant",
463
+ content: fullContent,
464
+ tool_calls: toolCalls
465
+ });
466
+ if (fullContent) {
467
+ console.log();
468
+ }
469
+ for (const tc of toolCalls) {
470
+ if (!isActive()) break;
471
+ const args = JSON.parse(tc.function.arguments);
472
+ printToolExecution(tc.function.name, formatToolArgs(tc.function.name, args));
473
+ showThinkingStatus(`Executing ${tc.function.name}`);
474
+ const result = await executeTool(tc.function.name, args);
475
+ hideThinkingStatus();
476
+ printToolResult(result.success, result.result);
477
+ messages.push({
478
+ role: "tool",
479
+ tool_call_id: tc.id,
480
+ content: result.result
481
+ });
482
+ }
483
+ continue;
484
+ } else {
485
+ if (fullContent) {
486
+ messages.push({ role: "assistant", content: fullContent });
487
+ console.log();
488
+ }
489
+ break;
490
+ }
491
+ }
492
+ }
493
+ async function handleSlashCommand(command, messages, session, options) {
494
+ const [cmd, ...args] = command.slice(1).split(" ");
495
+ switch (cmd.toLowerCase()) {
496
+ case "help":
497
+ console.log();
498
+ console.log(chalk3.bold("Available commands:"));
499
+ console.log(chalk3.cyan(" /help") + chalk3.gray(" - Show this help"));
500
+ console.log(chalk3.cyan(" /clear") + chalk3.gray(" - Clear conversation"));
501
+ console.log(chalk3.cyan(" /model") + chalk3.gray(" - Switch model"));
502
+ console.log(chalk3.cyan(" /context") + chalk3.gray(" - Show current context"));
503
+ console.log(chalk3.cyan(" /save") + chalk3.gray(" - Save conversation"));
504
+ console.log(chalk3.cyan(" /exit") + chalk3.gray(" - Exit Codea"));
505
+ console.log();
506
+ break;
507
+ case "clear":
508
+ messages.length = 0;
509
+ console.log(chalk3.green("Conversation cleared."));
510
+ break;
511
+ case "model":
512
+ const modelArg = args[0];
513
+ if (modelArg) {
514
+ options.model = modelArg.startsWith("alia-") ? modelArg : `alia-v1-${modelArg}`;
515
+ console.log(chalk3.green(`Model switched to ${options.model}`));
516
+ } else {
517
+ console.log(chalk3.gray("Current model: ") + chalk3.cyan(options.model));
518
+ try {
519
+ const { fetchModels } = await import("./api-X2G5QROW.js");
520
+ const apiModels = await fetchModels();
521
+ if (apiModels.length > 0) {
522
+ console.log(chalk3.gray("Available models:"));
523
+ for (const m of apiModels) {
524
+ console.log(chalk3.gray(" ") + chalk3.cyan(m.id) + chalk3.gray(` - ${m.name}`));
525
+ }
526
+ } else {
527
+ console.log(chalk3.gray("Available: codea, codea-pro, codea-thinking"));
528
+ }
529
+ } catch {
530
+ console.log(chalk3.gray("Available: codea, codea-pro, codea-thinking"));
531
+ }
532
+ }
533
+ break;
534
+ case "context":
535
+ console.log(chalk3.gray(`Messages in context: ${messages.length}`));
536
+ console.log(chalk3.gray(`Working directory: ${process.cwd()}`));
537
+ break;
538
+ case "save":
539
+ session.messages = messages.map((m) => ({ role: m.role, content: m.content }));
540
+ session.updatedAt = Date.now();
541
+ saveSession(session);
542
+ console.log(chalk3.green("Conversation saved."));
543
+ break;
544
+ case "exit":
545
+ case "quit":
546
+ console.log(chalk3.gray("Goodbye!"));
547
+ process.exit(0);
548
+ break;
549
+ default:
550
+ console.log(chalk3.yellow(`Unknown command: /${cmd}`));
551
+ console.log(chalk3.gray("Type /help for available commands."));
552
+ }
553
+ }
554
+ function formatToolArgs(name, args) {
555
+ switch (name) {
556
+ case "read_file":
557
+ case "write_file":
558
+ case "edit_file":
559
+ return args.path || "";
560
+ case "list_files":
561
+ return args.path || ".";
562
+ case "search_files":
563
+ return `"${args.pattern}" in ${args.path || "."}`;
564
+ case "run_command":
565
+ return args.command || "";
566
+ default:
567
+ return JSON.stringify(args).slice(0, 50);
568
+ }
569
+ }
570
+ function getModelDisplayName(model) {
571
+ const names = {
572
+ "alia-v1-codea": "codea",
573
+ "alia-v1-pro": "codea-pro",
574
+ "alia-v1-thinking": "codea-thinking"
575
+ };
576
+ return names[model] || model;
577
+ }
578
+
579
+ // src/commands/run.ts
580
+ import chalk4 from "chalk";
581
+ async function runPrompt(prompt, options) {
582
+ const messages = [];
583
+ let codebaseContext = "";
584
+ if (options.context !== false) {
585
+ codebaseContext = await getCodebaseContext();
586
+ }
587
+ messages.push({ role: "user", content: prompt });
588
+ const systemMessage = buildSystemMessage(options.model, codebaseContext);
589
+ await processConversation2(messages, systemMessage, options.model, options.yes);
590
+ }
591
+ async function processConversation2(messages, systemMessage, model, autoApprove) {
592
+ let continueProcessing = true;
593
+ while (continueProcessing) {
594
+ printAssistantPrefix();
595
+ let fullContent = "";
596
+ let toolCalls;
597
+ showThinkingStatus("Thinking");
598
+ try {
599
+ await streamChat(messages, systemMessage, model, {
600
+ onContent: (content) => {
601
+ hideThinkingStatus();
602
+ process.stdout.write(content);
603
+ fullContent += content;
604
+ },
605
+ onToolCall: () => {
606
+ },
607
+ onDone: (content, tcs) => {
608
+ hideThinkingStatus();
609
+ toolCalls = tcs;
610
+ },
611
+ onError: (error) => {
612
+ hideThinkingStatus();
613
+ printError(error.message);
614
+ continueProcessing = false;
615
+ }
616
+ });
617
+ } catch (error) {
618
+ hideThinkingStatus();
619
+ printError(error.message);
620
+ break;
621
+ }
622
+ if (toolCalls && toolCalls.length > 0) {
623
+ messages.push({
624
+ role: "assistant",
625
+ content: fullContent,
626
+ tool_calls: toolCalls
627
+ });
628
+ if (fullContent) console.log();
629
+ for (const tc of toolCalls) {
630
+ const args = JSON.parse(tc.function.arguments);
631
+ const isDestructive = ["write_file", "edit_file", "run_command"].includes(tc.function.name);
632
+ if (isDestructive && !autoApprove) {
633
+ console.log();
634
+ console.log(chalk4.yellow("\u26A0 ") + chalk4.bold("Approval required:"));
635
+ console.log(formatToolCall(tc.function.name, args));
636
+ console.log();
637
+ const approved = await askApproval();
638
+ if (!approved) {
639
+ messages.push({
640
+ role: "tool",
641
+ tool_call_id: tc.id,
642
+ content: "User declined this action."
643
+ });
644
+ continue;
645
+ }
646
+ }
647
+ printToolExecution(tc.function.name, formatToolArgs2(tc.function.name, args));
648
+ showThinkingStatus(`Executing ${tc.function.name}`);
649
+ const result = await executeTool(tc.function.name, args);
650
+ hideThinkingStatus();
651
+ printToolResult(result.success, result.result);
652
+ messages.push({
653
+ role: "tool",
654
+ tool_call_id: tc.id,
655
+ content: result.result
656
+ });
657
+ }
658
+ continue;
659
+ } else {
660
+ if (fullContent) {
661
+ messages.push({ role: "assistant", content: fullContent });
662
+ console.log();
663
+ }
664
+ break;
665
+ }
666
+ }
667
+ }
668
+ async function askApproval() {
669
+ const readline5 = await import("readline");
670
+ const rl = readline5.createInterface({
671
+ input: process.stdin,
672
+ output: process.stdout
673
+ });
674
+ return new Promise((resolve2) => {
675
+ rl.question(chalk4.cyan("Allow? [y/N] "), (answer) => {
676
+ rl.close();
677
+ resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
678
+ });
679
+ });
680
+ }
681
+ function formatToolArgs2(name, args) {
682
+ switch (name) {
683
+ case "read_file":
684
+ case "write_file":
685
+ case "edit_file":
686
+ return args.path || "";
687
+ case "list_files":
688
+ return args.path || ".";
689
+ case "search_files":
690
+ return `"${args.pattern}" in ${args.path || "."}`;
691
+ case "run_command":
692
+ return args.command || "";
693
+ default:
694
+ return JSON.stringify(args).slice(0, 50);
695
+ }
696
+ }
697
+
698
+ // src/commands/auth.ts
699
+ import * as readline3 from "readline";
700
+ import chalk5 from "chalk";
701
+ async function login() {
702
+ console.log();
703
+ console.log(chalk5.bold("Codea CLI Login"));
704
+ console.log(chalk5.gray("Enter your Alia API key to get started."));
705
+ console.log(chalk5.gray("Get your API key at: ") + chalk5.cyan("https://alia.onl/settings/api"));
706
+ console.log();
707
+ const rl = readline3.createInterface({
708
+ input: process.stdin,
709
+ output: process.stdout
710
+ });
711
+ return new Promise((resolve2) => {
712
+ rl.question(chalk5.cyan("API Key: "), async (apiKey) => {
713
+ rl.close();
714
+ const trimmedKey = apiKey.trim();
715
+ if (!trimmedKey) {
716
+ printError("No API key provided.");
717
+ resolve2();
718
+ return;
719
+ }
720
+ printInfo("Validating API key...");
721
+ try {
722
+ const baseUrl = config.get("apiBaseUrl") || "https://api.alia.onl";
723
+ const response = await fetch(`${baseUrl}/codea/me`, {
724
+ headers: {
725
+ "Authorization": `Bearer ${trimmedKey}`
726
+ }
727
+ });
728
+ if (response.ok) {
729
+ const data = await response.json();
730
+ config.set("apiKey", trimmedKey);
731
+ console.log();
732
+ printSuccess(`Logged in successfully!`);
733
+ if (data.name) {
734
+ console.log(chalk5.gray(`Welcome, ${data.name}!`));
735
+ }
736
+ console.log();
737
+ console.log(chalk5.gray("Run ") + chalk5.cyan("codea") + chalk5.gray(" to start coding."));
738
+ } else {
739
+ printError("Invalid API key. Please check and try again.");
740
+ }
741
+ } catch (error) {
742
+ printError(`Could not validate API key: ${error.message}`);
743
+ }
744
+ resolve2();
745
+ });
746
+ });
747
+ }
748
+
749
+ // src/commands/sessions.ts
750
+ import chalk6 from "chalk";
751
+ import * as readline4 from "readline";
752
+ async function listSessions() {
753
+ const sessions = getSessions();
754
+ if (sessions.length === 0) {
755
+ printInfo("No saved sessions found.");
756
+ console.log(chalk6.gray("Start a new session with: ") + chalk6.cyan("codea"));
757
+ return;
758
+ }
759
+ console.log();
760
+ console.log(chalk6.bold("Recent Sessions"));
761
+ console.log(chalk6.gray("\u2500".repeat(60)));
762
+ sessions.slice(0, 10).forEach((session, index) => {
763
+ const date = new Date(session.updatedAt).toLocaleDateString();
764
+ const time = new Date(session.updatedAt).toLocaleTimeString();
765
+ const messageCount = session.messages?.length || 0;
766
+ const title = session.title.slice(0, 40) + (session.title.length > 40 ? "..." : "");
767
+ console.log(
768
+ chalk6.cyan(`${index + 1}.`) + " " + chalk6.white(title) + " " + chalk6.gray(`(${messageCount} msgs, ${date} ${time})`)
769
+ );
770
+ });
771
+ console.log();
772
+ console.log(chalk6.gray("Resume a session with: ") + chalk6.cyan("codea resume <number>"));
773
+ }
774
+ async function resumeSession(sessionId) {
775
+ const sessions = getSessions();
776
+ if (sessions.length === 0) {
777
+ printInfo("No saved sessions found.");
778
+ return;
779
+ }
780
+ let selectedSession;
781
+ if (sessionId) {
782
+ const index = parseInt(sessionId) - 1;
783
+ if (!isNaN(index) && index >= 0 && index < sessions.length) {
784
+ selectedSession = sessions[index];
785
+ } else {
786
+ selectedSession = getSession(sessionId);
787
+ }
788
+ if (!selectedSession) {
789
+ printError(`Session not found: ${sessionId}`);
790
+ return;
791
+ }
792
+ } else {
793
+ console.log();
794
+ console.log(chalk6.bold("Select a session to resume:"));
795
+ console.log();
796
+ sessions.slice(0, 10).forEach((session, index) => {
797
+ const date = new Date(session.updatedAt).toLocaleDateString();
798
+ const title = session.title.slice(0, 50) + (session.title.length > 50 ? "..." : "");
799
+ console.log(chalk6.cyan(` ${index + 1}.`) + " " + title + " " + chalk6.gray(`(${date})`));
800
+ });
801
+ console.log();
802
+ const rl = readline4.createInterface({
803
+ input: process.stdin,
804
+ output: process.stdout
805
+ });
806
+ return new Promise((resolve2) => {
807
+ rl.question(chalk6.cyan("Enter number: "), (answer) => {
808
+ rl.close();
809
+ const index = parseInt(answer) - 1;
810
+ if (isNaN(index) || index < 0 || index >= sessions.length) {
811
+ printError("Invalid selection.");
812
+ resolve2();
813
+ return;
814
+ }
815
+ selectedSession = sessions[index];
816
+ startRestoredSession(selectedSession).then(resolve2);
817
+ });
818
+ });
819
+ }
820
+ if (selectedSession) {
821
+ await startRestoredSession(selectedSession);
822
+ }
823
+ }
824
+ async function startRestoredSession(session) {
825
+ printInfo(`Resuming: ${session.title}`);
826
+ console.log();
827
+ for (const msg of session.messages || []) {
828
+ if (msg.role === "user") {
829
+ console.log(chalk6.cyan("\u276F ") + msg.content);
830
+ } else if (msg.role === "assistant") {
831
+ console.log(chalk6.magenta("\u2726 ") + msg.content.slice(0, 200) + (msg.content.length > 200 ? "..." : ""));
832
+ }
833
+ console.log();
834
+ }
835
+ console.log(chalk6.gray("\u2500".repeat(60)));
836
+ console.log(chalk6.gray("Session restored. Continue the conversation below."));
837
+ console.log();
838
+ const model = config.get("defaultModel") || "alia-v1-codea";
839
+ await startRepl({ model, context: true });
840
+ }
841
+
842
+ // src/index.ts
843
+ import chalk7 from "chalk";
844
+ var VERSION = "1.0.0";
845
+ var program = new Command();
846
+ var banner = `
847
+ ${chalk7.cyan(" ____ _ ")}
848
+ ${chalk7.cyan(" / ___|___ __| | ___ __ _ ")}
849
+ ${chalk7.cyan(" | | / _ \\ / _` |/ _ \\/ _` |")}
850
+ ${chalk7.cyan(" | |__| (_) | (_| | __/ (_| |")}
851
+ ${chalk7.cyan(" \\____\\___/ \\__,_|\\___|\\__,_|")}
852
+ ${chalk7.gray(" AI Coding Assistant by Alia")}
853
+ `;
854
+ program.name("codea").description("Codea CLI - AI coding assistant for your terminal").version(VERSION).hook("preAction", () => {
855
+ const command = program.args[0];
856
+ if (command !== "login" && command !== "help" && !config.get("apiKey")) {
857
+ console.log(chalk7.yellow("\nNo API key found. Please run `codea login` first.\n"));
858
+ process.exit(1);
859
+ }
860
+ });
861
+ program.command("chat", { isDefault: true }).description("Start an interactive chat session").option("-m, --model <model>", "Model to use (codea, codea-pro, codea-thinking)", "alia-v1-codea").option("--no-context", "Disable automatic codebase context").action(async (options) => {
862
+ console.log(banner);
863
+ await startRepl(options);
864
+ });
865
+ program.command("run <prompt>").alias("r").description("Run a single prompt and exit").option("-m, --model <model>", "Model to use", "alia-v1-codea").option("-y, --yes", "Auto-approve all file changes").option("--no-context", "Disable automatic codebase context").action(async (prompt, options) => {
866
+ await runPrompt(prompt, options);
867
+ });
868
+ program.command("login").description("Configure your Alia API key").action(async () => {
869
+ await login();
870
+ });
871
+ program.command("sessions").alias("s").description("List recent chat sessions").action(async () => {
872
+ await listSessions();
873
+ });
874
+ program.command("resume [sessionId]").description("Resume a previous chat session").action(async (sessionId) => {
875
+ console.log(banner);
876
+ await resumeSession(sessionId);
877
+ });
878
+ program.parse();