agdi 1.0.2 → 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/README.md +51 -1
- package/bin/agdi.js +1 -1
- package/dist/index.js +1068 -247
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { input as input3, select as select2, confirm as confirm2 } from "@inquirer/prompts";
|
|
5
|
+
import chalk9 from "chalk";
|
|
6
|
+
import ora4 from "ora";
|
|
8
7
|
|
|
9
8
|
// src/core/llm/index.ts
|
|
10
9
|
var PuterProvider = class {
|
|
@@ -73,33 +72,41 @@ var GeminiProvider = class {
|
|
|
73
72
|
};
|
|
74
73
|
}
|
|
75
74
|
};
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
75
|
+
var OpenRouterProvider = class {
|
|
76
|
+
config;
|
|
77
|
+
constructor(config) {
|
|
78
|
+
this.config = config;
|
|
79
|
+
}
|
|
80
|
+
async generate(prompt, systemPrompt) {
|
|
81
|
+
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
86
|
+
"HTTP-Referer": "https://agdi.dev",
|
|
87
|
+
"X-Title": "Agdi CLI"
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
model: this.config.model || "anthropic/claude-3.5-sonnet",
|
|
91
|
+
messages: [
|
|
92
|
+
...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
|
|
93
|
+
{ role: "user", content: prompt }
|
|
94
|
+
]
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const error = await response.text();
|
|
99
|
+
throw new Error(`OpenRouter API error: ${response.status} - ${error}`);
|
|
100
|
+
}
|
|
101
|
+
const data = await response.json();
|
|
102
|
+
return {
|
|
103
|
+
text: data.choices?.[0]?.message?.content || "",
|
|
104
|
+
usage: data.usage ? {
|
|
105
|
+
inputTokens: data.usage.prompt_tokens,
|
|
106
|
+
outputTokens: data.usage.completion_tokens
|
|
107
|
+
} : void 0
|
|
108
|
+
};
|
|
109
|
+
}
|
|
103
110
|
};
|
|
104
111
|
function createLLMProvider(provider, config) {
|
|
105
112
|
switch (provider) {
|
|
@@ -107,6 +114,8 @@ function createLLMProvider(provider, config) {
|
|
|
107
114
|
return new PuterProvider(config);
|
|
108
115
|
case "gemini":
|
|
109
116
|
return new GeminiProvider(config);
|
|
117
|
+
case "openrouter":
|
|
118
|
+
return new OpenRouterProvider(config);
|
|
110
119
|
default:
|
|
111
120
|
throw new Error(`Unsupported LLM provider: ${provider}`);
|
|
112
121
|
}
|
|
@@ -270,13 +279,29 @@ async function generateApp(prompt, llm, onProgress) {
|
|
|
270
279
|
type: "module",
|
|
271
280
|
scripts: {
|
|
272
281
|
dev: "vite",
|
|
273
|
-
build: "vite build",
|
|
282
|
+
build: "tsc -b && vite build",
|
|
274
283
|
preview: "vite preview"
|
|
275
284
|
},
|
|
276
|
-
dependencies:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
285
|
+
dependencies: {
|
|
286
|
+
"react": "^18.3.1",
|
|
287
|
+
"react-dom": "^18.3.1",
|
|
288
|
+
...plan.dependencies.reduce((acc, dep) => {
|
|
289
|
+
if (dep !== "react" && dep !== "react-dom") {
|
|
290
|
+
acc[dep] = "latest";
|
|
291
|
+
}
|
|
292
|
+
return acc;
|
|
293
|
+
}, {})
|
|
294
|
+
},
|
|
295
|
+
devDependencies: {
|
|
296
|
+
"@types/react": "^18.3.0",
|
|
297
|
+
"@types/react-dom": "^18.3.0",
|
|
298
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
299
|
+
"autoprefixer": "^10.4.20",
|
|
300
|
+
"postcss": "^8.4.45",
|
|
301
|
+
"tailwindcss": "^3.4.10",
|
|
302
|
+
"typescript": "~5.5.0",
|
|
303
|
+
"vite": "^5.4.0"
|
|
304
|
+
}
|
|
280
305
|
}, null, 2)
|
|
281
306
|
});
|
|
282
307
|
return { plan, files };
|
|
@@ -285,22 +310,321 @@ async function generateApp(prompt, llm, onProgress) {
|
|
|
285
310
|
// src/utils/fs.ts
|
|
286
311
|
import fs from "fs-extra";
|
|
287
312
|
import path from "path";
|
|
313
|
+
import chalk2 from "chalk";
|
|
314
|
+
|
|
315
|
+
// src/security/code-firewall.ts
|
|
316
|
+
import chalk from "chalk";
|
|
317
|
+
var MALICIOUS_PATTERNS = [
|
|
318
|
+
// ==================== HARDCODED SECRETS ====================
|
|
319
|
+
{
|
|
320
|
+
pattern: /sk-proj-[A-Za-z0-9_-]{20,}/g,
|
|
321
|
+
category: "secret",
|
|
322
|
+
description: "OpenAI API key detected",
|
|
323
|
+
severity: "critical"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
pattern: /AKIA[A-Z0-9]{16}/g,
|
|
327
|
+
category: "secret",
|
|
328
|
+
description: "AWS Access Key detected",
|
|
329
|
+
severity: "critical"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
pattern: /ghp_[A-Za-z0-9]{36}/g,
|
|
333
|
+
category: "secret",
|
|
334
|
+
description: "GitHub Personal Access Token detected",
|
|
335
|
+
severity: "critical"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
pattern: /gho_[A-Za-z0-9]{36}/g,
|
|
339
|
+
category: "secret",
|
|
340
|
+
description: "GitHub OAuth Token detected",
|
|
341
|
+
severity: "critical"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
pattern: /AIza[A-Za-z0-9_-]{35}/g,
|
|
345
|
+
category: "secret",
|
|
346
|
+
description: "Google API key detected",
|
|
347
|
+
severity: "critical"
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
pattern: /xox[baprs]-[A-Za-z0-9-]{10,}/g,
|
|
351
|
+
category: "secret",
|
|
352
|
+
description: "Slack token detected",
|
|
353
|
+
severity: "critical"
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
pattern: /sk_live_[A-Za-z0-9]{24,}/g,
|
|
357
|
+
category: "secret",
|
|
358
|
+
description: "Stripe live key detected",
|
|
359
|
+
severity: "critical"
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
pattern: /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
|
|
363
|
+
category: "secret",
|
|
364
|
+
description: "Private key detected",
|
|
365
|
+
severity: "critical"
|
|
366
|
+
},
|
|
367
|
+
// ==================== DANGEROUS CODE PATTERNS ====================
|
|
368
|
+
{
|
|
369
|
+
pattern: /\beval\s*\(/g,
|
|
370
|
+
category: "dangerous",
|
|
371
|
+
description: "eval() usage - code injection risk",
|
|
372
|
+
severity: "high"
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
pattern: /new\s+Function\s*\(/g,
|
|
376
|
+
category: "dangerous",
|
|
377
|
+
description: "Dynamic function creation - code injection risk",
|
|
378
|
+
severity: "high"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
pattern: /exec\s*\(\s*['"`][^'"`]*\$\{/g,
|
|
382
|
+
category: "dangerous",
|
|
383
|
+
description: "Shell command with variable interpolation",
|
|
384
|
+
severity: "high"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
pattern: /child_process\s*\.\s*(exec|spawn|execSync)\s*\([^)]*\$\{/g,
|
|
388
|
+
category: "dangerous",
|
|
389
|
+
description: "Shell command injection via child_process",
|
|
390
|
+
severity: "critical"
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
pattern: /document\s*\.\s*write\s*\(/g,
|
|
394
|
+
category: "dangerous",
|
|
395
|
+
description: "document.write() - XSS risk",
|
|
396
|
+
severity: "medium"
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
pattern: /innerHTML\s*=\s*[^;]*\$\{/g,
|
|
400
|
+
category: "dangerous",
|
|
401
|
+
description: "innerHTML with interpolation - XSS risk",
|
|
402
|
+
severity: "high"
|
|
403
|
+
},
|
|
404
|
+
// ==================== ENV/SECRET EXFILTRATION ====================
|
|
405
|
+
{
|
|
406
|
+
pattern: /JSON\s*\.\s*stringify\s*\(\s*process\s*\.\s*env\s*\)/g,
|
|
407
|
+
category: "suspicious",
|
|
408
|
+
description: "Serializing entire process.env",
|
|
409
|
+
severity: "critical"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
pattern: /console\s*\.\s*log\s*\(\s*process\s*\.\s*env\s*\)/g,
|
|
413
|
+
category: "suspicious",
|
|
414
|
+
description: "Logging process.env to console",
|
|
415
|
+
severity: "high"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
pattern: /fetch\s*\([^)]*\+\s*process\s*\.\s*env/g,
|
|
419
|
+
category: "suspicious",
|
|
420
|
+
description: "Sending env variables via network",
|
|
421
|
+
severity: "critical"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
pattern: /axios\s*\.\s*(get|post)\s*\([^)]*process\s*\.\s*env/g,
|
|
425
|
+
category: "suspicious",
|
|
426
|
+
description: "Sending env variables via axios",
|
|
427
|
+
severity: "critical"
|
|
428
|
+
},
|
|
429
|
+
// ==================== SUSPICIOUS FILE PATHS ====================
|
|
430
|
+
{
|
|
431
|
+
pattern: /\/etc\/passwd/g,
|
|
432
|
+
category: "path",
|
|
433
|
+
description: "Access to /etc/passwd",
|
|
434
|
+
severity: "critical"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
pattern: /\/etc\/shadow/g,
|
|
438
|
+
category: "path",
|
|
439
|
+
description: "Access to /etc/shadow",
|
|
440
|
+
severity: "critical"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
pattern: /C:\\Windows\\System32/gi,
|
|
444
|
+
category: "path",
|
|
445
|
+
description: "Access to Windows System32",
|
|
446
|
+
severity: "high"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
pattern: /~\/\.ssh\//g,
|
|
450
|
+
category: "path",
|
|
451
|
+
description: "Access to SSH directory",
|
|
452
|
+
severity: "critical"
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
pattern: /~\/\.aws\//g,
|
|
456
|
+
category: "path",
|
|
457
|
+
description: "Access to AWS credentials",
|
|
458
|
+
severity: "critical"
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
pattern: /~\/\.gnupg\//g,
|
|
462
|
+
category: "path",
|
|
463
|
+
description: "Access to GPG keys",
|
|
464
|
+
severity: "critical"
|
|
465
|
+
},
|
|
466
|
+
// ==================== COMMAND INJECTION ====================
|
|
467
|
+
{
|
|
468
|
+
pattern: /\$\([^)]+\)/g,
|
|
469
|
+
category: "dangerous",
|
|
470
|
+
description: "Shell command substitution",
|
|
471
|
+
severity: "medium"
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
pattern: /`[^`]*\$\{[^}]+\}[^`]*`/g,
|
|
475
|
+
category: "dangerous",
|
|
476
|
+
description: "Template literal with shell commands",
|
|
477
|
+
severity: "medium"
|
|
478
|
+
}
|
|
479
|
+
];
|
|
480
|
+
function scanCode(code, filename) {
|
|
481
|
+
const matches = [];
|
|
482
|
+
const lines = code.split("\n");
|
|
483
|
+
for (const patternDef of MALICIOUS_PATTERNS) {
|
|
484
|
+
let match;
|
|
485
|
+
const regex = new RegExp(patternDef.pattern.source, patternDef.pattern.flags);
|
|
486
|
+
while ((match = regex.exec(code)) !== null) {
|
|
487
|
+
const beforeMatch = code.substring(0, match.index);
|
|
488
|
+
const lineNumber = (beforeMatch.match(/\n/g) || []).length + 1;
|
|
489
|
+
matches.push({
|
|
490
|
+
pattern: patternDef.pattern.source,
|
|
491
|
+
category: patternDef.category,
|
|
492
|
+
description: patternDef.description,
|
|
493
|
+
severity: patternDef.severity,
|
|
494
|
+
line: lineNumber,
|
|
495
|
+
match: match[0].substring(0, 50) + (match[0].length > 50 ? "..." : "")
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
safe: matches.length === 0,
|
|
501
|
+
matches
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function shouldBlockCode(result) {
|
|
505
|
+
return result.matches.some((m) => m.severity === "critical" || m.severity === "high");
|
|
506
|
+
}
|
|
507
|
+
function displayScanResults(result, filename) {
|
|
508
|
+
if (result.safe) {
|
|
509
|
+
console.log(chalk.green("\u2705 No malicious patterns detected"));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
console.log(chalk.red.bold("\n\u{1F6A8} SECURITY SCAN FAILED"));
|
|
513
|
+
if (filename) {
|
|
514
|
+
console.log(chalk.gray(`File: ${filename}`));
|
|
515
|
+
}
|
|
516
|
+
console.log("");
|
|
517
|
+
const criticals = result.matches.filter((m) => m.severity === "critical");
|
|
518
|
+
const highs = result.matches.filter((m) => m.severity === "high");
|
|
519
|
+
const others = result.matches.filter((m) => m.severity !== "critical" && m.severity !== "high");
|
|
520
|
+
if (criticals.length > 0) {
|
|
521
|
+
console.log(chalk.red("\u{1F534} CRITICAL:"));
|
|
522
|
+
for (const m of criticals) {
|
|
523
|
+
console.log(chalk.red(` Line ${m.line}: ${m.description}`));
|
|
524
|
+
console.log(chalk.gray(` Found: ${m.match}`));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (highs.length > 0) {
|
|
528
|
+
console.log(chalk.yellow("\n\u{1F7E0} HIGH:"));
|
|
529
|
+
for (const m of highs) {
|
|
530
|
+
console.log(chalk.yellow(` Line ${m.line}: ${m.description}`));
|
|
531
|
+
console.log(chalk.gray(` Found: ${m.match}`));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (others.length > 0) {
|
|
535
|
+
console.log(chalk.cyan("\n\u{1F7E1} WARNINGS:"));
|
|
536
|
+
for (const m of others) {
|
|
537
|
+
console.log(chalk.cyan(` Line ${m.line}: ${m.description}`));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
console.log("");
|
|
541
|
+
}
|
|
542
|
+
function validateCodeBeforeWrite(code, filename) {
|
|
543
|
+
const result = scanCode(code, filename);
|
|
544
|
+
if (!result.safe) {
|
|
545
|
+
displayScanResults(result, filename);
|
|
546
|
+
if (shouldBlockCode(result)) {
|
|
547
|
+
console.log(chalk.red.bold("\u{1F6A8} BLOCKED: Code contains critical security issues"));
|
|
548
|
+
console.log(chalk.gray("The file will NOT be written to disk.\n"));
|
|
549
|
+
return false;
|
|
550
|
+
} else {
|
|
551
|
+
console.log(chalk.yellow("\u26A0\uFE0F Warning: Code contains potential issues but will be written.\n"));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/utils/fs.ts
|
|
288
558
|
async function writeProject(project, outputDir) {
|
|
289
559
|
await fs.ensureDir(outputDir);
|
|
560
|
+
let blockedCount = 0;
|
|
561
|
+
let writtenCount = 0;
|
|
290
562
|
for (const file of project.files) {
|
|
291
563
|
const filePath = path.join(outputDir, file.path);
|
|
564
|
+
const isSafe = validateCodeBeforeWrite(file.content, file.path);
|
|
565
|
+
if (!isSafe) {
|
|
566
|
+
blockedCount++;
|
|
567
|
+
console.log(chalk2.red(`\u26D4 BLOCKED: ${file.path}`));
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
292
570
|
await fs.ensureDir(path.dirname(filePath));
|
|
293
571
|
await fs.writeFile(filePath, file.content, "utf-8");
|
|
572
|
+
writtenCount++;
|
|
294
573
|
}
|
|
574
|
+
console.log("");
|
|
575
|
+
if (blockedCount > 0) {
|
|
576
|
+
console.log(chalk2.yellow(`\u26A0\uFE0F ${blockedCount} file(s) blocked by security scan`));
|
|
577
|
+
}
|
|
578
|
+
console.log(chalk2.green(`\u2705 ${writtenCount} file(s) written successfully`));
|
|
295
579
|
}
|
|
296
580
|
|
|
297
581
|
// src/utils/config.ts
|
|
298
582
|
import fs2 from "fs-extra";
|
|
299
583
|
import path2 from "path";
|
|
300
584
|
import os from "os";
|
|
585
|
+
import chalk3 from "chalk";
|
|
301
586
|
var CONFIG_DIR = path2.join(os.homedir(), ".agdi");
|
|
302
587
|
var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
|
|
588
|
+
var SECURE_FILE_MODE = 384;
|
|
589
|
+
var SECURE_DIR_MODE = 448;
|
|
590
|
+
function checkPermissions() {
|
|
591
|
+
try {
|
|
592
|
+
if (!fs2.existsSync(CONFIG_FILE)) {
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
const stats = fs2.statSync(CONFIG_FILE);
|
|
596
|
+
const mode = stats.mode & 511;
|
|
597
|
+
if (os.platform() === "win32") {
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
const isWorldReadable = (mode & 36) !== 0;
|
|
601
|
+
if (isWorldReadable) {
|
|
602
|
+
console.log(chalk3.yellow("\n\u26A0\uFE0F SECURITY WARNING"));
|
|
603
|
+
console.log(chalk3.gray("Your config file is readable by other users!"));
|
|
604
|
+
console.log(chalk3.gray(`File: ${CONFIG_FILE}`));
|
|
605
|
+
console.log(chalk3.gray("Run the following to fix:"));
|
|
606
|
+
console.log(chalk3.cyan(` chmod 600 "${CONFIG_FILE}"
|
|
607
|
+
`));
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
return true;
|
|
611
|
+
} catch {
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function setSecurePermissions() {
|
|
616
|
+
try {
|
|
617
|
+
if (os.platform() !== "win32") {
|
|
618
|
+
fs2.chmodSync(CONFIG_DIR, SECURE_DIR_MODE);
|
|
619
|
+
if (fs2.existsSync(CONFIG_FILE)) {
|
|
620
|
+
fs2.chmodSync(CONFIG_FILE, SECURE_FILE_MODE);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
} catch (error) {
|
|
624
|
+
}
|
|
625
|
+
}
|
|
303
626
|
function loadConfig() {
|
|
627
|
+
checkPermissions();
|
|
304
628
|
try {
|
|
305
629
|
if (fs2.existsSync(CONFIG_FILE)) {
|
|
306
630
|
return fs2.readJsonSync(CONFIG_FILE);
|
|
@@ -313,92 +637,101 @@ function saveConfig(config) {
|
|
|
313
637
|
try {
|
|
314
638
|
fs2.ensureDirSync(CONFIG_DIR);
|
|
315
639
|
fs2.writeJsonSync(CONFIG_FILE, config, { spaces: 2 });
|
|
640
|
+
setSecurePermissions();
|
|
316
641
|
} catch (error) {
|
|
317
|
-
console.error("Failed to save config:", error);
|
|
642
|
+
console.error(chalk3.red("Failed to save config:"), error);
|
|
318
643
|
}
|
|
319
644
|
}
|
|
320
645
|
|
|
321
646
|
// src/commands/auth.ts
|
|
322
647
|
import { input, select, password } from "@inquirer/prompts";
|
|
323
|
-
import
|
|
648
|
+
import chalk4 from "chalk";
|
|
324
649
|
async function login() {
|
|
325
|
-
console.log(
|
|
326
|
-
console.log(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (provider === "ollama") {
|
|
340
|
-
const ollamaUrl = await input({
|
|
341
|
-
message: "Ollama server URL:",
|
|
342
|
-
default: "http://localhost:11434"
|
|
650
|
+
console.log(chalk4.cyan.bold("\n\u{1F510} Agdi Authentication\n"));
|
|
651
|
+
console.log(chalk4.gray("Configure your API key to use Agdi CLI.\n"));
|
|
652
|
+
try {
|
|
653
|
+
const config = loadConfig();
|
|
654
|
+
const provider = await select({
|
|
655
|
+
message: "Select your AI provider:",
|
|
656
|
+
choices: [
|
|
657
|
+
{ name: "\u{1F511} Google Gemini (Recommended)", value: "gemini" },
|
|
658
|
+
{ name: "\u{1F511} OpenRouter (100+ models)", value: "openrouter" },
|
|
659
|
+
{ name: "\u{1F511} OpenAI (GPT-4, GPT-5)", value: "openai" },
|
|
660
|
+
{ name: "\u{1F511} Anthropic (Claude)", value: "anthropic" },
|
|
661
|
+
{ name: "\u{1F511} DeepSeek", value: "deepseek" },
|
|
662
|
+
{ name: "\u{1F3E0} Local LLM (Ollama)", value: "ollama" }
|
|
663
|
+
]
|
|
343
664
|
});
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
665
|
+
if (provider === "ollama") {
|
|
666
|
+
const ollamaUrl = await input({
|
|
667
|
+
message: "Ollama server URL:",
|
|
668
|
+
default: "http://localhost:11434"
|
|
669
|
+
});
|
|
670
|
+
config.ollamaUrl = ollamaUrl;
|
|
671
|
+
config.defaultProvider = "ollama";
|
|
672
|
+
saveConfig(config);
|
|
673
|
+
console.log(chalk4.green("\n\u2705 Ollama configured"));
|
|
674
|
+
console.log(chalk4.gray(`Server: ${ollamaUrl}
|
|
349
675
|
`));
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const apiKey = await password({
|
|
679
|
+
message: `Enter your ${provider} API key:`,
|
|
680
|
+
mask: "*"
|
|
681
|
+
});
|
|
682
|
+
switch (provider) {
|
|
683
|
+
case "gemini":
|
|
684
|
+
config.geminiApiKey = apiKey;
|
|
685
|
+
break;
|
|
686
|
+
case "openai":
|
|
687
|
+
config.openaiApiKey = apiKey;
|
|
688
|
+
break;
|
|
689
|
+
case "anthropic":
|
|
690
|
+
config.anthropicApiKey = apiKey;
|
|
691
|
+
break;
|
|
692
|
+
case "deepseek":
|
|
693
|
+
config.deepseekApiKey = apiKey;
|
|
694
|
+
break;
|
|
695
|
+
case "openrouter":
|
|
696
|
+
config.openrouterApiKey = apiKey;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
config.defaultProvider = provider;
|
|
700
|
+
saveConfig(config);
|
|
701
|
+
console.log(chalk4.green(`
|
|
376
702
|
\u2705 ${provider} API key saved securely`));
|
|
377
|
-
|
|
703
|
+
console.log(chalk4.gray("Keys stored in ~/.agdi/config.json\n"));
|
|
704
|
+
} catch (error) {
|
|
705
|
+
if (error.name === "ExitPromptError") {
|
|
706
|
+
console.log(chalk4.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
707
|
+
process.exit(0);
|
|
708
|
+
}
|
|
709
|
+
throw error;
|
|
710
|
+
}
|
|
378
711
|
}
|
|
379
712
|
async function showStatus() {
|
|
380
713
|
const config = loadConfig();
|
|
381
|
-
console.log(
|
|
714
|
+
console.log(chalk4.cyan.bold("\n\u{1F4CA} Authentication Status\n"));
|
|
382
715
|
const providers = [
|
|
383
716
|
{ name: "Gemini", key: config.geminiApiKey },
|
|
717
|
+
{ name: "OpenRouter", key: config.openrouterApiKey },
|
|
384
718
|
{ name: "OpenAI", key: config.openaiApiKey },
|
|
385
719
|
{ name: "Anthropic", key: config.anthropicApiKey },
|
|
386
|
-
{ name: "DeepSeek", key: config.deepseekApiKey }
|
|
387
|
-
{ name: "OpenRouter", key: config.openrouterApiKey }
|
|
720
|
+
{ name: "DeepSeek", key: config.deepseekApiKey }
|
|
388
721
|
];
|
|
389
722
|
for (const p of providers) {
|
|
390
|
-
const status = p.key ?
|
|
723
|
+
const status = p.key ? chalk4.green("\u2713 Configured") : chalk4.gray("\u2717 Not set");
|
|
391
724
|
console.log(` ${p.name.padEnd(12)} ${status}`);
|
|
392
725
|
}
|
|
393
|
-
console.log(
|
|
394
|
-
Default: ${config.defaultProvider || "
|
|
726
|
+
console.log(chalk4.cyan(`
|
|
727
|
+
Default: ${config.defaultProvider || "gemini"}
|
|
395
728
|
`));
|
|
396
|
-
console.log(
|
|
729
|
+
console.log(chalk4.gray('\u{1F4A1} Tip: Use "agdi auth" to reconfigure\n'));
|
|
397
730
|
}
|
|
398
731
|
|
|
399
732
|
// src/commands/chat.ts
|
|
400
733
|
import { input as input2 } from "@inquirer/prompts";
|
|
401
|
-
import
|
|
734
|
+
import chalk5 from "chalk";
|
|
402
735
|
import ora from "ora";
|
|
403
736
|
var SYSTEM_PROMPT2 = `You are Agdi, an elite full-stack software architect and senior engineer with deep expertise across the entire web development stack.
|
|
404
737
|
|
|
@@ -553,34 +886,38 @@ When asked to build something:
|
|
|
553
886
|
|
|
554
887
|
You build software that works, scales, and follows industry best practices. Every solution is complete, tested, and ready for production deployment.`;
|
|
555
888
|
async function startChat() {
|
|
556
|
-
console.log(
|
|
557
|
-
console.log(
|
|
889
|
+
console.log(chalk5.cyan.bold("\n\u{1F4AC} Agdi Interactive Mode\n"));
|
|
890
|
+
console.log(chalk5.gray('Type your coding requests. Type "exit" to quit.\n'));
|
|
558
891
|
const config = loadConfig();
|
|
559
892
|
let provider;
|
|
560
893
|
let apiKey = "";
|
|
561
894
|
if (config.geminiApiKey) {
|
|
562
895
|
provider = "gemini";
|
|
563
896
|
apiKey = config.geminiApiKey;
|
|
897
|
+
} else if (config.openrouterApiKey) {
|
|
898
|
+
provider = "openrouter";
|
|
899
|
+
apiKey = config.openrouterApiKey;
|
|
900
|
+
console.log(chalk5.gray("Using OpenRouter (100+ models available)\n"));
|
|
564
901
|
} else if (config.defaultProvider === "puter") {
|
|
565
|
-
console.log(
|
|
566
|
-
console.log(
|
|
567
|
-
console.log(
|
|
568
|
-
console.log(
|
|
902
|
+
console.log(chalk5.yellow("\u26A0\uFE0F Puter.com FREE mode requires browser authentication."));
|
|
903
|
+
console.log(chalk5.gray("For CLI usage, please configure an API key:\n"));
|
|
904
|
+
console.log(chalk5.cyan(" agdi auth"));
|
|
905
|
+
console.log(chalk5.gray("\nSupported providers: Gemini, OpenRouter, OpenAI, Anthropic, DeepSeek\n"));
|
|
569
906
|
return;
|
|
570
907
|
} else {
|
|
571
|
-
console.log(
|
|
572
|
-
console.log(
|
|
908
|
+
console.log(chalk5.yellow("\u26A0\uFE0F No API key configured."));
|
|
909
|
+
console.log(chalk5.gray('Run "agdi auth" to configure your API key.\n'));
|
|
573
910
|
return;
|
|
574
911
|
}
|
|
575
|
-
console.log(
|
|
576
|
-
console.log(
|
|
912
|
+
console.log(chalk5.gray(`Using provider: ${chalk5.cyan(provider)}`));
|
|
913
|
+
console.log(chalk5.gray("\u2500".repeat(50) + "\n"));
|
|
577
914
|
const pm = new ProjectManager();
|
|
578
915
|
while (true) {
|
|
579
916
|
const userInput = await input2({
|
|
580
|
-
message:
|
|
917
|
+
message: chalk5.cyan("You:")
|
|
581
918
|
});
|
|
582
919
|
if (userInput.toLowerCase() === "exit" || userInput.toLowerCase() === "quit") {
|
|
583
|
-
console.log(
|
|
920
|
+
console.log(chalk5.gray("\n\u{1F44B} Goodbye!\n"));
|
|
584
921
|
break;
|
|
585
922
|
}
|
|
586
923
|
if (!userInput.trim()) {
|
|
@@ -597,9 +934,9 @@ async function startChat() {
|
|
|
597
934
|
});
|
|
598
935
|
pm.updateFiles(files);
|
|
599
936
|
spinner.succeed("Application generated!");
|
|
600
|
-
console.log(
|
|
937
|
+
console.log(chalk5.green("\n\u{1F4C1} Files created:"));
|
|
601
938
|
for (const file of files) {
|
|
602
|
-
console.log(
|
|
939
|
+
console.log(chalk5.gray(` - ${file.path}`));
|
|
603
940
|
}
|
|
604
941
|
const shouldWrite = await input2({
|
|
605
942
|
message: "Write files to disk? (y/n):",
|
|
@@ -611,188 +948,672 @@ async function startChat() {
|
|
|
611
948
|
default: "./generated-app"
|
|
612
949
|
});
|
|
613
950
|
await writeProject(pm.get(), dir);
|
|
614
|
-
console.log(
|
|
951
|
+
console.log(chalk5.green(`
|
|
615
952
|
\u2705 Files written to ${dir}
|
|
616
953
|
`));
|
|
617
954
|
}
|
|
618
955
|
} else {
|
|
619
956
|
const response = await llm.generate(userInput, SYSTEM_PROMPT2);
|
|
620
957
|
spinner.stop();
|
|
621
|
-
console.log(
|
|
958
|
+
console.log(chalk5.cyan("\nAgdi: ") + response.text + "\n");
|
|
622
959
|
}
|
|
623
960
|
} catch (error) {
|
|
961
|
+
if (error.name === "ExitPromptError") {
|
|
962
|
+
console.log(chalk5.gray("\n\n\u{1F44B} Goodbye!\n"));
|
|
963
|
+
process.exit(0);
|
|
964
|
+
}
|
|
624
965
|
spinner.fail("Error");
|
|
625
|
-
|
|
626
|
-
|
|
966
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
967
|
+
if (errorMessage.includes("429") || errorMessage.includes("quota") || errorMessage.includes("Resource exhausted") || errorMessage.includes("ResourceExhausted")) {
|
|
968
|
+
console.log(chalk5.yellow("\n\u26A0\uFE0F API quota exceeded!"));
|
|
969
|
+
console.log(chalk5.gray("Your API key has run out of credits."));
|
|
970
|
+
console.log(chalk5.gray("Try: Use a different API key or wait for quota reset.\n"));
|
|
971
|
+
} else if (errorMessage.includes("401") || errorMessage.includes("Unauthorized") || errorMessage.includes("Invalid API key")) {
|
|
972
|
+
console.log(chalk5.red("\n\u{1F511} Invalid API key"));
|
|
973
|
+
console.log(chalk5.gray("Please reconfigure your API key:"));
|
|
974
|
+
console.log(chalk5.cyan(" agdi auth\n"));
|
|
975
|
+
} else if (errorMessage.includes("403") || errorMessage.includes("Forbidden")) {
|
|
976
|
+
console.log(chalk5.red("\n\u{1F6AB} Access denied"));
|
|
977
|
+
console.log(chalk5.gray("Your API key doesn't have permission for this operation.\n"));
|
|
978
|
+
} else if (errorMessage.includes("network") || errorMessage.includes("fetch") || errorMessage.includes("ENOTFOUND")) {
|
|
979
|
+
console.log(chalk5.red("\n\u{1F310} Network error"));
|
|
980
|
+
console.log(chalk5.gray("Please check your internet connection.\n"));
|
|
981
|
+
} else {
|
|
982
|
+
console.log(chalk5.red("\n" + errorMessage + "\n"));
|
|
983
|
+
}
|
|
627
984
|
}
|
|
628
985
|
}
|
|
629
986
|
}
|
|
630
987
|
|
|
631
|
-
// src/
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
console.log(
|
|
643
|
-
console.log(
|
|
644
|
-
|
|
645
|
-
program.command("auth").description("Login or configure API keys").option("--status", "Show authentication status").action(async (options) => {
|
|
646
|
-
if (options.status) {
|
|
647
|
-
await showStatus();
|
|
648
|
-
} else {
|
|
649
|
-
await login();
|
|
988
|
+
// src/commands/run.ts
|
|
989
|
+
import { spawn } from "child_process";
|
|
990
|
+
import chalk6 from "chalk";
|
|
991
|
+
import fs3 from "fs-extra";
|
|
992
|
+
import path3 from "path";
|
|
993
|
+
import ora2 from "ora";
|
|
994
|
+
async function runProject(targetDir) {
|
|
995
|
+
const dir = targetDir || process.cwd();
|
|
996
|
+
const absoluteDir = path3.resolve(dir);
|
|
997
|
+
console.log(chalk6.cyan.bold("\n\u{1F680} Agdi Run\n"));
|
|
998
|
+
if (!fs3.existsSync(absoluteDir)) {
|
|
999
|
+
console.log(chalk6.red(`\u274C Directory not found: ${absoluteDir}`));
|
|
1000
|
+
console.log(chalk6.gray("Create a project first with: agdi init"));
|
|
1001
|
+
return;
|
|
650
1002
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
1003
|
+
const packageJsonPath = path3.join(absoluteDir, "package.json");
|
|
1004
|
+
if (!fs3.existsSync(packageJsonPath)) {
|
|
1005
|
+
console.log(chalk6.red(`\u274C No package.json found in: ${absoluteDir}`));
|
|
1006
|
+
console.log(chalk6.gray("This doesn't appear to be a Node.js project."));
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const packageJson = fs3.readJsonSync(packageJsonPath);
|
|
1010
|
+
const scripts = packageJson.scripts || {};
|
|
1011
|
+
let runScript = "dev";
|
|
1012
|
+
if (!scripts.dev && scripts.start) {
|
|
1013
|
+
runScript = "start";
|
|
1014
|
+
} else if (!scripts.dev && !scripts.start) {
|
|
1015
|
+
console.log(chalk6.red('\u274C No "dev" or "start" script found in package.json'));
|
|
1016
|
+
console.log(chalk6.gray('Add a script like: "dev": "vite" or "start": "node index.js"'));
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
const nodeModulesPath = path3.join(absoluteDir, "node_modules");
|
|
1020
|
+
if (!fs3.existsSync(nodeModulesPath)) {
|
|
1021
|
+
console.log(chalk6.yellow("\u{1F4E6} Installing dependencies...\n"));
|
|
1022
|
+
const installSpinner = ora2("Running npm install...").start();
|
|
1023
|
+
await new Promise((resolve, reject) => {
|
|
1024
|
+
const install = spawn("npm", ["install"], {
|
|
1025
|
+
cwd: absoluteDir,
|
|
1026
|
+
stdio: "inherit",
|
|
1027
|
+
shell: true
|
|
1028
|
+
});
|
|
1029
|
+
install.on("close", (code) => {
|
|
1030
|
+
if (code === 0) {
|
|
1031
|
+
installSpinner.succeed("Dependencies installed!");
|
|
1032
|
+
resolve();
|
|
1033
|
+
} else {
|
|
1034
|
+
installSpinner.fail("npm install failed");
|
|
1035
|
+
reject(new Error(`npm install exited with code ${code}`));
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
install.on("error", reject);
|
|
1039
|
+
});
|
|
1040
|
+
console.log("");
|
|
1041
|
+
}
|
|
1042
|
+
console.log(chalk6.green(`\u25B6 Running: npm run ${runScript}`));
|
|
1043
|
+
console.log(chalk6.gray(` Directory: ${absoluteDir}
|
|
1044
|
+
`));
|
|
1045
|
+
console.log(chalk6.gray("\u2500".repeat(50)));
|
|
1046
|
+
console.log(chalk6.gray("Press Ctrl+C to stop\n"));
|
|
1047
|
+
const child = spawn("npm", ["run", runScript], {
|
|
1048
|
+
cwd: absoluteDir,
|
|
1049
|
+
stdio: "inherit",
|
|
1050
|
+
shell: true
|
|
1051
|
+
});
|
|
1052
|
+
process.on("SIGINT", () => {
|
|
1053
|
+
child.kill("SIGINT");
|
|
1054
|
+
console.log(chalk6.gray("\n\n\u{1F44B} Server stopped."));
|
|
1055
|
+
process.exit(0);
|
|
1056
|
+
});
|
|
1057
|
+
child.on("close", (code) => {
|
|
1058
|
+
if (code !== 0) {
|
|
1059
|
+
console.log(chalk6.red(`
|
|
1060
|
+
\u274C Process exited with code ${code}`));
|
|
1061
|
+
}
|
|
1062
|
+
process.exit(code || 0);
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// src/commands/onboarding.ts
|
|
1067
|
+
import { select as select2, password as password2 } from "@inquirer/prompts";
|
|
1068
|
+
import chalk7 from "chalk";
|
|
1069
|
+
var PROVIDER_MODELS = {
|
|
1070
|
+
gemini: [
|
|
1071
|
+
{ name: "\u26A1 Gemini 2.5 Flash (Fast)", value: "gemini-2.5-flash" },
|
|
1072
|
+
{ name: "\u{1F680} Gemini 2.5 Pro (Best)", value: "gemini-2.5-pro" },
|
|
1073
|
+
{ name: "\u{1F48E} Gemini 2.0 Flash", value: "gemini-2.0-flash" }
|
|
1074
|
+
],
|
|
1075
|
+
openrouter: [
|
|
1076
|
+
{ name: "\u{1F9E0} Claude 3.5 Sonnet", value: "anthropic/claude-3.5-sonnet" },
|
|
1077
|
+
{ name: "\u{1F48E} GPT-4o", value: "openai/gpt-4o" },
|
|
1078
|
+
{ name: "\u26A1 GPT-4o Mini", value: "openai/gpt-4o-mini" },
|
|
1079
|
+
{ name: "\u{1F525} Gemini 2.5 Flash", value: "google/gemini-2.5-flash-preview" },
|
|
1080
|
+
{ name: "\u{1F999} Llama 3.3 70B", value: "meta-llama/llama-3.3-70b-instruct" },
|
|
1081
|
+
{ name: "\u{1F30A} DeepSeek R1", value: "deepseek/deepseek-r1" },
|
|
1082
|
+
{ name: "\u{1F916} Mistral Large", value: "mistral/mistral-large-latest" }
|
|
1083
|
+
],
|
|
1084
|
+
openai: [
|
|
1085
|
+
{ name: "\u{1F48E} GPT-4o", value: "gpt-4o" },
|
|
1086
|
+
{ name: "\u26A1 GPT-4o Mini", value: "gpt-4o-mini" },
|
|
1087
|
+
{ name: "\u{1F9E0} o1", value: "o1" },
|
|
1088
|
+
{ name: "\u{1F680} o1-mini", value: "o1-mini" }
|
|
1089
|
+
],
|
|
1090
|
+
anthropic: [
|
|
1091
|
+
{ name: "\u{1F9E0} Claude 3.5 Sonnet", value: "claude-3-5-sonnet-20241022" },
|
|
1092
|
+
{ name: "\u{1F48E} Claude 3.5 Haiku", value: "claude-3-5-haiku-20241022" },
|
|
1093
|
+
{ name: "\u{1F680} Claude 3 Opus", value: "claude-3-opus-20240229" }
|
|
1094
|
+
],
|
|
1095
|
+
deepseek: [
|
|
1096
|
+
{ name: "\u{1F30A} DeepSeek V3", value: "deepseek-chat" },
|
|
1097
|
+
{ name: "\u{1F9E0} DeepSeek R1 (Reasoning)", value: "deepseek-reasoner" }
|
|
1098
|
+
]
|
|
1099
|
+
};
|
|
1100
|
+
function needsOnboarding() {
|
|
1101
|
+
const config = loadConfig();
|
|
1102
|
+
return !(config.geminiApiKey || config.openrouterApiKey || config.openaiApiKey || config.anthropicApiKey || config.deepseekApiKey);
|
|
1103
|
+
}
|
|
1104
|
+
function getActiveProvider() {
|
|
654
1105
|
const config = loadConfig();
|
|
1106
|
+
const provider = config.defaultProvider;
|
|
1107
|
+
const keyMap = {
|
|
1108
|
+
gemini: config.geminiApiKey,
|
|
1109
|
+
openrouter: config.openrouterApiKey,
|
|
1110
|
+
openai: config.openaiApiKey,
|
|
1111
|
+
anthropic: config.anthropicApiKey,
|
|
1112
|
+
deepseek: config.deepseekApiKey
|
|
1113
|
+
};
|
|
1114
|
+
const apiKey = keyMap[provider];
|
|
1115
|
+
if (apiKey) {
|
|
1116
|
+
return {
|
|
1117
|
+
provider,
|
|
1118
|
+
apiKey,
|
|
1119
|
+
model: config.defaultModel || PROVIDER_MODELS[provider]?.[0]?.value || "gemini-2.5-flash"
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
for (const [p, key] of Object.entries(keyMap)) {
|
|
1123
|
+
if (key) {
|
|
1124
|
+
return {
|
|
1125
|
+
provider: p,
|
|
1126
|
+
apiKey: key,
|
|
1127
|
+
model: config.defaultModel || PROVIDER_MODELS[p]?.[0]?.value || "gemini-2.5-flash"
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
async function runOnboarding() {
|
|
1134
|
+
console.log(chalk7.cyan.bold("\n\u{1F680} Welcome to Agdi!\n"));
|
|
1135
|
+
console.log(chalk7.gray("Let's set up your AI provider in 3 quick steps.\n"));
|
|
1136
|
+
const config = loadConfig();
|
|
1137
|
+
console.log(chalk7.white.bold("Step 1/3: Select your AI provider\n"));
|
|
655
1138
|
const provider = await select2({
|
|
656
|
-
message: "
|
|
1139
|
+
message: "Which AI provider would you like to use?",
|
|
657
1140
|
choices: [
|
|
658
|
-
{ name: "\u{
|
|
659
|
-
{ name: "\u{
|
|
1141
|
+
{ name: "\u{1F525} Google Gemini (Recommended - Free tier)", value: "gemini" },
|
|
1142
|
+
{ name: "\u{1F310} OpenRouter (100+ models, pay-per-use)", value: "openrouter" },
|
|
1143
|
+
{ name: "\u{1F9E0} OpenAI (GPT-4o)", value: "openai" },
|
|
1144
|
+
{ name: "\u{1F49C} Anthropic (Claude)", value: "anthropic" },
|
|
1145
|
+
{ name: "\u{1F30A} DeepSeek", value: "deepseek" }
|
|
660
1146
|
]
|
|
661
1147
|
});
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1148
|
+
console.log(chalk7.white.bold("\nStep 2/3: Enter your API key\n"));
|
|
1149
|
+
const keyUrls = {
|
|
1150
|
+
gemini: "https://aistudio.google.com/apikey",
|
|
1151
|
+
openrouter: "https://openrouter.ai/keys",
|
|
1152
|
+
openai: "https://platform.openai.com/api-keys",
|
|
1153
|
+
anthropic: "https://console.anthropic.com/",
|
|
1154
|
+
deepseek: "https://platform.deepseek.com/"
|
|
1155
|
+
};
|
|
1156
|
+
console.log(chalk7.gray(`Get your key at: ${chalk7.cyan(keyUrls[provider])}
|
|
1157
|
+
`));
|
|
1158
|
+
const apiKey = await password2({
|
|
1159
|
+
message: `Enter your ${provider} API key:`,
|
|
1160
|
+
mask: "*"
|
|
1161
|
+
});
|
|
1162
|
+
switch (provider) {
|
|
1163
|
+
case "gemini":
|
|
1164
|
+
config.geminiApiKey = apiKey;
|
|
1165
|
+
break;
|
|
1166
|
+
case "openrouter":
|
|
1167
|
+
config.openrouterApiKey = apiKey;
|
|
1168
|
+
break;
|
|
1169
|
+
case "openai":
|
|
1170
|
+
config.openaiApiKey = apiKey;
|
|
1171
|
+
break;
|
|
1172
|
+
case "anthropic":
|
|
1173
|
+
config.anthropicApiKey = apiKey;
|
|
1174
|
+
break;
|
|
1175
|
+
case "deepseek":
|
|
1176
|
+
config.deepseekApiKey = apiKey;
|
|
1177
|
+
break;
|
|
671
1178
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1179
|
+
config.defaultProvider = provider;
|
|
1180
|
+
console.log(chalk7.white.bold("\nStep 3/3: Choose your default model\n"));
|
|
1181
|
+
const models = PROVIDER_MODELS[provider] || PROVIDER_MODELS.gemini;
|
|
1182
|
+
const model = await select2({
|
|
1183
|
+
message: "Select your default model:",
|
|
1184
|
+
choices: models
|
|
675
1185
|
});
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1186
|
+
config.defaultModel = model;
|
|
1187
|
+
saveConfig(config);
|
|
1188
|
+
console.log(chalk7.green("\n\u2705 Setup complete!"));
|
|
1189
|
+
console.log(chalk7.gray(`Provider: ${chalk7.cyan(provider)}`));
|
|
1190
|
+
console.log(chalk7.gray(`Model: ${chalk7.cyan(model)}
|
|
1191
|
+
`));
|
|
1192
|
+
return { provider, apiKey, model };
|
|
1193
|
+
}
|
|
1194
|
+
async function selectModel() {
|
|
1195
|
+
const config = loadConfig();
|
|
1196
|
+
const provider = config.defaultProvider || "gemini";
|
|
1197
|
+
console.log(chalk7.cyan.bold("\n\u{1F504} Change Model\n"));
|
|
1198
|
+
console.log(chalk7.gray(`Current provider: ${chalk7.cyan(provider)}`));
|
|
1199
|
+
console.log(chalk7.gray(`Current model: ${chalk7.cyan(config.defaultModel || "not set")}
|
|
1200
|
+
`));
|
|
1201
|
+
const changeProvider = await select2({
|
|
1202
|
+
message: "What would you like to do?",
|
|
1203
|
+
choices: [
|
|
1204
|
+
{ name: "\u{1F4DD} Change model (same provider)", value: "model" },
|
|
1205
|
+
{ name: "\u{1F504} Change provider & model", value: "provider" },
|
|
1206
|
+
{ name: "\u274C Cancel", value: "cancel" }
|
|
1207
|
+
]
|
|
1208
|
+
});
|
|
1209
|
+
if (changeProvider === "cancel") {
|
|
1210
|
+
console.log(chalk7.gray("\nCancelled.\n"));
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
let selectedProvider = provider;
|
|
1214
|
+
if (changeProvider === "provider") {
|
|
1215
|
+
selectedProvider = await select2({
|
|
1216
|
+
message: "Select provider:",
|
|
695
1217
|
choices: [
|
|
696
|
-
{ name: "Gemini
|
|
697
|
-
{ name: "
|
|
1218
|
+
{ name: "\u{1F525} Gemini", value: "gemini" },
|
|
1219
|
+
{ name: "\u{1F310} OpenRouter", value: "openrouter" },
|
|
1220
|
+
{ name: "\u{1F9E0} OpenAI", value: "openai" },
|
|
1221
|
+
{ name: "\u{1F49C} Anthropic", value: "anthropic" },
|
|
1222
|
+
{ name: "\u{1F30A} DeepSeek", value: "deepseek" }
|
|
698
1223
|
]
|
|
699
1224
|
});
|
|
1225
|
+
const keyMap = {
|
|
1226
|
+
gemini: config.geminiApiKey,
|
|
1227
|
+
openrouter: config.openrouterApiKey,
|
|
1228
|
+
openai: config.openaiApiKey,
|
|
1229
|
+
anthropic: config.anthropicApiKey,
|
|
1230
|
+
deepseek: config.deepseekApiKey
|
|
1231
|
+
};
|
|
1232
|
+
if (!keyMap[selectedProvider]) {
|
|
1233
|
+
console.log(chalk7.yellow(`
|
|
1234
|
+
\u26A0\uFE0F No API key configured for ${selectedProvider}
|
|
1235
|
+
`));
|
|
1236
|
+
const apiKey = await password2({
|
|
1237
|
+
message: `Enter your ${selectedProvider} API key:`,
|
|
1238
|
+
mask: "*"
|
|
1239
|
+
});
|
|
1240
|
+
switch (selectedProvider) {
|
|
1241
|
+
case "gemini":
|
|
1242
|
+
config.geminiApiKey = apiKey;
|
|
1243
|
+
break;
|
|
1244
|
+
case "openrouter":
|
|
1245
|
+
config.openrouterApiKey = apiKey;
|
|
1246
|
+
break;
|
|
1247
|
+
case "openai":
|
|
1248
|
+
config.openaiApiKey = apiKey;
|
|
1249
|
+
break;
|
|
1250
|
+
case "anthropic":
|
|
1251
|
+
config.anthropicApiKey = apiKey;
|
|
1252
|
+
break;
|
|
1253
|
+
case "deepseek":
|
|
1254
|
+
config.deepseekApiKey = apiKey;
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
config.defaultProvider = selectedProvider;
|
|
700
1259
|
}
|
|
701
|
-
const
|
|
702
|
-
const
|
|
703
|
-
message: "
|
|
704
|
-
|
|
1260
|
+
const models = PROVIDER_MODELS[selectedProvider] || PROVIDER_MODELS.gemini;
|
|
1261
|
+
const model = await select2({
|
|
1262
|
+
message: "Select model:",
|
|
1263
|
+
choices: models
|
|
705
1264
|
});
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
|
|
1265
|
+
config.defaultModel = model;
|
|
1266
|
+
saveConfig(config);
|
|
1267
|
+
console.log(chalk7.green("\n\u2705 Model changed!"));
|
|
1268
|
+
console.log(chalk7.gray(`Provider: ${chalk7.cyan(selectedProvider)}`));
|
|
1269
|
+
console.log(chalk7.gray(`Model: ${chalk7.cyan(model)}
|
|
1270
|
+
`));
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// src/commands/codex.ts
|
|
1274
|
+
import { input as input4 } from "@inquirer/prompts";
|
|
1275
|
+
import chalk8 from "chalk";
|
|
1276
|
+
import ora3 from "ora";
|
|
1277
|
+
var SYSTEM_PROMPT3 = `You are Agdi dev, an elite AI coding assistant. You help developers write code, debug issues, and build applications.
|
|
1278
|
+
|
|
1279
|
+
## Your Capabilities
|
|
1280
|
+
- Write complete, production-ready code
|
|
1281
|
+
- Debug and fix code issues
|
|
1282
|
+
- Explain code and architecture
|
|
1283
|
+
- Generate full applications from descriptions
|
|
1284
|
+
- Run shell commands (when requested)
|
|
1285
|
+
|
|
1286
|
+
## Response Style
|
|
1287
|
+
- Be concise and direct
|
|
1288
|
+
- Show code in proper markdown blocks
|
|
1289
|
+
- Explain complex decisions briefly
|
|
1290
|
+
- Ask clarifying questions when needed
|
|
1291
|
+
|
|
1292
|
+
## Code Quality
|
|
1293
|
+
- Use TypeScript by default
|
|
1294
|
+
- Follow modern best practices
|
|
1295
|
+
- Include proper error handling
|
|
1296
|
+
- Write self-documenting code
|
|
1297
|
+
|
|
1298
|
+
When generating applications, create complete file structures with all necessary code.`;
|
|
1299
|
+
async function startCodingMode() {
|
|
1300
|
+
const activeConfig = getActiveProvider();
|
|
1301
|
+
if (!activeConfig) {
|
|
1302
|
+
console.log(chalk8.red("\u274C No API key configured. Run: agdi"));
|
|
712
1303
|
return;
|
|
713
1304
|
}
|
|
714
|
-
|
|
715
|
-
const
|
|
1305
|
+
const { provider, apiKey, model } = activeConfig;
|
|
1306
|
+
const config = loadConfig();
|
|
1307
|
+
console.log(chalk8.cyan.bold("\n\u26A1 Agdi dev\n"));
|
|
1308
|
+
console.log(chalk8.gray(`Model: ${chalk8.cyan(model)}`));
|
|
1309
|
+
console.log(chalk8.gray("Commands: /model, /chat, /build, /help, /exit\n"));
|
|
1310
|
+
console.log(chalk8.gray("\u2500".repeat(50) + "\n"));
|
|
1311
|
+
const pm = new ProjectManager();
|
|
1312
|
+
let llm = createLLMProvider(provider, { apiKey, model });
|
|
1313
|
+
while (true) {
|
|
1314
|
+
try {
|
|
1315
|
+
const userInput = await input4({
|
|
1316
|
+
message: chalk8.green("\u2192")
|
|
1317
|
+
});
|
|
1318
|
+
const trimmed = userInput.trim().toLowerCase();
|
|
1319
|
+
if (trimmed === "/exit" || trimmed === "exit" || trimmed === "quit") {
|
|
1320
|
+
console.log(chalk8.gray("\n\u{1F44B} Goodbye!\n"));
|
|
1321
|
+
break;
|
|
1322
|
+
}
|
|
1323
|
+
if (trimmed === "/help") {
|
|
1324
|
+
showHelp();
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
if (trimmed === "/model" || trimmed === "/models") {
|
|
1328
|
+
await selectModel();
|
|
1329
|
+
const newConfig = getActiveProvider();
|
|
1330
|
+
if (newConfig) {
|
|
1331
|
+
llm = createLLMProvider(newConfig.provider, {
|
|
1332
|
+
apiKey: newConfig.apiKey,
|
|
1333
|
+
model: newConfig.model
|
|
1334
|
+
});
|
|
1335
|
+
console.log(chalk8.gray(`Now using: ${chalk8.cyan(newConfig.model)}
|
|
1336
|
+
`));
|
|
1337
|
+
}
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
if (trimmed === "/chat") {
|
|
1341
|
+
console.log(chalk8.gray("\nSwitching to chat mode. Type /code to return.\n"));
|
|
1342
|
+
await chatMode(llm);
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
if (trimmed.startsWith("/build ") || trimmed.startsWith("build ")) {
|
|
1346
|
+
const prompt = userInput.replace(/^\/?build\s+/i, "").trim();
|
|
1347
|
+
if (prompt) {
|
|
1348
|
+
await buildApp(prompt, llm, pm);
|
|
1349
|
+
} else {
|
|
1350
|
+
console.log(chalk8.yellow("\nUsage: /build <description>\n"));
|
|
1351
|
+
}
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
if (!userInput.trim()) {
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
const isGenerationRequest = /^(create|build|make|generate|design|implement)\s+(me\s+)?(a\s+|an\s+)?/i.test(userInput.trim());
|
|
1358
|
+
if (isGenerationRequest) {
|
|
1359
|
+
await buildApp(userInput, llm, pm);
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
const spinner = ora3("Thinking...").start();
|
|
1363
|
+
try {
|
|
1364
|
+
const response = await llm.generate(userInput, SYSTEM_PROMPT3);
|
|
1365
|
+
spinner.stop();
|
|
1366
|
+
console.log("\n" + formatResponse(response.text) + "\n");
|
|
1367
|
+
} catch (error) {
|
|
1368
|
+
spinner.fail("Error");
|
|
1369
|
+
handleError(error);
|
|
1370
|
+
}
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
if (error.name === "ExitPromptError") {
|
|
1373
|
+
console.log(chalk8.gray("\n\n\u{1F44B} Goodbye!\n"));
|
|
1374
|
+
process.exit(0);
|
|
1375
|
+
}
|
|
1376
|
+
throw error;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
async function buildApp(prompt, llm, pm) {
|
|
1381
|
+
const spinner = ora3("Generating application...").start();
|
|
716
1382
|
try {
|
|
717
|
-
|
|
718
|
-
apiKey: provider === "gemini" ? config.geminiApiKey : "",
|
|
719
|
-
model
|
|
720
|
-
});
|
|
721
|
-
const pm = new ProjectManager();
|
|
722
|
-
pm.create(outputDir.replace("./", ""), prompt);
|
|
1383
|
+
pm.create("generated-app", prompt);
|
|
723
1384
|
const { plan, files } = await generateApp(prompt, llm, (step, file) => {
|
|
724
|
-
spinner.text = file ? `${step} ${
|
|
1385
|
+
spinner.text = file ? `${step} ${chalk8.gray(file)}` : step;
|
|
725
1386
|
});
|
|
726
1387
|
pm.updateFiles(files);
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
1388
|
+
spinner.succeed(chalk8.green("Application generated!"));
|
|
1389
|
+
console.log(chalk8.gray("\n\u{1F4C1} Files:"));
|
|
1390
|
+
for (const file of files.slice(0, 10)) {
|
|
1391
|
+
console.log(chalk8.gray(` ${file.path}`));
|
|
1392
|
+
}
|
|
1393
|
+
if (files.length > 10) {
|
|
1394
|
+
console.log(chalk8.gray(` ... and ${files.length - 10} more`));
|
|
1395
|
+
}
|
|
1396
|
+
const shouldWrite = await input4({
|
|
1397
|
+
message: "Write to disk? (y/n):",
|
|
1398
|
+
default: "y"
|
|
1399
|
+
});
|
|
1400
|
+
if (shouldWrite.toLowerCase() === "y") {
|
|
1401
|
+
const dir = await input4({
|
|
1402
|
+
message: "Output directory:",
|
|
1403
|
+
default: "./generated-app"
|
|
1404
|
+
});
|
|
1405
|
+
await writeProject(pm.get(), dir);
|
|
1406
|
+
console.log(chalk8.green(`
|
|
1407
|
+
\u2705 Written to ${dir}
|
|
733
1408
|
`));
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
console.log(chalk3.gray(" npm install"));
|
|
737
|
-
console.log(chalk3.gray(" npm run dev\n"));
|
|
1409
|
+
console.log(chalk8.gray("Next: cd " + dir + " && npm install && npm run dev\n"));
|
|
1410
|
+
}
|
|
738
1411
|
} catch (error) {
|
|
739
|
-
spinner.fail(
|
|
740
|
-
|
|
741
|
-
|
|
1412
|
+
spinner.fail("Generation failed");
|
|
1413
|
+
handleError(error);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
async function chatMode(llm) {
|
|
1417
|
+
while (true) {
|
|
1418
|
+
try {
|
|
1419
|
+
const userInput = await input4({
|
|
1420
|
+
message: chalk8.blue("\u{1F4AC}")
|
|
1421
|
+
});
|
|
1422
|
+
if (userInput.toLowerCase() === "/code" || userInput.toLowerCase() === "/exit") {
|
|
1423
|
+
console.log(chalk8.gray("\nBack to Agdi dev mode.\n"));
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
if (!userInput.trim()) continue;
|
|
1427
|
+
const spinner = ora3("...").start();
|
|
1428
|
+
const response = await llm.generate(userInput, "You are a helpful assistant. Be friendly and concise.");
|
|
1429
|
+
spinner.stop();
|
|
1430
|
+
console.log("\n" + response.text + "\n");
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
if (error.name === "ExitPromptError") {
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
handleError(error);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
function formatResponse(text) {
|
|
1440
|
+
return text.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => {
|
|
1441
|
+
const header = lang ? chalk8.gray(`\u2500\u2500 ${lang} \u2500\u2500`) : chalk8.gray("\u2500\u2500 code \u2500\u2500");
|
|
1442
|
+
return `
|
|
1443
|
+
${header}
|
|
1444
|
+
${chalk8.white(code.trim())}
|
|
1445
|
+
${chalk8.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
1446
|
+
`;
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
function showHelp() {
|
|
1450
|
+
console.log(chalk8.cyan.bold("\n\u{1F4D6} Commands\n"));
|
|
1451
|
+
console.log(chalk8.gray(" /model ") + "Change AI model");
|
|
1452
|
+
console.log(chalk8.gray(" /build ") + "Generate a full application");
|
|
1453
|
+
console.log(chalk8.gray(" /chat ") + "Switch to chat mode");
|
|
1454
|
+
console.log(chalk8.gray(" /help ") + "Show this help");
|
|
1455
|
+
console.log(chalk8.gray(" /exit ") + "Exit Agdi");
|
|
1456
|
+
console.log(chalk8.gray("\n Or just type your coding question!\n"));
|
|
1457
|
+
}
|
|
1458
|
+
function handleError(error) {
|
|
1459
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1460
|
+
if (msg.includes("429") || msg.includes("quota")) {
|
|
1461
|
+
console.log(chalk8.yellow("\n\u26A0\uFE0F Quota exceeded. Run /model to switch.\n"));
|
|
1462
|
+
} else if (msg.includes("401") || msg.includes("403")) {
|
|
1463
|
+
console.log(chalk8.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
|
|
1464
|
+
} else {
|
|
1465
|
+
console.log(chalk8.red("\n" + msg + "\n"));
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/index.ts
|
|
1470
|
+
var BANNER = `
|
|
1471
|
+
${chalk9.cyan(` ___ __ _ `)}
|
|
1472
|
+
${chalk9.cyan(` / | ____ _____/ /(_) `)}
|
|
1473
|
+
${chalk9.cyan(` / /| | / __ \`/ __ // / `)}
|
|
1474
|
+
${chalk9.cyan(` / ___ |/ /_/ / /_/ // / `)}
|
|
1475
|
+
${chalk9.cyan(`/_/ |_|\\__, /\\__,_//_/ `)}
|
|
1476
|
+
${chalk9.cyan(` /____/ `)}
|
|
1477
|
+
`;
|
|
1478
|
+
var program = new Command();
|
|
1479
|
+
program.name("agdi").description(chalk9.cyan("\u{1F680} AI-powered coding assistant")).version("2.0.0").configureHelp({
|
|
1480
|
+
// Show banner only when help is requested
|
|
1481
|
+
formatHelp: (cmd, helper) => {
|
|
1482
|
+
return BANNER + "\n" + chalk9.gray(" The Open Source AI Architect") + "\n" + chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n") + "\n" + helper.formatHelp(cmd, helper);
|
|
742
1483
|
}
|
|
743
1484
|
});
|
|
744
|
-
program.
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1485
|
+
program.action(async () => {
|
|
1486
|
+
try {
|
|
1487
|
+
if (needsOnboarding()) {
|
|
1488
|
+
await runOnboarding();
|
|
1489
|
+
}
|
|
1490
|
+
await startCodingMode();
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
if (error.name === "ExitPromptError") {
|
|
1493
|
+
console.log(chalk9.gray("\n\n\u{1F44B} Goodbye!\n"));
|
|
1494
|
+
process.exit(0);
|
|
1495
|
+
}
|
|
1496
|
+
throw error;
|
|
751
1497
|
}
|
|
752
|
-
|
|
1498
|
+
});
|
|
1499
|
+
program.command("auth").description("Configure API keys").option("--status", "Show authentication status").action(async (options) => {
|
|
753
1500
|
try {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
pm.create(options.output.replace("./", ""), prompt);
|
|
760
|
-
const { plan, files } = await generateApp(prompt, llm, (step) => {
|
|
761
|
-
spinner.text = step;
|
|
762
|
-
});
|
|
763
|
-
pm.updateFiles(files);
|
|
764
|
-
pm.updateDependencies(plan.dependencies);
|
|
765
|
-
await writeProject(pm.get(), options.output);
|
|
766
|
-
spinner.succeed(`App generated in ${chalk3.cyan(options.output)}`);
|
|
1501
|
+
if (options.status) {
|
|
1502
|
+
await showStatus();
|
|
1503
|
+
} else {
|
|
1504
|
+
await login();
|
|
1505
|
+
}
|
|
767
1506
|
} catch (error) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1507
|
+
if (error.name === "ExitPromptError") {
|
|
1508
|
+
console.log(chalk9.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
1509
|
+
process.exit(0);
|
|
1510
|
+
}
|
|
1511
|
+
throw error;
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1514
|
+
program.command("model").alias("models").description("Change AI model").action(async () => {
|
|
1515
|
+
try {
|
|
1516
|
+
await selectModel();
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
if (error.name === "ExitPromptError") {
|
|
1519
|
+
console.log(chalk9.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
1520
|
+
process.exit(0);
|
|
1521
|
+
}
|
|
1522
|
+
throw error;
|
|
771
1523
|
}
|
|
772
1524
|
});
|
|
773
|
-
program.command("
|
|
774
|
-
|
|
1525
|
+
program.command("chat").description("Start a chat session").action(async () => {
|
|
1526
|
+
try {
|
|
1527
|
+
if (needsOnboarding()) {
|
|
1528
|
+
await runOnboarding();
|
|
1529
|
+
}
|
|
1530
|
+
await startChat();
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
if (error.name === "ExitPromptError") {
|
|
1533
|
+
console.log(chalk9.gray("\n\n\u{1F44B} Goodbye!\n"));
|
|
1534
|
+
process.exit(0);
|
|
1535
|
+
}
|
|
1536
|
+
throw error;
|
|
1537
|
+
}
|
|
775
1538
|
});
|
|
776
|
-
program.command("
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1539
|
+
program.command("run [directory]").description("Run a generated project").action(async (directory) => {
|
|
1540
|
+
try {
|
|
1541
|
+
await runProject(directory);
|
|
1542
|
+
} catch (error) {
|
|
1543
|
+
if (error.name === "ExitPromptError") {
|
|
1544
|
+
console.log(chalk9.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
1545
|
+
process.exit(0);
|
|
1546
|
+
}
|
|
1547
|
+
throw error;
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
program.command("build <prompt>").alias("b").description("Generate an app from a prompt").option("-o, --output <dir>", "Output directory", "./generated-app").action(async (prompt, options) => {
|
|
1551
|
+
try {
|
|
1552
|
+
if (needsOnboarding()) {
|
|
1553
|
+
await runOnboarding();
|
|
1554
|
+
}
|
|
1555
|
+
const activeConfig = getActiveProvider();
|
|
1556
|
+
if (!activeConfig) {
|
|
1557
|
+
console.log(chalk9.red("\u274C No API key configured. Run: agdi auth"));
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
const spinner = ora4("Generating application...").start();
|
|
1561
|
+
try {
|
|
1562
|
+
const llm = createLLMProvider(activeConfig.provider, {
|
|
1563
|
+
apiKey: activeConfig.apiKey,
|
|
1564
|
+
model: activeConfig.model
|
|
1565
|
+
});
|
|
1566
|
+
const pm = new ProjectManager();
|
|
1567
|
+
pm.create(options.output.replace("./", ""), prompt);
|
|
1568
|
+
const { plan, files } = await generateApp(prompt, llm, (step, file) => {
|
|
1569
|
+
spinner.text = file ? `${step} ${chalk9.gray(file)}` : step;
|
|
1570
|
+
});
|
|
1571
|
+
pm.updateFiles(files);
|
|
1572
|
+
pm.updateDependencies(plan.dependencies);
|
|
1573
|
+
await writeProject(pm.get(), options.output);
|
|
1574
|
+
spinner.succeed(chalk9.green("App generated!"));
|
|
1575
|
+
console.log(chalk9.gray(`
|
|
1576
|
+
\u{1F4C1} Created ${files.length} files in ${chalk9.cyan(options.output)}`));
|
|
1577
|
+
console.log(chalk9.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
spinner.fail("Generation failed");
|
|
1580
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1581
|
+
if (msg.includes("429") || msg.includes("quota")) {
|
|
1582
|
+
console.log(chalk9.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
|
|
1583
|
+
} else if (msg.includes("401") || msg.includes("403")) {
|
|
1584
|
+
console.log(chalk9.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
|
|
1585
|
+
} else {
|
|
1586
|
+
console.error(chalk9.red("\n" + msg + "\n"));
|
|
1587
|
+
}
|
|
1588
|
+
process.exit(1);
|
|
1589
|
+
}
|
|
1590
|
+
} catch (error) {
|
|
1591
|
+
if (error.name === "ExitPromptError") {
|
|
1592
|
+
console.log(chalk9.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
1593
|
+
process.exit(0);
|
|
790
1594
|
}
|
|
1595
|
+
throw error;
|
|
791
1596
|
}
|
|
792
|
-
console.log(chalk3.gray("\n ... and 400+ more models available!\n"));
|
|
793
|
-
console.log(chalk3.white('Usage: agdi generate "prompt" -p puter -m claude-sonnet-4\n'));
|
|
794
1597
|
});
|
|
795
|
-
program.action(() => {
|
|
796
|
-
|
|
1598
|
+
program.command("config").description("Show configuration").action(async () => {
|
|
1599
|
+
const config = loadConfig();
|
|
1600
|
+
const active = getActiveProvider();
|
|
1601
|
+
console.log(chalk9.cyan.bold("\n\u2699\uFE0F Configuration\n"));
|
|
1602
|
+
console.log(chalk9.gray(" Provider: ") + chalk9.cyan(config.defaultProvider || "not set"));
|
|
1603
|
+
console.log(chalk9.gray(" Model: ") + chalk9.cyan(config.defaultModel || "not set"));
|
|
1604
|
+
console.log(chalk9.gray(" Config: ") + chalk9.gray("~/.agdi/config.json"));
|
|
1605
|
+
console.log(chalk9.cyan.bold("\n\u{1F510} API Keys\n"));
|
|
1606
|
+
const keys = [
|
|
1607
|
+
["Gemini", config.geminiApiKey],
|
|
1608
|
+
["OpenRouter", config.openrouterApiKey],
|
|
1609
|
+
["OpenAI", config.openaiApiKey],
|
|
1610
|
+
["Anthropic", config.anthropicApiKey],
|
|
1611
|
+
["DeepSeek", config.deepseekApiKey]
|
|
1612
|
+
];
|
|
1613
|
+
for (const [name, key] of keys) {
|
|
1614
|
+
const status = key ? chalk9.green("\u2713") : chalk9.gray("\u2717");
|
|
1615
|
+
console.log(` ${status} ${name}`);
|
|
1616
|
+
}
|
|
1617
|
+
console.log("");
|
|
797
1618
|
});
|
|
798
1619
|
program.parse();
|