next-anteater 0.1.0 → 0.1.2

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/bin/cli.mjs ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ const command = process.argv[2];
4
+
5
+ if (command === "uninstall") {
6
+ const { main } = await import("../lib/uninstall.mjs");
7
+ await main();
8
+ } else {
9
+ // Default to setup (handles "setup" arg or no arg)
10
+ const { main } = await import("../lib/setup.mjs");
11
+ await main();
12
+ }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../lib/uninstall.mjs";
3
+
4
+ main().catch((err) => {
5
+ console.error(`\n \x1b[31mError:\x1b[39m ${err.message}\n`);
6
+ process.exit(1);
7
+ });
package/lib/scaffold.mjs CHANGED
@@ -432,6 +432,95 @@ jobs:
432
432
  `;
433
433
  }
434
434
 
435
+ /**
436
+ * Generate the /api/anteater/runs route handler for multi-run discovery.
437
+ */
438
+ export function generateRunsRoute({ isTypeScript }) {
439
+ const ext = isTypeScript ? "ts" : "js";
440
+ const TS = isTypeScript;
441
+ const lines = [];
442
+ const add = (s) => lines.push(s);
443
+
444
+ add('import { NextResponse } from "next/server";');
445
+ if (TS) add('import type { AnteaterRun, AnteaterRunsResponse } from "@anteater/next";');
446
+ add("");
447
+ add("function getRepo()" + (TS ? ": string | undefined" : "") + " {");
448
+ add(" if (process.env.ANTEATER_GITHUB_REPO) return process.env.ANTEATER_GITHUB_REPO;");
449
+ add(" const owner = process.env.VERCEL_GIT_REPO_OWNER;");
450
+ add(" const slug = process.env.VERCEL_GIT_REPO_SLUG;");
451
+ add(" if (owner && slug) return `${owner}/${slug}`;");
452
+ add(" return undefined;");
453
+ add("}");
454
+ add("");
455
+ add("export async function GET() {");
456
+ add(" const repo = getRepo();");
457
+ add(" const token = process.env.GITHUB_TOKEN;");
458
+ add(" if (!repo || !token) {");
459
+ add(" return NextResponse.json" + (TS ? "<AnteaterRunsResponse>" : "") + "({ runs: [], deploymentId: process.env.VERCEL_DEPLOYMENT_ID });");
460
+ add(" }");
461
+ add("");
462
+ add(" const gh = (url" + (TS ? ": string" : "") + ") =>");
463
+ add(" fetch(url, {");
464
+ add(" headers: {");
465
+ add(" Authorization: `Bearer ${token}`,");
466
+ add(' Accept: "application/vnd.github+json",');
467
+ add(' "X-GitHub-Api-Version": "2022-11-28",');
468
+ add(" },");
469
+ add(' cache: "no-store",');
470
+ add(" });");
471
+ add("");
472
+ add(" try {");
473
+ add(" const owner = repo.split(\"/\")[0];");
474
+ add(" const refsRes = await gh(`https://api.github.com/repos/${repo}/git/matching-refs/heads/anteater/`);");
475
+ add(" const refs" + (TS ? ": Array<{ ref: string }>" : "") + " = refsRes.ok ? await refsRes.json() : [];");
476
+ add("");
477
+ add(" const prsRes = await gh(`https://api.github.com/repos/${repo}/pulls?state=all&per_page=20&sort=created&direction=desc`);");
478
+ add(" const allPrs" + (TS ? ": any[]" : "") + " = prsRes.ok ? await prsRes.json() : [];");
479
+ add(" const anteaterPrs = allPrs.filter((pr" + (TS ? ": any" : "") + ") => pr.head.ref.startsWith(\"anteater/\"));");
480
+ add(" const prByBranch = new Map(anteaterPrs.map((pr" + (TS ? ": any" : "") + ") => [pr.head.ref, pr]));");
481
+ add("");
482
+ add(" const runs" + (TS ? ": AnteaterRun[]" : "") + " = [];");
483
+ add(" const branchNames = refs.map((r) => r.ref.replace(\"refs/heads/\", \"\"));");
484
+ add(" for (const pr of anteaterPrs) {");
485
+ add(" if (!branchNames.includes(pr.head.ref)) branchNames.push(pr.head.ref);");
486
+ add(" }");
487
+ add("");
488
+ add(" for (const branch of branchNames) {");
489
+ add(" const pr = prByBranch.get(branch);");
490
+ add(" const parts = branch.split(\"-\");");
491
+ add(" const requestId = parts[parts.length - 1] || \"\";");
492
+ add(" const mode = branch.includes(\"friend-\") ? \"copy\" : \"prod\";");
493
+ add(" let step" + (TS ? ": string" : "") + ";");
494
+ add("");
495
+ add(" if (pr?.merged_at) {");
496
+ add(" const mergedAgo = Date.now() - new Date(pr.merged_at).getTime();");
497
+ add(" if (mergedAgo > 300000) continue;");
498
+ add(" step = mergedAgo > 150000 ? \"done\" : \"redeploying\";");
499
+ add(" } else if (pr?.state === \"closed\") {");
500
+ add(" continue;");
501
+ add(" } else if (pr) {");
502
+ add(" step = \"merging\";");
503
+ add(" } else {");
504
+ add(" step = \"working\";");
505
+ add(" }");
506
+ add("");
507
+ add(" const prompt = pr?.title?.replace(/^anteater:\\\\s*/i, \"\") || \"Starting...\";");
508
+ add(" runs.push({ branch, requestId, prompt, step, mode });");
509
+ add(" if (runs.length >= 5) break;");
510
+ add(" }");
511
+ add("");
512
+ add(" return NextResponse.json" + (TS ? "<AnteaterRunsResponse>" : "") + "({ runs, deploymentId: process.env.VERCEL_DEPLOYMENT_ID });");
513
+ add(" } catch {");
514
+ add(" return NextResponse.json" + (TS ? "<AnteaterRunsResponse>" : "") + "({ runs: [], deploymentId: process.env.VERCEL_DEPLOYMENT_ID });");
515
+ add(" }");
516
+ add("}");
517
+
518
+ return {
519
+ filename: `route.${ext}`,
520
+ content: lines.join("\n") + "\n",
521
+ };
522
+ }
523
+
435
524
  /**
436
525
  * Generate the AI apply script.
437
526
  */
@@ -606,6 +695,14 @@ export async function scaffoldFiles(cwd, options) {
606
695
  results.push(join(routeDir, route.filename));
607
696
  }
608
697
 
698
+ // Runs API route (multi-run discovery)
699
+ const runsRoute = generateRunsRoute(options);
700
+ const runsDir = options.isAppRouter ? "app/api/anteater/runs" : "pages/api/anteater/runs";
701
+ const runsPath = join(cwd, runsDir, runsRoute.filename);
702
+ if (await writeIfNotExists(runsPath, runsRoute.content)) {
703
+ results.push(join(runsDir, runsRoute.filename));
704
+ }
705
+
609
706
  // GitHub Action workflow
610
707
  const workflowPath = join(cwd, ".github/workflows/anteater.yml");
611
708
  if (await writeIfNotExists(workflowPath, generateWorkflow(options))) {
@@ -0,0 +1,112 @@
1
+ /**
2
+ * anteater uninstall — Removes all scaffolded Anteater files from the project.
3
+ */
4
+ import { readFile, writeFile, rm, access } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { bold, green, red, dim, ok, fail, heading, blank } from "./ui.mjs";
7
+
8
+ const cwd = process.cwd();
9
+
10
+ async function fileExists(path) {
11
+ try {
12
+ await access(path);
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ async function removeFile(path, label) {
20
+ if (await fileExists(path)) {
21
+ await rm(path, { recursive: true });
22
+ ok(`Removed ${label}`);
23
+ return true;
24
+ }
25
+ return false;
26
+ }
27
+
28
+ async function unpatchLayout() {
29
+ for (const layoutFile of ["app/layout.tsx", "app/layout.js"]) {
30
+ const fullPath = join(cwd, layoutFile);
31
+ if (!(await fileExists(fullPath))) continue;
32
+
33
+ let content = await readFile(fullPath, "utf-8");
34
+ if (!content.includes("AnteaterBar")) return false;
35
+
36
+ // Remove import lines
37
+ content = content.replace(/^.*AnteaterBar.*\n/gm, "");
38
+ // Remove component usage
39
+ content = content.replace(/^\s*<AnteaterBar[^]*?\/>\s*\n?/gm, "");
40
+ content = content.replace(/^\s*<AnteaterBarWrapper\s*\/>\s*\n?/gm, "");
41
+
42
+ await writeFile(fullPath, content, "utf-8");
43
+ ok(`Unpatched ${layoutFile}`);
44
+ return true;
45
+ }
46
+ return false;
47
+ }
48
+
49
+ async function removeDependency() {
50
+ const pkgPath = join(cwd, "package.json");
51
+ if (!(await fileExists(pkgPath))) return false;
52
+
53
+ const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
54
+ let changed = false;
55
+
56
+ if (pkg.dependencies?.["@anteater/next"]) {
57
+ delete pkg.dependencies["@anteater/next"];
58
+ changed = true;
59
+ }
60
+ if (pkg.devDependencies?.["@anteater/next"]) {
61
+ delete pkg.devDependencies["@anteater/next"];
62
+ changed = true;
63
+ }
64
+
65
+ if (changed) {
66
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
67
+ ok("Removed @anteater/next from package.json");
68
+ }
69
+ return changed;
70
+ }
71
+
72
+ export async function main() {
73
+ console.log();
74
+ console.log(` ${bold("\u{1F41C} Anteater Uninstall")}`);
75
+ console.log(` ${"\u2500".repeat(20)}`);
76
+ blank();
77
+
78
+ heading("Removing scaffolded files");
79
+
80
+ let removed = 0;
81
+
82
+ // Config file
83
+ if (await removeFile(join(cwd, "anteater.config.ts"), "anteater.config.ts")) removed++;
84
+ if (await removeFile(join(cwd, "anteater.config.js"), "anteater.config.js")) removed++;
85
+
86
+ // API routes
87
+ if (await removeFile(join(cwd, "app/api/anteater"), "app/api/anteater/")) removed++;
88
+ if (await removeFile(join(cwd, "pages/api/anteater"), "pages/api/anteater/")) removed++;
89
+
90
+ // Wrapper component
91
+ if (await removeFile(join(cwd, "components/anteater-bar-wrapper.tsx"), "components/anteater-bar-wrapper.tsx")) removed++;
92
+ if (await removeFile(join(cwd, "components/anteater-bar-wrapper.js"), "components/anteater-bar-wrapper.js")) removed++;
93
+
94
+ // GitHub workflow
95
+ if (await removeFile(join(cwd, ".github/workflows/anteater.yml"), ".github/workflows/anteater.yml")) removed++;
96
+
97
+ // Unpatch layout
98
+ heading("Cleaning up layout");
99
+ await unpatchLayout();
100
+
101
+ // Remove dependency
102
+ heading("Removing dependency");
103
+ await removeDependency();
104
+
105
+ blank();
106
+ if (removed > 0) {
107
+ console.log(` ${green("\u2713")} Anteater uninstalled. Run ${dim("npx next-anteater setup")} to reinstall.`);
108
+ } else {
109
+ console.log(` ${dim("No Anteater files found — nothing to remove.")}`);
110
+ }
111
+ console.log();
112
+ }
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "next-anteater",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "AI-powered live editing for your Next.js app",
5
5
  "bin": {
6
- "anteater": "./bin/setup-anteater.mjs"
6
+ "anteater": "bin/setup-anteater.mjs",
7
+ "anteater-uninstall": "bin/uninstall-anteater.mjs",
8
+ "next-anteater": "bin/cli.mjs"
7
9
  },
8
10
  "type": "module",
9
11
  "files": ["bin/", "lib/"],