next-anteater 0.2.5 → 0.2.6
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 +47 -0
- package/lib/scaffold.mjs +12 -5
- package/lib/setup.mjs +330 -324
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# next-anteater
|
|
2
|
+
|
|
3
|
+
Let users make your app.
|
|
4
|
+
|
|
5
|
+
Anteater adds a prompt bar to your Next.js app. When a user describes a change, Claude edits the code, opens a PR, and redeploys automatically via GitHub Actions.
|
|
6
|
+
|
|
7
|
+
## Security Warning
|
|
8
|
+
|
|
9
|
+
**Anteater gives users the ability to modify your application's code via AI. Only expose it to trusted users in a sandboxed environment.**
|
|
10
|
+
|
|
11
|
+
- Users with access to the prompt bar can make **arbitrary code changes**, including destructive ones.
|
|
12
|
+
- The AI agent runs in GitHub Actions with access to your **repository secrets and deployment pipeline**.
|
|
13
|
+
- A malicious or careless prompt could **access sensitive data, delete files, or break your app**.
|
|
14
|
+
- Anteater does **not** provide authentication or authorization. You must protect the prompt bar behind your own auth layer.
|
|
15
|
+
|
|
16
|
+
> Treat Anteater like giving someone commit access to your repo. Never expose it to the public internet with real credentials or production data.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
npx next-anteater setup
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Three steps: Anthropic key, GitHub PAT, editable paths. Everything else is automatic.
|
|
25
|
+
|
|
26
|
+
## How it works
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
User types change → GitHub Action runs AI agent → PR auto-merges → Vercel redeploys
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Configuration
|
|
33
|
+
|
|
34
|
+
After setup, edit `anteater.config.ts` to control:
|
|
35
|
+
|
|
36
|
+
- `allowedGlobs` / `blockedGlobs` — which files the agent can modify
|
|
37
|
+
- `autoMerge` — whether PRs merge automatically
|
|
38
|
+
- `requireReviewFor` — keywords that block auto-merge (e.g., "auth", "billing")
|
|
39
|
+
- `maxFilesChanged` / `maxDiffBytes` — safety limits on change size
|
|
40
|
+
|
|
41
|
+
## Security Disclaimer ⚠️
|
|
42
|
+
|
|
43
|
+
This software is provided "as is", without warranty of any kind. Use it at your own risk. The authors and contributors are not responsible for any damage, data loss, security breaches, or other harm resulting from the use of this software. By using Anteater, you accept full responsibility for how it is deployed, configured, and who is granted access. See [LICENSE](https://github.com/scottgriffinm/anteater/blob/master/LICENSE) for the full terms.
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
[MIT](https://github.com/scottgriffinm/anteater/blob/master/LICENSE)
|
package/lib/scaffold.mjs
CHANGED
|
@@ -27,7 +27,14 @@ export function generateConfig({ repo, allowedGlobs, blockedGlobs, autoMerge, is
|
|
|
27
27
|
|
|
28
28
|
return {
|
|
29
29
|
filename: `anteater.config.${ext}`,
|
|
30
|
-
content:
|
|
30
|
+
content: `/**
|
|
31
|
+
* SECURITY: Anteater lets users modify your app's code via AI.
|
|
32
|
+
* Only expose the prompt bar to trusted users behind your own auth layer.
|
|
33
|
+
* Users can make destructive changes and potentially access sensitive data.
|
|
34
|
+
* Never use this in a production environment with real credentials.
|
|
35
|
+
* See: https://github.com/scottgriffinm/anteater#security-warning
|
|
36
|
+
*/
|
|
37
|
+
${typeImport}const config${typeAnnotation} = {
|
|
31
38
|
repo: "${repo}",
|
|
32
39
|
productionBranch: "${productionBranch}",
|
|
33
40
|
modes: ["prod", "copy"],
|
|
@@ -792,12 +799,12 @@ export async function scaffoldFiles(cwd, options) {
|
|
|
792
799
|
results.push(".github/workflows/anteater.yml");
|
|
793
800
|
}
|
|
794
801
|
|
|
795
|
-
// Claude Code agent settings
|
|
802
|
+
// Claude Code agent settings (always overwrite — reflects current choices)
|
|
796
803
|
if (options.model && options.permissionsMode) {
|
|
797
804
|
const settingsPath = join(cwd, ".claude/settings.local.json");
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
805
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
806
|
+
await writeFile(settingsPath, generateClaudeSettings(options), "utf-8");
|
|
807
|
+
results.push(".claude/settings.local.json");
|
|
801
808
|
}
|
|
802
809
|
|
|
803
810
|
// Patch layout
|
package/lib/setup.mjs
CHANGED
|
@@ -1,324 +1,330 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* anteater setup — Interactive CLI to install and configure Anteater.
|
|
3
|
-
*
|
|
4
|
-
* Core logic extracted from bin/setup-anteater.mjs so it can be
|
|
5
|
-
* imported by tests without hitting the shebang line.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { execSync } from "node:child_process";
|
|
9
|
-
import {
|
|
10
|
-
bold, dim, green, red, yellow, cyan,
|
|
11
|
-
ok, fail, warn, info, heading, blank,
|
|
12
|
-
ask, confirm, select, spinner, closeRL,
|
|
13
|
-
} from "./ui.mjs";
|
|
14
|
-
import { detectProject } from "./detect.mjs";
|
|
15
|
-
import { scaffoldFiles } from "./scaffold.mjs";
|
|
16
|
-
import {
|
|
17
|
-
validateAnthropicKey, validateGitHubToken, setGitHubSecret, setVercelEnv,
|
|
18
|
-
writeEnvLocal, hasCommand,
|
|
19
|
-
} from "./secrets.mjs";
|
|
20
|
-
|
|
21
|
-
const cwd = process.cwd();
|
|
22
|
-
|
|
23
|
-
export async function main() {
|
|
24
|
-
console.log();
|
|
25
|
-
console.log(` ${bold("\u{1F41C} Anteater Setup")}`);
|
|
26
|
-
console.log(` ${"\u2500".repeat(17)}`);
|
|
27
|
-
blank();
|
|
28
|
-
|
|
29
|
-
// ───
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
console.log(` ${
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
{ label: "
|
|
169
|
-
{ label: "Opus
|
|
170
|
-
{ label: "
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
{ label: "
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
info(" -
|
|
185
|
-
info(" -
|
|
186
|
-
info(" -
|
|
187
|
-
info(" -
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
warn("
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
execSync(`git
|
|
264
|
-
execSync(`git
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
{
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
info(
|
|
323
|
-
|
|
324
|
-
|
|
1
|
+
/**
|
|
2
|
+
* anteater setup — Interactive CLI to install and configure Anteater.
|
|
3
|
+
*
|
|
4
|
+
* Core logic extracted from bin/setup-anteater.mjs so it can be
|
|
5
|
+
* imported by tests without hitting the shebang line.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import {
|
|
10
|
+
bold, dim, green, red, yellow, cyan,
|
|
11
|
+
ok, fail, warn, info, heading, blank,
|
|
12
|
+
ask, confirm, select, spinner, closeRL,
|
|
13
|
+
} from "./ui.mjs";
|
|
14
|
+
import { detectProject } from "./detect.mjs";
|
|
15
|
+
import { scaffoldFiles } from "./scaffold.mjs";
|
|
16
|
+
import {
|
|
17
|
+
validateAnthropicKey, validateGitHubToken, setGitHubSecret, setVercelEnv,
|
|
18
|
+
writeEnvLocal, hasCommand,
|
|
19
|
+
} from "./secrets.mjs";
|
|
20
|
+
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
|
|
23
|
+
export async function main() {
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(` ${bold("\u{1F41C} Anteater Setup")}`);
|
|
26
|
+
console.log(` ${"\u2500".repeat(17)}`);
|
|
27
|
+
blank();
|
|
28
|
+
|
|
29
|
+
// ─── Agent piping guide ────────────────────────────────────
|
|
30
|
+
if (!process.stdin.isTTY) {
|
|
31
|
+
console.log(` ${bold("Piped input detected — Agent Guide")}`);
|
|
32
|
+
console.log(` ${"─".repeat(40)}`);
|
|
33
|
+
console.log(` Prompts (one per line, in order):`);
|
|
34
|
+
console.log(` 1. Anthropic API key`);
|
|
35
|
+
console.log(` 2. GitHub PAT (ghp_... or github_pat_...)`);
|
|
36
|
+
console.log(` 3. Use default paths? (Y/n)`);
|
|
37
|
+
console.log(` 4. Model (1=Sonnet 2=Opus 3=Opus1M 4=Haiku)`);
|
|
38
|
+
console.log(` 5. Permissions (1=Sandboxed 2=Unrestricted)`);
|
|
39
|
+
console.log(` 6. Confirm unrestricted? (y/N) — only if 5=2`);
|
|
40
|
+
console.log();
|
|
41
|
+
console.log(` If paths=n, 2 extra prompts after #3:`);
|
|
42
|
+
console.log(` 3a. Allowed globs (comma-separated)`);
|
|
43
|
+
console.log(` 3b. Blocked globs (comma-separated)`);
|
|
44
|
+
console.log(` ${"─".repeat(40)}`);
|
|
45
|
+
blank();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Security notice ───────────────────────────────────────
|
|
49
|
+
warn("SECURITY: Anteater lets users modify your app's code via AI.");
|
|
50
|
+
info("Only expose it to trusted users in a sandboxed environment.");
|
|
51
|
+
info("Users can make destructive changes and potentially access sensitive data.");
|
|
52
|
+
info(`Learn more: ${cyan("https://github.com/scottgriffinm/anteater#security-warning")}`);
|
|
53
|
+
blank();
|
|
54
|
+
|
|
55
|
+
// ─── Preflight checks ──────────────────────────────────────
|
|
56
|
+
heading("Preflight");
|
|
57
|
+
|
|
58
|
+
if (!hasCommand("gh")) {
|
|
59
|
+
fail("GitHub CLI (gh) is required. Install it: https://cli.github.com");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
ok("GitHub CLI installed");
|
|
63
|
+
|
|
64
|
+
if (!hasCommand("vercel")) {
|
|
65
|
+
fail("Vercel CLI is required. Install it: npm i -g vercel");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
ok("Vercel CLI installed");
|
|
69
|
+
|
|
70
|
+
// ─── Detect project ─────────────────────────────────────────
|
|
71
|
+
const project = await detectProject(cwd);
|
|
72
|
+
|
|
73
|
+
if (!project.isNextJs) {
|
|
74
|
+
fail("No Next.js project found. Run this from your Next.js project root.");
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
ok(`Next.js ${project.nextVersion ?? ""} ${project.isAppRouter ? "(App Router)" : "(Pages Router)"}`);
|
|
78
|
+
|
|
79
|
+
if (!project.hasGit || !project.gitRemote) {
|
|
80
|
+
fail("No GitHub remote found. Run: git remote add origin <url>");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
ok(`Repo: ${project.gitRemote}`);
|
|
84
|
+
ok(`Branch: ${project.defaultBranch || "main"}`);
|
|
85
|
+
ok(`Package manager: ${project.packageManager}`);
|
|
86
|
+
blank();
|
|
87
|
+
|
|
88
|
+
// ─── Step 1: Anthropic API key ──────────────────────────────
|
|
89
|
+
heading("Step 1 of 4 \u2014 AI Provider");
|
|
90
|
+
info(`Get a key at ${cyan("https://console.anthropic.com/keys")}`);
|
|
91
|
+
blank();
|
|
92
|
+
|
|
93
|
+
let anthropicKey;
|
|
94
|
+
while (true) {
|
|
95
|
+
anthropicKey = await ask("Anthropic API key:", { mask: true });
|
|
96
|
+
if (!anthropicKey) { warn("Required."); continue; }
|
|
97
|
+
const valid = await spinner("Validating", () => validateAnthropicKey(anthropicKey));
|
|
98
|
+
if (valid) break;
|
|
99
|
+
fail("Invalid key. Check that it starts with sk-ant- and try again.");
|
|
100
|
+
}
|
|
101
|
+
blank();
|
|
102
|
+
|
|
103
|
+
// ─── Step 2: GitHub PAT ─────────────────────────────────────
|
|
104
|
+
heading("Step 2 of 4 \u2014 GitHub Access");
|
|
105
|
+
|
|
106
|
+
info("Anteater needs a long-lived Personal Access Token (PAT) for the deployed API route.");
|
|
107
|
+
blank();
|
|
108
|
+
info(`${bold("Create a Fine-grained token:")} ${cyan("https://github.com/settings/tokens?type=beta")}`);
|
|
109
|
+
info(` 1. Click ${bold("Generate new token")}`);
|
|
110
|
+
info(` 2. Select ${bold("Only select repositories")} \u2192 pick your repo`);
|
|
111
|
+
info(` 3. Set permissions: ${bold("Contents")}, ${bold("Pull requests")}, ${bold("Actions")} \u2192 Read and write`);
|
|
112
|
+
info(` 4. Generate and copy the token`);
|
|
113
|
+
blank();
|
|
114
|
+
const githubToken = await ask("Paste your GitHub PAT (ghp_... or github_pat_...):");
|
|
115
|
+
if (!githubToken) {
|
|
116
|
+
fail("A GitHub PAT is required.");
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const check = await spinner("Checking permissions", () =>
|
|
121
|
+
validateGitHubToken(githubToken, project.gitRemote)
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (!check.ok && check.missing.length > 0 && !check.missing.includes("unknown")) {
|
|
125
|
+
fail("Token is missing required scopes: " + check.missing.join(", "));
|
|
126
|
+
info(`Create a new Fine-grained PAT at ${cyan("https://github.com/settings/tokens?type=beta")}`);
|
|
127
|
+
info(`Set permissions: ${bold("Contents")}, ${bold("Pull requests")}, ${bold("Actions")} \u2192 Read and write`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
} else if (check.ok) {
|
|
130
|
+
ok("Token has required permissions");
|
|
131
|
+
}
|
|
132
|
+
blank();
|
|
133
|
+
|
|
134
|
+
// ─── Step 3: Configure paths ────────────────────────────────
|
|
135
|
+
heading("Step 3 of 4 \u2014 Editable Paths");
|
|
136
|
+
|
|
137
|
+
const defaultAllowed = [];
|
|
138
|
+
const defaultBlocked = ["lib/auth/**", "lib/billing/**", ".env*"];
|
|
139
|
+
|
|
140
|
+
if (project.isAppRouter) {
|
|
141
|
+
defaultAllowed.push("app/**", "components/**", "styles/**");
|
|
142
|
+
defaultBlocked.push("app/api/**");
|
|
143
|
+
} else {
|
|
144
|
+
defaultAllowed.push("pages/**", "components/**", "styles/**");
|
|
145
|
+
defaultBlocked.push("pages/api/**");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(` ${green("Allowed:")} ${defaultAllowed.join(", ")}`);
|
|
149
|
+
console.log(` ${red("Blocked:")} ${defaultBlocked.join(", ")}`);
|
|
150
|
+
blank();
|
|
151
|
+
|
|
152
|
+
const useDefaults = await confirm("Use these defaults?");
|
|
153
|
+
let allowedGlobs = defaultAllowed;
|
|
154
|
+
let blockedGlobs = defaultBlocked;
|
|
155
|
+
|
|
156
|
+
if (!useDefaults) {
|
|
157
|
+
const customAllowed = await ask("Allowed globs (comma-separated):");
|
|
158
|
+
const customBlocked = await ask("Blocked globs (comma-separated):");
|
|
159
|
+
if (customAllowed) allowedGlobs = customAllowed.split(",").map((s) => s.trim());
|
|
160
|
+
if (customBlocked) blockedGlobs = customBlocked.split(",").map((s) => s.trim());
|
|
161
|
+
}
|
|
162
|
+
blank();
|
|
163
|
+
|
|
164
|
+
// ─── Step 4: Agent configuration ─────────────────────────────
|
|
165
|
+
heading("Step 4 of 4 \u2014 Agent Configuration");
|
|
166
|
+
|
|
167
|
+
const model = await select("Select AI model:", [
|
|
168
|
+
{ label: "Sonnet (recommended)", hint: "fast, cost-effective, great for most changes", value: "sonnet" },
|
|
169
|
+
{ label: "Opus", hint: "most capable, higher cost", value: "opus" },
|
|
170
|
+
{ label: "Opus 1M", hint: "Opus with extended context (1M tokens)", value: "opus[1m]" },
|
|
171
|
+
{ label: "Haiku", hint: "fastest, lowest cost, best for simple changes", value: "haiku" },
|
|
172
|
+
]);
|
|
173
|
+
ok(`Model: ${model}`);
|
|
174
|
+
blank();
|
|
175
|
+
|
|
176
|
+
let permissionsMode = await select("Select agent permissions mode:", [
|
|
177
|
+
{ label: "Sandboxed (recommended)", hint: "full local access, no internet or external services", value: "sandboxed" },
|
|
178
|
+
{ label: "Unrestricted", hint: "full access including web, GitHub CLI, Vercel, and all MCP tools", value: "unrestricted" },
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
if (permissionsMode === "unrestricted") {
|
|
182
|
+
blank();
|
|
183
|
+
warn("Unrestricted mode grants the AI agent full access to:");
|
|
184
|
+
info(" - Internet (web fetches, searches, curl)");
|
|
185
|
+
info(" - GitHub CLI (push, PR creation, issue management)");
|
|
186
|
+
info(" - Vercel CLI (deployments, env vars)");
|
|
187
|
+
info(" - All MCP tools (browser automation, etc.)");
|
|
188
|
+
info(" - File deletion and system commands");
|
|
189
|
+
blank();
|
|
190
|
+
warn("The agent will run with bypassPermissions \u2014 no confirmation prompts.");
|
|
191
|
+
warn("Only use this if you trust the prompts your users will submit.");
|
|
192
|
+
blank();
|
|
193
|
+
const confirmed = await confirm("Confirm unrestricted mode?", false);
|
|
194
|
+
if (!confirmed) {
|
|
195
|
+
permissionsMode = "sandboxed";
|
|
196
|
+
ok("Falling back to Sandboxed mode");
|
|
197
|
+
} else {
|
|
198
|
+
ok("Unrestricted mode confirmed");
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
ok("Sandboxed mode \u2014 agent cannot access internet or external services");
|
|
202
|
+
}
|
|
203
|
+
blank();
|
|
204
|
+
|
|
205
|
+
// ─── Install & scaffold ─────────────────────────────────────
|
|
206
|
+
heading("Installing");
|
|
207
|
+
|
|
208
|
+
const installCmd = {
|
|
209
|
+
pnpm: "pnpm add next-anteater",
|
|
210
|
+
yarn: "yarn add next-anteater",
|
|
211
|
+
npm: "npm install next-anteater",
|
|
212
|
+
}[project.packageManager];
|
|
213
|
+
|
|
214
|
+
await spinner("Installing next-anteater", () => {
|
|
215
|
+
execSync(installCmd, { cwd, stdio: "ignore" });
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const productionBranch = project.defaultBranch || "main";
|
|
219
|
+
const scaffolded = await spinner("Creating files", () =>
|
|
220
|
+
scaffoldFiles(cwd, {
|
|
221
|
+
repo: project.gitRemote,
|
|
222
|
+
allowedGlobs,
|
|
223
|
+
blockedGlobs,
|
|
224
|
+
autoMerge: true,
|
|
225
|
+
productionBranch,
|
|
226
|
+
isTypeScript: project.isTypeScript,
|
|
227
|
+
isAppRouter: project.isAppRouter,
|
|
228
|
+
layoutFile: project.layoutFile,
|
|
229
|
+
model,
|
|
230
|
+
permissionsMode,
|
|
231
|
+
packageManager: project.packageManager,
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
for (const f of scaffolded) ok(`Created ${f}`);
|
|
236
|
+
|
|
237
|
+
// ─── Set secrets ────────────────────────────────────────────
|
|
238
|
+
heading("Configuring secrets");
|
|
239
|
+
|
|
240
|
+
// GitHub Actions secret
|
|
241
|
+
try {
|
|
242
|
+
await spinner("Setting ANTHROPIC_API_KEY in GitHub secrets", () => {
|
|
243
|
+
setGitHubSecret(project.gitRemote, "ANTHROPIC_API_KEY", anthropicKey);
|
|
244
|
+
});
|
|
245
|
+
} catch (err) {
|
|
246
|
+
warn(`Could not set secret: ${err.message}`);
|
|
247
|
+
info("Set manually: gh secret set ANTHROPIC_API_KEY --repo " + project.gitRemote);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// .env.local for local dev (only GITHUB_TOKEN needed)
|
|
251
|
+
await spinner("Writing .env.local", () =>
|
|
252
|
+
writeEnvLocal(cwd, { GITHUB_TOKEN: githubToken })
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// Vercel: only GITHUB_TOKEN needed (repo auto-detected, deploy detection automatic)
|
|
256
|
+
await spinner("Setting GITHUB_TOKEN in Vercel", () => {
|
|
257
|
+
setVercelEnv("GITHUB_TOKEN", githubToken);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ─── Push workflow ──────────────────────────────────────────
|
|
261
|
+
if (scaffolded.some((f) => f.includes("anteater.yml"))) {
|
|
262
|
+
await spinner("Pushing workflow to GitHub", () => {
|
|
263
|
+
execSync(`git add .github/workflows/anteater.yml`, { cwd, stdio: "ignore" });
|
|
264
|
+
execSync(`git commit -m "chore: add Anteater workflow"`, { cwd, stdio: "ignore" });
|
|
265
|
+
execSync(`git push origin ${productionBranch}`, { cwd, stdio: "ignore" });
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Verify
|
|
269
|
+
const activated = await spinner("Verifying workflow", async () => {
|
|
270
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
271
|
+
try {
|
|
272
|
+
const res = await fetch(
|
|
273
|
+
`https://api.github.com/repos/${project.gitRemote}/actions/workflows`,
|
|
274
|
+
{ headers: { Authorization: `Bearer ${githubToken}`, Accept: "application/vnd.github+json" } }
|
|
275
|
+
);
|
|
276
|
+
const data = await res.json();
|
|
277
|
+
return data.workflows?.some((w) => w.path === ".github/workflows/anteater.yml" && w.state === "active");
|
|
278
|
+
} catch { return false; }
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (activated) ok("Workflow active");
|
|
282
|
+
else warn(`Check: ${cyan(`https://github.com/${project.gitRemote}/actions`)}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ─── Test ───────────────────────────────────────────────────
|
|
286
|
+
const dispatchOk = await spinner("Running test dispatch", async () => {
|
|
287
|
+
try {
|
|
288
|
+
const res = await fetch(
|
|
289
|
+
`https://api.github.com/repos/${project.gitRemote}/actions/workflows/anteater.yml/dispatches`,
|
|
290
|
+
{
|
|
291
|
+
method: "POST",
|
|
292
|
+
headers: {
|
|
293
|
+
Authorization: `Bearer ${githubToken}`,
|
|
294
|
+
Accept: "application/vnd.github+json",
|
|
295
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
296
|
+
},
|
|
297
|
+
body: JSON.stringify({
|
|
298
|
+
ref: productionBranch,
|
|
299
|
+
inputs: {
|
|
300
|
+
requestId: "setup-test",
|
|
301
|
+
prompt: "setup verification \u2014 no changes expected",
|
|
302
|
+
mode: "prod",
|
|
303
|
+
branch: "anteater/setup-test",
|
|
304
|
+
baseBranch: productionBranch,
|
|
305
|
+
autoMerge: "false",
|
|
306
|
+
},
|
|
307
|
+
}),
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
return res.status === 204;
|
|
311
|
+
} catch { return false; }
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
if (dispatchOk) ok("Pipeline is working");
|
|
315
|
+
else warn("Test dispatch failed \u2014 check GitHub Actions");
|
|
316
|
+
|
|
317
|
+
// ─── Done! ──────────────────────────────────────────────────
|
|
318
|
+
closeRL();
|
|
319
|
+
blank();
|
|
320
|
+
console.log(` ${bold(green("\u{1F41C} Anteater is ready."))}`);
|
|
321
|
+
blank();
|
|
322
|
+
info(`Deploy your app and look for the "${green("Edit this page")}" button.`);
|
|
323
|
+
info("Users can modify your app by typing changes in the Anteater bar.");
|
|
324
|
+
blank();
|
|
325
|
+
warn("Reminder: only expose Anteater to trusted users.");
|
|
326
|
+
info("Users with access to the prompt bar can make arbitrary code changes.");
|
|
327
|
+
info("Use a sandboxed environment without real credentials or production data.");
|
|
328
|
+
info(`Protect the prompt bar behind your own auth layer \u2014 Anteater does ${bold("not")} provide auth.`);
|
|
329
|
+
blank();
|
|
330
|
+
}
|