agdi 2.4.0 → 2.5.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.
Files changed (3) hide show
  1. package/README.md +340 -93
  2. package/dist/index.js +587 -108
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -8,8 +8,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/index.ts
10
10
  import { Command } from "commander";
11
- import chalk12 from "chalk";
12
- import ora4 from "ora";
11
+ import chalk13 from "chalk";
12
+ import ora5 from "ora";
13
13
 
14
14
  // src/core/llm/index.ts
15
15
  var PuterProvider = class {
@@ -143,7 +143,7 @@ var OpenRouterProvider = class {
143
143
  headers: {
144
144
  "Content-Type": "application/json",
145
145
  "Authorization": `Bearer ${this.config.apiKey}`,
146
- "HTTP-Referer": "https://agdi.dev",
146
+ "HTTP-Referer": "https://agdi-dev.vercel.app",
147
147
  "X-Title": "Agdi CLI"
148
148
  },
149
149
  body: JSON.stringify({
@@ -173,7 +173,7 @@ var OpenRouterProvider = class {
173
173
  headers: {
174
174
  "Content-Type": "application/json",
175
175
  "Authorization": `Bearer ${this.config.apiKey}`,
176
- "HTTP-Referer": "https://agdi.dev",
176
+ "HTTP-Referer": "https://agdi-dev.vercel.app",
177
177
  "X-Title": "Agdi CLI"
178
178
  },
179
179
  body: JSON.stringify({
@@ -574,6 +574,50 @@ var MALICIOUS_PATTERNS = [
574
574
  category: "dangerous",
575
575
  description: "Template literal with shell commands",
576
576
  severity: "medium"
577
+ },
578
+ // ==================== PROMPT INJECTION DEFENSE ====================
579
+ {
580
+ pattern: /ignore\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?|rules?)/gi,
581
+ category: "suspicious",
582
+ description: "Prompt injection: instruction override attempt",
583
+ severity: "critical"
584
+ },
585
+ {
586
+ pattern: /\[SYSTEM\]|\[INST\]|<\|im_start\|>|<\|endoftext\|>/gi,
587
+ category: "suspicious",
588
+ description: "Prompt injection: model control tokens",
589
+ severity: "critical"
590
+ },
591
+ {
592
+ pattern: /you\s+are\s+(now\s+)?(a|an|the)\s+\w+\s+(that|who|which)/gi,
593
+ category: "suspicious",
594
+ description: "Prompt injection: role override attempt",
595
+ severity: "high"
596
+ },
597
+ {
598
+ pattern: /disregard\s+(all\s+)?safety|bypass\s+(all\s+)?security|disable\s+(all\s+)?restrictions/gi,
599
+ category: "suspicious",
600
+ description: "Prompt injection: safety bypass attempt",
601
+ severity: "critical"
602
+ },
603
+ {
604
+ pattern: /\bexec\s*\(\s*[`'"].*?\$\(.*?\)/g,
605
+ category: "dangerous",
606
+ description: "Command injection via exec with substitution",
607
+ severity: "critical"
608
+ },
609
+ // ==================== NETWORK EXFILTRATION ====================
610
+ {
611
+ pattern: /\bfetch\s*\(\s*['"`]https?:\/\/[^'"`]*\.(ru|cn|tk|ml|ga)\//gi,
612
+ category: "suspicious",
613
+ description: "Request to suspicious TLD",
614
+ severity: "high"
615
+ },
616
+ {
617
+ pattern: /\bnew\s+WebSocket\s*\(\s*['"`]wss?:\/\/(?!localhost|127\.0\.0\.1)/g,
618
+ category: "suspicious",
619
+ description: "WebSocket to external server",
620
+ severity: "medium"
577
621
  }
578
622
  ];
579
623
  function scanCode(code, filename) {
@@ -1172,6 +1216,17 @@ var PROVIDER_MODELS = {
1172
1216
  { name: "\u{1F48E} Gemini 2.0 Flash", value: "gemini-2.0-flash" }
1173
1217
  ],
1174
1218
  openrouter: [
1219
+ // FREE MODELS (no API credits needed)
1220
+ { name: "\u{1F193} GPT-OSS 120B (Free)", value: "openai/gpt-oss-120b:free" },
1221
+ { name: "\u{1F193} GPT-OSS 20B (Free)", value: "openai/gpt-oss-20b:free" },
1222
+ { name: "\u{1F193} Qwen3 Coder (Free)", value: "qwen/qwen3-coder:free" },
1223
+ { name: "\u{1F193} Qwen3 Next 80B (Free)", value: "qwen/qwen3-next-80b-a3b-instruct:free" },
1224
+ { name: "\u{1F193} Kimi K2 (Free)", value: "moonshotai/kimi-k2:free" },
1225
+ { name: "\u{1F193} Gemma 3N E2B (Free)", value: "google/gemma-3n-e2b-it:free" },
1226
+ { name: "\u{1F193} Qwen 2.5 VL 7B (Free)", value: "qwen/qwen-2.5-vl-7b-instruct:free" },
1227
+ { name: "\u{1F193} LFM Thinking 1.2B (Free, Agentic)", value: "liquid/lfm-2.5-1.2b-thinking:free" },
1228
+ { name: "\u26A0\uFE0F Devstral (Free, ends Jan 27)", value: "mistralai/devstral-2512:free" },
1229
+ // PAID MODELS
1175
1230
  { name: "\u{1F9E0} Claude 3.5 Sonnet", value: "anthropic/claude-3.5-sonnet" },
1176
1231
  { name: "\u{1F48E} GPT-4o", value: "openai/gpt-4o" },
1177
1232
  { name: "\u26A1 GPT-4o Mini", value: "openai/gpt-4o-mini" },
@@ -1370,9 +1425,9 @@ async function selectModel() {
1370
1425
  }
1371
1426
 
1372
1427
  // src/commands/agdi-dev.ts
1373
- import { input as input4, confirm as confirm2 } from "@inquirer/prompts";
1374
- import chalk11 from "chalk";
1375
- import ora3 from "ora";
1428
+ import { input as input5, confirm as confirm3 } from "@inquirer/prompts";
1429
+ import chalk12 from "chalk";
1430
+ import ora4 from "ora";
1376
1431
 
1377
1432
  // src/actions/plan-executor.ts
1378
1433
  import { select as select4, confirm } from "@inquirer/prompts";
@@ -1430,6 +1485,48 @@ function summarizePlan(plan) {
1430
1485
  riskTier: maxRiskTier
1431
1486
  };
1432
1487
  }
1488
+ function validateAction(action) {
1489
+ if (!action || typeof action !== "object") return null;
1490
+ const obj = action;
1491
+ const type = obj.type;
1492
+ if (type === "mkdir") {
1493
+ if (typeof obj.path !== "string" || !obj.path) return null;
1494
+ if (obj.path.includes("..") || obj.path.startsWith("/") || /^[A-Z]:/i.test(obj.path)) return null;
1495
+ return { type: "mkdir", path: obj.path };
1496
+ }
1497
+ if (type === "writeFile") {
1498
+ if (typeof obj.path !== "string" || !obj.path) return null;
1499
+ if (typeof obj.content !== "string") return null;
1500
+ if (obj.path.includes("..") || obj.path.startsWith("/") || /^[A-Z]:/i.test(obj.path)) return null;
1501
+ return { type: "writeFile", path: obj.path, content: obj.content };
1502
+ }
1503
+ if (type === "deleteFile") {
1504
+ if (typeof obj.path !== "string" || !obj.path) return null;
1505
+ if (obj.path.includes("..") || obj.path.startsWith("/") || /^[A-Z]:/i.test(obj.path)) return null;
1506
+ return { type: "deleteFile", path: obj.path };
1507
+ }
1508
+ if (type === "exec") {
1509
+ if (!Array.isArray(obj.argv) || obj.argv.length === 0) return null;
1510
+ if (!obj.argv.every((a) => typeof a === "string")) return null;
1511
+ const dangerous = ["sudo", "su", "rm -rf /", "format", "mkfs", "dd", ":(){"];
1512
+ const cmdStr = obj.argv.join(" ").toLowerCase();
1513
+ if (dangerous.some((d) => cmdStr.includes(d))) return null;
1514
+ return {
1515
+ type: "exec",
1516
+ argv: obj.argv,
1517
+ cwd: typeof obj.cwd === "string" ? obj.cwd : void 0
1518
+ };
1519
+ }
1520
+ if (type === "generateImage") {
1521
+ if (typeof obj.prompt !== "string" || !obj.prompt) return null;
1522
+ if (typeof obj.savePath !== "string" || !obj.savePath) return null;
1523
+ if (obj.savePath.includes("..") || obj.savePath.startsWith("/") || /^[A-Z]:/i.test(obj.savePath)) return null;
1524
+ const validStyles = ["realistic", "artistic", "minimal", "tech"];
1525
+ const style = typeof obj.style === "string" && validStyles.includes(obj.style) ? obj.style : void 0;
1526
+ return { type: "generateImage", prompt: obj.prompt, savePath: obj.savePath, style };
1527
+ }
1528
+ return null;
1529
+ }
1433
1530
  function parseActionPlan(response) {
1434
1531
  try {
1435
1532
  const jsonMatch = response.match(/\{[\s\S]*"actions"[\s\S]*\}/);
@@ -1440,10 +1537,23 @@ function parseActionPlan(response) {
1440
1537
  if (!parsed.actions || !Array.isArray(parsed.actions)) {
1441
1538
  return null;
1442
1539
  }
1540
+ const validatedActions = [];
1541
+ for (const action of parsed.actions) {
1542
+ const validated = validateAction(action);
1543
+ if (!validated) {
1544
+ console.warn(`Invalid action rejected: ${JSON.stringify(action).slice(0, 100)}`);
1545
+ continue;
1546
+ }
1547
+ validatedActions.push(validated);
1548
+ }
1549
+ if (validatedActions.length === 0) {
1550
+ return null;
1551
+ }
1552
+ const projectName = typeof parsed.projectName === "string" ? parsed.projectName.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 50) : "generated-app";
1443
1553
  return {
1444
- projectName: parsed.projectName || "generated-app",
1445
- actions: parsed.actions,
1446
- nextSteps: parsed.nextSteps
1554
+ projectName,
1555
+ actions: validatedActions,
1556
+ nextSteps: typeof parsed.nextSteps === "string" ? parsed.nextSteps : void 0
1447
1557
  };
1448
1558
  } catch {
1449
1559
  return null;
@@ -1760,6 +1870,68 @@ async function deleteFileTool(path4) {
1760
1870
  return { success: false, error: msg };
1761
1871
  }
1762
1872
  }
1873
+ async function applyPatchTool(path4, unifiedDiff) {
1874
+ const validation = validatePath(path4);
1875
+ if (!validation.valid) {
1876
+ return { success: false, error: validation.error };
1877
+ }
1878
+ try {
1879
+ const current = existsSync3(validation.resolved) ? await readFile(validation.resolved, "utf-8") : "";
1880
+ const patched = applySimplePatch(current, unifiedDiff);
1881
+ if (patched === null) {
1882
+ return { success: false, error: "Failed to apply patch" };
1883
+ }
1884
+ await writeFile(validation.resolved, patched, "utf-8");
1885
+ logEvent({
1886
+ eventType: "command_result",
1887
+ command: `applyPatch ${path4}`,
1888
+ result: { exitCode: 0 },
1889
+ metadata: {
1890
+ tool: "applyPatchTool",
1891
+ path: validation.resolved,
1892
+ patchLength: unifiedDiff.length
1893
+ }
1894
+ });
1895
+ return { success: true };
1896
+ } catch (error) {
1897
+ const msg = error instanceof Error ? error.message : String(error);
1898
+ return { success: false, error: msg };
1899
+ }
1900
+ }
1901
+ function applySimplePatch(content, diff) {
1902
+ const lines = content.split("\n");
1903
+ const diffLines = diff.split("\n");
1904
+ let lineIndex = 0;
1905
+ const result = [];
1906
+ for (const diffLine of diffLines) {
1907
+ if (diffLine.startsWith("---") || diffLine.startsWith("+++") || diffLine.startsWith("@@")) {
1908
+ continue;
1909
+ }
1910
+ if (diffLine.startsWith("-")) {
1911
+ lineIndex++;
1912
+ } else if (diffLine.startsWith("+")) {
1913
+ result.push(diffLine.slice(1));
1914
+ } else if (diffLine.startsWith(" ") || diffLine === "") {
1915
+ if (lineIndex < lines.length) {
1916
+ result.push(lines[lineIndex]);
1917
+ lineIndex++;
1918
+ }
1919
+ }
1920
+ }
1921
+ while (lineIndex < lines.length) {
1922
+ result.push(lines[lineIndex]);
1923
+ lineIndex++;
1924
+ }
1925
+ return result.join("\n");
1926
+ }
1927
+ function resolvePath(path4) {
1928
+ const env = getEnvironment();
1929
+ return resolve(env.workspaceRoot, path4);
1930
+ }
1931
+ function fileExists(path4) {
1932
+ const resolved = resolvePath(path4);
1933
+ return existsSync3(resolved);
1934
+ }
1763
1935
 
1764
1936
  // src/security/permission-gate.ts
1765
1937
  import { resolve as resolve2, relative as relative2, isAbsolute as isAbsolute2 } from "path";
@@ -2630,6 +2802,58 @@ async function ensureTrusted(workspacePath) {
2630
2802
  return result !== null;
2631
2803
  }
2632
2804
 
2805
+ // src/core/image-generator.ts
2806
+ async function generateImage(prompt, apiKey, options = {}) {
2807
+ const { width = 1024, height = 1024, style } = options;
2808
+ let enhancedPrompt = prompt;
2809
+ if (style === "tech") {
2810
+ enhancedPrompt = `Modern tech startup style, clean minimal design, professional: ${prompt}`;
2811
+ } else if (style === "realistic") {
2812
+ enhancedPrompt = `Photorealistic, high quality, professional photography: ${prompt}`;
2813
+ } else if (style === "artistic") {
2814
+ enhancedPrompt = `Artistic, creative, vibrant colors: ${prompt}`;
2815
+ } else if (style === "minimal") {
2816
+ enhancedPrompt = `Minimalist, clean, simple, modern: ${prompt}`;
2817
+ }
2818
+ const response = await fetch("https://openrouter.ai/api/v1/images/generations", {
2819
+ method: "POST",
2820
+ headers: {
2821
+ "Content-Type": "application/json",
2822
+ "Authorization": `Bearer ${apiKey}`,
2823
+ "HTTP-Referer": "https://agdi-dev.vercel.app",
2824
+ "X-Title": "Agdi CLI"
2825
+ },
2826
+ body: JSON.stringify({
2827
+ model: "bytedance-seed/seedream-4.5",
2828
+ prompt: enhancedPrompt,
2829
+ n: 1,
2830
+ size: `${width}x${height}`
2831
+ })
2832
+ });
2833
+ if (!response.ok) {
2834
+ const error = await response.text();
2835
+ throw new Error(`Image generation failed: ${response.status} - ${error}`);
2836
+ }
2837
+ const data = await response.json();
2838
+ const imageData = data.data?.[0];
2839
+ if (!imageData) {
2840
+ throw new Error("No image data returned from API");
2841
+ }
2842
+ return {
2843
+ url: imageData.url,
2844
+ base64: imageData.b64_json,
2845
+ revisedPrompt: imageData.revised_prompt
2846
+ };
2847
+ }
2848
+ async function downloadImageAsBase64(url) {
2849
+ const response = await fetch(url);
2850
+ if (!response.ok) {
2851
+ throw new Error(`Failed to download image: ${response.status}`);
2852
+ }
2853
+ const buffer = await response.arrayBuffer();
2854
+ return Buffer.from(buffer).toString("base64");
2855
+ }
2856
+
2633
2857
  // src/actions/plan-executor.ts
2634
2858
  function displayPlanSummary(plan) {
2635
2859
  const summary = summarizePlan(plan);
@@ -2678,6 +2902,9 @@ function displayActionProgress(action, index, total) {
2678
2902
  case "exec":
2679
2903
  console.log(chalk10.blue(`${num} Running: ${action.argv.join(" ")}`));
2680
2904
  break;
2905
+ case "generateImage":
2906
+ console.log(chalk10.magenta(`${num} \u{1F3A8} Generating image: ${action.savePath}`));
2907
+ break;
2681
2908
  }
2682
2909
  }
2683
2910
  async function dryRunActions(plan) {
@@ -2776,6 +3003,41 @@ async function executeAction(action) {
2776
3003
  });
2777
3004
  });
2778
3005
  }
3006
+ case "generateImage": {
3007
+ const config = loadConfig();
3008
+ const apiKey = config.openrouterApiKey;
3009
+ if (!apiKey) {
3010
+ return {
3011
+ action,
3012
+ success: false,
3013
+ error: "OpenRouter API key required for image generation. Run: agdi auth"
3014
+ };
3015
+ }
3016
+ try {
3017
+ console.log(chalk10.gray(` Prompt: "${action.prompt.slice(0, 50)}..."`));
3018
+ const result = await generateImage(action.prompt, apiKey, { style: action.style });
3019
+ if (result.url) {
3020
+ const base64Data = await downloadImageAsBase64(result.url);
3021
+ const imageBuffer = Buffer.from(base64Data, "base64");
3022
+ await writeFileTool(action.savePath, imageBuffer.toString("base64"));
3023
+ console.log(chalk10.green(` \u2713 Saved to ${action.savePath}`));
3024
+ } else if (result.base64) {
3025
+ await writeFileTool(action.savePath, result.base64);
3026
+ console.log(chalk10.green(` \u2713 Saved to ${action.savePath}`));
3027
+ }
3028
+ return {
3029
+ action,
3030
+ success: true,
3031
+ output: `Image generated: ${action.savePath}`
3032
+ };
3033
+ } catch (err) {
3034
+ return {
3035
+ action,
3036
+ success: false,
3037
+ error: err instanceof Error ? err.message : "Image generation failed"
3038
+ };
3039
+ }
3040
+ }
2779
3041
  }
2780
3042
  }
2781
3043
  async function executePlan(plan) {
@@ -3455,6 +3717,204 @@ function clearConversation() {
3455
3717
  }
3456
3718
  }
3457
3719
 
3720
+ // src/core/file-editor.ts
3721
+ import { readFile as readFile2 } from "fs/promises";
3722
+ import { existsSync as existsSync9 } from "fs";
3723
+ import chalk11 from "chalk";
3724
+ import ora3 from "ora";
3725
+ import { input as input4, confirm as confirm2 } from "@inquirer/prompts";
3726
+ var EDIT_SYSTEM_PROMPT = `You are a surgical code editor. Given a file's content and an edit instruction, output ONLY a unified diff patch.
3727
+
3728
+ ## Output Format (STRICT)
3729
+ \`\`\`diff
3730
+ --- a/filename
3731
+ +++ b/filename
3732
+ @@ -start,count +start,count @@
3733
+ context line (unchanged)
3734
+ -removed line
3735
+ +added line
3736
+ context line (unchanged)
3737
+ \`\`\`
3738
+
3739
+ ## Rules
3740
+ 1. Output ONLY the diff block, no explanation before or after
3741
+ 2. Include 3 context lines around each change
3742
+ 3. Use proper unified diff format with @@ hunk headers
3743
+ 4. Preserve exact indentation (spaces/tabs)
3744
+ 5. Multiple changes = multiple @@ hunks
3745
+ 6. Line numbers in @@ must be accurate
3746
+
3747
+ ## Example
3748
+ Input: "Add a console.log at line 5"
3749
+ Output:
3750
+ \`\`\`diff
3751
+ --- a/index.ts
3752
+ +++ b/index.ts
3753
+ @@ -3,6 +3,7 @@
3754
+ import { foo } from './foo';
3755
+
3756
+ function main() {
3757
+ + console.log('Debug point');
3758
+ const result = foo();
3759
+ return result;
3760
+ }
3761
+ \`\`\``;
3762
+ async function readFileForEdit(filePath) {
3763
+ const resolved = resolvePath(filePath);
3764
+ if (!existsSync9(resolved)) {
3765
+ return null;
3766
+ }
3767
+ try {
3768
+ const content = await readFile2(resolved, "utf-8");
3769
+ const lines = content.split("\n");
3770
+ const numbered = lines.map((line, i) => `${String(i + 1).padStart(4, " ")} \u2502 ${line}`).join("\n");
3771
+ return { content, numbered };
3772
+ } catch {
3773
+ return null;
3774
+ }
3775
+ }
3776
+ function extractDiff(response) {
3777
+ const diffMatch = response.match(/```diff\n([\s\S]*?)```/);
3778
+ if (diffMatch) {
3779
+ return diffMatch[1].trim();
3780
+ }
3781
+ const codeMatch = response.match(/```\n([\s\S]*?)```/);
3782
+ if (codeMatch && codeMatch[1].includes("@@")) {
3783
+ return codeMatch[1].trim();
3784
+ }
3785
+ if (response.includes("@@") && (response.includes("---") || response.includes("+++"))) {
3786
+ return response.trim();
3787
+ }
3788
+ return null;
3789
+ }
3790
+ function previewDiff(diff) {
3791
+ console.log(chalk11.cyan.bold("\n\u{1F4DD} Proposed Changes:\n"));
3792
+ const lines = diff.split("\n");
3793
+ for (const line of lines) {
3794
+ if (line.startsWith("+++") || line.startsWith("---")) {
3795
+ console.log(chalk11.gray(line));
3796
+ } else if (line.startsWith("@@")) {
3797
+ console.log(chalk11.cyan(line));
3798
+ } else if (line.startsWith("+")) {
3799
+ console.log(chalk11.green(line));
3800
+ } else if (line.startsWith("-")) {
3801
+ console.log(chalk11.red(line));
3802
+ } else {
3803
+ console.log(chalk11.gray(line));
3804
+ }
3805
+ }
3806
+ console.log("");
3807
+ }
3808
+ function countChanges(diff) {
3809
+ const lines = diff.split("\n");
3810
+ let added = 0;
3811
+ let removed = 0;
3812
+ for (const line of lines) {
3813
+ if (line.startsWith("+") && !line.startsWith("+++")) {
3814
+ added++;
3815
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
3816
+ removed++;
3817
+ }
3818
+ }
3819
+ return { added, removed };
3820
+ }
3821
+ async function handleFileEdit(filePath, llm) {
3822
+ const env = getEnvironment();
3823
+ if (!fileExists(filePath)) {
3824
+ console.log(chalk11.red(`
3825
+ \u2717 File not found: ${filePath}
3826
+ `));
3827
+ return { success: false, error: "File not found" };
3828
+ }
3829
+ const fileData = await readFileForEdit(filePath);
3830
+ if (!fileData) {
3831
+ console.log(chalk11.red(`
3832
+ \u2717 Could not read file: ${filePath}
3833
+ `));
3834
+ return { success: false, error: "Could not read file" };
3835
+ }
3836
+ const previewLines = fileData.numbered.split("\n").slice(0, 20);
3837
+ console.log(chalk11.cyan.bold(`
3838
+ \u{1F4C4} ${filePath}
3839
+ `));
3840
+ console.log(chalk11.gray(previewLines.join("\n")));
3841
+ if (fileData.content.split("\n").length > 20) {
3842
+ console.log(chalk11.gray(` ... (${fileData.content.split("\n").length - 20} more lines)`));
3843
+ }
3844
+ console.log("");
3845
+ const instruction = await input4({
3846
+ message: chalk11.yellow("Describe the edit:")
3847
+ });
3848
+ if (!instruction.trim()) {
3849
+ console.log(chalk11.gray("\n(no instruction provided)\n"));
3850
+ return { success: false, error: "No instruction" };
3851
+ }
3852
+ const spinner = ora3("Generating edit...").start();
3853
+ try {
3854
+ const prompt = `File: ${filePath}
3855
+
3856
+ Content:
3857
+ \`\`\`
3858
+ ${fileData.content}
3859
+ \`\`\`
3860
+
3861
+ Edit instruction: ${instruction}
3862
+
3863
+ Generate the unified diff to make this change.`;
3864
+ const response = await llm.generate(prompt, EDIT_SYSTEM_PROMPT);
3865
+ spinner.stop();
3866
+ const diff = extractDiff(response.text);
3867
+ if (!diff) {
3868
+ console.log(chalk11.yellow("\n\u26A0\uFE0F Could not generate a valid diff.\n"));
3869
+ console.log(chalk11.gray("AI response:\n" + response.text.slice(0, 500)));
3870
+ return { success: false, error: "Invalid diff generated" };
3871
+ }
3872
+ previewDiff(diff);
3873
+ const changes = countChanges(diff);
3874
+ console.log(chalk11.gray(` ${chalk11.green(`+${changes.added}`)} additions, ${chalk11.red(`-${changes.removed}`)} deletions
3875
+ `));
3876
+ const shouldApply = await confirm2({
3877
+ message: "Apply these changes?",
3878
+ default: true
3879
+ });
3880
+ if (!shouldApply) {
3881
+ console.log(chalk11.gray("\n\u{1F44B} Edit cancelled.\n"));
3882
+ return { success: false, error: "Cancelled by user" };
3883
+ }
3884
+ const applySpinner = ora3("Applying changes...").start();
3885
+ const result = await applyPatchTool(filePath, diff);
3886
+ applySpinner.stop();
3887
+ if (result.success) {
3888
+ console.log(chalk11.green(`
3889
+ \u2713 Successfully edited ${filePath}
3890
+ `));
3891
+ logEvent({
3892
+ eventType: "command_result",
3893
+ command: `/edit ${filePath}`,
3894
+ result: { exitCode: 0 },
3895
+ metadata: {
3896
+ instruction,
3897
+ added: changes.added,
3898
+ removed: changes.removed
3899
+ }
3900
+ });
3901
+ return { success: true, linesChanged: changes.added + changes.removed };
3902
+ } else {
3903
+ console.log(chalk11.red(`
3904
+ \u2717 Failed to apply changes: ${result.error}
3905
+ `));
3906
+ return { success: false, error: result.error };
3907
+ }
3908
+ } catch (error) {
3909
+ spinner.stop();
3910
+ const msg = error instanceof Error ? error.message : String(error);
3911
+ console.log(chalk11.red(`
3912
+ \u2717 Error: ${msg}
3913
+ `));
3914
+ return { success: false, error: msg };
3915
+ }
3916
+ }
3917
+
3458
3918
  // src/commands/agdi-dev.ts
3459
3919
  var BASE_CHAT_PROMPT = `You are Agdi dev, an elite AI coding assistant. You help developers write code, debug issues, and build applications.
3460
3920
 
@@ -3511,6 +3971,14 @@ Instead, output a single JSON object with this exact structure:
3511
3971
  - writeFile: { type: "writeFile", path: "relative/path/file.ts", content: "file contents" }
3512
3972
  - deleteFile: { type: "deleteFile", path: "relative/path/file.ts" }
3513
3973
  - exec: { type: "exec", argv: ["command", "arg1", "arg2"], cwd: "." }
3974
+ - generateImage: { type: "generateImage", prompt: "description of image", savePath: "public/hero.png", style: "tech" }
3975
+
3976
+ ## Image Generation
3977
+ For landing pages, portfolios, or apps that need visuals, use generateImage actions:
3978
+ - style options: "realistic", "artistic", "minimal", "tech"
3979
+ - Save to public/ folder (e.g., public/hero.png, public/logo.png)
3980
+ - Use descriptive prompts based on the project type
3981
+ - Example: { type: "generateImage", prompt: "Modern tech startup hero image, abstract blue gradient", savePath: "public/hero.png", style: "tech" }
3514
3982
 
3515
3983
  ## Rules
3516
3984
  1. All paths MUST be relative to workspace root (no leading /)
@@ -3520,11 +3988,12 @@ Instead, output a single JSON object with this exact structure:
3520
3988
  5. Create complete, production-ready code
3521
3989
  6. Use TypeScript by default
3522
3990
  7. Include proper error handling
3523
- 8. Output ONLY the JSON object, no extra text`;
3991
+ 8. For visual projects, include 1-2 generateImage actions for hero/logo
3992
+ 9. Output ONLY the JSON object, no extra text`;
3524
3993
  async function startCodingMode() {
3525
3994
  const activeConfig = getActiveProvider();
3526
3995
  if (!activeConfig) {
3527
- console.log(chalk11.red("\u274C No API key configured. Run: agdi"));
3996
+ console.log(chalk12.red("\u274C No API key configured. Run: agdi"));
3528
3997
  return;
3529
3998
  }
3530
3999
  const { provider, apiKey, model } = activeConfig;
@@ -3536,24 +4005,24 @@ async function startCodingMode() {
3536
4005
  process.exit(0);
3537
4006
  }
3538
4007
  logSessionStart(env.workspaceRoot, env.trustLevel);
3539
- console.log(chalk11.cyan.bold("\u26A1 Agdi dev\n"));
3540
- console.log(chalk11.gray(`Model: ${chalk11.cyan(model)}`));
3541
- console.log(chalk11.gray(`Workspace: ${chalk11.cyan(env.workspaceRoot)}`));
3542
- console.log(chalk11.gray("Commands: /status, /diff, /commit, /build, /clear, /history, /model, /help, /exit\n"));
3543
- console.log(chalk11.gray("\u2500".repeat(50) + "\n"));
4008
+ console.log(chalk12.cyan.bold("\u26A1 Agdi dev\n"));
4009
+ console.log(chalk12.gray(`Model: ${chalk12.cyan(model)}`));
4010
+ console.log(chalk12.gray(`Workspace: ${chalk12.cyan(env.workspaceRoot)}`));
4011
+ console.log(chalk12.gray("Commands: /status, /diff, /commit, /build, /clear, /history, /model, /help, /exit\n"));
4012
+ console.log(chalk12.gray("\u2500".repeat(50) + "\n"));
3544
4013
  const pm = new ProjectManager();
3545
4014
  let llm = createLLMProvider(provider, { apiKey, model });
3546
4015
  const conversation = getConversation();
3547
4016
  conversation.setSystemPrompt(buildContextAwarePrompt());
3548
4017
  while (true) {
3549
4018
  try {
3550
- const userInput = await input4({
3551
- message: chalk11.green("\u2192")
4019
+ const userInput = await input5({
4020
+ message: chalk12.green("\u2192")
3552
4021
  });
3553
4022
  const trimmed = userInput.trim().toLowerCase();
3554
4023
  if (trimmed === "/exit" || trimmed === "exit" || trimmed === "quit") {
3555
4024
  logSessionEnd();
3556
- console.log(chalk11.gray("\n\u{1F44B} Goodbye!\n"));
4025
+ console.log(chalk12.gray("\n\u{1F44B} Goodbye!\n"));
3557
4026
  break;
3558
4027
  }
3559
4028
  if (trimmed === "/help") {
@@ -3568,34 +4037,34 @@ async function startCodingMode() {
3568
4037
  apiKey: newConfig.apiKey,
3569
4038
  model: newConfig.model
3570
4039
  });
3571
- console.log(chalk11.gray(`Now using: ${chalk11.cyan(newConfig.model)}
4040
+ console.log(chalk12.gray(`Now using: ${chalk12.cyan(newConfig.model)}
3572
4041
  `));
3573
4042
  }
3574
4043
  continue;
3575
4044
  }
3576
4045
  if (trimmed === "/chat") {
3577
- console.log(chalk11.gray("\nSwitching to chat mode. Type /code to return.\n"));
4046
+ console.log(chalk12.gray("\nSwitching to chat mode. Type /code to return.\n"));
3578
4047
  await chatMode(llm);
3579
4048
  continue;
3580
4049
  }
3581
4050
  if (trimmed === "/clear") {
3582
4051
  clearConversation();
3583
4052
  conversation.setSystemPrompt(buildContextAwarePrompt());
3584
- console.log(chalk11.green("\n\u2713 Conversation cleared.\n"));
4053
+ console.log(chalk12.green("\n\u2713 Conversation cleared.\n"));
3585
4054
  continue;
3586
4055
  }
3587
4056
  if (trimmed === "/history") {
3588
4057
  const messages = conversation.getMessages();
3589
4058
  if (messages.length === 0) {
3590
- console.log(chalk11.gray("\n(no conversation history)\n"));
4059
+ console.log(chalk12.gray("\n(no conversation history)\n"));
3591
4060
  } else {
3592
- console.log(chalk11.cyan.bold("\n\u{1F4DC} Conversation History\n"));
3593
- console.log(chalk11.gray(conversation.getSummary()));
4061
+ console.log(chalk12.cyan.bold("\n\u{1F4DC} Conversation History\n"));
4062
+ console.log(chalk12.gray(conversation.getSummary()));
3594
4063
  console.log("");
3595
4064
  for (const msg of messages.slice(-6)) {
3596
- const role = msg.role === "user" ? chalk11.green("You") : chalk11.cyan("AI");
4065
+ const role = msg.role === "user" ? chalk12.green("You") : chalk12.cyan("AI");
3597
4066
  const preview = msg.content.slice(0, 80) + (msg.content.length > 80 ? "..." : "");
3598
- console.log(` ${role}: ${chalk11.gray(preview)}`);
4067
+ console.log(` ${role}: ${chalk12.gray(preview)}`);
3599
4068
  }
3600
4069
  console.log("");
3601
4070
  }
@@ -3613,12 +4082,21 @@ async function startCodingMode() {
3613
4082
  await handleGitCommit(llm);
3614
4083
  continue;
3615
4084
  }
4085
+ if (trimmed.startsWith("/edit ")) {
4086
+ const filePath = userInput.slice(6).trim();
4087
+ if (filePath) {
4088
+ await handleFileEdit(filePath, llm);
4089
+ } else {
4090
+ console.log(chalk12.yellow("\nUsage: /edit <file>\n"));
4091
+ }
4092
+ continue;
4093
+ }
3616
4094
  if (trimmed.startsWith("/build ") || trimmed.startsWith("build ")) {
3617
4095
  const prompt = userInput.replace(/^\/?build\s+/i, "").trim();
3618
4096
  if (prompt) {
3619
4097
  await buildAppWithPlan(prompt, llm);
3620
4098
  } else {
3621
- console.log(chalk11.yellow("\nUsage: /build <description>\n"));
4099
+ console.log(chalk12.yellow("\nUsage: /build <description>\n"));
3622
4100
  }
3623
4101
  continue;
3624
4102
  }
@@ -3630,7 +4108,7 @@ async function startCodingMode() {
3630
4108
  await buildAppWithPlan(userInput, llm);
3631
4109
  continue;
3632
4110
  }
3633
- const spinner = ora3("Thinking...").start();
4111
+ const spinner = ora4("Thinking...").start();
3634
4112
  try {
3635
4113
  conversation.addUserMessage(userInput);
3636
4114
  let response;
@@ -3650,7 +4128,7 @@ async function startCodingMode() {
3650
4128
  } catch (error) {
3651
4129
  if (error.name === "ExitPromptError") {
3652
4130
  logSessionEnd();
3653
- console.log(chalk11.gray("\n\n\u{1F44B} Goodbye!\n"));
4131
+ console.log(chalk12.gray("\n\n\u{1F44B} Goodbye!\n"));
3654
4132
  process.exit(0);
3655
4133
  }
3656
4134
  throw error;
@@ -3658,13 +4136,13 @@ async function startCodingMode() {
3658
4136
  }
3659
4137
  }
3660
4138
  async function buildAppWithPlan(prompt, llm) {
3661
- const spinner = ora3("Generating action plan...").start();
4139
+ const spinner = ora4("Generating action plan...").start();
3662
4140
  try {
3663
4141
  const response = await llm.generate(prompt, BUILD_SYSTEM_PROMPT);
3664
4142
  spinner.stop();
3665
4143
  const result = await parseAndExecutePlan(response.text);
3666
4144
  if (!result) {
3667
- console.log(chalk11.yellow("\n\u26A0\uFE0F Model did not return an action plan. Showing response:\n"));
4145
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Model did not return an action plan. Showing response:\n"));
3668
4146
  console.log(formatResponse(response.text) + "\n");
3669
4147
  }
3670
4148
  } catch (error) {
@@ -3675,15 +4153,15 @@ async function buildAppWithPlan(prompt, llm) {
3675
4153
  async function chatMode(llm) {
3676
4154
  while (true) {
3677
4155
  try {
3678
- const userInput = await input4({
3679
- message: chalk11.blue("\u{1F4AC}")
4156
+ const userInput = await input5({
4157
+ message: chalk12.blue("\u{1F4AC}")
3680
4158
  });
3681
4159
  if (userInput.toLowerCase() === "/code" || userInput.toLowerCase() === "/exit") {
3682
- console.log(chalk11.gray("\nBack to Agdi dev mode.\n"));
4160
+ console.log(chalk12.gray("\nBack to Agdi dev mode.\n"));
3683
4161
  return;
3684
4162
  }
3685
4163
  if (!userInput.trim()) continue;
3686
- const spinner = ora3("...").start();
4164
+ const spinner = ora4("...").start();
3687
4165
  const response = await llm.generate(userInput, "You are a helpful assistant. Be friendly and concise.");
3688
4166
  spinner.stop();
3689
4167
  console.log("\n" + response.text + "\n");
@@ -3697,10 +4175,10 @@ async function chatMode(llm) {
3697
4175
  }
3698
4176
  async function handleGitStatus(llm) {
3699
4177
  if (!isGitRepo()) {
3700
- console.log(chalk11.yellow("\n\u26A0\uFE0F Not a git repository\n"));
4178
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Not a git repository\n"));
3701
4179
  return;
3702
4180
  }
3703
- const spinner = ora3("Analyzing git status...").start();
4181
+ const spinner = ora4("Analyzing git status...").start();
3704
4182
  try {
3705
4183
  const status = getStatus();
3706
4184
  const statusText = formatStatusForPrompt(status);
@@ -3709,9 +4187,9 @@ async function handleGitStatus(llm) {
3709
4187
  ${statusText}`;
3710
4188
  const response = await llm.generate(prompt, BASE_CHAT_PROMPT);
3711
4189
  spinner.stop();
3712
- console.log(chalk11.cyan.bold("\n\u{1F4CA} Git Status Analysis\n"));
3713
- console.log(chalk11.gray(statusText));
3714
- console.log(chalk11.cyan("\n\u2500\u2500\u2500 AI Analysis \u2500\u2500\u2500\n"));
4190
+ console.log(chalk12.cyan.bold("\n\u{1F4CA} Git Status Analysis\n"));
4191
+ console.log(chalk12.gray(statusText));
4192
+ console.log(chalk12.cyan("\n\u2500\u2500\u2500 AI Analysis \u2500\u2500\u2500\n"));
3715
4193
  console.log(formatResponse(response.text) + "\n");
3716
4194
  } catch (error) {
3717
4195
  spinner.fail("Error analyzing status");
@@ -3720,16 +4198,16 @@ ${statusText}`;
3720
4198
  }
3721
4199
  async function handleGitDiff(llm) {
3722
4200
  if (!isGitRepo()) {
3723
- console.log(chalk11.yellow("\n\u26A0\uFE0F Not a git repository\n"));
4201
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Not a git repository\n"));
3724
4202
  return;
3725
4203
  }
3726
- const spinner = ora3("Analyzing changes...").start();
4204
+ const spinner = ora4("Analyzing changes...").start();
3727
4205
  try {
3728
4206
  const stagedDiff = getDiff(true);
3729
4207
  const unstagedDiff = getDiff(false);
3730
4208
  if (stagedDiff.files.length === 0 && unstagedDiff.files.length === 0) {
3731
4209
  spinner.stop();
3732
- console.log(chalk11.gray("\n(no changes to analyze)\n"));
4210
+ console.log(chalk12.gray("\n(no changes to analyze)\n"));
3733
4211
  return;
3734
4212
  }
3735
4213
  let diffContext = "";
@@ -3744,7 +4222,7 @@ async function handleGitDiff(llm) {
3744
4222
  ${diffContext}`;
3745
4223
  const response = await llm.generate(prompt, BASE_CHAT_PROMPT);
3746
4224
  spinner.stop();
3747
- console.log(chalk11.cyan.bold("\n\u{1F50D} Diff Analysis\n"));
4225
+ console.log(chalk12.cyan.bold("\n\u{1F50D} Diff Analysis\n"));
3748
4226
  console.log(formatResponse(response.text) + "\n");
3749
4227
  } catch (error) {
3750
4228
  spinner.fail("Error analyzing diff");
@@ -3753,15 +4231,15 @@ ${diffContext}`;
3753
4231
  }
3754
4232
  async function handleGitCommit(llm) {
3755
4233
  if (!isGitRepo()) {
3756
- console.log(chalk11.yellow("\n\u26A0\uFE0F Not a git repository\n"));
4234
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Not a git repository\n"));
3757
4235
  return;
3758
4236
  }
3759
4237
  const status = getStatus();
3760
4238
  if (status.staged.length === 0) {
3761
- console.log(chalk11.yellow("\n\u26A0\uFE0F No staged changes. Stage some changes first with `git add`.\n"));
4239
+ console.log(chalk12.yellow("\n\u26A0\uFE0F No staged changes. Stage some changes first with `git add`.\n"));
3762
4240
  return;
3763
4241
  }
3764
- const spinner = ora3("Generating commit message...").start();
4242
+ const spinner = ora4("Generating commit message...").start();
3765
4243
  try {
3766
4244
  const stagedDiff = getDiff(true);
3767
4245
  const diffText = formatDiffForPrompt(stagedDiff);
@@ -3776,10 +4254,10 @@ ${diffText}`;
3776
4254
  const response = await llm.generate(prompt, "You are a git commit message generator. Output ONLY the commit message, no explanation.");
3777
4255
  spinner.stop();
3778
4256
  const commitMessage = response.text.trim().split("\n")[0];
3779
- console.log(chalk11.cyan.bold("\n\u{1F4AC} Generated Commit Message\n"));
3780
- console.log(chalk11.white(` ${commitMessage}
4257
+ console.log(chalk12.cyan.bold("\n\u{1F4AC} Generated Commit Message\n"));
4258
+ console.log(chalk12.white(` ${commitMessage}
3781
4259
  `));
3782
- const shouldCommit = await confirm2({
4260
+ const shouldCommit = await confirm3({
3783
4261
  message: "Commit with this message?",
3784
4262
  default: true
3785
4263
  });
@@ -3791,12 +4269,12 @@ ${diffText}`;
3791
4269
  cwd: env.workspaceRoot,
3792
4270
  stdio: "inherit"
3793
4271
  });
3794
- console.log(chalk11.green("\n\u2713 Committed successfully!\n"));
4272
+ console.log(chalk12.green("\n\u2713 Committed successfully!\n"));
3795
4273
  } catch (gitError) {
3796
- console.log(chalk11.red("\n\u2717 Commit failed. Check git output above.\n"));
4274
+ console.log(chalk12.red("\n\u2717 Commit failed. Check git output above.\n"));
3797
4275
  }
3798
4276
  } else {
3799
- console.log(chalk11.gray("\n\u{1F44B} Commit cancelled.\n"));
4277
+ console.log(chalk12.gray("\n\u{1F44B} Commit cancelled.\n"));
3800
4278
  }
3801
4279
  } catch (error) {
3802
4280
  spinner.fail("Error generating commit");
@@ -3805,61 +4283,62 @@ ${diffText}`;
3805
4283
  }
3806
4284
  function formatResponse(text) {
3807
4285
  return text.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => {
3808
- const header = lang ? chalk11.gray(`\u2500\u2500 ${lang} \u2500\u2500`) : chalk11.gray("\u2500\u2500 code \u2500\u2500");
4286
+ const header = lang ? chalk12.gray(`\u2500\u2500 ${lang} \u2500\u2500`) : chalk12.gray("\u2500\u2500 code \u2500\u2500");
3809
4287
  return `
3810
4288
  ${header}
3811
- ${chalk11.white(code.trim())}
3812
- ${chalk11.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
4289
+ ${chalk12.white(code.trim())}
4290
+ ${chalk12.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
3813
4291
  `;
3814
4292
  });
3815
4293
  }
3816
4294
  function showHelp() {
3817
- console.log(chalk11.cyan.bold("\n\u{1F4D6} Commands\n"));
3818
- console.log(chalk11.cyan(" Git Commands:"));
3819
- console.log(chalk11.gray(" /status ") + "AI analysis of git status");
3820
- console.log(chalk11.gray(" /diff ") + "AI explanation of current changes");
3821
- console.log(chalk11.gray(" /commit ") + "Generate and run git commit");
4295
+ console.log(chalk12.cyan.bold("\n\u{1F4D6} Commands\n"));
4296
+ console.log(chalk12.cyan(" Git Commands:"));
4297
+ console.log(chalk12.gray(" /status ") + "AI analysis of git status");
4298
+ console.log(chalk12.gray(" /diff ") + "AI explanation of current changes");
4299
+ console.log(chalk12.gray(" /commit ") + "Generate and run git commit");
3822
4300
  console.log("");
3823
- console.log(chalk11.cyan(" Build Commands:"));
3824
- console.log(chalk11.gray(" /build ") + "Generate and execute an application");
4301
+ console.log(chalk12.cyan(" Build Commands:"));
4302
+ console.log(chalk12.gray(" /build ") + "Generate and execute an application");
4303
+ console.log(chalk12.gray(" /edit ") + "AI-powered surgical file editing");
3825
4304
  console.log("");
3826
- console.log(chalk11.cyan(" Conversation:"));
3827
- console.log(chalk11.gray(" /clear ") + "Clear conversation history");
3828
- console.log(chalk11.gray(" /history ") + "Show recent conversation");
4305
+ console.log(chalk12.cyan(" Conversation:"));
4306
+ console.log(chalk12.gray(" /clear ") + "Clear conversation history");
4307
+ console.log(chalk12.gray(" /history ") + "Show recent conversation");
3829
4308
  console.log("");
3830
- console.log(chalk11.cyan(" General:"));
3831
- console.log(chalk11.gray(" /model ") + "Change AI model");
3832
- console.log(chalk11.gray(" /chat ") + "Switch to chat mode");
3833
- console.log(chalk11.gray(" /help ") + "Show this help");
3834
- console.log(chalk11.gray(" /exit ") + "Exit Agdi");
3835
- console.log(chalk11.gray("\n Or just type your coding question!\n"));
3836
- console.log(chalk11.gray('Tip: "Create a todo app" will generate & write files.\n'));
4309
+ console.log(chalk12.cyan(" General:"));
4310
+ console.log(chalk12.gray(" /model ") + "Change AI model");
4311
+ console.log(chalk12.gray(" /chat ") + "Switch to chat mode");
4312
+ console.log(chalk12.gray(" /help ") + "Show this help");
4313
+ console.log(chalk12.gray(" /exit ") + "Exit Agdi");
4314
+ console.log(chalk12.gray("\n Or just type your coding question!\n"));
4315
+ console.log(chalk12.gray('Tip: "Create a todo app" will generate & write files.\n'));
3837
4316
  }
3838
4317
  function handleError(error) {
3839
4318
  const msg = error instanceof Error ? error.message : String(error);
3840
4319
  if (msg.includes("429") || msg.includes("quota")) {
3841
- console.log(chalk11.yellow("\n\u26A0\uFE0F Quota exceeded. Run /model to switch.\n"));
4320
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Quota exceeded. Run /model to switch.\n"));
3842
4321
  } else if (msg.includes("401") || msg.includes("403")) {
3843
- console.log(chalk11.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
4322
+ console.log(chalk12.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
3844
4323
  } else {
3845
- console.log(chalk11.red("\n" + msg + "\n"));
4324
+ console.log(chalk12.red("\n" + msg + "\n"));
3846
4325
  }
3847
4326
  }
3848
4327
 
3849
4328
  // src/index.ts
3850
4329
  var BANNER = `
3851
- ${chalk12.cyan(` ___ __ _ `)}
3852
- ${chalk12.cyan(` / | ____ _____/ /(_) `)}
3853
- ${chalk12.cyan(` / /| | / __ \`/ __ // / `)}
3854
- ${chalk12.cyan(` / ___ |/ /_/ / /_/ // / `)}
3855
- ${chalk12.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
3856
- ${chalk12.cyan(` /____/ `)}
4330
+ ${chalk13.cyan(` ___ __ _ `)}
4331
+ ${chalk13.cyan(` / | ____ _____/ /(_) `)}
4332
+ ${chalk13.cyan(` / /| | / __ \`/ __ // / `)}
4333
+ ${chalk13.cyan(` / ___ |/ /_/ / /_/ // / `)}
4334
+ ${chalk13.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
4335
+ ${chalk13.cyan(` /____/ `)}
3857
4336
  `;
3858
4337
  var program = new Command();
3859
- program.name("agdi").description(chalk12.cyan("\u{1F680} AI-powered coding assistant")).version("2.2.2").configureHelp({
4338
+ program.name("agdi").description(chalk13.cyan("\u{1F680} AI-powered coding assistant")).version("2.2.2").configureHelp({
3860
4339
  // Show banner only when help is requested
3861
4340
  formatHelp: (cmd, helper) => {
3862
- return BANNER + "\n" + chalk12.gray(" The Open Source AI Architect") + "\n" + chalk12.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);
4341
+ return BANNER + "\n" + chalk13.gray(" The Open Source AI Architect") + "\n" + chalk13.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);
3863
4342
  }
3864
4343
  });
3865
4344
  program.action(async () => {
@@ -3870,7 +4349,7 @@ program.action(async () => {
3870
4349
  await startCodingMode();
3871
4350
  } catch (error) {
3872
4351
  if (error.name === "ExitPromptError") {
3873
- console.log(chalk12.gray("\n\n\u{1F44B} Goodbye!\n"));
4352
+ console.log(chalk13.gray("\n\n\u{1F44B} Goodbye!\n"));
3874
4353
  process.exit(0);
3875
4354
  }
3876
4355
  throw error;
@@ -3885,7 +4364,7 @@ program.command("auth").description("Configure API keys").option("--status", "Sh
3885
4364
  }
3886
4365
  } catch (error) {
3887
4366
  if (error.name === "ExitPromptError") {
3888
- console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
4367
+ console.log(chalk13.gray("\n\n\u{1F44B} Cancelled.\n"));
3889
4368
  process.exit(0);
3890
4369
  }
3891
4370
  throw error;
@@ -3896,7 +4375,7 @@ program.command("model").alias("models").description("Change AI model").action(a
3896
4375
  await selectModel();
3897
4376
  } catch (error) {
3898
4377
  if (error.name === "ExitPromptError") {
3899
- console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
4378
+ console.log(chalk13.gray("\n\n\u{1F44B} Cancelled.\n"));
3900
4379
  process.exit(0);
3901
4380
  }
3902
4381
  throw error;
@@ -3910,7 +4389,7 @@ program.command("chat").description("Start a chat session").action(async () => {
3910
4389
  await startChat();
3911
4390
  } catch (error) {
3912
4391
  if (error.name === "ExitPromptError") {
3913
- console.log(chalk12.gray("\n\n\u{1F44B} Goodbye!\n"));
4392
+ console.log(chalk13.gray("\n\n\u{1F44B} Goodbye!\n"));
3914
4393
  process.exit(0);
3915
4394
  }
3916
4395
  throw error;
@@ -3921,7 +4400,7 @@ program.command("run [directory]").description("Run a generated project").action
3921
4400
  await runProject(directory);
3922
4401
  } catch (error) {
3923
4402
  if (error.name === "ExitPromptError") {
3924
- console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
4403
+ console.log(chalk13.gray("\n\n\u{1F44B} Cancelled.\n"));
3925
4404
  process.exit(0);
3926
4405
  }
3927
4406
  throw error;
@@ -3934,10 +4413,10 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
3934
4413
  }
3935
4414
  const activeConfig = getActiveProvider();
3936
4415
  if (!activeConfig) {
3937
- console.log(chalk12.red("\u274C No API key configured. Run: agdi auth"));
4416
+ console.log(chalk13.red("\u274C No API key configured. Run: agdi auth"));
3938
4417
  return;
3939
4418
  }
3940
- const spinner = ora4("Generating application...").start();
4419
+ const spinner = ora5("Generating application...").start();
3941
4420
  try {
3942
4421
  const llm = createLLMProvider(activeConfig.provider, {
3943
4422
  apiKey: activeConfig.apiKey,
@@ -3946,30 +4425,30 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
3946
4425
  const pm = new ProjectManager();
3947
4426
  pm.create(options.output.replace("./", ""), prompt);
3948
4427
  const { plan, files } = await generateApp(prompt, llm, (step, file) => {
3949
- spinner.text = file ? `${step} ${chalk12.gray(file)}` : step;
4428
+ spinner.text = file ? `${step} ${chalk13.gray(file)}` : step;
3950
4429
  });
3951
4430
  pm.updateFiles(files);
3952
4431
  pm.updateDependencies(plan.dependencies);
3953
4432
  await writeProject(pm.get(), options.output);
3954
- spinner.succeed(chalk12.green("App generated!"));
3955
- console.log(chalk12.gray(`
3956
- \u{1F4C1} Created ${files.length} files in ${chalk12.cyan(options.output)}`));
3957
- console.log(chalk12.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
4433
+ spinner.succeed(chalk13.green("App generated!"));
4434
+ console.log(chalk13.gray(`
4435
+ \u{1F4C1} Created ${files.length} files in ${chalk13.cyan(options.output)}`));
4436
+ console.log(chalk13.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
3958
4437
  } catch (error) {
3959
4438
  spinner.fail("Generation failed");
3960
4439
  const msg = error instanceof Error ? error.message : String(error);
3961
4440
  if (msg.includes("429") || msg.includes("quota")) {
3962
- console.log(chalk12.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
4441
+ console.log(chalk13.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
3963
4442
  } else if (msg.includes("401") || msg.includes("403")) {
3964
- console.log(chalk12.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
4443
+ console.log(chalk13.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
3965
4444
  } else {
3966
- console.error(chalk12.red("\n" + msg + "\n"));
4445
+ console.error(chalk13.red("\n" + msg + "\n"));
3967
4446
  }
3968
4447
  process.exit(1);
3969
4448
  }
3970
4449
  } catch (error) {
3971
4450
  if (error.name === "ExitPromptError") {
3972
- console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
4451
+ console.log(chalk13.gray("\n\n\u{1F44B} Cancelled.\n"));
3973
4452
  process.exit(0);
3974
4453
  }
3975
4454
  throw error;
@@ -3978,11 +4457,11 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
3978
4457
  program.command("config").description("Show configuration").action(async () => {
3979
4458
  const config = loadConfig();
3980
4459
  const active = getActiveProvider();
3981
- console.log(chalk12.cyan.bold("\n\u2699\uFE0F Configuration\n"));
3982
- console.log(chalk12.gray(" Provider: ") + chalk12.cyan(config.defaultProvider || "not set"));
3983
- console.log(chalk12.gray(" Model: ") + chalk12.cyan(config.defaultModel || "not set"));
3984
- console.log(chalk12.gray(" Config: ") + chalk12.gray("~/.agdi/config.json"));
3985
- console.log(chalk12.cyan.bold("\n\u{1F510} API Keys\n"));
4460
+ console.log(chalk13.cyan.bold("\n\u2699\uFE0F Configuration\n"));
4461
+ console.log(chalk13.gray(" Provider: ") + chalk13.cyan(config.defaultProvider || "not set"));
4462
+ console.log(chalk13.gray(" Model: ") + chalk13.cyan(config.defaultModel || "not set"));
4463
+ console.log(chalk13.gray(" Config: ") + chalk13.gray("~/.agdi/config.json"));
4464
+ console.log(chalk13.cyan.bold("\n\u{1F510} API Keys\n"));
3986
4465
  const keys = [
3987
4466
  ["Gemini", config.geminiApiKey],
3988
4467
  ["OpenRouter", config.openrouterApiKey],
@@ -3991,7 +4470,7 @@ program.command("config").description("Show configuration").action(async () => {
3991
4470
  ["DeepSeek", config.deepseekApiKey]
3992
4471
  ];
3993
4472
  for (const [name, key] of keys) {
3994
- const status = key ? chalk12.green("\u2713") : chalk12.gray("\u2717");
4473
+ const status = key ? chalk13.green("\u2713") : chalk13.gray("\u2717");
3995
4474
  console.log(` ${status} ${name}`);
3996
4475
  }
3997
4476
  console.log("");