agdi 2.2.3 → 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.
Files changed (2) hide show
  1. package/dist/index.js +911 -6
  2. 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/codex.ts
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/commands/codex.ts
2737
- var CHAT_SYSTEM_PROMPT = `You are Agdi dev, an elite AI coding assistant. You help developers write code, debug issues, and build applications.
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("Commands: /model, /chat, /build, /help, /exit\n"));
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
- const response = await llm.generate(userInput, CHAT_SYSTEM_PROMPT);
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agdi",
3
- "version": "2.2.3",
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
+ }