agdi 2.2.3 → 2.4.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 (3) hide show
  1. package/README.md +321 -93
  2. package/dist/index.js +1341 -75
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,9 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/index.ts
4
10
  import { Command } from "commander";
5
- import chalk12 from "chalk";
6
- import ora4 from "ora";
11
+ import chalk13 from "chalk";
12
+ import ora5 from "ora";
7
13
 
8
14
  // src/core/llm/index.ts
9
15
  var PuterProvider = class {
@@ -46,6 +52,38 @@ var PuterProvider = class {
46
52
  usage: typeof data !== "string" ? data.usage : void 0
47
53
  };
48
54
  }
55
+ async chat(messages) {
56
+ const response = await fetch("https://api.puter.com/ai/chat", {
57
+ method: "POST",
58
+ headers: {
59
+ "Content-Type": "application/json"
60
+ },
61
+ body: JSON.stringify({
62
+ model: this.model,
63
+ messages: messages.map((m) => ({ role: m.role, content: m.content }))
64
+ })
65
+ });
66
+ if (!response.ok) {
67
+ throw new Error(`Puter API error: ${response.status} ${response.statusText}`);
68
+ }
69
+ const data = await response.json();
70
+ let text = "";
71
+ if (typeof data === "string") {
72
+ text = data;
73
+ } else if (data.message?.content) {
74
+ if (Array.isArray(data.message.content)) {
75
+ text = data.message.content.map((c) => c.text || "").join("");
76
+ } else {
77
+ text = data.message.content;
78
+ }
79
+ } else if (data.choices?.[0]?.message?.content) {
80
+ text = data.choices[0].message.content;
81
+ }
82
+ return {
83
+ text,
84
+ usage: typeof data !== "string" ? data.usage : void 0
85
+ };
86
+ }
49
87
  };
50
88
  var GeminiProvider = class {
51
89
  config;
@@ -71,6 +109,28 @@ var GeminiProvider = class {
71
109
  usage: void 0
72
110
  };
73
111
  }
112
+ async chat(messages) {
113
+ const { GoogleGenAI } = await import("@google/genai");
114
+ const ai = new GoogleGenAI({ apiKey: this.config.apiKey });
115
+ const contents = messages.map((m) => {
116
+ if (m.role === "system") {
117
+ return { role: "user", parts: [{ text: m.content }] };
118
+ }
119
+ return {
120
+ role: m.role === "assistant" ? "model" : "user",
121
+ parts: [{ text: m.content }]
122
+ };
123
+ });
124
+ const response = await ai.models.generateContent({
125
+ model: this.config.model || "gemini-2.5-flash",
126
+ contents
127
+ });
128
+ const text = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
129
+ return {
130
+ text,
131
+ usage: void 0
132
+ };
133
+ }
74
134
  };
75
135
  var OpenRouterProvider = class {
76
136
  config;
@@ -107,6 +167,33 @@ var OpenRouterProvider = class {
107
167
  } : void 0
108
168
  };
109
169
  }
170
+ async chat(messages) {
171
+ const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
172
+ method: "POST",
173
+ headers: {
174
+ "Content-Type": "application/json",
175
+ "Authorization": `Bearer ${this.config.apiKey}`,
176
+ "HTTP-Referer": "https://agdi.dev",
177
+ "X-Title": "Agdi CLI"
178
+ },
179
+ body: JSON.stringify({
180
+ model: this.config.model || "anthropic/claude-3.5-sonnet",
181
+ messages: messages.map((m) => ({ role: m.role, content: m.content }))
182
+ })
183
+ });
184
+ if (!response.ok) {
185
+ const error = await response.text();
186
+ throw new Error(`OpenRouter API error: ${response.status} - ${error}`);
187
+ }
188
+ const data = await response.json();
189
+ return {
190
+ text: data.choices?.[0]?.message?.content || "",
191
+ usage: data.usage ? {
192
+ inputTokens: data.usage.prompt_tokens,
193
+ outputTokens: data.usage.completion_tokens
194
+ } : void 0
195
+ };
196
+ }
110
197
  };
111
198
  function createLLMProvider(provider, config) {
112
199
  switch (provider) {
@@ -364,6 +451,18 @@ var MALICIOUS_PATTERNS = [
364
451
  description: "Private key detected",
365
452
  severity: "critical"
366
453
  },
454
+ {
455
+ pattern: /sk-ant-[A-Za-z0-9-_]{95,}/g,
456
+ category: "secret",
457
+ description: "Anthropic API key detected",
458
+ severity: "critical"
459
+ },
460
+ {
461
+ pattern: /sk-ant-api[A-Za-z0-9-_]{90,}/g,
462
+ category: "secret",
463
+ description: "Anthropic API key (alt format) detected",
464
+ severity: "critical"
465
+ },
367
466
  // ==================== DANGEROUS CODE PATTERNS ====================
368
467
  {
369
468
  pattern: /\beval\s*\(/g,
@@ -475,6 +574,50 @@ var MALICIOUS_PATTERNS = [
475
574
  category: "dangerous",
476
575
  description: "Template literal with shell commands",
477
576
  severity: "medium"
577
+ },
578
+ // ==================== PROMPT INJECTION DEFENSE ====================
579
+ {
580
+ pattern: /ignore\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?|rules?)/gi,
581
+ category: "suspicious",
582
+ description: "Prompt injection: instruction override attempt",
583
+ severity: "critical"
584
+ },
585
+ {
586
+ pattern: /\[SYSTEM\]|\[INST\]|<\|im_start\|>|<\|endoftext\|>/gi,
587
+ category: "suspicious",
588
+ description: "Prompt injection: model control tokens",
589
+ severity: "critical"
590
+ },
591
+ {
592
+ pattern: /you\s+are\s+(now\s+)?(a|an|the)\s+\w+\s+(that|who|which)/gi,
593
+ category: "suspicious",
594
+ description: "Prompt injection: role override attempt",
595
+ severity: "high"
596
+ },
597
+ {
598
+ pattern: /disregard\s+(all\s+)?safety|bypass\s+(all\s+)?security|disable\s+(all\s+)?restrictions/gi,
599
+ category: "suspicious",
600
+ description: "Prompt injection: safety bypass attempt",
601
+ severity: "critical"
602
+ },
603
+ {
604
+ pattern: /\bexec\s*\(\s*[`'"].*?\$\(.*?\)/g,
605
+ category: "dangerous",
606
+ description: "Command injection via exec with substitution",
607
+ severity: "critical"
608
+ },
609
+ // ==================== NETWORK EXFILTRATION ====================
610
+ {
611
+ pattern: /\bfetch\s*\(\s*['"`]https?:\/\/[^'"`]*\.(ru|cn|tk|ml|ga)\//gi,
612
+ category: "suspicious",
613
+ description: "Request to suspicious TLD",
614
+ severity: "high"
615
+ },
616
+ {
617
+ pattern: /\bnew\s+WebSocket\s*\(\s*['"`]wss?:\/\/(?!localhost|127\.0\.0\.1)/g,
618
+ category: "suspicious",
619
+ description: "WebSocket to external server",
620
+ severity: "medium"
478
621
  }
479
622
  ];
480
623
  function scanCode(code, filename) {
@@ -1270,10 +1413,10 @@ async function selectModel() {
1270
1413
  `));
1271
1414
  }
1272
1415
 
1273
- // src/commands/codex.ts
1274
- import { input as input4 } from "@inquirer/prompts";
1275
- import chalk11 from "chalk";
1276
- import ora3 from "ora";
1416
+ // src/commands/agdi-dev.ts
1417
+ import { input as input5, confirm as confirm3 } from "@inquirer/prompts";
1418
+ import chalk12 from "chalk";
1419
+ import ora4 from "ora";
1277
1420
 
1278
1421
  // src/actions/plan-executor.ts
1279
1422
  import { select as select4, confirm } from "@inquirer/prompts";
@@ -1331,6 +1474,40 @@ function summarizePlan(plan) {
1331
1474
  riskTier: maxRiskTier
1332
1475
  };
1333
1476
  }
1477
+ function validateAction(action) {
1478
+ if (!action || typeof action !== "object") return null;
1479
+ const obj = action;
1480
+ const type = obj.type;
1481
+ if (type === "mkdir") {
1482
+ if (typeof obj.path !== "string" || !obj.path) return null;
1483
+ if (obj.path.includes("..") || obj.path.startsWith("/") || /^[A-Z]:/i.test(obj.path)) return null;
1484
+ return { type: "mkdir", path: obj.path };
1485
+ }
1486
+ if (type === "writeFile") {
1487
+ if (typeof obj.path !== "string" || !obj.path) return null;
1488
+ if (typeof obj.content !== "string") return null;
1489
+ if (obj.path.includes("..") || obj.path.startsWith("/") || /^[A-Z]:/i.test(obj.path)) return null;
1490
+ return { type: "writeFile", path: obj.path, content: obj.content };
1491
+ }
1492
+ if (type === "deleteFile") {
1493
+ if (typeof obj.path !== "string" || !obj.path) return null;
1494
+ if (obj.path.includes("..") || obj.path.startsWith("/") || /^[A-Z]:/i.test(obj.path)) return null;
1495
+ return { type: "deleteFile", path: obj.path };
1496
+ }
1497
+ if (type === "exec") {
1498
+ if (!Array.isArray(obj.argv) || obj.argv.length === 0) return null;
1499
+ if (!obj.argv.every((a) => typeof a === "string")) return null;
1500
+ const dangerous = ["sudo", "su", "rm -rf /", "format", "mkfs", "dd", ":(){"];
1501
+ const cmdStr = obj.argv.join(" ").toLowerCase();
1502
+ if (dangerous.some((d) => cmdStr.includes(d))) return null;
1503
+ return {
1504
+ type: "exec",
1505
+ argv: obj.argv,
1506
+ cwd: typeof obj.cwd === "string" ? obj.cwd : void 0
1507
+ };
1508
+ }
1509
+ return null;
1510
+ }
1334
1511
  function parseActionPlan(response) {
1335
1512
  try {
1336
1513
  const jsonMatch = response.match(/\{[\s\S]*"actions"[\s\S]*\}/);
@@ -1341,10 +1518,23 @@ function parseActionPlan(response) {
1341
1518
  if (!parsed.actions || !Array.isArray(parsed.actions)) {
1342
1519
  return null;
1343
1520
  }
1521
+ const validatedActions = [];
1522
+ for (const action of parsed.actions) {
1523
+ const validated = validateAction(action);
1524
+ if (!validated) {
1525
+ console.warn(`Invalid action rejected: ${JSON.stringify(action).slice(0, 100)}`);
1526
+ continue;
1527
+ }
1528
+ validatedActions.push(validated);
1529
+ }
1530
+ if (validatedActions.length === 0) {
1531
+ return null;
1532
+ }
1533
+ const projectName = typeof parsed.projectName === "string" ? parsed.projectName.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 50) : "generated-app";
1344
1534
  return {
1345
- projectName: parsed.projectName || "generated-app",
1346
- actions: parsed.actions,
1347
- nextSteps: parsed.nextSteps
1535
+ projectName,
1536
+ actions: validatedActions,
1537
+ nextSteps: typeof parsed.nextSteps === "string" ? parsed.nextSteps : void 0
1348
1538
  };
1349
1539
  } catch {
1350
1540
  return null;
@@ -1661,6 +1851,68 @@ async function deleteFileTool(path4) {
1661
1851
  return { success: false, error: msg };
1662
1852
  }
1663
1853
  }
1854
+ async function applyPatchTool(path4, unifiedDiff) {
1855
+ const validation = validatePath(path4);
1856
+ if (!validation.valid) {
1857
+ return { success: false, error: validation.error };
1858
+ }
1859
+ try {
1860
+ const current = existsSync3(validation.resolved) ? await readFile(validation.resolved, "utf-8") : "";
1861
+ const patched = applySimplePatch(current, unifiedDiff);
1862
+ if (patched === null) {
1863
+ return { success: false, error: "Failed to apply patch" };
1864
+ }
1865
+ await writeFile(validation.resolved, patched, "utf-8");
1866
+ logEvent({
1867
+ eventType: "command_result",
1868
+ command: `applyPatch ${path4}`,
1869
+ result: { exitCode: 0 },
1870
+ metadata: {
1871
+ tool: "applyPatchTool",
1872
+ path: validation.resolved,
1873
+ patchLength: unifiedDiff.length
1874
+ }
1875
+ });
1876
+ return { success: true };
1877
+ } catch (error) {
1878
+ const msg = error instanceof Error ? error.message : String(error);
1879
+ return { success: false, error: msg };
1880
+ }
1881
+ }
1882
+ function applySimplePatch(content, diff) {
1883
+ const lines = content.split("\n");
1884
+ const diffLines = diff.split("\n");
1885
+ let lineIndex = 0;
1886
+ const result = [];
1887
+ for (const diffLine of diffLines) {
1888
+ if (diffLine.startsWith("---") || diffLine.startsWith("+++") || diffLine.startsWith("@@")) {
1889
+ continue;
1890
+ }
1891
+ if (diffLine.startsWith("-")) {
1892
+ lineIndex++;
1893
+ } else if (diffLine.startsWith("+")) {
1894
+ result.push(diffLine.slice(1));
1895
+ } else if (diffLine.startsWith(" ") || diffLine === "") {
1896
+ if (lineIndex < lines.length) {
1897
+ result.push(lines[lineIndex]);
1898
+ lineIndex++;
1899
+ }
1900
+ }
1901
+ }
1902
+ while (lineIndex < lines.length) {
1903
+ result.push(lines[lineIndex]);
1904
+ lineIndex++;
1905
+ }
1906
+ return result.join("\n");
1907
+ }
1908
+ function resolvePath(path4) {
1909
+ const env = getEnvironment();
1910
+ return resolve(env.workspaceRoot, path4);
1911
+ }
1912
+ function fileExists(path4) {
1913
+ const resolved = resolvePath(path4);
1914
+ return existsSync3(resolved);
1915
+ }
1664
1916
 
1665
1917
  // src/security/permission-gate.ts
1666
1918
  import { resolve as resolve2, relative as relative2, isAbsolute as isAbsolute2 } from "path";
@@ -2455,6 +2707,11 @@ function saveTrustConfig(config) {
2455
2707
  function normalizePath(path4) {
2456
2708
  return resolve3(path4).toLowerCase().replace(/\\/g, "/");
2457
2709
  }
2710
+ function isWorkspaceTrusted(workspacePath) {
2711
+ const config = loadTrustConfig();
2712
+ const normalized = normalizePath(workspacePath);
2713
+ return config.trustedWorkspaces.some((w) => w.normalizedPath === normalized);
2714
+ }
2458
2715
  function trustWorkspace(workspacePath) {
2459
2716
  const config = loadTrustConfig();
2460
2717
  const normalized = normalizePath(workspacePath);
@@ -2468,6 +2725,63 @@ function trustWorkspace(workspacePath) {
2468
2725
  });
2469
2726
  saveTrustConfig(config);
2470
2727
  }
2728
+ async function promptWorkspaceTrust(workspacePath) {
2729
+ if (isWorkspaceTrusted(workspacePath)) {
2730
+ console.log(chalk9.green("\u2713 Workspace is trusted\n"));
2731
+ return "persistent";
2732
+ }
2733
+ console.log(chalk9.yellow("\n\u26A0\uFE0F Untrusted Workspace"));
2734
+ console.log(chalk9.gray(` ${workspacePath}
2735
+ `));
2736
+ console.log(chalk9.gray("Agdi can run commands in this workspace."));
2737
+ console.log(chalk9.gray("Do you trust the contents of this folder?\n"));
2738
+ const choice = await select3({
2739
+ message: "Trust this workspace?",
2740
+ choices: [
2741
+ {
2742
+ name: "Trust for this session only",
2743
+ value: "session",
2744
+ description: "Allow commands for this session, ask again next time"
2745
+ },
2746
+ {
2747
+ name: "Trust and remember",
2748
+ value: "persistent",
2749
+ description: "Always trust this workspace"
2750
+ },
2751
+ {
2752
+ name: "Exit (don't trust)",
2753
+ value: "exit",
2754
+ description: "Exit without granting trust"
2755
+ }
2756
+ ]
2757
+ });
2758
+ return choice;
2759
+ }
2760
+ async function handleTrustFlow(workspacePath) {
2761
+ const choice = await promptWorkspaceTrust(workspacePath);
2762
+ switch (choice) {
2763
+ case "session":
2764
+ updateEnvironment({ trustLevel: "session" });
2765
+ console.log(chalk9.green("\u2713 Trusted for this session\n"));
2766
+ return "session";
2767
+ case "persistent":
2768
+ trustWorkspace(workspacePath);
2769
+ updateEnvironment({ trustLevel: "persistent" });
2770
+ console.log(chalk9.green("\u2713 Workspace trusted and remembered\n"));
2771
+ return "persistent";
2772
+ case "exit":
2773
+ console.log(chalk9.yellow("\n\u{1F44B} Exiting. Workspace not trusted.\n"));
2774
+ return null;
2775
+ }
2776
+ }
2777
+ async function ensureTrusted(workspacePath) {
2778
+ if (isWorkspaceTrusted(workspacePath)) {
2779
+ updateEnvironment({ trustLevel: "persistent" });
2780
+ return true;
2781
+ }
2782
+ const result = await handleTrustFlow(workspacePath);
2783
+ return result !== null;
2784
+ }
2471
2785
 
2472
2786
  // src/actions/plan-executor.ts
2473
2787
  function displayPlanSummary(plan) {
@@ -2733,14 +3047,775 @@ async function parseAndExecutePlan(response) {
2733
3047
  return executePlan(plan);
2734
3048
  }
2735
3049
 
2736
- // src/commands/codex.ts
2737
- var CHAT_SYSTEM_PROMPT = `You are Agdi dev, an elite AI coding assistant. You help developers write code, debug issues, and build applications.
3050
+ // src/core/context-manager.ts
3051
+ import { readdirSync, readFileSync as readFileSync5, statSync, existsSync as existsSync6 } from "fs";
3052
+ import { join as join4, basename } from "path";
3053
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
3054
+ "node_modules",
3055
+ ".git",
3056
+ "dist",
3057
+ "build",
3058
+ ".next",
3059
+ ".nuxt",
3060
+ ".cache",
3061
+ "coverage",
3062
+ "__pycache__",
3063
+ ".venv",
3064
+ "venv",
3065
+ ".idea",
3066
+ ".vscode",
3067
+ ".turbo"
3068
+ ]);
3069
+ var IGNORED_FILES = /* @__PURE__ */ new Set([
3070
+ ".DS_Store",
3071
+ "Thumbs.db",
3072
+ ".env",
3073
+ ".env.local",
3074
+ "package-lock.json",
3075
+ "yarn.lock",
3076
+ "pnpm-lock.yaml"
3077
+ ]);
3078
+ var MAX_TREE_DEPTH = 4;
3079
+ var MAX_FILES_PER_DIR = 20;
3080
+ function getWorkspaceTree(rootDir, maxDepth = MAX_TREE_DEPTH) {
3081
+ const env = getEnvironment();
3082
+ const root = rootDir || env.workspaceRoot;
3083
+ if (!existsSync6(root)) {
3084
+ return "(workspace not found)";
3085
+ }
3086
+ const lines = [];
3087
+ lines.push(basename(root) + "/");
3088
+ buildTree(root, "", lines, 0, maxDepth);
3089
+ return lines.join("\n");
3090
+ }
3091
+ function buildTree(dir, prefix, lines, depth, maxDepth) {
3092
+ if (depth >= maxDepth) {
3093
+ lines.push(prefix + "\u2514\u2500\u2500 ...");
3094
+ return;
3095
+ }
3096
+ let entries;
3097
+ try {
3098
+ entries = readdirSync(dir);
3099
+ } catch {
3100
+ return;
3101
+ }
3102
+ const filtered = entries.filter((e) => {
3103
+ if (IGNORED_DIRS.has(e) || IGNORED_FILES.has(e)) return false;
3104
+ if (e.startsWith(".") && e !== ".github") return false;
3105
+ return true;
3106
+ });
3107
+ const dirs = [];
3108
+ const files = [];
3109
+ for (const entry of filtered) {
3110
+ const fullPath = join4(dir, entry);
3111
+ try {
3112
+ const stat = statSync(fullPath);
3113
+ if (stat.isDirectory()) {
3114
+ dirs.push(entry);
3115
+ } else {
3116
+ files.push(entry);
3117
+ }
3118
+ } catch {
3119
+ }
3120
+ }
3121
+ dirs.sort();
3122
+ files.sort();
3123
+ const allEntries = [...dirs.map((d) => ({ name: d, isDir: true })), ...files.map((f) => ({ name: f, isDir: false }))];
3124
+ const truncated = allEntries.length > MAX_FILES_PER_DIR;
3125
+ const toShow = truncated ? allEntries.slice(0, MAX_FILES_PER_DIR) : allEntries;
3126
+ toShow.forEach((entry, index) => {
3127
+ const isLast = index === toShow.length - 1 && !truncated;
3128
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
3129
+ const childPrefix = isLast ? " " : "\u2502 ";
3130
+ if (entry.isDir) {
3131
+ lines.push(prefix + connector + entry.name + "/");
3132
+ buildTree(join4(dir, entry.name), prefix + childPrefix, lines, depth + 1, maxDepth);
3133
+ } else {
3134
+ lines.push(prefix + connector + entry.name);
3135
+ }
3136
+ });
3137
+ if (truncated) {
3138
+ lines.push(prefix + "\u2514\u2500\u2500 ... (" + (allEntries.length - MAX_FILES_PER_DIR) + " more)");
3139
+ }
3140
+ }
3141
+ function getProjectConfig(rootDir) {
3142
+ const env = getEnvironment();
3143
+ const root = rootDir || env.workspaceRoot;
3144
+ const packageJsonPath = join4(root, "package.json");
3145
+ if (existsSync6(packageJsonPath)) {
3146
+ try {
3147
+ const content = readFileSync5(packageJsonPath, "utf-8");
3148
+ const pkg = JSON.parse(content);
3149
+ return {
3150
+ name: pkg.name || basename(root),
3151
+ version: pkg.version,
3152
+ description: pkg.description,
3153
+ dependencies: pkg.dependencies,
3154
+ devDependencies: pkg.devDependencies,
3155
+ scripts: pkg.scripts
3156
+ };
3157
+ } catch {
3158
+ return null;
3159
+ }
3160
+ }
3161
+ return null;
3162
+ }
3163
+ function buildWorkspaceContext(rootDir) {
3164
+ const env = getEnvironment();
3165
+ const root = rootDir || env.workspaceRoot;
3166
+ return {
3167
+ workspaceRoot: root,
3168
+ fileTree: getWorkspaceTree(root),
3169
+ projectConfig: getProjectConfig(root),
3170
+ gitStatus: null
3171
+ // Filled by GitManager
3172
+ };
3173
+ }
3174
+ function formatContextForPrompt(context) {
3175
+ const sections = [];
3176
+ sections.push("## Current Workspace");
3177
+ sections.push(`Path: ${context.workspaceRoot}`);
3178
+ if (context.projectConfig) {
3179
+ const cfg = context.projectConfig;
3180
+ sections.push(`
3181
+ Project: ${cfg.name}${cfg.version ? " v" + cfg.version : ""}`);
3182
+ if (cfg.description) {
3183
+ sections.push(`Description: ${cfg.description}`);
3184
+ }
3185
+ if (cfg.scripts) {
3186
+ const scriptList = Object.keys(cfg.scripts).slice(0, 5).join(", ");
3187
+ sections.push(`Scripts: ${scriptList}`);
3188
+ }
3189
+ }
3190
+ sections.push("\n## File Structure");
3191
+ sections.push("```");
3192
+ sections.push(context.fileTree);
3193
+ sections.push("```");
3194
+ if (context.gitStatus) {
3195
+ sections.push("\n## Git Status");
3196
+ sections.push(context.gitStatus);
3197
+ }
3198
+ return sections.join("\n");
3199
+ }
3200
+
3201
+ // src/core/git-manager.ts
3202
+ import { spawnSync } from "child_process";
3203
+ import { existsSync as existsSync7 } from "fs";
3204
+ import { join as join5 } from "path";
3205
+ function execGit(args, cwd) {
3206
+ const env = getEnvironment();
3207
+ const workDir = cwd || env.workspaceRoot;
3208
+ try {
3209
+ const result = spawnSync("git", args, {
3210
+ cwd: workDir,
3211
+ encoding: "utf-8",
3212
+ timeout: 1e4
3213
+ });
3214
+ if (result.error) {
3215
+ throw result.error;
3216
+ }
3217
+ return result.stdout?.trim() || "";
3218
+ } catch (error) {
3219
+ return "";
3220
+ }
3221
+ }
3222
+ function isGitRepo(dir) {
3223
+ const env = getEnvironment();
3224
+ const checkDir = dir || env.workspaceRoot;
3225
+ return existsSync7(join5(checkDir, ".git"));
3226
+ }
3227
+ function getStatus(cwd) {
3228
+ if (!isGitRepo(cwd)) {
3229
+ return {
3230
+ isGitRepo: false,
3231
+ branch: "",
3232
+ ahead: 0,
3233
+ behind: 0,
3234
+ staged: [],
3235
+ unstaged: [],
3236
+ untracked: [],
3237
+ hasConflicts: false
3238
+ };
3239
+ }
3240
+ const branch = execGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd) || "main";
3241
+ let ahead = 0;
3242
+ let behind = 0;
3243
+ const trackingInfo = execGit(["rev-list", "--left-right", "--count", `origin/${branch}...HEAD`], cwd);
3244
+ if (trackingInfo) {
3245
+ const [behindStr, aheadStr] = trackingInfo.split(/\s+/);
3246
+ behind = parseInt(behindStr, 10) || 0;
3247
+ ahead = parseInt(aheadStr, 10) || 0;
3248
+ }
3249
+ const statusOutput = execGit(["status", "--porcelain=v1"], cwd);
3250
+ const staged = [];
3251
+ const unstaged = [];
3252
+ const untracked = [];
3253
+ let hasConflicts = false;
3254
+ for (const line of statusOutput.split("\n")) {
3255
+ if (!line) continue;
3256
+ const indexStatus = line[0];
3257
+ const workTreeStatus = line[1];
3258
+ const filePath = line.slice(3).trim();
3259
+ if (indexStatus === "U" || workTreeStatus === "U" || indexStatus === "A" && workTreeStatus === "A" || indexStatus === "D" && workTreeStatus === "D") {
3260
+ hasConflicts = true;
3261
+ }
3262
+ if (indexStatus === "?" && workTreeStatus === "?") {
3263
+ untracked.push(filePath);
3264
+ continue;
3265
+ }
3266
+ if (indexStatus !== " " && indexStatus !== "?") {
3267
+ staged.push({
3268
+ path: filePath,
3269
+ status: parseStatus(indexStatus)
3270
+ });
3271
+ }
3272
+ if (workTreeStatus !== " " && workTreeStatus !== "?") {
3273
+ unstaged.push({
3274
+ path: filePath,
3275
+ status: parseStatus(workTreeStatus)
3276
+ });
3277
+ }
3278
+ }
3279
+ return {
3280
+ isGitRepo: true,
3281
+ branch,
3282
+ ahead,
3283
+ behind,
3284
+ staged,
3285
+ unstaged,
3286
+ untracked,
3287
+ hasConflicts
3288
+ };
3289
+ }
3290
+ function parseStatus(code) {
3291
+ switch (code) {
3292
+ case "A":
3293
+ return "added";
3294
+ case "M":
3295
+ return "modified";
3296
+ case "D":
3297
+ return "deleted";
3298
+ case "R":
3299
+ return "renamed";
3300
+ case "C":
3301
+ return "copied";
3302
+ default:
3303
+ return "modified";
3304
+ }
3305
+ }
3306
+ function getDiff(staged = false, cwd) {
3307
+ if (!isGitRepo(cwd)) {
3308
+ return { files: [], summary: "(not a git repository)" };
3309
+ }
3310
+ const args = staged ? ["diff", "--cached", "--stat"] : ["diff", "--stat"];
3311
+ const statOutput = execGit(args, cwd);
3312
+ const patchArgs = staged ? ["diff", "--cached"] : ["diff"];
3313
+ const patchOutput = execGit(patchArgs, cwd);
3314
+ const files = [];
3315
+ const statLines = statOutput.split("\n");
3316
+ for (const line of statLines) {
3317
+ const match = line.match(/^\s*(.+?)\s+\|\s+(\d+)\s*([+-]*)/);
3318
+ if (match) {
3319
+ const [, path4, changes, plusMinus] = match;
3320
+ const additions = (plusMinus.match(/\+/g) || []).length;
3321
+ const deletions = (plusMinus.match(/-/g) || []).length;
3322
+ const filePatches = patchOutput.split(/^diff --git/m);
3323
+ const filePatch = filePatches.find((p) => p.includes(path4.trim())) || "";
3324
+ files.push({
3325
+ path: path4.trim(),
3326
+ additions,
3327
+ deletions,
3328
+ patch: filePatch.slice(0, 2e3)
3329
+ // Limit patch size
3330
+ });
3331
+ }
3332
+ }
3333
+ const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
3334
+ const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
3335
+ const summary = `${files.length} file(s) changed, ${totalAdditions} insertion(s), ${totalDeletions} deletion(s)`;
3336
+ return { files, summary };
3337
+ }
3338
+ function formatStatusForPrompt(status) {
3339
+ if (!status.isGitRepo) {
3340
+ return "(not a git repository)";
3341
+ }
3342
+ const lines = [];
3343
+ lines.push(`Branch: ${status.branch}`);
3344
+ if (status.ahead > 0 || status.behind > 0) {
3345
+ lines.push(`Tracking: ${status.ahead} ahead, ${status.behind} behind origin`);
3346
+ }
3347
+ if (status.hasConflicts) {
3348
+ lines.push("\u26A0\uFE0F MERGE CONFLICTS DETECTED");
3349
+ }
3350
+ if (status.staged.length > 0) {
3351
+ lines.push(`
3352
+ Staged (${status.staged.length}):`);
3353
+ for (const f of status.staged.slice(0, 10)) {
3354
+ lines.push(` ${f.status[0].toUpperCase()} ${f.path}`);
3355
+ }
3356
+ if (status.staged.length > 10) {
3357
+ lines.push(` ... and ${status.staged.length - 10} more`);
3358
+ }
3359
+ }
3360
+ if (status.unstaged.length > 0) {
3361
+ lines.push(`
3362
+ Unstaged (${status.unstaged.length}):`);
3363
+ for (const f of status.unstaged.slice(0, 10)) {
3364
+ lines.push(` ${f.status[0].toUpperCase()} ${f.path}`);
3365
+ }
3366
+ if (status.unstaged.length > 10) {
3367
+ lines.push(` ... and ${status.unstaged.length - 10} more`);
3368
+ }
3369
+ }
3370
+ if (status.untracked.length > 0) {
3371
+ lines.push(`
3372
+ Untracked (${status.untracked.length}):`);
3373
+ for (const f of status.untracked.slice(0, 5)) {
3374
+ lines.push(` ? ${f}`);
3375
+ }
3376
+ if (status.untracked.length > 5) {
3377
+ lines.push(` ... and ${status.untracked.length - 5} more`);
3378
+ }
3379
+ }
3380
+ if (status.staged.length === 0 && status.unstaged.length === 0 && status.untracked.length === 0) {
3381
+ lines.push("\n\u2713 Working tree clean");
3382
+ }
3383
+ return lines.join("\n");
3384
+ }
3385
+ function formatDiffForPrompt(diff) {
3386
+ if (diff.files.length === 0) {
3387
+ return "(no changes)";
3388
+ }
3389
+ const lines = [];
3390
+ lines.push(diff.summary);
3391
+ lines.push("");
3392
+ for (const file of diff.files.slice(0, 5)) {
3393
+ lines.push(`### ${file.path}`);
3394
+ lines.push(`+${file.additions} -${file.deletions}`);
3395
+ if (file.patch) {
3396
+ lines.push("```diff");
3397
+ lines.push(file.patch.slice(0, 1e3));
3398
+ if (file.patch.length > 1e3) lines.push("... (truncated)");
3399
+ lines.push("```");
3400
+ }
3401
+ lines.push("");
3402
+ }
3403
+ if (diff.files.length > 5) {
3404
+ lines.push(`... and ${diff.files.length - 5} more files`);
3405
+ }
3406
+ return lines.join("\n");
3407
+ }
3408
+
3409
+ // src/core/conversation-manager.ts
3410
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3411
+ import { join as join6 } from "path";
3412
+ import { homedir as homedir5 } from "os";
3413
+ var AGDI_DIR = join6(homedir5(), ".agdi");
3414
+ var SESSIONS_DIR = join6(AGDI_DIR, "sessions");
3415
+ var MAX_CONTEXT_TOKENS = 32e3;
3416
+ var CHARS_PER_TOKEN = 4;
3417
+ var ConversationManager = class _ConversationManager {
3418
+ messages = [];
3419
+ sessionId;
3420
+ systemPrompt = "";
3421
+ constructor(sessionId) {
3422
+ this.sessionId = sessionId || this.generateSessionId();
3423
+ }
3424
+ /**
3425
+ * Set the system prompt (persists across conversation)
3426
+ */
3427
+ setSystemPrompt(prompt) {
3428
+ this.systemPrompt = prompt;
3429
+ }
3430
+ /**
3431
+ * Get the system prompt
3432
+ */
3433
+ getSystemPrompt() {
3434
+ return this.systemPrompt;
3435
+ }
3436
+ /**
3437
+ * Add a user message
3438
+ */
3439
+ addUserMessage(content) {
3440
+ this.messages.push({
3441
+ role: "user",
3442
+ content,
3443
+ timestamp: Date.now()
3444
+ });
3445
+ this.trimToContextLimit();
3446
+ }
3447
+ /**
3448
+ * Add an assistant response
3449
+ */
3450
+ addAssistantMessage(content) {
3451
+ this.messages.push({
3452
+ role: "assistant",
3453
+ content,
3454
+ timestamp: Date.now()
3455
+ });
3456
+ this.trimToContextLimit();
3457
+ }
3458
+ /**
3459
+ * Get all messages for LLM context
3460
+ */
3461
+ getMessages() {
3462
+ return [...this.messages];
3463
+ }
3464
+ /**
3465
+ * Get messages formatted for API calls
3466
+ */
3467
+ getMessagesForAPI() {
3468
+ const result = [];
3469
+ if (this.systemPrompt) {
3470
+ result.push({ role: "system", content: this.systemPrompt });
3471
+ }
3472
+ for (const msg of this.messages) {
3473
+ result.push({ role: msg.role, content: msg.content });
3474
+ }
3475
+ return result;
3476
+ }
3477
+ /**
3478
+ * Get message count
3479
+ */
3480
+ getMessageCount() {
3481
+ return this.messages.length;
3482
+ }
3483
+ /**
3484
+ * Get turn count (user messages)
3485
+ */
3486
+ getTurnCount() {
3487
+ return this.messages.filter((m) => m.role === "user").length;
3488
+ }
3489
+ /**
3490
+ * Clear conversation history
3491
+ */
3492
+ clear() {
3493
+ this.messages = [];
3494
+ }
3495
+ /**
3496
+ * Get session ID
3497
+ */
3498
+ getSessionId() {
3499
+ return this.sessionId;
3500
+ }
3501
+ /**
3502
+ * Get last N messages
3503
+ */
3504
+ getLastMessages(n) {
3505
+ return this.messages.slice(-n);
3506
+ }
3507
+ /**
3508
+ * Get conversation summary for display
3509
+ */
3510
+ getSummary() {
3511
+ const turns = this.getTurnCount();
3512
+ const tokens = this.estimateTokens();
3513
+ return `Session: ${this.sessionId} | ${turns} turns | ~${tokens} tokens`;
3514
+ }
3515
+ /**
3516
+ * Estimate token count
3517
+ */
3518
+ estimateTokens() {
3519
+ let chars = this.systemPrompt.length;
3520
+ for (const msg of this.messages) {
3521
+ chars += msg.content.length;
3522
+ }
3523
+ return Math.ceil(chars / CHARS_PER_TOKEN);
3524
+ }
3525
+ /**
3526
+ * Trim messages to stay within context limit
3527
+ */
3528
+ trimToContextLimit() {
3529
+ while (this.estimateTokens() > MAX_CONTEXT_TOKENS && this.messages.length > 2) {
3530
+ this.messages.shift();
3531
+ }
3532
+ }
3533
+ /**
3534
+ * Generate a unique session ID
3535
+ */
3536
+ generateSessionId() {
3537
+ const now = /* @__PURE__ */ new Date();
3538
+ const date = now.toISOString().split("T")[0];
3539
+ const random = Math.random().toString(36).substring(2, 8);
3540
+ return `${date}-${random}`;
3541
+ }
3542
+ // ==================== PERSISTENCE ====================
3543
+ /**
3544
+ * Save session to disk
3545
+ */
3546
+ save() {
3547
+ this.ensureDirectories();
3548
+ const session = {
3549
+ id: this.sessionId,
3550
+ messages: this.messages,
3551
+ createdAt: this.messages[0]?.timestamp || Date.now(),
3552
+ updatedAt: Date.now()
3553
+ };
3554
+ const filePath = join6(SESSIONS_DIR, `${this.sessionId}.json`);
3555
+ writeFileSync4(filePath, JSON.stringify(session, null, 2));
3556
+ }
3557
+ /**
3558
+ * Load session from disk
3559
+ */
3560
+ static load(sessionId) {
3561
+ const filePath = join6(SESSIONS_DIR, `${sessionId}.json`);
3562
+ if (!existsSync8(filePath)) {
3563
+ return null;
3564
+ }
3565
+ try {
3566
+ const content = readFileSync6(filePath, "utf-8");
3567
+ const session = JSON.parse(content);
3568
+ const manager = new _ConversationManager(session.id);
3569
+ manager.messages = session.messages;
3570
+ return manager;
3571
+ } catch {
3572
+ return null;
3573
+ }
3574
+ }
3575
+ /**
3576
+ * List available sessions
3577
+ */
3578
+ static listSessions() {
3579
+ if (!existsSync8(SESSIONS_DIR)) {
3580
+ return [];
3581
+ }
3582
+ const { readdirSync: readdirSync2 } = __require("fs");
3583
+ const files = readdirSync2(SESSIONS_DIR);
3584
+ return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", "")).sort().reverse();
3585
+ }
3586
+ /**
3587
+ * Ensure directories exist
3588
+ */
3589
+ ensureDirectories() {
3590
+ if (!existsSync8(AGDI_DIR)) {
3591
+ mkdirSync4(AGDI_DIR, { recursive: true });
3592
+ }
3593
+ if (!existsSync8(SESSIONS_DIR)) {
3594
+ mkdirSync4(SESSIONS_DIR, { recursive: true });
3595
+ }
3596
+ }
3597
+ };
3598
+ var currentConversation = null;
3599
+ function getConversation() {
3600
+ if (!currentConversation) {
3601
+ currentConversation = new ConversationManager();
3602
+ }
3603
+ return currentConversation;
3604
+ }
3605
+ function clearConversation() {
3606
+ if (currentConversation) {
3607
+ currentConversation.clear();
3608
+ }
3609
+ }
3610
+
3611
+ // src/core/file-editor.ts
3612
+ import { readFile as readFile2 } from "fs/promises";
3613
+ import { existsSync as existsSync9 } from "fs";
3614
+ import chalk11 from "chalk";
3615
+ import ora3 from "ora";
3616
+ import { input as input4, confirm as confirm2 } from "@inquirer/prompts";
3617
+ var EDIT_SYSTEM_PROMPT = `You are a surgical code editor. Given a file's content and an edit instruction, output ONLY a unified diff patch.
3618
+
3619
+ ## Output Format (STRICT)
3620
+ \`\`\`diff
3621
+ --- a/filename
3622
+ +++ b/filename
3623
+ @@ -start,count +start,count @@
3624
+ context line (unchanged)
3625
+ -removed line
3626
+ +added line
3627
+ context line (unchanged)
3628
+ \`\`\`
3629
+
3630
+ ## Rules
3631
+ 1. Output ONLY the diff block, no explanation before or after
3632
+ 2. Include 3 context lines around each change
3633
+ 3. Use proper unified diff format with @@ hunk headers
3634
+ 4. Preserve exact indentation (spaces/tabs)
3635
+ 5. Multiple changes = multiple @@ hunks
3636
+ 6. Line numbers in @@ must be accurate
3637
+
3638
+ ## Example
3639
+ Input: "Add a console.log at line 5"
3640
+ Output:
3641
+ \`\`\`diff
3642
+ --- a/index.ts
3643
+ +++ b/index.ts
3644
+ @@ -3,6 +3,7 @@
3645
+ import { foo } from './foo';
3646
+
3647
+ function main() {
3648
+ + console.log('Debug point');
3649
+ const result = foo();
3650
+ return result;
3651
+ }
3652
+ \`\`\``;
3653
+ async function readFileForEdit(filePath) {
3654
+ const resolved = resolvePath(filePath);
3655
+ if (!existsSync9(resolved)) {
3656
+ return null;
3657
+ }
3658
+ try {
3659
+ const content = await readFile2(resolved, "utf-8");
3660
+ const lines = content.split("\n");
3661
+ const numbered = lines.map((line, i) => `${String(i + 1).padStart(4, " ")} \u2502 ${line}`).join("\n");
3662
+ return { content, numbered };
3663
+ } catch {
3664
+ return null;
3665
+ }
3666
+ }
3667
+ function extractDiff(response) {
3668
+ const diffMatch = response.match(/```diff\n([\s\S]*?)```/);
3669
+ if (diffMatch) {
3670
+ return diffMatch[1].trim();
3671
+ }
3672
+ const codeMatch = response.match(/```\n([\s\S]*?)```/);
3673
+ if (codeMatch && codeMatch[1].includes("@@")) {
3674
+ return codeMatch[1].trim();
3675
+ }
3676
+ if (response.includes("@@") && (response.includes("---") || response.includes("+++"))) {
3677
+ return response.trim();
3678
+ }
3679
+ return null;
3680
+ }
3681
+ function previewDiff(diff) {
3682
+ console.log(chalk11.cyan.bold("\n\u{1F4DD} Proposed Changes:\n"));
3683
+ const lines = diff.split("\n");
3684
+ for (const line of lines) {
3685
+ if (line.startsWith("+++") || line.startsWith("---")) {
3686
+ console.log(chalk11.gray(line));
3687
+ } else if (line.startsWith("@@")) {
3688
+ console.log(chalk11.cyan(line));
3689
+ } else if (line.startsWith("+")) {
3690
+ console.log(chalk11.green(line));
3691
+ } else if (line.startsWith("-")) {
3692
+ console.log(chalk11.red(line));
3693
+ } else {
3694
+ console.log(chalk11.gray(line));
3695
+ }
3696
+ }
3697
+ console.log("");
3698
+ }
3699
+ function countChanges(diff) {
3700
+ const lines = diff.split("\n");
3701
+ let added = 0;
3702
+ let removed = 0;
3703
+ for (const line of lines) {
3704
+ if (line.startsWith("+") && !line.startsWith("+++")) {
3705
+ added++;
3706
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
3707
+ removed++;
3708
+ }
3709
+ }
3710
+ return { added, removed };
3711
+ }
3712
+ async function handleFileEdit(filePath, llm) {
3713
+ const env = getEnvironment();
3714
+ if (!fileExists(filePath)) {
3715
+ console.log(chalk11.red(`
3716
+ \u2717 File not found: ${filePath}
3717
+ `));
3718
+ return { success: false, error: "File not found" };
3719
+ }
3720
+ const fileData = await readFileForEdit(filePath);
3721
+ if (!fileData) {
3722
+ console.log(chalk11.red(`
3723
+ \u2717 Could not read file: ${filePath}
3724
+ `));
3725
+ return { success: false, error: "Could not read file" };
3726
+ }
3727
+ const previewLines = fileData.numbered.split("\n").slice(0, 20);
3728
+ console.log(chalk11.cyan.bold(`
3729
+ \u{1F4C4} ${filePath}
3730
+ `));
3731
+ console.log(chalk11.gray(previewLines.join("\n")));
3732
+ if (fileData.content.split("\n").length > 20) {
3733
+ console.log(chalk11.gray(` ... (${fileData.content.split("\n").length - 20} more lines)`));
3734
+ }
3735
+ console.log("");
3736
+ const instruction = await input4({
3737
+ message: chalk11.yellow("Describe the edit:")
3738
+ });
3739
+ if (!instruction.trim()) {
3740
+ console.log(chalk11.gray("\n(no instruction provided)\n"));
3741
+ return { success: false, error: "No instruction" };
3742
+ }
3743
+ const spinner = ora3("Generating edit...").start();
3744
+ try {
3745
+ const prompt = `File: ${filePath}
3746
+
3747
+ Content:
3748
+ \`\`\`
3749
+ ${fileData.content}
3750
+ \`\`\`
3751
+
3752
+ Edit instruction: ${instruction}
3753
+
3754
+ Generate the unified diff to make this change.`;
3755
+ const response = await llm.generate(prompt, EDIT_SYSTEM_PROMPT);
3756
+ spinner.stop();
3757
+ const diff = extractDiff(response.text);
3758
+ if (!diff) {
3759
+ console.log(chalk11.yellow("\n\u26A0\uFE0F Could not generate a valid diff.\n"));
3760
+ console.log(chalk11.gray("AI response:\n" + response.text.slice(0, 500)));
3761
+ return { success: false, error: "Invalid diff generated" };
3762
+ }
3763
+ previewDiff(diff);
3764
+ const changes = countChanges(diff);
3765
+ console.log(chalk11.gray(` ${chalk11.green(`+${changes.added}`)} additions, ${chalk11.red(`-${changes.removed}`)} deletions
3766
+ `));
3767
+ const shouldApply = await confirm2({
3768
+ message: "Apply these changes?",
3769
+ default: true
3770
+ });
3771
+ if (!shouldApply) {
3772
+ console.log(chalk11.gray("\n\u{1F44B} Edit cancelled.\n"));
3773
+ return { success: false, error: "Cancelled by user" };
3774
+ }
3775
+ const applySpinner = ora3("Applying changes...").start();
3776
+ const result = await applyPatchTool(filePath, diff);
3777
+ applySpinner.stop();
3778
+ if (result.success) {
3779
+ console.log(chalk11.green(`
3780
+ \u2713 Successfully edited ${filePath}
3781
+ `));
3782
+ logEvent({
3783
+ eventType: "command_result",
3784
+ command: `/edit ${filePath}`,
3785
+ result: { exitCode: 0 },
3786
+ metadata: {
3787
+ instruction,
3788
+ added: changes.added,
3789
+ removed: changes.removed
3790
+ }
3791
+ });
3792
+ return { success: true, linesChanged: changes.added + changes.removed };
3793
+ } else {
3794
+ console.log(chalk11.red(`
3795
+ \u2717 Failed to apply changes: ${result.error}
3796
+ `));
3797
+ return { success: false, error: result.error };
3798
+ }
3799
+ } catch (error) {
3800
+ spinner.stop();
3801
+ const msg = error instanceof Error ? error.message : String(error);
3802
+ console.log(chalk11.red(`
3803
+ \u2717 Error: ${msg}
3804
+ `));
3805
+ return { success: false, error: msg };
3806
+ }
3807
+ }
3808
+
3809
+ // src/commands/agdi-dev.ts
3810
+ var BASE_CHAT_PROMPT = `You are Agdi dev, an elite AI coding assistant. You help developers write code, debug issues, and build applications.
2738
3811
 
2739
3812
  ## Your Capabilities
2740
3813
  - Write complete, production-ready code
2741
3814
  - Debug and fix code issues
2742
3815
  - Explain code and architecture
2743
3816
  - Answer coding questions
3817
+ - Analyze git status and diffs
3818
+ - Generate meaningful commit messages
2744
3819
 
2745
3820
  ## Response Style
2746
3821
  - Be concise and direct
@@ -2753,6 +3828,17 @@ var CHAT_SYSTEM_PROMPT = `You are Agdi dev, an elite AI coding assistant. You he
2753
3828
  - Follow modern best practices
2754
3829
  - Include proper error handling
2755
3830
  - Write self-documenting code`;
3831
+ function buildContextAwarePrompt() {
3832
+ const context = buildWorkspaceContext();
3833
+ const gitStatus = getStatus();
3834
+ context.gitStatus = formatStatusForPrompt(gitStatus);
3835
+ const contextSection = formatContextForPrompt(context);
3836
+ return `${BASE_CHAT_PROMPT}
3837
+
3838
+ ---
3839
+
3840
+ ${contextSection}`;
3841
+ }
2756
3842
  var BUILD_SYSTEM_PROMPT = `You are Agdi dev, an AI coding assistant that generates applications.
2757
3843
 
2758
3844
  ## CRITICAL: Output Format
@@ -2789,29 +3875,36 @@ Instead, output a single JSON object with this exact structure:
2789
3875
  async function startCodingMode() {
2790
3876
  const activeConfig = getActiveProvider();
2791
3877
  if (!activeConfig) {
2792
- console.log(chalk11.red("\u274C No API key configured. Run: agdi"));
3878
+ console.log(chalk12.red("\u274C No API key configured. Run: agdi"));
2793
3879
  return;
2794
3880
  }
2795
3881
  const { provider, apiKey, model } = activeConfig;
2796
3882
  const config = loadConfig();
2797
3883
  const env = initSession();
2798
3884
  displaySessionHeader(env);
3885
+ const isTrusted = await ensureTrusted(env.workspaceRoot);
3886
+ if (!isTrusted) {
3887
+ process.exit(0);
3888
+ }
2799
3889
  logSessionStart(env.workspaceRoot, env.trustLevel);
2800
- console.log(chalk11.cyan.bold("\u26A1 Agdi dev\n"));
2801
- console.log(chalk11.gray(`Model: ${chalk11.cyan(model)}`));
2802
- console.log(chalk11.gray("Commands: /model, /chat, /build, /help, /exit\n"));
2803
- console.log(chalk11.gray("\u2500".repeat(50) + "\n"));
3890
+ console.log(chalk12.cyan.bold("\u26A1 Agdi dev\n"));
3891
+ console.log(chalk12.gray(`Model: ${chalk12.cyan(model)}`));
3892
+ console.log(chalk12.gray(`Workspace: ${chalk12.cyan(env.workspaceRoot)}`));
3893
+ console.log(chalk12.gray("Commands: /status, /diff, /commit, /build, /clear, /history, /model, /help, /exit\n"));
3894
+ console.log(chalk12.gray("\u2500".repeat(50) + "\n"));
2804
3895
  const pm = new ProjectManager();
2805
3896
  let llm = createLLMProvider(provider, { apiKey, model });
3897
+ const conversation = getConversation();
3898
+ conversation.setSystemPrompt(buildContextAwarePrompt());
2806
3899
  while (true) {
2807
3900
  try {
2808
- const userInput = await input4({
2809
- message: chalk11.green("\u2192")
3901
+ const userInput = await input5({
3902
+ message: chalk12.green("\u2192")
2810
3903
  });
2811
3904
  const trimmed = userInput.trim().toLowerCase();
2812
3905
  if (trimmed === "/exit" || trimmed === "exit" || trimmed === "quit") {
2813
3906
  logSessionEnd();
2814
- console.log(chalk11.gray("\n\u{1F44B} Goodbye!\n"));
3907
+ console.log(chalk12.gray("\n\u{1F44B} Goodbye!\n"));
2815
3908
  break;
2816
3909
  }
2817
3910
  if (trimmed === "/help") {
@@ -2826,22 +3919,66 @@ async function startCodingMode() {
2826
3919
  apiKey: newConfig.apiKey,
2827
3920
  model: newConfig.model
2828
3921
  });
2829
- console.log(chalk11.gray(`Now using: ${chalk11.cyan(newConfig.model)}
3922
+ console.log(chalk12.gray(`Now using: ${chalk12.cyan(newConfig.model)}
2830
3923
  `));
2831
3924
  }
2832
3925
  continue;
2833
3926
  }
2834
3927
  if (trimmed === "/chat") {
2835
- console.log(chalk11.gray("\nSwitching to chat mode. Type /code to return.\n"));
3928
+ console.log(chalk12.gray("\nSwitching to chat mode. Type /code to return.\n"));
2836
3929
  await chatMode(llm);
2837
3930
  continue;
2838
3931
  }
3932
+ if (trimmed === "/clear") {
3933
+ clearConversation();
3934
+ conversation.setSystemPrompt(buildContextAwarePrompt());
3935
+ console.log(chalk12.green("\n\u2713 Conversation cleared.\n"));
3936
+ continue;
3937
+ }
3938
+ if (trimmed === "/history") {
3939
+ const messages = conversation.getMessages();
3940
+ if (messages.length === 0) {
3941
+ console.log(chalk12.gray("\n(no conversation history)\n"));
3942
+ } else {
3943
+ console.log(chalk12.cyan.bold("\n\u{1F4DC} Conversation History\n"));
3944
+ console.log(chalk12.gray(conversation.getSummary()));
3945
+ console.log("");
3946
+ for (const msg of messages.slice(-6)) {
3947
+ const role = msg.role === "user" ? chalk12.green("You") : chalk12.cyan("AI");
3948
+ const preview = msg.content.slice(0, 80) + (msg.content.length > 80 ? "..." : "");
3949
+ console.log(` ${role}: ${chalk12.gray(preview)}`);
3950
+ }
3951
+ console.log("");
3952
+ }
3953
+ continue;
3954
+ }
3955
+ if (trimmed === "/status") {
3956
+ await handleGitStatus(llm);
3957
+ continue;
3958
+ }
3959
+ if (trimmed === "/diff") {
3960
+ await handleGitDiff(llm);
3961
+ continue;
3962
+ }
3963
+ if (trimmed === "/commit") {
3964
+ await handleGitCommit(llm);
3965
+ continue;
3966
+ }
3967
+ if (trimmed.startsWith("/edit ")) {
3968
+ const filePath = userInput.slice(6).trim();
3969
+ if (filePath) {
3970
+ await handleFileEdit(filePath, llm);
3971
+ } else {
3972
+ console.log(chalk12.yellow("\nUsage: /edit <file>\n"));
3973
+ }
3974
+ continue;
3975
+ }
2839
3976
  if (trimmed.startsWith("/build ") || trimmed.startsWith("build ")) {
2840
3977
  const prompt = userInput.replace(/^\/?build\s+/i, "").trim();
2841
3978
  if (prompt) {
2842
3979
  await buildAppWithPlan(prompt, llm);
2843
3980
  } else {
2844
- console.log(chalk11.yellow("\nUsage: /build <description>\n"));
3981
+ console.log(chalk12.yellow("\nUsage: /build <description>\n"));
2845
3982
  }
2846
3983
  continue;
2847
3984
  }
@@ -2853,9 +3990,17 @@ async function startCodingMode() {
2853
3990
  await buildAppWithPlan(userInput, llm);
2854
3991
  continue;
2855
3992
  }
2856
- const spinner = ora3("Thinking...").start();
3993
+ const spinner = ora4("Thinking...").start();
2857
3994
  try {
2858
- const response = await llm.generate(userInput, CHAT_SYSTEM_PROMPT);
3995
+ conversation.addUserMessage(userInput);
3996
+ let response;
3997
+ if (llm.chat) {
3998
+ response = await llm.chat(conversation.getMessagesForAPI());
3999
+ } else {
4000
+ const contextPrompt = buildContextAwarePrompt();
4001
+ response = await llm.generate(userInput, contextPrompt);
4002
+ }
4003
+ conversation.addAssistantMessage(response.text);
2859
4004
  spinner.stop();
2860
4005
  console.log("\n" + formatResponse(response.text) + "\n");
2861
4006
  } catch (error) {
@@ -2865,7 +4010,7 @@ async function startCodingMode() {
2865
4010
  } catch (error) {
2866
4011
  if (error.name === "ExitPromptError") {
2867
4012
  logSessionEnd();
2868
- console.log(chalk11.gray("\n\n\u{1F44B} Goodbye!\n"));
4013
+ console.log(chalk12.gray("\n\n\u{1F44B} Goodbye!\n"));
2869
4014
  process.exit(0);
2870
4015
  }
2871
4016
  throw error;
@@ -2873,13 +4018,13 @@ async function startCodingMode() {
2873
4018
  }
2874
4019
  }
2875
4020
  async function buildAppWithPlan(prompt, llm) {
2876
- const spinner = ora3("Generating action plan...").start();
4021
+ const spinner = ora4("Generating action plan...").start();
2877
4022
  try {
2878
4023
  const response = await llm.generate(prompt, BUILD_SYSTEM_PROMPT);
2879
4024
  spinner.stop();
2880
4025
  const result = await parseAndExecutePlan(response.text);
2881
4026
  if (!result) {
2882
- console.log(chalk11.yellow("\n\u26A0\uFE0F Model did not return an action plan. Showing response:\n"));
4027
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Model did not return an action plan. Showing response:\n"));
2883
4028
  console.log(formatResponse(response.text) + "\n");
2884
4029
  }
2885
4030
  } catch (error) {
@@ -2890,15 +4035,15 @@ async function buildAppWithPlan(prompt, llm) {
2890
4035
  async function chatMode(llm) {
2891
4036
  while (true) {
2892
4037
  try {
2893
- const userInput = await input4({
2894
- message: chalk11.blue("\u{1F4AC}")
4038
+ const userInput = await input5({
4039
+ message: chalk12.blue("\u{1F4AC}")
2895
4040
  });
2896
4041
  if (userInput.toLowerCase() === "/code" || userInput.toLowerCase() === "/exit") {
2897
- console.log(chalk11.gray("\nBack to Agdi dev mode.\n"));
4042
+ console.log(chalk12.gray("\nBack to Agdi dev mode.\n"));
2898
4043
  return;
2899
4044
  }
2900
4045
  if (!userInput.trim()) continue;
2901
- const spinner = ora3("...").start();
4046
+ const spinner = ora4("...").start();
2902
4047
  const response = await llm.generate(userInput, "You are a helpful assistant. Be friendly and concise.");
2903
4048
  spinner.stop();
2904
4049
  console.log("\n" + response.text + "\n");
@@ -2910,51 +4055,172 @@ async function chatMode(llm) {
2910
4055
  }
2911
4056
  }
2912
4057
  }
4058
+ async function handleGitStatus(llm) {
4059
+ if (!isGitRepo()) {
4060
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Not a git repository\n"));
4061
+ return;
4062
+ }
4063
+ const spinner = ora4("Analyzing git status...").start();
4064
+ try {
4065
+ const status = getStatus();
4066
+ const statusText = formatStatusForPrompt(status);
4067
+ const prompt = `Analyze this git status and provide a brief summary of the current state of the repository. What's staged, unstaged, and what should be done next?
4068
+
4069
+ ${statusText}`;
4070
+ const response = await llm.generate(prompt, BASE_CHAT_PROMPT);
4071
+ spinner.stop();
4072
+ console.log(chalk12.cyan.bold("\n\u{1F4CA} Git Status Analysis\n"));
4073
+ console.log(chalk12.gray(statusText));
4074
+ console.log(chalk12.cyan("\n\u2500\u2500\u2500 AI Analysis \u2500\u2500\u2500\n"));
4075
+ console.log(formatResponse(response.text) + "\n");
4076
+ } catch (error) {
4077
+ spinner.fail("Error analyzing status");
4078
+ handleError(error);
4079
+ }
4080
+ }
4081
+ async function handleGitDiff(llm) {
4082
+ if (!isGitRepo()) {
4083
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Not a git repository\n"));
4084
+ return;
4085
+ }
4086
+ const spinner = ora4("Analyzing changes...").start();
4087
+ try {
4088
+ const stagedDiff = getDiff(true);
4089
+ const unstagedDiff = getDiff(false);
4090
+ if (stagedDiff.files.length === 0 && unstagedDiff.files.length === 0) {
4091
+ spinner.stop();
4092
+ console.log(chalk12.gray("\n(no changes to analyze)\n"));
4093
+ return;
4094
+ }
4095
+ let diffContext = "";
4096
+ if (stagedDiff.files.length > 0) {
4097
+ diffContext += "## Staged Changes\n" + formatDiffForPrompt(stagedDiff) + "\n\n";
4098
+ }
4099
+ if (unstagedDiff.files.length > 0) {
4100
+ diffContext += "## Unstaged Changes\n" + formatDiffForPrompt(unstagedDiff);
4101
+ }
4102
+ const prompt = `Explain these code changes. What are the key modifications and their purpose?
4103
+
4104
+ ${diffContext}`;
4105
+ const response = await llm.generate(prompt, BASE_CHAT_PROMPT);
4106
+ spinner.stop();
4107
+ console.log(chalk12.cyan.bold("\n\u{1F50D} Diff Analysis\n"));
4108
+ console.log(formatResponse(response.text) + "\n");
4109
+ } catch (error) {
4110
+ spinner.fail("Error analyzing diff");
4111
+ handleError(error);
4112
+ }
4113
+ }
4114
+ async function handleGitCommit(llm) {
4115
+ if (!isGitRepo()) {
4116
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Not a git repository\n"));
4117
+ return;
4118
+ }
4119
+ const status = getStatus();
4120
+ if (status.staged.length === 0) {
4121
+ console.log(chalk12.yellow("\n\u26A0\uFE0F No staged changes. Stage some changes first with `git add`.\n"));
4122
+ return;
4123
+ }
4124
+ const spinner = ora4("Generating commit message...").start();
4125
+ try {
4126
+ const stagedDiff = getDiff(true);
4127
+ const diffText = formatDiffForPrompt(stagedDiff);
4128
+ const prompt = `Generate a concise, conventional commit message for these staged changes. Use the format: type(scope): description
4129
+
4130
+ Types: feat, fix, docs, style, refactor, test, chore
4131
+ Keep the message under 72 characters.
4132
+ Return ONLY the commit message, nothing else.
4133
+
4134
+ Changes:
4135
+ ${diffText}`;
4136
+ const response = await llm.generate(prompt, "You are a git commit message generator. Output ONLY the commit message, no explanation.");
4137
+ spinner.stop();
4138
+ const commitMessage = response.text.trim().split("\n")[0];
4139
+ console.log(chalk12.cyan.bold("\n\u{1F4AC} Generated Commit Message\n"));
4140
+ console.log(chalk12.white(` ${commitMessage}
4141
+ `));
4142
+ const shouldCommit = await confirm3({
4143
+ message: "Commit with this message?",
4144
+ default: true
4145
+ });
4146
+ if (shouldCommit) {
4147
+ const { execSync: execSync2 } = await import("child_process");
4148
+ const env = getEnvironment();
4149
+ try {
4150
+ execSync2(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
4151
+ cwd: env.workspaceRoot,
4152
+ stdio: "inherit"
4153
+ });
4154
+ console.log(chalk12.green("\n\u2713 Committed successfully!\n"));
4155
+ } catch (gitError) {
4156
+ console.log(chalk12.red("\n\u2717 Commit failed. Check git output above.\n"));
4157
+ }
4158
+ } else {
4159
+ console.log(chalk12.gray("\n\u{1F44B} Commit cancelled.\n"));
4160
+ }
4161
+ } catch (error) {
4162
+ spinner.fail("Error generating commit");
4163
+ handleError(error);
4164
+ }
4165
+ }
2913
4166
  function formatResponse(text) {
2914
4167
  return text.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => {
2915
- const header = lang ? chalk11.gray(`\u2500\u2500 ${lang} \u2500\u2500`) : chalk11.gray("\u2500\u2500 code \u2500\u2500");
4168
+ const header = lang ? chalk12.gray(`\u2500\u2500 ${lang} \u2500\u2500`) : chalk12.gray("\u2500\u2500 code \u2500\u2500");
2916
4169
  return `
2917
4170
  ${header}
2918
- ${chalk11.white(code.trim())}
2919
- ${chalk11.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
4171
+ ${chalk12.white(code.trim())}
4172
+ ${chalk12.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
2920
4173
  `;
2921
4174
  });
2922
4175
  }
2923
4176
  function showHelp() {
2924
- console.log(chalk11.cyan.bold("\n\u{1F4D6} Commands\n"));
2925
- console.log(chalk11.gray(" /build ") + "Generate and execute an application");
2926
- console.log(chalk11.gray(" /model ") + "Change AI model");
2927
- console.log(chalk11.gray(" /chat ") + "Switch to chat mode");
2928
- console.log(chalk11.gray(" /help ") + "Show this help");
2929
- console.log(chalk11.gray(" /exit ") + "Exit Agdi");
2930
- console.log(chalk11.gray("\n Or just type your coding question!\n"));
2931
- console.log(chalk11.gray('Tip: "Create a todo app" will generate & write files.\n'));
4177
+ console.log(chalk12.cyan.bold("\n\u{1F4D6} Commands\n"));
4178
+ console.log(chalk12.cyan(" Git Commands:"));
4179
+ console.log(chalk12.gray(" /status ") + "AI analysis of git status");
4180
+ console.log(chalk12.gray(" /diff ") + "AI explanation of current changes");
4181
+ console.log(chalk12.gray(" /commit ") + "Generate and run git commit");
4182
+ console.log("");
4183
+ console.log(chalk12.cyan(" Build Commands:"));
4184
+ console.log(chalk12.gray(" /build ") + "Generate and execute an application");
4185
+ console.log(chalk12.gray(" /edit ") + "AI-powered surgical file editing");
4186
+ console.log("");
4187
+ console.log(chalk12.cyan(" Conversation:"));
4188
+ console.log(chalk12.gray(" /clear ") + "Clear conversation history");
4189
+ console.log(chalk12.gray(" /history ") + "Show recent conversation");
4190
+ console.log("");
4191
+ console.log(chalk12.cyan(" General:"));
4192
+ console.log(chalk12.gray(" /model ") + "Change AI model");
4193
+ console.log(chalk12.gray(" /chat ") + "Switch to chat mode");
4194
+ console.log(chalk12.gray(" /help ") + "Show this help");
4195
+ console.log(chalk12.gray(" /exit ") + "Exit Agdi");
4196
+ console.log(chalk12.gray("\n Or just type your coding question!\n"));
4197
+ console.log(chalk12.gray('Tip: "Create a todo app" will generate & write files.\n'));
2932
4198
  }
2933
4199
  function handleError(error) {
2934
4200
  const msg = error instanceof Error ? error.message : String(error);
2935
4201
  if (msg.includes("429") || msg.includes("quota")) {
2936
- console.log(chalk11.yellow("\n\u26A0\uFE0F Quota exceeded. Run /model to switch.\n"));
4202
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Quota exceeded. Run /model to switch.\n"));
2937
4203
  } else if (msg.includes("401") || msg.includes("403")) {
2938
- console.log(chalk11.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
4204
+ console.log(chalk12.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
2939
4205
  } else {
2940
- console.log(chalk11.red("\n" + msg + "\n"));
4206
+ console.log(chalk12.red("\n" + msg + "\n"));
2941
4207
  }
2942
4208
  }
2943
4209
 
2944
4210
  // src/index.ts
2945
4211
  var BANNER = `
2946
- ${chalk12.cyan(` ___ __ _ `)}
2947
- ${chalk12.cyan(` / | ____ _____/ /(_) `)}
2948
- ${chalk12.cyan(` / /| | / __ \`/ __ // / `)}
2949
- ${chalk12.cyan(` / ___ |/ /_/ / /_/ // / `)}
2950
- ${chalk12.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
2951
- ${chalk12.cyan(` /____/ `)}
4212
+ ${chalk13.cyan(` ___ __ _ `)}
4213
+ ${chalk13.cyan(` / | ____ _____/ /(_) `)}
4214
+ ${chalk13.cyan(` / /| | / __ \`/ __ // / `)}
4215
+ ${chalk13.cyan(` / ___ |/ /_/ / /_/ // / `)}
4216
+ ${chalk13.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
4217
+ ${chalk13.cyan(` /____/ `)}
2952
4218
  `;
2953
4219
  var program = new Command();
2954
- program.name("agdi").description(chalk12.cyan("\u{1F680} AI-powered coding assistant")).version("2.2.2").configureHelp({
4220
+ program.name("agdi").description(chalk13.cyan("\u{1F680} AI-powered coding assistant")).version("2.2.2").configureHelp({
2955
4221
  // Show banner only when help is requested
2956
4222
  formatHelp: (cmd, helper) => {
2957
- return BANNER + "\n" + chalk12.gray(" The Open Source AI Architect") + "\n" + chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n") + "\n" + helper.formatHelp(cmd, helper);
4223
+ return BANNER + "\n" + chalk13.gray(" The Open Source AI Architect") + "\n" + chalk13.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n") + "\n" + helper.formatHelp(cmd, helper);
2958
4224
  }
2959
4225
  });
2960
4226
  program.action(async () => {
@@ -2965,7 +4231,7 @@ program.action(async () => {
2965
4231
  await startCodingMode();
2966
4232
  } catch (error) {
2967
4233
  if (error.name === "ExitPromptError") {
2968
- console.log(chalk12.gray("\n\n\u{1F44B} Goodbye!\n"));
4234
+ console.log(chalk13.gray("\n\n\u{1F44B} Goodbye!\n"));
2969
4235
  process.exit(0);
2970
4236
  }
2971
4237
  throw error;
@@ -2980,7 +4246,7 @@ program.command("auth").description("Configure API keys").option("--status", "Sh
2980
4246
  }
2981
4247
  } catch (error) {
2982
4248
  if (error.name === "ExitPromptError") {
2983
- console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
4249
+ console.log(chalk13.gray("\n\n\u{1F44B} Cancelled.\n"));
2984
4250
  process.exit(0);
2985
4251
  }
2986
4252
  throw error;
@@ -2991,7 +4257,7 @@ program.command("model").alias("models").description("Change AI model").action(a
2991
4257
  await selectModel();
2992
4258
  } catch (error) {
2993
4259
  if (error.name === "ExitPromptError") {
2994
- console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
4260
+ console.log(chalk13.gray("\n\n\u{1F44B} Cancelled.\n"));
2995
4261
  process.exit(0);
2996
4262
  }
2997
4263
  throw error;
@@ -3005,7 +4271,7 @@ program.command("chat").description("Start a chat session").action(async () => {
3005
4271
  await startChat();
3006
4272
  } catch (error) {
3007
4273
  if (error.name === "ExitPromptError") {
3008
- console.log(chalk12.gray("\n\n\u{1F44B} Goodbye!\n"));
4274
+ console.log(chalk13.gray("\n\n\u{1F44B} Goodbye!\n"));
3009
4275
  process.exit(0);
3010
4276
  }
3011
4277
  throw error;
@@ -3016,7 +4282,7 @@ program.command("run [directory]").description("Run a generated project").action
3016
4282
  await runProject(directory);
3017
4283
  } catch (error) {
3018
4284
  if (error.name === "ExitPromptError") {
3019
- console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
4285
+ console.log(chalk13.gray("\n\n\u{1F44B} Cancelled.\n"));
3020
4286
  process.exit(0);
3021
4287
  }
3022
4288
  throw error;
@@ -3029,10 +4295,10 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
3029
4295
  }
3030
4296
  const activeConfig = getActiveProvider();
3031
4297
  if (!activeConfig) {
3032
- console.log(chalk12.red("\u274C No API key configured. Run: agdi auth"));
4298
+ console.log(chalk13.red("\u274C No API key configured. Run: agdi auth"));
3033
4299
  return;
3034
4300
  }
3035
- const spinner = ora4("Generating application...").start();
4301
+ const spinner = ora5("Generating application...").start();
3036
4302
  try {
3037
4303
  const llm = createLLMProvider(activeConfig.provider, {
3038
4304
  apiKey: activeConfig.apiKey,
@@ -3041,30 +4307,30 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
3041
4307
  const pm = new ProjectManager();
3042
4308
  pm.create(options.output.replace("./", ""), prompt);
3043
4309
  const { plan, files } = await generateApp(prompt, llm, (step, file) => {
3044
- spinner.text = file ? `${step} ${chalk12.gray(file)}` : step;
4310
+ spinner.text = file ? `${step} ${chalk13.gray(file)}` : step;
3045
4311
  });
3046
4312
  pm.updateFiles(files);
3047
4313
  pm.updateDependencies(plan.dependencies);
3048
4314
  await writeProject(pm.get(), options.output);
3049
- spinner.succeed(chalk12.green("App generated!"));
3050
- console.log(chalk12.gray(`
3051
- \u{1F4C1} Created ${files.length} files in ${chalk12.cyan(options.output)}`));
3052
- console.log(chalk12.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
4315
+ spinner.succeed(chalk13.green("App generated!"));
4316
+ console.log(chalk13.gray(`
4317
+ \u{1F4C1} Created ${files.length} files in ${chalk13.cyan(options.output)}`));
4318
+ console.log(chalk13.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
3053
4319
  } catch (error) {
3054
4320
  spinner.fail("Generation failed");
3055
4321
  const msg = error instanceof Error ? error.message : String(error);
3056
4322
  if (msg.includes("429") || msg.includes("quota")) {
3057
- console.log(chalk12.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
4323
+ console.log(chalk13.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
3058
4324
  } else if (msg.includes("401") || msg.includes("403")) {
3059
- console.log(chalk12.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
4325
+ console.log(chalk13.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
3060
4326
  } else {
3061
- console.error(chalk12.red("\n" + msg + "\n"));
4327
+ console.error(chalk13.red("\n" + msg + "\n"));
3062
4328
  }
3063
4329
  process.exit(1);
3064
4330
  }
3065
4331
  } catch (error) {
3066
4332
  if (error.name === "ExitPromptError") {
3067
- console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
4333
+ console.log(chalk13.gray("\n\n\u{1F44B} Cancelled.\n"));
3068
4334
  process.exit(0);
3069
4335
  }
3070
4336
  throw error;
@@ -3073,11 +4339,11 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
3073
4339
  program.command("config").description("Show configuration").action(async () => {
3074
4340
  const config = loadConfig();
3075
4341
  const active = getActiveProvider();
3076
- console.log(chalk12.cyan.bold("\n\u2699\uFE0F Configuration\n"));
3077
- console.log(chalk12.gray(" Provider: ") + chalk12.cyan(config.defaultProvider || "not set"));
3078
- console.log(chalk12.gray(" Model: ") + chalk12.cyan(config.defaultModel || "not set"));
3079
- console.log(chalk12.gray(" Config: ") + chalk12.gray("~/.agdi/config.json"));
3080
- console.log(chalk12.cyan.bold("\n\u{1F510} API Keys\n"));
4342
+ console.log(chalk13.cyan.bold("\n\u2699\uFE0F Configuration\n"));
4343
+ console.log(chalk13.gray(" Provider: ") + chalk13.cyan(config.defaultProvider || "not set"));
4344
+ console.log(chalk13.gray(" Model: ") + chalk13.cyan(config.defaultModel || "not set"));
4345
+ console.log(chalk13.gray(" Config: ") + chalk13.gray("~/.agdi/config.json"));
4346
+ console.log(chalk13.cyan.bold("\n\u{1F510} API Keys\n"));
3081
4347
  const keys = [
3082
4348
  ["Gemini", config.geminiApiKey],
3083
4349
  ["OpenRouter", config.openrouterApiKey],
@@ -3086,7 +4352,7 @@ program.command("config").description("Show configuration").action(async () => {
3086
4352
  ["DeepSeek", config.deepseekApiKey]
3087
4353
  ];
3088
4354
  for (const [name, key] of keys) {
3089
- const status = key ? chalk12.green("\u2713") : chalk12.gray("\u2717");
4355
+ const status = key ? chalk13.green("\u2713") : chalk13.gray("\u2717");
3090
4356
  console.log(` ${status} ${name}`);
3091
4357
  }
3092
4358
  console.log("");