aidx 1.0.2 → 1.0.4
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 +191 -160
- package/package.json +6 -16
- package/readme.md +5 -5
package/dist/index.js
CHANGED
|
@@ -1,27 +1,97 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
2
|
import { checkbox, confirm } from '@inquirer/prompts';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { statSync } from 'fs';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import fsPromises from 'fs/promises';
|
|
7
5
|
import path from 'path';
|
|
8
6
|
import clipboardy from 'clipboardy';
|
|
9
|
-
import chalk from 'chalk';
|
|
10
|
-
import { encode } from 'gpt-tokenizer';
|
|
11
7
|
import * as Diff from 'diff';
|
|
12
|
-
import { isBinaryFile } from 'isbinaryfile';
|
|
13
8
|
// --- CONFIGURATION ---
|
|
14
9
|
const METADATA = {
|
|
15
10
|
name: "aidx",
|
|
16
11
|
description: "A CLI bridge between local code and LLMs.",
|
|
17
12
|
author: "rx76d",
|
|
18
|
-
version: "1.0.
|
|
13
|
+
version: "1.0.4",
|
|
19
14
|
license: "MIT",
|
|
20
15
|
github: "https://github.com/rx76d/aidx"
|
|
21
16
|
};
|
|
22
17
|
const CONFIG_FILE = '.aidxrc.json';
|
|
23
18
|
const MAX_FILE_SIZE = 1.5 * 1024 * 1024; // 1.5MB Limit
|
|
24
19
|
const SECRET_REGEX = /(?:sk-[a-zA-Z0-9]{32,})|(?:AKIA[0-9A-Z]{16})|(?:[a-zA-Z0-9+/]{40,}=)/;
|
|
20
|
+
// --- UTILS: NATIVE COLORS ---
|
|
21
|
+
const colors = {
|
|
22
|
+
reset: "\x1b[0m",
|
|
23
|
+
red: (t) => `\x1b[31m${t}\x1b[0m`,
|
|
24
|
+
green: (t) => `\x1b[32m${t}\x1b[0m`,
|
|
25
|
+
yellow: (t) => `\x1b[33m${t}\x1b[0m`,
|
|
26
|
+
blue: (t) => `\x1b[34m${t}\x1b[0m`,
|
|
27
|
+
cyan: (t) => `\x1b[36m${t}\x1b[0m`,
|
|
28
|
+
dim: (t) => `\x1b[2m${t}\x1b[0m`,
|
|
29
|
+
bold: (t) => `\x1b[1m${t}\x1b[0m`,
|
|
30
|
+
bgBlue: (t) => `\x1b[44m${t}\x1b[0m`,
|
|
31
|
+
bgRed: (t) => `\x1b[41m${t}\x1b[0m`,
|
|
32
|
+
bgGreen: (t) => `\x1b[42m\x1b[30m${t}\x1b[0m`
|
|
33
|
+
};
|
|
34
|
+
// --- UTILS: NATIVE FILE SCANNER ---
|
|
35
|
+
async function scanFiles(startDir) {
|
|
36
|
+
const ignoredFolders = new Set([
|
|
37
|
+
'node_modules', '.git', '.vscode', '.idea', 'dist', 'build', '.next',
|
|
38
|
+
'__pycache__', 'venv', 'env', 'target', 'bin', 'obj', 'vendor',
|
|
39
|
+
'Application Data', 'Cookies', 'Local Settings', 'Recent', 'Start Menu'
|
|
40
|
+
]);
|
|
41
|
+
const ignoredExts = new Set([
|
|
42
|
+
'.lock', '.log', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico',
|
|
43
|
+
'.pdf', '.zip', '.tar', '.gz', '.exe', '.dll', '.iso', '.class', '.pyc'
|
|
44
|
+
]);
|
|
45
|
+
const results = [];
|
|
46
|
+
async function walk(dir) {
|
|
47
|
+
try {
|
|
48
|
+
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const fullPath = path.join(dir, entry.name);
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
if (!ignoredFolders.has(entry.name))
|
|
53
|
+
await walk(fullPath);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
57
|
+
if (!ignoredExts.has(ext)) {
|
|
58
|
+
results.push(path.relative(startDir, fullPath));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (e) { /* Suppress EPERM */ }
|
|
64
|
+
}
|
|
65
|
+
await walk(startDir);
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
// --- UTILS: HELPERS ---
|
|
69
|
+
function estimateTokens(text) {
|
|
70
|
+
return Math.ceil(text.length / 4);
|
|
71
|
+
}
|
|
72
|
+
function isBinary(buffer) {
|
|
73
|
+
if (buffer.length === 0)
|
|
74
|
+
return false;
|
|
75
|
+
const len = Math.min(buffer.length, 1000);
|
|
76
|
+
for (let i = 0; i < len; i++)
|
|
77
|
+
if (buffer[i] === 0x00)
|
|
78
|
+
return true;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
async function getBackupStatus() {
|
|
82
|
+
try {
|
|
83
|
+
const configPath = path.resolve(process.cwd(), CONFIG_FILE);
|
|
84
|
+
const data = await fsPromises.readFile(configPath, 'utf-8');
|
|
85
|
+
return !!JSON.parse(data).backup;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function setBackupStatus(enabled) {
|
|
92
|
+
await fsPromises.writeFile(path.resolve(process.cwd(), CONFIG_FILE), JSON.stringify({ backup: enabled }, null, 2));
|
|
93
|
+
}
|
|
94
|
+
// --- PROTOCOLS ---
|
|
25
95
|
const SYSTEM_HEADER = `
|
|
26
96
|
================================================================
|
|
27
97
|
SYSTEM PROMPT: STRICT CODE MODE
|
|
@@ -46,59 +116,59 @@ console.log("Full code here...");
|
|
|
46
116
|
</file>
|
|
47
117
|
================================================================
|
|
48
118
|
`;
|
|
49
|
-
// ---
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
119
|
+
// --- MAIN CLI LOGIC ---
|
|
120
|
+
async function main() {
|
|
121
|
+
const args = process.argv.slice(2);
|
|
122
|
+
const command = args[0] || 'menu';
|
|
123
|
+
switch (command) {
|
|
124
|
+
case 'copy':
|
|
125
|
+
await runCopy();
|
|
126
|
+
break;
|
|
127
|
+
case 'apply':
|
|
128
|
+
await runApply();
|
|
129
|
+
break;
|
|
130
|
+
case 'backup':
|
|
131
|
+
await runBackup(args[1]);
|
|
132
|
+
break;
|
|
133
|
+
case 'stl':
|
|
134
|
+
runSTL();
|
|
135
|
+
break;
|
|
136
|
+
case 'menu':
|
|
137
|
+
case '--help':
|
|
138
|
+
case '-h':
|
|
139
|
+
await showMenu();
|
|
140
|
+
break;
|
|
141
|
+
case '-v':
|
|
142
|
+
case '--version':
|
|
143
|
+
console.log(METADATA.version);
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
console.log(colors.red(`\nError: Unknown command '${command}'`));
|
|
147
|
+
console.log(`Run ${colors.cyan('npx aidx')} for help.\n`);
|
|
148
|
+
process.exit(1);
|
|
61
149
|
}
|
|
62
|
-
catch (e) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async function setBackupStatus(enabled) {
|
|
67
|
-
const configPath = path.resolve(process.cwd(), CONFIG_FILE);
|
|
68
|
-
await fs.writeFile(configPath, JSON.stringify({ backup: enabled }, null, 2));
|
|
69
150
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.name(METADATA.name)
|
|
73
|
-
.description(METADATA.description)
|
|
74
|
-
.version(METADATA.version);
|
|
75
|
-
// --- ROOT COMMAND (DASHBOARD) ---
|
|
76
|
-
program.action(async () => {
|
|
151
|
+
// --- ACTIONS ---
|
|
152
|
+
async function showMenu() {
|
|
77
153
|
const backupEnabled = await getBackupStatus();
|
|
78
|
-
console.log('\n' +
|
|
79
|
-
console.log(
|
|
80
|
-
console.log(`${
|
|
81
|
-
console.log(`${
|
|
82
|
-
console.log(`${
|
|
83
|
-
console.log(
|
|
84
|
-
console.log(chalk.dim('----------------------------------------'));
|
|
154
|
+
console.log('\n' + colors.bgBlue(` ${METADATA.name.toUpperCase()} `) + colors.dim(` v${METADATA.version}`));
|
|
155
|
+
console.log(colors.dim('----------------------------------------'));
|
|
156
|
+
console.log(`${colors.bold('Description:')} ${METADATA.description}`);
|
|
157
|
+
console.log(`${colors.bold('Author:')} ${METADATA.author}`);
|
|
158
|
+
console.log(`${colors.bold('Backups:')} ${backupEnabled ? colors.green('ENABLED') : colors.dim('DISABLED')}`);
|
|
159
|
+
console.log(colors.dim('----------------------------------------'));
|
|
85
160
|
console.log('\nAvailable Commands:');
|
|
86
|
-
console.log(` ${
|
|
87
|
-
console.log(` ${
|
|
88
|
-
console.log(` ${
|
|
89
|
-
console.log(` ${
|
|
90
|
-
console.log(` ${
|
|
91
|
-
console.log(`\nRun ${
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.
|
|
96
|
-
.description('Show safe token limits for AI models')
|
|
97
|
-
.action(() => {
|
|
98
|
-
console.log('\n' + chalk.bold('AI Model Context Limits (2025 Reference)'));
|
|
99
|
-
console.log(chalk.dim('--------------------------------------------------'));
|
|
161
|
+
console.log(` ${colors.cyan('npx aidx copy')} Select files and copy context`);
|
|
162
|
+
console.log(` ${colors.cyan('npx aidx apply')} Apply AI changes to disk`);
|
|
163
|
+
console.log(` ${colors.cyan('npx aidx backup --on')} Enable auto-backups`);
|
|
164
|
+
console.log(` ${colors.cyan('npx aidx backup --off')} Disable auto-backups`);
|
|
165
|
+
console.log(` ${colors.cyan('npx aidx stl')} Show AI token limits`);
|
|
166
|
+
console.log(`\nRun ${colors.dim('npx aidx copy')} to start.\n`);
|
|
167
|
+
}
|
|
168
|
+
function runSTL() {
|
|
169
|
+
console.log('\n' + colors.bold('AI Model Context Limits (2025 Reference)'));
|
|
170
|
+
console.log(colors.dim('--------------------------------------------------'));
|
|
100
171
|
const models = [
|
|
101
|
-
// HUGE (≈ 1M+ tokens)
|
|
102
172
|
{ name: "Gemini 3 Pro", limit: "2,000,000+", type: "Huge" },
|
|
103
173
|
{ name: "Gemini 2.5 Pro", limit: "1,000,000+", type: "Huge" },
|
|
104
174
|
{ name: "Gemini 2.5 Flash", limit: "1,000,000+", type: "Huge" },
|
|
@@ -106,77 +176,48 @@ program
|
|
|
106
176
|
{ name: "Llama 4 Maverick", limit: "1,000,000+", type: "Huge" },
|
|
107
177
|
{ name: "Qwen 2.5 1M", limit: "1,000,000+", type: "Huge" },
|
|
108
178
|
{ name: "GPT-4.1", limit: "1,000,000+", type: "Huge" },
|
|
109
|
-
// LARGE (≈ 200K–500K tokens)
|
|
110
179
|
{ name: "ChatGPT-5", limit: "200,000+", type: "Large" },
|
|
111
180
|
{ name: "Claude 4.5 Sonnet", limit: "200,000+", type: "Large" },
|
|
112
181
|
{ name: "Claude 4.5 Opus", limit: "200,000+", type: "Large" },
|
|
113
182
|
{ name: "Grok 4", limit: "256,000", type: "Large" },
|
|
114
183
|
{ name: "Cohere Command A", limit: "256,000", type: "Large" },
|
|
115
|
-
// MEDIUM (≈ 100K–150K tokens)
|
|
116
184
|
{ name: "GPT-4o", limit: "128,000", type: "Medium" },
|
|
117
185
|
{ name: "Llama 4 405B", limit: "128,000", type: "Medium" },
|
|
118
186
|
{ name: "DeepSeek V3", limit: "128,000", type: "Medium" },
|
|
119
187
|
{ name: "Grok 3", limit: "128,000", type: "Medium" },
|
|
120
188
|
{ name: "GPT-5 Mini", limit: "128,000", type: "Medium" },
|
|
121
|
-
// SMALL (< 50K tokens)
|
|
122
189
|
{ name: "ChatGPT (Free)", limit: "~8,000", type: "Small" },
|
|
123
190
|
{ name: "Claude Haiku", limit: "~16,000", type: "Small" },
|
|
124
191
|
];
|
|
125
|
-
console.log(
|
|
126
|
-
console.log(
|
|
192
|
+
console.log(colors.cyan('Model Name'.padEnd(20)) + colors.yellow('Max Tokens'.padEnd(15)) + colors.dim('Category'));
|
|
193
|
+
console.log(colors.dim('--------------------------------------------------'));
|
|
127
194
|
models.forEach(m => {
|
|
128
|
-
const color = m.type === "Huge" ?
|
|
129
|
-
console.log(m.name.padEnd(20) + color(m.limit.padEnd(15)) +
|
|
195
|
+
const color = m.type === "Huge" ? colors.green : m.type === "Large" ? colors.blue : colors.dim;
|
|
196
|
+
console.log(m.name.padEnd(20) + color(m.limit.padEnd(15)) + colors.dim(m.type));
|
|
130
197
|
});
|
|
131
|
-
console.log(
|
|
132
|
-
console.log(
|
|
133
|
-
console.log(
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.command('backup')
|
|
138
|
-
.description('Configure automatic backups')
|
|
139
|
-
.option('--on', 'Enable backups')
|
|
140
|
-
.option('--off', 'Disable backups')
|
|
141
|
-
.action(async (options) => {
|
|
142
|
-
if (options.on) {
|
|
198
|
+
console.log(colors.dim('--------------------------------------------------'));
|
|
199
|
+
console.log(colors.dim('* 1,000 tokens ≈ 750 words of code/text.'));
|
|
200
|
+
console.log(colors.dim('* Estimates based on latest model specs.\n'));
|
|
201
|
+
}
|
|
202
|
+
async function runBackup(flag) {
|
|
203
|
+
if (flag === '--on') {
|
|
143
204
|
await setBackupStatus(true);
|
|
144
|
-
console.log(
|
|
205
|
+
console.log(colors.green(`\n✔ Backups enabled. Settings saved to ${CONFIG_FILE}`));
|
|
145
206
|
}
|
|
146
|
-
else if (
|
|
207
|
+
else if (flag === '--off') {
|
|
147
208
|
await setBackupStatus(false);
|
|
148
|
-
console.log(
|
|
209
|
+
console.log(colors.yellow(`\nBackups disabled.`));
|
|
149
210
|
}
|
|
150
211
|
else {
|
|
151
|
-
|
|
152
|
-
console.log(`\nCurrent Backup Status: ${status ? chalk.green('ENABLED') : chalk.red('DISABLED')}`);
|
|
212
|
+
console.log(colors.red('Error: Use --on or --off'));
|
|
153
213
|
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.
|
|
158
|
-
.description('Select files and copy to clipboard')
|
|
159
|
-
.action(async () => {
|
|
160
|
-
console.log(chalk.blue('Scanning directory...'));
|
|
161
|
-
const files = await glob(['**/*'], {
|
|
162
|
-
ignore: [
|
|
163
|
-
// Windows System Junk (CRITICAL FOR STABILITY)
|
|
164
|
-
'**/Application Data/**', '**/Cookies/**', '**/Local Settings/**', '**/Recent/**', '**/Start Menu/**',
|
|
165
|
-
// Dev Junk
|
|
166
|
-
'**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**', '**/.vscode/**',
|
|
167
|
-
'**/__pycache__/**', '**/venv/**', '**/target/**', '**/bin/**', '**/obj/**',
|
|
168
|
-
'**/vendor/**',
|
|
169
|
-
// File Types
|
|
170
|
-
'**/*.lock', '**/*.log', '**/*.png', '**/*.exe', '**/*.dll', '**/*.zip', '**/*.tar', '**/*.gz'
|
|
171
|
-
],
|
|
172
|
-
onlyFiles: true,
|
|
173
|
-
dot: true,
|
|
174
|
-
suppressErrors: true, // Fixes Windows EPERM crashes
|
|
175
|
-
followSymbolicLinks: false // Fixes Infinite Loops
|
|
176
|
-
});
|
|
214
|
+
}
|
|
215
|
+
async function runCopy() {
|
|
216
|
+
console.log(colors.blue('Scanning directory...'));
|
|
217
|
+
const files = await scanFiles(process.cwd());
|
|
177
218
|
if (files.length === 0)
|
|
178
|
-
return console.log(
|
|
179
|
-
let selectedFiles
|
|
219
|
+
return console.log(colors.red('Error: No files found.'));
|
|
220
|
+
let selectedFiles;
|
|
180
221
|
try {
|
|
181
222
|
selectedFiles = await checkbox({
|
|
182
223
|
message: 'Select files to send to AI:',
|
|
@@ -185,68 +226,62 @@ program
|
|
|
185
226
|
});
|
|
186
227
|
}
|
|
187
228
|
catch (e) {
|
|
188
|
-
return console.log(
|
|
229
|
+
return console.log(colors.yellow('\nSelection cancelled.'));
|
|
189
230
|
}
|
|
190
231
|
if (selectedFiles.length === 0)
|
|
191
|
-
return console.log(
|
|
232
|
+
return console.log(colors.yellow('No files selected.'));
|
|
192
233
|
let output = SYSTEM_HEADER + "\n";
|
|
193
234
|
let skippedCount = 0;
|
|
194
|
-
console.log(
|
|
235
|
+
console.log(colors.dim('Reading files...'));
|
|
195
236
|
for (const file of selectedFiles) {
|
|
196
237
|
try {
|
|
197
|
-
|
|
198
|
-
const stats = statSync(file);
|
|
238
|
+
const stats = fs.statSync(file);
|
|
199
239
|
if (stats.size > MAX_FILE_SIZE) {
|
|
200
|
-
console.log(
|
|
240
|
+
console.log(colors.yellow(`⚠ Skipped large file (>1.5MB): ${file}`));
|
|
201
241
|
skippedCount++;
|
|
202
242
|
continue;
|
|
203
243
|
}
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
console.log(
|
|
244
|
+
const buffer = await fsPromises.readFile(file);
|
|
245
|
+
if (isBinary(buffer)) {
|
|
246
|
+
console.log(colors.yellow(`⚠ Skipped binary file: ${file}`));
|
|
207
247
|
skippedCount++;
|
|
208
248
|
continue;
|
|
209
249
|
}
|
|
210
|
-
const content =
|
|
211
|
-
// 3. Check Secrets
|
|
250
|
+
const content = buffer.toString('utf-8');
|
|
212
251
|
if (file.includes('.env') || SECRET_REGEX.test(content)) {
|
|
213
|
-
console.log(
|
|
252
|
+
console.log(colors.red(`\n🛑 SECURITY ALERT: Secrets detected in ${file}`));
|
|
214
253
|
skippedCount++;
|
|
215
254
|
continue;
|
|
216
255
|
}
|
|
217
256
|
output += `File: ${file}\n\`\`\`\n${content}\n\`\`\`\n\n`;
|
|
218
257
|
}
|
|
219
258
|
catch (e) {
|
|
220
|
-
console.log(
|
|
259
|
+
console.log(colors.red(`Error reading ${file}`));
|
|
221
260
|
}
|
|
222
261
|
}
|
|
223
262
|
output += XML_SCHEMA_INSTRUCTION;
|
|
224
263
|
try {
|
|
225
|
-
await clipboardy.write(output);
|
|
226
|
-
const tokens =
|
|
264
|
+
await clipboardy.write(output); // Uses robust library
|
|
265
|
+
const tokens = estimateTokens(output);
|
|
227
266
|
const finalCount = selectedFiles.length - skippedCount;
|
|
228
|
-
const tokenColor = tokens > 100000 ?
|
|
229
|
-
console.log(
|
|
267
|
+
const tokenColor = tokens > 100000 ? colors.red : tokens > 30000 ? colors.yellow : colors.green;
|
|
268
|
+
console.log(colors.green(`\n✔ Copied ${finalCount} files to clipboard`));
|
|
230
269
|
console.log(`Estimated Tokens: ${tokenColor(tokens.toLocaleString())}`);
|
|
231
270
|
}
|
|
232
271
|
catch (e) {
|
|
233
|
-
console.log(
|
|
234
|
-
console.log(chalk.dim('Try selecting fewer files.'));
|
|
272
|
+
console.log(colors.red(`❌ Clipboard write failed: ${e instanceof Error ? e.message : 'Unknown'}`));
|
|
235
273
|
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
program
|
|
239
|
-
.command('apply')
|
|
240
|
-
.description('Apply AI changes from clipboard')
|
|
241
|
-
.action(async () => {
|
|
274
|
+
}
|
|
275
|
+
async function runApply() {
|
|
242
276
|
const backupsEnabled = await getBackupStatus();
|
|
243
|
-
console.log(
|
|
277
|
+
console.log(colors.dim('Reading clipboard...'));
|
|
244
278
|
let content;
|
|
245
279
|
try {
|
|
246
280
|
content = await clipboardy.read();
|
|
247
281
|
}
|
|
248
|
-
catch (e) {
|
|
249
|
-
|
|
282
|
+
catch (e) { // Uses robust library
|
|
283
|
+
console.log(colors.red(`Error: Could not read clipboard.`));
|
|
284
|
+
return;
|
|
250
285
|
}
|
|
251
286
|
const cleanedContent = content.replace(/```xml/g, '').replace(/```/g, '');
|
|
252
287
|
const fileRegex = /<file\s+path=["'](.*?)["']\s*>([\s\S]*?)<\/file>/gi;
|
|
@@ -258,21 +293,21 @@ program
|
|
|
258
293
|
while ((match = deleteRegex.exec(cleanedContent)) !== null)
|
|
259
294
|
updates.push({ type: 'delete', path: match[1] });
|
|
260
295
|
if (updates.length === 0)
|
|
261
|
-
return console.log(
|
|
262
|
-
console.log(
|
|
296
|
+
return console.log(colors.red('\nNo valid XML tags found.'));
|
|
297
|
+
console.log(colors.bold(`\nFound ${updates.length} pending change(s):\n`));
|
|
263
298
|
for (const update of updates) {
|
|
264
299
|
const targetPath = path.resolve(process.cwd(), update.path);
|
|
265
|
-
console.log(
|
|
300
|
+
console.log(colors.bgBlue(` ${update.path} `));
|
|
266
301
|
if (update.type === 'delete') {
|
|
267
|
-
console.log(
|
|
302
|
+
console.log(colors.bgRed(' [DELETE] '));
|
|
268
303
|
}
|
|
269
304
|
else {
|
|
270
305
|
let originalContent = '';
|
|
271
306
|
try {
|
|
272
|
-
originalContent = await
|
|
307
|
+
originalContent = await fsPromises.readFile(targetPath, 'utf-8');
|
|
273
308
|
}
|
|
274
309
|
catch (e) {
|
|
275
|
-
console.log(
|
|
310
|
+
console.log(colors.bgGreen(' [NEW FILE] '));
|
|
276
311
|
}
|
|
277
312
|
if (originalContent && update.content !== undefined) {
|
|
278
313
|
const changes = Diff.diffLines(originalContent, update.content);
|
|
@@ -281,20 +316,20 @@ program
|
|
|
281
316
|
if (count > 50)
|
|
282
317
|
return;
|
|
283
318
|
if (part.added) {
|
|
284
|
-
process.stdout.write(
|
|
319
|
+
process.stdout.write(colors.green(part.value.replace(/^/gm, '+ ')));
|
|
285
320
|
count++;
|
|
286
321
|
}
|
|
287
322
|
else if (part.removed) {
|
|
288
|
-
process.stdout.write(
|
|
323
|
+
process.stdout.write(colors.red(part.value.replace(/^/gm, '- ')));
|
|
289
324
|
count++;
|
|
290
325
|
}
|
|
291
326
|
});
|
|
292
327
|
if (count > 50)
|
|
293
|
-
console.log(
|
|
328
|
+
console.log(colors.dim('...'));
|
|
294
329
|
console.log('');
|
|
295
330
|
}
|
|
296
331
|
}
|
|
297
|
-
console.log(
|
|
332
|
+
console.log(colors.dim('--------------------------------------------------\n'));
|
|
298
333
|
}
|
|
299
334
|
let proceed = false;
|
|
300
335
|
try {
|
|
@@ -304,7 +339,7 @@ program
|
|
|
304
339
|
return;
|
|
305
340
|
}
|
|
306
341
|
if (!proceed)
|
|
307
|
-
return console.log(
|
|
342
|
+
return console.log(colors.yellow('Aborted.'));
|
|
308
343
|
console.log('');
|
|
309
344
|
for (const update of updates) {
|
|
310
345
|
const targetPath = path.resolve(process.cwd(), update.path);
|
|
@@ -312,37 +347,33 @@ program
|
|
|
312
347
|
if (update.type === 'delete') {
|
|
313
348
|
if (backupsEnabled) {
|
|
314
349
|
try {
|
|
315
|
-
await
|
|
350
|
+
await fsPromises.copyFile(targetPath, `${targetPath}.bak`);
|
|
316
351
|
}
|
|
317
352
|
catch (e) { }
|
|
318
353
|
}
|
|
319
354
|
try {
|
|
320
|
-
await
|
|
321
|
-
console.log(
|
|
355
|
+
await fsPromises.unlink(targetPath);
|
|
356
|
+
console.log(colors.dim(`Deleted ${update.path}`));
|
|
322
357
|
}
|
|
323
358
|
catch (e) { }
|
|
324
359
|
}
|
|
325
360
|
else {
|
|
326
|
-
await
|
|
361
|
+
await fsPromises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
327
362
|
if (backupsEnabled) {
|
|
328
363
|
try {
|
|
329
|
-
await
|
|
330
|
-
console.log(
|
|
364
|
+
await fsPromises.copyFile(targetPath, `${targetPath}.bak`);
|
|
365
|
+
console.log(colors.dim(`(Backup saved)`));
|
|
331
366
|
}
|
|
332
367
|
catch (e) { }
|
|
333
368
|
}
|
|
334
|
-
await
|
|
335
|
-
console.log(
|
|
369
|
+
await fsPromises.writeFile(targetPath, update.content || '');
|
|
370
|
+
console.log(colors.green(`✔ Wrote ${update.path}`));
|
|
336
371
|
}
|
|
337
372
|
}
|
|
338
373
|
catch (e) {
|
|
339
|
-
console.log(
|
|
374
|
+
console.log(colors.bgRed(` ERROR `) + ` ${update.path}: ${e.message}`);
|
|
340
375
|
}
|
|
341
376
|
}
|
|
342
|
-
console.log(
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
console.error(chalk.red(`\nError: Unknown command '${operands[0]}'`));
|
|
346
|
-
process.exit(1);
|
|
347
|
-
});
|
|
348
|
-
program.parse();
|
|
377
|
+
console.log(colors.cyan('\nDone.'));
|
|
378
|
+
}
|
|
379
|
+
main().catch(() => { process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aidx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "A CLI bridge between local code and LLMs. Copy context to clipboard and apply AI changes safely with diffs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"aidx": "dist/index.js"
|
|
8
|
+
"aidx": "./dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc",
|
|
17
17
|
"start": "tsx src/index.ts",
|
|
18
|
-
"dev": "tsx src/index.ts",
|
|
19
18
|
"prepublishOnly": "npm run build"
|
|
20
19
|
},
|
|
21
20
|
"repository": {
|
|
@@ -23,13 +22,10 @@
|
|
|
23
22
|
"url": "git+https://github.com/rx76d/aidx.git"
|
|
24
23
|
},
|
|
25
24
|
"homepage": "https://github.com/rx76d/aidx#readme",
|
|
26
|
-
"bugs": {
|
|
27
|
-
"url": "https://github.com/rx76d/aidx/issues"
|
|
28
|
-
},
|
|
29
25
|
"keywords": [
|
|
26
|
+
"ai",
|
|
30
27
|
"aidx",
|
|
31
28
|
"rx76d",
|
|
32
|
-
"ai",
|
|
33
29
|
"cli",
|
|
34
30
|
"clipboard",
|
|
35
31
|
"workflow",
|
|
@@ -37,20 +33,14 @@
|
|
|
37
33
|
"llm",
|
|
38
34
|
"chatgpt",
|
|
39
35
|
"claude",
|
|
40
|
-
"context"
|
|
41
|
-
"diff"
|
|
36
|
+
"context"
|
|
42
37
|
],
|
|
43
38
|
"author": "rx76d",
|
|
44
39
|
"license": "MIT",
|
|
45
40
|
"dependencies": {
|
|
46
41
|
"@inquirer/prompts": "^7.0.0",
|
|
47
|
-
"chalk": "^5.3.0",
|
|
48
42
|
"clipboardy": "^4.0.0",
|
|
49
|
-
"
|
|
50
|
-
"diff": "^5.2.0",
|
|
51
|
-
"fast-glob": "^3.3.2",
|
|
52
|
-
"gpt-tokenizer": "^2.1.2",
|
|
53
|
-
"isbinaryfile": "^5.0.2"
|
|
43
|
+
"diff": "^5.2.0"
|
|
54
44
|
},
|
|
55
45
|
"devDependencies": {
|
|
56
46
|
"@types/diff": "^5.0.9",
|
|
@@ -61,4 +51,4 @@
|
|
|
61
51
|
"engines": {
|
|
62
52
|
"node": ">=18.0.0"
|
|
63
53
|
}
|
|
64
|
-
}
|
|
54
|
+
}
|
package/readme.md
CHANGED
|
@@ -90,25 +90,25 @@ Command Description
|
|
|
90
90
|
|
|
91
91
|
# ✨ Features
|
|
92
92
|
|
|
93
|
-
🛡️ **Security Guard
|
|
93
|
+
🛡️ **Security Guard**:
|
|
94
94
|
Automatically detects and blocks API keys (AWS, OpenAI, Stripe) from being copied to the clipboard. If a file looks like a secret, it is skipped.
|
|
95
95
|
|
|
96
|
-
💾 **Automatic Backups
|
|
96
|
+
💾 **Automatic Backups**:
|
|
97
97
|
Don't trust the AI completely? Turn on backups.
|
|
98
98
|
```npx aidx backup --on```
|
|
99
99
|
Before src/App.tsx is updated, aidx will save a copy to src/App.tsx.bak.
|
|
100
100
|
|
|
101
|
-
🌍 **Universal Support
|
|
101
|
+
🌍 **Universal Support**:
|
|
102
102
|
Works with almost any text-based language:
|
|
103
103
|
Web: TS, JS, HTML, CSS, Svelte, Vue, JSX
|
|
104
104
|
Backend: Python, Go, Rust, Java, C#, PHP
|
|
105
105
|
Config: JSON, YAML, TOML, SQL, Markdown
|
|
106
106
|
Smart Ignores: Automatically ignores node_modules, .git, __pycache__, venv, target, bin, and binary files (.png, .exe).
|
|
107
107
|
|
|
108
|
-
📊 **Token Awareness
|
|
108
|
+
📊 **Token Awareness**:
|
|
109
109
|
Calculates estimated token usage before you paste, so you know if you are about to exceed the limits of GPT-5 or Claude 3.5.
|
|
110
110
|
|
|
111
|
-
🛡️ **License
|
|
111
|
+
🛡️ **License**:
|
|
112
112
|
This project is open source and available under the MIT License.
|
|
113
113
|
<div align="center">
|
|
114
114
|
<sub>Developed by rx76d</sub>
|