@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/README.md +131 -0
- package/dist/api-X2G5QROW.js +10 -0
- package/dist/chunk-SVPL4GNV.js +230 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +878 -0
- package/package.json +52 -0
- package/src/commands/auth.ts +66 -0
- package/src/commands/repl.ts +309 -0
- package/src/commands/run.ts +177 -0
- package/src/commands/sessions.ts +122 -0
- package/src/index.ts +87 -0
- package/src/tools/executor.ts +191 -0
- package/src/utils/api.ts +213 -0
- package/src/utils/config.ts +70 -0
- package/src/utils/context.ts +127 -0
- package/src/utils/ui.ts +153 -0
- package/tsconfig.json +21 -0
|
@@ -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
|
+
}
|
package/src/utils/ui.ts
ADDED
|
@@ -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
|
+
}
|