@nairon-ai/aegis 0.2.0 → 0.3.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 +62 -13
- package/dist-node/cli/commands/init.js +6 -6
- package/dist-node/cli/commands/init.js.map +1 -1
- package/dist-node/cli/commands/setup.d.ts.map +1 -1
- package/dist-node/cli/commands/setup.js +68 -19
- package/dist-node/cli/commands/setup.js.map +1 -1
- package/dist-node/cli/github-app-flow.d.ts +9 -0
- package/dist-node/cli/github-app-flow.d.ts.map +1 -0
- package/dist-node/cli/github-app-flow.js +190 -0
- package/dist-node/cli/github-app-flow.js.map +1 -0
- package/dist-node/github/manifest.d.ts +13 -3
- package/dist-node/github/manifest.d.ts.map +1 -1
- package/dist-node/github/manifest.js +19 -10
- package/dist-node/github/manifest.js.map +1 -1
- package/docs/SETUP.md +27 -6
- package/package.json +1 -1
- package/src/cli/commands/init.ts +6 -6
- package/src/cli/commands/setup.ts +72 -27
- package/src/cli/github-app-flow.ts +248 -0
- package/src/github/manifest.ts +26 -10
package/README.md
CHANGED
|
@@ -22,19 +22,68 @@ Minimal fix branch -> PR -> stop
|
|
|
22
22
|
|
|
23
23
|
## Start Here
|
|
24
24
|
|
|
25
|
-
If you want to connect Aegis to another repo and monitor its bugs,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
If you want to connect Aegis to another repo and monitor its bugs, run Aegis from the product repo you want it to fix.
|
|
26
|
+
|
|
27
|
+
For example, if Aegis should monitor `KeyLead-Team/keylead`, run every setup command from your local KeyLead checkout:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd ~/Desktop/keylead
|
|
31
|
+
npx --yes @nairon-ai/aegis@latest init
|
|
32
|
+
npx --yes @nairon-ai/aegis@latest setup
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
First prove Aegis can see a ready bug locally:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx --yes @nairon-ai/aegis@latest status
|
|
39
|
+
npx --yes @nairon-ai/aegis@latest pickup --dry-run
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Only deploy after `pickup --dry-run` finds the test issue.
|
|
43
|
+
|
|
44
|
+
Full setup guide: [docs/SETUP.md](docs/SETUP.md).
|
|
45
|
+
|
|
46
|
+
### First Local Test
|
|
47
|
+
|
|
48
|
+
No Cloudflare required for this part.
|
|
49
|
+
|
|
50
|
+
1. `cd` into the product repo Aegis should monitor.
|
|
51
|
+
2. Run `npx --yes @nairon-ai/aegis@latest init`.
|
|
52
|
+
3. Create a GitHub token for that repo with Contents, Issues, and Pull Requests read/write access.
|
|
53
|
+
4. Run `npx --yes @nairon-ai/aegis@latest setup`.
|
|
54
|
+
5. Choose `plan-first` and `minimal` for the first test.
|
|
55
|
+
6. Add `bug` and `ready to implement` labels to the monitored GitHub repo.
|
|
56
|
+
7. Create a test bug issue with both labels.
|
|
57
|
+
8. Run `npx --yes @nairon-ai/aegis@latest pickup --dry-run`.
|
|
58
|
+
|
|
59
|
+
Good first result:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
github:#123 would run - ready bug with enough context
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If you see `No ready bugs found`, check `MONITORED_REPO`, exact label names, issue state, and GitHub token access.
|
|
66
|
+
|
|
67
|
+
### GitHub Access Choices
|
|
68
|
+
|
|
69
|
+
During `setup`, choose one:
|
|
70
|
+
|
|
71
|
+
- `GitHub token (recommended first test)`: fastest path. Best for proving Aegis can see ready bugs.
|
|
72
|
+
- `One-click GitHub App`: best for deployed/team use. Aegis opens a browser page, creates the app, asks you to install it on the repo, then saves the credentials.
|
|
73
|
+
- `Paste existing GitHub App credentials`: only if you already made an app manually.
|
|
74
|
+
|
|
75
|
+
The repo prompt accepts either `owner/repo` or a full GitHub URL like `https://github.com/KeyLead-Team/keylead`.
|
|
76
|
+
|
|
77
|
+
### Deployed AFK Mode
|
|
78
|
+
|
|
79
|
+
Deploy only after the local dry-run works.
|
|
80
|
+
|
|
81
|
+
1. Create a Cloudflare account.
|
|
82
|
+
2. Enable Workers Paid.
|
|
83
|
+
3. Start Docker locally.
|
|
84
|
+
4. Log in with `npx wrangler login`.
|
|
85
|
+
5. Run `npx --yes @nairon-ai/aegis@latest deploy`.
|
|
86
|
+
6. Add GitHub/Linear/Telegram webhooks using the Worker URL printed by deploy.
|
|
38
87
|
|
|
39
88
|
## Two Repos
|
|
40
89
|
|
|
@@ -55,9 +55,9 @@ This directory configures Aegis for this repository.
|
|
|
55
55
|
## First Run
|
|
56
56
|
|
|
57
57
|
\`\`\`bash
|
|
58
|
-
npx @nairon-ai/aegis setup
|
|
59
|
-
npx @nairon-ai/aegis pickup --dry-run
|
|
60
|
-
npx @nairon-ai/aegis deploy
|
|
58
|
+
npx --yes @nairon-ai/aegis@latest setup
|
|
59
|
+
npx --yes @nairon-ai/aegis@latest pickup --dry-run
|
|
60
|
+
npx --yes @nairon-ai/aegis@latest deploy
|
|
61
61
|
\`\`\`
|
|
62
62
|
|
|
63
63
|
For the first test, use:
|
|
@@ -98,11 +98,11 @@ export function runInit() {
|
|
|
98
98
|
writeIfMissing(resolve(PROJECT_AEGIS_DIR, "README.md"), README);
|
|
99
99
|
consola.success("Created .aegis/");
|
|
100
100
|
consola.log("\nNext:");
|
|
101
|
-
consola.log(" 1. npx @nairon-ai/aegis setup");
|
|
101
|
+
consola.log(" 1. npx --yes @nairon-ai/aegis@latest setup");
|
|
102
102
|
consola.log(" 2. create GitHub labels: bug, ready to implement");
|
|
103
103
|
consola.log(" 3. create a tiny test bug issue");
|
|
104
|
-
consola.log(" 4. npx @nairon-ai/aegis pickup --dry-run");
|
|
105
|
-
consola.log(" 5. npx @nairon-ai/aegis deploy");
|
|
104
|
+
consola.log(" 4. npx --yes @nairon-ai/aegis@latest pickup --dry-run");
|
|
105
|
+
consola.log(" 5. npx --yes @nairon-ai/aegis@latest deploy");
|
|
106
106
|
consola.log("");
|
|
107
107
|
}
|
|
108
108
|
function writeIfMissing(path, contents) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CnB,CAAC;AAEF,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCd,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;IACxC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,qCAAqC,EAAE;IAC1E,KAAK,CAAC,GAAG;QACR,OAAO,EAAE,CAAC;IACX,CAAC;CACD,CAAC,CAAC;AAEH,MAAM,UAAU,OAAO;IACtB,SAAS,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,cAAc,CAAC,OAAO,CAAC,iBAAiB,EAAE,YAAY,CAAC,EAAE,eAAe,CAAC,CAAC;IAC1E,cAAc,CAAC,OAAO,CAAC,iBAAiB,EAAE,cAAc,CAAC,EAAE,WAAW,CAAC,CAAC;IACxE,cAAc,CAAC,OAAO,CAAC,iBAAiB,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;IAChE,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CnB,CAAC;AAEF,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCd,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;IACxC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,qCAAqC,EAAE;IAC1E,KAAK,CAAC,GAAG;QACR,OAAO,EAAE,CAAC;IACX,CAAC;CACD,CAAC,CAAC;AAEH,MAAM,UAAU,OAAO;IACtB,SAAS,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,cAAc,CAAC,OAAO,CAAC,iBAAiB,EAAE,YAAY,CAAC,EAAE,eAAe,CAAC,CAAC;IAC1E,cAAc,CAAC,OAAO,CAAC,iBAAiB,EAAE,cAAc,CAAC,EAAE,WAAW,CAAC,CAAC;IACxE,cAAc,CAAC,OAAO,CAAC,iBAAiB,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;IAChE,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;IAClE,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC;AAED,SAAS,cAAc,CAAC,IAAY,EAAE,QAAgB;IACrD,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;QACzC,OAAO;IACR,CAAC;IACD,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,YAAY,qDAKvB,CAAC;AAEH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAuBpD"}
|
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { consola } from "consola";
|
|
5
|
+
import { runGitHubAppFlow } from "../github-app-flow.js";
|
|
5
6
|
import { resolveEnvFileForWrite } from "../paths.js";
|
|
6
7
|
import { detectSetupState, printState, saveConfig } from "../state.js";
|
|
7
8
|
export const setupCommand = defineCommand({
|
|
@@ -33,39 +34,55 @@ export async function runSetupWizard() {
|
|
|
33
34
|
consola.log("");
|
|
34
35
|
}
|
|
35
36
|
async function setupRepo(config) {
|
|
36
|
-
config.monitoredRepo = await promptText("GitHub repo to monitor (owner/repo):", config.monitoredRepo);
|
|
37
|
+
config.monitoredRepo = normalizeGitHubRepo(await promptText("GitHub repo to monitor (owner/repo):", config.monitoredRepo));
|
|
37
38
|
config.baseBranch = await promptText("Base branch:", config.baseBranch ?? "main");
|
|
38
39
|
}
|
|
39
40
|
async function setupGitHub(config) {
|
|
40
41
|
consola.log("\nGitHub access");
|
|
41
|
-
consola.info("For the first test, a fine-grained token is fastest. For
|
|
42
|
+
consola.info("For the first test, a fine-grained token is fastest. For deployed/team use, use the one-click GitHub App flow.");
|
|
42
43
|
const method = (await consola.prompt("Auth method:", {
|
|
43
44
|
type: "select",
|
|
44
45
|
options: [
|
|
45
|
-
{ label: "GitHub
|
|
46
|
-
{ label: "GitHub
|
|
46
|
+
{ label: "GitHub token (recommended first test)", value: "token" },
|
|
47
|
+
{ label: "One-click GitHub App (best for deployed/team use)", value: "app-flow" },
|
|
48
|
+
{ label: "Paste existing GitHub App credentials", value: "app-manual" },
|
|
47
49
|
],
|
|
48
50
|
}));
|
|
49
51
|
if (method === "token") {
|
|
50
52
|
config.githubToken = await promptText("GitHub token with contents/issues/PR read-write access:", config.githubToken);
|
|
53
|
+
config.githubAppId = undefined;
|
|
54
|
+
config.githubAppPrivateKey = undefined;
|
|
55
|
+
config.githubInstallationId = undefined;
|
|
51
56
|
}
|
|
52
|
-
else {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
else if (method === "app-flow") {
|
|
58
|
+
if (!config.monitoredRepo) {
|
|
59
|
+
consola.warn("Set the monitored repo first.");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const app = await runGitHubAppFlow({
|
|
64
|
+
monitoredRepo: config.monitoredRepo,
|
|
65
|
+
workerUrl: config.workerUrl,
|
|
66
|
+
});
|
|
67
|
+
config.githubAppId = app.appId;
|
|
68
|
+
config.githubAppPrivateKey = Buffer.from(app.privateKey, "utf-8").toString("base64");
|
|
69
|
+
config.githubInstallationId = app.installationId;
|
|
70
|
+
config.githubWebhookSecret = app.webhookSecret;
|
|
71
|
+
config.githubToken = undefined;
|
|
72
|
+
consola.success("Saved GitHub App credentials from GitHub.");
|
|
64
73
|
}
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
catch (error) {
|
|
75
|
+
consola.warn(error instanceof Error ? error.message : String(error));
|
|
76
|
+
consola.info("Falling back to manual GitHub App credentials.");
|
|
77
|
+
await setupGitHubAppManual(config);
|
|
67
78
|
}
|
|
68
|
-
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
await setupGitHubAppManual(config);
|
|
82
|
+
}
|
|
83
|
+
if (config.githubWebhookSecret) {
|
|
84
|
+
consola.info("GitHub webhook secret is already configured.");
|
|
85
|
+
return;
|
|
69
86
|
}
|
|
70
87
|
const generateSecret = (await consola.prompt("Generate a GitHub webhook secret?", {
|
|
71
88
|
type: "confirm",
|
|
@@ -78,6 +95,25 @@ async function setupGitHub(config) {
|
|
|
78
95
|
consola.info("Use this same value when you add the GitHub App or repo webhook.");
|
|
79
96
|
}
|
|
80
97
|
}
|
|
98
|
+
async function setupGitHubAppManual(config) {
|
|
99
|
+
config.githubAppId = await promptText("GitHub App ID:", config.githubAppId);
|
|
100
|
+
const keyPath = await promptText("Path to GitHub App private key .pem:", undefined);
|
|
101
|
+
if (keyPath) {
|
|
102
|
+
const resolved = resolve(keyPath.replace(/^~/, process.env.HOME ?? "~"));
|
|
103
|
+
if (existsSync(resolved)) {
|
|
104
|
+
config.githubAppPrivateKey = Buffer.from(readFileSync(resolved, "utf-8")).toString("base64");
|
|
105
|
+
consola.success("Loaded private key");
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
consola.warn(`File not found: ${resolved}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!config.githubAppPrivateKey) {
|
|
112
|
+
config.githubAppPrivateKey = Buffer.from(await promptText("Paste GitHub App private key PEM:", config.githubAppPrivateKey)).toString("base64");
|
|
113
|
+
}
|
|
114
|
+
config.githubInstallationId = await promptText("GitHub App installation ID:", config.githubInstallationId);
|
|
115
|
+
config.githubToken = undefined;
|
|
116
|
+
}
|
|
81
117
|
async function setupLinear(config) {
|
|
82
118
|
const enable = (await consola.prompt("Connect Linear too?", {
|
|
83
119
|
type: "confirm",
|
|
@@ -160,4 +196,17 @@ async function promptText(message, initial) {
|
|
|
160
196
|
}));
|
|
161
197
|
return value?.trim() ?? "";
|
|
162
198
|
}
|
|
199
|
+
function normalizeGitHubRepo(input) {
|
|
200
|
+
const value = input
|
|
201
|
+
.trim()
|
|
202
|
+
.replace(/\.git$/, "")
|
|
203
|
+
.replace(/\/$/, "");
|
|
204
|
+
const urlMatch = value.match(/github\.com[:/]([^/\s]+)\/([^/\s]+)$/i);
|
|
205
|
+
if (urlMatch)
|
|
206
|
+
return `${urlMatch[1]}/${urlMatch[2]}`;
|
|
207
|
+
const shorthandMatch = value.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
208
|
+
if (shorthandMatch)
|
|
209
|
+
return `${shorthandMatch[1]}/${shorthandMatch[2]}`;
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
163
212
|
//# sourceMappingURL=setup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEvE,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;IACzC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,4BAA4B,EAAE;IAClE,KAAK,CAAC,GAAG;QACR,MAAM,cAAc,EAAE,CAAC;IACxB,CAAC;CACD,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,cAAc;IACnC,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,UAAU,CAAC,KAAK,CAAC,CAAC;IAClB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAE5B,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,sBAAsB,CAAC,MAAM,CAAC,CAAC;IACrC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IAE1B,UAAU,CAAC,MAAM,CAAC,CAAC;IACnB,OAAO,CAAC,OAAO,CAAC,SAAS,sBAAsB,EAAE,EAAE,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC,mEAAmE,CAAC,CAAC;IACjF,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,MAAsB;IAC9C,MAAM,CAAC,aAAa,GAAG,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ManifestResult } from "../github/manifest.js";
|
|
2
|
+
export type GitHubAppFlowResult = ManifestResult & {
|
|
3
|
+
installationId: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function runGitHubAppFlow(options: {
|
|
6
|
+
monitoredRepo: string;
|
|
7
|
+
workerUrl?: string;
|
|
8
|
+
}): Promise<GitHubAppFlowResult>;
|
|
9
|
+
//# sourceMappingURL=github-app-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-app-flow.d.ts","sourceRoot":"","sources":["../../src/cli/github-app-flow.ts"],"names":[],"mappings":"AAIA,OAAO,EACN,KAAK,cAAc,EAInB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG;IAClD,cAAc,EAAE,MAAM,CAAC;CACvB,CAAC;AAIF,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA2G/B"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { consola } from "consola";
|
|
5
|
+
import { exchangeManifestCode, generateManifest, getManifestCreationUrl, } from "../github/manifest.js";
|
|
6
|
+
export async function runGitHubAppFlow(options) {
|
|
7
|
+
const [owner, repo] = options.monitoredRepo.split("/");
|
|
8
|
+
if (!owner || !repo) {
|
|
9
|
+
throw new Error("GitHub App setup needs MONITORED_REPO in owner/repo format.");
|
|
10
|
+
}
|
|
11
|
+
const ownerType = await detectOwnerType(owner);
|
|
12
|
+
const state = randomUUID();
|
|
13
|
+
const appName = buildAppName(owner, repo);
|
|
14
|
+
let createdApp;
|
|
15
|
+
const result = await new Promise((resolve, reject) => {
|
|
16
|
+
const server = createServer(async (request, response) => {
|
|
17
|
+
try {
|
|
18
|
+
const host = request.headers.host;
|
|
19
|
+
if (!host) {
|
|
20
|
+
writeText(response, 400, "Missing Host header.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const currentUrl = new URL(request.url ?? "/", `http://${host}`);
|
|
24
|
+
const baseUrl = `http://${host}`;
|
|
25
|
+
if (currentUrl.pathname === "/") {
|
|
26
|
+
const manifest = generateManifest({
|
|
27
|
+
appName,
|
|
28
|
+
redirectUrl: `${baseUrl}/callback`,
|
|
29
|
+
setupUrl: `${baseUrl}/installed`,
|
|
30
|
+
webhookUrl: options.workerUrl
|
|
31
|
+
? `${options.workerUrl.replace(/\/$/, "")}/webhook/github`
|
|
32
|
+
: "https://example.com/aegis/webhook/github",
|
|
33
|
+
webhookActive: Boolean(options.workerUrl),
|
|
34
|
+
});
|
|
35
|
+
writeHtml(response, renderManifestForm({
|
|
36
|
+
actionUrl: getManifestCreationUrl(ownerType === "Organization" ? owner : undefined, state),
|
|
37
|
+
manifest,
|
|
38
|
+
owner,
|
|
39
|
+
repo,
|
|
40
|
+
appName,
|
|
41
|
+
webhookActive: Boolean(options.workerUrl),
|
|
42
|
+
}));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (currentUrl.pathname === "/callback") {
|
|
46
|
+
if (currentUrl.searchParams.get("state") !== state) {
|
|
47
|
+
writeText(response, 400, "State mismatch. Close this tab and rerun aegis setup.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const code = currentUrl.searchParams.get("code");
|
|
51
|
+
if (!code) {
|
|
52
|
+
writeText(response, 400, "Missing GitHub manifest code.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
createdApp = await exchangeManifestCode(code);
|
|
56
|
+
const installUrl = `${createdApp.htmlUrl}/installations/new`;
|
|
57
|
+
consola.success(`Created GitHub App: ${createdApp.appName}`);
|
|
58
|
+
consola.info(`Install it on ${options.monitoredRepo}: ${installUrl}`);
|
|
59
|
+
writeHtml(response, renderInstallPage(installUrl, options.monitoredRepo));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (currentUrl.pathname === "/installed") {
|
|
63
|
+
const installationId = currentUrl.searchParams.get("installation_id");
|
|
64
|
+
if (!installationId || !createdApp) {
|
|
65
|
+
writeText(response, 400, "Install callback was missing installation_id. Copy the installation ID from the GitHub URL and rerun setup.");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
writeHtml(response, renderDonePage());
|
|
69
|
+
resolve({ ...createdApp, installationId });
|
|
70
|
+
setTimeout(() => server.close(), 250);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
writeText(response, 404, "Not found.");
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
reject(error);
|
|
77
|
+
writeText(response, 500, error instanceof Error ? error.message : String(error));
|
|
78
|
+
setTimeout(() => server.close(), 250);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
server.on("error", reject);
|
|
82
|
+
server.listen(0, "127.0.0.1", () => {
|
|
83
|
+
const address = server.address();
|
|
84
|
+
if (!address || typeof address === "string") {
|
|
85
|
+
reject(new Error("Could not start local GitHub App setup server."));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const url = `http://127.0.0.1:${address.port}/`;
|
|
89
|
+
consola.info("Opening GitHub App setup in your browser.");
|
|
90
|
+
consola.info("Click Create GitHub App, then Install, then select only the monitored repo.");
|
|
91
|
+
consola.info(`If the browser does not open, paste this URL:\n${url}`);
|
|
92
|
+
openBrowser(url);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
async function detectOwnerType(owner) {
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch(`https://api.github.com/users/${owner}`, {
|
|
100
|
+
headers: {
|
|
101
|
+
Accept: "application/vnd.github+json",
|
|
102
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok)
|
|
106
|
+
return "Organization";
|
|
107
|
+
const data = (await response.json());
|
|
108
|
+
return data.type === "User" ? "User" : "Organization";
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return "Organization";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function buildAppName(owner, repo) {
|
|
115
|
+
const suffix = randomBytes(2).toString("hex");
|
|
116
|
+
return `aegis-${owner}-${repo}-${suffix}`
|
|
117
|
+
.toLowerCase()
|
|
118
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
119
|
+
.replace(/-+/g, "-")
|
|
120
|
+
.slice(0, 34);
|
|
121
|
+
}
|
|
122
|
+
function openBrowser(url) {
|
|
123
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
124
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
125
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
126
|
+
child.unref();
|
|
127
|
+
}
|
|
128
|
+
function renderManifestForm(options) {
|
|
129
|
+
return page("Create Aegis GitHub App", `
|
|
130
|
+
<h1>Create Aegis GitHub App</h1>
|
|
131
|
+
<p>This will create a private GitHub App for <strong>${escapeHtml(options.owner)}/${escapeHtml(options.repo)}</strong>.</p>
|
|
132
|
+
<ul>
|
|
133
|
+
<li>Permissions: contents, issues, pull requests</li>
|
|
134
|
+
<li>Events: issues, issue comments, pull requests</li>
|
|
135
|
+
<li>App name: ${escapeHtml(options.appName)}</li>
|
|
136
|
+
<li>Webhook: ${options.webhookActive ? "enabled" : "disabled until deploy"}</li>
|
|
137
|
+
</ul>
|
|
138
|
+
<form action="${escapeHtml(options.actionUrl)}" method="post">
|
|
139
|
+
<input type="hidden" name="manifest" value="${escapeHtml(JSON.stringify(options.manifest))}">
|
|
140
|
+
<button type="submit">Create GitHub App</button>
|
|
141
|
+
</form>
|
|
142
|
+
`);
|
|
143
|
+
}
|
|
144
|
+
function renderInstallPage(installUrl, repo) {
|
|
145
|
+
return page("Install Aegis GitHub App", `
|
|
146
|
+
<h1>GitHub App created</h1>
|
|
147
|
+
<p>Now install it on <strong>${escapeHtml(repo)}</strong>. Choose <strong>Only select repositories</strong> and pick that repo.</p>
|
|
148
|
+
<p><a class="button" href="${escapeHtml(installUrl)}">Install GitHub App</a></p>
|
|
149
|
+
`);
|
|
150
|
+
}
|
|
151
|
+
function renderDonePage() {
|
|
152
|
+
return page("Aegis GitHub App Connected", `
|
|
153
|
+
<h1>Aegis is connected</h1>
|
|
154
|
+
<p>You can close this tab and return to your terminal.</p>
|
|
155
|
+
`);
|
|
156
|
+
}
|
|
157
|
+
function page(title, body) {
|
|
158
|
+
return `<!doctype html>
|
|
159
|
+
<html lang="en">
|
|
160
|
+
<head>
|
|
161
|
+
<meta charset="utf-8">
|
|
162
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
163
|
+
<title>${escapeHtml(title)}</title>
|
|
164
|
+
<style>
|
|
165
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; max-width: 680px; margin: 64px auto; padding: 0 24px; color: #111827; line-height: 1.5; }
|
|
166
|
+
h1 { font-size: 28px; margin-bottom: 12px; }
|
|
167
|
+
button, .button { display: inline-block; border: 0; border-radius: 6px; background: #166534; color: white; padding: 10px 14px; font: inherit; text-decoration: none; cursor: pointer; }
|
|
168
|
+
ul { padding-left: 22px; }
|
|
169
|
+
</style>
|
|
170
|
+
</head>
|
|
171
|
+
<body>${body}</body>
|
|
172
|
+
</html>`;
|
|
173
|
+
}
|
|
174
|
+
function writeHtml(response, html) {
|
|
175
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
176
|
+
response.end(html);
|
|
177
|
+
}
|
|
178
|
+
function writeText(response, status, text) {
|
|
179
|
+
response.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
|
|
180
|
+
response.end(text);
|
|
181
|
+
}
|
|
182
|
+
function escapeHtml(value) {
|
|
183
|
+
return value
|
|
184
|
+
.replace(/&/g, "&")
|
|
185
|
+
.replace(/</g, "<")
|
|
186
|
+
.replace(/>/g, ">")
|
|
187
|
+
.replace(/"/g, """)
|
|
188
|
+
.replace(/'/g, "'");
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=github-app-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-app-flow.js","sourceRoot":"","sources":["../../src/cli/github-app-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export type ManifestResult = {
|
|
14
14
|
appId: string;
|
|
15
|
+
slug: string;
|
|
15
16
|
appName: string;
|
|
16
17
|
privateKey: string;
|
|
17
18
|
webhookSecret: string;
|
|
@@ -22,13 +23,22 @@ export type ManifestResult = {
|
|
|
22
23
|
/**
|
|
23
24
|
* Generate the GitHub App manifest JSON.
|
|
24
25
|
*/
|
|
25
|
-
export declare function generateManifest(
|
|
26
|
+
export declare function generateManifest(options: {
|
|
27
|
+
appName: string;
|
|
28
|
+
redirectUrl: string;
|
|
29
|
+
setupUrl: string;
|
|
30
|
+
webhookUrl: string;
|
|
31
|
+
webhookActive: boolean;
|
|
32
|
+
}): Record<string, unknown>;
|
|
26
33
|
/**
|
|
27
34
|
* Exchange a manifest code for full app credentials.
|
|
28
35
|
*/
|
|
29
36
|
export declare function exchangeManifestCode(code: string): Promise<ManifestResult>;
|
|
30
37
|
/**
|
|
31
|
-
* Get the manifest creation URL
|
|
38
|
+
* Get the manifest creation URL for manual fallback.
|
|
39
|
+
*
|
|
40
|
+
* GitHub requires the manifest to be POSTed to this URL. Do not send users here
|
|
41
|
+
* directly without a form body.
|
|
32
42
|
*/
|
|
33
|
-
export declare function getManifestCreationUrl(
|
|
43
|
+
export declare function getManifestCreationUrl(owner?: string, state?: string): string;
|
|
34
44
|
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/github/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/github/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;CACvB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiB1B;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAmChF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAK7E"}
|
|
@@ -20,16 +20,19 @@ const MANIFEST_EVENTS = ["issues", "issue_comment", "pull_request"];
|
|
|
20
20
|
/**
|
|
21
21
|
* Generate the GitHub App manifest JSON.
|
|
22
22
|
*/
|
|
23
|
-
export function generateManifest(
|
|
23
|
+
export function generateManifest(options) {
|
|
24
24
|
return {
|
|
25
|
-
name:
|
|
25
|
+
name: options.appName,
|
|
26
26
|
url: "https://github.com/Nairon-AI/aegis",
|
|
27
|
+
description: "Self-hosted AFK bug-fixing agent for ready GitHub issues.",
|
|
27
28
|
hook_attributes: {
|
|
28
|
-
url:
|
|
29
|
-
active:
|
|
29
|
+
url: options.webhookUrl,
|
|
30
|
+
active: options.webhookActive,
|
|
30
31
|
},
|
|
31
|
-
redirect_url:
|
|
32
|
-
callback_urls: [
|
|
32
|
+
redirect_url: options.redirectUrl,
|
|
33
|
+
callback_urls: [options.redirectUrl, options.setupUrl],
|
|
34
|
+
setup_url: options.setupUrl,
|
|
35
|
+
setup_on_update: true,
|
|
33
36
|
public: false,
|
|
34
37
|
default_permissions: MANIFEST_PERMISSIONS,
|
|
35
38
|
default_events: MANIFEST_EVENTS,
|
|
@@ -53,6 +56,7 @@ export async function exchangeManifestCode(code) {
|
|
|
53
56
|
const data = (await response.json());
|
|
54
57
|
return {
|
|
55
58
|
appId: String(data.id),
|
|
59
|
+
slug: data.slug,
|
|
56
60
|
appName: data.name,
|
|
57
61
|
privateKey: data.pem,
|
|
58
62
|
webhookSecret: data.webhook_secret,
|
|
@@ -62,10 +66,15 @@ export async function exchangeManifestCode(code) {
|
|
|
62
66
|
};
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
65
|
-
* Get the manifest creation URL
|
|
69
|
+
* Get the manifest creation URL for manual fallback.
|
|
70
|
+
*
|
|
71
|
+
* GitHub requires the manifest to be POSTed to this URL. Do not send users here
|
|
72
|
+
* directly without a form body.
|
|
66
73
|
*/
|
|
67
|
-
export function getManifestCreationUrl(
|
|
68
|
-
const
|
|
69
|
-
|
|
74
|
+
export function getManifestCreationUrl(owner, state) {
|
|
75
|
+
const base = owner
|
|
76
|
+
? `https://github.com/organizations/${owner}/settings/apps/new`
|
|
77
|
+
: "https://github.com/settings/apps/new";
|
|
78
|
+
return state ? `${base}?state=${encodeURIComponent(state)}` : base;
|
|
70
79
|
}
|
|
71
80
|
//# sourceMappingURL=manifest.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/github/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,oBAAoB,GAAG;IAC5B,QAAQ,EAAE,OAAO;IACjB,aAAa,EAAE,OAAO;IACtB,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,MAAM;CAChB,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,eAAe,EAAE,cAAc,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/github/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,oBAAoB,GAAG;IAC5B,QAAQ,EAAE,OAAO;IACjB,aAAa,EAAE,OAAO;IACtB,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,MAAM;CAChB,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,eAAe,EAAE,cAAc,CAAC,CAAC;AAapE;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAMhC;IACA,OAAO;QACN,IAAI,EAAE,OAAO,CAAC,OAAO;QACrB,GAAG,EAAE,oCAAoC;QACzC,WAAW,EAAE,2DAA2D;QACxE,eAAe,EAAE;YAChB,GAAG,EAAE,OAAO,CAAC,UAAU;YACvB,MAAM,EAAE,OAAO,CAAC,aAAa;SAC7B;QACD,YAAY,EAAE,OAAO,CAAC,WAAW;QACjC,aAAa,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC;QACtD,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,eAAe,EAAE,IAAI;QACrB,MAAM,EAAE,KAAK;QACb,mBAAmB,EAAE,oBAAoB;QACzC,cAAc,EAAE,eAAe;KAC/B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,IAAY;IACtD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,wCAAwC,IAAI,cAAc,EAAE;QACxF,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACR,MAAM,EAAE,6BAA6B;YACrC,sBAAsB,EAAE,YAAY;SACpC;KACD,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CASlC,CAAC;IAEF,OAAO;QACN,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,CAAC,IAAI;QAClB,UAAU,EAAE,IAAI,CAAC,GAAG;QACpB,aAAa,EAAE,IAAI,CAAC,cAAc;QAClC,QAAQ,EAAE,IAAI,CAAC,SAAS;QACxB,YAAY,EAAE,IAAI,CAAC,aAAa;QAChC,OAAO,EAAE,IAAI,CAAC,QAAQ;KACtB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAc,EAAE,KAAc;IACpE,MAAM,IAAI,GAAG,KAAK;QACjB,CAAC,CAAC,oCAAoC,KAAK,oBAAoB;QAC/D,CAAC,CAAC,sCAAsC,CAAC;IAC1C,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,UAAU,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACpE,CAAC"}
|
package/docs/SETUP.md
CHANGED
|
@@ -88,7 +88,7 @@ Pick one path.
|
|
|
88
88
|
|
|
89
89
|
### Option A: GitHub Token
|
|
90
90
|
|
|
91
|
-
Fastest first setup.
|
|
91
|
+
Fastest first setup. Use this for the first local dry-run.
|
|
92
92
|
|
|
93
93
|
Create a fine-grained personal access token:
|
|
94
94
|
|
|
@@ -100,11 +100,32 @@ Create a fine-grained personal access token:
|
|
|
100
100
|
|
|
101
101
|
The token lets Aegis scan issues, create branches, push fixes, and open PRs.
|
|
102
102
|
|
|
103
|
-
### Option B: GitHub App
|
|
103
|
+
### Option B: One-Click GitHub App
|
|
104
104
|
|
|
105
|
-
Better for
|
|
105
|
+
Better for deployed/team use. The setup wizard can create the GitHub App for you.
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
When `aegis setup` asks for `Auth method`, choose:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
One-click GitHub App (best for deployed/team use)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Aegis opens a browser page. You click:
|
|
114
|
+
|
|
115
|
+
1. `Create GitHub App`
|
|
116
|
+
2. `Install`
|
|
117
|
+
3. `Only select repositories`
|
|
118
|
+
4. Pick the monitored repo
|
|
119
|
+
|
|
120
|
+
The CLI saves the App ID, private key, webhook secret, and installation ID into `.aegis/.env`.
|
|
121
|
+
|
|
122
|
+
If you have not deployed yet, the app webhook starts disabled. That is fine for local dry-run. After deploy, add the repo webhook from Step 9.
|
|
123
|
+
|
|
124
|
+
### Option C: Existing GitHub App
|
|
125
|
+
|
|
126
|
+
Use this only if you already have an app.
|
|
127
|
+
|
|
128
|
+
The app needs:
|
|
108
129
|
|
|
109
130
|
- Contents: read/write
|
|
110
131
|
- Issues: read/write
|
|
@@ -130,9 +151,9 @@ npx @nairon-ai/aegis setup
|
|
|
130
151
|
|
|
131
152
|
Important answers:
|
|
132
153
|
|
|
133
|
-
- `GitHub repo to monitor`: the app repo, for example `acme/web-app`
|
|
154
|
+
- `GitHub repo to monitor`: the app repo, for example `acme/web-app` or `https://github.com/acme/web-app`
|
|
134
155
|
- `Base branch`: usually `main`
|
|
135
|
-
- GitHub auth: token or
|
|
156
|
+
- GitHub auth: use `GitHub token` for the first test, or `One-click GitHub App` for deployed/team use
|
|
136
157
|
- `GitHub webhook secret`: generate one and keep it in `.aegis/.env`
|
|
137
158
|
- `Automation mode`: start with `plan-first`
|
|
138
159
|
- `Context profile`: start with `minimal`
|
package/package.json
CHANGED
package/src/cli/commands/init.ts
CHANGED
|
@@ -57,9 +57,9 @@ This directory configures Aegis for this repository.
|
|
|
57
57
|
## First Run
|
|
58
58
|
|
|
59
59
|
\`\`\`bash
|
|
60
|
-
npx @nairon-ai/aegis setup
|
|
61
|
-
npx @nairon-ai/aegis pickup --dry-run
|
|
62
|
-
npx @nairon-ai/aegis deploy
|
|
60
|
+
npx --yes @nairon-ai/aegis@latest setup
|
|
61
|
+
npx --yes @nairon-ai/aegis@latest pickup --dry-run
|
|
62
|
+
npx --yes @nairon-ai/aegis@latest deploy
|
|
63
63
|
\`\`\`
|
|
64
64
|
|
|
65
65
|
For the first test, use:
|
|
@@ -102,11 +102,11 @@ export function runInit(): void {
|
|
|
102
102
|
writeIfMissing(resolve(PROJECT_AEGIS_DIR, "README.md"), README);
|
|
103
103
|
consola.success("Created .aegis/");
|
|
104
104
|
consola.log("\nNext:");
|
|
105
|
-
consola.log(" 1. npx @nairon-ai/aegis setup");
|
|
105
|
+
consola.log(" 1. npx --yes @nairon-ai/aegis@latest setup");
|
|
106
106
|
consola.log(" 2. create GitHub labels: bug, ready to implement");
|
|
107
107
|
consola.log(" 3. create a tiny test bug issue");
|
|
108
|
-
consola.log(" 4. npx @nairon-ai/aegis pickup --dry-run");
|
|
109
|
-
consola.log(" 5. npx @nairon-ai/aegis deploy");
|
|
108
|
+
consola.log(" 4. npx --yes @nairon-ai/aegis@latest pickup --dry-run");
|
|
109
|
+
consola.log(" 5. npx --yes @nairon-ai/aegis@latest deploy");
|
|
110
110
|
consola.log("");
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -3,6 +3,7 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { consola } from "consola";
|
|
5
5
|
import type { AegisCliConfig } from "../../shared/types.js";
|
|
6
|
+
import { runGitHubAppFlow } from "../github-app-flow.js";
|
|
6
7
|
import { resolveEnvFileForWrite } from "../paths.js";
|
|
7
8
|
import { detectSetupState, printState, saveConfig } from "../state.js";
|
|
8
9
|
|
|
@@ -39,9 +40,8 @@ export async function runSetupWizard(): Promise<void> {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
async function setupRepo(config: AegisCliConfig): Promise<void> {
|
|
42
|
-
config.monitoredRepo =
|
|
43
|
-
"GitHub repo to monitor (owner/repo):",
|
|
44
|
-
config.monitoredRepo,
|
|
43
|
+
config.monitoredRepo = normalizeGitHubRepo(
|
|
44
|
+
await promptText("GitHub repo to monitor (owner/repo):", config.monitoredRepo),
|
|
45
45
|
);
|
|
46
46
|
config.baseBranch = await promptText("Base branch:", config.baseBranch ?? "main");
|
|
47
47
|
}
|
|
@@ -49,13 +49,14 @@ async function setupRepo(config: AegisCliConfig): Promise<void> {
|
|
|
49
49
|
async function setupGitHub(config: AegisCliConfig): Promise<void> {
|
|
50
50
|
consola.log("\nGitHub access");
|
|
51
51
|
consola.info(
|
|
52
|
-
"For the first test, a fine-grained token is fastest. For
|
|
52
|
+
"For the first test, a fine-grained token is fastest. For deployed/team use, use the one-click GitHub App flow.",
|
|
53
53
|
);
|
|
54
54
|
const method = (await consola.prompt("Auth method:", {
|
|
55
55
|
type: "select",
|
|
56
56
|
options: [
|
|
57
|
-
{ label: "GitHub
|
|
58
|
-
{ label: "GitHub
|
|
57
|
+
{ label: "GitHub token (recommended first test)", value: "token" },
|
|
58
|
+
{ label: "One-click GitHub App (best for deployed/team use)", value: "app-flow" },
|
|
59
|
+
{ label: "Paste existing GitHub App credentials", value: "app-manual" },
|
|
59
60
|
],
|
|
60
61
|
})) as string;
|
|
61
62
|
|
|
@@ -64,29 +65,37 @@ async function setupGitHub(config: AegisCliConfig): Promise<void> {
|
|
|
64
65
|
"GitHub token with contents/issues/PR read-write access:",
|
|
65
66
|
config.githubToken,
|
|
66
67
|
);
|
|
67
|
-
|
|
68
|
-
config.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"base64",
|
|
75
|
-
);
|
|
76
|
-
consola.success("Loaded private key");
|
|
77
|
-
} else {
|
|
78
|
-
consola.warn(`File not found: ${resolved}`);
|
|
79
|
-
}
|
|
68
|
+
config.githubAppId = undefined;
|
|
69
|
+
config.githubAppPrivateKey = undefined;
|
|
70
|
+
config.githubInstallationId = undefined;
|
|
71
|
+
} else if (method === "app-flow") {
|
|
72
|
+
if (!config.monitoredRepo) {
|
|
73
|
+
consola.warn("Set the monitored repo first.");
|
|
74
|
+
return;
|
|
80
75
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
try {
|
|
77
|
+
const app = await runGitHubAppFlow({
|
|
78
|
+
monitoredRepo: config.monitoredRepo,
|
|
79
|
+
workerUrl: config.workerUrl,
|
|
80
|
+
});
|
|
81
|
+
config.githubAppId = app.appId;
|
|
82
|
+
config.githubAppPrivateKey = Buffer.from(app.privateKey, "utf-8").toString("base64");
|
|
83
|
+
config.githubInstallationId = app.installationId;
|
|
84
|
+
config.githubWebhookSecret = app.webhookSecret;
|
|
85
|
+
config.githubToken = undefined;
|
|
86
|
+
consola.success("Saved GitHub App credentials from GitHub.");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
consola.warn(error instanceof Error ? error.message : String(error));
|
|
89
|
+
consola.info("Falling back to manual GitHub App credentials.");
|
|
90
|
+
await setupGitHubAppManual(config);
|
|
85
91
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
} else {
|
|
93
|
+
await setupGitHubAppManual(config);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (config.githubWebhookSecret) {
|
|
97
|
+
consola.info("GitHub webhook secret is already configured.");
|
|
98
|
+
return;
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
const generateSecret = (await consola.prompt("Generate a GitHub webhook secret?", {
|
|
@@ -101,6 +110,30 @@ async function setupGitHub(config: AegisCliConfig): Promise<void> {
|
|
|
101
110
|
}
|
|
102
111
|
}
|
|
103
112
|
|
|
113
|
+
async function setupGitHubAppManual(config: AegisCliConfig): Promise<void> {
|
|
114
|
+
config.githubAppId = await promptText("GitHub App ID:", config.githubAppId);
|
|
115
|
+
const keyPath = await promptText("Path to GitHub App private key .pem:", undefined);
|
|
116
|
+
if (keyPath) {
|
|
117
|
+
const resolved = resolve(keyPath.replace(/^~/, process.env.HOME ?? "~"));
|
|
118
|
+
if (existsSync(resolved)) {
|
|
119
|
+
config.githubAppPrivateKey = Buffer.from(readFileSync(resolved, "utf-8")).toString("base64");
|
|
120
|
+
consola.success("Loaded private key");
|
|
121
|
+
} else {
|
|
122
|
+
consola.warn(`File not found: ${resolved}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!config.githubAppPrivateKey) {
|
|
126
|
+
config.githubAppPrivateKey = Buffer.from(
|
|
127
|
+
await promptText("Paste GitHub App private key PEM:", config.githubAppPrivateKey),
|
|
128
|
+
).toString("base64");
|
|
129
|
+
}
|
|
130
|
+
config.githubInstallationId = await promptText(
|
|
131
|
+
"GitHub App installation ID:",
|
|
132
|
+
config.githubInstallationId,
|
|
133
|
+
);
|
|
134
|
+
config.githubToken = undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
104
137
|
async function setupLinear(config: AegisCliConfig): Promise<void> {
|
|
105
138
|
const enable = (await consola.prompt("Connect Linear too?", {
|
|
106
139
|
type: "confirm",
|
|
@@ -215,3 +248,15 @@ async function promptText(message: string, initial: string | undefined): Promise
|
|
|
215
248
|
})) as string;
|
|
216
249
|
return value?.trim() ?? "";
|
|
217
250
|
}
|
|
251
|
+
|
|
252
|
+
function normalizeGitHubRepo(input: string): string {
|
|
253
|
+
const value = input
|
|
254
|
+
.trim()
|
|
255
|
+
.replace(/\.git$/, "")
|
|
256
|
+
.replace(/\/$/, "");
|
|
257
|
+
const urlMatch = value.match(/github\.com[:/]([^/\s]+)\/([^/\s]+)$/i);
|
|
258
|
+
if (urlMatch) return `${urlMatch[1]}/${urlMatch[2]}`;
|
|
259
|
+
const shorthandMatch = value.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
260
|
+
if (shorthandMatch) return `${shorthandMatch[1]}/${shorthandMatch[2]}`;
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
|
+
import { type ServerResponse, createServer } from "node:http";
|
|
4
|
+
import { consola } from "consola";
|
|
5
|
+
import {
|
|
6
|
+
type ManifestResult,
|
|
7
|
+
exchangeManifestCode,
|
|
8
|
+
generateManifest,
|
|
9
|
+
getManifestCreationUrl,
|
|
10
|
+
} from "../github/manifest.js";
|
|
11
|
+
|
|
12
|
+
export type GitHubAppFlowResult = ManifestResult & {
|
|
13
|
+
installationId: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type OwnerType = "Organization" | "User";
|
|
17
|
+
|
|
18
|
+
export async function runGitHubAppFlow(options: {
|
|
19
|
+
monitoredRepo: string;
|
|
20
|
+
workerUrl?: string;
|
|
21
|
+
}): Promise<GitHubAppFlowResult> {
|
|
22
|
+
const [owner, repo] = options.monitoredRepo.split("/");
|
|
23
|
+
if (!owner || !repo) {
|
|
24
|
+
throw new Error("GitHub App setup needs MONITORED_REPO in owner/repo format.");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ownerType = await detectOwnerType(owner);
|
|
28
|
+
const state = randomUUID();
|
|
29
|
+
const appName = buildAppName(owner, repo);
|
|
30
|
+
let createdApp: ManifestResult | undefined;
|
|
31
|
+
|
|
32
|
+
const result = await new Promise<GitHubAppFlowResult>((resolve, reject) => {
|
|
33
|
+
const server = createServer(async (request, response) => {
|
|
34
|
+
try {
|
|
35
|
+
const host = request.headers.host;
|
|
36
|
+
if (!host) {
|
|
37
|
+
writeText(response, 400, "Missing Host header.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const currentUrl = new URL(request.url ?? "/", `http://${host}`);
|
|
41
|
+
const baseUrl = `http://${host}`;
|
|
42
|
+
|
|
43
|
+
if (currentUrl.pathname === "/") {
|
|
44
|
+
const manifest = generateManifest({
|
|
45
|
+
appName,
|
|
46
|
+
redirectUrl: `${baseUrl}/callback`,
|
|
47
|
+
setupUrl: `${baseUrl}/installed`,
|
|
48
|
+
webhookUrl: options.workerUrl
|
|
49
|
+
? `${options.workerUrl.replace(/\/$/, "")}/webhook/github`
|
|
50
|
+
: "https://example.com/aegis/webhook/github",
|
|
51
|
+
webhookActive: Boolean(options.workerUrl),
|
|
52
|
+
});
|
|
53
|
+
writeHtml(
|
|
54
|
+
response,
|
|
55
|
+
renderManifestForm({
|
|
56
|
+
actionUrl: getManifestCreationUrl(
|
|
57
|
+
ownerType === "Organization" ? owner : undefined,
|
|
58
|
+
state,
|
|
59
|
+
),
|
|
60
|
+
manifest,
|
|
61
|
+
owner,
|
|
62
|
+
repo,
|
|
63
|
+
appName,
|
|
64
|
+
webhookActive: Boolean(options.workerUrl),
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (currentUrl.pathname === "/callback") {
|
|
71
|
+
if (currentUrl.searchParams.get("state") !== state) {
|
|
72
|
+
writeText(response, 400, "State mismatch. Close this tab and rerun aegis setup.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const code = currentUrl.searchParams.get("code");
|
|
76
|
+
if (!code) {
|
|
77
|
+
writeText(response, 400, "Missing GitHub manifest code.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
createdApp = await exchangeManifestCode(code);
|
|
81
|
+
const installUrl = `${createdApp.htmlUrl}/installations/new`;
|
|
82
|
+
consola.success(`Created GitHub App: ${createdApp.appName}`);
|
|
83
|
+
consola.info(`Install it on ${options.monitoredRepo}: ${installUrl}`);
|
|
84
|
+
writeHtml(response, renderInstallPage(installUrl, options.monitoredRepo));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (currentUrl.pathname === "/installed") {
|
|
89
|
+
const installationId = currentUrl.searchParams.get("installation_id");
|
|
90
|
+
if (!installationId || !createdApp) {
|
|
91
|
+
writeText(
|
|
92
|
+
response,
|
|
93
|
+
400,
|
|
94
|
+
"Install callback was missing installation_id. Copy the installation ID from the GitHub URL and rerun setup.",
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
writeHtml(response, renderDonePage());
|
|
99
|
+
resolve({ ...createdApp, installationId });
|
|
100
|
+
setTimeout(() => server.close(), 250);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
writeText(response, 404, "Not found.");
|
|
105
|
+
} catch (error) {
|
|
106
|
+
reject(error);
|
|
107
|
+
writeText(response, 500, error instanceof Error ? error.message : String(error));
|
|
108
|
+
setTimeout(() => server.close(), 250);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
server.on("error", reject);
|
|
113
|
+
server.listen(0, "127.0.0.1", () => {
|
|
114
|
+
const address = server.address();
|
|
115
|
+
if (!address || typeof address === "string") {
|
|
116
|
+
reject(new Error("Could not start local GitHub App setup server."));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const url = `http://127.0.0.1:${address.port}/`;
|
|
120
|
+
consola.info("Opening GitHub App setup in your browser.");
|
|
121
|
+
consola.info("Click Create GitHub App, then Install, then select only the monitored repo.");
|
|
122
|
+
consola.info(`If the browser does not open, paste this URL:\n${url}`);
|
|
123
|
+
openBrowser(url);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function detectOwnerType(owner: string): Promise<OwnerType> {
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch(`https://api.github.com/users/${owner}`, {
|
|
133
|
+
headers: {
|
|
134
|
+
Accept: "application/vnd.github+json",
|
|
135
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
if (!response.ok) return "Organization";
|
|
139
|
+
const data = (await response.json()) as { type?: string };
|
|
140
|
+
return data.type === "User" ? "User" : "Organization";
|
|
141
|
+
} catch {
|
|
142
|
+
return "Organization";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function buildAppName(owner: string, repo: string): string {
|
|
147
|
+
const suffix = randomBytes(2).toString("hex");
|
|
148
|
+
return `aegis-${owner}-${repo}-${suffix}`
|
|
149
|
+
.toLowerCase()
|
|
150
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
151
|
+
.replace(/-+/g, "-")
|
|
152
|
+
.slice(0, 34);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function openBrowser(url: string): void {
|
|
156
|
+
const command =
|
|
157
|
+
process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
158
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
159
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
160
|
+
child.unref();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderManifestForm(options: {
|
|
164
|
+
actionUrl: string;
|
|
165
|
+
manifest: Record<string, unknown>;
|
|
166
|
+
owner: string;
|
|
167
|
+
repo: string;
|
|
168
|
+
appName: string;
|
|
169
|
+
webhookActive: boolean;
|
|
170
|
+
}): string {
|
|
171
|
+
return page(
|
|
172
|
+
"Create Aegis GitHub App",
|
|
173
|
+
`
|
|
174
|
+
<h1>Create Aegis GitHub App</h1>
|
|
175
|
+
<p>This will create a private GitHub App for <strong>${escapeHtml(options.owner)}/${escapeHtml(
|
|
176
|
+
options.repo,
|
|
177
|
+
)}</strong>.</p>
|
|
178
|
+
<ul>
|
|
179
|
+
<li>Permissions: contents, issues, pull requests</li>
|
|
180
|
+
<li>Events: issues, issue comments, pull requests</li>
|
|
181
|
+
<li>App name: ${escapeHtml(options.appName)}</li>
|
|
182
|
+
<li>Webhook: ${options.webhookActive ? "enabled" : "disabled until deploy"}</li>
|
|
183
|
+
</ul>
|
|
184
|
+
<form action="${escapeHtml(options.actionUrl)}" method="post">
|
|
185
|
+
<input type="hidden" name="manifest" value="${escapeHtml(JSON.stringify(options.manifest))}">
|
|
186
|
+
<button type="submit">Create GitHub App</button>
|
|
187
|
+
</form>
|
|
188
|
+
`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function renderInstallPage(installUrl: string, repo: string): string {
|
|
193
|
+
return page(
|
|
194
|
+
"Install Aegis GitHub App",
|
|
195
|
+
`
|
|
196
|
+
<h1>GitHub App created</h1>
|
|
197
|
+
<p>Now install it on <strong>${escapeHtml(repo)}</strong>. Choose <strong>Only select repositories</strong> and pick that repo.</p>
|
|
198
|
+
<p><a class="button" href="${escapeHtml(installUrl)}">Install GitHub App</a></p>
|
|
199
|
+
`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderDonePage(): string {
|
|
204
|
+
return page(
|
|
205
|
+
"Aegis GitHub App Connected",
|
|
206
|
+
`
|
|
207
|
+
<h1>Aegis is connected</h1>
|
|
208
|
+
<p>You can close this tab and return to your terminal.</p>
|
|
209
|
+
`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function page(title: string, body: string): string {
|
|
214
|
+
return `<!doctype html>
|
|
215
|
+
<html lang="en">
|
|
216
|
+
<head>
|
|
217
|
+
<meta charset="utf-8">
|
|
218
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
219
|
+
<title>${escapeHtml(title)}</title>
|
|
220
|
+
<style>
|
|
221
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; max-width: 680px; margin: 64px auto; padding: 0 24px; color: #111827; line-height: 1.5; }
|
|
222
|
+
h1 { font-size: 28px; margin-bottom: 12px; }
|
|
223
|
+
button, .button { display: inline-block; border: 0; border-radius: 6px; background: #166534; color: white; padding: 10px 14px; font: inherit; text-decoration: none; cursor: pointer; }
|
|
224
|
+
ul { padding-left: 22px; }
|
|
225
|
+
</style>
|
|
226
|
+
</head>
|
|
227
|
+
<body>${body}</body>
|
|
228
|
+
</html>`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function writeHtml(response: ServerResponse, html: string): void {
|
|
232
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
233
|
+
response.end(html);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function writeText(response: ServerResponse, status: number, text: string): void {
|
|
237
|
+
response.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
|
|
238
|
+
response.end(text);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function escapeHtml(value: string): string {
|
|
242
|
+
return value
|
|
243
|
+
.replace(/&/g, "&")
|
|
244
|
+
.replace(/</g, "<")
|
|
245
|
+
.replace(/>/g, ">")
|
|
246
|
+
.replace(/"/g, """)
|
|
247
|
+
.replace(/'/g, "'");
|
|
248
|
+
}
|
package/src/github/manifest.ts
CHANGED
|
@@ -22,6 +22,7 @@ const MANIFEST_EVENTS = ["issues", "issue_comment", "pull_request"];
|
|
|
22
22
|
|
|
23
23
|
export type ManifestResult = {
|
|
24
24
|
appId: string;
|
|
25
|
+
slug: string;
|
|
25
26
|
appName: string;
|
|
26
27
|
privateKey: string;
|
|
27
28
|
webhookSecret: string;
|
|
@@ -33,16 +34,25 @@ export type ManifestResult = {
|
|
|
33
34
|
/**
|
|
34
35
|
* Generate the GitHub App manifest JSON.
|
|
35
36
|
*/
|
|
36
|
-
export function generateManifest(
|
|
37
|
+
export function generateManifest(options: {
|
|
38
|
+
appName: string;
|
|
39
|
+
redirectUrl: string;
|
|
40
|
+
setupUrl: string;
|
|
41
|
+
webhookUrl: string;
|
|
42
|
+
webhookActive: boolean;
|
|
43
|
+
}): Record<string, unknown> {
|
|
37
44
|
return {
|
|
38
|
-
name:
|
|
45
|
+
name: options.appName,
|
|
39
46
|
url: "https://github.com/Nairon-AI/aegis",
|
|
47
|
+
description: "Self-hosted AFK bug-fixing agent for ready GitHub issues.",
|
|
40
48
|
hook_attributes: {
|
|
41
|
-
url:
|
|
42
|
-
active:
|
|
49
|
+
url: options.webhookUrl,
|
|
50
|
+
active: options.webhookActive,
|
|
43
51
|
},
|
|
44
|
-
redirect_url:
|
|
45
|
-
callback_urls: [
|
|
52
|
+
redirect_url: options.redirectUrl,
|
|
53
|
+
callback_urls: [options.redirectUrl, options.setupUrl],
|
|
54
|
+
setup_url: options.setupUrl,
|
|
55
|
+
setup_on_update: true,
|
|
46
56
|
public: false,
|
|
47
57
|
default_permissions: MANIFEST_PERMISSIONS,
|
|
48
58
|
default_events: MANIFEST_EVENTS,
|
|
@@ -79,6 +89,7 @@ export async function exchangeManifestCode(code: string): Promise<ManifestResult
|
|
|
79
89
|
|
|
80
90
|
return {
|
|
81
91
|
appId: String(data.id),
|
|
92
|
+
slug: data.slug,
|
|
82
93
|
appName: data.name,
|
|
83
94
|
privateKey: data.pem,
|
|
84
95
|
webhookSecret: data.webhook_secret,
|
|
@@ -89,9 +100,14 @@ export async function exchangeManifestCode(code: string): Promise<ManifestResult
|
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
/**
|
|
92
|
-
* Get the manifest creation URL
|
|
103
|
+
* Get the manifest creation URL for manual fallback.
|
|
104
|
+
*
|
|
105
|
+
* GitHub requires the manifest to be POSTed to this URL. Do not send users here
|
|
106
|
+
* directly without a form body.
|
|
93
107
|
*/
|
|
94
|
-
export function getManifestCreationUrl(
|
|
95
|
-
const
|
|
96
|
-
|
|
108
|
+
export function getManifestCreationUrl(owner?: string, state?: string): string {
|
|
109
|
+
const base = owner
|
|
110
|
+
? `https://github.com/organizations/${owner}/settings/apps/new`
|
|
111
|
+
: "https://github.com/settings/apps/new";
|
|
112
|
+
return state ? `${base}?state=${encodeURIComponent(state)}` : base;
|
|
97
113
|
}
|