codebakers 2.0.4 ā 2.1.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/dist/advisors-J3S64IZK.js +7 -0
- package/dist/chunk-FWQNLNTI.js +565 -0
- package/dist/chunk-RCC7FYEU.js +319 -0
- package/dist/chunk-YGVDLNXY.js +326 -0
- package/dist/index.js +3704 -1907
- package/dist/prd-HBUCYLVG.js +7 -0
- package/package.json +1 -1
- package/src/commands/build.ts +989 -0
- package/src/commands/code.ts +102 -5
- package/src/commands/integrate.ts +985 -0
- package/src/commands/prd-maker.ts +587 -0
- package/src/index.ts +134 -4
- package/src/utils/files.ts +418 -0
- package/src/utils/nlp.ts +312 -0
- package/src/utils/voice.ts +323 -0
package/src/index.ts
CHANGED
|
@@ -23,8 +23,12 @@ import { designCommand } from './commands/design.js';
|
|
|
23
23
|
import { prdCommand } from './commands/prd.js';
|
|
24
24
|
import { advisorsCommand } from './commands/advisors.js';
|
|
25
25
|
import { migrateCommand } from './commands/migrate.js';
|
|
26
|
+
import { prdMakerCommand } from './commands/prd-maker.js';
|
|
27
|
+
import { buildCommand } from './commands/build.js';
|
|
28
|
+
import { integrateCommand, INTEGRATIONS } from './commands/integrate.js';
|
|
29
|
+
import { parseNaturalLanguage, clarifyCommand, clarifyDeployTarget } from './utils/nlp.js';
|
|
26
30
|
|
|
27
|
-
const VERSION = '1.0
|
|
31
|
+
const VERSION = '2.1.0';
|
|
28
32
|
|
|
29
33
|
// ASCII art logo
|
|
30
34
|
const logo = `
|
|
@@ -60,9 +64,10 @@ async function showMainMenu(): Promise<void> {
|
|
|
60
64
|
|
|
61
65
|
// Main menu
|
|
62
66
|
const action = await p.select({
|
|
63
|
-
message: 'What do you want to do?',
|
|
67
|
+
message: 'What do you want to do? (or type naturally)',
|
|
64
68
|
options: inProject ? [
|
|
65
69
|
{ value: 'code', label: 'š¬ Code with AI', hint: 'build features, fix bugs' },
|
|
70
|
+
{ value: 'integrate', label: 'š One-Click Integrations', hint: '50+ services, browser auth' },
|
|
66
71
|
{ value: 'check', label: 'š Check code quality', hint: 'run pattern enforcement' },
|
|
67
72
|
{ value: 'deploy', label: 'š Deploy', hint: 'deploy to Vercel' },
|
|
68
73
|
{ value: 'migrate', label: 'šļø Database migrations', hint: 'push, generate, status' },
|
|
@@ -77,13 +82,17 @@ async function showMainMenu(): Promise<void> {
|
|
|
77
82
|
{ value: 'design', label: 'šØ Design system', hint: 'set profile, colors' },
|
|
78
83
|
{ value: 'separator2', label: 'āāāāāāāāāāāāāāāāāāāāāāāāā' },
|
|
79
84
|
{ value: 'new', label: 'š Create new project' },
|
|
80
|
-
{ value: '
|
|
85
|
+
{ value: 'build', label: 'šļø Parallel Build', hint: '3 agents from PRD (swarm)' },
|
|
86
|
+
{ value: 'prd', label: 'š Build from PRD', hint: 'sequential build' },
|
|
87
|
+
{ value: 'prd-maker', label: 'āļø Create PRD', hint: 'interview ā generate PRD' },
|
|
81
88
|
{ value: 'advisors', label: 'š Dream Team Advisors', hint: 'consult with experts' },
|
|
82
89
|
{ value: 'settings', label: 'āļø Settings' },
|
|
83
90
|
{ value: 'help', label: 'ā Help' },
|
|
84
91
|
] : [
|
|
85
92
|
{ value: 'new', label: 'š Create new project' },
|
|
86
|
-
{ value: '
|
|
93
|
+
{ value: 'build', label: 'šļø Parallel Build', hint: '3 agents from PRD (swarm)' },
|
|
94
|
+
{ value: 'prd', label: 'š Build from PRD', hint: 'sequential build' },
|
|
95
|
+
{ value: 'prd-maker', label: 'āļø Create PRD', hint: 'interview ā generate PRD' },
|
|
87
96
|
{ value: 'advisors', label: 'š Dream Team Advisors', hint: 'consult with experts' },
|
|
88
97
|
{ value: 'open', label: 'š Open existing project' },
|
|
89
98
|
{ value: 'status', label: 'š View all projects' },
|
|
@@ -106,6 +115,9 @@ async function showMainMenu(): Promise<void> {
|
|
|
106
115
|
case 'code':
|
|
107
116
|
await codeCommand();
|
|
108
117
|
break;
|
|
118
|
+
case 'integrate':
|
|
119
|
+
await integrateCommand();
|
|
120
|
+
break;
|
|
109
121
|
case 'check':
|
|
110
122
|
await checkCommand();
|
|
111
123
|
break;
|
|
@@ -142,9 +154,15 @@ async function showMainMenu(): Promise<void> {
|
|
|
142
154
|
case 'new':
|
|
143
155
|
await initCommand();
|
|
144
156
|
break;
|
|
157
|
+
case 'build':
|
|
158
|
+
await buildCommand();
|
|
159
|
+
break;
|
|
145
160
|
case 'prd':
|
|
146
161
|
await prdCommand();
|
|
147
162
|
break;
|
|
163
|
+
case 'prd-maker':
|
|
164
|
+
await prdMakerCommand();
|
|
165
|
+
break;
|
|
148
166
|
case 'advisors':
|
|
149
167
|
await advisorsCommand();
|
|
150
168
|
break;
|
|
@@ -301,6 +319,99 @@ program
|
|
|
301
319
|
.option('--status', 'Check migration status')
|
|
302
320
|
.action(migrateCommand);
|
|
303
321
|
|
|
322
|
+
program
|
|
323
|
+
.command('prd-maker')
|
|
324
|
+
.alias('create-prd')
|
|
325
|
+
.description('Create a PRD through guided interview (supports voice input)')
|
|
326
|
+
.action(prdMakerCommand);
|
|
327
|
+
|
|
328
|
+
program
|
|
329
|
+
.command('build [prd-file]')
|
|
330
|
+
.alias('swarm')
|
|
331
|
+
.description('Parallel build with 3 AI agents (self-healing)')
|
|
332
|
+
.option('--sequential', 'Disable parallel execution')
|
|
333
|
+
.action(buildCommand);
|
|
334
|
+
|
|
335
|
+
program
|
|
336
|
+
.command('integrate [integration]')
|
|
337
|
+
.alias('add')
|
|
338
|
+
.description('One-click integrations (50+ services with browser auth)')
|
|
339
|
+
.action(integrateCommand);
|
|
340
|
+
|
|
341
|
+
// Natural language processing for free-form input
|
|
342
|
+
async function handleNaturalLanguage(input: string): Promise<void> {
|
|
343
|
+
const config = new Config();
|
|
344
|
+
|
|
345
|
+
console.log(chalk.dim('\n Parsing your request...\n'));
|
|
346
|
+
|
|
347
|
+
const parsed = await parseNaturalLanguage(input, config);
|
|
348
|
+
|
|
349
|
+
if (!parsed) {
|
|
350
|
+
// Fall back to code command
|
|
351
|
+
await codeCommand(input);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Clarify if needed
|
|
356
|
+
const clarified = await clarifyCommand(parsed);
|
|
357
|
+
|
|
358
|
+
if (clarified.command === 'cancel') {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Execute the command
|
|
363
|
+
switch (clarified.command) {
|
|
364
|
+
case 'init':
|
|
365
|
+
await initCommand();
|
|
366
|
+
break;
|
|
367
|
+
case 'build':
|
|
368
|
+
await buildCommand(clarified.args[0]);
|
|
369
|
+
break;
|
|
370
|
+
case 'code':
|
|
371
|
+
await codeCommand(clarified.args.join(' '));
|
|
372
|
+
break;
|
|
373
|
+
case 'check':
|
|
374
|
+
await checkCommand();
|
|
375
|
+
break;
|
|
376
|
+
case 'deploy':
|
|
377
|
+
const target = await clarifyDeployTarget();
|
|
378
|
+
if (target) {
|
|
379
|
+
await deployCommand({ preview: target === 'preview' });
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
case 'fix':
|
|
383
|
+
await fixCommand();
|
|
384
|
+
break;
|
|
385
|
+
case 'migrate':
|
|
386
|
+
await migrateCommand();
|
|
387
|
+
break;
|
|
388
|
+
case 'design':
|
|
389
|
+
await designCommand();
|
|
390
|
+
break;
|
|
391
|
+
case 'advisors':
|
|
392
|
+
await advisorsCommand();
|
|
393
|
+
break;
|
|
394
|
+
case 'prd-maker':
|
|
395
|
+
await prdMakerCommand();
|
|
396
|
+
break;
|
|
397
|
+
case 'security':
|
|
398
|
+
await securityCommand();
|
|
399
|
+
break;
|
|
400
|
+
case 'status':
|
|
401
|
+
await statusCommand();
|
|
402
|
+
break;
|
|
403
|
+
case 'setup':
|
|
404
|
+
await setupCommand();
|
|
405
|
+
break;
|
|
406
|
+
case 'help':
|
|
407
|
+
showHelp();
|
|
408
|
+
break;
|
|
409
|
+
default:
|
|
410
|
+
// Unknown command - pass to code agent
|
|
411
|
+
await codeCommand(input);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
304
415
|
// Parse args or show menu
|
|
305
416
|
const args = process.argv.slice(2);
|
|
306
417
|
|
|
@@ -310,6 +421,25 @@ if (args.length === 0) {
|
|
|
310
421
|
|
|
311
422
|
// Show interactive menu
|
|
312
423
|
showMainMenu().catch(console.error);
|
|
424
|
+
} else if (args.length > 0 && args[0] && !args[0].startsWith('-')) {
|
|
425
|
+
// Check if it's a known command
|
|
426
|
+
const knownCommands = [
|
|
427
|
+
'setup', 'init', 'code', 'check', 'deploy', 'fix', 'generate',
|
|
428
|
+
'connect', 'status', 'gateway', 'security', 'learn', 'design',
|
|
429
|
+
'prd', 'advisors', 'migrate', 'prd-maker', 'build', 'swarm',
|
|
430
|
+
'integrate', 'add', 'help'
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
const firstArg = args[0] as string;
|
|
434
|
+
|
|
435
|
+
if (knownCommands.includes(firstArg)) {
|
|
436
|
+
program.parse();
|
|
437
|
+
} else {
|
|
438
|
+
// Natural language input
|
|
439
|
+
const input = args.join(' ');
|
|
440
|
+
checkForUpdates().catch(() => {});
|
|
441
|
+
handleNaturalLanguage(input).catch(console.error);
|
|
442
|
+
}
|
|
313
443
|
} else {
|
|
314
444
|
program.parse();
|
|
315
445
|
}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as fs from 'fs-extra';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { execa } from 'execa';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Read content from clipboard
|
|
9
|
+
*/
|
|
10
|
+
export async function readClipboard(): Promise<string | null> {
|
|
11
|
+
try {
|
|
12
|
+
if (process.platform === 'win32') {
|
|
13
|
+
// Windows: PowerShell Get-Clipboard
|
|
14
|
+
const result = await execa('powershell', ['-Command', 'Get-Clipboard'], { timeout: 5000 });
|
|
15
|
+
return result.stdout;
|
|
16
|
+
} else if (process.platform === 'darwin') {
|
|
17
|
+
// macOS: pbpaste
|
|
18
|
+
const result = await execa('pbpaste', [], { timeout: 5000 });
|
|
19
|
+
return result.stdout;
|
|
20
|
+
} else {
|
|
21
|
+
// Linux: xclip or xsel
|
|
22
|
+
try {
|
|
23
|
+
const result = await execa('xclip', ['-selection', 'clipboard', '-o'], { timeout: 5000 });
|
|
24
|
+
return result.stdout;
|
|
25
|
+
} catch {
|
|
26
|
+
const result = await execa('xsel', ['--clipboard', '--output'], { timeout: 5000 });
|
|
27
|
+
return result.stdout;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Read a file from path (supports drag & drop paths)
|
|
37
|
+
*/
|
|
38
|
+
export async function readFile(filePath: string): Promise<{ content: string; type: string; name: string } | null> {
|
|
39
|
+
try {
|
|
40
|
+
// Clean up the path (remove quotes, handle drag & drop formatting)
|
|
41
|
+
let cleanPath = filePath.trim();
|
|
42
|
+
|
|
43
|
+
// Remove surrounding quotes (common when drag & dropping)
|
|
44
|
+
if ((cleanPath.startsWith('"') && cleanPath.endsWith('"')) ||
|
|
45
|
+
(cleanPath.startsWith("'") && cleanPath.endsWith("'"))) {
|
|
46
|
+
cleanPath = cleanPath.slice(1, -1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Handle Windows paths with spaces
|
|
50
|
+
cleanPath = cleanPath.replace(/\\ /g, ' ');
|
|
51
|
+
|
|
52
|
+
// Check if file exists
|
|
53
|
+
if (!await fs.pathExists(cleanPath)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const stats = await fs.stat(cleanPath);
|
|
58
|
+
|
|
59
|
+
// Don't read files larger than 10MB
|
|
60
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
61
|
+
console.log(chalk.yellow('File too large (max 10MB)'));
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const ext = path.extname(cleanPath).toLowerCase();
|
|
66
|
+
const name = path.basename(cleanPath);
|
|
67
|
+
|
|
68
|
+
// Determine file type
|
|
69
|
+
const textExtensions = [
|
|
70
|
+
'.txt', '.md', '.json', '.js', '.ts', '.tsx', '.jsx',
|
|
71
|
+
'.css', '.scss', '.html', '.xml', '.yaml', '.yml',
|
|
72
|
+
'.py', '.rb', '.go', '.rs', '.java', '.c', '.cpp', '.h',
|
|
73
|
+
'.sql', '.sh', '.bash', '.zsh', '.fish', '.ps1',
|
|
74
|
+
'.env', '.gitignore', '.dockerfile', '.toml', '.ini', '.cfg',
|
|
75
|
+
'.csv', '.log', '.mdx', '.astro', '.vue', '.svelte',
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp'];
|
|
79
|
+
const pdfExtensions = ['.pdf'];
|
|
80
|
+
|
|
81
|
+
if (textExtensions.includes(ext) || ext === '') {
|
|
82
|
+
// Read as text
|
|
83
|
+
const content = await fs.readFile(cleanPath, 'utf-8');
|
|
84
|
+
return { content, type: 'text', name };
|
|
85
|
+
} else if (imageExtensions.includes(ext)) {
|
|
86
|
+
// Read as base64 for images
|
|
87
|
+
const buffer = await fs.readFile(cleanPath);
|
|
88
|
+
const base64 = buffer.toString('base64');
|
|
89
|
+
const mimeType = getMimeType(ext);
|
|
90
|
+
return {
|
|
91
|
+
content: `data:${mimeType};base64,${base64}`,
|
|
92
|
+
type: 'image',
|
|
93
|
+
name
|
|
94
|
+
};
|
|
95
|
+
} else if (pdfExtensions.includes(ext)) {
|
|
96
|
+
// For PDFs, try to extract text
|
|
97
|
+
const text = await extractPdfText(cleanPath);
|
|
98
|
+
if (text) {
|
|
99
|
+
return { content: text, type: 'pdf', name };
|
|
100
|
+
}
|
|
101
|
+
// Fall back to noting it's a PDF
|
|
102
|
+
return {
|
|
103
|
+
content: `[PDF file: ${name} - text extraction not available]`,
|
|
104
|
+
type: 'pdf',
|
|
105
|
+
name
|
|
106
|
+
};
|
|
107
|
+
} else {
|
|
108
|
+
// Try to read as text anyway
|
|
109
|
+
try {
|
|
110
|
+
const content = await fs.readFile(cleanPath, 'utf-8');
|
|
111
|
+
// Check if it's actually text (not binary)
|
|
112
|
+
if (content.includes('\0')) {
|
|
113
|
+
return {
|
|
114
|
+
content: `[Binary file: ${name}]`,
|
|
115
|
+
type: 'binary',
|
|
116
|
+
name
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { content, type: 'text', name };
|
|
120
|
+
} catch {
|
|
121
|
+
return {
|
|
122
|
+
content: `[Unsupported file type: ${name}]`,
|
|
123
|
+
type: 'unknown',
|
|
124
|
+
name
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.log(chalk.red(`Error reading file: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Extract text from PDF using pdftotext or pdf-parse
|
|
136
|
+
*/
|
|
137
|
+
async function extractPdfText(filePath: string): Promise<string | null> {
|
|
138
|
+
try {
|
|
139
|
+
// Try pdftotext (poppler-utils)
|
|
140
|
+
const result = await execa('pdftotext', [filePath, '-'], { timeout: 30000 });
|
|
141
|
+
return result.stdout;
|
|
142
|
+
} catch {
|
|
143
|
+
// pdftotext not available
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getMimeType(ext: string): string {
|
|
149
|
+
const mimeTypes: Record<string, string> = {
|
|
150
|
+
'.png': 'image/png',
|
|
151
|
+
'.jpg': 'image/jpeg',
|
|
152
|
+
'.jpeg': 'image/jpeg',
|
|
153
|
+
'.gif': 'image/gif',
|
|
154
|
+
'.webp': 'image/webp',
|
|
155
|
+
'.svg': 'image/svg+xml',
|
|
156
|
+
'.bmp': 'image/bmp',
|
|
157
|
+
};
|
|
158
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Watch for file drops/pastes and integrate with text input
|
|
163
|
+
* Returns enhanced input with file contents
|
|
164
|
+
*/
|
|
165
|
+
export async function textWithFileSupport(options: {
|
|
166
|
+
message: string;
|
|
167
|
+
placeholder?: string;
|
|
168
|
+
}): Promise<{ text: string; files: Array<{ name: string; content: string; type: string }> } | symbol> {
|
|
169
|
+
|
|
170
|
+
const hint = chalk.dim(' (paste file path, or "clip" for clipboard)');
|
|
171
|
+
|
|
172
|
+
const input = await p.text({
|
|
173
|
+
message: options.message + hint,
|
|
174
|
+
placeholder: options.placeholder,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (p.isCancel(input)) return input;
|
|
178
|
+
|
|
179
|
+
const text = (input as string).trim();
|
|
180
|
+
const files: Array<{ name: string; content: string; type: string }> = [];
|
|
181
|
+
|
|
182
|
+
// Check for clipboard command
|
|
183
|
+
if (text.toLowerCase() === 'clip' || text.toLowerCase() === 'clipboard' || text.toLowerCase() === 'paste') {
|
|
184
|
+
const clipContent = await readClipboard();
|
|
185
|
+
if (clipContent) {
|
|
186
|
+
console.log(chalk.green(`\nš Read ${clipContent.length} characters from clipboard\n`));
|
|
187
|
+
|
|
188
|
+
// Check if clipboard contains a file path
|
|
189
|
+
if (await fs.pathExists(clipContent.trim())) {
|
|
190
|
+
const file = await readFile(clipContent.trim());
|
|
191
|
+
if (file) {
|
|
192
|
+
files.push(file);
|
|
193
|
+
return { text: `[File: ${file.name}]`, files };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Otherwise treat as text
|
|
198
|
+
files.push({ name: 'clipboard', content: clipContent, type: 'text' });
|
|
199
|
+
|
|
200
|
+
const preview = clipContent.length > 100
|
|
201
|
+
? clipContent.slice(0, 100) + '...'
|
|
202
|
+
: clipContent;
|
|
203
|
+
console.log(chalk.dim(`Preview: ${preview}\n`));
|
|
204
|
+
|
|
205
|
+
// Ask what to do with clipboard content
|
|
206
|
+
const action = await p.select({
|
|
207
|
+
message: 'What would you like me to do with this?',
|
|
208
|
+
options: [
|
|
209
|
+
{ value: 'analyze', label: 'š Analyze this code/text' },
|
|
210
|
+
{ value: 'fix', label: 'š§ Fix any issues' },
|
|
211
|
+
{ value: 'explain', label: 'š Explain this' },
|
|
212
|
+
{ value: 'improve', label: '⨠Improve/refactor' },
|
|
213
|
+
{ value: 'convert', label: 'š Convert to different format' },
|
|
214
|
+
{ value: 'custom', label: 'āļø Custom instruction' },
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (p.isCancel(action)) return action;
|
|
219
|
+
|
|
220
|
+
if (action === 'custom') {
|
|
221
|
+
const customPrompt = await p.text({
|
|
222
|
+
message: 'What should I do with this?',
|
|
223
|
+
placeholder: 'Add error handling to this function',
|
|
224
|
+
});
|
|
225
|
+
if (p.isCancel(customPrompt)) return customPrompt;
|
|
226
|
+
return { text: customPrompt as string, files };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const actionText: Record<string, string> = {
|
|
230
|
+
analyze: 'Analyze this code/text and explain what it does',
|
|
231
|
+
fix: 'Fix any bugs, issues, or problems in this code',
|
|
232
|
+
explain: 'Explain this code/text in detail',
|
|
233
|
+
improve: 'Improve and refactor this code for better quality',
|
|
234
|
+
convert: 'Convert this to a better format or structure',
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
return { text: actionText[action as string], files };
|
|
238
|
+
} else {
|
|
239
|
+
console.log(chalk.yellow('Clipboard is empty'));
|
|
240
|
+
// Re-prompt
|
|
241
|
+
return textWithFileSupport(options);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check if input is a file path
|
|
246
|
+
if (await fs.pathExists(text)) {
|
|
247
|
+
const file = await readFile(text);
|
|
248
|
+
if (file) {
|
|
249
|
+
files.push(file);
|
|
250
|
+
console.log(chalk.green(`\nš Read file: ${file.name} (${file.type})\n`));
|
|
251
|
+
|
|
252
|
+
// Ask what to do
|
|
253
|
+
const action = await p.select({
|
|
254
|
+
message: `What should I do with ${file.name}?`,
|
|
255
|
+
options: [
|
|
256
|
+
{ value: 'analyze', label: 'š Analyze this file' },
|
|
257
|
+
{ value: 'fix', label: 'š§ Fix any issues' },
|
|
258
|
+
{ value: 'explain', label: 'š Explain this' },
|
|
259
|
+
{ value: 'improve', label: '⨠Improve/refactor' },
|
|
260
|
+
{ value: 'custom', label: 'āļø Custom instruction' },
|
|
261
|
+
],
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (p.isCancel(action)) return action;
|
|
265
|
+
|
|
266
|
+
if (action === 'custom') {
|
|
267
|
+
const customPrompt = await p.text({
|
|
268
|
+
message: 'What should I do with this file?',
|
|
269
|
+
placeholder: 'Add TypeScript types to this file',
|
|
270
|
+
});
|
|
271
|
+
if (p.isCancel(customPrompt)) return customPrompt;
|
|
272
|
+
return { text: customPrompt as string, files };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const actionText: Record<string, string> = {
|
|
276
|
+
analyze: `Analyze this file (${file.name}) and explain what it does`,
|
|
277
|
+
fix: `Fix any bugs or issues in this file (${file.name})`,
|
|
278
|
+
explain: `Explain this file (${file.name}) in detail`,
|
|
279
|
+
improve: `Improve and refactor this file (${file.name})`,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return { text: actionText[action as string], files };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check if input contains multiple file paths (drag & drop multiple)
|
|
287
|
+
const potentialPaths = text.split('\n').map(p => p.trim()).filter(Boolean);
|
|
288
|
+
if (potentialPaths.length > 1) {
|
|
289
|
+
for (const potentialPath of potentialPaths) {
|
|
290
|
+
if (await fs.pathExists(potentialPath)) {
|
|
291
|
+
const file = await readFile(potentialPath);
|
|
292
|
+
if (file) {
|
|
293
|
+
files.push(file);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (files.length > 0) {
|
|
299
|
+
console.log(chalk.green(`\nš Read ${files.length} files: ${files.map(f => f.name).join(', ')}\n`));
|
|
300
|
+
|
|
301
|
+
const instruction = await p.text({
|
|
302
|
+
message: 'What should I do with these files?',
|
|
303
|
+
placeholder: 'Combine these into a single component',
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (p.isCancel(instruction)) return instruction;
|
|
307
|
+
return { text: instruction as string, files };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Regular text input
|
|
312
|
+
return { text, files };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Format files for Claude context
|
|
317
|
+
*/
|
|
318
|
+
export function formatFilesForContext(files: Array<{ name: string; content: string; type: string }>): string {
|
|
319
|
+
if (files.length === 0) return '';
|
|
320
|
+
|
|
321
|
+
let context = '\n\n--- ATTACHED FILES ---\n\n';
|
|
322
|
+
|
|
323
|
+
for (const file of files) {
|
|
324
|
+
if (file.type === 'image') {
|
|
325
|
+
context += `[Image: ${file.name}]\n`;
|
|
326
|
+
context += `${file.content}\n\n`; // base64 data URL
|
|
327
|
+
} else {
|
|
328
|
+
context += `--- ${file.name} ---\n`;
|
|
329
|
+
context += '```\n';
|
|
330
|
+
context += file.content;
|
|
331
|
+
context += '\n```\n\n';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
context += '--- END FILES ---\n\n';
|
|
336
|
+
|
|
337
|
+
return context;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Quick paste detection - check if text looks like it was pasted
|
|
342
|
+
*/
|
|
343
|
+
export function looksLikePaste(text: string): boolean {
|
|
344
|
+
// Multi-line content is likely pasted
|
|
345
|
+
if (text.includes('\n') && text.split('\n').length > 3) return true;
|
|
346
|
+
|
|
347
|
+
// Code-like content
|
|
348
|
+
if (text.includes('function ') || text.includes('const ') ||
|
|
349
|
+
text.includes('import ') || text.includes('export ') ||
|
|
350
|
+
text.includes('class ') || text.includes('def ') ||
|
|
351
|
+
text.includes('public ') || text.includes('private ')) return true;
|
|
352
|
+
|
|
353
|
+
// JSON/object-like
|
|
354
|
+
if ((text.startsWith('{') && text.endsWith('}')) ||
|
|
355
|
+
(text.startsWith('[') && text.endsWith(']'))) return true;
|
|
356
|
+
|
|
357
|
+
// Very long single line
|
|
358
|
+
if (text.length > 200 && !text.includes('\n')) return true;
|
|
359
|
+
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Handle pasted content intelligently
|
|
365
|
+
*/
|
|
366
|
+
export async function handlePastedContent(text: string): Promise<{ prompt: string; context: string } | null> {
|
|
367
|
+
if (!looksLikePaste(text)) {
|
|
368
|
+
return null; // Not pasted content, handle normally
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
console.log(chalk.cyan('\nš Detected pasted content!\n'));
|
|
372
|
+
|
|
373
|
+
const preview = text.length > 200 ? text.slice(0, 200) + '...' : text;
|
|
374
|
+
console.log(chalk.dim(preview));
|
|
375
|
+
console.log('');
|
|
376
|
+
|
|
377
|
+
const action = await p.select({
|
|
378
|
+
message: 'What should I do with this?',
|
|
379
|
+
options: [
|
|
380
|
+
{ value: 'analyze', label: 'š Analyze this code' },
|
|
381
|
+
{ value: 'fix', label: 'š§ Fix any issues' },
|
|
382
|
+
{ value: 'explain', label: 'š Explain this' },
|
|
383
|
+
{ value: 'improve', label: '⨠Improve/refactor' },
|
|
384
|
+
{ value: 'custom', label: 'āļø Add my own instruction' },
|
|
385
|
+
{ value: 'literal', label: 'š Use as literal prompt (not code)' },
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
if (p.isCancel(action)) return null;
|
|
390
|
+
|
|
391
|
+
if (action === 'literal') {
|
|
392
|
+
return { prompt: text, context: '' };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (action === 'custom') {
|
|
396
|
+
const instruction = await p.text({
|
|
397
|
+
message: 'What should I do?',
|
|
398
|
+
placeholder: 'Add error handling and types',
|
|
399
|
+
});
|
|
400
|
+
if (p.isCancel(instruction)) return null;
|
|
401
|
+
return {
|
|
402
|
+
prompt: instruction as string,
|
|
403
|
+
context: `\n\n--- PASTED CODE ---\n\`\`\`\n${text}\n\`\`\`\n--- END ---\n\n`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const actionPrompts: Record<string, string> = {
|
|
408
|
+
analyze: 'Analyze this code and explain what it does, any issues, and potential improvements',
|
|
409
|
+
fix: 'Fix any bugs, issues, or problems in this code',
|
|
410
|
+
explain: 'Explain this code in detail, step by step',
|
|
411
|
+
improve: 'Improve and refactor this code for better quality, readability, and performance',
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
prompt: actionPrompts[action as string],
|
|
416
|
+
context: `\n\n--- PASTED CODE ---\n\`\`\`\n${text}\n\`\`\`\n--- END ---\n\n`
|
|
417
|
+
};
|
|
418
|
+
}
|