@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.
@@ -0,0 +1,127 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { exec } from 'child_process';
4
+ import { promisify } from 'util';
5
+
6
+ const execAsync = promisify(exec);
7
+
8
+ export function buildSystemMessage(model: string, codebaseContext: string): string {
9
+ 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.
10
+
11
+ ## Core Principles
12
+ - Be concise and direct. Avoid unnecessary explanations unless asked.
13
+ - Write clean, idiomatic, well-structured code following best practices.
14
+ - Consider edge cases, error handling, and security implications.
15
+ - Match the existing code style and conventions of the project.
16
+
17
+ ## Tools Available
18
+ You have powerful tools to interact with the user's workspace:
19
+
20
+ - **read_file**: Read file contents. Use to understand existing code before making changes.
21
+ - **write_file**: Create new files or completely rewrite existing ones.
22
+ - **edit_file**: Make precise, targeted changes to existing files. Preferred for small modifications.
23
+ - **list_files**: Explore directory structure. Use to understand project layout.
24
+ - **search_files**: Find text/patterns across the codebase. Great for finding usages, definitions, etc.
25
+ - **run_command**: Execute shell commands (build, test, git, npm, etc.)
26
+
27
+ ## Best Practices
28
+ 1. **Read before writing**: Always read relevant files before modifying them.
29
+ 2. **Minimal changes**: Make the smallest change necessary to accomplish the task.
30
+ 3. **Preserve style**: Match existing formatting, naming conventions, and patterns.
31
+ 4. **Explain when helpful**: For complex changes, briefly explain the approach.
32
+
33
+ ## Response Style
34
+ - Use markdown for formatting code blocks, lists, and emphasis.
35
+ - For code explanations, be thorough but focused.
36
+ - For code changes, be precise and action-oriented.
37
+ - If unsure about requirements, ask clarifying questions.`;
38
+
39
+ if (codebaseContext) {
40
+ systemMessage += `\n\n## Current Codebase Context\n${codebaseContext}`;
41
+ }
42
+
43
+ return systemMessage;
44
+ }
45
+
46
+ export async function getCodebaseContext(): Promise<string> {
47
+ const cwd = process.cwd();
48
+ const contextParts: string[] = [];
49
+
50
+ // Try to get project info from package.json
51
+ try {
52
+ const pkgPath = path.join(cwd, 'package.json');
53
+ const pkgContent = await fs.readFile(pkgPath, 'utf-8');
54
+ const pkg = JSON.parse(pkgContent);
55
+ contextParts.push(`Project: ${pkg.name || 'Unknown'} (${pkg.description || 'No description'})`);
56
+
57
+ if (pkg.dependencies) {
58
+ const deps = Object.keys(pkg.dependencies).slice(0, 10);
59
+ contextParts.push(`Dependencies: ${deps.join(', ')}${Object.keys(pkg.dependencies).length > 10 ? '...' : ''}`);
60
+ }
61
+ } catch {
62
+ // No package.json
63
+ }
64
+
65
+ // Try to get git info
66
+ try {
67
+ const { stdout: branch } = await execAsync('git branch --show-current', { cwd });
68
+ contextParts.push(`Git branch: ${branch.trim()}`);
69
+
70
+ const { stdout: status } = await execAsync('git status --porcelain', { cwd });
71
+ const changedFiles = status.trim().split('\n').filter(Boolean).length;
72
+ if (changedFiles > 0) {
73
+ contextParts.push(`Uncommitted changes: ${changedFiles} files`);
74
+ }
75
+ } catch {
76
+ // Not a git repo
77
+ }
78
+
79
+ // Get directory structure (limited)
80
+ try {
81
+ const files = await getRelevantFiles(cwd);
82
+ if (files.length > 0) {
83
+ contextParts.push(`\nKey files:\n${files.map(f => `- ${f}`).join('\n')}`);
84
+ }
85
+ } catch {
86
+ // Can't read directory
87
+ }
88
+
89
+ return contextParts.join('\n');
90
+ }
91
+
92
+ async function getRelevantFiles(dir: string, maxFiles: number = 20): Promise<string[]> {
93
+ const relevantFiles: string[] = [];
94
+ const relevantExtensions = ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java', '.md'];
95
+ const ignoreDirs = ['node_modules', '.git', 'dist', 'build', '__pycache__', '.next', 'coverage'];
96
+
97
+ async function walk(currentDir: string, depth: number = 0): Promise<void> {
98
+ if (depth > 3 || relevantFiles.length >= maxFiles) return;
99
+
100
+ try {
101
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
102
+
103
+ for (const entry of entries) {
104
+ if (relevantFiles.length >= maxFiles) break;
105
+
106
+ if (ignoreDirs.includes(entry.name)) continue;
107
+
108
+ const fullPath = path.join(currentDir, entry.name);
109
+ const relativePath = path.relative(dir, fullPath);
110
+
111
+ if (entry.isDirectory()) {
112
+ await walk(fullPath, depth + 1);
113
+ } else {
114
+ const ext = path.extname(entry.name);
115
+ if (relevantExtensions.includes(ext) || entry.name === 'README.md' || entry.name === 'package.json') {
116
+ relevantFiles.push(relativePath);
117
+ }
118
+ }
119
+ }
120
+ } catch {
121
+ // Can't read directory
122
+ }
123
+ }
124
+
125
+ await walk(dir);
126
+ return relevantFiles;
127
+ }
@@ -0,0 +1,153 @@
1
+ import chalk from 'chalk';
2
+ import * as readline from 'readline';
3
+
4
+ // Gradient colors for the banner (cyan to magenta)
5
+ const gradientColors = [
6
+ '#00d4ff', '#00c4ff', '#00b4ff', '#00a4ff',
7
+ '#4094ff', '#8084ff', '#c074ff', '#ff64ff'
8
+ ];
9
+
10
+ function hexToRgb(hex: string): [number, number, number] {
11
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
12
+ return result
13
+ ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]
14
+ : [255, 255, 255];
15
+ }
16
+
17
+ function colorize(text: string, colorIndex: number, totalColors: number): string {
18
+ const ratio = colorIndex / totalColors;
19
+ const startColor = hexToRgb(gradientColors[0]);
20
+ const endColor = hexToRgb(gradientColors[gradientColors.length - 1]);
21
+
22
+ const r = Math.round(startColor[0] + ratio * (endColor[0] - startColor[0]));
23
+ const g = Math.round(startColor[1] + ratio * (endColor[1] - startColor[1]));
24
+ const b = Math.round(startColor[2] + ratio * (endColor[2] - startColor[2]));
25
+
26
+ return chalk.rgb(r, g, b)(text);
27
+ }
28
+
29
+ // ASCII art banner with gradient
30
+ export function printBanner(): void {
31
+ const banner = [
32
+ ' ██████╗ ██████╗ ██████╗ ███████╗ █████╗ ',
33
+ ' ██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗',
34
+ ' ██║ ██║ ██║██║ ██║█████╗ ███████║',
35
+ ' ██║ ██║ ██║██║ ██║██╔══╝ ██╔══██║',
36
+ ' ╚██████╗╚██████╔╝██████╔╝███████╗██║ ██║',
37
+ ' ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝',
38
+ ];
39
+
40
+ console.log();
41
+ banner.forEach((line, i) => {
42
+ let coloredLine = '';
43
+ for (let j = 0; j < line.length; j++) {
44
+ coloredLine += colorize(line[j], j, line.length);
45
+ }
46
+ console.log(coloredLine);
47
+ });
48
+ console.log(chalk.gray(' by Alia'));
49
+ console.log();
50
+ }
51
+
52
+ export function printTips(): void {
53
+ console.log(chalk.white('Tips for getting started:'));
54
+ console.log(chalk.gray('1. Ask questions, edit files, or run commands.'));
55
+ console.log(chalk.gray('2. Be specific for the best results.'));
56
+ console.log(chalk.gray('3. ') + chalk.cyan('/help') + chalk.gray(' for more information.'));
57
+ console.log();
58
+ }
59
+
60
+ export function printPrompt(): void {
61
+ process.stdout.write(chalk.cyan('❯ '));
62
+ }
63
+
64
+ export function printToolExecution(tool: string, description: string): void {
65
+ const boxWidth = Math.min(process.stdout.columns || 80, 80);
66
+ const content = `${chalk.bold(tool)} ${description}`;
67
+ const paddedContent = ` ← ${content} `.padEnd(boxWidth - 4);
68
+
69
+ console.log();
70
+ console.log(chalk.gray('┌' + '─'.repeat(boxWidth - 2) + '┐'));
71
+ console.log(chalk.gray('│') + paddedContent + chalk.gray('│'));
72
+ console.log(chalk.gray('└' + '─'.repeat(boxWidth - 2) + '┘'));
73
+ }
74
+
75
+ export function printToolResult(success: boolean, result: string): void {
76
+ const status = success ? chalk.green('✓') : chalk.red('✗');
77
+ const preview = result.slice(0, 100).replace(/\n/g, ' ');
78
+ console.log(` ${status} ${chalk.gray(preview)}${result.length > 100 ? '...' : ''}`);
79
+ console.log();
80
+ }
81
+
82
+ let statusInterval: NodeJS.Timeout | null = null;
83
+ let startTime: number = 0;
84
+
85
+ export function showThinkingStatus(message: string): void {
86
+ startTime = Date.now();
87
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
88
+ let frameIndex = 0;
89
+
90
+ statusInterval = setInterval(() => {
91
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
92
+ const frame = frames[frameIndex % frames.length];
93
+ frameIndex++;
94
+
95
+ readline.clearLine(process.stdout, 0);
96
+ readline.cursorTo(process.stdout, 0);
97
+ process.stdout.write(
98
+ chalk.cyan(frame) + ' ' +
99
+ chalk.bold(message) +
100
+ chalk.gray(` (esc to cancel, ${elapsed}s)`)
101
+ );
102
+ }, 80);
103
+ }
104
+
105
+ export function hideThinkingStatus(): void {
106
+ if (statusInterval) {
107
+ clearInterval(statusInterval);
108
+ statusInterval = null;
109
+ readline.clearLine(process.stdout, 0);
110
+ readline.cursorTo(process.stdout, 0);
111
+ }
112
+ }
113
+
114
+ export function printStatusBar(cwd: string, model: string, contextPercent: number): void {
115
+ const width = process.stdout.columns || 80;
116
+ const cwdPart = chalk.cyan(shortenPath(cwd));
117
+ const modelPart = chalk.magenta(`${model} (${contextPercent}% context left)`);
118
+
119
+ const padding = width - stripAnsi(cwdPart).length - stripAnsi(modelPart).length - 4;
120
+ const spacer = ' '.repeat(Math.max(padding, 2));
121
+
122
+ console.log();
123
+ console.log(chalk.gray('─'.repeat(width)));
124
+ console.log(`${cwdPart}${spacer}${modelPart}`);
125
+ }
126
+
127
+ function shortenPath(p: string): string {
128
+ const home = process.env.HOME || '';
129
+ if (p.startsWith(home)) {
130
+ return '~' + p.slice(home.length);
131
+ }
132
+ return p;
133
+ }
134
+
135
+ function stripAnsi(str: string): string {
136
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
137
+ }
138
+
139
+ export function printAssistantPrefix(): void {
140
+ process.stdout.write(chalk.magenta('✦ '));
141
+ }
142
+
143
+ export function printError(message: string): void {
144
+ console.log(chalk.red('✗ Error: ') + message);
145
+ }
146
+
147
+ export function printSuccess(message: string): void {
148
+ console.log(chalk.green('✓ ') + message);
149
+ }
150
+
151
+ export function printInfo(message: string): void {
152
+ console.log(chalk.blue('ℹ ') + message);
153
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "outDir": "dist",
12
+ "rootDir": "src",
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "jsx": "react-jsx",
17
+ "resolveJsonModule": true
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist"]
21
+ }