agdi 3.1.2 β 3.2.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 +98 -63
- package/dist/index.js +153 -96
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -1,111 +1,146 @@
|
|
|
1
1
|
# Agdi CLI β The Autonomous AI Employee
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
|
-
<img src="https://agdi-dev.vercel.app/logo.svg" alt="Agdi Logo" width="
|
|
4
|
+
<img src="https://agdi-dev.vercel.app/logo.svg" alt="Agdi Logo" width="120" height="120">
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
###
|
|
6
|
+
# π¦Έ AGDI
|
|
7
|
+
### The Autonomous AI Software Squad in Your Terminal
|
|
8
8
|
|
|
9
|
-
[](https://www.npmjs.com/package/agdi)
|
|
10
|
+
[](https://github.com/anassagd432/Agdi-dev)
|
|
11
|
+
[](https://www.npmjs.com/package/agdi)
|
|
11
12
|
|
|
13
|
+
<p align="center">
|
|
14
|
+
<b>Build, Test, and Deploy Full-Stack SaaS Apps with a Single Prompt.</b><br>
|
|
15
|
+
Powered by Gemini 3, GPT-5, and Claude 4.5.
|
|
16
|
+
</p>
|
|
12
17
|
</div>
|
|
13
18
|
|
|
14
19
|
---
|
|
15
20
|
|
|
16
|
-
##
|
|
21
|
+
## β‘ What is Agdi?
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
npm install -g agdi
|
|
20
|
-
```
|
|
23
|
+
Agdi isn't just a code generator. It's an **autonomous software agency** that lives in your terminal. When you give it a prompt, it doesn't just spit out codeβit spins up a **Squad** of specialized AI agents that work together:
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
- **π§ The Manager:** Plans the architecture, breaks down tasks, and oversees the project.
|
|
26
|
+
- **π¨ The Designer:** Builds beautiful, responsive UI components (React + Tailwind + Shadcn).
|
|
27
|
+
- **βοΈ The Engineer:** Implements secure APIs, database schemas, and backend logic.
|
|
28
|
+
- **π΅οΈ The QA:** Runs builds, writes tests, detects errors, and **auto-fixes** them before you ever see the code.
|
|
29
|
+
- **π The DevOps:** Handles deployment to Vercel/Netlify automatically.
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## π Getting Started
|
|
34
|
+
|
|
35
|
+
### 1. Install via npm
|
|
25
36
|
```bash
|
|
26
|
-
agdi
|
|
37
|
+
npm install -g agdi
|
|
27
38
|
```
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
2. **π API Setup** β Configure Gemini/OpenAI/Anthropic keys
|
|
32
|
-
3. **π§ Prompt** β Describe what you want to build
|
|
33
|
-
|
|
34
|
-
### Direct Mode
|
|
40
|
+
### 2. Run the Wizard
|
|
41
|
+
The easiest way to start is the interactive wizard. It will guide you through authentication and project setup.
|
|
35
42
|
```bash
|
|
36
|
-
agdi
|
|
43
|
+
agdi
|
|
37
44
|
```
|
|
38
45
|
|
|
39
|
-
###
|
|
46
|
+
### 3. Build a SaaS in Seconds
|
|
47
|
+
Want to go fast? Use the direct build command:
|
|
40
48
|
```bash
|
|
41
|
-
agdi
|
|
49
|
+
agdi build "A project management tool with Kanban boards and team chat"
|
|
42
50
|
```
|
|
43
51
|
|
|
44
52
|
---
|
|
45
53
|
|
|
46
|
-
##
|
|
54
|
+
## π οΈ Features
|
|
55
|
+
|
|
56
|
+
### π’ The SaaS Blueprint (`--saas`)
|
|
57
|
+
Generate a production-ready SaaS foundation instantly:
|
|
58
|
+
- **Framework:** Next.js 15 (App Router)
|
|
59
|
+
- **Database:** Prisma + PostgreSQL
|
|
60
|
+
- **Auth:** Clerk / NextAuth
|
|
61
|
+
- **Payments:** Stripe Subscription integration
|
|
62
|
+
- **Styling:** Tailwind CSS + Shadcn UI
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
agdi build "AI-powered CRM for real estate agents" --saas
|
|
66
|
+
```
|
|
47
67
|
|
|
48
|
-
|
|
68
|
+
### π Auto-Healing Code
|
|
69
|
+
Agdi's QA agent runs in a loop. If the build fails:
|
|
70
|
+
1. It analyzes the error logs.
|
|
71
|
+
2. It reads the source code.
|
|
72
|
+
3. It generates a surgical fix.
|
|
73
|
+
4. It re-runs the build.
|
|
74
|
+
*You get working code, not error messages.*
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"modelRouting": {
|
|
55
|
-
"planning": "claude-opus-4",
|
|
56
|
-
"coding": "gemini-2.5-pro",
|
|
57
|
-
"chat": "groq/llama-3"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
76
|
+
### π¦ Import & Refactor
|
|
77
|
+
Have an existing repo? Import it and ask Agdi to add features.
|
|
78
|
+
```bash
|
|
79
|
+
agdi import https://github.com/user/repo
|
|
60
80
|
```
|
|
61
81
|
|
|
62
82
|
---
|
|
63
83
|
|
|
64
|
-
##
|
|
84
|
+
## π€ Supported Models
|
|
65
85
|
|
|
66
|
-
|
|
86
|
+
Agdi supports the absolute bleeding edge of AI models via a unified interface:
|
|
67
87
|
|
|
68
|
-
|
|
|
69
|
-
|
|
70
|
-
| **
|
|
71
|
-
| **
|
|
72
|
-
| **
|
|
88
|
+
| Provider | Models Supported | Best For |
|
|
89
|
+
|----------|------------------|----------|
|
|
90
|
+
| **Google** | Gemini 3 Pro, 2.5 Flash | **Speed & Context** (Recommended) |
|
|
91
|
+
| **OpenAI** | GPT-5, GPT-4o, o1 | **Complex Logic** |
|
|
92
|
+
| **Anthropic** | Claude 3.5 Sonnet, Opus | **Coding & Architecture** |
|
|
93
|
+
| **DeepSeek** | DeepSeek V3, R1 | **Reasoning & Cost Efficiency** |
|
|
94
|
+
| **OpenRouter**| 100+ Models | **Flexibility** |
|
|
73
95
|
|
|
74
96
|
---
|
|
75
97
|
|
|
76
|
-
##
|
|
98
|
+
## βοΈ Advanced Usage
|
|
77
99
|
|
|
78
|
-
|
|
100
|
+
### Squad Mode (Multi-Agent)
|
|
101
|
+
For complex projects, invoke the full squad explicitly:
|
|
102
|
+
```bash
|
|
103
|
+
agdi squad "Crypto portfolio tracker with real-time websocket updates"
|
|
104
|
+
```
|
|
79
105
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
106
|
+
### Config Management
|
|
107
|
+
View or edit your API keys and settings:
|
|
108
|
+
```bash
|
|
109
|
+
agdi config
|
|
110
|
+
```
|
|
111
|
+
Enable telemetry to help us debug crashes (fully anonymous):
|
|
112
|
+
```bash
|
|
113
|
+
agdi config telemetry --enable
|
|
114
|
+
```
|
|
84
115
|
|
|
85
116
|
---
|
|
86
117
|
|
|
87
|
-
##
|
|
118
|
+
## π‘οΈ Security
|
|
88
119
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
| `agdi --help` | Show all options |
|
|
94
|
-
| `agdi --version` | Show version |
|
|
120
|
+
Agdi takes security seriously.
|
|
121
|
+
- **Zero-Trust:** It never executes dangerous shell commands without permission (unless `--yes` is used).
|
|
122
|
+
- **Local Keys:** API keys are stored in `~/.agdi/config.json` with `0600` permissions (readable only by you).
|
|
123
|
+
- **Sandboxed:** Code generation happens in your specified directory, never outside it.
|
|
95
124
|
|
|
96
125
|
---
|
|
97
126
|
|
|
98
|
-
##
|
|
127
|
+
## β Troubleshooting
|
|
99
128
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
| `GEMINI_API_KEY` | Google Gemini API key |
|
|
103
|
-
| `OPENAI_API_KEY` | OpenAI API key |
|
|
104
|
-
| `ANTHROPIC_API_KEY` | Anthropic API key |
|
|
105
|
-
| `AGDI_DEBUG` | Enable debug logging |
|
|
129
|
+
**"API Key Invalid"**
|
|
130
|
+
Run `agdi auth` to re-enter your keys. Ensure your plan covers the model you selected.
|
|
106
131
|
|
|
107
|
-
|
|
132
|
+
**"Build Failed"**
|
|
133
|
+
If the auto-healer gives up, check the logs in `runs/<id>/report.md`. You can often fix the small typo manually and run `npm run dev`.
|
|
108
134
|
|
|
109
|
-
|
|
135
|
+
**"Quota Exceeded"**
|
|
136
|
+
Switch to a cheaper model or provider:
|
|
137
|
+
```bash
|
|
138
|
+
agdi model
|
|
139
|
+
```
|
|
110
140
|
|
|
111
|
-
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
<div align="center">
|
|
144
|
+
<p>Built with β€οΈ by Agdi Systems Inc.</p>
|
|
145
|
+
<p>2026 Edition</p>
|
|
146
|
+
</div>
|
package/dist/index.js
CHANGED
|
@@ -2749,6 +2749,8 @@ Requirements:
|
|
|
2749
2749
|
// src/agents/core/qa-agent.ts
|
|
2750
2750
|
import { exec } from "child_process";
|
|
2751
2751
|
import { promisify } from "util";
|
|
2752
|
+
import * as fs3 from "fs/promises";
|
|
2753
|
+
import * as path3 from "path";
|
|
2752
2754
|
var execAsync = promisify(exec);
|
|
2753
2755
|
var QA_SYSTEM_PROMPT = `You are a SENIOR QA ENGINEER and DEBUGGER with expertise in:
|
|
2754
2756
|
- TypeScript/JavaScript debugging
|
|
@@ -2758,10 +2760,10 @@ var QA_SYSTEM_PROMPT = `You are a SENIOR QA ENGINEER and DEBUGGER with expertise
|
|
|
2758
2760
|
- ESLint / TypeScript errors
|
|
2759
2761
|
|
|
2760
2762
|
Your responsibilities:
|
|
2761
|
-
1.
|
|
2762
|
-
2.
|
|
2763
|
-
3.
|
|
2764
|
-
4.
|
|
2763
|
+
1. Analyze build/test errors
|
|
2764
|
+
2. Read the provided source code context
|
|
2765
|
+
3. Diagnose the root cause (e.g., missing prop, type mismatch, bad import)
|
|
2766
|
+
4. Generate SURGICAL fixes
|
|
2765
2767
|
|
|
2766
2768
|
When fixing errors, you MUST:
|
|
2767
2769
|
1. Quote the exact error message
|
|
@@ -2776,10 +2778,13 @@ import React from 'react';
|
|
|
2776
2778
|
// ... fixed code
|
|
2777
2779
|
\`\`\`
|
|
2778
2780
|
|
|
2779
|
-
|
|
2780
|
-
-
|
|
2781
|
+
IMPORTANT:
|
|
2782
|
+
- Do NOT generate "placeholder" fixes.
|
|
2783
|
+
- If an import is missing, make sure the file actually exists or fix the path.
|
|
2784
|
+
- If a type is missing, check where it is defined.
|
|
2781
2785
|
|
|
2782
|
-
|
|
2786
|
+
Sources:
|
|
2787
|
+
- If you reference any external fixes or docs, list their URLs in a "Sources:" section at the end.`;
|
|
2783
2788
|
var QAAgent = class extends BaseAgent {
|
|
2784
2789
|
maxFixAttempts = 3;
|
|
2785
2790
|
constructor(llm, options = {}) {
|
|
@@ -2796,8 +2801,8 @@ var QAAgent = class extends BaseAgent {
|
|
|
2796
2801
|
try {
|
|
2797
2802
|
const { stdout, stderr } = await execAsync("npm run build", {
|
|
2798
2803
|
cwd,
|
|
2799
|
-
timeout:
|
|
2800
|
-
//
|
|
2804
|
+
timeout: 3e5
|
|
2805
|
+
// 5 minutes
|
|
2801
2806
|
});
|
|
2802
2807
|
return {
|
|
2803
2808
|
success: true,
|
|
@@ -2819,7 +2824,7 @@ var QAAgent = class extends BaseAgent {
|
|
|
2819
2824
|
try {
|
|
2820
2825
|
const { stdout, stderr } = await execAsync("npm test -- --run", {
|
|
2821
2826
|
cwd,
|
|
2822
|
-
timeout:
|
|
2827
|
+
timeout: 3e5
|
|
2823
2828
|
});
|
|
2824
2829
|
return { success: true, output: stdout + stderr };
|
|
2825
2830
|
} catch (error) {
|
|
@@ -2830,27 +2835,61 @@ var QAAgent = class extends BaseAgent {
|
|
|
2830
2835
|
};
|
|
2831
2836
|
}
|
|
2832
2837
|
}
|
|
2838
|
+
/**
|
|
2839
|
+
* Extract file paths from error log
|
|
2840
|
+
*/
|
|
2841
|
+
extractFilePaths(errorLog) {
|
|
2842
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2843
|
+
const regex = /((?:src|app|pages|lib|components)\/[a-zA-Z0-9_\-\/\.]+\.(?:ts|tsx|js|jsx|json))/g;
|
|
2844
|
+
const matches = errorLog.matchAll(regex);
|
|
2845
|
+
for (const match of matches) {
|
|
2846
|
+
paths.add(match[1]);
|
|
2847
|
+
}
|
|
2848
|
+
return paths;
|
|
2849
|
+
}
|
|
2850
|
+
/**
|
|
2851
|
+
* Read file content for context
|
|
2852
|
+
*/
|
|
2853
|
+
async getFileContext(cwd, paths) {
|
|
2854
|
+
if (paths.size === 0) return "";
|
|
2855
|
+
const contextParts = [];
|
|
2856
|
+
for (const filePath of paths) {
|
|
2857
|
+
try {
|
|
2858
|
+
const fullPath = path3.join(cwd, filePath);
|
|
2859
|
+
const content = await fs3.readFile(fullPath, "utf-8");
|
|
2860
|
+
contextParts.push(`// File: ${filePath}
|
|
2861
|
+
${content}`);
|
|
2862
|
+
} catch (e) {
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
return contextParts.join("\n\n");
|
|
2866
|
+
}
|
|
2833
2867
|
/**
|
|
2834
2868
|
* Diagnose build errors and generate fixes
|
|
2835
2869
|
*/
|
|
2836
|
-
async diagnoseAndFix(errorOutput) {
|
|
2837
|
-
this.log("Analyzing errors...");
|
|
2870
|
+
async diagnoseAndFix(cwd, errorOutput) {
|
|
2871
|
+
this.log("Analyzing errors and reading source files...");
|
|
2872
|
+
const problematicFiles = this.extractFilePaths(errorOutput);
|
|
2873
|
+
this.log(`Identified ${problematicFiles.size} problematic files: ${Array.from(problematicFiles).join(", ")}`);
|
|
2874
|
+
const fileContext = await this.getFileContext(cwd, problematicFiles);
|
|
2838
2875
|
const response = await this.think(`
|
|
2839
2876
|
The following build/test errors occurred:
|
|
2840
2877
|
|
|
2841
2878
|
\`\`\`
|
|
2842
|
-
${errorOutput.slice(0,
|
|
2879
|
+
${errorOutput.slice(0, 5e3)}
|
|
2843
2880
|
\`\`\`
|
|
2844
2881
|
|
|
2845
|
-
|
|
2846
|
-
For each error:
|
|
2847
|
-
1. Quote the exact error
|
|
2848
|
-
2. Identify the file and line
|
|
2849
|
-
3. Explain the fix
|
|
2850
|
-
4. Provide the corrected code
|
|
2882
|
+
Here is the content of the files mentioned in the errors:
|
|
2851
2883
|
|
|
2852
|
-
|
|
2853
|
-
|
|
2884
|
+
\`\`\`typescript
|
|
2885
|
+
${fileContext.slice(0, 2e4)}
|
|
2886
|
+
\`\`\`
|
|
2887
|
+
|
|
2888
|
+
Analyze these errors and the provided code.
|
|
2889
|
+
Diagnose the root cause and provide SURGICAL fixes.
|
|
2890
|
+
For example, if a property is missing in a component prop type, fix the interface.
|
|
2891
|
+
If an import is wrong, correct it.
|
|
2892
|
+
`);
|
|
2854
2893
|
const fixes = this.extractCodeBlocks(response);
|
|
2855
2894
|
this.log(`Generated ${fixes.length} fixes`, fixes.length > 0 ? "success" : "warn");
|
|
2856
2895
|
return fixes;
|
|
@@ -2882,7 +2921,7 @@ Only fix what's broken. Keep the rest intact.
|
|
|
2882
2921
|
commandOutputs
|
|
2883
2922
|
};
|
|
2884
2923
|
} else {
|
|
2885
|
-
const testFixes = await this.diagnoseAndFix(testResult.output);
|
|
2924
|
+
const testFixes = await this.diagnoseAndFix(cwd, testResult.output);
|
|
2886
2925
|
allFixes.push(...testFixes);
|
|
2887
2926
|
if (testFixes.length === 0) {
|
|
2888
2927
|
errors.push(`Tests failed but couldn't generate fixes: ${testResult.output.slice(0, 500)}`);
|
|
@@ -2892,12 +2931,22 @@ Only fix what's broken. Keep the rest intact.
|
|
|
2892
2931
|
}
|
|
2893
2932
|
}
|
|
2894
2933
|
this.log("Build failed, diagnosing...", "warn");
|
|
2895
|
-
const fixes = await this.diagnoseAndFix(buildResult.output);
|
|
2934
|
+
const fixes = await this.diagnoseAndFix(cwd, buildResult.output);
|
|
2896
2935
|
if (fixes.length === 0) {
|
|
2897
2936
|
errors.push(`Build failed but couldn't generate fixes: ${buildResult.output.slice(0, 500)}`);
|
|
2898
2937
|
break;
|
|
2899
2938
|
}
|
|
2900
2939
|
allFixes.push(...fixes);
|
|
2940
|
+
for (const fix of fixes) {
|
|
2941
|
+
try {
|
|
2942
|
+
const fullPath = path3.join(cwd, fix.path);
|
|
2943
|
+
await fs3.mkdir(path3.dirname(fullPath), { recursive: true });
|
|
2944
|
+
await fs3.writeFile(fullPath, fix.content, "utf-8");
|
|
2945
|
+
this.log(` Applied fix to: ${fix.path}`, "info");
|
|
2946
|
+
} catch (e) {
|
|
2947
|
+
this.log(`Failed to apply fix to ${fix.path}: ${e}`, "error");
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2901
2950
|
}
|
|
2902
2951
|
return {
|
|
2903
2952
|
success: false,
|
|
@@ -2933,13 +2982,13 @@ Only fix what's broken. Keep the rest intact.
|
|
|
2933
2982
|
// src/agents/core/devops-agent.ts
|
|
2934
2983
|
import { exec as exec2 } from "child_process";
|
|
2935
2984
|
import { promisify as promisify2 } from "util";
|
|
2936
|
-
import
|
|
2937
|
-
import
|
|
2985
|
+
import fs4 from "fs/promises";
|
|
2986
|
+
import path4 from "path";
|
|
2938
2987
|
var execAsync2 = promisify2(exec2);
|
|
2939
2988
|
async function detectBuildOutputDir(cwd) {
|
|
2940
2989
|
try {
|
|
2941
|
-
const pkgPath =
|
|
2942
|
-
const raw = await
|
|
2990
|
+
const pkgPath = path4.join(cwd, "package.json");
|
|
2991
|
+
const raw = await fs4.readFile(pkgPath, "utf-8");
|
|
2943
2992
|
const pkg = JSON.parse(raw);
|
|
2944
2993
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2945
2994
|
if (deps["next"]) return ".next";
|
|
@@ -3270,8 +3319,8 @@ URL: ${deployResult.url || "See dashboard"}`,
|
|
|
3270
3319
|
};
|
|
3271
3320
|
|
|
3272
3321
|
// src/agents/core/squad-orchestrator.ts
|
|
3273
|
-
import * as
|
|
3274
|
-
import * as
|
|
3322
|
+
import * as fs5 from "fs/promises";
|
|
3323
|
+
import * as path5 from "path";
|
|
3275
3324
|
import crypto2 from "crypto";
|
|
3276
3325
|
var SquadOrchestrator = class {
|
|
3277
3326
|
manager;
|
|
@@ -3409,7 +3458,7 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3409
3458
|
this.log("\u2705 Codebase is stable.", "success");
|
|
3410
3459
|
}
|
|
3411
3460
|
let deploymentUrl;
|
|
3412
|
-
if (this.config.autoDeploy) {
|
|
3461
|
+
if (healthy && this.config.autoDeploy) {
|
|
3413
3462
|
this.log("\u{1F680} Phase 4: Deployment", "phase");
|
|
3414
3463
|
const devopsTask = this.tasks.find((t) => t.assignee === "devops");
|
|
3415
3464
|
if (devopsTask) {
|
|
@@ -3471,7 +3520,15 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3471
3520
|
(t) => t.assignee !== "qa" && t.assignee !== "devops"
|
|
3472
3521
|
);
|
|
3473
3522
|
const pending = /* @__PURE__ */ new Map();
|
|
3474
|
-
coreTasks.forEach((task) =>
|
|
3523
|
+
coreTasks.forEach((task) => {
|
|
3524
|
+
if (!this.completedTasks.has(task.id)) {
|
|
3525
|
+
pending.set(task.id, task);
|
|
3526
|
+
}
|
|
3527
|
+
});
|
|
3528
|
+
if (pending.size === 0) {
|
|
3529
|
+
this.log("No new tasks to execute.", "info");
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3475
3532
|
const runTask = async (task) => {
|
|
3476
3533
|
const taskStart = Date.now();
|
|
3477
3534
|
const result = await this.executeTask(task);
|
|
@@ -3561,24 +3618,24 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3561
3618
|
*/
|
|
3562
3619
|
async writeFiles(files, filesCreated, filesModified) {
|
|
3563
3620
|
for (const file of files) {
|
|
3564
|
-
const fullPath =
|
|
3565
|
-
const dir =
|
|
3621
|
+
const fullPath = path5.join(this.config.workspaceRoot, file.path);
|
|
3622
|
+
const dir = path5.dirname(fullPath);
|
|
3566
3623
|
try {
|
|
3567
|
-
await
|
|
3624
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
3568
3625
|
let existed = false;
|
|
3569
3626
|
let beforeContent = null;
|
|
3570
3627
|
try {
|
|
3571
|
-
await
|
|
3628
|
+
await fs5.access(fullPath);
|
|
3572
3629
|
existed = true;
|
|
3573
|
-
beforeContent = await
|
|
3630
|
+
beforeContent = await fs5.readFile(fullPath, "utf-8");
|
|
3574
3631
|
} catch {
|
|
3575
3632
|
existed = false;
|
|
3576
3633
|
}
|
|
3577
|
-
await
|
|
3634
|
+
await fs5.writeFile(fullPath, file.content, "utf-8");
|
|
3578
3635
|
const afterContent = file.content;
|
|
3579
|
-
const outputPath =
|
|
3580
|
-
await
|
|
3581
|
-
await
|
|
3636
|
+
const outputPath = path5.join(this.runDir, "outputs", file.path);
|
|
3637
|
+
await fs5.mkdir(path5.dirname(outputPath), { recursive: true });
|
|
3638
|
+
await fs5.writeFile(outputPath, file.content, "utf-8");
|
|
3582
3639
|
const beforeHash = beforeContent ? this.hashContent(beforeContent) : null;
|
|
3583
3640
|
const afterHash = this.hashContent(afterContent);
|
|
3584
3641
|
await this.appendTrace("file_write", {
|
|
@@ -3605,17 +3662,17 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3605
3662
|
}
|
|
3606
3663
|
async initializeRun(userPrompt) {
|
|
3607
3664
|
this.runId = uuidv42();
|
|
3608
|
-
this.runDir =
|
|
3609
|
-
this.tracePath =
|
|
3610
|
-
this.reportPath =
|
|
3611
|
-
this.sourcesPath =
|
|
3612
|
-
this.snapshotManifestPath =
|
|
3613
|
-
this.runConfigPath =
|
|
3614
|
-
const outputsDir =
|
|
3615
|
-
await
|
|
3616
|
-
await
|
|
3617
|
-
await
|
|
3618
|
-
await
|
|
3665
|
+
this.runDir = path5.join(this.config.workspaceRoot, "runs", this.runId);
|
|
3666
|
+
this.tracePath = path5.join(this.runDir, "trace.jsonl");
|
|
3667
|
+
this.reportPath = path5.join(this.runDir, "report.md");
|
|
3668
|
+
this.sourcesPath = path5.join(this.runDir, "sources.json");
|
|
3669
|
+
this.snapshotManifestPath = path5.join(this.runDir, "snapshot", "manifest.json");
|
|
3670
|
+
this.runConfigPath = path5.join(this.runDir, "run.json");
|
|
3671
|
+
const outputsDir = path5.join(this.runDir, "outputs");
|
|
3672
|
+
await fs5.mkdir(this.runDir, { recursive: true });
|
|
3673
|
+
await fs5.mkdir(path5.join(this.runDir, "snapshot"), { recursive: true });
|
|
3674
|
+
await fs5.mkdir(outputsDir, { recursive: true });
|
|
3675
|
+
await fs5.writeFile(this.runConfigPath, JSON.stringify({
|
|
3619
3676
|
runId: this.runId,
|
|
3620
3677
|
prompt: userPrompt,
|
|
3621
3678
|
config: {
|
|
@@ -3650,7 +3707,7 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3650
3707
|
errors: result.errors,
|
|
3651
3708
|
deploymentUrl: result.deploymentUrl
|
|
3652
3709
|
});
|
|
3653
|
-
await
|
|
3710
|
+
await fs5.writeFile(this.sourcesPath, JSON.stringify(Array.from(this.sources).sort(), null, 2), "utf-8");
|
|
3654
3711
|
const diffSummary = await this.generateDiffReport();
|
|
3655
3712
|
const reportLines = [
|
|
3656
3713
|
`# Agdi Squad Run Report`,
|
|
@@ -3676,13 +3733,13 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3676
3733
|
result.errors.length ? `## Errors` : void 0,
|
|
3677
3734
|
...result.errors.length ? result.errors.map((e) => `- ${e}`) : []
|
|
3678
3735
|
].filter(Boolean).join("\n");
|
|
3679
|
-
await
|
|
3736
|
+
await fs5.writeFile(this.reportPath, reportLines, "utf-8");
|
|
3680
3737
|
}
|
|
3681
3738
|
async appendTrace(event, data) {
|
|
3682
3739
|
const entry = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), event, data };
|
|
3683
3740
|
this.traceBuffer.push(entry);
|
|
3684
3741
|
if (!this.tracePath) return;
|
|
3685
|
-
await
|
|
3742
|
+
await fs5.appendFile(this.tracePath, JSON.stringify(entry) + "\n", "utf-8");
|
|
3686
3743
|
}
|
|
3687
3744
|
hashContent(content) {
|
|
3688
3745
|
return crypto2.createHash("sha256").update(content).digest("hex");
|
|
@@ -3697,15 +3754,15 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3697
3754
|
const ignore = /* @__PURE__ */ new Set(["node_modules", ".git", "runs", "dist", "build", ".next"]);
|
|
3698
3755
|
const manifest = [];
|
|
3699
3756
|
const walk = async (dir) => {
|
|
3700
|
-
const entries = await
|
|
3757
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
3701
3758
|
for (const entry of entries) {
|
|
3702
3759
|
if (ignore.has(entry.name)) continue;
|
|
3703
|
-
const fullPath =
|
|
3704
|
-
const relPath =
|
|
3760
|
+
const fullPath = path5.join(dir, entry.name);
|
|
3761
|
+
const relPath = path5.relative(snapshotRoot, fullPath);
|
|
3705
3762
|
if (entry.isDirectory()) {
|
|
3706
3763
|
await walk(fullPath);
|
|
3707
3764
|
} else if (entry.isFile()) {
|
|
3708
|
-
const buffer = await
|
|
3765
|
+
const buffer = await fs5.readFile(fullPath);
|
|
3709
3766
|
const content = buffer.toString("utf-8");
|
|
3710
3767
|
manifest.push({
|
|
3711
3768
|
path: relPath,
|
|
@@ -3716,23 +3773,23 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3716
3773
|
}
|
|
3717
3774
|
};
|
|
3718
3775
|
await walk(snapshotRoot);
|
|
3719
|
-
await
|
|
3776
|
+
await fs5.writeFile(this.snapshotManifestPath, JSON.stringify({
|
|
3720
3777
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3721
3778
|
root: snapshotRoot,
|
|
3722
3779
|
files: manifest
|
|
3723
3780
|
}, null, 2), "utf-8");
|
|
3724
3781
|
await this.appendTrace("snapshot", {
|
|
3725
3782
|
files: manifest.length,
|
|
3726
|
-
manifestPath:
|
|
3783
|
+
manifestPath: path5.relative(this.config.workspaceRoot, this.snapshotManifestPath)
|
|
3727
3784
|
});
|
|
3728
3785
|
}
|
|
3729
3786
|
async generateDiffReport() {
|
|
3730
3787
|
const snapshotRoot = this.config.workspaceRoot;
|
|
3731
3788
|
const ignore = /* @__PURE__ */ new Set(["node_modules", ".git", "runs", "dist", "build", ".next"]);
|
|
3732
|
-
const diffPath =
|
|
3789
|
+
const diffPath = path5.join(this.runDir, "diff.json");
|
|
3733
3790
|
let snapshotData = null;
|
|
3734
3791
|
try {
|
|
3735
|
-
const raw = await
|
|
3792
|
+
const raw = await fs5.readFile(this.snapshotManifestPath, "utf-8");
|
|
3736
3793
|
snapshotData = JSON.parse(raw);
|
|
3737
3794
|
} catch {
|
|
3738
3795
|
snapshotData = { files: [] };
|
|
@@ -3740,15 +3797,15 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3740
3797
|
const snapshotMap = new Map(snapshotData.files.map((file) => [file.path, file.hash]));
|
|
3741
3798
|
const currentMap = /* @__PURE__ */ new Map();
|
|
3742
3799
|
const walk = async (dir) => {
|
|
3743
|
-
const entries = await
|
|
3800
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
3744
3801
|
for (const entry of entries) {
|
|
3745
3802
|
if (ignore.has(entry.name)) continue;
|
|
3746
|
-
const fullPath =
|
|
3747
|
-
const relPath =
|
|
3803
|
+
const fullPath = path5.join(dir, entry.name);
|
|
3804
|
+
const relPath = path5.relative(snapshotRoot, fullPath);
|
|
3748
3805
|
if (entry.isDirectory()) {
|
|
3749
3806
|
await walk(fullPath);
|
|
3750
3807
|
} else if (entry.isFile()) {
|
|
3751
|
-
const buffer = await
|
|
3808
|
+
const buffer = await fs5.readFile(fullPath);
|
|
3752
3809
|
const content = buffer.toString("utf-8");
|
|
3753
3810
|
currentMap.set(relPath, this.hashContent(content));
|
|
3754
3811
|
}
|
|
@@ -3775,12 +3832,12 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
3775
3832
|
modified,
|
|
3776
3833
|
deleted
|
|
3777
3834
|
};
|
|
3778
|
-
await
|
|
3835
|
+
await fs5.writeFile(diffPath, JSON.stringify(diffPayload, null, 2), "utf-8");
|
|
3779
3836
|
await this.appendTrace("diff", {
|
|
3780
3837
|
created: created.length,
|
|
3781
3838
|
modified: modified.length,
|
|
3782
3839
|
deleted: deleted.length,
|
|
3783
|
-
diffPath:
|
|
3840
|
+
diffPath: path5.relative(this.config.workspaceRoot, diffPath)
|
|
3784
3841
|
});
|
|
3785
3842
|
return diffPayload;
|
|
3786
3843
|
}
|
|
@@ -3892,8 +3949,8 @@ Constraints: Build a production SaaS using Next.js App Router, Prisma, Postgres,
|
|
|
3892
3949
|
// src/commands/import.ts
|
|
3893
3950
|
import chalk12 from "chalk";
|
|
3894
3951
|
import ora5 from "ora";
|
|
3895
|
-
import { mkdir as
|
|
3896
|
-
import { join as
|
|
3952
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
3953
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
3897
3954
|
function sanitizeRelativePath(p) {
|
|
3898
3955
|
if (!p || p.startsWith("/") || p.match(/^[A-Za-z]:/)) return null;
|
|
3899
3956
|
const normalized = p.replace(/\\/g, "/");
|
|
@@ -3910,8 +3967,8 @@ function parseGitHubUrl(url) {
|
|
|
3910
3967
|
if (!cleanUrl.startsWith("github.com/")) {
|
|
3911
3968
|
throw new Error("Invalid GitHub URL. Please use format: https://github.com/owner/repo");
|
|
3912
3969
|
}
|
|
3913
|
-
const
|
|
3914
|
-
const parts =
|
|
3970
|
+
const path8 = cleanUrl.replace("github.com/", "");
|
|
3971
|
+
const parts = path8.split("/");
|
|
3915
3972
|
if (parts.length < 2) {
|
|
3916
3973
|
throw new Error("Invalid GitHub URL. Could not extract owner/repo.");
|
|
3917
3974
|
}
|
|
@@ -3979,10 +4036,10 @@ async function extractZipToFiles(buffer) {
|
|
|
3979
4036
|
}
|
|
3980
4037
|
async function writeFiles(files, outputDir) {
|
|
3981
4038
|
for (const file of files) {
|
|
3982
|
-
const fullPath =
|
|
3983
|
-
const dir =
|
|
3984
|
-
await
|
|
3985
|
-
await
|
|
4039
|
+
const fullPath = join4(outputDir, file.path);
|
|
4040
|
+
const dir = dirname3(fullPath);
|
|
4041
|
+
await mkdir3(dir, { recursive: true });
|
|
4042
|
+
await writeFile3(fullPath, file.content, "utf-8");
|
|
3986
4043
|
}
|
|
3987
4044
|
}
|
|
3988
4045
|
async function runImportCommand(url, outputDir) {
|
|
@@ -4021,9 +4078,9 @@ ${msg}
|
|
|
4021
4078
|
// src/commands/wizard.ts
|
|
4022
4079
|
import { input as input5, select as select3, confirm, password as password3 } from "@inquirer/prompts";
|
|
4023
4080
|
import chalk13 from "chalk";
|
|
4024
|
-
import
|
|
4081
|
+
import path6 from "path";
|
|
4025
4082
|
import os from "os";
|
|
4026
|
-
import
|
|
4083
|
+
import fs6 from "fs";
|
|
4027
4084
|
async function runWizard() {
|
|
4028
4085
|
await checkSafety();
|
|
4029
4086
|
if (needsOnboarding()) {
|
|
@@ -4118,7 +4175,7 @@ Rewrite the original request into a comprehensive technical specification for a
|
|
|
4118
4175
|
async function checkSafety() {
|
|
4119
4176
|
const cwd = process.cwd();
|
|
4120
4177
|
const home = os.homedir();
|
|
4121
|
-
const root =
|
|
4178
|
+
const root = path6.parse(cwd).root;
|
|
4122
4179
|
const isUnsafe = cwd === home || cwd === root;
|
|
4123
4180
|
if (isUnsafe) {
|
|
4124
4181
|
console.log(chalk13.yellow.bold("\n\u26A0\uFE0F Safety Warning"));
|
|
@@ -4141,9 +4198,9 @@ async function checkSafety() {
|
|
|
4141
4198
|
default: "my-agdi-app",
|
|
4142
4199
|
validate: (v) => /^[a-z0-9-_]+$/i.test(v) ? true : "Invalid folder name"
|
|
4143
4200
|
});
|
|
4144
|
-
const newPath =
|
|
4145
|
-
if (!
|
|
4146
|
-
|
|
4201
|
+
const newPath = path6.join(cwd, folderName);
|
|
4202
|
+
if (!fs6.existsSync(newPath)) {
|
|
4203
|
+
fs6.mkdirSync(newPath);
|
|
4147
4204
|
}
|
|
4148
4205
|
process.chdir(newPath);
|
|
4149
4206
|
console.log(chalk13.green(`
|
|
@@ -4226,14 +4283,14 @@ async function setupOptionalTools() {
|
|
|
4226
4283
|
|
|
4227
4284
|
// src/commands/replay.ts
|
|
4228
4285
|
import chalk14 from "chalk";
|
|
4229
|
-
import * as
|
|
4230
|
-
import * as
|
|
4286
|
+
import * as fs7 from "fs/promises";
|
|
4287
|
+
import * as path7 from "path";
|
|
4231
4288
|
async function runReplayCommand(runId, llm, options = {}) {
|
|
4232
4289
|
const workspaceRoot = options.workspace || process.cwd();
|
|
4233
|
-
const runConfigPath =
|
|
4234
|
-
const outputsDir =
|
|
4290
|
+
const runConfigPath = path7.join(workspaceRoot, "runs", runId, "run.json");
|
|
4291
|
+
const outputsDir = path7.join(workspaceRoot, "runs", runId, "outputs");
|
|
4235
4292
|
try {
|
|
4236
|
-
const raw = await
|
|
4293
|
+
const raw = await fs7.readFile(runConfigPath, "utf-8");
|
|
4237
4294
|
const runConfig = JSON.parse(raw);
|
|
4238
4295
|
if (!runConfig.prompt) {
|
|
4239
4296
|
console.log(chalk14.red("\u274C Invalid run.json (missing prompt)"));
|
|
@@ -4249,22 +4306,22 @@ async function runReplayCommand(runId, llm, options = {}) {
|
|
|
4249
4306
|
\u{1F501} Replaying run ${runId}`));
|
|
4250
4307
|
if (options.exact !== false) {
|
|
4251
4308
|
try {
|
|
4252
|
-
const entries = await
|
|
4309
|
+
const entries = await fs7.readdir(outputsDir, { withFileTypes: true });
|
|
4253
4310
|
if (entries.length === 0) {
|
|
4254
4311
|
throw new Error("No outputs found");
|
|
4255
4312
|
}
|
|
4256
4313
|
const copyOutputs = async (dir, rel = "") => {
|
|
4257
|
-
const files = await
|
|
4314
|
+
const files = await fs7.readdir(dir, { withFileTypes: true });
|
|
4258
4315
|
for (const file of files) {
|
|
4259
|
-
const sourcePath =
|
|
4260
|
-
const targetRel =
|
|
4261
|
-
const targetPath =
|
|
4316
|
+
const sourcePath = path7.join(dir, file.name);
|
|
4317
|
+
const targetRel = path7.join(rel, file.name);
|
|
4318
|
+
const targetPath = path7.join(workspaceRoot, targetRel);
|
|
4262
4319
|
if (file.isDirectory()) {
|
|
4263
4320
|
await copyOutputs(sourcePath, targetRel);
|
|
4264
4321
|
} else if (file.isFile()) {
|
|
4265
|
-
await
|
|
4266
|
-
const content = await
|
|
4267
|
-
await
|
|
4322
|
+
await fs7.mkdir(path7.dirname(targetPath), { recursive: true });
|
|
4323
|
+
const content = await fs7.readFile(sourcePath, "utf-8");
|
|
4324
|
+
await fs7.writeFile(targetPath, content, "utf-8");
|
|
4268
4325
|
}
|
|
4269
4326
|
}
|
|
4270
4327
|
};
|
|
@@ -4307,7 +4364,7 @@ ${chalk15.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
|
|
|
4307
4364
|
${chalk15.cyan(` /____/ `)}
|
|
4308
4365
|
`;
|
|
4309
4366
|
var program = new Command();
|
|
4310
|
-
program.name("agdi").description(chalk15.cyan("\u{1F9B8} The Autonomous AI Employee")).version("3.
|
|
4367
|
+
program.name("agdi").description(chalk15.cyan("\u{1F9B8} The Autonomous AI Employee")).version("3.2.0").option("-y, --yes", "Auto-approve all prompts (headless/CI mode)").option("-m, --minimal", "Generate only the requested file(s), not a full app").option("-d, --dry-run", "Show what would be created without writing files").option("--saas", "Generate a production SaaS blueprint (Next.js + Prisma + Postgres + Stripe)");
|
|
4311
4368
|
program.hook("preAction", (thisCommand) => {
|
|
4312
4369
|
const opts = thisCommand.opts();
|
|
4313
4370
|
if (opts.yes) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agdi",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.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": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsup src/index.ts --format esm --clean",
|
|
22
22
|
"dev": "tsx src/index.ts",
|
|
23
|
-
"start": "
|
|
23
|
+
"start": "tsx src/dev.ts",
|
|
24
24
|
"test": "vitest run",
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
26
|
"prepublishOnly": "npm run build"
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@types/node": "^20.0.0",
|
|
61
61
|
"@types/uuid": "^10.0.0",
|
|
62
62
|
"tsup": "^8.0.0",
|
|
63
|
-
"tsx": "^4.
|
|
63
|
+
"tsx": "^4.21.0",
|
|
64
64
|
"typescript": "^5.4.0",
|
|
65
65
|
"vitest": "^3.0.0"
|
|
66
66
|
},
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@babel/traverse": "^7.28.6",
|
|
71
71
|
"@babel/types": "^7.28.6",
|
|
72
72
|
"@google/genai": "^1.0.0",
|
|
73
|
-
"@inquirer/prompts": "^
|
|
73
|
+
"@inquirer/prompts": "^8.2.0",
|
|
74
74
|
"better-sqlite3": "^12.6.2",
|
|
75
75
|
"boxen": "^8.0.1",
|
|
76
76
|
"chalk": "^5.3.0",
|
|
@@ -78,8 +78,16 @@
|
|
|
78
78
|
"figlet": "^1.9.4",
|
|
79
79
|
"fs-extra": "^11.2.0",
|
|
80
80
|
"gradient-string": "^3.0.0",
|
|
81
|
+
"ink": "^6.6.0",
|
|
82
|
+
"ink-big-text": "^2.0.0",
|
|
83
|
+
"ink-gradient": "^3.0.0",
|
|
84
|
+
"ink-spinner": "^5.0.0",
|
|
85
|
+
"ink-text-input": "^6.0.0",
|
|
86
|
+
"ink-use-stdout-dimensions": "^1.0.5",
|
|
81
87
|
"jszip": "^3.10.0",
|
|
82
88
|
"ora": "^8.0.0",
|
|
89
|
+
"react": "^19.2.4",
|
|
90
|
+
"ts-node": "^10.9.2",
|
|
83
91
|
"uuid": "^13.0.0"
|
|
84
92
|
}
|
|
85
|
-
}
|
|
93
|
+
}
|