aidx 1.0.3 → 1.0.5
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 +188 -168
- package/package.json +9 -17
package/dist/index.js
CHANGED
|
@@ -1,25 +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
7
|
import * as Diff from 'diff';
|
|
11
8
|
// --- CONFIGURATION ---
|
|
12
9
|
const METADATA = {
|
|
13
10
|
name: "aidx",
|
|
14
11
|
description: "A CLI bridge between local code and LLMs.",
|
|
15
12
|
author: "rx76d",
|
|
16
|
-
version: "1.0.
|
|
13
|
+
version: "1.0.5",
|
|
17
14
|
license: "MIT",
|
|
18
15
|
github: "https://github.com/rx76d/aidx"
|
|
19
16
|
};
|
|
20
17
|
const CONFIG_FILE = '.aidxrc.json';
|
|
21
18
|
const MAX_FILE_SIZE = 1.5 * 1024 * 1024; // 1.5MB Limit
|
|
22
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 ---
|
|
23
95
|
const SYSTEM_HEADER = `
|
|
24
96
|
================================================================
|
|
25
97
|
SYSTEM PROMPT: STRICT CODE MODE
|
|
@@ -44,76 +116,59 @@ console.log("Full code here...");
|
|
|
44
116
|
</file>
|
|
45
117
|
================================================================
|
|
46
118
|
`;
|
|
47
|
-
// ---
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
catch (e) {
|
|
79
|
-
return false;
|
|
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);
|
|
80
149
|
}
|
|
81
150
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
await fs.writeFile(configPath, JSON.stringify({ backup: enabled }, null, 2));
|
|
85
|
-
}
|
|
86
|
-
const program = new Command();
|
|
87
|
-
program
|
|
88
|
-
.name(METADATA.name)
|
|
89
|
-
.description(METADATA.description)
|
|
90
|
-
.version(METADATA.version);
|
|
91
|
-
// --- ROOT COMMAND ---
|
|
92
|
-
program.action(async () => {
|
|
151
|
+
// --- ACTIONS ---
|
|
152
|
+
async function showMenu() {
|
|
93
153
|
const backupEnabled = await getBackupStatus();
|
|
94
|
-
console.log('\n' +
|
|
95
|
-
console.log(
|
|
96
|
-
console.log(`${
|
|
97
|
-
console.log(`${
|
|
98
|
-
console.log(`${
|
|
99
|
-
console.log(
|
|
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('----------------------------------------'));
|
|
100
160
|
console.log('\nAvailable Commands:');
|
|
101
|
-
console.log(` ${
|
|
102
|
-
console.log(` ${
|
|
103
|
-
console.log(` ${
|
|
104
|
-
console.log(` ${
|
|
105
|
-
console.log(` ${
|
|
106
|
-
console.log(`\nRun ${
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.
|
|
111
|
-
.description('Show safe token limits for AI models')
|
|
112
|
-
.action(() => {
|
|
113
|
-
console.log('\n' + chalk.bold('AI Model Context Limits (2025 Reference)'));
|
|
114
|
-
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('--------------------------------------------------'));
|
|
115
171
|
const models = [
|
|
116
|
-
// HUGE (≈ 1M+ tokens)
|
|
117
172
|
{ name: "Gemini 3 Pro", limit: "2,000,000+", type: "Huge" },
|
|
118
173
|
{ name: "Gemini 2.5 Pro", limit: "1,000,000+", type: "Huge" },
|
|
119
174
|
{ name: "Gemini 2.5 Flash", limit: "1,000,000+", type: "Huge" },
|
|
@@ -121,73 +176,48 @@ program
|
|
|
121
176
|
{ name: "Llama 4 Maverick", limit: "1,000,000+", type: "Huge" },
|
|
122
177
|
{ name: "Qwen 2.5 1M", limit: "1,000,000+", type: "Huge" },
|
|
123
178
|
{ name: "GPT-4.1", limit: "1,000,000+", type: "Huge" },
|
|
124
|
-
// LARGE (≈ 200K–500K tokens)
|
|
125
179
|
{ name: "ChatGPT-5", limit: "200,000+", type: "Large" },
|
|
126
180
|
{ name: "Claude 4.5 Sonnet", limit: "200,000+", type: "Large" },
|
|
127
181
|
{ name: "Claude 4.5 Opus", limit: "200,000+", type: "Large" },
|
|
128
182
|
{ name: "Grok 4", limit: "256,000", type: "Large" },
|
|
129
183
|
{ name: "Cohere Command A", limit: "256,000", type: "Large" },
|
|
130
|
-
// MEDIUM (≈ 100K–150K tokens)
|
|
131
184
|
{ name: "GPT-4o", limit: "128,000", type: "Medium" },
|
|
132
185
|
{ name: "Llama 4 405B", limit: "128,000", type: "Medium" },
|
|
133
186
|
{ name: "DeepSeek V3", limit: "128,000", type: "Medium" },
|
|
134
187
|
{ name: "Grok 3", limit: "128,000", type: "Medium" },
|
|
135
188
|
{ name: "GPT-5 Mini", limit: "128,000", type: "Medium" },
|
|
136
|
-
// SMALL (< 50K tokens)
|
|
137
189
|
{ name: "ChatGPT (Free)", limit: "~8,000", type: "Small" },
|
|
138
190
|
{ name: "Claude Haiku", limit: "~16,000", type: "Small" },
|
|
139
191
|
];
|
|
140
|
-
console.log(
|
|
141
|
-
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('--------------------------------------------------'));
|
|
142
194
|
models.forEach(m => {
|
|
143
|
-
const color = m.type === "Huge" ?
|
|
144
|
-
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));
|
|
145
197
|
});
|
|
146
|
-
console.log(
|
|
147
|
-
console.log(
|
|
148
|
-
console.log(
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
.command('backup')
|
|
153
|
-
.description('Configure automatic backups')
|
|
154
|
-
.option('--on', 'Enable backups')
|
|
155
|
-
.option('--off', 'Disable backups')
|
|
156
|
-
.action(async (options) => {
|
|
157
|
-
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') {
|
|
158
204
|
await setBackupStatus(true);
|
|
159
|
-
console.log(
|
|
205
|
+
console.log(colors.green(`\n✔ Backups enabled. Settings saved to ${CONFIG_FILE}`));
|
|
160
206
|
}
|
|
161
|
-
else if (
|
|
207
|
+
else if (flag === '--off') {
|
|
162
208
|
await setBackupStatus(false);
|
|
163
|
-
console.log(
|
|
209
|
+
console.log(colors.yellow(`\nBackups disabled.`));
|
|
164
210
|
}
|
|
165
211
|
else {
|
|
166
|
-
|
|
167
|
-
console.log(`\nCurrent Backup Status: ${status ? chalk.green('ENABLED') : chalk.red('DISABLED')}`);
|
|
212
|
+
console.log(colors.red('Error: Use --on or --off'));
|
|
168
213
|
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
.
|
|
173
|
-
.description('Select files and copy to clipboard')
|
|
174
|
-
.action(async () => {
|
|
175
|
-
console.log(chalk.blue('Scanning directory...'));
|
|
176
|
-
const files = await glob(['**/*'], {
|
|
177
|
-
ignore: [
|
|
178
|
-
'**/Application Data/**', '**/Cookies/**', '**/Local Settings/**', '**/Recent/**', '**/Start Menu/**',
|
|
179
|
-
'**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**', '**/.vscode/**',
|
|
180
|
-
'**/__pycache__/**', '**/venv/**', '**/target/**', '**/bin/**', '**/obj/**',
|
|
181
|
-
'**/vendor/**', '**/*.lock', '**/*.log', '**/*.png', '**/*.exe', '**/*.dll', '**/*.zip', '**/*.tar', '**/*.gz'
|
|
182
|
-
],
|
|
183
|
-
onlyFiles: true,
|
|
184
|
-
dot: true,
|
|
185
|
-
suppressErrors: true,
|
|
186
|
-
followSymbolicLinks: false
|
|
187
|
-
});
|
|
214
|
+
}
|
|
215
|
+
async function runCopy() {
|
|
216
|
+
console.log(colors.blue('Scanning directory...'));
|
|
217
|
+
const files = await scanFiles(process.cwd());
|
|
188
218
|
if (files.length === 0)
|
|
189
|
-
return console.log(
|
|
190
|
-
let selectedFiles
|
|
219
|
+
return console.log(colors.red('Error: No files found.'));
|
|
220
|
+
let selectedFiles;
|
|
191
221
|
try {
|
|
192
222
|
selectedFiles = await checkbox({
|
|
193
223
|
message: 'Select files to send to AI:',
|
|
@@ -196,68 +226,62 @@ program
|
|
|
196
226
|
});
|
|
197
227
|
}
|
|
198
228
|
catch (e) {
|
|
199
|
-
return console.log(
|
|
229
|
+
return console.log(colors.yellow('\nSelection cancelled.'));
|
|
200
230
|
}
|
|
201
231
|
if (selectedFiles.length === 0)
|
|
202
|
-
return console.log(
|
|
232
|
+
return console.log(colors.yellow('No files selected.'));
|
|
203
233
|
let output = SYSTEM_HEADER + "\n";
|
|
204
234
|
let skippedCount = 0;
|
|
205
|
-
console.log(
|
|
235
|
+
console.log(colors.dim('Reading files...'));
|
|
206
236
|
for (const file of selectedFiles) {
|
|
207
237
|
try {
|
|
208
|
-
const stats = statSync(file);
|
|
238
|
+
const stats = fs.statSync(file);
|
|
209
239
|
if (stats.size > MAX_FILE_SIZE) {
|
|
210
|
-
console.log(
|
|
240
|
+
console.log(colors.yellow(`⚠ Skipped large file (>1.5MB): ${file}`));
|
|
211
241
|
skippedCount++;
|
|
212
242
|
continue;
|
|
213
243
|
}
|
|
214
|
-
|
|
215
|
-
const buffer = await fs.readFile(file);
|
|
244
|
+
const buffer = await fsPromises.readFile(file);
|
|
216
245
|
if (isBinary(buffer)) {
|
|
217
|
-
console.log(
|
|
246
|
+
console.log(colors.yellow(`⚠ Skipped binary file: ${file}`));
|
|
218
247
|
skippedCount++;
|
|
219
248
|
continue;
|
|
220
249
|
}
|
|
221
250
|
const content = buffer.toString('utf-8');
|
|
222
251
|
if (file.includes('.env') || SECRET_REGEX.test(content)) {
|
|
223
|
-
console.log(
|
|
252
|
+
console.log(colors.red(`\n🛑 SECURITY ALERT: Secrets detected in ${file}`));
|
|
224
253
|
skippedCount++;
|
|
225
254
|
continue;
|
|
226
255
|
}
|
|
227
256
|
output += `File: ${file}\n\`\`\`\n${content}\n\`\`\`\n\n`;
|
|
228
257
|
}
|
|
229
258
|
catch (e) {
|
|
230
|
-
console.log(
|
|
259
|
+
console.log(colors.red(`Error reading ${file}`));
|
|
231
260
|
}
|
|
232
261
|
}
|
|
233
262
|
output += XML_SCHEMA_INSTRUCTION;
|
|
234
263
|
try {
|
|
235
|
-
await clipboardy.write(output);
|
|
236
|
-
// LIGHTWEIGHT TOKENIZER USAGE
|
|
264
|
+
await clipboardy.write(output); // Uses robust library
|
|
237
265
|
const tokens = estimateTokens(output);
|
|
238
266
|
const finalCount = selectedFiles.length - skippedCount;
|
|
239
|
-
const tokenColor = tokens > 100000 ?
|
|
240
|
-
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`));
|
|
241
269
|
console.log(`Estimated Tokens: ${tokenColor(tokens.toLocaleString())}`);
|
|
242
270
|
}
|
|
243
271
|
catch (e) {
|
|
244
|
-
console.log(
|
|
245
|
-
console.log(chalk.dim('Try selecting fewer files.'));
|
|
272
|
+
console.log(colors.red(`❌ Clipboard write failed: ${e instanceof Error ? e.message : 'Unknown'}`));
|
|
246
273
|
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
program
|
|
250
|
-
.command('apply')
|
|
251
|
-
.description('Apply AI changes from clipboard')
|
|
252
|
-
.action(async () => {
|
|
274
|
+
}
|
|
275
|
+
async function runApply() {
|
|
253
276
|
const backupsEnabled = await getBackupStatus();
|
|
254
|
-
console.log(
|
|
277
|
+
console.log(colors.dim('Reading clipboard...'));
|
|
255
278
|
let content;
|
|
256
279
|
try {
|
|
257
280
|
content = await clipboardy.read();
|
|
258
281
|
}
|
|
259
|
-
catch (e) {
|
|
260
|
-
|
|
282
|
+
catch (e) { // Uses robust library
|
|
283
|
+
console.log(colors.red(`Error: Could not read clipboard.`));
|
|
284
|
+
return;
|
|
261
285
|
}
|
|
262
286
|
const cleanedContent = content.replace(/```xml/g, '').replace(/```/g, '');
|
|
263
287
|
const fileRegex = /<file\s+path=["'](.*?)["']\s*>([\s\S]*?)<\/file>/gi;
|
|
@@ -269,21 +293,21 @@ program
|
|
|
269
293
|
while ((match = deleteRegex.exec(cleanedContent)) !== null)
|
|
270
294
|
updates.push({ type: 'delete', path: match[1] });
|
|
271
295
|
if (updates.length === 0)
|
|
272
|
-
return console.log(
|
|
273
|
-
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`));
|
|
274
298
|
for (const update of updates) {
|
|
275
299
|
const targetPath = path.resolve(process.cwd(), update.path);
|
|
276
|
-
console.log(
|
|
300
|
+
console.log(colors.bgBlue(` ${update.path} `));
|
|
277
301
|
if (update.type === 'delete') {
|
|
278
|
-
console.log(
|
|
302
|
+
console.log(colors.bgRed(' [DELETE] '));
|
|
279
303
|
}
|
|
280
304
|
else {
|
|
281
305
|
let originalContent = '';
|
|
282
306
|
try {
|
|
283
|
-
originalContent = await
|
|
307
|
+
originalContent = await fsPromises.readFile(targetPath, 'utf-8');
|
|
284
308
|
}
|
|
285
309
|
catch (e) {
|
|
286
|
-
console.log(
|
|
310
|
+
console.log(colors.bgGreen(' [NEW FILE] '));
|
|
287
311
|
}
|
|
288
312
|
if (originalContent && update.content !== undefined) {
|
|
289
313
|
const changes = Diff.diffLines(originalContent, update.content);
|
|
@@ -292,20 +316,20 @@ program
|
|
|
292
316
|
if (count > 50)
|
|
293
317
|
return;
|
|
294
318
|
if (part.added) {
|
|
295
|
-
process.stdout.write(
|
|
319
|
+
process.stdout.write(colors.green(part.value.replace(/^/gm, '+ ')));
|
|
296
320
|
count++;
|
|
297
321
|
}
|
|
298
322
|
else if (part.removed) {
|
|
299
|
-
process.stdout.write(
|
|
323
|
+
process.stdout.write(colors.red(part.value.replace(/^/gm, '- ')));
|
|
300
324
|
count++;
|
|
301
325
|
}
|
|
302
326
|
});
|
|
303
327
|
if (count > 50)
|
|
304
|
-
console.log(
|
|
328
|
+
console.log(colors.dim('...'));
|
|
305
329
|
console.log('');
|
|
306
330
|
}
|
|
307
331
|
}
|
|
308
|
-
console.log(
|
|
332
|
+
console.log(colors.dim('--------------------------------------------------\n'));
|
|
309
333
|
}
|
|
310
334
|
let proceed = false;
|
|
311
335
|
try {
|
|
@@ -315,7 +339,7 @@ program
|
|
|
315
339
|
return;
|
|
316
340
|
}
|
|
317
341
|
if (!proceed)
|
|
318
|
-
return console.log(
|
|
342
|
+
return console.log(colors.yellow('Aborted.'));
|
|
319
343
|
console.log('');
|
|
320
344
|
for (const update of updates) {
|
|
321
345
|
const targetPath = path.resolve(process.cwd(), update.path);
|
|
@@ -323,37 +347,33 @@ program
|
|
|
323
347
|
if (update.type === 'delete') {
|
|
324
348
|
if (backupsEnabled) {
|
|
325
349
|
try {
|
|
326
|
-
await
|
|
350
|
+
await fsPromises.copyFile(targetPath, `${targetPath}.bak`);
|
|
327
351
|
}
|
|
328
352
|
catch (e) { }
|
|
329
353
|
}
|
|
330
354
|
try {
|
|
331
|
-
await
|
|
332
|
-
console.log(
|
|
355
|
+
await fsPromises.unlink(targetPath);
|
|
356
|
+
console.log(colors.dim(`Deleted ${update.path}`));
|
|
333
357
|
}
|
|
334
358
|
catch (e) { }
|
|
335
359
|
}
|
|
336
360
|
else {
|
|
337
|
-
await
|
|
361
|
+
await fsPromises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
338
362
|
if (backupsEnabled) {
|
|
339
363
|
try {
|
|
340
|
-
await
|
|
341
|
-
console.log(
|
|
364
|
+
await fsPromises.copyFile(targetPath, `${targetPath}.bak`);
|
|
365
|
+
console.log(colors.dim(`(Backup saved)`));
|
|
342
366
|
}
|
|
343
367
|
catch (e) { }
|
|
344
368
|
}
|
|
345
|
-
await
|
|
346
|
-
console.log(
|
|
369
|
+
await fsPromises.writeFile(targetPath, update.content || '');
|
|
370
|
+
console.log(colors.green(`✔ Wrote ${update.path}`));
|
|
347
371
|
}
|
|
348
372
|
}
|
|
349
373
|
catch (e) {
|
|
350
|
-
console.log(
|
|
374
|
+
console.log(colors.bgRed(` ERROR `) + ` ${update.path}: ${e.message}`);
|
|
351
375
|
}
|
|
352
376
|
}
|
|
353
|
-
console.log(
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
console.error(chalk.red(`\nError: Unknown command '${operands[0]}'`));
|
|
357
|
-
process.exit(1);
|
|
358
|
-
});
|
|
359
|
-
program.parse();
|
|
377
|
+
console.log(colors.cyan('\nDone.'));
|
|
378
|
+
}
|
|
379
|
+
main().catch(() => { process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aidx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
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",
|
|
@@ -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,26 +33,22 @@
|
|
|
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
|
-
"@inquirer/prompts": "^
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"commander": "^12.0.0",
|
|
50
|
-
"diff": "^5.2.0",
|
|
51
|
-
"fast-glob": "^3.3.2"
|
|
41
|
+
"@inquirer/prompts": "^8.1.0",
|
|
42
|
+
"clipboardy": "^5.0.2",
|
|
43
|
+
"diff": "^8.0.2"
|
|
52
44
|
},
|
|
53
45
|
"devDependencies": {
|
|
54
|
-
"@types/diff": "^
|
|
55
|
-
"@types/node": "^
|
|
46
|
+
"@types/diff": "^7.0.2",
|
|
47
|
+
"@types/node": "^25.0.3",
|
|
56
48
|
"tsx": "^4.7.0",
|
|
57
49
|
"typescript": "^5.3.0"
|
|
58
50
|
},
|
|
59
51
|
"engines": {
|
|
60
|
-
"node": ">=
|
|
52
|
+
"node": ">=20.0.0"
|
|
61
53
|
}
|
|
62
54
|
}
|