agdi 2.2.2 → 2.4.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/dist/index.js +912 -7
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
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";
|
|
@@ -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,
|
|
@@ -1270,8 +1369,8 @@ async function selectModel() {
|
|
|
1270
1369
|
`));
|
|
1271
1370
|
}
|
|
1272
1371
|
|
|
1273
|
-
// src/commands/
|
|
1274
|
-
import { input as input4 } from "@inquirer/prompts";
|
|
1372
|
+
// src/commands/agdi-dev.ts
|
|
1373
|
+
import { input as input4, confirm as confirm2 } from "@inquirer/prompts";
|
|
1275
1374
|
import chalk11 from "chalk";
|
|
1276
1375
|
import ora3 from "ora";
|
|
1277
1376
|
|
|
@@ -2455,6 +2554,11 @@ function saveTrustConfig(config) {
|
|
|
2455
2554
|
function normalizePath(path4) {
|
|
2456
2555
|
return resolve3(path4).toLowerCase().replace(/\\/g, "/");
|
|
2457
2556
|
}
|
|
2557
|
+
function isWorkspaceTrusted(workspacePath) {
|
|
2558
|
+
const config = loadTrustConfig();
|
|
2559
|
+
const normalized = normalizePath(workspacePath);
|
|
2560
|
+
return config.trustedWorkspaces.some((w) => w.normalizedPath === normalized);
|
|
2561
|
+
}
|
|
2458
2562
|
function trustWorkspace(workspacePath) {
|
|
2459
2563
|
const config = loadTrustConfig();
|
|
2460
2564
|
const normalized = normalizePath(workspacePath);
|
|
@@ -2468,6 +2572,63 @@ function trustWorkspace(workspacePath) {
|
|
|
2468
2572
|
});
|
|
2469
2573
|
saveTrustConfig(config);
|
|
2470
2574
|
}
|
|
2575
|
+
async function promptWorkspaceTrust(workspacePath) {
|
|
2576
|
+
if (isWorkspaceTrusted(workspacePath)) {
|
|
2577
|
+
console.log(chalk9.green("\u2713 Workspace is trusted\n"));
|
|
2578
|
+
return "persistent";
|
|
2579
|
+
}
|
|
2580
|
+
console.log(chalk9.yellow("\n\u26A0\uFE0F Untrusted Workspace"));
|
|
2581
|
+
console.log(chalk9.gray(` ${workspacePath}
|
|
2582
|
+
`));
|
|
2583
|
+
console.log(chalk9.gray("Agdi can run commands in this workspace."));
|
|
2584
|
+
console.log(chalk9.gray("Do you trust the contents of this folder?\n"));
|
|
2585
|
+
const choice = await select3({
|
|
2586
|
+
message: "Trust this workspace?",
|
|
2587
|
+
choices: [
|
|
2588
|
+
{
|
|
2589
|
+
name: "Trust for this session only",
|
|
2590
|
+
value: "session",
|
|
2591
|
+
description: "Allow commands for this session, ask again next time"
|
|
2592
|
+
},
|
|
2593
|
+
{
|
|
2594
|
+
name: "Trust and remember",
|
|
2595
|
+
value: "persistent",
|
|
2596
|
+
description: "Always trust this workspace"
|
|
2597
|
+
},
|
|
2598
|
+
{
|
|
2599
|
+
name: "Exit (don't trust)",
|
|
2600
|
+
value: "exit",
|
|
2601
|
+
description: "Exit without granting trust"
|
|
2602
|
+
}
|
|
2603
|
+
]
|
|
2604
|
+
});
|
|
2605
|
+
return choice;
|
|
2606
|
+
}
|
|
2607
|
+
async function handleTrustFlow(workspacePath) {
|
|
2608
|
+
const choice = await promptWorkspaceTrust(workspacePath);
|
|
2609
|
+
switch (choice) {
|
|
2610
|
+
case "session":
|
|
2611
|
+
updateEnvironment({ trustLevel: "session" });
|
|
2612
|
+
console.log(chalk9.green("\u2713 Trusted for this session\n"));
|
|
2613
|
+
return "session";
|
|
2614
|
+
case "persistent":
|
|
2615
|
+
trustWorkspace(workspacePath);
|
|
2616
|
+
updateEnvironment({ trustLevel: "persistent" });
|
|
2617
|
+
console.log(chalk9.green("\u2713 Workspace trusted and remembered\n"));
|
|
2618
|
+
return "persistent";
|
|
2619
|
+
case "exit":
|
|
2620
|
+
console.log(chalk9.yellow("\n\u{1F44B} Exiting. Workspace not trusted.\n"));
|
|
2621
|
+
return null;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
async function ensureTrusted(workspacePath) {
|
|
2625
|
+
if (isWorkspaceTrusted(workspacePath)) {
|
|
2626
|
+
updateEnvironment({ trustLevel: "persistent" });
|
|
2627
|
+
return true;
|
|
2628
|
+
}
|
|
2629
|
+
const result = await handleTrustFlow(workspacePath);
|
|
2630
|
+
return result !== null;
|
|
2631
|
+
}
|
|
2471
2632
|
|
|
2472
2633
|
// src/actions/plan-executor.ts
|
|
2473
2634
|
function displayPlanSummary(plan) {
|
|
@@ -2733,14 +2894,577 @@ async function parseAndExecutePlan(response) {
|
|
|
2733
2894
|
return executePlan(plan);
|
|
2734
2895
|
}
|
|
2735
2896
|
|
|
2736
|
-
// src/
|
|
2737
|
-
|
|
2897
|
+
// src/core/context-manager.ts
|
|
2898
|
+
import { readdirSync, readFileSync as readFileSync5, statSync, existsSync as existsSync6 } from "fs";
|
|
2899
|
+
import { join as join4, basename } from "path";
|
|
2900
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2901
|
+
"node_modules",
|
|
2902
|
+
".git",
|
|
2903
|
+
"dist",
|
|
2904
|
+
"build",
|
|
2905
|
+
".next",
|
|
2906
|
+
".nuxt",
|
|
2907
|
+
".cache",
|
|
2908
|
+
"coverage",
|
|
2909
|
+
"__pycache__",
|
|
2910
|
+
".venv",
|
|
2911
|
+
"venv",
|
|
2912
|
+
".idea",
|
|
2913
|
+
".vscode",
|
|
2914
|
+
".turbo"
|
|
2915
|
+
]);
|
|
2916
|
+
var IGNORED_FILES = /* @__PURE__ */ new Set([
|
|
2917
|
+
".DS_Store",
|
|
2918
|
+
"Thumbs.db",
|
|
2919
|
+
".env",
|
|
2920
|
+
".env.local",
|
|
2921
|
+
"package-lock.json",
|
|
2922
|
+
"yarn.lock",
|
|
2923
|
+
"pnpm-lock.yaml"
|
|
2924
|
+
]);
|
|
2925
|
+
var MAX_TREE_DEPTH = 4;
|
|
2926
|
+
var MAX_FILES_PER_DIR = 20;
|
|
2927
|
+
function getWorkspaceTree(rootDir, maxDepth = MAX_TREE_DEPTH) {
|
|
2928
|
+
const env = getEnvironment();
|
|
2929
|
+
const root = rootDir || env.workspaceRoot;
|
|
2930
|
+
if (!existsSync6(root)) {
|
|
2931
|
+
return "(workspace not found)";
|
|
2932
|
+
}
|
|
2933
|
+
const lines = [];
|
|
2934
|
+
lines.push(basename(root) + "/");
|
|
2935
|
+
buildTree(root, "", lines, 0, maxDepth);
|
|
2936
|
+
return lines.join("\n");
|
|
2937
|
+
}
|
|
2938
|
+
function buildTree(dir, prefix, lines, depth, maxDepth) {
|
|
2939
|
+
if (depth >= maxDepth) {
|
|
2940
|
+
lines.push(prefix + "\u2514\u2500\u2500 ...");
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
let entries;
|
|
2944
|
+
try {
|
|
2945
|
+
entries = readdirSync(dir);
|
|
2946
|
+
} catch {
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
const filtered = entries.filter((e) => {
|
|
2950
|
+
if (IGNORED_DIRS.has(e) || IGNORED_FILES.has(e)) return false;
|
|
2951
|
+
if (e.startsWith(".") && e !== ".github") return false;
|
|
2952
|
+
return true;
|
|
2953
|
+
});
|
|
2954
|
+
const dirs = [];
|
|
2955
|
+
const files = [];
|
|
2956
|
+
for (const entry of filtered) {
|
|
2957
|
+
const fullPath = join4(dir, entry);
|
|
2958
|
+
try {
|
|
2959
|
+
const stat = statSync(fullPath);
|
|
2960
|
+
if (stat.isDirectory()) {
|
|
2961
|
+
dirs.push(entry);
|
|
2962
|
+
} else {
|
|
2963
|
+
files.push(entry);
|
|
2964
|
+
}
|
|
2965
|
+
} catch {
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
dirs.sort();
|
|
2969
|
+
files.sort();
|
|
2970
|
+
const allEntries = [...dirs.map((d) => ({ name: d, isDir: true })), ...files.map((f) => ({ name: f, isDir: false }))];
|
|
2971
|
+
const truncated = allEntries.length > MAX_FILES_PER_DIR;
|
|
2972
|
+
const toShow = truncated ? allEntries.slice(0, MAX_FILES_PER_DIR) : allEntries;
|
|
2973
|
+
toShow.forEach((entry, index) => {
|
|
2974
|
+
const isLast = index === toShow.length - 1 && !truncated;
|
|
2975
|
+
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
2976
|
+
const childPrefix = isLast ? " " : "\u2502 ";
|
|
2977
|
+
if (entry.isDir) {
|
|
2978
|
+
lines.push(prefix + connector + entry.name + "/");
|
|
2979
|
+
buildTree(join4(dir, entry.name), prefix + childPrefix, lines, depth + 1, maxDepth);
|
|
2980
|
+
} else {
|
|
2981
|
+
lines.push(prefix + connector + entry.name);
|
|
2982
|
+
}
|
|
2983
|
+
});
|
|
2984
|
+
if (truncated) {
|
|
2985
|
+
lines.push(prefix + "\u2514\u2500\u2500 ... (" + (allEntries.length - MAX_FILES_PER_DIR) + " more)");
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
function getProjectConfig(rootDir) {
|
|
2989
|
+
const env = getEnvironment();
|
|
2990
|
+
const root = rootDir || env.workspaceRoot;
|
|
2991
|
+
const packageJsonPath = join4(root, "package.json");
|
|
2992
|
+
if (existsSync6(packageJsonPath)) {
|
|
2993
|
+
try {
|
|
2994
|
+
const content = readFileSync5(packageJsonPath, "utf-8");
|
|
2995
|
+
const pkg = JSON.parse(content);
|
|
2996
|
+
return {
|
|
2997
|
+
name: pkg.name || basename(root),
|
|
2998
|
+
version: pkg.version,
|
|
2999
|
+
description: pkg.description,
|
|
3000
|
+
dependencies: pkg.dependencies,
|
|
3001
|
+
devDependencies: pkg.devDependencies,
|
|
3002
|
+
scripts: pkg.scripts
|
|
3003
|
+
};
|
|
3004
|
+
} catch {
|
|
3005
|
+
return null;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
return null;
|
|
3009
|
+
}
|
|
3010
|
+
function buildWorkspaceContext(rootDir) {
|
|
3011
|
+
const env = getEnvironment();
|
|
3012
|
+
const root = rootDir || env.workspaceRoot;
|
|
3013
|
+
return {
|
|
3014
|
+
workspaceRoot: root,
|
|
3015
|
+
fileTree: getWorkspaceTree(root),
|
|
3016
|
+
projectConfig: getProjectConfig(root),
|
|
3017
|
+
gitStatus: null
|
|
3018
|
+
// Filled by GitManager
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
3021
|
+
function formatContextForPrompt(context) {
|
|
3022
|
+
const sections = [];
|
|
3023
|
+
sections.push("## Current Workspace");
|
|
3024
|
+
sections.push(`Path: ${context.workspaceRoot}`);
|
|
3025
|
+
if (context.projectConfig) {
|
|
3026
|
+
const cfg = context.projectConfig;
|
|
3027
|
+
sections.push(`
|
|
3028
|
+
Project: ${cfg.name}${cfg.version ? " v" + cfg.version : ""}`);
|
|
3029
|
+
if (cfg.description) {
|
|
3030
|
+
sections.push(`Description: ${cfg.description}`);
|
|
3031
|
+
}
|
|
3032
|
+
if (cfg.scripts) {
|
|
3033
|
+
const scriptList = Object.keys(cfg.scripts).slice(0, 5).join(", ");
|
|
3034
|
+
sections.push(`Scripts: ${scriptList}`);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
sections.push("\n## File Structure");
|
|
3038
|
+
sections.push("```");
|
|
3039
|
+
sections.push(context.fileTree);
|
|
3040
|
+
sections.push("```");
|
|
3041
|
+
if (context.gitStatus) {
|
|
3042
|
+
sections.push("\n## Git Status");
|
|
3043
|
+
sections.push(context.gitStatus);
|
|
3044
|
+
}
|
|
3045
|
+
return sections.join("\n");
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
// src/core/git-manager.ts
|
|
3049
|
+
import { spawnSync } from "child_process";
|
|
3050
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3051
|
+
import { join as join5 } from "path";
|
|
3052
|
+
function execGit(args, cwd) {
|
|
3053
|
+
const env = getEnvironment();
|
|
3054
|
+
const workDir = cwd || env.workspaceRoot;
|
|
3055
|
+
try {
|
|
3056
|
+
const result = spawnSync("git", args, {
|
|
3057
|
+
cwd: workDir,
|
|
3058
|
+
encoding: "utf-8",
|
|
3059
|
+
timeout: 1e4
|
|
3060
|
+
});
|
|
3061
|
+
if (result.error) {
|
|
3062
|
+
throw result.error;
|
|
3063
|
+
}
|
|
3064
|
+
return result.stdout?.trim() || "";
|
|
3065
|
+
} catch (error) {
|
|
3066
|
+
return "";
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
function isGitRepo(dir) {
|
|
3070
|
+
const env = getEnvironment();
|
|
3071
|
+
const checkDir = dir || env.workspaceRoot;
|
|
3072
|
+
return existsSync7(join5(checkDir, ".git"));
|
|
3073
|
+
}
|
|
3074
|
+
function getStatus(cwd) {
|
|
3075
|
+
if (!isGitRepo(cwd)) {
|
|
3076
|
+
return {
|
|
3077
|
+
isGitRepo: false,
|
|
3078
|
+
branch: "",
|
|
3079
|
+
ahead: 0,
|
|
3080
|
+
behind: 0,
|
|
3081
|
+
staged: [],
|
|
3082
|
+
unstaged: [],
|
|
3083
|
+
untracked: [],
|
|
3084
|
+
hasConflicts: false
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
const branch = execGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd) || "main";
|
|
3088
|
+
let ahead = 0;
|
|
3089
|
+
let behind = 0;
|
|
3090
|
+
const trackingInfo = execGit(["rev-list", "--left-right", "--count", `origin/${branch}...HEAD`], cwd);
|
|
3091
|
+
if (trackingInfo) {
|
|
3092
|
+
const [behindStr, aheadStr] = trackingInfo.split(/\s+/);
|
|
3093
|
+
behind = parseInt(behindStr, 10) || 0;
|
|
3094
|
+
ahead = parseInt(aheadStr, 10) || 0;
|
|
3095
|
+
}
|
|
3096
|
+
const statusOutput = execGit(["status", "--porcelain=v1"], cwd);
|
|
3097
|
+
const staged = [];
|
|
3098
|
+
const unstaged = [];
|
|
3099
|
+
const untracked = [];
|
|
3100
|
+
let hasConflicts = false;
|
|
3101
|
+
for (const line of statusOutput.split("\n")) {
|
|
3102
|
+
if (!line) continue;
|
|
3103
|
+
const indexStatus = line[0];
|
|
3104
|
+
const workTreeStatus = line[1];
|
|
3105
|
+
const filePath = line.slice(3).trim();
|
|
3106
|
+
if (indexStatus === "U" || workTreeStatus === "U" || indexStatus === "A" && workTreeStatus === "A" || indexStatus === "D" && workTreeStatus === "D") {
|
|
3107
|
+
hasConflicts = true;
|
|
3108
|
+
}
|
|
3109
|
+
if (indexStatus === "?" && workTreeStatus === "?") {
|
|
3110
|
+
untracked.push(filePath);
|
|
3111
|
+
continue;
|
|
3112
|
+
}
|
|
3113
|
+
if (indexStatus !== " " && indexStatus !== "?") {
|
|
3114
|
+
staged.push({
|
|
3115
|
+
path: filePath,
|
|
3116
|
+
status: parseStatus(indexStatus)
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
if (workTreeStatus !== " " && workTreeStatus !== "?") {
|
|
3120
|
+
unstaged.push({
|
|
3121
|
+
path: filePath,
|
|
3122
|
+
status: parseStatus(workTreeStatus)
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
return {
|
|
3127
|
+
isGitRepo: true,
|
|
3128
|
+
branch,
|
|
3129
|
+
ahead,
|
|
3130
|
+
behind,
|
|
3131
|
+
staged,
|
|
3132
|
+
unstaged,
|
|
3133
|
+
untracked,
|
|
3134
|
+
hasConflicts
|
|
3135
|
+
};
|
|
3136
|
+
}
|
|
3137
|
+
function parseStatus(code) {
|
|
3138
|
+
switch (code) {
|
|
3139
|
+
case "A":
|
|
3140
|
+
return "added";
|
|
3141
|
+
case "M":
|
|
3142
|
+
return "modified";
|
|
3143
|
+
case "D":
|
|
3144
|
+
return "deleted";
|
|
3145
|
+
case "R":
|
|
3146
|
+
return "renamed";
|
|
3147
|
+
case "C":
|
|
3148
|
+
return "copied";
|
|
3149
|
+
default:
|
|
3150
|
+
return "modified";
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
function getDiff(staged = false, cwd) {
|
|
3154
|
+
if (!isGitRepo(cwd)) {
|
|
3155
|
+
return { files: [], summary: "(not a git repository)" };
|
|
3156
|
+
}
|
|
3157
|
+
const args = staged ? ["diff", "--cached", "--stat"] : ["diff", "--stat"];
|
|
3158
|
+
const statOutput = execGit(args, cwd);
|
|
3159
|
+
const patchArgs = staged ? ["diff", "--cached"] : ["diff"];
|
|
3160
|
+
const patchOutput = execGit(patchArgs, cwd);
|
|
3161
|
+
const files = [];
|
|
3162
|
+
const statLines = statOutput.split("\n");
|
|
3163
|
+
for (const line of statLines) {
|
|
3164
|
+
const match = line.match(/^\s*(.+?)\s+\|\s+(\d+)\s*([+-]*)/);
|
|
3165
|
+
if (match) {
|
|
3166
|
+
const [, path4, changes, plusMinus] = match;
|
|
3167
|
+
const additions = (plusMinus.match(/\+/g) || []).length;
|
|
3168
|
+
const deletions = (plusMinus.match(/-/g) || []).length;
|
|
3169
|
+
const filePatches = patchOutput.split(/^diff --git/m);
|
|
3170
|
+
const filePatch = filePatches.find((p) => p.includes(path4.trim())) || "";
|
|
3171
|
+
files.push({
|
|
3172
|
+
path: path4.trim(),
|
|
3173
|
+
additions,
|
|
3174
|
+
deletions,
|
|
3175
|
+
patch: filePatch.slice(0, 2e3)
|
|
3176
|
+
// Limit patch size
|
|
3177
|
+
});
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
|
|
3181
|
+
const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
|
|
3182
|
+
const summary = `${files.length} file(s) changed, ${totalAdditions} insertion(s), ${totalDeletions} deletion(s)`;
|
|
3183
|
+
return { files, summary };
|
|
3184
|
+
}
|
|
3185
|
+
function formatStatusForPrompt(status) {
|
|
3186
|
+
if (!status.isGitRepo) {
|
|
3187
|
+
return "(not a git repository)";
|
|
3188
|
+
}
|
|
3189
|
+
const lines = [];
|
|
3190
|
+
lines.push(`Branch: ${status.branch}`);
|
|
3191
|
+
if (status.ahead > 0 || status.behind > 0) {
|
|
3192
|
+
lines.push(`Tracking: ${status.ahead} ahead, ${status.behind} behind origin`);
|
|
3193
|
+
}
|
|
3194
|
+
if (status.hasConflicts) {
|
|
3195
|
+
lines.push("\u26A0\uFE0F MERGE CONFLICTS DETECTED");
|
|
3196
|
+
}
|
|
3197
|
+
if (status.staged.length > 0) {
|
|
3198
|
+
lines.push(`
|
|
3199
|
+
Staged (${status.staged.length}):`);
|
|
3200
|
+
for (const f of status.staged.slice(0, 10)) {
|
|
3201
|
+
lines.push(` ${f.status[0].toUpperCase()} ${f.path}`);
|
|
3202
|
+
}
|
|
3203
|
+
if (status.staged.length > 10) {
|
|
3204
|
+
lines.push(` ... and ${status.staged.length - 10} more`);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
if (status.unstaged.length > 0) {
|
|
3208
|
+
lines.push(`
|
|
3209
|
+
Unstaged (${status.unstaged.length}):`);
|
|
3210
|
+
for (const f of status.unstaged.slice(0, 10)) {
|
|
3211
|
+
lines.push(` ${f.status[0].toUpperCase()} ${f.path}`);
|
|
3212
|
+
}
|
|
3213
|
+
if (status.unstaged.length > 10) {
|
|
3214
|
+
lines.push(` ... and ${status.unstaged.length - 10} more`);
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
if (status.untracked.length > 0) {
|
|
3218
|
+
lines.push(`
|
|
3219
|
+
Untracked (${status.untracked.length}):`);
|
|
3220
|
+
for (const f of status.untracked.slice(0, 5)) {
|
|
3221
|
+
lines.push(` ? ${f}`);
|
|
3222
|
+
}
|
|
3223
|
+
if (status.untracked.length > 5) {
|
|
3224
|
+
lines.push(` ... and ${status.untracked.length - 5} more`);
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
if (status.staged.length === 0 && status.unstaged.length === 0 && status.untracked.length === 0) {
|
|
3228
|
+
lines.push("\n\u2713 Working tree clean");
|
|
3229
|
+
}
|
|
3230
|
+
return lines.join("\n");
|
|
3231
|
+
}
|
|
3232
|
+
function formatDiffForPrompt(diff) {
|
|
3233
|
+
if (diff.files.length === 0) {
|
|
3234
|
+
return "(no changes)";
|
|
3235
|
+
}
|
|
3236
|
+
const lines = [];
|
|
3237
|
+
lines.push(diff.summary);
|
|
3238
|
+
lines.push("");
|
|
3239
|
+
for (const file of diff.files.slice(0, 5)) {
|
|
3240
|
+
lines.push(`### ${file.path}`);
|
|
3241
|
+
lines.push(`+${file.additions} -${file.deletions}`);
|
|
3242
|
+
if (file.patch) {
|
|
3243
|
+
lines.push("```diff");
|
|
3244
|
+
lines.push(file.patch.slice(0, 1e3));
|
|
3245
|
+
if (file.patch.length > 1e3) lines.push("... (truncated)");
|
|
3246
|
+
lines.push("```");
|
|
3247
|
+
}
|
|
3248
|
+
lines.push("");
|
|
3249
|
+
}
|
|
3250
|
+
if (diff.files.length > 5) {
|
|
3251
|
+
lines.push(`... and ${diff.files.length - 5} more files`);
|
|
3252
|
+
}
|
|
3253
|
+
return lines.join("\n");
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
// src/core/conversation-manager.ts
|
|
3257
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
3258
|
+
import { join as join6 } from "path";
|
|
3259
|
+
import { homedir as homedir5 } from "os";
|
|
3260
|
+
var AGDI_DIR = join6(homedir5(), ".agdi");
|
|
3261
|
+
var SESSIONS_DIR = join6(AGDI_DIR, "sessions");
|
|
3262
|
+
var MAX_CONTEXT_TOKENS = 32e3;
|
|
3263
|
+
var CHARS_PER_TOKEN = 4;
|
|
3264
|
+
var ConversationManager = class _ConversationManager {
|
|
3265
|
+
messages = [];
|
|
3266
|
+
sessionId;
|
|
3267
|
+
systemPrompt = "";
|
|
3268
|
+
constructor(sessionId) {
|
|
3269
|
+
this.sessionId = sessionId || this.generateSessionId();
|
|
3270
|
+
}
|
|
3271
|
+
/**
|
|
3272
|
+
* Set the system prompt (persists across conversation)
|
|
3273
|
+
*/
|
|
3274
|
+
setSystemPrompt(prompt) {
|
|
3275
|
+
this.systemPrompt = prompt;
|
|
3276
|
+
}
|
|
3277
|
+
/**
|
|
3278
|
+
* Get the system prompt
|
|
3279
|
+
*/
|
|
3280
|
+
getSystemPrompt() {
|
|
3281
|
+
return this.systemPrompt;
|
|
3282
|
+
}
|
|
3283
|
+
/**
|
|
3284
|
+
* Add a user message
|
|
3285
|
+
*/
|
|
3286
|
+
addUserMessage(content) {
|
|
3287
|
+
this.messages.push({
|
|
3288
|
+
role: "user",
|
|
3289
|
+
content,
|
|
3290
|
+
timestamp: Date.now()
|
|
3291
|
+
});
|
|
3292
|
+
this.trimToContextLimit();
|
|
3293
|
+
}
|
|
3294
|
+
/**
|
|
3295
|
+
* Add an assistant response
|
|
3296
|
+
*/
|
|
3297
|
+
addAssistantMessage(content) {
|
|
3298
|
+
this.messages.push({
|
|
3299
|
+
role: "assistant",
|
|
3300
|
+
content,
|
|
3301
|
+
timestamp: Date.now()
|
|
3302
|
+
});
|
|
3303
|
+
this.trimToContextLimit();
|
|
3304
|
+
}
|
|
3305
|
+
/**
|
|
3306
|
+
* Get all messages for LLM context
|
|
3307
|
+
*/
|
|
3308
|
+
getMessages() {
|
|
3309
|
+
return [...this.messages];
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* Get messages formatted for API calls
|
|
3313
|
+
*/
|
|
3314
|
+
getMessagesForAPI() {
|
|
3315
|
+
const result = [];
|
|
3316
|
+
if (this.systemPrompt) {
|
|
3317
|
+
result.push({ role: "system", content: this.systemPrompt });
|
|
3318
|
+
}
|
|
3319
|
+
for (const msg of this.messages) {
|
|
3320
|
+
result.push({ role: msg.role, content: msg.content });
|
|
3321
|
+
}
|
|
3322
|
+
return result;
|
|
3323
|
+
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Get message count
|
|
3326
|
+
*/
|
|
3327
|
+
getMessageCount() {
|
|
3328
|
+
return this.messages.length;
|
|
3329
|
+
}
|
|
3330
|
+
/**
|
|
3331
|
+
* Get turn count (user messages)
|
|
3332
|
+
*/
|
|
3333
|
+
getTurnCount() {
|
|
3334
|
+
return this.messages.filter((m) => m.role === "user").length;
|
|
3335
|
+
}
|
|
3336
|
+
/**
|
|
3337
|
+
* Clear conversation history
|
|
3338
|
+
*/
|
|
3339
|
+
clear() {
|
|
3340
|
+
this.messages = [];
|
|
3341
|
+
}
|
|
3342
|
+
/**
|
|
3343
|
+
* Get session ID
|
|
3344
|
+
*/
|
|
3345
|
+
getSessionId() {
|
|
3346
|
+
return this.sessionId;
|
|
3347
|
+
}
|
|
3348
|
+
/**
|
|
3349
|
+
* Get last N messages
|
|
3350
|
+
*/
|
|
3351
|
+
getLastMessages(n) {
|
|
3352
|
+
return this.messages.slice(-n);
|
|
3353
|
+
}
|
|
3354
|
+
/**
|
|
3355
|
+
* Get conversation summary for display
|
|
3356
|
+
*/
|
|
3357
|
+
getSummary() {
|
|
3358
|
+
const turns = this.getTurnCount();
|
|
3359
|
+
const tokens = this.estimateTokens();
|
|
3360
|
+
return `Session: ${this.sessionId} | ${turns} turns | ~${tokens} tokens`;
|
|
3361
|
+
}
|
|
3362
|
+
/**
|
|
3363
|
+
* Estimate token count
|
|
3364
|
+
*/
|
|
3365
|
+
estimateTokens() {
|
|
3366
|
+
let chars = this.systemPrompt.length;
|
|
3367
|
+
for (const msg of this.messages) {
|
|
3368
|
+
chars += msg.content.length;
|
|
3369
|
+
}
|
|
3370
|
+
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
3371
|
+
}
|
|
3372
|
+
/**
|
|
3373
|
+
* Trim messages to stay within context limit
|
|
3374
|
+
*/
|
|
3375
|
+
trimToContextLimit() {
|
|
3376
|
+
while (this.estimateTokens() > MAX_CONTEXT_TOKENS && this.messages.length > 2) {
|
|
3377
|
+
this.messages.shift();
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3381
|
+
* Generate a unique session ID
|
|
3382
|
+
*/
|
|
3383
|
+
generateSessionId() {
|
|
3384
|
+
const now = /* @__PURE__ */ new Date();
|
|
3385
|
+
const date = now.toISOString().split("T")[0];
|
|
3386
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
3387
|
+
return `${date}-${random}`;
|
|
3388
|
+
}
|
|
3389
|
+
// ==================== PERSISTENCE ====================
|
|
3390
|
+
/**
|
|
3391
|
+
* Save session to disk
|
|
3392
|
+
*/
|
|
3393
|
+
save() {
|
|
3394
|
+
this.ensureDirectories();
|
|
3395
|
+
const session = {
|
|
3396
|
+
id: this.sessionId,
|
|
3397
|
+
messages: this.messages,
|
|
3398
|
+
createdAt: this.messages[0]?.timestamp || Date.now(),
|
|
3399
|
+
updatedAt: Date.now()
|
|
3400
|
+
};
|
|
3401
|
+
const filePath = join6(SESSIONS_DIR, `${this.sessionId}.json`);
|
|
3402
|
+
writeFileSync4(filePath, JSON.stringify(session, null, 2));
|
|
3403
|
+
}
|
|
3404
|
+
/**
|
|
3405
|
+
* Load session from disk
|
|
3406
|
+
*/
|
|
3407
|
+
static load(sessionId) {
|
|
3408
|
+
const filePath = join6(SESSIONS_DIR, `${sessionId}.json`);
|
|
3409
|
+
if (!existsSync8(filePath)) {
|
|
3410
|
+
return null;
|
|
3411
|
+
}
|
|
3412
|
+
try {
|
|
3413
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
3414
|
+
const session = JSON.parse(content);
|
|
3415
|
+
const manager = new _ConversationManager(session.id);
|
|
3416
|
+
manager.messages = session.messages;
|
|
3417
|
+
return manager;
|
|
3418
|
+
} catch {
|
|
3419
|
+
return null;
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* List available sessions
|
|
3424
|
+
*/
|
|
3425
|
+
static listSessions() {
|
|
3426
|
+
if (!existsSync8(SESSIONS_DIR)) {
|
|
3427
|
+
return [];
|
|
3428
|
+
}
|
|
3429
|
+
const { readdirSync: readdirSync2 } = __require("fs");
|
|
3430
|
+
const files = readdirSync2(SESSIONS_DIR);
|
|
3431
|
+
return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", "")).sort().reverse();
|
|
3432
|
+
}
|
|
3433
|
+
/**
|
|
3434
|
+
* Ensure directories exist
|
|
3435
|
+
*/
|
|
3436
|
+
ensureDirectories() {
|
|
3437
|
+
if (!existsSync8(AGDI_DIR)) {
|
|
3438
|
+
mkdirSync4(AGDI_DIR, { recursive: true });
|
|
3439
|
+
}
|
|
3440
|
+
if (!existsSync8(SESSIONS_DIR)) {
|
|
3441
|
+
mkdirSync4(SESSIONS_DIR, { recursive: true });
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
};
|
|
3445
|
+
var currentConversation = null;
|
|
3446
|
+
function getConversation() {
|
|
3447
|
+
if (!currentConversation) {
|
|
3448
|
+
currentConversation = new ConversationManager();
|
|
3449
|
+
}
|
|
3450
|
+
return currentConversation;
|
|
3451
|
+
}
|
|
3452
|
+
function clearConversation() {
|
|
3453
|
+
if (currentConversation) {
|
|
3454
|
+
currentConversation.clear();
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
// src/commands/agdi-dev.ts
|
|
3459
|
+
var BASE_CHAT_PROMPT = `You are Agdi dev, an elite AI coding assistant. You help developers write code, debug issues, and build applications.
|
|
2738
3460
|
|
|
2739
3461
|
## Your Capabilities
|
|
2740
3462
|
- Write complete, production-ready code
|
|
2741
3463
|
- Debug and fix code issues
|
|
2742
3464
|
- Explain code and architecture
|
|
2743
3465
|
- Answer coding questions
|
|
3466
|
+
- Analyze git status and diffs
|
|
3467
|
+
- Generate meaningful commit messages
|
|
2744
3468
|
|
|
2745
3469
|
## Response Style
|
|
2746
3470
|
- Be concise and direct
|
|
@@ -2753,6 +3477,17 @@ var CHAT_SYSTEM_PROMPT = `You are Agdi dev, an elite AI coding assistant. You he
|
|
|
2753
3477
|
- Follow modern best practices
|
|
2754
3478
|
- Include proper error handling
|
|
2755
3479
|
- Write self-documenting code`;
|
|
3480
|
+
function buildContextAwarePrompt() {
|
|
3481
|
+
const context = buildWorkspaceContext();
|
|
3482
|
+
const gitStatus = getStatus();
|
|
3483
|
+
context.gitStatus = formatStatusForPrompt(gitStatus);
|
|
3484
|
+
const contextSection = formatContextForPrompt(context);
|
|
3485
|
+
return `${BASE_CHAT_PROMPT}
|
|
3486
|
+
|
|
3487
|
+
---
|
|
3488
|
+
|
|
3489
|
+
${contextSection}`;
|
|
3490
|
+
}
|
|
2756
3491
|
var BUILD_SYSTEM_PROMPT = `You are Agdi dev, an AI coding assistant that generates applications.
|
|
2757
3492
|
|
|
2758
3493
|
## CRITICAL: Output Format
|
|
@@ -2796,13 +3531,20 @@ async function startCodingMode() {
|
|
|
2796
3531
|
const config = loadConfig();
|
|
2797
3532
|
const env = initSession();
|
|
2798
3533
|
displaySessionHeader(env);
|
|
3534
|
+
const isTrusted = await ensureTrusted(env.workspaceRoot);
|
|
3535
|
+
if (!isTrusted) {
|
|
3536
|
+
process.exit(0);
|
|
3537
|
+
}
|
|
2799
3538
|
logSessionStart(env.workspaceRoot, env.trustLevel);
|
|
2800
3539
|
console.log(chalk11.cyan.bold("\u26A1 Agdi dev\n"));
|
|
2801
3540
|
console.log(chalk11.gray(`Model: ${chalk11.cyan(model)}`));
|
|
2802
|
-
console.log(chalk11.gray(
|
|
3541
|
+
console.log(chalk11.gray(`Workspace: ${chalk11.cyan(env.workspaceRoot)}`));
|
|
3542
|
+
console.log(chalk11.gray("Commands: /status, /diff, /commit, /build, /clear, /history, /model, /help, /exit\n"));
|
|
2803
3543
|
console.log(chalk11.gray("\u2500".repeat(50) + "\n"));
|
|
2804
3544
|
const pm = new ProjectManager();
|
|
2805
3545
|
let llm = createLLMProvider(provider, { apiKey, model });
|
|
3546
|
+
const conversation = getConversation();
|
|
3547
|
+
conversation.setSystemPrompt(buildContextAwarePrompt());
|
|
2806
3548
|
while (true) {
|
|
2807
3549
|
try {
|
|
2808
3550
|
const userInput = await input4({
|
|
@@ -2836,6 +3578,41 @@ async function startCodingMode() {
|
|
|
2836
3578
|
await chatMode(llm);
|
|
2837
3579
|
continue;
|
|
2838
3580
|
}
|
|
3581
|
+
if (trimmed === "/clear") {
|
|
3582
|
+
clearConversation();
|
|
3583
|
+
conversation.setSystemPrompt(buildContextAwarePrompt());
|
|
3584
|
+
console.log(chalk11.green("\n\u2713 Conversation cleared.\n"));
|
|
3585
|
+
continue;
|
|
3586
|
+
}
|
|
3587
|
+
if (trimmed === "/history") {
|
|
3588
|
+
const messages = conversation.getMessages();
|
|
3589
|
+
if (messages.length === 0) {
|
|
3590
|
+
console.log(chalk11.gray("\n(no conversation history)\n"));
|
|
3591
|
+
} else {
|
|
3592
|
+
console.log(chalk11.cyan.bold("\n\u{1F4DC} Conversation History\n"));
|
|
3593
|
+
console.log(chalk11.gray(conversation.getSummary()));
|
|
3594
|
+
console.log("");
|
|
3595
|
+
for (const msg of messages.slice(-6)) {
|
|
3596
|
+
const role = msg.role === "user" ? chalk11.green("You") : chalk11.cyan("AI");
|
|
3597
|
+
const preview = msg.content.slice(0, 80) + (msg.content.length > 80 ? "..." : "");
|
|
3598
|
+
console.log(` ${role}: ${chalk11.gray(preview)}`);
|
|
3599
|
+
}
|
|
3600
|
+
console.log("");
|
|
3601
|
+
}
|
|
3602
|
+
continue;
|
|
3603
|
+
}
|
|
3604
|
+
if (trimmed === "/status") {
|
|
3605
|
+
await handleGitStatus(llm);
|
|
3606
|
+
continue;
|
|
3607
|
+
}
|
|
3608
|
+
if (trimmed === "/diff") {
|
|
3609
|
+
await handleGitDiff(llm);
|
|
3610
|
+
continue;
|
|
3611
|
+
}
|
|
3612
|
+
if (trimmed === "/commit") {
|
|
3613
|
+
await handleGitCommit(llm);
|
|
3614
|
+
continue;
|
|
3615
|
+
}
|
|
2839
3616
|
if (trimmed.startsWith("/build ") || trimmed.startsWith("build ")) {
|
|
2840
3617
|
const prompt = userInput.replace(/^\/?build\s+/i, "").trim();
|
|
2841
3618
|
if (prompt) {
|
|
@@ -2855,7 +3632,15 @@ async function startCodingMode() {
|
|
|
2855
3632
|
}
|
|
2856
3633
|
const spinner = ora3("Thinking...").start();
|
|
2857
3634
|
try {
|
|
2858
|
-
|
|
3635
|
+
conversation.addUserMessage(userInput);
|
|
3636
|
+
let response;
|
|
3637
|
+
if (llm.chat) {
|
|
3638
|
+
response = await llm.chat(conversation.getMessagesForAPI());
|
|
3639
|
+
} else {
|
|
3640
|
+
const contextPrompt = buildContextAwarePrompt();
|
|
3641
|
+
response = await llm.generate(userInput, contextPrompt);
|
|
3642
|
+
}
|
|
3643
|
+
conversation.addAssistantMessage(response.text);
|
|
2859
3644
|
spinner.stop();
|
|
2860
3645
|
console.log("\n" + formatResponse(response.text) + "\n");
|
|
2861
3646
|
} catch (error) {
|
|
@@ -2910,6 +3695,114 @@ async function chatMode(llm) {
|
|
|
2910
3695
|
}
|
|
2911
3696
|
}
|
|
2912
3697
|
}
|
|
3698
|
+
async function handleGitStatus(llm) {
|
|
3699
|
+
if (!isGitRepo()) {
|
|
3700
|
+
console.log(chalk11.yellow("\n\u26A0\uFE0F Not a git repository\n"));
|
|
3701
|
+
return;
|
|
3702
|
+
}
|
|
3703
|
+
const spinner = ora3("Analyzing git status...").start();
|
|
3704
|
+
try {
|
|
3705
|
+
const status = getStatus();
|
|
3706
|
+
const statusText = formatStatusForPrompt(status);
|
|
3707
|
+
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?
|
|
3708
|
+
|
|
3709
|
+
${statusText}`;
|
|
3710
|
+
const response = await llm.generate(prompt, BASE_CHAT_PROMPT);
|
|
3711
|
+
spinner.stop();
|
|
3712
|
+
console.log(chalk11.cyan.bold("\n\u{1F4CA} Git Status Analysis\n"));
|
|
3713
|
+
console.log(chalk11.gray(statusText));
|
|
3714
|
+
console.log(chalk11.cyan("\n\u2500\u2500\u2500 AI Analysis \u2500\u2500\u2500\n"));
|
|
3715
|
+
console.log(formatResponse(response.text) + "\n");
|
|
3716
|
+
} catch (error) {
|
|
3717
|
+
spinner.fail("Error analyzing status");
|
|
3718
|
+
handleError(error);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
async function handleGitDiff(llm) {
|
|
3722
|
+
if (!isGitRepo()) {
|
|
3723
|
+
console.log(chalk11.yellow("\n\u26A0\uFE0F Not a git repository\n"));
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
const spinner = ora3("Analyzing changes...").start();
|
|
3727
|
+
try {
|
|
3728
|
+
const stagedDiff = getDiff(true);
|
|
3729
|
+
const unstagedDiff = getDiff(false);
|
|
3730
|
+
if (stagedDiff.files.length === 0 && unstagedDiff.files.length === 0) {
|
|
3731
|
+
spinner.stop();
|
|
3732
|
+
console.log(chalk11.gray("\n(no changes to analyze)\n"));
|
|
3733
|
+
return;
|
|
3734
|
+
}
|
|
3735
|
+
let diffContext = "";
|
|
3736
|
+
if (stagedDiff.files.length > 0) {
|
|
3737
|
+
diffContext += "## Staged Changes\n" + formatDiffForPrompt(stagedDiff) + "\n\n";
|
|
3738
|
+
}
|
|
3739
|
+
if (unstagedDiff.files.length > 0) {
|
|
3740
|
+
diffContext += "## Unstaged Changes\n" + formatDiffForPrompt(unstagedDiff);
|
|
3741
|
+
}
|
|
3742
|
+
const prompt = `Explain these code changes. What are the key modifications and their purpose?
|
|
3743
|
+
|
|
3744
|
+
${diffContext}`;
|
|
3745
|
+
const response = await llm.generate(prompt, BASE_CHAT_PROMPT);
|
|
3746
|
+
spinner.stop();
|
|
3747
|
+
console.log(chalk11.cyan.bold("\n\u{1F50D} Diff Analysis\n"));
|
|
3748
|
+
console.log(formatResponse(response.text) + "\n");
|
|
3749
|
+
} catch (error) {
|
|
3750
|
+
spinner.fail("Error analyzing diff");
|
|
3751
|
+
handleError(error);
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
async function handleGitCommit(llm) {
|
|
3755
|
+
if (!isGitRepo()) {
|
|
3756
|
+
console.log(chalk11.yellow("\n\u26A0\uFE0F Not a git repository\n"));
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
const status = getStatus();
|
|
3760
|
+
if (status.staged.length === 0) {
|
|
3761
|
+
console.log(chalk11.yellow("\n\u26A0\uFE0F No staged changes. Stage some changes first with `git add`.\n"));
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3764
|
+
const spinner = ora3("Generating commit message...").start();
|
|
3765
|
+
try {
|
|
3766
|
+
const stagedDiff = getDiff(true);
|
|
3767
|
+
const diffText = formatDiffForPrompt(stagedDiff);
|
|
3768
|
+
const prompt = `Generate a concise, conventional commit message for these staged changes. Use the format: type(scope): description
|
|
3769
|
+
|
|
3770
|
+
Types: feat, fix, docs, style, refactor, test, chore
|
|
3771
|
+
Keep the message under 72 characters.
|
|
3772
|
+
Return ONLY the commit message, nothing else.
|
|
3773
|
+
|
|
3774
|
+
Changes:
|
|
3775
|
+
${diffText}`;
|
|
3776
|
+
const response = await llm.generate(prompt, "You are a git commit message generator. Output ONLY the commit message, no explanation.");
|
|
3777
|
+
spinner.stop();
|
|
3778
|
+
const commitMessage = response.text.trim().split("\n")[0];
|
|
3779
|
+
console.log(chalk11.cyan.bold("\n\u{1F4AC} Generated Commit Message\n"));
|
|
3780
|
+
console.log(chalk11.white(` ${commitMessage}
|
|
3781
|
+
`));
|
|
3782
|
+
const shouldCommit = await confirm2({
|
|
3783
|
+
message: "Commit with this message?",
|
|
3784
|
+
default: true
|
|
3785
|
+
});
|
|
3786
|
+
if (shouldCommit) {
|
|
3787
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
3788
|
+
const env = getEnvironment();
|
|
3789
|
+
try {
|
|
3790
|
+
execSync2(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
|
|
3791
|
+
cwd: env.workspaceRoot,
|
|
3792
|
+
stdio: "inherit"
|
|
3793
|
+
});
|
|
3794
|
+
console.log(chalk11.green("\n\u2713 Committed successfully!\n"));
|
|
3795
|
+
} catch (gitError) {
|
|
3796
|
+
console.log(chalk11.red("\n\u2717 Commit failed. Check git output above.\n"));
|
|
3797
|
+
}
|
|
3798
|
+
} else {
|
|
3799
|
+
console.log(chalk11.gray("\n\u{1F44B} Commit cancelled.\n"));
|
|
3800
|
+
}
|
|
3801
|
+
} catch (error) {
|
|
3802
|
+
spinner.fail("Error generating commit");
|
|
3803
|
+
handleError(error);
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
2913
3806
|
function formatResponse(text) {
|
|
2914
3807
|
return text.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => {
|
|
2915
3808
|
const header = lang ? chalk11.gray(`\u2500\u2500 ${lang} \u2500\u2500`) : chalk11.gray("\u2500\u2500 code \u2500\u2500");
|
|
@@ -2922,7 +3815,19 @@ ${chalk11.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
|
2922
3815
|
}
|
|
2923
3816
|
function showHelp() {
|
|
2924
3817
|
console.log(chalk11.cyan.bold("\n\u{1F4D6} Commands\n"));
|
|
3818
|
+
console.log(chalk11.cyan(" Git Commands:"));
|
|
3819
|
+
console.log(chalk11.gray(" /status ") + "AI analysis of git status");
|
|
3820
|
+
console.log(chalk11.gray(" /diff ") + "AI explanation of current changes");
|
|
3821
|
+
console.log(chalk11.gray(" /commit ") + "Generate and run git commit");
|
|
3822
|
+
console.log("");
|
|
3823
|
+
console.log(chalk11.cyan(" Build Commands:"));
|
|
2925
3824
|
console.log(chalk11.gray(" /build ") + "Generate and execute an application");
|
|
3825
|
+
console.log("");
|
|
3826
|
+
console.log(chalk11.cyan(" Conversation:"));
|
|
3827
|
+
console.log(chalk11.gray(" /clear ") + "Clear conversation history");
|
|
3828
|
+
console.log(chalk11.gray(" /history ") + "Show recent conversation");
|
|
3829
|
+
console.log("");
|
|
3830
|
+
console.log(chalk11.cyan(" General:"));
|
|
2926
3831
|
console.log(chalk11.gray(" /model ") + "Change AI model");
|
|
2927
3832
|
console.log(chalk11.gray(" /chat ") + "Switch to chat mode");
|
|
2928
3833
|
console.log(chalk11.gray(" /help ") + "Show this help");
|
|
@@ -2951,7 +3856,7 @@ ${chalk12.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
|
|
|
2951
3856
|
${chalk12.cyan(` /____/ `)}
|
|
2952
3857
|
`;
|
|
2953
3858
|
var program = new Command();
|
|
2954
|
-
program.name("agdi").description(chalk12.cyan("\u{1F680} AI-powered coding assistant")).version("2.
|
|
3859
|
+
program.name("agdi").description(chalk12.cyan("\u{1F680} AI-powered coding assistant")).version("2.2.2").configureHelp({
|
|
2955
3860
|
// Show banner only when help is requested
|
|
2956
3861
|
formatHelp: (cmd, helper) => {
|
|
2957
3862
|
return BANNER + "\n" + chalk12.gray(" The Open Source AI Architect") + "\n" + chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n") + "\n" + helper.formatHelp(cmd, helper);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agdi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "AI-powered app generator - build full-stack apps from natural language in your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -62,4 +62,4 @@
|
|
|
62
62
|
"jszip": "^3.10.0",
|
|
63
63
|
"ora": "^8.0.0"
|
|
64
64
|
}
|
|
65
|
-
}
|
|
65
|
+
}
|