agdi 1.0.3 → 2.2.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/index.js CHANGED
@@ -2,9 +2,8 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk3 from "chalk";
6
- import ora2 from "ora";
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 PUTER_MODELS = {
77
- // OpenAI Models
78
- "gpt-5": "GPT-5 (Aug 2025)",
79
- "gpt-5-mini": "GPT-5 Mini",
80
- "gpt-4o": "GPT-4o",
81
- "o3-mini": "o3 Mini (Jan 2025)",
82
- "o1": "o1 (Reasoning)",
83
- // Claude Models
84
- "claude-opus-4-5": "Claude 4.5 Opus (Nov 2025)",
85
- "claude-sonnet-4-5": "Claude 4.5 Sonnet (Sep 2025)",
86
- "claude-sonnet-4": "Claude Sonnet 4",
87
- "claude-3-5-sonnet": "Claude 3.5 Sonnet",
88
- // Google Models
89
- "gemini-3-pro-preview": "Gemini 3 Pro (Preview)",
90
- "google/gemini-2.5-flash": "Gemini 2.5 Flash",
91
- "google/gemini-2.5-pro": "Gemini 2.5 Pro",
92
- // Meta Llama
93
- "meta/llama-4-maverick": "Llama 4 Maverick (Apr 2025)",
94
- "meta/llama-4-scout": "Llama 4 Scout",
95
- "meta/llama-3.3-70b": "Llama 3.3 70B",
96
- // DeepSeek
97
- "deepseek/deepseek-v3.2": "DeepSeek V3.2 (Dec 2025)",
98
- "deepseek/deepseek-reasoner": "DeepSeek R1",
99
- // xAI
100
- "x-ai/grok-3": "Grok 3",
101
- // Mistral
102
- "mistral/mistral-large": "Mistral Large"
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: plan.dependencies.reduce((acc, dep) => {
277
- acc[dep] = "latest";
278
- return acc;
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 chalk from "chalk";
648
+ import chalk4 from "chalk";
324
649
  async function login() {
325
- console.log(chalk.cyan.bold("\n\u{1F510} Agdi Authentication\n"));
326
- console.log(chalk.gray("Configure your API key to use Agdi CLI.\n"));
327
- const config = loadConfig();
328
- const provider = await select({
329
- message: "Select your AI provider:",
330
- choices: [
331
- { name: "\u{1F511} Google Gemini (Recommended)", value: "gemini" },
332
- { name: "\u{1F511} OpenAI (GPT-4, GPT-5)", value: "openai" },
333
- { name: "\u{1F511} Anthropic (Claude)", value: "anthropic" },
334
- { name: "\u{1F511} DeepSeek", value: "deepseek" },
335
- { name: "\u{1F511} OpenRouter (Multi-model)", value: "openrouter" },
336
- { name: "\u{1F3E0} Local LLM (Ollama)", value: "ollama" }
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
- config.ollamaUrl = ollamaUrl;
345
- config.defaultProvider = "ollama";
346
- saveConfig(config);
347
- console.log(chalk.green("\n\u2705 Ollama configured"));
348
- console.log(chalk.gray(`Server: ${ollamaUrl}
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
- return;
351
- }
352
- const apiKey = await password({
353
- message: `Enter your ${provider} API key:`,
354
- mask: "*"
355
- });
356
- switch (provider) {
357
- case "gemini":
358
- config.geminiApiKey = apiKey;
359
- break;
360
- case "openai":
361
- config.openaiApiKey = apiKey;
362
- break;
363
- case "anthropic":
364
- config.anthropicApiKey = apiKey;
365
- break;
366
- case "deepseek":
367
- config.deepseekApiKey = apiKey;
368
- break;
369
- case "openrouter":
370
- config.openrouterApiKey = apiKey;
371
- break;
372
- }
373
- config.defaultProvider = provider;
374
- saveConfig(config);
375
- console.log(chalk.green(`
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
- console.log(chalk.gray("Keys stored in ~/.agdi/config.json\n"));
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(chalk.cyan.bold("\n\u{1F4CA} Authentication Status\n"));
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 ? chalk.green("\u2713 Configured") : chalk.gray("\u2717 Not set");
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(chalk.cyan(`
394
- Default: ${config.defaultProvider || "puter (FREE)"}
726
+ console.log(chalk4.cyan(`
727
+ Default: ${config.defaultProvider || "gemini"}
395
728
  `));
396
- console.log(chalk.gray('\u{1F4A1} Tip: Use "agdi auth" to reconfigure\n'));
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 chalk2 from "chalk";
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(chalk2.cyan.bold("\n\u{1F4AC} Agdi Interactive Mode\n"));
557
- console.log(chalk2.gray('Type your coding requests. Type "exit" to quit.\n'));
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(chalk2.yellow("\u26A0\uFE0F Puter.com FREE mode requires browser authentication."));
566
- console.log(chalk2.gray("For CLI usage, please configure an API key:\n"));
567
- console.log(chalk2.cyan(" agdi auth"));
568
- console.log(chalk2.gray("\nSupported providers: Gemini, OpenAI, Anthropic, DeepSeek\n"));
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(chalk2.yellow("\u26A0\uFE0F No API key configured."));
572
- console.log(chalk2.gray('Run "agdi auth" to configure your API key.\n'));
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(chalk2.gray(`Using provider: ${chalk2.cyan(provider)}`));
576
- console.log(chalk2.gray("\u2500".repeat(50) + "\n"));
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: chalk2.cyan("You:")
917
+ message: chalk5.cyan("You:")
581
918
  });
582
919
  if (userInput.toLowerCase() === "exit" || userInput.toLowerCase() === "quit") {
583
- console.log(chalk2.gray("\n\u{1F44B} Goodbye!\n"));
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(chalk2.green("\n\u{1F4C1} Files created:"));
937
+ console.log(chalk5.green("\n\u{1F4C1} Files created:"));
601
938
  for (const file of files) {
602
- console.log(chalk2.gray(` - ${file.path}`));
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,212 +948,672 @@ async function startChat() {
611
948
  default: "./generated-app"
612
949
  });
613
950
  await writeProject(pm.get(), dir);
614
- console.log(chalk2.green(`
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(chalk2.cyan("\nAgdi: ") + response.text + "\n");
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
966
  const errorMessage = error instanceof Error ? error.message : String(error);
626
967
  if (errorMessage.includes("429") || errorMessage.includes("quota") || errorMessage.includes("Resource exhausted") || errorMessage.includes("ResourceExhausted")) {
627
- console.log(chalk2.yellow("\n\u26A0\uFE0F API quota exceeded!"));
628
- console.log(chalk2.gray("Your API key has run out of credits."));
629
- console.log(chalk2.gray("Try: Use a different API key or wait for quota reset.\n"));
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"));
630
971
  } else if (errorMessage.includes("401") || errorMessage.includes("Unauthorized") || errorMessage.includes("Invalid API key")) {
631
- console.log(chalk2.red("\n\u{1F511} Invalid API key"));
632
- console.log(chalk2.gray("Please reconfigure your API key:"));
633
- console.log(chalk2.cyan(" agdi auth\n"));
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"));
634
975
  } else if (errorMessage.includes("403") || errorMessage.includes("Forbidden")) {
635
- console.log(chalk2.red("\n\u{1F6AB} Access denied"));
636
- console.log(chalk2.gray("Your API key doesn't have permission for this operation.\n"));
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"));
637
978
  } else if (errorMessage.includes("network") || errorMessage.includes("fetch") || errorMessage.includes("ENOTFOUND")) {
638
- console.log(chalk2.red("\n\u{1F310} Network error"));
639
- console.log(chalk2.gray("Please check your internet connection.\n"));
979
+ console.log(chalk5.red("\n\u{1F310} Network error"));
980
+ console.log(chalk5.gray("Please check your internet connection.\n"));
640
981
  } else {
641
- console.log(chalk2.red("\n" + errorMessage + "\n"));
982
+ console.log(chalk5.red("\n" + errorMessage + "\n"));
642
983
  }
643
984
  }
644
985
  }
645
986
  }
646
987
 
647
- // src/index.ts
648
- var BANNER = `
649
- ${chalk3.cyan(` ___ __ _ `)}
650
- ${chalk3.cyan(` / | ____ _____/ /(_) `)}
651
- ${chalk3.cyan(` / /| | / __ \`/ __ // / `)}
652
- ${chalk3.cyan(` / ___ |/ /_/ / /_/ // / `)}
653
- ${chalk3.cyan(`/_/ |_|\\__, /\\__,_//_/ `)}
654
- ${chalk3.cyan(` /____/ `)}
655
- `;
656
- var program = new Command();
657
- console.log(BANNER);
658
- console.log(chalk3.gray(" The Open Source AI Architect"));
659
- console.log(chalk3.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"));
660
- program.name("agdi").description(chalk3.cyan("\u{1F680} AI-powered app generator - build apps from natural language")).version("1.0.0");
661
- program.command("auth").description("Login or configure API keys").option("--status", "Show authentication status").action(async (options) => {
662
- if (options.status) {
663
- await showStatus();
664
- } else {
665
- 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;
666
1002
  }
667
- });
668
- program.command("chat").description("Start an interactive coding session").action(startChat);
669
- program.command("init").description("Create a new project interactively").action(async () => {
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() {
670
1101
  const config = loadConfig();
1102
+ return !(config.geminiApiKey || config.openrouterApiKey || config.openaiApiKey || config.anthropicApiKey || config.deepseekApiKey);
1103
+ }
1104
+ function getActiveProvider() {
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"));
671
1138
  const provider = await select2({
672
- message: "Select AI provider:",
1139
+ message: "Which AI provider would you like to use?",
673
1140
  choices: [
674
- { name: "\u{1F193} Agdi Cloud (FREE - No API key!)", value: "puter" },
675
- { name: "\u{1F511} Gemini (Requires API key)", value: "gemini" }
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" }
676
1146
  ]
677
1147
  });
678
- if (provider === "gemini" && !config.geminiApiKey) {
679
- console.log(chalk3.yellow("\n\u26A0\uFE0F No Gemini API key configured\n"));
680
- const apiKey = await input3({
681
- message: "Enter your Gemini API key:",
682
- validate: (v) => v.trim().length > 0 || "API key is required"
683
- });
684
- config.geminiApiKey = apiKey.trim();
685
- saveConfig(config);
686
- console.log(chalk3.green("\u2705 API key saved\n"));
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;
687
1178
  }
688
- const prompt = await input3({
689
- message: "What would you like to build?",
690
- default: "A modern todo app with categories, dark mode, and local storage"
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
691
1185
  });
692
- let model;
693
- if (provider === "puter") {
694
- model = await select2({
695
- message: "Select AI model (400+ available):",
696
- choices: [
697
- { name: "\u26A1 GPT-4.1 Nano (Fast)", value: "gpt-4.1-nano" },
698
- { name: "\u{1F680} Claude Sonnet 4 (Best)", value: "claude-sonnet-4" },
699
- { name: "\u{1F9E0} Claude 3.5 Sonnet", value: "claude-3-5-sonnet" },
700
- { name: "\u{1F48E} GPT-4o", value: "gpt-4o" },
701
- { name: "\u{1F525} Gemini 2.5 Flash", value: "google/gemini-2.5-flash" },
702
- { name: "\u{1F999} Llama 3.3 70B", value: "meta/llama-3.3-70b" },
703
- { name: "\u{1F30A} DeepSeek R1", value: "deepseek/deepseek-r1" },
704
- { name: "\u{1F916} Mistral Large", value: "mistral/mistral-large" },
705
- { name: "\u{1F47D} Grok 2", value: "x-ai/grok-2" }
706
- ]
707
- });
708
- } else {
709
- model = await select2({
710
- message: "Select AI model:",
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:",
711
1217
  choices: [
712
- { name: "Gemini 2.5 Flash (Fast)", value: "gemini-2.5-flash" },
713
- { name: "Gemini 2.5 Pro (Best quality)", value: "gemini-2.5-pro" }
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" }
714
1223
  ]
715
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;
716
1259
  }
717
- const defaultDir = `./${prompt.split(" ").slice(0, 3).join("-").toLowerCase().replace(/[^a-z0-9-]/g, "")}`;
718
- const outputDir = await input3({
719
- message: "Output directory:",
720
- default: defaultDir
721
- });
722
- const shouldProceed = await confirm2({
723
- message: `Generate app in ${chalk3.cyan(outputDir)} using ${chalk3.yellow(model)}?`,
724
- default: true
1260
+ const models = PROVIDER_MODELS[selectedProvider] || PROVIDER_MODELS.gemini;
1261
+ const model = await select2({
1262
+ message: "Select model:",
1263
+ choices: models
725
1264
  });
726
- if (!shouldProceed) {
727
- console.log(chalk3.yellow("\nCancelled."));
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"));
728
1303
  return;
729
1304
  }
730
- console.log("");
731
- const spinner = ora2("Initializing AI architect...").start();
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();
732
1382
  try {
733
- const llm = createLLMProvider(provider, {
734
- apiKey: provider === "gemini" ? config.geminiApiKey : "",
735
- model
736
- });
737
- const pm = new ProjectManager();
738
- pm.create(outputDir.replace("./", ""), prompt);
1383
+ pm.create("generated-app", prompt);
739
1384
  const { plan, files } = await generateApp(prompt, llm, (step, file) => {
740
- spinner.text = file ? `${step} ${chalk3.gray(file)}` : step;
1385
+ spinner.text = file ? `${step} ${chalk8.gray(file)}` : step;
741
1386
  });
742
1387
  pm.updateFiles(files);
743
- pm.updateDependencies(plan.dependencies);
744
- spinner.text = "Writing files to disk...";
745
- await writeProject(pm.get(), outputDir);
746
- spinner.succeed(chalk3.green("App generated successfully!"));
747
- console.log(chalk3.gray("\n\u{1F4C1} Project created at:"), chalk3.cyan(outputDir));
748
- console.log(chalk3.gray(`\u{1F4C4} ${files.length} files generated
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}
749
1408
  `));
750
- console.log(chalk3.white("Next steps:\n"));
751
- console.log(chalk3.gray(` cd ${outputDir}`));
752
- console.log(chalk3.gray(" npm install"));
753
- console.log(chalk3.gray(" npm run dev\n"));
1409
+ console.log(chalk8.gray("Next: cd " + dir + " && npm install && npm run dev\n"));
1410
+ }
754
1411
  } catch (error) {
755
- spinner.fail(chalk3.red("Generation failed"));
756
- console.error(chalk3.red("\n" + (error instanceof Error ? error.message : String(error))));
757
- process.exit(1);
1412
+ spinner.fail("Generation failed");
1413
+ handleError(error);
758
1414
  }
759
- });
760
- program.command("generate <prompt>").alias("g").description("Generate an app from a prompt").option("-p, --provider <provider>", "AI provider (gemini, openai, anthropic)", "gemini").option("-m, --model <model>", "AI model to use", "gemini-2.5-flash").option("-o, --output <dir>", "Output directory", "./generated-app").action(async (prompt, options) => {
761
- const config = loadConfig();
762
- const provider = options.provider;
763
- if (!config.geminiApiKey && provider === "gemini") {
764
- console.error(chalk3.red("\u274C No Gemini API key configured."));
765
- console.error(chalk3.yellow("\u{1F4A1} Run: agdi auth"));
766
- process.exit(1);
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
+ }
767
1437
  }
768
- const spinner = ora2(`Generating app with ${chalk3.cyan(options.model)}...`).start();
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.1.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);
1483
+ }
1484
+ });
1485
+ program.action(async () => {
769
1486
  try {
770
- const llm = createLLMProvider(provider, {
771
- apiKey: provider === "gemini" ? config.geminiApiKey : "",
772
- model: options.model
773
- });
774
- const pm = new ProjectManager();
775
- pm.create(options.output.replace("./", ""), prompt);
776
- const { plan, files } = await generateApp(prompt, llm, (step) => {
777
- spinner.text = step;
778
- });
779
- pm.updateFiles(files);
780
- pm.updateDependencies(plan.dependencies);
781
- await writeProject(pm.get(), options.output);
782
- spinner.succeed(`App generated in ${chalk3.cyan(options.output)}`);
1487
+ if (needsOnboarding()) {
1488
+ await runOnboarding();
1489
+ }
1490
+ await startCodingMode();
783
1491
  } catch (error) {
784
- spinner.fail("Generation failed");
785
- const errorMessage = error instanceof Error ? error.message : String(error);
786
- if (errorMessage.includes("429") || errorMessage.includes("quota") || errorMessage.includes("Resource exhausted")) {
787
- console.log(chalk3.yellow("\n\u26A0\uFE0F API quota exceeded!"));
788
- console.log(chalk3.gray("Your API key has run out of credits.\n"));
789
- } else if (errorMessage.includes("401") || errorMessage.includes("Unauthorized")) {
790
- console.log(chalk3.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
1492
+ if (error.name === "ExitPromptError") {
1493
+ console.log(chalk9.gray("\n\n\u{1F44B} Goodbye!\n"));
1494
+ process.exit(0);
1495
+ }
1496
+ throw error;
1497
+ }
1498
+ });
1499
+ program.command("auth").description("Configure API keys").option("--status", "Show authentication status").action(async (options) => {
1500
+ try {
1501
+ if (options.status) {
1502
+ await showStatus();
791
1503
  } else {
792
- console.error(chalk3.red(errorMessage));
1504
+ await login();
1505
+ }
1506
+ } catch (error) {
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);
793
1521
  }
794
- process.exit(1);
1522
+ throw error;
795
1523
  }
796
1524
  });
797
- program.command("config").description("Show or modify configuration").option("--show", "Show current configuration").action(async (options) => {
798
- await showStatus();
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
+ }
799
1538
  });
800
- program.command("models").description("List available AI models").action(() => {
801
- console.log(chalk3.cyan.bold("\n\u{1F916} Available Puter.com Models (FREE - No API key!)\n"));
802
- const categories = {
803
- "OpenAI": ["gpt-4.1-nano", "gpt-4.1-mini", "gpt-4o", "gpt-4-turbo", "o1", "o1-mini"],
804
- "Anthropic": ["claude-sonnet-4", "claude-3-5-sonnet", "claude-3-opus"],
805
- "Google": ["google/gemini-2.5-flash", "google/gemini-2.5-pro"],
806
- "Open Source": ["meta/llama-3.3-70b", "mistral/mistral-large", "deepseek/deepseek-r1", "x-ai/grok-2"]
807
- };
808
- for (const [category, models] of Object.entries(categories)) {
809
- console.log(chalk3.yellow(`
810
- ${category}:`));
811
- for (const model of models) {
812
- const name = PUTER_MODELS[model] || model;
813
- console.log(chalk3.gray(` - ${model}`) + chalk3.white(` (${name})`));
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);
814
1594
  }
1595
+ throw error;
815
1596
  }
816
- console.log(chalk3.gray("\n ... and 400+ more models available!\n"));
817
- console.log(chalk3.white('Usage: agdi generate "prompt" -p puter -m claude-sonnet-4\n'));
818
1597
  });
819
- program.action(() => {
820
- program.help();
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("");
821
1618
  });
822
1619
  program.parse();