agdi 2.7.1 → 2.8.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 (2) hide show
  1. package/dist/index.js +570 -336
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
 
8
8
  // src/index.ts
9
9
  import { Command } from "commander";
10
- import chalk14 from "chalk";
10
+ import chalk15 from "chalk";
11
11
  import ora5 from "ora";
12
12
 
13
13
  // src/core/llm/index.ts
@@ -287,6 +287,167 @@ var ProjectManager = class {
287
287
  // src/core/io/index.ts
288
288
  import JSZip from "jszip";
289
289
 
290
+ // src/utils/ui.ts
291
+ import chalk from "chalk";
292
+ import gradient from "gradient-string";
293
+ import boxen from "boxen";
294
+ import figlet from "figlet";
295
+ import ora from "ora";
296
+ var THEME = {
297
+ cyan: "#06b6d4",
298
+ // Cyan-500
299
+ purple: "#8b5cf6",
300
+ // Violet-500
301
+ red: "#ef4444",
302
+ // Red-500
303
+ yellow: "#eab308",
304
+ // Yellow-500
305
+ gray: "#71717a",
306
+ // Zinc-500
307
+ dim: "#52525b"
308
+ // Zinc-600
309
+ };
310
+ var brandGradient = gradient([THEME.cyan, THEME.purple]);
311
+ var errorGradient = gradient([THEME.red, "#b91c1c"]);
312
+ var goldGradient = gradient([THEME.yellow, "#fbbf24"]);
313
+ async function renderBanner(version = "v2.6.0") {
314
+ console.clear();
315
+ const text = await new Promise((resolve5) => {
316
+ figlet("AGDI", { font: "Slant" }, (err, data) => {
317
+ resolve5(data || "AGDI");
318
+ });
319
+ });
320
+ console.log(brandGradient.multiline(text));
321
+ console.log(chalk.hex(THEME.dim)(` ${version} [ARCHITECT ONLINE]
322
+ `));
323
+ }
324
+ function renderBox(title, content, style = "info") {
325
+ let borderColor = THEME.cyan;
326
+ let titleColor = chalk.cyan;
327
+ if (style === "success") {
328
+ borderColor = THEME.cyan;
329
+ } else if (style === "warning") {
330
+ borderColor = THEME.yellow;
331
+ titleColor = chalk.yellow;
332
+ } else if (style === "error") {
333
+ borderColor = THEME.red;
334
+ titleColor = chalk.red;
335
+ }
336
+ const box = boxen(content, {
337
+ title: titleColor.bold(title),
338
+ padding: 1,
339
+ margin: 1,
340
+ borderStyle: "round",
341
+ borderColor,
342
+ dimBorder: false,
343
+ float: "left"
344
+ });
345
+ console.log(box);
346
+ }
347
+ function renderAlert(title, message) {
348
+ console.log("");
349
+ const box = boxen(chalk.white(message), {
350
+ title: chalk.red.bold(`\u{1F6E1}\uFE0F ${title.toUpperCase()} `),
351
+ padding: 1,
352
+ borderStyle: "double",
353
+ borderColor: "red",
354
+ textAlignment: "center"
355
+ });
356
+ console.log(box);
357
+ console.log("");
358
+ }
359
+ function printUserMessage(message) {
360
+ console.log("");
361
+ console.log(chalk.cyan.bold("\u{1F464} YOU \u203A ") + chalk.white(message));
362
+ console.log("");
363
+ }
364
+ function printAIMessage(message) {
365
+ console.log("");
366
+ console.log(brandGradient.multiline("\u26A1 AGDI \u203A "));
367
+ console.log(message.trim());
368
+ console.log("");
369
+ }
370
+ function createSpinner(text) {
371
+ return ora({
372
+ text: chalk.hex(THEME.gray)(text),
373
+ color: "cyan",
374
+ spinner: "dots",
375
+ discardStdin: false
376
+ // Important for allowing interruption if needed
377
+ });
378
+ }
379
+ function printIter() {
380
+ console.log(chalk.hex(THEME.dim)("\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
381
+ }
382
+ var activeReadlineInterface = null;
383
+ function registerActivePrompt(rl) {
384
+ activeReadlineInterface = rl;
385
+ }
386
+ var flags = {
387
+ yes: false,
388
+ headless: false,
389
+ minimal: false,
390
+ dryRun: false
391
+ };
392
+ function setFlags(newFlags) {
393
+ Object.assign(flags, newFlags);
394
+ }
395
+ function safeExit(code = 0) {
396
+ if (activeReadlineInterface) {
397
+ try {
398
+ activeReadlineInterface.close?.();
399
+ activeReadlineInterface.destroy?.();
400
+ } catch {
401
+ }
402
+ activeReadlineInterface = null;
403
+ }
404
+ setImmediate(() => {
405
+ process.exit(code);
406
+ });
407
+ throw new Error("Process exiting");
408
+ }
409
+ async function smartConfirm(message, defaultValue = false) {
410
+ if (flags.yes || flags.headless || process.env.CI === "true" || process.env.CI === "1") {
411
+ console.log(chalk.gray(` [Auto-approved: ${message}]`));
412
+ return true;
413
+ }
414
+ if (!process.stdout.isTTY) {
415
+ console.warn(chalk.yellow("\u26A0\uFE0F Non-interactive session detected. Use --yes to approve actions."));
416
+ return false;
417
+ }
418
+ const { confirm: confirm4 } = await import("@inquirer/prompts");
419
+ return confirm4({ message, default: defaultValue });
420
+ }
421
+ async function smartSelect(message, choices, defaultValue) {
422
+ if (!process.stdout.isTTY || flags.headless) {
423
+ const result = defaultValue || choices[0]?.value;
424
+ if (result) {
425
+ console.log(chalk.gray(` [Auto-selected: ${result}]`));
426
+ }
427
+ return result || null;
428
+ }
429
+ const { select: select5 } = await import("@inquirer/prompts");
430
+ return select5({ message, choices });
431
+ }
432
+ var ui = {
433
+ renderBanner,
434
+ renderBox,
435
+ renderAlert,
436
+ printUserMessage,
437
+ printAIMessage,
438
+ createSpinner,
439
+ printIter,
440
+ brandGradient,
441
+ THEME,
442
+ // Safety & Automation
443
+ safeExit,
444
+ smartConfirm,
445
+ smartSelect,
446
+ setFlags,
447
+ flags,
448
+ registerActivePrompt
449
+ };
450
+
290
451
  // src/core/architect/index.ts
291
452
  var SYSTEM_PROMPT = `You are Agdi Architect, an expert software architect AI.
292
453
  Your job is to generate complete, production-ready React applications.
@@ -294,7 +455,7 @@ Always use TypeScript, Tailwind CSS, and Vite.
294
455
  Generate all necessary files including package.json, tsconfig.json, vite.config.ts.
295
456
  Make the UI beautiful with modern design patterns.`;
296
457
  async function generatePlan(prompt, llm) {
297
- const planPrompt = `Create a detailed plan for: ${prompt}
458
+ let planPrompt = `Create a detailed plan for: ${prompt}
298
459
 
299
460
  Return a JSON object with:
300
461
  {
@@ -306,6 +467,25 @@ Return a JSON object with:
306
467
  }
307
468
 
308
469
  Return ONLY valid JSON, no markdown.`;
470
+ if (ui.flags.minimal) {
471
+ planPrompt = `Create a minimal plan for: ${prompt}
472
+
473
+ CRITICAL: Minimal mode matches user request exactly.
474
+ - If user asks for "hello.ts", generate ONLY "hello.ts".
475
+ - Do NOT scaffold a full React app unless explicitly asked.
476
+ - Do NOT add boilerplate headers/footers.
477
+
478
+ Return a JSON object with:
479
+ {
480
+ "name": "minimal-project",
481
+ "description": "Minimal generation",
482
+ "files": [{"path": "requested-file.ts", "description": "Requested logic"}],
483
+ "dependencies": [],
484
+ "architecture": "Single file script"
485
+ }
486
+
487
+ Return ONLY valid JSON.`;
488
+ }
309
489
  const response = await llm.generate(planPrompt, SYSTEM_PROMPT);
310
490
  try {
311
491
  const jsonMatch = response.text.match(/\{[\s\S]*\}/);
@@ -356,50 +536,52 @@ async function generateApp(prompt, llm, onProgress) {
356
536
  const file = await generateFile(fileSpec.path, fileSpec.description, plan, llm);
357
537
  files.push(file);
358
538
  }
359
- onProgress?.("Creating package.json...", "package.json");
360
- files.push({
361
- path: "package.json",
362
- content: JSON.stringify({
363
- name: plan.name,
364
- version: "0.1.0",
365
- type: "module",
366
- scripts: {
367
- dev: "vite",
368
- build: "tsc -b && vite build",
369
- preview: "vite preview"
370
- },
371
- dependencies: {
372
- "react": "^18.3.1",
373
- "react-dom": "^18.3.1",
374
- ...plan.dependencies.reduce((acc, dep) => {
375
- if (dep !== "react" && dep !== "react-dom") {
376
- acc[dep] = "latest";
377
- }
378
- return acc;
379
- }, {})
380
- },
381
- devDependencies: {
382
- "@types/react": "^18.3.0",
383
- "@types/react-dom": "^18.3.0",
384
- "@vitejs/plugin-react": "^4.3.0",
385
- "autoprefixer": "^10.4.20",
386
- "postcss": "^8.4.45",
387
- "tailwindcss": "^3.4.10",
388
- "typescript": "~5.5.0",
389
- "vite": "^5.4.0"
390
- }
391
- }, null, 2)
392
- });
539
+ if (!ui.flags.minimal) {
540
+ onProgress?.("Creating package.json...", "package.json");
541
+ files.push({
542
+ path: "package.json",
543
+ content: JSON.stringify({
544
+ name: plan.name,
545
+ version: "0.1.0",
546
+ type: "module",
547
+ scripts: {
548
+ dev: "vite",
549
+ build: "tsc -b && vite build",
550
+ preview: "vite preview"
551
+ },
552
+ dependencies: {
553
+ "react": "^18.3.1",
554
+ "react-dom": "^18.3.1",
555
+ ...plan.dependencies.reduce((acc, dep) => {
556
+ if (dep !== "react" && dep !== "react-dom") {
557
+ acc[dep] = "latest";
558
+ }
559
+ return acc;
560
+ }, {})
561
+ },
562
+ devDependencies: {
563
+ "@types/react": "^18.3.0",
564
+ "@types/react-dom": "^18.3.0",
565
+ "@vitejs/plugin-react": "^4.3.0",
566
+ "autoprefixer": "^10.4.20",
567
+ "postcss": "^8.4.45",
568
+ "tailwindcss": "^3.4.10",
569
+ "typescript": "~5.5.0",
570
+ "vite": "^5.4.0"
571
+ }
572
+ }, null, 2)
573
+ });
574
+ }
393
575
  return { plan, files };
394
576
  }
395
577
 
396
578
  // src/utils/fs.ts
397
579
  import fs from "fs-extra";
398
580
  import path from "path";
399
- import chalk2 from "chalk";
581
+ import chalk3 from "chalk";
400
582
 
401
583
  // src/security/code-firewall.ts
402
- import chalk from "chalk";
584
+ import chalk2 from "chalk";
403
585
  var MALICIOUS_PATTERNS = [
404
586
  // ==================== HARDCODED SECRETS ====================
405
587
  {
@@ -648,35 +830,35 @@ function shouldBlockCode(result) {
648
830
  }
649
831
  function displayScanResults(result, filename) {
650
832
  if (result.safe) {
651
- console.log(chalk.green("\u2705 No malicious patterns detected"));
833
+ console.log(chalk2.green("\u2705 No malicious patterns detected"));
652
834
  return;
653
835
  }
654
- console.log(chalk.red.bold("\n\u{1F6A8} SECURITY SCAN FAILED"));
836
+ console.log(chalk2.red.bold("\n\u{1F6A8} SECURITY SCAN FAILED"));
655
837
  if (filename) {
656
- console.log(chalk.gray(`File: ${filename}`));
838
+ console.log(chalk2.gray(`File: ${filename}`));
657
839
  }
658
840
  console.log("");
659
841
  const criticals = result.matches.filter((m) => m.severity === "critical");
660
842
  const highs = result.matches.filter((m) => m.severity === "high");
661
843
  const others = result.matches.filter((m) => m.severity !== "critical" && m.severity !== "high");
662
844
  if (criticals.length > 0) {
663
- console.log(chalk.red("\u{1F534} CRITICAL:"));
845
+ console.log(chalk2.red("\u{1F534} CRITICAL:"));
664
846
  for (const m of criticals) {
665
- console.log(chalk.red(` Line ${m.line}: ${m.description}`));
666
- console.log(chalk.gray(` Found: ${m.match}`));
847
+ console.log(chalk2.red(` Line ${m.line}: ${m.description}`));
848
+ console.log(chalk2.gray(` Found: ${m.match}`));
667
849
  }
668
850
  }
669
851
  if (highs.length > 0) {
670
- console.log(chalk.yellow("\n\u{1F7E0} HIGH:"));
852
+ console.log(chalk2.yellow("\n\u{1F7E0} HIGH:"));
671
853
  for (const m of highs) {
672
- console.log(chalk.yellow(` Line ${m.line}: ${m.description}`));
673
- console.log(chalk.gray(` Found: ${m.match}`));
854
+ console.log(chalk2.yellow(` Line ${m.line}: ${m.description}`));
855
+ console.log(chalk2.gray(` Found: ${m.match}`));
674
856
  }
675
857
  }
676
858
  if (others.length > 0) {
677
- console.log(chalk.cyan("\n\u{1F7E1} WARNINGS:"));
859
+ console.log(chalk2.cyan("\n\u{1F7E1} WARNINGS:"));
678
860
  for (const m of others) {
679
- console.log(chalk.cyan(` Line ${m.line}: ${m.description}`));
861
+ console.log(chalk2.cyan(` Line ${m.line}: ${m.description}`));
680
862
  }
681
863
  }
682
864
  console.log("");
@@ -686,11 +868,11 @@ function validateCodeBeforeWrite(code, filename) {
686
868
  if (!result.safe) {
687
869
  displayScanResults(result, filename);
688
870
  if (shouldBlockCode(result)) {
689
- console.log(chalk.red.bold("\u{1F6A8} BLOCKED: Code contains critical security issues"));
690
- console.log(chalk.gray("The file will NOT be written to disk.\n"));
871
+ console.log(chalk2.red.bold("\u{1F6A8} BLOCKED: Code contains critical security issues"));
872
+ console.log(chalk2.gray("The file will NOT be written to disk.\n"));
691
873
  return false;
692
874
  } else {
693
- console.log(chalk.yellow("\u26A0\uFE0F Warning: Code contains potential issues but will be written.\n"));
875
+ console.log(chalk2.yellow("\u26A0\uFE0F Warning: Code contains potential issues but will be written.\n"));
694
876
  }
695
877
  }
696
878
  return true;
@@ -706,7 +888,7 @@ async function writeProject(project, outputDir) {
706
888
  const isSafe = validateCodeBeforeWrite(file.content, file.path);
707
889
  if (!isSafe) {
708
890
  blockedCount++;
709
- console.log(chalk2.red(`\u26D4 BLOCKED: ${file.path}`));
891
+ console.log(chalk3.red(`\u26D4 BLOCKED: ${file.path}`));
710
892
  continue;
711
893
  }
712
894
  await fs.ensureDir(path.dirname(filePath));
@@ -715,16 +897,16 @@ async function writeProject(project, outputDir) {
715
897
  }
716
898
  console.log("");
717
899
  if (blockedCount > 0) {
718
- console.log(chalk2.yellow(`\u26A0\uFE0F ${blockedCount} file(s) blocked by security scan`));
900
+ console.log(chalk3.yellow(`\u26A0\uFE0F ${blockedCount} file(s) blocked by security scan`));
719
901
  }
720
- console.log(chalk2.green(`\u2705 ${writtenCount} file(s) written successfully`));
902
+ console.log(chalk3.green(`\u2705 ${writtenCount} file(s) written successfully`));
721
903
  }
722
904
 
723
905
  // src/utils/config.ts
724
906
  import fs2 from "fs-extra";
725
907
  import path2 from "path";
726
908
  import os from "os";
727
- import chalk3 from "chalk";
909
+ import chalk4 from "chalk";
728
910
  var CONFIG_DIR = path2.join(os.homedir(), ".agdi");
729
911
  var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
730
912
  var SECURE_FILE_MODE = 384;
@@ -741,11 +923,11 @@ function checkPermissions() {
741
923
  }
742
924
  const isWorldReadable = (mode & 36) !== 0;
743
925
  if (isWorldReadable) {
744
- console.log(chalk3.yellow("\n\u26A0\uFE0F SECURITY WARNING"));
745
- console.log(chalk3.gray("Your config file is readable by other users!"));
746
- console.log(chalk3.gray(`File: ${CONFIG_FILE}`));
747
- console.log(chalk3.gray("Run the following to fix:"));
748
- console.log(chalk3.cyan(` chmod 600 "${CONFIG_FILE}"
926
+ console.log(chalk4.yellow("\n\u26A0\uFE0F SECURITY WARNING"));
927
+ console.log(chalk4.gray("Your config file is readable by other users!"));
928
+ console.log(chalk4.gray(`File: ${CONFIG_FILE}`));
929
+ console.log(chalk4.gray("Run the following to fix:"));
930
+ console.log(chalk4.cyan(` chmod 600 "${CONFIG_FILE}"
749
931
  `));
750
932
  return false;
751
933
  }
@@ -781,16 +963,16 @@ function saveConfig(config) {
781
963
  fs2.writeJsonSync(CONFIG_FILE, config, { spaces: 2 });
782
964
  setSecurePermissions();
783
965
  } catch (error) {
784
- console.error(chalk3.red("Failed to save config:"), error);
966
+ console.error(chalk4.red("Failed to save config:"), error);
785
967
  }
786
968
  }
787
969
 
788
970
  // src/commands/auth.ts
789
971
  import { input, select, password } from "@inquirer/prompts";
790
- import chalk4 from "chalk";
972
+ import chalk5 from "chalk";
791
973
  async function login() {
792
- console.log(chalk4.cyan.bold("\n\u{1F510} Agdi Authentication\n"));
793
- console.log(chalk4.gray("Configure your API key to use Agdi CLI.\n"));
974
+ console.log(chalk5.cyan.bold("\n\u{1F510} Agdi Authentication\n"));
975
+ console.log(chalk5.gray("Configure your API key to use Agdi CLI.\n"));
794
976
  try {
795
977
  const config = loadConfig();
796
978
  const provider = await select({
@@ -812,8 +994,8 @@ async function login() {
812
994
  config.ollamaUrl = ollamaUrl;
813
995
  config.defaultProvider = "ollama";
814
996
  saveConfig(config);
815
- console.log(chalk4.green("\n\u2705 Ollama configured"));
816
- console.log(chalk4.gray(`Server: ${ollamaUrl}
997
+ console.log(chalk5.green("\n\u2705 Ollama configured"));
998
+ console.log(chalk5.gray(`Server: ${ollamaUrl}
817
999
  `));
818
1000
  return;
819
1001
  }
@@ -840,12 +1022,12 @@ async function login() {
840
1022
  }
841
1023
  config.defaultProvider = provider;
842
1024
  saveConfig(config);
843
- console.log(chalk4.green(`
1025
+ console.log(chalk5.green(`
844
1026
  \u2705 ${provider} API key saved securely`));
845
- console.log(chalk4.gray("Keys stored in ~/.agdi/config.json\n"));
1027
+ console.log(chalk5.gray("Keys stored in ~/.agdi/config.json\n"));
846
1028
  } catch (error) {
847
1029
  if (error.name === "ExitPromptError") {
848
- console.log(chalk4.gray("\n\n\u{1F44B} Cancelled.\n"));
1030
+ console.log(chalk5.gray("\n\n\u{1F44B} Cancelled.\n"));
849
1031
  process.exit(0);
850
1032
  }
851
1033
  throw error;
@@ -853,7 +1035,7 @@ async function login() {
853
1035
  }
854
1036
  async function showStatus() {
855
1037
  const config = loadConfig();
856
- console.log(chalk4.cyan.bold("\n\u{1F4CA} Authentication Status\n"));
1038
+ console.log(chalk5.cyan.bold("\n\u{1F4CA} Authentication Status\n"));
857
1039
  const providers = [
858
1040
  { name: "Gemini", key: config.geminiApiKey },
859
1041
  { name: "OpenRouter", key: config.openrouterApiKey },
@@ -862,19 +1044,19 @@ async function showStatus() {
862
1044
  { name: "DeepSeek", key: config.deepseekApiKey }
863
1045
  ];
864
1046
  for (const p of providers) {
865
- const status = p.key ? chalk4.green("\u2713 Configured") : chalk4.gray("\u2717 Not set");
1047
+ const status = p.key ? chalk5.green("\u2713 Configured") : chalk5.gray("\u2717 Not set");
866
1048
  console.log(` ${p.name.padEnd(12)} ${status}`);
867
1049
  }
868
- console.log(chalk4.cyan(`
1050
+ console.log(chalk5.cyan(`
869
1051
  Default: ${config.defaultProvider || "gemini"}
870
1052
  `));
871
- console.log(chalk4.gray('\u{1F4A1} Tip: Use "agdi auth" to reconfigure\n'));
1053
+ console.log(chalk5.gray('\u{1F4A1} Tip: Use "agdi auth" to reconfigure\n'));
872
1054
  }
873
1055
 
874
1056
  // src/commands/chat.ts
875
1057
  import { input as input2 } from "@inquirer/prompts";
876
- import chalk5 from "chalk";
877
- import ora from "ora";
1058
+ import chalk6 from "chalk";
1059
+ import ora2 from "ora";
878
1060
  var SYSTEM_PROMPT2 = `You are Agdi, an elite full-stack software architect and senior engineer with deep expertise across the entire web development stack.
879
1061
 
880
1062
  # Core Expertise
@@ -1028,8 +1210,8 @@ When asked to build something:
1028
1210
 
1029
1211
  You build software that works, scales, and follows industry best practices. Every solution is complete, tested, and ready for production deployment.`;
1030
1212
  async function startChat() {
1031
- console.log(chalk5.cyan.bold("\n\u{1F4AC} Agdi Interactive Mode\n"));
1032
- console.log(chalk5.gray('Type your coding requests. Type "exit" to quit.\n'));
1213
+ console.log(chalk6.cyan.bold("\n\u{1F4AC} Agdi Interactive Mode\n"));
1214
+ console.log(chalk6.gray('Type your coding requests. Type "exit" to quit.\n'));
1033
1215
  const config = loadConfig();
1034
1216
  let provider;
1035
1217
  let apiKey = "";
@@ -1039,33 +1221,33 @@ async function startChat() {
1039
1221
  } else if (config.openrouterApiKey) {
1040
1222
  provider = "openrouter";
1041
1223
  apiKey = config.openrouterApiKey;
1042
- console.log(chalk5.gray("Using OpenRouter (100+ models available)\n"));
1224
+ console.log(chalk6.gray("Using OpenRouter (100+ models available)\n"));
1043
1225
  } else if (config.defaultProvider === "puter") {
1044
- console.log(chalk5.yellow("\u26A0\uFE0F Puter.com FREE mode requires browser authentication."));
1045
- console.log(chalk5.gray("For CLI usage, please configure an API key:\n"));
1046
- console.log(chalk5.cyan(" agdi auth"));
1047
- console.log(chalk5.gray("\nSupported providers: Gemini, OpenRouter, OpenAI, Anthropic, DeepSeek\n"));
1226
+ console.log(chalk6.yellow("\u26A0\uFE0F Puter.com FREE mode requires browser authentication."));
1227
+ console.log(chalk6.gray("For CLI usage, please configure an API key:\n"));
1228
+ console.log(chalk6.cyan(" agdi auth"));
1229
+ console.log(chalk6.gray("\nSupported providers: Gemini, OpenRouter, OpenAI, Anthropic, DeepSeek\n"));
1048
1230
  return;
1049
1231
  } else {
1050
- console.log(chalk5.yellow("\u26A0\uFE0F No API key configured."));
1051
- console.log(chalk5.gray('Run "agdi auth" to configure your API key.\n'));
1232
+ console.log(chalk6.yellow("\u26A0\uFE0F No API key configured."));
1233
+ console.log(chalk6.gray('Run "agdi auth" to configure your API key.\n'));
1052
1234
  return;
1053
1235
  }
1054
- console.log(chalk5.gray(`Using provider: ${chalk5.cyan(provider)}`));
1055
- console.log(chalk5.gray("\u2500".repeat(50) + "\n"));
1236
+ console.log(chalk6.gray(`Using provider: ${chalk6.cyan(provider)}`));
1237
+ console.log(chalk6.gray("\u2500".repeat(50) + "\n"));
1056
1238
  const pm = new ProjectManager();
1057
1239
  while (true) {
1058
1240
  const userInput = await input2({
1059
- message: chalk5.cyan("You:")
1241
+ message: chalk6.cyan("You:")
1060
1242
  });
1061
1243
  if (userInput.toLowerCase() === "exit" || userInput.toLowerCase() === "quit") {
1062
- console.log(chalk5.gray("\n\u{1F44B} Goodbye!\n"));
1244
+ console.log(chalk6.gray("\n\u{1F44B} Goodbye!\n"));
1063
1245
  break;
1064
1246
  }
1065
1247
  if (!userInput.trim()) {
1066
1248
  continue;
1067
1249
  }
1068
- const spinner = ora("Thinking...").start();
1250
+ const spinner = ora2("Thinking...").start();
1069
1251
  try {
1070
1252
  const llm = createLLMProvider(provider, { apiKey, model: config.defaultModel });
1071
1253
  if (userInput.toLowerCase().includes("create") || userInput.toLowerCase().includes("build") || userInput.toLowerCase().includes("make")) {
@@ -1076,9 +1258,9 @@ async function startChat() {
1076
1258
  });
1077
1259
  pm.updateFiles(files);
1078
1260
  spinner.succeed("Application generated!");
1079
- console.log(chalk5.green("\n\u{1F4C1} Files created:"));
1261
+ console.log(chalk6.green("\n\u{1F4C1} Files created:"));
1080
1262
  for (const file of files) {
1081
- console.log(chalk5.gray(` - ${file.path}`));
1263
+ console.log(chalk6.gray(` - ${file.path}`));
1082
1264
  }
1083
1265
  const shouldWrite = await input2({
1084
1266
  message: "Write files to disk? (y/n):",
@@ -1090,38 +1272,38 @@ async function startChat() {
1090
1272
  default: "./generated-app"
1091
1273
  });
1092
1274
  await writeProject(pm.get(), dir);
1093
- console.log(chalk5.green(`
1275
+ console.log(chalk6.green(`
1094
1276
  \u2705 Files written to ${dir}
1095
1277
  `));
1096
1278
  }
1097
1279
  } else {
1098
1280
  const response = await llm.generate(userInput, SYSTEM_PROMPT2);
1099
1281
  spinner.stop();
1100
- console.log(chalk5.cyan("\nAgdi: ") + response.text + "\n");
1282
+ console.log(chalk6.cyan("\nAgdi: ") + response.text + "\n");
1101
1283
  }
1102
1284
  } catch (error) {
1103
1285
  if (error.name === "ExitPromptError") {
1104
- console.log(chalk5.gray("\n\n\u{1F44B} Goodbye!\n"));
1286
+ console.log(chalk6.gray("\n\n\u{1F44B} Goodbye!\n"));
1105
1287
  process.exit(0);
1106
1288
  }
1107
1289
  spinner.fail("Error");
1108
1290
  const errorMessage = error instanceof Error ? error.message : String(error);
1109
1291
  if (errorMessage.includes("429") || errorMessage.includes("quota") || errorMessage.includes("Resource exhausted") || errorMessage.includes("ResourceExhausted")) {
1110
- console.log(chalk5.yellow("\n\u26A0\uFE0F API quota exceeded!"));
1111
- console.log(chalk5.gray("Your API key has run out of credits."));
1112
- console.log(chalk5.gray("Try: Use a different API key or wait for quota reset.\n"));
1292
+ console.log(chalk6.yellow("\n\u26A0\uFE0F API quota exceeded!"));
1293
+ console.log(chalk6.gray("Your API key has run out of credits."));
1294
+ console.log(chalk6.gray("Try: Use a different API key or wait for quota reset.\n"));
1113
1295
  } else if (errorMessage.includes("401") || errorMessage.includes("Unauthorized") || errorMessage.includes("Invalid API key")) {
1114
- console.log(chalk5.red("\n\u{1F511} Invalid API key"));
1115
- console.log(chalk5.gray("Please reconfigure your API key:"));
1116
- console.log(chalk5.cyan(" agdi auth\n"));
1296
+ console.log(chalk6.red("\n\u{1F511} Invalid API key"));
1297
+ console.log(chalk6.gray("Please reconfigure your API key:"));
1298
+ console.log(chalk6.cyan(" agdi auth\n"));
1117
1299
  } else if (errorMessage.includes("403") || errorMessage.includes("Forbidden")) {
1118
- console.log(chalk5.red("\n\u{1F6AB} Access denied"));
1119
- console.log(chalk5.gray("Your API key doesn't have permission for this operation.\n"));
1300
+ console.log(chalk6.red("\n\u{1F6AB} Access denied"));
1301
+ console.log(chalk6.gray("Your API key doesn't have permission for this operation.\n"));
1120
1302
  } else if (errorMessage.includes("network") || errorMessage.includes("fetch") || errorMessage.includes("ENOTFOUND")) {
1121
- console.log(chalk5.red("\n\u{1F310} Network error"));
1122
- console.log(chalk5.gray("Please check your internet connection.\n"));
1303
+ console.log(chalk6.red("\n\u{1F310} Network error"));
1304
+ console.log(chalk6.gray("Please check your internet connection.\n"));
1123
1305
  } else {
1124
- console.log(chalk5.red("\n" + errorMessage + "\n"));
1306
+ console.log(chalk6.red("\n" + errorMessage + "\n"));
1125
1307
  }
1126
1308
  }
1127
1309
  }
@@ -1129,23 +1311,23 @@ async function startChat() {
1129
1311
 
1130
1312
  // src/commands/run.ts
1131
1313
  import { spawn } from "child_process";
1132
- import chalk6 from "chalk";
1314
+ import chalk7 from "chalk";
1133
1315
  import fs3 from "fs-extra";
1134
1316
  import path3 from "path";
1135
- import ora2 from "ora";
1317
+ import ora3 from "ora";
1136
1318
  async function runProject(targetDir) {
1137
1319
  const dir = targetDir || process.cwd();
1138
1320
  const absoluteDir = path3.resolve(dir);
1139
- console.log(chalk6.cyan.bold("\n\u{1F680} Agdi Run\n"));
1321
+ console.log(chalk7.cyan.bold("\n\u{1F680} Agdi Run\n"));
1140
1322
  if (!fs3.existsSync(absoluteDir)) {
1141
- console.log(chalk6.red(`\u274C Directory not found: ${absoluteDir}`));
1142
- console.log(chalk6.gray("Create a project first with: agdi init"));
1323
+ console.log(chalk7.red(`\u274C Directory not found: ${absoluteDir}`));
1324
+ console.log(chalk7.gray("Create a project first with: agdi init"));
1143
1325
  return;
1144
1326
  }
1145
1327
  const packageJsonPath = path3.join(absoluteDir, "package.json");
1146
1328
  if (!fs3.existsSync(packageJsonPath)) {
1147
- console.log(chalk6.red(`\u274C No package.json found in: ${absoluteDir}`));
1148
- console.log(chalk6.gray("This doesn't appear to be a Node.js project."));
1329
+ console.log(chalk7.red(`\u274C No package.json found in: ${absoluteDir}`));
1330
+ console.log(chalk7.gray("This doesn't appear to be a Node.js project."));
1149
1331
  return;
1150
1332
  }
1151
1333
  const packageJson = fs3.readJsonSync(packageJsonPath);
@@ -1154,14 +1336,14 @@ async function runProject(targetDir) {
1154
1336
  if (!scripts.dev && scripts.start) {
1155
1337
  runScript = "start";
1156
1338
  } else if (!scripts.dev && !scripts.start) {
1157
- console.log(chalk6.red('\u274C No "dev" or "start" script found in package.json'));
1158
- console.log(chalk6.gray('Add a script like: "dev": "vite" or "start": "node index.js"'));
1339
+ console.log(chalk7.red('\u274C No "dev" or "start" script found in package.json'));
1340
+ console.log(chalk7.gray('Add a script like: "dev": "vite" or "start": "node index.js"'));
1159
1341
  return;
1160
1342
  }
1161
1343
  const nodeModulesPath = path3.join(absoluteDir, "node_modules");
1162
1344
  if (!fs3.existsSync(nodeModulesPath)) {
1163
- console.log(chalk6.yellow("\u{1F4E6} Installing dependencies...\n"));
1164
- const installSpinner = ora2("Running npm install...").start();
1345
+ console.log(chalk7.yellow("\u{1F4E6} Installing dependencies...\n"));
1346
+ const installSpinner = ora3("Running npm install...").start();
1165
1347
  await new Promise((resolve5, reject) => {
1166
1348
  const install = spawn("npm", ["install"], {
1167
1349
  cwd: absoluteDir,
@@ -1181,11 +1363,11 @@ async function runProject(targetDir) {
1181
1363
  });
1182
1364
  console.log("");
1183
1365
  }
1184
- console.log(chalk6.green(`\u25B6 Running: npm run ${runScript}`));
1185
- console.log(chalk6.gray(` Directory: ${absoluteDir}
1366
+ console.log(chalk7.green(`\u25B6 Running: npm run ${runScript}`));
1367
+ console.log(chalk7.gray(` Directory: ${absoluteDir}
1186
1368
  `));
1187
- console.log(chalk6.gray("\u2500".repeat(50)));
1188
- console.log(chalk6.gray("Press Ctrl+C to stop\n"));
1369
+ console.log(chalk7.gray("\u2500".repeat(50)));
1370
+ console.log(chalk7.gray("Press Ctrl+C to stop\n"));
1189
1371
  const child = spawn("npm", ["run", runScript], {
1190
1372
  cwd: absoluteDir,
1191
1373
  stdio: "inherit",
@@ -1193,12 +1375,12 @@ async function runProject(targetDir) {
1193
1375
  });
1194
1376
  process.on("SIGINT", () => {
1195
1377
  child.kill("SIGINT");
1196
- console.log(chalk6.gray("\n\n\u{1F44B} Server stopped."));
1378
+ console.log(chalk7.gray("\n\n\u{1F44B} Server stopped."));
1197
1379
  process.exit(0);
1198
1380
  });
1199
1381
  child.on("close", (code) => {
1200
1382
  if (code !== 0) {
1201
- console.log(chalk6.red(`
1383
+ console.log(chalk7.red(`
1202
1384
  \u274C Process exited with code ${code}`));
1203
1385
  }
1204
1386
  process.exit(code || 0);
@@ -1207,7 +1389,7 @@ async function runProject(targetDir) {
1207
1389
 
1208
1390
  // src/commands/onboarding.ts
1209
1391
  import { select as select2, password as password2 } from "@inquirer/prompts";
1210
- import chalk7 from "chalk";
1392
+ import chalk8 from "chalk";
1211
1393
  var PROVIDER_MODELS = {
1212
1394
  gemini: [
1213
1395
  { name: "\u26A1 Gemini 2.5 Flash (Fast)", value: "gemini-2.5-flash" },
@@ -1284,10 +1466,10 @@ function getActiveProvider() {
1284
1466
  return null;
1285
1467
  }
1286
1468
  async function runOnboarding() {
1287
- console.log(chalk7.cyan.bold("\n\u{1F680} Welcome to Agdi!\n"));
1288
- console.log(chalk7.gray("Let's set up your AI provider in 3 quick steps.\n"));
1469
+ console.log(chalk8.cyan.bold("\n\u{1F680} Welcome to Agdi!\n"));
1470
+ console.log(chalk8.gray("Let's set up your AI provider in 3 quick steps.\n"));
1289
1471
  const config = loadConfig();
1290
- console.log(chalk7.white.bold("Step 1/3: Select your AI provider\n"));
1472
+ console.log(chalk8.white.bold("Step 1/3: Select your AI provider\n"));
1291
1473
  const provider = await select2({
1292
1474
  message: "Which AI provider would you like to use?",
1293
1475
  choices: [
@@ -1298,7 +1480,7 @@ async function runOnboarding() {
1298
1480
  { name: "\u{1F30A} DeepSeek", value: "deepseek" }
1299
1481
  ]
1300
1482
  });
1301
- console.log(chalk7.white.bold("\nStep 2/3: Enter your API key\n"));
1483
+ console.log(chalk8.white.bold("\nStep 2/3: Enter your API key\n"));
1302
1484
  const keyUrls = {
1303
1485
  gemini: "https://aistudio.google.com/apikey",
1304
1486
  openrouter: "https://openrouter.ai/keys",
@@ -1306,7 +1488,7 @@ async function runOnboarding() {
1306
1488
  anthropic: "https://console.anthropic.com/",
1307
1489
  deepseek: "https://platform.deepseek.com/"
1308
1490
  };
1309
- console.log(chalk7.gray(`Get your key at: ${chalk7.cyan(keyUrls[provider])}
1491
+ console.log(chalk8.gray(`Get your key at: ${chalk8.cyan(keyUrls[provider])}
1310
1492
  `));
1311
1493
  const apiKey = await password2({
1312
1494
  message: `Enter your ${provider} API key:`,
@@ -1330,7 +1512,7 @@ async function runOnboarding() {
1330
1512
  break;
1331
1513
  }
1332
1514
  config.defaultProvider = provider;
1333
- console.log(chalk7.white.bold("\nStep 3/3: Choose your default model\n"));
1515
+ console.log(chalk8.white.bold("\nStep 3/3: Choose your default model\n"));
1334
1516
  const models = PROVIDER_MODELS[provider] || PROVIDER_MODELS.gemini;
1335
1517
  const model = await select2({
1336
1518
  message: "Select your default model:",
@@ -1338,18 +1520,18 @@ async function runOnboarding() {
1338
1520
  });
1339
1521
  config.defaultModel = model;
1340
1522
  saveConfig(config);
1341
- console.log(chalk7.green("\n\u2705 Setup complete!"));
1342
- console.log(chalk7.gray(`Provider: ${chalk7.cyan(provider)}`));
1343
- console.log(chalk7.gray(`Model: ${chalk7.cyan(model)}
1523
+ console.log(chalk8.green("\n\u2705 Setup complete!"));
1524
+ console.log(chalk8.gray(`Provider: ${chalk8.cyan(provider)}`));
1525
+ console.log(chalk8.gray(`Model: ${chalk8.cyan(model)}
1344
1526
  `));
1345
1527
  return { provider, apiKey, model };
1346
1528
  }
1347
1529
  async function selectModel() {
1348
1530
  const config = loadConfig();
1349
1531
  const provider = config.defaultProvider || "gemini";
1350
- console.log(chalk7.cyan.bold("\n\u{1F504} Change Model\n"));
1351
- console.log(chalk7.gray(`Current provider: ${chalk7.cyan(provider)}`));
1352
- console.log(chalk7.gray(`Current model: ${chalk7.cyan(config.defaultModel || "not set")}
1532
+ console.log(chalk8.cyan.bold("\n\u{1F504} Change Model\n"));
1533
+ console.log(chalk8.gray(`Current provider: ${chalk8.cyan(provider)}`));
1534
+ console.log(chalk8.gray(`Current model: ${chalk8.cyan(config.defaultModel || "not set")}
1353
1535
  `));
1354
1536
  const changeProvider = await select2({
1355
1537
  message: "What would you like to do?",
@@ -1360,7 +1542,7 @@ async function selectModel() {
1360
1542
  ]
1361
1543
  });
1362
1544
  if (changeProvider === "cancel") {
1363
- console.log(chalk7.gray("\nCancelled.\n"));
1545
+ console.log(chalk8.gray("\nCancelled.\n"));
1364
1546
  return;
1365
1547
  }
1366
1548
  let selectedProvider = provider;
@@ -1383,7 +1565,7 @@ async function selectModel() {
1383
1565
  deepseek: config.deepseekApiKey
1384
1566
  };
1385
1567
  if (!keyMap[selectedProvider]) {
1386
- console.log(chalk7.yellow(`
1568
+ console.log(chalk8.yellow(`
1387
1569
  \u26A0\uFE0F No API key configured for ${selectedProvider}
1388
1570
  `));
1389
1571
  const apiKey = await password2({
@@ -1417,9 +1599,9 @@ async function selectModel() {
1417
1599
  });
1418
1600
  config.defaultModel = model;
1419
1601
  saveConfig(config);
1420
- console.log(chalk7.green("\n\u2705 Model changed!"));
1421
- console.log(chalk7.gray(`Provider: ${chalk7.cyan(selectedProvider)}`));
1422
- console.log(chalk7.gray(`Model: ${chalk7.cyan(model)}
1602
+ console.log(chalk8.green("\n\u2705 Model changed!"));
1603
+ console.log(chalk8.gray(`Provider: ${chalk8.cyan(selectedProvider)}`));
1604
+ console.log(chalk8.gray(`Model: ${chalk8.cyan(model)}
1423
1605
  `));
1424
1606
  }
1425
1607
 
@@ -1427,165 +1609,6 @@ async function selectModel() {
1427
1609
  import { input as input5, confirm as confirm3 } from "@inquirer/prompts";
1428
1610
  import chalk13 from "chalk";
1429
1611
 
1430
- // src/utils/ui.ts
1431
- import chalk8 from "chalk";
1432
- import gradient from "gradient-string";
1433
- import boxen from "boxen";
1434
- import figlet from "figlet";
1435
- import ora3 from "ora";
1436
- var THEME = {
1437
- cyan: "#06b6d4",
1438
- // Cyan-500
1439
- purple: "#8b5cf6",
1440
- // Violet-500
1441
- red: "#ef4444",
1442
- // Red-500
1443
- yellow: "#eab308",
1444
- // Yellow-500
1445
- gray: "#71717a",
1446
- // Zinc-500
1447
- dim: "#52525b"
1448
- // Zinc-600
1449
- };
1450
- var brandGradient = gradient([THEME.cyan, THEME.purple]);
1451
- var errorGradient = gradient([THEME.red, "#b91c1c"]);
1452
- var goldGradient = gradient([THEME.yellow, "#fbbf24"]);
1453
- async function renderBanner(version = "v2.6.0") {
1454
- console.clear();
1455
- const text = await new Promise((resolve5) => {
1456
- figlet("AGDI", { font: "Slant" }, (err, data) => {
1457
- resolve5(data || "AGDI");
1458
- });
1459
- });
1460
- console.log(brandGradient.multiline(text));
1461
- console.log(chalk8.hex(THEME.dim)(` ${version} [ARCHITECT ONLINE]
1462
- `));
1463
- }
1464
- function renderBox(title, content, style = "info") {
1465
- let borderColor = THEME.cyan;
1466
- let titleColor = chalk8.cyan;
1467
- if (style === "success") {
1468
- borderColor = THEME.cyan;
1469
- } else if (style === "warning") {
1470
- borderColor = THEME.yellow;
1471
- titleColor = chalk8.yellow;
1472
- } else if (style === "error") {
1473
- borderColor = THEME.red;
1474
- titleColor = chalk8.red;
1475
- }
1476
- const box = boxen(content, {
1477
- title: titleColor.bold(title),
1478
- padding: 1,
1479
- margin: 1,
1480
- borderStyle: "round",
1481
- borderColor,
1482
- dimBorder: false,
1483
- float: "left"
1484
- });
1485
- console.log(box);
1486
- }
1487
- function renderAlert(title, message) {
1488
- console.log("");
1489
- const box = boxen(chalk8.white(message), {
1490
- title: chalk8.red.bold(`\u{1F6E1}\uFE0F ${title.toUpperCase()} `),
1491
- padding: 1,
1492
- borderStyle: "double",
1493
- borderColor: "red",
1494
- textAlignment: "center"
1495
- });
1496
- console.log(box);
1497
- console.log("");
1498
- }
1499
- function printUserMessage(message) {
1500
- console.log("");
1501
- console.log(chalk8.cyan.bold("\u{1F464} YOU \u203A ") + chalk8.white(message));
1502
- console.log("");
1503
- }
1504
- function printAIMessage(message) {
1505
- console.log("");
1506
- console.log(brandGradient.multiline("\u26A1 AGDI \u203A "));
1507
- console.log(message.trim());
1508
- console.log("");
1509
- }
1510
- function createSpinner(text) {
1511
- return ora3({
1512
- text: chalk8.hex(THEME.gray)(text),
1513
- color: "cyan",
1514
- spinner: "dots",
1515
- discardStdin: false
1516
- // Important for allowing interruption if needed
1517
- });
1518
- }
1519
- function printIter() {
1520
- console.log(chalk8.hex(THEME.dim)("\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1521
- }
1522
- var activeReadlineInterface = null;
1523
- function registerActivePrompt(rl) {
1524
- activeReadlineInterface = rl;
1525
- }
1526
- var flags = {
1527
- yes: false,
1528
- headless: false
1529
- };
1530
- function setFlags(newFlags) {
1531
- Object.assign(flags, newFlags);
1532
- }
1533
- function safeExit(code = 0) {
1534
- if (activeReadlineInterface) {
1535
- try {
1536
- activeReadlineInterface.close?.();
1537
- activeReadlineInterface.destroy?.();
1538
- } catch {
1539
- }
1540
- activeReadlineInterface = null;
1541
- }
1542
- setImmediate(() => {
1543
- process.exit(code);
1544
- });
1545
- throw new Error("Process exiting");
1546
- }
1547
- async function smartConfirm(message, defaultValue = false) {
1548
- if (flags.yes || flags.headless || process.env.CI === "true" || process.env.CI === "1") {
1549
- console.log(chalk8.gray(` [Auto-approved: ${message}]`));
1550
- return true;
1551
- }
1552
- if (!process.stdout.isTTY) {
1553
- console.warn(chalk8.yellow("\u26A0\uFE0F Non-interactive session detected. Use --yes to approve actions."));
1554
- return false;
1555
- }
1556
- const { confirm: confirm4 } = await import("@inquirer/prompts");
1557
- return confirm4({ message, default: defaultValue });
1558
- }
1559
- async function smartSelect(message, choices, defaultValue) {
1560
- if (!process.stdout.isTTY || flags.headless) {
1561
- const result = defaultValue || choices[0]?.value;
1562
- if (result) {
1563
- console.log(chalk8.gray(` [Auto-selected: ${result}]`));
1564
- }
1565
- return result || null;
1566
- }
1567
- const { select: select5 } = await import("@inquirer/prompts");
1568
- return select5({ message, choices });
1569
- }
1570
- var ui = {
1571
- renderBanner,
1572
- renderBox,
1573
- renderAlert,
1574
- printUserMessage,
1575
- printAIMessage,
1576
- createSpinner,
1577
- printIter,
1578
- brandGradient,
1579
- THEME,
1580
- // Safety & Automation
1581
- safeExit,
1582
- smartConfirm,
1583
- smartSelect,
1584
- setFlags,
1585
- flags,
1586
- registerActivePrompt
1587
- };
1588
-
1589
1612
  // src/actions/plan-executor.ts
1590
1613
  import { select as select4, confirm } from "@inquirer/prompts";
1591
1614
  import chalk11 from "chalk";
@@ -3165,10 +3188,37 @@ Target: ${env.workspaceRoot}`
3165
3188
  console.log(chalk11.green("\u2713 Trusted for this session\n"));
3166
3189
  }
3167
3190
  }
3168
- const approved = await confirm({
3169
- message: `Execute ${plan.actions.length} actions?`,
3170
- default: true
3191
+ const dangerousKeywords = ["rm", "rimraf", "del", "npm install", "pnpm install", "yarn install", "chmod", "chown"];
3192
+ const dangerousActions = plan.actions.filter((a) => {
3193
+ if (a.type !== "exec") return false;
3194
+ const cmd = a.argv.join(" ");
3195
+ return dangerousKeywords.some((k) => cmd.includes(k));
3171
3196
  });
3197
+ if (dangerousActions.length > 0) {
3198
+ ui.renderAlert(
3199
+ "\u26A0\uFE0F DANGEROUS COMMANDS DETECTED",
3200
+ dangerousActions.map((a) => `\u2022 ${a.argv.join(" ")}`).join("\n") + "\n\nThese commands can modify your system or delete files."
3201
+ );
3202
+ const allowDangerous = await confirm({
3203
+ message: "Are you SURE you want to execute these dangerous commands?",
3204
+ default: false
3205
+ });
3206
+ if (!allowDangerous) {
3207
+ console.log(chalk11.yellow("\n\u{1F44B} Execution cancelled for safety.\n"));
3208
+ return { success: false, results, filesCreated, commandsRun, errors: ["User cancelled dangerous commands"] };
3209
+ }
3210
+ }
3211
+ let approved = false;
3212
+ if (ui.flags.dryRun) {
3213
+ console.log(chalk11.yellow("\n\u{1F6A7} DRY RUN MODE ENABLED"));
3214
+ console.log(chalk11.gray(" No files will be written and no commands will be executed.\n"));
3215
+ approved = true;
3216
+ } else {
3217
+ approved = await confirm({
3218
+ message: `Execute ${plan.actions.length} actions?`,
3219
+ default: true
3220
+ });
3221
+ }
3172
3222
  if (!approved) {
3173
3223
  console.log(chalk11.yellow("\n\u{1F44B} Plan cancelled.\n"));
3174
3224
  return { success: false, results, filesCreated, commandsRun, errors: ["User cancelled"] };
@@ -3185,6 +3235,11 @@ Target: ${env.workspaceRoot}`
3185
3235
  for (let i = 0; i < plan.actions.length; i++) {
3186
3236
  const action = plan.actions[i];
3187
3237
  displayActionProgress(action, i, plan.actions.length);
3238
+ if (ui.flags.dryRun) {
3239
+ console.log(chalk11.yellow(` [DRY RUN] Would execute: ${action.type} ${action.path || action.argv?.join(" ")}`));
3240
+ results.push({ action, success: true, output: "(dry run)" });
3241
+ continue;
3242
+ }
3188
3243
  const result = await executeAction(action);
3189
3244
  results.push(result);
3190
3245
  if (result.success) {
@@ -4040,8 +4095,8 @@ registerTool({
4040
4095
  { name: "path", type: "string", description: "Directory path", required: false, default: "." }
4041
4096
  ],
4042
4097
  execute: async (params) => {
4043
- const { readdirSync: readdirSync2, statSync: statSync2 } = await import("fs");
4044
- const { resolve: resolve5, join: join8 } = await import("path");
4098
+ const { readdirSync: readdirSync2, statSync: statSync3 } = await import("fs");
4099
+ const { resolve: resolve5, join: join9 } = await import("path");
4045
4100
  try {
4046
4101
  const dirPath = resolve5(process.cwd(), params.path || ".");
4047
4102
  const entries = readdirSync2(dirPath);
@@ -4049,7 +4104,7 @@ registerTool({
4049
4104
  const dirs = [];
4050
4105
  for (const entry of entries) {
4051
4106
  try {
4052
- const stat = statSync2(join8(dirPath, entry));
4107
+ const stat = statSync3(join9(dirPath, entry));
4053
4108
  if (stat.isDirectory()) {
4054
4109
  dirs.push(entry + "/");
4055
4110
  } else {
@@ -4649,25 +4704,183 @@ function handleError(error) {
4649
4704
  }
4650
4705
  }
4651
4706
 
4707
+ // src/commands/doctor.ts
4708
+ import chalk14 from "chalk";
4709
+ import { existsSync as existsSync11, statSync as statSync2 } from "fs";
4710
+ import { homedir as homedir7, platform as platform2 } from "os";
4711
+ import { join as join8 } from "path";
4712
+ async function runDoctor() {
4713
+ console.log("");
4714
+ ui.renderBox("AGDI DOCTOR", "Running diagnostics...", "info");
4715
+ console.log("");
4716
+ const results = [];
4717
+ const nodeVersion = process.version;
4718
+ const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0], 10);
4719
+ if (majorVersion >= 18) {
4720
+ results.push({
4721
+ name: "Node.js Version",
4722
+ status: "pass",
4723
+ message: `${nodeVersion} (\u226518 required)`
4724
+ });
4725
+ } else {
4726
+ results.push({
4727
+ name: "Node.js Version",
4728
+ status: "fail",
4729
+ message: `${nodeVersion} - Node.js 18+ required!`
4730
+ });
4731
+ }
4732
+ const configDir = join8(homedir7(), ".agdi");
4733
+ const configPath = join8(configDir, "config.json");
4734
+ if (existsSync11(configPath)) {
4735
+ if (platform2() !== "win32") {
4736
+ try {
4737
+ const stats = statSync2(configPath);
4738
+ const mode = (stats.mode & parseInt("777", 8)).toString(8);
4739
+ if (mode === "600") {
4740
+ results.push({
4741
+ name: "Config Permissions",
4742
+ status: "pass",
4743
+ message: `${configPath} (mode: 600)`
4744
+ });
4745
+ } else {
4746
+ results.push({
4747
+ name: "Config Permissions",
4748
+ status: "warn",
4749
+ message: `${configPath} (mode: ${mode}) - Should be 600!`
4750
+ });
4751
+ }
4752
+ } catch {
4753
+ results.push({
4754
+ name: "Config Permissions",
4755
+ status: "warn",
4756
+ message: "Could not check permissions"
4757
+ });
4758
+ }
4759
+ } else {
4760
+ results.push({
4761
+ name: "Config File",
4762
+ status: "pass",
4763
+ message: `${configPath} exists`
4764
+ });
4765
+ }
4766
+ } else {
4767
+ results.push({
4768
+ name: "Config File",
4769
+ status: "warn",
4770
+ message: "No config found. Run: agdi auth"
4771
+ });
4772
+ }
4773
+ const activeConfig = getActiveProvider();
4774
+ if (activeConfig) {
4775
+ const keyPreview = activeConfig.apiKey.slice(0, 8) + "..." + activeConfig.apiKey.slice(-4);
4776
+ results.push({
4777
+ name: "API Key",
4778
+ status: "pass",
4779
+ message: `${activeConfig.provider} (${keyPreview})`
4780
+ });
4781
+ results.push({
4782
+ name: "Active Model",
4783
+ status: "pass",
4784
+ message: activeConfig.model
4785
+ });
4786
+ } else {
4787
+ results.push({
4788
+ name: "API Key",
4789
+ status: "fail",
4790
+ message: "No API key configured. Run: agdi auth"
4791
+ });
4792
+ }
4793
+ const auditPath = join8(configDir, "audit.jsonl");
4794
+ if (existsSync11(auditPath)) {
4795
+ results.push({
4796
+ name: "Audit Log",
4797
+ status: "pass",
4798
+ message: `Logging to ${auditPath}`
4799
+ });
4800
+ } else {
4801
+ results.push({
4802
+ name: "Audit Log",
4803
+ status: "warn",
4804
+ message: "No audit log found (will be created on first action)"
4805
+ });
4806
+ }
4807
+ const trustPath = join8(configDir, "trusted-workspaces.json");
4808
+ if (existsSync11(trustPath)) {
4809
+ results.push({
4810
+ name: "Trust Store",
4811
+ status: "pass",
4812
+ message: "Workspace trust store found"
4813
+ });
4814
+ } else {
4815
+ results.push({
4816
+ name: "Trust Store",
4817
+ status: "warn",
4818
+ message: "No trusted workspaces yet"
4819
+ });
4820
+ }
4821
+ console.log(chalk14.bold("Diagnostic Results:\n"));
4822
+ for (const result of results) {
4823
+ let icon;
4824
+ let color;
4825
+ switch (result.status) {
4826
+ case "pass":
4827
+ icon = "\u2705";
4828
+ color = chalk14.green;
4829
+ break;
4830
+ case "warn":
4831
+ icon = "\u26A0\uFE0F";
4832
+ color = chalk14.yellow;
4833
+ break;
4834
+ case "fail":
4835
+ icon = "\u274C";
4836
+ color = chalk14.red;
4837
+ break;
4838
+ }
4839
+ console.log(` ${icon} ${chalk14.bold(result.name)}`);
4840
+ console.log(` ${color(result.message)}
4841
+ `);
4842
+ }
4843
+ const passed = results.filter((r) => r.status === "pass").length;
4844
+ const warnings = results.filter((r) => r.status === "warn").length;
4845
+ const failed = results.filter((r) => r.status === "fail").length;
4846
+ console.log(chalk14.gray("\u2500".repeat(50)));
4847
+ console.log(`
4848
+ ${chalk14.bold("Summary:")} ${chalk14.green(passed + " passed")}, ${chalk14.yellow(warnings + " warnings")}, ${chalk14.red(failed + " failed")}
4849
+ `);
4850
+ if (failed > 0) {
4851
+ console.log(chalk14.red(" \u26A0\uFE0F There are critical issues that need attention.\n"));
4852
+ } else if (warnings > 0) {
4853
+ console.log(chalk14.yellow(" \u2139\uFE0F There are some warnings, but Agdi should work.\n"));
4854
+ } else {
4855
+ console.log(chalk14.green(" \u{1F389} All checks passed! Agdi is ready to use.\n"));
4856
+ }
4857
+ }
4858
+
4652
4859
  // src/index.ts
4653
4860
  var BANNER = `
4654
- ${chalk14.cyan(` ___ __ _ `)}
4655
- ${chalk14.cyan(` / | ____ _____/ /(_) `)}
4656
- ${chalk14.cyan(` / /| | / __ \`/ __ // / `)}
4657
- ${chalk14.cyan(` / ___ |/ /_/ / /_/ // / `)}
4658
- ${chalk14.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
4659
- ${chalk14.cyan(` /____/ `)}
4861
+ ${chalk15.cyan(` ___ __ _ `)}
4862
+ ${chalk15.cyan(` / | ____ _____/ /(_) `)}
4863
+ ${chalk15.cyan(` / /| | / __ \`/ __ // / `)}
4864
+ ${chalk15.cyan(` / ___ |/ /_/ / /_/ // / `)}
4865
+ ${chalk15.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
4866
+ ${chalk15.cyan(` /____/ `)}
4660
4867
  `;
4661
4868
  var program = new Command();
4662
- program.name("agdi").description(chalk14.cyan("\u{1F680} AI-powered coding assistant")).version("2.7.0").option("-y, --yes", "Auto-approve all prompts (headless/CI mode)");
4869
+ program.name("agdi").description(chalk15.cyan("\u{1F680} AI-powered coding assistant")).version("2.8.0").option("-y, --yes", "Auto-approve all prompts (headless/CI mode)").option("-m, --minimal", "Generate only the requested file(s), not a full app").option("-d, --dry-run", "Show what would be created without writing files");
4663
4870
  program.hook("preAction", (thisCommand) => {
4664
4871
  const opts = thisCommand.opts();
4665
4872
  if (opts.yes) {
4666
4873
  ui.setFlags({ yes: true, headless: true });
4667
4874
  }
4875
+ if (opts.minimal) {
4876
+ ui.setFlags({ minimal: true });
4877
+ }
4878
+ if (opts.dryRun) {
4879
+ ui.setFlags({ dryRun: true });
4880
+ }
4668
4881
  });
4669
4882
  program.addHelpText("beforeAll", () => {
4670
- return BANNER + "\n" + chalk14.gray(" The Open Source AI Architect") + "\n" + chalk14.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");
4883
+ return BANNER + "\n" + chalk15.gray(" The Open Source AI Architect") + "\n" + chalk15.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");
4671
4884
  });
4672
4885
  program.action(async () => {
4673
4886
  try {
@@ -4678,7 +4891,7 @@ program.action(async () => {
4678
4891
  await startCodingMode();
4679
4892
  } catch (error) {
4680
4893
  if (error.name === "ExitPromptError") {
4681
- console.log(chalk14.gray("\n\n\u{1F44B} Goodbye!\n"));
4894
+ console.log(chalk15.gray("\n\n\u{1F44B} Goodbye!\n"));
4682
4895
  ui.safeExit(0);
4683
4896
  }
4684
4897
  throw error;
@@ -4693,7 +4906,7 @@ program.command("auth").description("Configure API keys").option("--status", "Sh
4693
4906
  }
4694
4907
  } catch (error) {
4695
4908
  if (error.name === "ExitPromptError") {
4696
- console.log(chalk14.gray("\n\n\u{1F44B} Cancelled.\n"));
4909
+ console.log(chalk15.gray("\n\n\u{1F44B} Cancelled.\n"));
4697
4910
  ui.safeExit(0);
4698
4911
  }
4699
4912
  throw error;
@@ -4704,7 +4917,7 @@ program.command("model").alias("models").description("Change AI model").action(a
4704
4917
  await selectModel();
4705
4918
  } catch (error) {
4706
4919
  if (error.name === "ExitPromptError") {
4707
- console.log(chalk14.gray("\n\n\u{1F44B} Cancelled.\n"));
4920
+ console.log(chalk15.gray("\n\n\u{1F44B} Cancelled.\n"));
4708
4921
  ui.safeExit(0);
4709
4922
  }
4710
4923
  throw error;
@@ -4718,7 +4931,7 @@ program.command("chat").description("Start a chat session").action(async () => {
4718
4931
  await startChat();
4719
4932
  } catch (error) {
4720
4933
  if (error.name === "ExitPromptError") {
4721
- console.log(chalk14.gray("\n\n\u{1F44B} Goodbye!\n"));
4934
+ console.log(chalk15.gray("\n\n\u{1F44B} Goodbye!\n"));
4722
4935
  ui.safeExit(0);
4723
4936
  }
4724
4937
  throw error;
@@ -4729,20 +4942,20 @@ program.command("run [directory]").description("Run a generated project").action
4729
4942
  await runProject(directory);
4730
4943
  } catch (error) {
4731
4944
  if (error.name === "ExitPromptError") {
4732
- console.log(chalk14.gray("\n\n\u{1F44B} Cancelled.\n"));
4945
+ console.log(chalk15.gray("\n\n\u{1F44B} Cancelled.\n"));
4733
4946
  ui.safeExit(0);
4734
4947
  }
4735
4948
  throw error;
4736
4949
  }
4737
4950
  });
4738
- 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) => {
4951
+ program.command("build <prompt>").alias("b").description("Generate an app from a prompt").option("-o, --output <dir>", "Output directory", "./generated-app").option("-m, --minimal", "Generate only the requested file(s), not a full app").option("-d, --dry-run", "Show what would be created without writing files").action(async (prompt, options) => {
4739
4952
  try {
4740
4953
  if (needsOnboarding()) {
4741
4954
  await runOnboarding();
4742
4955
  }
4743
4956
  const activeConfig = getActiveProvider();
4744
4957
  if (!activeConfig) {
4745
- console.log(chalk14.red("\u274C No API key configured. Run: agdi auth"));
4958
+ console.log(chalk15.red("\u274C No API key configured. Run: agdi auth"));
4746
4959
  return;
4747
4960
  }
4748
4961
  const spinner = ora5("Generating application...").start();
@@ -4754,30 +4967,42 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
4754
4967
  const pm = new ProjectManager();
4755
4968
  pm.create(options.output.replace("./", ""), prompt);
4756
4969
  const { plan, files } = await generateApp(prompt, llm, (step, file) => {
4757
- spinner.text = file ? `${step} ${chalk14.gray(file)}` : step;
4970
+ spinner.text = file ? `${step} ${chalk15.gray(file)}` : step;
4758
4971
  });
4759
4972
  pm.updateFiles(files);
4760
4973
  pm.updateDependencies(plan.dependencies);
4974
+ if (options.dryRun || ui.flags.dryRun) {
4975
+ spinner.stop();
4976
+ console.log(chalk15.cyan.bold("\n\u{1F6A7} DRY RUN SUMMARY\n"));
4977
+ console.log(chalk15.gray(`Project: ${plan.name}
4978
+ `));
4979
+ console.log(chalk15.cyan("Files to be created:"));
4980
+ files.forEach((f) => console.log(chalk15.gray(` \u{1F4C4} ${f.path}`)));
4981
+ console.log(chalk15.cyan("\nDependencies:"));
4982
+ console.log(chalk15.gray(` \u{1F4E6} ${plan.dependencies.join(", ")}`));
4983
+ console.log(chalk15.green("\n\u2713 Dry run complete. No files written.\n"));
4984
+ return;
4985
+ }
4761
4986
  await writeProject(pm.get(), options.output);
4762
- spinner.succeed(chalk14.green("App generated!"));
4763
- console.log(chalk14.gray(`
4764
- \u{1F4C1} Created ${files.length} files in ${chalk14.cyan(options.output)}`));
4765
- console.log(chalk14.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
4987
+ spinner.succeed(chalk15.green("App generated!"));
4988
+ console.log(chalk15.gray(`
4989
+ \u{1F4C1} Created ${files.length} files in ${chalk15.cyan(options.output)}`));
4990
+ console.log(chalk15.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
4766
4991
  } catch (error) {
4767
4992
  spinner.fail("Generation failed");
4768
4993
  const msg = error instanceof Error ? error.message : String(error);
4769
4994
  if (msg.includes("429") || msg.includes("quota")) {
4770
- console.log(chalk14.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
4995
+ console.log(chalk15.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
4771
4996
  } else if (msg.includes("401") || msg.includes("403")) {
4772
- console.log(chalk14.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
4997
+ console.log(chalk15.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
4773
4998
  } else {
4774
- console.error(chalk14.red("\n" + msg + "\n"));
4999
+ console.error(chalk15.red("\n" + msg + "\n"));
4775
5000
  }
4776
5001
  ui.safeExit(1);
4777
5002
  }
4778
5003
  } catch (error) {
4779
5004
  if (error.name === "ExitPromptError") {
4780
- console.log(chalk14.gray("\n\n\u{1F44B} Cancelled.\n"));
5005
+ console.log(chalk15.gray("\n\n\u{1F44B} Cancelled.\n"));
4781
5006
  ui.safeExit(0);
4782
5007
  }
4783
5008
  throw error;
@@ -4786,11 +5011,11 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
4786
5011
  program.command("config").description("Show configuration").action(async () => {
4787
5012
  const config = loadConfig();
4788
5013
  const active = getActiveProvider();
4789
- console.log(chalk14.cyan.bold("\n\u2699\uFE0F Configuration\n"));
4790
- console.log(chalk14.gray(" Provider: ") + chalk14.cyan(config.defaultProvider || "not set"));
4791
- console.log(chalk14.gray(" Model: ") + chalk14.cyan(config.defaultModel || "not set"));
4792
- console.log(chalk14.gray(" Config: ") + chalk14.gray("~/.agdi/config.json"));
4793
- console.log(chalk14.cyan.bold("\n\u{1F510} API Keys\n"));
5014
+ console.log(chalk15.cyan.bold("\n\u2699\uFE0F Configuration\n"));
5015
+ console.log(chalk15.gray(" Provider: ") + chalk15.cyan(config.defaultProvider || "not set"));
5016
+ console.log(chalk15.gray(" Model: ") + chalk15.cyan(config.defaultModel || "not set"));
5017
+ console.log(chalk15.gray(" Config: ") + chalk15.gray("~/.agdi/config.json"));
5018
+ console.log(chalk15.cyan.bold("\n\u{1F510} API Keys\n"));
4794
5019
  const keys = [
4795
5020
  ["Gemini", config.geminiApiKey],
4796
5021
  ["OpenRouter", config.openrouterApiKey],
@@ -4799,9 +5024,18 @@ program.command("config").description("Show configuration").action(async () => {
4799
5024
  ["DeepSeek", config.deepseekApiKey]
4800
5025
  ];
4801
5026
  for (const [name, key] of keys) {
4802
- const status = key ? chalk14.green("\u2713") : chalk14.gray("\u2717");
5027
+ const status = key ? chalk15.green("\u2713") : chalk15.gray("\u2717");
4803
5028
  console.log(` ${status} ${name}`);
4804
5029
  }
4805
5030
  console.log("");
5031
+ console.log("");
5032
+ });
5033
+ program.command("doctor").alias("doc").description("Run self-diagnosis checks").action(async () => {
5034
+ try {
5035
+ await runDoctor();
5036
+ } catch (error) {
5037
+ console.error(chalk15.red("Diagnostic failed: " + error));
5038
+ ui.safeExit(1);
5039
+ }
4806
5040
  });
4807
5041
  program.parse();