agdi 2.7.1 → 2.8.1

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 +624 -342
  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,173 @@ 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
+ var GracefulExitError = class extends Error {
396
+ constructor() {
397
+ super("Process exiting");
398
+ this.name = "GracefulExitError";
399
+ }
400
+ };
401
+ function safeExit(code = 0) {
402
+ if (activeReadlineInterface) {
403
+ try {
404
+ activeReadlineInterface.close?.();
405
+ activeReadlineInterface.destroy?.();
406
+ } catch {
407
+ }
408
+ activeReadlineInterface = null;
409
+ }
410
+ setImmediate(() => {
411
+ process.exit(code);
412
+ });
413
+ throw new GracefulExitError();
414
+ }
415
+ async function smartConfirm(message, defaultValue = false) {
416
+ if (flags.yes || flags.headless || process.env.CI === "true" || process.env.CI === "1") {
417
+ console.log(chalk.gray(` [Auto-approved: ${message}]`));
418
+ return true;
419
+ }
420
+ if (!process.stdout.isTTY) {
421
+ console.warn(chalk.yellow("\u26A0\uFE0F Non-interactive session detected. Use --yes to approve actions."));
422
+ return false;
423
+ }
424
+ const { confirm: confirm4 } = await import("@inquirer/prompts");
425
+ return confirm4({ message, default: defaultValue });
426
+ }
427
+ async function smartSelect(message, choices, defaultValue) {
428
+ if (!process.stdout.isTTY || flags.headless) {
429
+ const result = defaultValue || choices[0]?.value;
430
+ if (result) {
431
+ console.log(chalk.gray(` [Auto-selected: ${result}]`));
432
+ }
433
+ return result || null;
434
+ }
435
+ const { select: select5 } = await import("@inquirer/prompts");
436
+ return select5({ message, choices });
437
+ }
438
+ var ui = {
439
+ renderBanner,
440
+ renderBox,
441
+ renderAlert,
442
+ printUserMessage,
443
+ printAIMessage,
444
+ createSpinner,
445
+ printIter,
446
+ brandGradient,
447
+ THEME,
448
+ // Safety & Automation
449
+ safeExit,
450
+ smartConfirm,
451
+ smartSelect,
452
+ setFlags,
453
+ flags,
454
+ registerActivePrompt
455
+ };
456
+
290
457
  // src/core/architect/index.ts
291
458
  var SYSTEM_PROMPT = `You are Agdi Architect, an expert software architect AI.
292
459
  Your job is to generate complete, production-ready React applications.
@@ -294,7 +461,7 @@ Always use TypeScript, Tailwind CSS, and Vite.
294
461
  Generate all necessary files including package.json, tsconfig.json, vite.config.ts.
295
462
  Make the UI beautiful with modern design patterns.`;
296
463
  async function generatePlan(prompt, llm) {
297
- const planPrompt = `Create a detailed plan for: ${prompt}
464
+ let planPrompt = `Create a detailed plan for: ${prompt}
298
465
 
299
466
  Return a JSON object with:
300
467
  {
@@ -306,6 +473,25 @@ Return a JSON object with:
306
473
  }
307
474
 
308
475
  Return ONLY valid JSON, no markdown.`;
476
+ if (ui.flags.minimal) {
477
+ planPrompt = `Create a minimal plan for: ${prompt}
478
+
479
+ CRITICAL: Minimal mode matches user request exactly.
480
+ - If user asks for "hello.ts", generate ONLY "hello.ts".
481
+ - Do NOT scaffold a full React app unless explicitly asked.
482
+ - Do NOT add boilerplate headers/footers.
483
+
484
+ Return a JSON object with:
485
+ {
486
+ "name": "minimal-project",
487
+ "description": "Minimal generation",
488
+ "files": [{"path": "requested-file.ts", "description": "Requested logic"}],
489
+ "dependencies": [],
490
+ "architecture": "Single file script"
491
+ }
492
+
493
+ Return ONLY valid JSON.`;
494
+ }
309
495
  const response = await llm.generate(planPrompt, SYSTEM_PROMPT);
310
496
  try {
311
497
  const jsonMatch = response.text.match(/\{[\s\S]*\}/);
@@ -356,50 +542,52 @@ async function generateApp(prompt, llm, onProgress) {
356
542
  const file = await generateFile(fileSpec.path, fileSpec.description, plan, llm);
357
543
  files.push(file);
358
544
  }
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
- });
545
+ if (!ui.flags.minimal) {
546
+ onProgress?.("Creating package.json...", "package.json");
547
+ files.push({
548
+ path: "package.json",
549
+ content: JSON.stringify({
550
+ name: plan.name,
551
+ version: "0.1.0",
552
+ type: "module",
553
+ scripts: {
554
+ dev: "vite",
555
+ build: "tsc -b && vite build",
556
+ preview: "vite preview"
557
+ },
558
+ dependencies: {
559
+ "react": "^18.3.1",
560
+ "react-dom": "^18.3.1",
561
+ ...plan.dependencies.reduce((acc, dep) => {
562
+ if (dep !== "react" && dep !== "react-dom") {
563
+ acc[dep] = "latest";
564
+ }
565
+ return acc;
566
+ }, {})
567
+ },
568
+ devDependencies: {
569
+ "@types/react": "^18.3.0",
570
+ "@types/react-dom": "^18.3.0",
571
+ "@vitejs/plugin-react": "^4.3.0",
572
+ "autoprefixer": "^10.4.20",
573
+ "postcss": "^8.4.45",
574
+ "tailwindcss": "^3.4.10",
575
+ "typescript": "~5.5.0",
576
+ "vite": "^5.4.0"
577
+ }
578
+ }, null, 2)
579
+ });
580
+ }
393
581
  return { plan, files };
394
582
  }
395
583
 
396
584
  // src/utils/fs.ts
397
585
  import fs from "fs-extra";
398
586
  import path from "path";
399
- import chalk2 from "chalk";
587
+ import chalk3 from "chalk";
400
588
 
401
589
  // src/security/code-firewall.ts
402
- import chalk from "chalk";
590
+ import chalk2 from "chalk";
403
591
  var MALICIOUS_PATTERNS = [
404
592
  // ==================== HARDCODED SECRETS ====================
405
593
  {
@@ -648,35 +836,35 @@ function shouldBlockCode(result) {
648
836
  }
649
837
  function displayScanResults(result, filename) {
650
838
  if (result.safe) {
651
- console.log(chalk.green("\u2705 No malicious patterns detected"));
839
+ console.log(chalk2.green("\u2705 No malicious patterns detected"));
652
840
  return;
653
841
  }
654
- console.log(chalk.red.bold("\n\u{1F6A8} SECURITY SCAN FAILED"));
842
+ console.log(chalk2.red.bold("\n\u{1F6A8} SECURITY SCAN FAILED"));
655
843
  if (filename) {
656
- console.log(chalk.gray(`File: ${filename}`));
844
+ console.log(chalk2.gray(`File: ${filename}`));
657
845
  }
658
846
  console.log("");
659
847
  const criticals = result.matches.filter((m) => m.severity === "critical");
660
848
  const highs = result.matches.filter((m) => m.severity === "high");
661
849
  const others = result.matches.filter((m) => m.severity !== "critical" && m.severity !== "high");
662
850
  if (criticals.length > 0) {
663
- console.log(chalk.red("\u{1F534} CRITICAL:"));
851
+ console.log(chalk2.red("\u{1F534} CRITICAL:"));
664
852
  for (const m of criticals) {
665
- console.log(chalk.red(` Line ${m.line}: ${m.description}`));
666
- console.log(chalk.gray(` Found: ${m.match}`));
853
+ console.log(chalk2.red(` Line ${m.line}: ${m.description}`));
854
+ console.log(chalk2.gray(` Found: ${m.match}`));
667
855
  }
668
856
  }
669
857
  if (highs.length > 0) {
670
- console.log(chalk.yellow("\n\u{1F7E0} HIGH:"));
858
+ console.log(chalk2.yellow("\n\u{1F7E0} HIGH:"));
671
859
  for (const m of highs) {
672
- console.log(chalk.yellow(` Line ${m.line}: ${m.description}`));
673
- console.log(chalk.gray(` Found: ${m.match}`));
860
+ console.log(chalk2.yellow(` Line ${m.line}: ${m.description}`));
861
+ console.log(chalk2.gray(` Found: ${m.match}`));
674
862
  }
675
863
  }
676
864
  if (others.length > 0) {
677
- console.log(chalk.cyan("\n\u{1F7E1} WARNINGS:"));
865
+ console.log(chalk2.cyan("\n\u{1F7E1} WARNINGS:"));
678
866
  for (const m of others) {
679
- console.log(chalk.cyan(` Line ${m.line}: ${m.description}`));
867
+ console.log(chalk2.cyan(` Line ${m.line}: ${m.description}`));
680
868
  }
681
869
  }
682
870
  console.log("");
@@ -686,11 +874,11 @@ function validateCodeBeforeWrite(code, filename) {
686
874
  if (!result.safe) {
687
875
  displayScanResults(result, filename);
688
876
  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"));
877
+ console.log(chalk2.red.bold("\u{1F6A8} BLOCKED: Code contains critical security issues"));
878
+ console.log(chalk2.gray("The file will NOT be written to disk.\n"));
691
879
  return false;
692
880
  } else {
693
- console.log(chalk.yellow("\u26A0\uFE0F Warning: Code contains potential issues but will be written.\n"));
881
+ console.log(chalk2.yellow("\u26A0\uFE0F Warning: Code contains potential issues but will be written.\n"));
694
882
  }
695
883
  }
696
884
  return true;
@@ -706,7 +894,7 @@ async function writeProject(project, outputDir) {
706
894
  const isSafe = validateCodeBeforeWrite(file.content, file.path);
707
895
  if (!isSafe) {
708
896
  blockedCount++;
709
- console.log(chalk2.red(`\u26D4 BLOCKED: ${file.path}`));
897
+ console.log(chalk3.red(`\u26D4 BLOCKED: ${file.path}`));
710
898
  continue;
711
899
  }
712
900
  await fs.ensureDir(path.dirname(filePath));
@@ -715,16 +903,16 @@ async function writeProject(project, outputDir) {
715
903
  }
716
904
  console.log("");
717
905
  if (blockedCount > 0) {
718
- console.log(chalk2.yellow(`\u26A0\uFE0F ${blockedCount} file(s) blocked by security scan`));
906
+ console.log(chalk3.yellow(`\u26A0\uFE0F ${blockedCount} file(s) blocked by security scan`));
719
907
  }
720
- console.log(chalk2.green(`\u2705 ${writtenCount} file(s) written successfully`));
908
+ console.log(chalk3.green(`\u2705 ${writtenCount} file(s) written successfully`));
721
909
  }
722
910
 
723
911
  // src/utils/config.ts
724
912
  import fs2 from "fs-extra";
725
913
  import path2 from "path";
726
914
  import os from "os";
727
- import chalk3 from "chalk";
915
+ import chalk4 from "chalk";
728
916
  var CONFIG_DIR = path2.join(os.homedir(), ".agdi");
729
917
  var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
730
918
  var SECURE_FILE_MODE = 384;
@@ -741,11 +929,11 @@ function checkPermissions() {
741
929
  }
742
930
  const isWorldReadable = (mode & 36) !== 0;
743
931
  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}"
932
+ console.log(chalk4.yellow("\n\u26A0\uFE0F SECURITY WARNING"));
933
+ console.log(chalk4.gray("Your config file is readable by other users!"));
934
+ console.log(chalk4.gray(`File: ${CONFIG_FILE}`));
935
+ console.log(chalk4.gray("Run the following to fix:"));
936
+ console.log(chalk4.cyan(` chmod 600 "${CONFIG_FILE}"
749
937
  `));
750
938
  return false;
751
939
  }
@@ -781,16 +969,16 @@ function saveConfig(config) {
781
969
  fs2.writeJsonSync(CONFIG_FILE, config, { spaces: 2 });
782
970
  setSecurePermissions();
783
971
  } catch (error) {
784
- console.error(chalk3.red("Failed to save config:"), error);
972
+ console.error(chalk4.red("Failed to save config:"), error);
785
973
  }
786
974
  }
787
975
 
788
976
  // src/commands/auth.ts
789
977
  import { input, select, password } from "@inquirer/prompts";
790
- import chalk4 from "chalk";
978
+ import chalk5 from "chalk";
791
979
  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"));
980
+ console.log(chalk5.cyan.bold("\n\u{1F510} Agdi Authentication\n"));
981
+ console.log(chalk5.gray("Configure your API key to use Agdi CLI.\n"));
794
982
  try {
795
983
  const config = loadConfig();
796
984
  const provider = await select({
@@ -812,8 +1000,8 @@ async function login() {
812
1000
  config.ollamaUrl = ollamaUrl;
813
1001
  config.defaultProvider = "ollama";
814
1002
  saveConfig(config);
815
- console.log(chalk4.green("\n\u2705 Ollama configured"));
816
- console.log(chalk4.gray(`Server: ${ollamaUrl}
1003
+ console.log(chalk5.green("\n\u2705 Ollama configured"));
1004
+ console.log(chalk5.gray(`Server: ${ollamaUrl}
817
1005
  `));
818
1006
  return;
819
1007
  }
@@ -840,12 +1028,12 @@ async function login() {
840
1028
  }
841
1029
  config.defaultProvider = provider;
842
1030
  saveConfig(config);
843
- console.log(chalk4.green(`
1031
+ console.log(chalk5.green(`
844
1032
  \u2705 ${provider} API key saved securely`));
845
- console.log(chalk4.gray("Keys stored in ~/.agdi/config.json\n"));
1033
+ console.log(chalk5.gray("Keys stored in ~/.agdi/config.json\n"));
846
1034
  } catch (error) {
847
1035
  if (error.name === "ExitPromptError") {
848
- console.log(chalk4.gray("\n\n\u{1F44B} Cancelled.\n"));
1036
+ console.log(chalk5.gray("\n\n\u{1F44B} Cancelled.\n"));
849
1037
  process.exit(0);
850
1038
  }
851
1039
  throw error;
@@ -853,7 +1041,7 @@ async function login() {
853
1041
  }
854
1042
  async function showStatus() {
855
1043
  const config = loadConfig();
856
- console.log(chalk4.cyan.bold("\n\u{1F4CA} Authentication Status\n"));
1044
+ console.log(chalk5.cyan.bold("\n\u{1F4CA} Authentication Status\n"));
857
1045
  const providers = [
858
1046
  { name: "Gemini", key: config.geminiApiKey },
859
1047
  { name: "OpenRouter", key: config.openrouterApiKey },
@@ -862,19 +1050,19 @@ async function showStatus() {
862
1050
  { name: "DeepSeek", key: config.deepseekApiKey }
863
1051
  ];
864
1052
  for (const p of providers) {
865
- const status = p.key ? chalk4.green("\u2713 Configured") : chalk4.gray("\u2717 Not set");
1053
+ const status = p.key ? chalk5.green("\u2713 Configured") : chalk5.gray("\u2717 Not set");
866
1054
  console.log(` ${p.name.padEnd(12)} ${status}`);
867
1055
  }
868
- console.log(chalk4.cyan(`
1056
+ console.log(chalk5.cyan(`
869
1057
  Default: ${config.defaultProvider || "gemini"}
870
1058
  `));
871
- console.log(chalk4.gray('\u{1F4A1} Tip: Use "agdi auth" to reconfigure\n'));
1059
+ console.log(chalk5.gray('\u{1F4A1} Tip: Use "agdi auth" to reconfigure\n'));
872
1060
  }
873
1061
 
874
1062
  // src/commands/chat.ts
875
1063
  import { input as input2 } from "@inquirer/prompts";
876
- import chalk5 from "chalk";
877
- import ora from "ora";
1064
+ import chalk6 from "chalk";
1065
+ import ora2 from "ora";
878
1066
  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
1067
 
880
1068
  # Core Expertise
@@ -1028,8 +1216,8 @@ When asked to build something:
1028
1216
 
1029
1217
  You build software that works, scales, and follows industry best practices. Every solution is complete, tested, and ready for production deployment.`;
1030
1218
  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'));
1219
+ console.log(chalk6.cyan.bold("\n\u{1F4AC} Agdi Interactive Mode\n"));
1220
+ console.log(chalk6.gray('Type your coding requests. Type "exit" to quit.\n'));
1033
1221
  const config = loadConfig();
1034
1222
  let provider;
1035
1223
  let apiKey = "";
@@ -1039,33 +1227,33 @@ async function startChat() {
1039
1227
  } else if (config.openrouterApiKey) {
1040
1228
  provider = "openrouter";
1041
1229
  apiKey = config.openrouterApiKey;
1042
- console.log(chalk5.gray("Using OpenRouter (100+ models available)\n"));
1230
+ console.log(chalk6.gray("Using OpenRouter (100+ models available)\n"));
1043
1231
  } 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"));
1232
+ console.log(chalk6.yellow("\u26A0\uFE0F Puter.com FREE mode requires browser authentication."));
1233
+ console.log(chalk6.gray("For CLI usage, please configure an API key:\n"));
1234
+ console.log(chalk6.cyan(" agdi auth"));
1235
+ console.log(chalk6.gray("\nSupported providers: Gemini, OpenRouter, OpenAI, Anthropic, DeepSeek\n"));
1048
1236
  return;
1049
1237
  } 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'));
1238
+ console.log(chalk6.yellow("\u26A0\uFE0F No API key configured."));
1239
+ console.log(chalk6.gray('Run "agdi auth" to configure your API key.\n'));
1052
1240
  return;
1053
1241
  }
1054
- console.log(chalk5.gray(`Using provider: ${chalk5.cyan(provider)}`));
1055
- console.log(chalk5.gray("\u2500".repeat(50) + "\n"));
1242
+ console.log(chalk6.gray(`Using provider: ${chalk6.cyan(provider)}`));
1243
+ console.log(chalk6.gray("\u2500".repeat(50) + "\n"));
1056
1244
  const pm = new ProjectManager();
1057
1245
  while (true) {
1058
1246
  const userInput = await input2({
1059
- message: chalk5.cyan("You:")
1247
+ message: chalk6.cyan("You:")
1060
1248
  });
1061
1249
  if (userInput.toLowerCase() === "exit" || userInput.toLowerCase() === "quit") {
1062
- console.log(chalk5.gray("\n\u{1F44B} Goodbye!\n"));
1250
+ console.log(chalk6.gray("\n\u{1F44B} Goodbye!\n"));
1063
1251
  break;
1064
1252
  }
1065
1253
  if (!userInput.trim()) {
1066
1254
  continue;
1067
1255
  }
1068
- const spinner = ora("Thinking...").start();
1256
+ const spinner = ora2("Thinking...").start();
1069
1257
  try {
1070
1258
  const llm = createLLMProvider(provider, { apiKey, model: config.defaultModel });
1071
1259
  if (userInput.toLowerCase().includes("create") || userInput.toLowerCase().includes("build") || userInput.toLowerCase().includes("make")) {
@@ -1076,9 +1264,9 @@ async function startChat() {
1076
1264
  });
1077
1265
  pm.updateFiles(files);
1078
1266
  spinner.succeed("Application generated!");
1079
- console.log(chalk5.green("\n\u{1F4C1} Files created:"));
1267
+ console.log(chalk6.green("\n\u{1F4C1} Files created:"));
1080
1268
  for (const file of files) {
1081
- console.log(chalk5.gray(` - ${file.path}`));
1269
+ console.log(chalk6.gray(` - ${file.path}`));
1082
1270
  }
1083
1271
  const shouldWrite = await input2({
1084
1272
  message: "Write files to disk? (y/n):",
@@ -1090,38 +1278,38 @@ async function startChat() {
1090
1278
  default: "./generated-app"
1091
1279
  });
1092
1280
  await writeProject(pm.get(), dir);
1093
- console.log(chalk5.green(`
1281
+ console.log(chalk6.green(`
1094
1282
  \u2705 Files written to ${dir}
1095
1283
  `));
1096
1284
  }
1097
1285
  } else {
1098
1286
  const response = await llm.generate(userInput, SYSTEM_PROMPT2);
1099
1287
  spinner.stop();
1100
- console.log(chalk5.cyan("\nAgdi: ") + response.text + "\n");
1288
+ console.log(chalk6.cyan("\nAgdi: ") + response.text + "\n");
1101
1289
  }
1102
1290
  } catch (error) {
1103
1291
  if (error.name === "ExitPromptError") {
1104
- console.log(chalk5.gray("\n\n\u{1F44B} Goodbye!\n"));
1292
+ console.log(chalk6.gray("\n\n\u{1F44B} Goodbye!\n"));
1105
1293
  process.exit(0);
1106
1294
  }
1107
1295
  spinner.fail("Error");
1108
1296
  const errorMessage = error instanceof Error ? error.message : String(error);
1109
1297
  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"));
1298
+ console.log(chalk6.yellow("\n\u26A0\uFE0F API quota exceeded!"));
1299
+ console.log(chalk6.gray("Your API key has run out of credits."));
1300
+ console.log(chalk6.gray("Try: Use a different API key or wait for quota reset.\n"));
1113
1301
  } 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"));
1302
+ console.log(chalk6.red("\n\u{1F511} Invalid API key"));
1303
+ console.log(chalk6.gray("Please reconfigure your API key:"));
1304
+ console.log(chalk6.cyan(" agdi auth\n"));
1117
1305
  } 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"));
1306
+ console.log(chalk6.red("\n\u{1F6AB} Access denied"));
1307
+ console.log(chalk6.gray("Your API key doesn't have permission for this operation.\n"));
1120
1308
  } 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"));
1309
+ console.log(chalk6.red("\n\u{1F310} Network error"));
1310
+ console.log(chalk6.gray("Please check your internet connection.\n"));
1123
1311
  } else {
1124
- console.log(chalk5.red("\n" + errorMessage + "\n"));
1312
+ console.log(chalk6.red("\n" + errorMessage + "\n"));
1125
1313
  }
1126
1314
  }
1127
1315
  }
@@ -1129,23 +1317,23 @@ async function startChat() {
1129
1317
 
1130
1318
  // src/commands/run.ts
1131
1319
  import { spawn } from "child_process";
1132
- import chalk6 from "chalk";
1320
+ import chalk7 from "chalk";
1133
1321
  import fs3 from "fs-extra";
1134
1322
  import path3 from "path";
1135
- import ora2 from "ora";
1323
+ import ora3 from "ora";
1136
1324
  async function runProject(targetDir) {
1137
1325
  const dir = targetDir || process.cwd();
1138
1326
  const absoluteDir = path3.resolve(dir);
1139
- console.log(chalk6.cyan.bold("\n\u{1F680} Agdi Run\n"));
1327
+ console.log(chalk7.cyan.bold("\n\u{1F680} Agdi Run\n"));
1140
1328
  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"));
1329
+ console.log(chalk7.red(`\u274C Directory not found: ${absoluteDir}`));
1330
+ console.log(chalk7.gray("Create a project first with: agdi init"));
1143
1331
  return;
1144
1332
  }
1145
1333
  const packageJsonPath = path3.join(absoluteDir, "package.json");
1146
1334
  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."));
1335
+ console.log(chalk7.red(`\u274C No package.json found in: ${absoluteDir}`));
1336
+ console.log(chalk7.gray("This doesn't appear to be a Node.js project."));
1149
1337
  return;
1150
1338
  }
1151
1339
  const packageJson = fs3.readJsonSync(packageJsonPath);
@@ -1154,14 +1342,14 @@ async function runProject(targetDir) {
1154
1342
  if (!scripts.dev && scripts.start) {
1155
1343
  runScript = "start";
1156
1344
  } 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"'));
1345
+ console.log(chalk7.red('\u274C No "dev" or "start" script found in package.json'));
1346
+ console.log(chalk7.gray('Add a script like: "dev": "vite" or "start": "node index.js"'));
1159
1347
  return;
1160
1348
  }
1161
1349
  const nodeModulesPath = path3.join(absoluteDir, "node_modules");
1162
1350
  if (!fs3.existsSync(nodeModulesPath)) {
1163
- console.log(chalk6.yellow("\u{1F4E6} Installing dependencies...\n"));
1164
- const installSpinner = ora2("Running npm install...").start();
1351
+ console.log(chalk7.yellow("\u{1F4E6} Installing dependencies...\n"));
1352
+ const installSpinner = ora3("Running npm install...").start();
1165
1353
  await new Promise((resolve5, reject) => {
1166
1354
  const install = spawn("npm", ["install"], {
1167
1355
  cwd: absoluteDir,
@@ -1181,11 +1369,11 @@ async function runProject(targetDir) {
1181
1369
  });
1182
1370
  console.log("");
1183
1371
  }
1184
- console.log(chalk6.green(`\u25B6 Running: npm run ${runScript}`));
1185
- console.log(chalk6.gray(` Directory: ${absoluteDir}
1372
+ console.log(chalk7.green(`\u25B6 Running: npm run ${runScript}`));
1373
+ console.log(chalk7.gray(` Directory: ${absoluteDir}
1186
1374
  `));
1187
- console.log(chalk6.gray("\u2500".repeat(50)));
1188
- console.log(chalk6.gray("Press Ctrl+C to stop\n"));
1375
+ console.log(chalk7.gray("\u2500".repeat(50)));
1376
+ console.log(chalk7.gray("Press Ctrl+C to stop\n"));
1189
1377
  const child = spawn("npm", ["run", runScript], {
1190
1378
  cwd: absoluteDir,
1191
1379
  stdio: "inherit",
@@ -1193,12 +1381,12 @@ async function runProject(targetDir) {
1193
1381
  });
1194
1382
  process.on("SIGINT", () => {
1195
1383
  child.kill("SIGINT");
1196
- console.log(chalk6.gray("\n\n\u{1F44B} Server stopped."));
1384
+ console.log(chalk7.gray("\n\n\u{1F44B} Server stopped."));
1197
1385
  process.exit(0);
1198
1386
  });
1199
1387
  child.on("close", (code) => {
1200
1388
  if (code !== 0) {
1201
- console.log(chalk6.red(`
1389
+ console.log(chalk7.red(`
1202
1390
  \u274C Process exited with code ${code}`));
1203
1391
  }
1204
1392
  process.exit(code || 0);
@@ -1207,7 +1395,7 @@ async function runProject(targetDir) {
1207
1395
 
1208
1396
  // src/commands/onboarding.ts
1209
1397
  import { select as select2, password as password2 } from "@inquirer/prompts";
1210
- import chalk7 from "chalk";
1398
+ import chalk8 from "chalk";
1211
1399
  var PROVIDER_MODELS = {
1212
1400
  gemini: [
1213
1401
  { name: "\u26A1 Gemini 2.5 Flash (Fast)", value: "gemini-2.5-flash" },
@@ -1284,10 +1472,10 @@ function getActiveProvider() {
1284
1472
  return null;
1285
1473
  }
1286
1474
  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"));
1475
+ console.log(chalk8.cyan.bold("\n\u{1F680} Welcome to Agdi!\n"));
1476
+ console.log(chalk8.gray("Let's set up your AI provider in 3 quick steps.\n"));
1289
1477
  const config = loadConfig();
1290
- console.log(chalk7.white.bold("Step 1/3: Select your AI provider\n"));
1478
+ console.log(chalk8.white.bold("Step 1/3: Select your AI provider\n"));
1291
1479
  const provider = await select2({
1292
1480
  message: "Which AI provider would you like to use?",
1293
1481
  choices: [
@@ -1298,7 +1486,7 @@ async function runOnboarding() {
1298
1486
  { name: "\u{1F30A} DeepSeek", value: "deepseek" }
1299
1487
  ]
1300
1488
  });
1301
- console.log(chalk7.white.bold("\nStep 2/3: Enter your API key\n"));
1489
+ console.log(chalk8.white.bold("\nStep 2/3: Enter your API key\n"));
1302
1490
  const keyUrls = {
1303
1491
  gemini: "https://aistudio.google.com/apikey",
1304
1492
  openrouter: "https://openrouter.ai/keys",
@@ -1306,7 +1494,7 @@ async function runOnboarding() {
1306
1494
  anthropic: "https://console.anthropic.com/",
1307
1495
  deepseek: "https://platform.deepseek.com/"
1308
1496
  };
1309
- console.log(chalk7.gray(`Get your key at: ${chalk7.cyan(keyUrls[provider])}
1497
+ console.log(chalk8.gray(`Get your key at: ${chalk8.cyan(keyUrls[provider])}
1310
1498
  `));
1311
1499
  const apiKey = await password2({
1312
1500
  message: `Enter your ${provider} API key:`,
@@ -1330,7 +1518,7 @@ async function runOnboarding() {
1330
1518
  break;
1331
1519
  }
1332
1520
  config.defaultProvider = provider;
1333
- console.log(chalk7.white.bold("\nStep 3/3: Choose your default model\n"));
1521
+ console.log(chalk8.white.bold("\nStep 3/3: Choose your default model\n"));
1334
1522
  const models = PROVIDER_MODELS[provider] || PROVIDER_MODELS.gemini;
1335
1523
  const model = await select2({
1336
1524
  message: "Select your default model:",
@@ -1338,18 +1526,18 @@ async function runOnboarding() {
1338
1526
  });
1339
1527
  config.defaultModel = model;
1340
1528
  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)}
1529
+ console.log(chalk8.green("\n\u2705 Setup complete!"));
1530
+ console.log(chalk8.gray(`Provider: ${chalk8.cyan(provider)}`));
1531
+ console.log(chalk8.gray(`Model: ${chalk8.cyan(model)}
1344
1532
  `));
1345
1533
  return { provider, apiKey, model };
1346
1534
  }
1347
1535
  async function selectModel() {
1348
1536
  const config = loadConfig();
1349
1537
  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")}
1538
+ console.log(chalk8.cyan.bold("\n\u{1F504} Change Model\n"));
1539
+ console.log(chalk8.gray(`Current provider: ${chalk8.cyan(provider)}`));
1540
+ console.log(chalk8.gray(`Current model: ${chalk8.cyan(config.defaultModel || "not set")}
1353
1541
  `));
1354
1542
  const changeProvider = await select2({
1355
1543
  message: "What would you like to do?",
@@ -1360,7 +1548,7 @@ async function selectModel() {
1360
1548
  ]
1361
1549
  });
1362
1550
  if (changeProvider === "cancel") {
1363
- console.log(chalk7.gray("\nCancelled.\n"));
1551
+ console.log(chalk8.gray("\nCancelled.\n"));
1364
1552
  return;
1365
1553
  }
1366
1554
  let selectedProvider = provider;
@@ -1383,7 +1571,7 @@ async function selectModel() {
1383
1571
  deepseek: config.deepseekApiKey
1384
1572
  };
1385
1573
  if (!keyMap[selectedProvider]) {
1386
- console.log(chalk7.yellow(`
1574
+ console.log(chalk8.yellow(`
1387
1575
  \u26A0\uFE0F No API key configured for ${selectedProvider}
1388
1576
  `));
1389
1577
  const apiKey = await password2({
@@ -1417,9 +1605,9 @@ async function selectModel() {
1417
1605
  });
1418
1606
  config.defaultModel = model;
1419
1607
  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)}
1608
+ console.log(chalk8.green("\n\u2705 Model changed!"));
1609
+ console.log(chalk8.gray(`Provider: ${chalk8.cyan(selectedProvider)}`));
1610
+ console.log(chalk8.gray(`Model: ${chalk8.cyan(model)}
1423
1611
  `));
1424
1612
  }
1425
1613
 
@@ -1427,165 +1615,6 @@ async function selectModel() {
1427
1615
  import { input as input5, confirm as confirm3 } from "@inquirer/prompts";
1428
1616
  import chalk13 from "chalk";
1429
1617
 
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
1618
  // src/actions/plan-executor.ts
1590
1619
  import { select as select4, confirm } from "@inquirer/prompts";
1591
1620
  import chalk11 from "chalk";
@@ -3165,10 +3194,37 @@ Target: ${env.workspaceRoot}`
3165
3194
  console.log(chalk11.green("\u2713 Trusted for this session\n"));
3166
3195
  }
3167
3196
  }
3168
- const approved = await confirm({
3169
- message: `Execute ${plan.actions.length} actions?`,
3170
- default: true
3197
+ const dangerousKeywords = ["rm", "rimraf", "del", "npm install", "pnpm install", "yarn install", "chmod", "chown"];
3198
+ const dangerousActions = plan.actions.filter((a) => {
3199
+ if (a.type !== "exec") return false;
3200
+ const cmd = a.argv.join(" ");
3201
+ return dangerousKeywords.some((k) => cmd.includes(k));
3171
3202
  });
3203
+ if (dangerousActions.length > 0) {
3204
+ ui.renderAlert(
3205
+ "\u26A0\uFE0F DANGEROUS COMMANDS DETECTED",
3206
+ dangerousActions.map((a) => `\u2022 ${a.argv.join(" ")}`).join("\n") + "\n\nThese commands can modify your system or delete files."
3207
+ );
3208
+ const allowDangerous = await confirm({
3209
+ message: "Are you SURE you want to execute these dangerous commands?",
3210
+ default: false
3211
+ });
3212
+ if (!allowDangerous) {
3213
+ console.log(chalk11.yellow("\n\u{1F44B} Execution cancelled for safety.\n"));
3214
+ return { success: false, results, filesCreated, commandsRun, errors: ["User cancelled dangerous commands"] };
3215
+ }
3216
+ }
3217
+ let approved = false;
3218
+ if (ui.flags.dryRun) {
3219
+ console.log(chalk11.yellow("\n\u{1F6A7} DRY RUN MODE ENABLED"));
3220
+ console.log(chalk11.gray(" No files will be written and no commands will be executed.\n"));
3221
+ approved = true;
3222
+ } else {
3223
+ approved = await confirm({
3224
+ message: `Execute ${plan.actions.length} actions?`,
3225
+ default: true
3226
+ });
3227
+ }
3172
3228
  if (!approved) {
3173
3229
  console.log(chalk11.yellow("\n\u{1F44B} Plan cancelled.\n"));
3174
3230
  return { success: false, results, filesCreated, commandsRun, errors: ["User cancelled"] };
@@ -3185,6 +3241,11 @@ Target: ${env.workspaceRoot}`
3185
3241
  for (let i = 0; i < plan.actions.length; i++) {
3186
3242
  const action = plan.actions[i];
3187
3243
  displayActionProgress(action, i, plan.actions.length);
3244
+ if (ui.flags.dryRun) {
3245
+ console.log(chalk11.yellow(` [DRY RUN] Would execute: ${action.type} ${action.path || action.argv?.join(" ")}`));
3246
+ results.push({ action, success: true, output: "(dry run)" });
3247
+ continue;
3248
+ }
3188
3249
  const result = await executeAction(action);
3189
3250
  results.push(result);
3190
3251
  if (result.success) {
@@ -4040,8 +4101,8 @@ registerTool({
4040
4101
  { name: "path", type: "string", description: "Directory path", required: false, default: "." }
4041
4102
  ],
4042
4103
  execute: async (params) => {
4043
- const { readdirSync: readdirSync2, statSync: statSync2 } = await import("fs");
4044
- const { resolve: resolve5, join: join8 } = await import("path");
4104
+ const { readdirSync: readdirSync2, statSync: statSync3 } = await import("fs");
4105
+ const { resolve: resolve5, join: join9 } = await import("path");
4045
4106
  try {
4046
4107
  const dirPath = resolve5(process.cwd(), params.path || ".");
4047
4108
  const entries = readdirSync2(dirPath);
@@ -4049,7 +4110,7 @@ registerTool({
4049
4110
  const dirs = [];
4050
4111
  for (const entry of entries) {
4051
4112
  try {
4052
- const stat = statSync2(join8(dirPath, entry));
4113
+ const stat = statSync3(join9(dirPath, entry));
4053
4114
  if (stat.isDirectory()) {
4054
4115
  dirs.push(entry + "/");
4055
4116
  } else {
@@ -4649,25 +4710,183 @@ function handleError(error) {
4649
4710
  }
4650
4711
  }
4651
4712
 
4713
+ // src/commands/doctor.ts
4714
+ import chalk14 from "chalk";
4715
+ import { existsSync as existsSync11, statSync as statSync2 } from "fs";
4716
+ import { homedir as homedir7, platform as platform2 } from "os";
4717
+ import { join as join8 } from "path";
4718
+ async function runDoctor() {
4719
+ console.log("");
4720
+ ui.renderBox("AGDI DOCTOR", "Running diagnostics...", "info");
4721
+ console.log("");
4722
+ const results = [];
4723
+ const nodeVersion = process.version;
4724
+ const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0], 10);
4725
+ if (majorVersion >= 18) {
4726
+ results.push({
4727
+ name: "Node.js Version",
4728
+ status: "pass",
4729
+ message: `${nodeVersion} (\u226518 required)`
4730
+ });
4731
+ } else {
4732
+ results.push({
4733
+ name: "Node.js Version",
4734
+ status: "fail",
4735
+ message: `${nodeVersion} - Node.js 18+ required!`
4736
+ });
4737
+ }
4738
+ const configDir = join8(homedir7(), ".agdi");
4739
+ const configPath = join8(configDir, "config.json");
4740
+ if (existsSync11(configPath)) {
4741
+ if (platform2() !== "win32") {
4742
+ try {
4743
+ const stats = statSync2(configPath);
4744
+ const mode = (stats.mode & parseInt("777", 8)).toString(8);
4745
+ if (mode === "600") {
4746
+ results.push({
4747
+ name: "Config Permissions",
4748
+ status: "pass",
4749
+ message: `${configPath} (mode: 600)`
4750
+ });
4751
+ } else {
4752
+ results.push({
4753
+ name: "Config Permissions",
4754
+ status: "warn",
4755
+ message: `${configPath} (mode: ${mode}) - Should be 600!`
4756
+ });
4757
+ }
4758
+ } catch {
4759
+ results.push({
4760
+ name: "Config Permissions",
4761
+ status: "warn",
4762
+ message: "Could not check permissions"
4763
+ });
4764
+ }
4765
+ } else {
4766
+ results.push({
4767
+ name: "Config File",
4768
+ status: "pass",
4769
+ message: `${configPath} exists`
4770
+ });
4771
+ }
4772
+ } else {
4773
+ results.push({
4774
+ name: "Config File",
4775
+ status: "warn",
4776
+ message: "No config found. Run: agdi auth"
4777
+ });
4778
+ }
4779
+ const activeConfig = getActiveProvider();
4780
+ if (activeConfig) {
4781
+ const keyPreview = activeConfig.apiKey.slice(0, 8) + "..." + activeConfig.apiKey.slice(-4);
4782
+ results.push({
4783
+ name: "API Key",
4784
+ status: "pass",
4785
+ message: `${activeConfig.provider} (${keyPreview})`
4786
+ });
4787
+ results.push({
4788
+ name: "Active Model",
4789
+ status: "pass",
4790
+ message: activeConfig.model
4791
+ });
4792
+ } else {
4793
+ results.push({
4794
+ name: "API Key",
4795
+ status: "fail",
4796
+ message: "No API key configured. Run: agdi auth"
4797
+ });
4798
+ }
4799
+ const auditPath = join8(configDir, "audit.jsonl");
4800
+ if (existsSync11(auditPath)) {
4801
+ results.push({
4802
+ name: "Audit Log",
4803
+ status: "pass",
4804
+ message: `Logging to ${auditPath}`
4805
+ });
4806
+ } else {
4807
+ results.push({
4808
+ name: "Audit Log",
4809
+ status: "warn",
4810
+ message: "No audit log found (will be created on first action)"
4811
+ });
4812
+ }
4813
+ const trustPath = join8(configDir, "trusted-workspaces.json");
4814
+ if (existsSync11(trustPath)) {
4815
+ results.push({
4816
+ name: "Trust Store",
4817
+ status: "pass",
4818
+ message: "Workspace trust store found"
4819
+ });
4820
+ } else {
4821
+ results.push({
4822
+ name: "Trust Store",
4823
+ status: "warn",
4824
+ message: "No trusted workspaces yet"
4825
+ });
4826
+ }
4827
+ console.log(chalk14.bold("Diagnostic Results:\n"));
4828
+ for (const result of results) {
4829
+ let icon;
4830
+ let color;
4831
+ switch (result.status) {
4832
+ case "pass":
4833
+ icon = "\u2705";
4834
+ color = chalk14.green;
4835
+ break;
4836
+ case "warn":
4837
+ icon = "\u26A0\uFE0F";
4838
+ color = chalk14.yellow;
4839
+ break;
4840
+ case "fail":
4841
+ icon = "\u274C";
4842
+ color = chalk14.red;
4843
+ break;
4844
+ }
4845
+ console.log(` ${icon} ${chalk14.bold(result.name)}`);
4846
+ console.log(` ${color(result.message)}
4847
+ `);
4848
+ }
4849
+ const passed = results.filter((r) => r.status === "pass").length;
4850
+ const warnings = results.filter((r) => r.status === "warn").length;
4851
+ const failed = results.filter((r) => r.status === "fail").length;
4852
+ console.log(chalk14.gray("\u2500".repeat(50)));
4853
+ console.log(`
4854
+ ${chalk14.bold("Summary:")} ${chalk14.green(passed + " passed")}, ${chalk14.yellow(warnings + " warnings")}, ${chalk14.red(failed + " failed")}
4855
+ `);
4856
+ if (failed > 0) {
4857
+ console.log(chalk14.red(" \u26A0\uFE0F There are critical issues that need attention.\n"));
4858
+ } else if (warnings > 0) {
4859
+ console.log(chalk14.yellow(" \u2139\uFE0F There are some warnings, but Agdi should work.\n"));
4860
+ } else {
4861
+ console.log(chalk14.green(" \u{1F389} All checks passed! Agdi is ready to use.\n"));
4862
+ }
4863
+ }
4864
+
4652
4865
  // src/index.ts
4653
4866
  var BANNER = `
4654
- ${chalk14.cyan(` ___ __ _ `)}
4655
- ${chalk14.cyan(` / | ____ _____/ /(_) `)}
4656
- ${chalk14.cyan(` / /| | / __ \`/ __ // / `)}
4657
- ${chalk14.cyan(` / ___ |/ /_/ / /_/ // / `)}
4658
- ${chalk14.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
4659
- ${chalk14.cyan(` /____/ `)}
4867
+ ${chalk15.cyan(` ___ __ _ `)}
4868
+ ${chalk15.cyan(` / | ____ _____/ /(_) `)}
4869
+ ${chalk15.cyan(` / /| | / __ \`/ __ // / `)}
4870
+ ${chalk15.cyan(` / ___ |/ /_/ / /_/ // / `)}
4871
+ ${chalk15.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
4872
+ ${chalk15.cyan(` /____/ `)}
4660
4873
  `;
4661
4874
  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)");
4875
+ program.name("agdi").description(chalk15.cyan("\u{1F680} AI-powered coding assistant")).version("2.8.1").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
4876
  program.hook("preAction", (thisCommand) => {
4664
4877
  const opts = thisCommand.opts();
4665
4878
  if (opts.yes) {
4666
4879
  ui.setFlags({ yes: true, headless: true });
4667
4880
  }
4881
+ if (opts.minimal) {
4882
+ ui.setFlags({ minimal: true });
4883
+ }
4884
+ if (opts.dryRun) {
4885
+ ui.setFlags({ dryRun: true });
4886
+ }
4668
4887
  });
4669
4888
  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");
4889
+ 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
4890
  });
4672
4891
  program.action(async () => {
4673
4892
  try {
@@ -4678,8 +4897,15 @@ program.action(async () => {
4678
4897
  await startCodingMode();
4679
4898
  } catch (error) {
4680
4899
  if (error.name === "ExitPromptError") {
4681
- console.log(chalk14.gray("\n\n\u{1F44B} Goodbye!\n"));
4682
- ui.safeExit(0);
4900
+ console.log(chalk15.gray("\n\n\u{1F44B} Goodbye!\n"));
4901
+ try {
4902
+ ui.safeExit(0);
4903
+ } catch {
4904
+ }
4905
+ return;
4906
+ }
4907
+ if (error.name === "GracefulExitError") {
4908
+ return;
4683
4909
  }
4684
4910
  throw error;
4685
4911
  }
@@ -4693,8 +4919,15 @@ program.command("auth").description("Configure API keys").option("--status", "Sh
4693
4919
  }
4694
4920
  } catch (error) {
4695
4921
  if (error.name === "ExitPromptError") {
4696
- console.log(chalk14.gray("\n\n\u{1F44B} Cancelled.\n"));
4697
- ui.safeExit(0);
4922
+ console.log(chalk15.gray("\n\n\u{1F44B} Cancelled.\n"));
4923
+ try {
4924
+ ui.safeExit(0);
4925
+ } catch {
4926
+ }
4927
+ return;
4928
+ }
4929
+ if (error.name === "GracefulExitError") {
4930
+ return;
4698
4931
  }
4699
4932
  throw error;
4700
4933
  }
@@ -4704,8 +4937,15 @@ program.command("model").alias("models").description("Change AI model").action(a
4704
4937
  await selectModel();
4705
4938
  } catch (error) {
4706
4939
  if (error.name === "ExitPromptError") {
4707
- console.log(chalk14.gray("\n\n\u{1F44B} Cancelled.\n"));
4708
- ui.safeExit(0);
4940
+ console.log(chalk15.gray("\n\n\u{1F44B} Cancelled.\n"));
4941
+ try {
4942
+ ui.safeExit(0);
4943
+ } catch {
4944
+ }
4945
+ return;
4946
+ }
4947
+ if (error.name === "GracefulExitError") {
4948
+ return;
4709
4949
  }
4710
4950
  throw error;
4711
4951
  }
@@ -4718,8 +4958,15 @@ program.command("chat").description("Start a chat session").action(async () => {
4718
4958
  await startChat();
4719
4959
  } catch (error) {
4720
4960
  if (error.name === "ExitPromptError") {
4721
- console.log(chalk14.gray("\n\n\u{1F44B} Goodbye!\n"));
4722
- ui.safeExit(0);
4961
+ console.log(chalk15.gray("\n\n\u{1F44B} Goodbye!\n"));
4962
+ try {
4963
+ ui.safeExit(0);
4964
+ } catch {
4965
+ }
4966
+ return;
4967
+ }
4968
+ if (error.name === "GracefulExitError") {
4969
+ return;
4723
4970
  }
4724
4971
  throw error;
4725
4972
  }
@@ -4729,20 +4976,27 @@ program.command("run [directory]").description("Run a generated project").action
4729
4976
  await runProject(directory);
4730
4977
  } catch (error) {
4731
4978
  if (error.name === "ExitPromptError") {
4732
- console.log(chalk14.gray("\n\n\u{1F44B} Cancelled.\n"));
4733
- ui.safeExit(0);
4979
+ console.log(chalk15.gray("\n\n\u{1F44B} Cancelled.\n"));
4980
+ try {
4981
+ ui.safeExit(0);
4982
+ } catch {
4983
+ }
4984
+ return;
4985
+ }
4986
+ if (error.name === "GracefulExitError") {
4987
+ return;
4734
4988
  }
4735
4989
  throw error;
4736
4990
  }
4737
4991
  });
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) => {
4992
+ 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
4993
  try {
4740
4994
  if (needsOnboarding()) {
4741
4995
  await runOnboarding();
4742
4996
  }
4743
4997
  const activeConfig = getActiveProvider();
4744
4998
  if (!activeConfig) {
4745
- console.log(chalk14.red("\u274C No API key configured. Run: agdi auth"));
4999
+ console.log(chalk15.red("\u274C No API key configured. Run: agdi auth"));
4746
5000
  return;
4747
5001
  }
4748
5002
  const spinner = ora5("Generating application...").start();
@@ -4754,31 +5008,50 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
4754
5008
  const pm = new ProjectManager();
4755
5009
  pm.create(options.output.replace("./", ""), prompt);
4756
5010
  const { plan, files } = await generateApp(prompt, llm, (step, file) => {
4757
- spinner.text = file ? `${step} ${chalk14.gray(file)}` : step;
5011
+ spinner.text = file ? `${step} ${chalk15.gray(file)}` : step;
4758
5012
  });
4759
5013
  pm.updateFiles(files);
4760
5014
  pm.updateDependencies(plan.dependencies);
5015
+ if (options.dryRun || ui.flags.dryRun) {
5016
+ spinner.stop();
5017
+ console.log(chalk15.cyan.bold("\n\u{1F6A7} DRY RUN SUMMARY\n"));
5018
+ console.log(chalk15.gray(`Project: ${plan.name}
5019
+ `));
5020
+ console.log(chalk15.cyan("Files to be created:"));
5021
+ files.forEach((f) => console.log(chalk15.gray(` \u{1F4C4} ${f.path}`)));
5022
+ console.log(chalk15.cyan("\nDependencies:"));
5023
+ console.log(chalk15.gray(` \u{1F4E6} ${plan.dependencies.join(", ")}`));
5024
+ console.log(chalk15.green("\n\u2713 Dry run complete. No files written.\n"));
5025
+ return;
5026
+ }
4761
5027
  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"));
5028
+ spinner.succeed(chalk15.green("App generated!"));
5029
+ console.log(chalk15.gray(`
5030
+ \u{1F4C1} Created ${files.length} files in ${chalk15.cyan(options.output)}`));
5031
+ console.log(chalk15.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
4766
5032
  } catch (error) {
4767
5033
  spinner.fail("Generation failed");
4768
5034
  const msg = error instanceof Error ? error.message : String(error);
4769
5035
  if (msg.includes("429") || msg.includes("quota")) {
4770
- console.log(chalk14.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
5036
+ console.log(chalk15.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
4771
5037
  } else if (msg.includes("401") || msg.includes("403")) {
4772
- console.log(chalk14.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
5038
+ console.log(chalk15.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
4773
5039
  } else {
4774
- console.error(chalk14.red("\n" + msg + "\n"));
5040
+ console.error(chalk15.red("\n" + msg + "\n"));
4775
5041
  }
4776
5042
  ui.safeExit(1);
4777
5043
  }
4778
5044
  } catch (error) {
4779
5045
  if (error.name === "ExitPromptError") {
4780
- console.log(chalk14.gray("\n\n\u{1F44B} Cancelled.\n"));
4781
- ui.safeExit(0);
5046
+ console.log(chalk15.gray("\n\n\u{1F44B} Cancelled.\n"));
5047
+ try {
5048
+ ui.safeExit(0);
5049
+ } catch {
5050
+ }
5051
+ return;
5052
+ }
5053
+ if (error.name === "GracefulExitError") {
5054
+ return;
4782
5055
  }
4783
5056
  throw error;
4784
5057
  }
@@ -4786,11 +5059,11 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
4786
5059
  program.command("config").description("Show configuration").action(async () => {
4787
5060
  const config = loadConfig();
4788
5061
  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"));
5062
+ console.log(chalk15.cyan.bold("\n\u2699\uFE0F Configuration\n"));
5063
+ console.log(chalk15.gray(" Provider: ") + chalk15.cyan(config.defaultProvider || "not set"));
5064
+ console.log(chalk15.gray(" Model: ") + chalk15.cyan(config.defaultModel || "not set"));
5065
+ console.log(chalk15.gray(" Config: ") + chalk15.gray("~/.agdi/config.json"));
5066
+ console.log(chalk15.cyan.bold("\n\u{1F510} API Keys\n"));
4794
5067
  const keys = [
4795
5068
  ["Gemini", config.geminiApiKey],
4796
5069
  ["OpenRouter", config.openrouterApiKey],
@@ -4799,9 +5072,18 @@ program.command("config").description("Show configuration").action(async () => {
4799
5072
  ["DeepSeek", config.deepseekApiKey]
4800
5073
  ];
4801
5074
  for (const [name, key] of keys) {
4802
- const status = key ? chalk14.green("\u2713") : chalk14.gray("\u2717");
5075
+ const status = key ? chalk15.green("\u2713") : chalk15.gray("\u2717");
4803
5076
  console.log(` ${status} ${name}`);
4804
5077
  }
4805
5078
  console.log("");
5079
+ console.log("");
5080
+ });
5081
+ program.command("doctor").alias("doc").description("Run self-diagnosis checks").action(async () => {
5082
+ try {
5083
+ await runDoctor();
5084
+ } catch (error) {
5085
+ console.error(chalk15.red("Diagnostic failed: " + error));
5086
+ ui.safeExit(1);
5087
+ }
4806
5088
  });
4807
5089
  program.parse();