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