vigthoria-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 +413 -0
- package/dist/commands/auth.d.ts +24 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +194 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/chat.d.ts +64 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +596 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/config.d.ts +25 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +291 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/edit.d.ts +28 -0
- package/dist/commands/edit.d.ts.map +1 -0
- package/dist/commands/edit.js +257 -0
- package/dist/commands/edit.js.map +1 -0
- package/dist/commands/explain.d.ts +21 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +98 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/generate.d.ts +25 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +155 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/review.d.ts +24 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +153 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +205 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/api.d.ts +88 -0
- package/dist/utils/api.d.ts.map +1 -0
- package/dist/utils/api.js +431 -0
- package/dist/utils/api.js.map +1 -0
- package/dist/utils/config.d.ts +57 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +167 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/files.d.ts +31 -0
- package/dist/utils/files.d.ts.map +1 -0
- package/dist/utils/files.js +217 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/logger.d.ts +23 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +104 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/session.d.ts +61 -0
- package/dist/utils/session.d.ts.map +1 -0
- package/dist/utils/session.js +172 -0
- package/dist/utils/session.js.map +1 -0
- package/dist/utils/tools.d.ts +145 -0
- package/dist/utils/tools.d.ts.map +1 -0
- package/dist/utils/tools.js +781 -0
- package/dist/utils/tools.js.map +1 -0
- package/install.sh +248 -0
- package/package.json +52 -0
- package/src/commands/auth.ts +225 -0
- package/src/commands/chat.ts +690 -0
- package/src/commands/config.ts +297 -0
- package/src/commands/edit.ts +310 -0
- package/src/commands/explain.ts +115 -0
- package/src/commands/generate.ts +177 -0
- package/src/commands/review.ts +186 -0
- package/src/index.ts +221 -0
- package/src/types/marked-terminal.d.ts +31 -0
- package/src/utils/api.ts +531 -0
- package/src/utils/config.ts +224 -0
- package/src/utils/files.ts +212 -0
- package/src/utils/logger.ts +125 -0
- package/src/utils/session.ts +167 -0
- package/src/utils/tools.ts +933 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Command - CLI configuration management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { Config, VigthoriaCLIConfig } from '../utils/config.js';
|
|
10
|
+
import { Logger } from '../utils/logger.js';
|
|
11
|
+
|
|
12
|
+
interface ConfigOptions {
|
|
13
|
+
set?: string;
|
|
14
|
+
get?: string;
|
|
15
|
+
list?: boolean;
|
|
16
|
+
reset?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ConfigCommand {
|
|
20
|
+
private config: Config;
|
|
21
|
+
private logger: Logger;
|
|
22
|
+
|
|
23
|
+
constructor(config: Config, logger: Logger) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
this.logger = logger;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async run(options: ConfigOptions): Promise<void> {
|
|
29
|
+
if (options.reset) {
|
|
30
|
+
await this.resetConfig();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (options.set) {
|
|
35
|
+
this.setConfig(options.set);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (options.get) {
|
|
40
|
+
this.getConfig(options.get);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (options.list) {
|
|
45
|
+
this.listConfig();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Interactive mode
|
|
50
|
+
await this.interactiveConfig();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async init(): Promise<void> {
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(chalk.cyan('═══ Initialize Vigthoria in Project ═══'));
|
|
56
|
+
console.log();
|
|
57
|
+
|
|
58
|
+
const cwd = process.cwd();
|
|
59
|
+
const configFile = path.join(cwd, '.vigthoria.json');
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(configFile)) {
|
|
62
|
+
const { overwrite } = await inquirer.prompt([
|
|
63
|
+
{
|
|
64
|
+
type: 'confirm',
|
|
65
|
+
name: 'overwrite',
|
|
66
|
+
message: '.vigthoria.json already exists. Overwrite?',
|
|
67
|
+
default: false,
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
if (!overwrite) {
|
|
72
|
+
this.logger.info('Initialization cancelled');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Gather project settings
|
|
78
|
+
const settings = await inquirer.prompt([
|
|
79
|
+
{
|
|
80
|
+
type: 'list',
|
|
81
|
+
name: 'defaultModel',
|
|
82
|
+
message: 'Default AI model:',
|
|
83
|
+
choices: [
|
|
84
|
+
{ name: 'Vigthoria Code (8B) - Best for coding', value: 'vigthoria-code' },
|
|
85
|
+
{ name: 'Vigthoria Fast (1.1B) - Quick responses', value: 'vigthoria-fast' },
|
|
86
|
+
{ name: 'Vigthoria Mini (3.8B) - Balanced', value: 'vigthoria-mini' },
|
|
87
|
+
{ name: 'Vigthoria Creative (9B) - Creative tasks', value: 'vigthoria-creative' },
|
|
88
|
+
],
|
|
89
|
+
default: 'vigthoria-code',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: 'input',
|
|
93
|
+
name: 'ignorePatterns',
|
|
94
|
+
message: 'Additional ignore patterns (comma-separated):',
|
|
95
|
+
default: '',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: 'confirm',
|
|
99
|
+
name: 'autoApplyFixes',
|
|
100
|
+
message: 'Auto-apply fixes without confirmation?',
|
|
101
|
+
default: false,
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
// Create project config
|
|
106
|
+
const projectConfig = {
|
|
107
|
+
defaultModel: settings.defaultModel,
|
|
108
|
+
ignorePatterns: settings.ignorePatterns
|
|
109
|
+
? settings.ignorePatterns.split(',').map((p: string) => p.trim())
|
|
110
|
+
: [],
|
|
111
|
+
autoApplyFixes: settings.autoApplyFixes,
|
|
112
|
+
createdAt: new Date().toISOString(),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
fs.writeFileSync(configFile, JSON.stringify(projectConfig, null, 2));
|
|
116
|
+
this.logger.success(`Created ${configFile}`);
|
|
117
|
+
|
|
118
|
+
// Add to .gitignore if exists
|
|
119
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
120
|
+
if (fs.existsSync(gitignorePath)) {
|
|
121
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
122
|
+
if (!gitignore.includes('.vigthoria.json')) {
|
|
123
|
+
fs.appendFileSync(gitignorePath, '\n# Vigthoria CLI\n.vigthoria.json\n');
|
|
124
|
+
this.logger.info('Added .vigthoria.json to .gitignore');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log();
|
|
129
|
+
console.log(chalk.gray('Project initialized! Run `vigthoria chat` to start.'));
|
|
130
|
+
console.log();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private setConfig(keyValue: string): void {
|
|
134
|
+
const [key, ...valueParts] = keyValue.split('=');
|
|
135
|
+
const value = valueParts.join('=');
|
|
136
|
+
|
|
137
|
+
if (!key || value === undefined) {
|
|
138
|
+
this.logger.error('Invalid format. Use: vigthoria config --set key=value');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const configMap: Record<string, (v: string) => void> = {
|
|
143
|
+
'model': (v) => this.config.set('preferences', { ...this.config.get('preferences'), defaultModel: v }),
|
|
144
|
+
'theme': (v) => this.config.set('preferences', { ...this.config.get('preferences'), theme: v as 'dark' | 'light' }),
|
|
145
|
+
'autoApply': (v) => this.config.set('preferences', { ...this.config.get('preferences'), autoApplyFixes: v === 'true' }),
|
|
146
|
+
'showDiffs': (v) => this.config.set('preferences', { ...this.config.get('preferences'), showDiffs: v === 'true' }),
|
|
147
|
+
'maxTokens': (v) => this.config.set('preferences', { ...this.config.get('preferences'), maxTokens: parseInt(v, 10) }),
|
|
148
|
+
'apiUrl': (v) => this.config.set('apiUrl', v),
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
if (configMap[key]) {
|
|
152
|
+
configMap[key](value);
|
|
153
|
+
this.logger.success(`Set ${key} = ${value}`);
|
|
154
|
+
} else {
|
|
155
|
+
this.logger.error(`Unknown config key: ${key}`);
|
|
156
|
+
console.log(chalk.gray('Available keys: model, theme, autoApply, showDiffs, maxTokens, apiUrl'));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private getConfig(key: string): void {
|
|
161
|
+
const all = this.config.getAll();
|
|
162
|
+
|
|
163
|
+
const flatConfig: Record<string, unknown> = {
|
|
164
|
+
apiUrl: all.apiUrl,
|
|
165
|
+
model: all.preferences.defaultModel,
|
|
166
|
+
theme: all.preferences.theme,
|
|
167
|
+
autoApply: all.preferences.autoApplyFixes,
|
|
168
|
+
showDiffs: all.preferences.showDiffs,
|
|
169
|
+
maxTokens: all.preferences.maxTokens,
|
|
170
|
+
email: all.email,
|
|
171
|
+
plan: all.subscription.plan,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (key in flatConfig) {
|
|
175
|
+
console.log(flatConfig[key]);
|
|
176
|
+
} else {
|
|
177
|
+
this.logger.error(`Unknown config key: ${key}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private listConfig(): void {
|
|
182
|
+
const all = this.config.getAll();
|
|
183
|
+
|
|
184
|
+
console.log();
|
|
185
|
+
console.log(chalk.cyan('═══ Vigthoria CLI Configuration ═══'));
|
|
186
|
+
console.log();
|
|
187
|
+
|
|
188
|
+
console.log(chalk.white('API:'));
|
|
189
|
+
console.log(chalk.gray(' URL: ') + chalk.cyan(all.apiUrl));
|
|
190
|
+
console.log(chalk.gray(' WebSocket: ') + chalk.cyan(all.wsUrl));
|
|
191
|
+
console.log();
|
|
192
|
+
|
|
193
|
+
console.log(chalk.white('Preferences:'));
|
|
194
|
+
console.log(chalk.gray(' Default Model: ') + chalk.cyan(all.preferences.defaultModel));
|
|
195
|
+
console.log(chalk.gray(' Theme: ') + chalk.cyan(all.preferences.theme));
|
|
196
|
+
console.log(chalk.gray(' Auto Apply Fixes: ') + chalk.cyan(all.preferences.autoApplyFixes));
|
|
197
|
+
console.log(chalk.gray(' Show Diffs: ') + chalk.cyan(all.preferences.showDiffs));
|
|
198
|
+
console.log(chalk.gray(' Max Tokens: ') + chalk.cyan(all.preferences.maxTokens));
|
|
199
|
+
console.log();
|
|
200
|
+
|
|
201
|
+
console.log(chalk.white('Project:'));
|
|
202
|
+
console.log(chalk.gray(' Ignore Patterns: ') + chalk.gray(all.project.ignorePatterns.join(', ')));
|
|
203
|
+
console.log();
|
|
204
|
+
|
|
205
|
+
console.log(chalk.gray(`Config file: ${this.config.getConfigPath()}`));
|
|
206
|
+
console.log();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async resetConfig(): Promise<void> {
|
|
210
|
+
const { confirm } = await inquirer.prompt([
|
|
211
|
+
{
|
|
212
|
+
type: 'confirm',
|
|
213
|
+
name: 'confirm',
|
|
214
|
+
message: 'Reset all settings to defaults? (This will not log you out)',
|
|
215
|
+
default: false,
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
if (confirm) {
|
|
220
|
+
// Save auth before reset
|
|
221
|
+
const authToken = this.config.get('authToken');
|
|
222
|
+
const refreshToken = this.config.get('refreshToken');
|
|
223
|
+
const userId = this.config.get('userId');
|
|
224
|
+
const email = this.config.get('email');
|
|
225
|
+
const subscription = this.config.get('subscription');
|
|
226
|
+
|
|
227
|
+
this.config.reset();
|
|
228
|
+
|
|
229
|
+
// Restore auth
|
|
230
|
+
if (authToken) {
|
|
231
|
+
this.config.set('authToken', authToken);
|
|
232
|
+
this.config.set('refreshToken', refreshToken);
|
|
233
|
+
this.config.set('userId', userId);
|
|
234
|
+
this.config.set('email', email);
|
|
235
|
+
this.config.set('subscription', subscription);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.logger.success('Configuration reset to defaults');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async interactiveConfig(): Promise<void> {
|
|
243
|
+
const current = this.config.getAll();
|
|
244
|
+
|
|
245
|
+
console.log();
|
|
246
|
+
console.log(chalk.cyan('═══ Configure Vigthoria CLI ═══'));
|
|
247
|
+
console.log();
|
|
248
|
+
|
|
249
|
+
const settings = await inquirer.prompt([
|
|
250
|
+
{
|
|
251
|
+
type: 'list',
|
|
252
|
+
name: 'defaultModel',
|
|
253
|
+
message: 'Default AI model:',
|
|
254
|
+
choices: [
|
|
255
|
+
{ name: 'Vigthoria Code (8B)', value: 'vigthoria-code' },
|
|
256
|
+
{ name: 'Vigthoria Fast (1.1B)', value: 'vigthoria-fast' },
|
|
257
|
+
{ name: 'Vigthoria Mini (3.8B)', value: 'vigthoria-mini' },
|
|
258
|
+
{ name: 'Vigthoria Creative (9B)', value: 'vigthoria-creative' },
|
|
259
|
+
],
|
|
260
|
+
default: current.preferences.defaultModel,
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
type: 'list',
|
|
264
|
+
name: 'theme',
|
|
265
|
+
message: 'Color theme:',
|
|
266
|
+
choices: ['dark', 'light'],
|
|
267
|
+
default: current.preferences.theme,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
type: 'confirm',
|
|
271
|
+
name: 'autoApplyFixes',
|
|
272
|
+
message: 'Auto-apply fixes without confirmation?',
|
|
273
|
+
default: current.preferences.autoApplyFixes,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
type: 'confirm',
|
|
277
|
+
name: 'showDiffs',
|
|
278
|
+
message: 'Show diffs before applying changes?',
|
|
279
|
+
default: current.preferences.showDiffs,
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
type: 'number',
|
|
283
|
+
name: 'maxTokens',
|
|
284
|
+
message: 'Maximum tokens per response:',
|
|
285
|
+
default: current.preferences.maxTokens,
|
|
286
|
+
},
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
this.config.set('preferences', {
|
|
290
|
+
...current.preferences,
|
|
291
|
+
...settings,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
this.logger.success('Configuration saved');
|
|
295
|
+
console.log();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit Command - File editing with AI assistance
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import * as readline from 'readline';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import { Config } from '../utils/config.js';
|
|
10
|
+
import { Logger } from '../utils/logger.js';
|
|
11
|
+
import { APIClient } from '../utils/api.js';
|
|
12
|
+
import { FileUtils } from '../utils/files.js';
|
|
13
|
+
|
|
14
|
+
interface EditOptions {
|
|
15
|
+
instruction?: string;
|
|
16
|
+
model: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface FixOptions {
|
|
20
|
+
type: string;
|
|
21
|
+
apply: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class EditCommand {
|
|
25
|
+
private config: Config;
|
|
26
|
+
private logger: Logger;
|
|
27
|
+
private api: APIClient;
|
|
28
|
+
private fileUtils: FileUtils;
|
|
29
|
+
|
|
30
|
+
constructor(config: Config, logger: Logger) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.logger = logger;
|
|
33
|
+
this.api = new APIClient(config, logger);
|
|
34
|
+
this.fileUtils = new FileUtils(process.cwd(), config.get('project').ignorePatterns);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async run(filePath: string, options: EditOptions): Promise<void> {
|
|
38
|
+
// Check auth
|
|
39
|
+
if (!this.config.isAuthenticated()) {
|
|
40
|
+
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Read file
|
|
45
|
+
const file = this.fileUtils.readFile(filePath);
|
|
46
|
+
if (!file) {
|
|
47
|
+
this.logger.error(`File not found: ${filePath}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.logger.section(`Editing: ${file.relativePath}`);
|
|
52
|
+
console.log(chalk.gray(`Language: ${file.language} | Lines: ${file.lines}`));
|
|
53
|
+
console.log();
|
|
54
|
+
|
|
55
|
+
// Get instruction
|
|
56
|
+
let instruction = options.instruction;
|
|
57
|
+
if (!instruction) {
|
|
58
|
+
const answer = await inquirer.prompt([
|
|
59
|
+
{
|
|
60
|
+
type: 'input',
|
|
61
|
+
name: 'instruction',
|
|
62
|
+
message: 'What changes would you like to make?',
|
|
63
|
+
validate: (input) => input.length > 0 || 'Please provide an instruction',
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
instruction = answer.instruction;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Generate edit
|
|
70
|
+
const spinner = ora({
|
|
71
|
+
text: 'Generating changes...',
|
|
72
|
+
spinner: 'dots',
|
|
73
|
+
}).start();
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const response = await this.api.chat([
|
|
77
|
+
{
|
|
78
|
+
role: 'system',
|
|
79
|
+
content: `You are a code editor. Edit the provided code according to the user's instruction.
|
|
80
|
+
Return ONLY the complete modified code without any explanation or markdown formatting.
|
|
81
|
+
Preserve the original formatting and style unless the instruction specifically asks to change it.`,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
role: 'user',
|
|
85
|
+
content: `File: ${file.relativePath}
|
|
86
|
+
Language: ${file.language}
|
|
87
|
+
|
|
88
|
+
Original code:
|
|
89
|
+
\`\`\`${file.language}
|
|
90
|
+
${file.content}
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
Instruction: ${instruction}
|
|
94
|
+
|
|
95
|
+
Return the complete modified code:`,
|
|
96
|
+
},
|
|
97
|
+
], options.model);
|
|
98
|
+
|
|
99
|
+
spinner.stop();
|
|
100
|
+
|
|
101
|
+
// Extract code from response
|
|
102
|
+
const modifiedCode = this.extractCode(response.message, file.language);
|
|
103
|
+
|
|
104
|
+
if (!modifiedCode) {
|
|
105
|
+
this.logger.error('Failed to generate valid code changes');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Show diff
|
|
110
|
+
await this.showDiffAndConfirm(file.path, file.content, modifiedCode);
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
spinner.stop();
|
|
114
|
+
this.logger.error('Edit failed:', (error as Error).message);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async fix(filePath: string, options: FixOptions): Promise<void> {
|
|
119
|
+
// Check auth
|
|
120
|
+
if (!this.config.isAuthenticated()) {
|
|
121
|
+
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Read file
|
|
126
|
+
const file = this.fileUtils.readFile(filePath);
|
|
127
|
+
if (!file) {
|
|
128
|
+
this.logger.error(`File not found: ${filePath}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.logger.section(`Fixing: ${file.relativePath}`);
|
|
133
|
+
console.log(chalk.gray(`Fix type: ${options.type} | Language: ${file.language}`));
|
|
134
|
+
console.log();
|
|
135
|
+
|
|
136
|
+
const spinner = ora({
|
|
137
|
+
text: `Analyzing for ${options.type} issues...`,
|
|
138
|
+
spinner: 'dots',
|
|
139
|
+
}).start();
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const result = await this.api.fixCode(file.content, file.language, options.type);
|
|
143
|
+
|
|
144
|
+
spinner.stop();
|
|
145
|
+
|
|
146
|
+
if (result.changes.length === 0) {
|
|
147
|
+
this.logger.success('No issues found!');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Show fixes
|
|
152
|
+
this.logger.section(`Found ${result.changes.length} issue(s)`);
|
|
153
|
+
|
|
154
|
+
result.changes.forEach((change, i) => {
|
|
155
|
+
console.log(chalk.yellow(`${i + 1}. Line ${change.line}:`));
|
|
156
|
+
console.log(chalk.red(` - ${change.before}`));
|
|
157
|
+
console.log(chalk.green(` + ${change.after}`));
|
|
158
|
+
console.log(chalk.gray(` Reason: ${change.reason}`));
|
|
159
|
+
console.log();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Apply or confirm
|
|
163
|
+
if (options.apply) {
|
|
164
|
+
await this.applyFix(file.path, file.content, result.fixed);
|
|
165
|
+
} else {
|
|
166
|
+
await this.showDiffAndConfirm(file.path, file.content, result.fixed);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
} catch (error) {
|
|
170
|
+
spinner.stop();
|
|
171
|
+
this.logger.error('Fix failed:', (error as Error).message);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private extractCode(response: string, language: string): string | null {
|
|
176
|
+
// Try to extract code block
|
|
177
|
+
const codeBlockRegex = new RegExp(`\`\`\`(?:${language})?\\n([\\s\\S]*?)\`\`\``, 'i');
|
|
178
|
+
const match = response.match(codeBlockRegex);
|
|
179
|
+
|
|
180
|
+
if (match) {
|
|
181
|
+
return match[1].trim();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// If no code block, check if response looks like code
|
|
185
|
+
const trimmed = response.trim();
|
|
186
|
+
if (!trimmed.startsWith('```') && !trimmed.includes('Here') && !trimmed.includes('I ')) {
|
|
187
|
+
return trimmed;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private async showDiffAndConfirm(
|
|
194
|
+
filePath: string,
|
|
195
|
+
original: string,
|
|
196
|
+
modified: string
|
|
197
|
+
): Promise<void> {
|
|
198
|
+
const diff = this.fileUtils.createDiff(original, modified);
|
|
199
|
+
|
|
200
|
+
if (diff.added.length === 0 && diff.removed.length === 0) {
|
|
201
|
+
this.logger.info('No changes detected');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Show diff
|
|
206
|
+
this.logger.section('Changes');
|
|
207
|
+
|
|
208
|
+
diff.removed.forEach(line => {
|
|
209
|
+
console.log(chalk.red(line));
|
|
210
|
+
});
|
|
211
|
+
diff.added.forEach(line => {
|
|
212
|
+
console.log(chalk.green(line));
|
|
213
|
+
});
|
|
214
|
+
console.log();
|
|
215
|
+
|
|
216
|
+
// Confirm
|
|
217
|
+
const showDiffs = this.config.get('preferences').showDiffs;
|
|
218
|
+
const autoApply = this.config.get('preferences').autoApplyFixes;
|
|
219
|
+
|
|
220
|
+
if (autoApply) {
|
|
221
|
+
await this.applyFix(filePath, original, modified);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const { action } = await inquirer.prompt([
|
|
226
|
+
{
|
|
227
|
+
type: 'list',
|
|
228
|
+
name: 'action',
|
|
229
|
+
message: 'What would you like to do?',
|
|
230
|
+
choices: [
|
|
231
|
+
{ name: 'Apply changes', value: 'apply' },
|
|
232
|
+
{ name: 'View full diff', value: 'view' },
|
|
233
|
+
{ name: 'Discard changes', value: 'discard' },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
switch (action) {
|
|
239
|
+
case 'apply':
|
|
240
|
+
await this.applyFix(filePath, original, modified);
|
|
241
|
+
break;
|
|
242
|
+
case 'view':
|
|
243
|
+
this.showFullDiff(original, modified);
|
|
244
|
+
const { confirm } = await inquirer.prompt([
|
|
245
|
+
{
|
|
246
|
+
type: 'confirm',
|
|
247
|
+
name: 'confirm',
|
|
248
|
+
message: 'Apply these changes?',
|
|
249
|
+
default: true,
|
|
250
|
+
},
|
|
251
|
+
]);
|
|
252
|
+
if (confirm) {
|
|
253
|
+
await this.applyFix(filePath, original, modified);
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
case 'discard':
|
|
257
|
+
this.logger.info('Changes discarded');
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private showFullDiff(original: string, modified: string): void {
|
|
263
|
+
const originalLines = original.split('\n');
|
|
264
|
+
const modifiedLines = modified.split('\n');
|
|
265
|
+
|
|
266
|
+
console.log();
|
|
267
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
268
|
+
|
|
269
|
+
const maxLen = Math.max(originalLines.length, modifiedLines.length);
|
|
270
|
+
|
|
271
|
+
for (let i = 0; i < maxLen; i++) {
|
|
272
|
+
const orig = originalLines[i];
|
|
273
|
+
const mod = modifiedLines[i];
|
|
274
|
+
const lineNum = String(i + 1).padStart(4, ' ');
|
|
275
|
+
|
|
276
|
+
if (orig === mod) {
|
|
277
|
+
console.log(chalk.gray(`${lineNum} │ ${orig || ''}`));
|
|
278
|
+
} else {
|
|
279
|
+
if (orig !== undefined) {
|
|
280
|
+
console.log(chalk.red(`${lineNum} - ${orig}`));
|
|
281
|
+
}
|
|
282
|
+
if (mod !== undefined) {
|
|
283
|
+
console.log(chalk.green(`${lineNum} + ${mod}`));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
289
|
+
console.log();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private async applyFix(
|
|
293
|
+
filePath: string,
|
|
294
|
+
original: string,
|
|
295
|
+
modified: string
|
|
296
|
+
): Promise<void> {
|
|
297
|
+
// Create backup
|
|
298
|
+
const backup = this.fileUtils.backupFile(filePath);
|
|
299
|
+
if (backup) {
|
|
300
|
+
this.logger.info(`Backup: ${chalk.gray(backup)}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Apply changes
|
|
304
|
+
if (this.fileUtils.writeFile(filePath, modified)) {
|
|
305
|
+
this.logger.success(`Changes applied to ${filePath}`);
|
|
306
|
+
} else {
|
|
307
|
+
this.logger.error('Failed to write changes');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|