aex-code 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Md. Abid Hasan Rafi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ ![AEX Code Cover](aex-code-cover.jpg)
2
+ # A E X - C O D E
3
+
4
+ AEX Code is a CLI application that functions as an advanced coding agent similar to Claude Code. It is optimized for free-tier usage via OpenRouter models and provides a clean UI with both interactive and command-based workflows. This project was developed by [Md. Abid Hasan Rafi](https://abidhasanrafi.github.io) and powered by [AI Extension](https://aiextension.org).
5
+
6
+ ## Features
7
+
8
+ - **Code Generation and Explanation:** Interactive terminal sessions within your workspace.
9
+ - **File Management:** Create, review, and edit files with context-aware AI assistance.
10
+ - **Safe Shell Commands:** Execute shell commands with controlled output handling.
11
+ - **Multi-file Context Processing:** Build rich context from your codebase across multiple files.
12
+ - **Configurable Models:** Use OpenRouter API keys to select preferred models. Defaults to `qwen/qwen3-coder:free`.
13
+
14
+ ## Requirements
15
+
16
+ - Node.js >= 16
17
+
18
+ ## Setup and Configuration
19
+
20
+ 1. Install globally:
21
+ ```bash
22
+ npm install -g aex-code
23
+ ```
24
+
25
+ 2. Configure your OpenRouter API key:
26
+ ```bash
27
+ aex-code config --set-key "sk-or-v1-..."
28
+ ```
29
+
30
+ 3. Set your preferred model (optional):
31
+ ```bash
32
+ aex-code config --set-model "anthropic/claude-3.5-sonnet:beta"
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ### Interactive Shell
38
+
39
+ Start an interactive session with the AI agent. The session maintains context and streams responses.
40
+
41
+ ```bash
42
+ aex-code chat
43
+ ```
44
+
45
+ ### Analyze and Edit a File
46
+
47
+ Analyze and modify local files using AI.
48
+
49
+ ```bash
50
+ aex-code edit src/index.js
51
+ ```
52
+
53
+ ### Initialize Configuration
54
+
55
+ ```bash
56
+ aex-code init
57
+ ```
58
+
59
+ ### Run Commands
60
+
61
+ ```bash
62
+ aex-code run "npm install"
63
+ ```
64
+
65
+ ## Development and Contribution
66
+
67
+ The project follows a modular architecture where commands are organized under `src/commands`, making it easy to extend and maintain.
68
+
69
+ ## License
70
+
71
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import '../src/index.js';
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "aex-code",
3
+ "version": "1.0.0",
4
+ "description": "Next-Gen OpenRouter Coding Agent CLI - Powered by AI Extension",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "start": "node ./bin/aex-code.js"
8
+ },
9
+ "keywords": [
10
+ "aex-code",
11
+ "ai",
12
+ "cli",
13
+ "coding-agent",
14
+ "agent",
15
+ "openrouter",
16
+ "llm"
17
+ ],
18
+ "author": "Your Name / AI Extension",
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "dependencies": {
22
+ "boxen": "^7.1.1",
23
+ "chalk": "^4.1.2",
24
+ "commander": "^14.0.3",
25
+ "dotenv": "^17.3.1",
26
+ "figlet": "^1.11.0",
27
+ "fs-extra": "^11.3.4",
28
+ "inquirer": "^8.2.7",
29
+ "openai": "^6.32.0",
30
+ "ora": "^5.4.1"
31
+ },
32
+ "bin": {
33
+ "aex-code": "bin/aex-code.js"
34
+ },
35
+ "engines": {
36
+ "node": ">=16.0.0"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/yourusername/aex-code.git"
41
+ }
42
+ }
@@ -0,0 +1,91 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import boxen from 'boxen';
7
+ import { createClient, getDefaultModel, debugApiKey } from '../utils/openrouter.js';
8
+
9
+ const SYSTEM_PROMPT = `You are AEX Code, an advanced coding agent. You provide short, actionable coding assistance.
10
+ If asked to edit a file, provide the full file contents or explicitly outline what to change in the file.
11
+ Try to use <tool_call> JSON format for file writes if possible.
12
+ `;
13
+
14
+ let conversationHistory = [];
15
+
16
+ export async function chatCommand() {
17
+ console.clear();
18
+ const chatHeader = boxen(chalk.greenBright.bold('A E X C O D E'), {
19
+ padding: 1, margin: 1, borderStyle: 'bold', borderColor: 'green'
20
+ });
21
+ console.log(chatHeader);
22
+ console.log(chalk.green(` ■ Target Model : ${chalk.gray(getDefaultModel())}`));
23
+ console.log(chalk.green(` ■ Exit Command : ${chalk.gray("'exit'")}\n`));
24
+
25
+ let client;
26
+ try {
27
+ client = createClient();
28
+ } catch (e) {
29
+ console.log(chalk.red(e.message));
30
+ process.exit(1);
31
+ }
32
+
33
+ conversationHistory.push({ role: 'system', content: SYSTEM_PROMPT });
34
+
35
+ while (true) {
36
+ process.stdout.write('\n');
37
+ const { userInput } = await inquirer.prompt([
38
+ {
39
+ type: 'input',
40
+ name: 'userInput',
41
+ message: chalk.bold.green('■ USER : '),
42
+ }
43
+ ]);
44
+
45
+ if (userInput.trim().toLowerCase() === 'exit') {
46
+ console.log(chalk.green('\n■ SESSION TERMINATED.'));
47
+ break;
48
+ }
49
+
50
+ conversationHistory.push({ role: 'user', content: userInput });
51
+
52
+ process.stdout.write('\n');
53
+ const spinner = ora({
54
+ text: chalk.italic.green('AEX PROCESSSING...'),
55
+ color: 'green',
56
+ }).start();
57
+
58
+ try {
59
+ const response = await client.chat.completions.create({
60
+ model: getDefaultModel(),
61
+ messages: conversationHistory,
62
+ stream: true,
63
+ });
64
+
65
+ spinner.stop();
66
+ process.stdout.write(`${chalk.bgGreen.black.bold(' ■ AEX AGENT ')} `);
67
+
68
+ let fullContent = '';
69
+ for await (const chunk of response) {
70
+ const text = chunk.choices[0]?.delta?.content || '';
71
+ process.stdout.write(chalk.green(text));
72
+ fullContent += text;
73
+ }
74
+ console.log('\n');
75
+ conversationHistory.push({ role: 'assistant', content: fullContent });
76
+
77
+ } catch (e) {
78
+ spinner.stop();
79
+ console.log(chalk.bgRed.white.bold(' ■ ERROR '));
80
+ if (e.status === 429) {
81
+ console.log(chalk.red(`\nRate Limit or Provider Error (429): The active model (${getDefaultModel()}) is currently overloaded.`));
82
+ console.log(chalk.yellow(`Tip: Try swapping your model using:\naex-code config --set-model "google/gemini-2.0-flash-lite-preview-02-05:free"\n`));
83
+ } else if (e.status === 401) {
84
+ console.log(chalk.red(`\nUnauthorized (401): Your API key is invalid or not recognized by OpenRouter.`));
85
+ } else {
86
+ console.log(chalk.red(`\nFailed to reach OpenRouter API: ${e.message}\n`));
87
+ }
88
+ conversationHistory.pop();
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,21 @@
1
+ import chalk from 'chalk';
2
+ import { saveConfig } from '../utils/configManager.js';
3
+
4
+ export const configCommand = (options) => {
5
+ if (options.setKey) {
6
+ // Automatically strip accidental quotes just in case
7
+ const cleanKey = options.setKey.replace(/^['"]|['"]$/g, '');
8
+ saveConfig({ OPENROUTER_API_KEY: cleanKey });
9
+ console.log(chalk.green('✔ OpenRouter API Key configured successfully.'));
10
+ }
11
+
12
+ if (options.setModel) {
13
+ const cleanModel = options.setModel.replace(/^['"]|['"]$/g, '');
14
+ saveConfig({ OPENROUTER_DEFAULT_MODEL: cleanModel });
15
+ console.log(chalk.green(`✔ Default Model set to ${cleanModel}.`));
16
+ }
17
+
18
+ if (!options.setKey && !options.setModel) {
19
+ console.log(chalk.yellow('Usage: aex-code config --set-key <key> or --set-model <model>'));
20
+ }
21
+ };
@@ -0,0 +1,33 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import ora from 'ora';
5
+ import { createClient, getDefaultModel } from '../utils/openrouter.js';
6
+
7
+ export async function editCommand(file, options) {
8
+ if (!fs.existsSync(file)) {
9
+ console.log(chalk.red(`File ${file} does not exist.`));
10
+ process.exit(1);
11
+ }
12
+
13
+ console.log(chalk.blue(`Suggesting edits for ${file}...`));
14
+ const content = fs.readFileSync(file, 'utf-8');
15
+
16
+ const client = createClient();
17
+ const spinner = ora('AEX Code is analyzing the file...').start();
18
+
19
+ try {
20
+ const response = await client.chat.completions.create({
21
+ model: getDefaultModel(),
22
+ messages: [
23
+ { role: 'system', content: 'You are AEX Code. Please evaluate this code file and suggest improvements. Keep output concise.' },
24
+ { role: 'user', content: `File name: ${file}\n\nCode:\n\`\`\`\n${content}\n\`\`\`` }
25
+ ]
26
+ });
27
+ spinner.stop();
28
+ console.log(chalk.green(response.choices[0].message.content));
29
+ } catch (e) {
30
+ spinner.fail('Error analyzing file.');
31
+ console.log(chalk.red(e.message));
32
+ }
33
+ }
@@ -0,0 +1,16 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export function initCommand() {
6
+ console.log(chalk.green('Initializing AEX Code project...'));
7
+ const configPath = path.join(process.cwd(), '.aex-code');
8
+
9
+ if (!fs.existsSync(configPath)) {
10
+ fs.mkdirSync(configPath);
11
+ fs.writeFileSync(path.join(configPath, 'rules.md'), '# AEX Code Project Rules\n\nAdd project-specific rules here.');
12
+ console.log(chalk.green('✔ Initialized .aex-code directory securely.'));
13
+ } else {
14
+ console.log(chalk.yellow('AEX Code project already initialized in this directory.'));
15
+ }
16
+ }
@@ -0,0 +1,21 @@
1
+ import { exec } from 'child_process';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+
5
+ export function runCommand(cmdString) {
6
+ const spinner = ora(`Running: ${cmdString}`).start();
7
+
8
+ exec(cmdString, (error, stdout, stderr) => {
9
+ spinner.stop();
10
+ if (error) {
11
+ console.error(chalk.red(`Error executing command: ${error.message}`));
12
+ return;
13
+ }
14
+ if (stderr) {
15
+ console.error(chalk.yellow(`Output:\n${stderr}`));
16
+ }
17
+ if (stdout) {
18
+ console.log(chalk.green(`Result:\n${stdout}`));
19
+ }
20
+ });
21
+ }
package/src/index.js ADDED
@@ -0,0 +1,72 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import figlet from 'figlet';
4
+ import boxen from 'boxen';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+
9
+ import { initCommand } from './commands/init.js';
10
+ import { chatCommand } from './commands/chat.js';
11
+ import { configCommand } from './commands/config.js';
12
+ import { editCommand } from './commands/edit.js';
13
+ import { runCommand } from './commands/run.js';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+
18
+ const packageJson = JSON.parse(
19
+ fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8')
20
+ );
21
+
22
+ const program = new Command();
23
+
24
+ const bannerText = figlet.textSync('AEX Code', { font: 'ANSI Shadow', horizontalLayout: 'fitted' });
25
+ const coloredBanner = chalk.greenBright(bannerText);
26
+ const rawTagline = ' NEXT-GEN OPENROUTER CODING AGENT - POWERED BY AI EXTENSION ';
27
+
28
+ const finalBanner = boxen(`${coloredBanner}\n\n${chalk.bgGreen.black.bold(rawTagline)}`, {
29
+ padding: 1,
30
+ margin: 1,
31
+ borderStyle: 'bold',
32
+ borderColor: 'green',
33
+ textAlignment: 'center',
34
+ });
35
+
36
+ program
37
+ .name('aex-code')
38
+ .description(finalBanner)
39
+ .version(packageJson.version);
40
+
41
+ program
42
+ .command('init')
43
+ .description('Initialize AEX Code in the current workspace')
44
+ .action(initCommand);
45
+
46
+ program
47
+ .command('chat')
48
+ .description('Start an interactive chat session with AEX Code')
49
+ .action(chatCommand);
50
+
51
+ program
52
+ .command('config')
53
+ .description('Configure API keys and default models for AEX Code')
54
+ .option('--set-key <key>', 'Set OpenRouter API key')
55
+ .option('--set-model <model>', 'Set default OpenRouter model')
56
+ .action(configCommand);
57
+
58
+ program
59
+ .command('edit <file>')
60
+ .description('Edit or analyze a file locally')
61
+ .action(editCommand);
62
+
63
+ program
64
+ .command('run <cmd>')
65
+ .description('Run a shell command safely')
66
+ .action(runCommand);
67
+
68
+ program.parse(process.argv);
69
+
70
+ if (!process.argv.slice(2).length) {
71
+ program.outputHelp();
72
+ }
@@ -0,0 +1,25 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ const CONFIG_PATH = path.join(os.homedir(), '.aex-code.json');
6
+
7
+ export const loadConfig = () => {
8
+ if (fs.existsSync(CONFIG_PATH)) {
9
+ try {
10
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
11
+ return JSON.parse(raw);
12
+ } catch (e) {
13
+ return {};
14
+ }
15
+ }
16
+ return {};
17
+ };
18
+
19
+ export const saveConfig = (newConfig) => {
20
+ const config = { ...loadConfig(), ...newConfig };
21
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
22
+ return config;
23
+ };
24
+
25
+ export const getConfig = (key) => loadConfig()[key];
@@ -0,0 +1,30 @@
1
+ import OpenAI from 'openai';
2
+ import { getConfig } from './configManager.js';
3
+ import dotenv from 'dotenv';
4
+ dotenv.config({ path: '.env.local', silent: true });
5
+
6
+ export const createClient = () => {
7
+ const apiKey = process.env.OPENROUTER_API_KEY || getConfig('OPENROUTER_API_KEY');
8
+ if (!apiKey) {
9
+ throw new Error('OpenRouter API Key not found. Please run "aex-code config" to setup your key.');
10
+ }
11
+
12
+ return new OpenAI({
13
+ baseURL: 'https://openrouter.ai/api/v1',
14
+ apiKey: apiKey.trim(),
15
+ defaultHeaders: {
16
+ 'HTTP-Referer': 'https://github.com/aex-code',
17
+ 'X-Title': 'AEX Code CLI',
18
+ }
19
+ });
20
+ };
21
+
22
+ export const debugApiKey = () => {
23
+ const key = process.env.OPENROUTER_API_KEY || getConfig('OPENROUTER_API_KEY');
24
+ if (!key) return null;
25
+ return `${key.substring(0, 10)}...${key.slice(-4)}`;
26
+ };
27
+
28
+ export const getDefaultModel = () => {
29
+ return process.env.OPENROUTER_DEFAULT_MODEL || getConfig('OPENROUTER_DEFAULT_MODEL') || 'qwen/qwen-2.5-coder-32b-instruct:free';
30
+ };