@nairon-ai/aegis 0.4.9 → 0.5.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/.flue/app.ts +35 -3
- package/README.md +16 -10
- package/dist-node/cli/commands/deploy.js +2 -0
- package/dist-node/cli/commands/deploy.js.map +1 -1
- package/dist-node/cli/commands/disconnect.d.ts.map +1 -1
- package/dist-node/cli/commands/disconnect.js +18 -4
- package/dist-node/cli/commands/disconnect.js.map +1 -1
- package/dist-node/cli/commands/init.d.ts.map +1 -1
- package/dist-node/cli/commands/init.js +5 -2
- 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 +260 -46
- package/dist-node/cli/commands/setup.js.map +1 -1
- package/dist-node/cli/state.d.ts.map +1 -1
- package/dist-node/cli/state.js +8 -0
- package/dist-node/cli/state.js.map +1 -1
- package/dist-node/shared/config.d.ts.map +1 -1
- package/dist-node/shared/config.js +4 -1
- package/dist-node/shared/config.js.map +1 -1
- package/dist-node/shared/constants.d.ts +36 -2
- package/dist-node/shared/constants.d.ts.map +1 -1
- package/dist-node/shared/constants.js +47 -2
- package/dist-node/shared/constants.js.map +1 -1
- package/dist-node/shared/types.d.ts +6 -0
- package/dist-node/shared/types.d.ts.map +1 -1
- package/docs/SETUP.md +19 -10
- package/package.json +2 -1
- package/src/cli/commands/deploy.ts +2 -0
- package/src/cli/commands/disconnect.ts +17 -4
- package/src/cli/commands/init.ts +5 -2
- package/src/cli/commands/setup.ts +304 -65
- package/src/cli/state.ts +8 -0
- package/src/shared/config.ts +4 -0
- package/src/shared/constants.ts +47 -2
- package/src/shared/types.ts +7 -0
package/src/cli/commands/init.ts
CHANGED
|
@@ -7,8 +7,11 @@ import { PROJECT_AEGIS_DIR } from "../paths.js";
|
|
|
7
7
|
const ENV_EXAMPLE = `# Required
|
|
8
8
|
MONITORED_REPO=KeyLead-Team/keylead
|
|
9
9
|
BASE_BRANCH=main
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
AGENT_AUTH_METHOD=codex-account
|
|
11
|
+
AGENT_MODEL=openai-codex/gpt-5.5
|
|
12
|
+
CODEX_OAUTH_CREDENTIALS_B64=
|
|
13
|
+
# Or use AGENT_AUTH_METHOD=openai-api-key with OPENAI_API_KEY=sk-...
|
|
14
|
+
OPENAI_API_KEY=
|
|
12
15
|
|
|
13
16
|
# Labels / automation
|
|
14
17
|
READY_LABEL=ready to implement
|
|
@@ -3,7 +3,13 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { consola } from "consola";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
CODEX_AGENT_MODEL_OPTIONS,
|
|
8
|
+
DEFAULT_AGENT_AUTH_METHOD,
|
|
9
|
+
DEFAULT_CODEX_AGENT_MODEL,
|
|
10
|
+
DEFAULT_OPENAI_API_AGENT_MODEL,
|
|
11
|
+
OPENAI_API_AGENT_MODEL_OPTIONS,
|
|
12
|
+
} from "../../shared/constants.js";
|
|
7
13
|
import type { AegisCliConfig } from "../../shared/types.js";
|
|
8
14
|
import { restoreGitHubAppCache, saveGitHubAppCache } from "../github-app-cache.js";
|
|
9
15
|
import { runGitHubAppFlow } from "../github-app-flow.js";
|
|
@@ -376,6 +382,59 @@ function hasGitHubAppConfig(config: AegisCliConfig): boolean {
|
|
|
376
382
|
return Boolean(config.githubAppId && config.githubAppPrivateKey && config.githubInstallationId);
|
|
377
383
|
}
|
|
378
384
|
|
|
385
|
+
function hasLinearConfig(config: AegisCliConfig): boolean {
|
|
386
|
+
return Boolean(config.linearApiKey && config.linearTeamId);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function hasLinearWorkflowConfig(config: AegisCliConfig): boolean {
|
|
390
|
+
return Boolean(
|
|
391
|
+
config.linearReadyStatus &&
|
|
392
|
+
config.linearInProgressStatus &&
|
|
393
|
+
config.linearNeedsInfoStatus &&
|
|
394
|
+
config.linearBlockedStatus &&
|
|
395
|
+
config.linearBugLabel,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function hasAutomationConfig(config: AegisCliConfig): boolean {
|
|
400
|
+
const authMethod = config.agentAuthMethod ?? DEFAULT_AGENT_AUTH_METHOD;
|
|
401
|
+
const hasModelKey =
|
|
402
|
+
authMethod === "codex-account"
|
|
403
|
+
? Boolean(config.codexOauthCredentials)
|
|
404
|
+
: !config.agentModel?.startsWith("openai/") || Boolean(config.openaiApiKey);
|
|
405
|
+
return Boolean(
|
|
406
|
+
config.readyLabel &&
|
|
407
|
+
config.bugLabel &&
|
|
408
|
+
config.automationMode &&
|
|
409
|
+
config.agentAuthMethod &&
|
|
410
|
+
config.agentModel &&
|
|
411
|
+
hasModelKey,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function hasProductionContextConfig(config: AegisCliConfig): boolean {
|
|
416
|
+
return Boolean(
|
|
417
|
+
config.contextProfile &&
|
|
418
|
+
(config.contextProfile === "minimal" ||
|
|
419
|
+
config.databaseUrl ||
|
|
420
|
+
(config.vercelToken && config.vercelProjectId)),
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function hasTelegramConfig(config: AegisCliConfig): boolean {
|
|
425
|
+
return Boolean(config.telegramBotToken && config.telegramChatId);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function maskSecret(value: string | undefined): string {
|
|
429
|
+
if (!value) return "not configured";
|
|
430
|
+
if (value.length <= 8) return "configured";
|
|
431
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function encodeBase64Json(value: unknown): string {
|
|
435
|
+
return Buffer.from(JSON.stringify(value), "utf-8").toString("base64");
|
|
436
|
+
}
|
|
437
|
+
|
|
379
438
|
function detectInstalledAegisApps(monitoredRepo: string | undefined): InstalledGitHubApp[] {
|
|
380
439
|
if (!monitoredRepo) return [];
|
|
381
440
|
const [owner, repo] = monitoredRepo.split("/");
|
|
@@ -437,6 +496,22 @@ function slugify(value: string): string {
|
|
|
437
496
|
}
|
|
438
497
|
|
|
439
498
|
async function setupLinear(config: AegisCliConfig): Promise<void> {
|
|
499
|
+
let forceTeamSelection = false;
|
|
500
|
+
let forceWorkflowSelection = false;
|
|
501
|
+
if (hasLinearConfig(config)) {
|
|
502
|
+
printStep("Step 4: Linear already connected", [
|
|
503
|
+
`Using saved API key ${maskSecret(config.linearApiKey)}.`,
|
|
504
|
+
`Watching team ${config.linearTeamId}.`,
|
|
505
|
+
config.linearProjectId
|
|
506
|
+
? `Limited to project ${config.linearProjectId}.`
|
|
507
|
+
: "Watching the whole Linear team.",
|
|
508
|
+
]);
|
|
509
|
+
const reconfigure = await promptConfirm("Reconfigure Linear?", false);
|
|
510
|
+
if (!reconfigure) return;
|
|
511
|
+
forceTeamSelection = true;
|
|
512
|
+
forceWorkflowSelection = true;
|
|
513
|
+
}
|
|
514
|
+
|
|
440
515
|
printStep("Step 4: optional Linear connection", [
|
|
441
516
|
"Use this only if your team tracks bugs in Linear.",
|
|
442
517
|
"If you only use GitHub Issues, choose no.",
|
|
@@ -445,50 +520,65 @@ async function setupLinear(config: AegisCliConfig): Promise<void> {
|
|
|
445
520
|
const enable = await promptConfirm("Connect Linear too?", Boolean(config.linearApiKey));
|
|
446
521
|
if (!enable) return;
|
|
447
522
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
523
|
+
if (config.linearApiKey) {
|
|
524
|
+
consola.info(`Using saved Linear API key ${maskSecret(config.linearApiKey)}.`);
|
|
525
|
+
} else {
|
|
526
|
+
printStep("Linear API key", [
|
|
527
|
+
"Open Linear settings, create a personal API key, then paste it here.",
|
|
528
|
+
"Aegis uses it to read ready bug tickets and comment with status updates.",
|
|
529
|
+
]);
|
|
530
|
+
config.linearApiKey = await promptText("Linear API key:", config.linearApiKey);
|
|
531
|
+
}
|
|
532
|
+
await setupLinearTeam(config, forceTeamSelection);
|
|
454
533
|
|
|
455
|
-
|
|
456
|
-
"
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
"Linear
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
534
|
+
if (config.linearWebhookSecret) {
|
|
535
|
+
consola.info("Linear webhook secret is already configured.");
|
|
536
|
+
} else {
|
|
537
|
+
printStep("Linear webhook secret", [
|
|
538
|
+
"This protects Linear webhook calls after deploy.",
|
|
539
|
+
"Choose yes and Aegis will generate one.",
|
|
540
|
+
]);
|
|
541
|
+
const generateSecret = await promptConfirm("Generate a Linear webhook signing secret?", true);
|
|
542
|
+
config.linearWebhookSecret = generateSecret
|
|
543
|
+
? crypto.randomUUID()
|
|
544
|
+
: await promptText("Linear webhook signing secret:", config.linearWebhookSecret);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (hasLinearWorkflowConfig(config) && !forceWorkflowSelection) {
|
|
548
|
+
consola.info("Linear statuses and bug label are already configured.");
|
|
549
|
+
} else {
|
|
550
|
+
printStep("Linear statuses and labels", [
|
|
551
|
+
"These names must match your Linear workflow.",
|
|
552
|
+
"If your team uses the default names, press Enter through these prompts.",
|
|
553
|
+
]);
|
|
554
|
+
config.linearReadyStatus = await promptText(
|
|
555
|
+
"Linear ready status name:",
|
|
556
|
+
config.linearReadyStatus ?? "Ready to Implement",
|
|
557
|
+
);
|
|
558
|
+
config.linearInProgressStatus = await promptText(
|
|
559
|
+
"Linear in-progress status name:",
|
|
560
|
+
config.linearInProgressStatus ?? "In Progress",
|
|
561
|
+
);
|
|
562
|
+
config.linearNeedsInfoStatus = await promptText(
|
|
563
|
+
"Linear needs-info status name:",
|
|
564
|
+
config.linearNeedsInfoStatus ?? "Needs Info",
|
|
565
|
+
);
|
|
566
|
+
config.linearBlockedStatus = await promptText(
|
|
567
|
+
"Linear blocked status name:",
|
|
568
|
+
config.linearBlockedStatus ?? "Blocked",
|
|
569
|
+
);
|
|
570
|
+
config.linearBugLabel = await promptText("Linear bug label:", config.linearBugLabel ?? "bug");
|
|
571
|
+
}
|
|
487
572
|
}
|
|
488
573
|
|
|
489
|
-
async function setupLinearTeam(config: AegisCliConfig): Promise<void> {
|
|
574
|
+
async function setupLinearTeam(config: AegisCliConfig, forceSelection = false): Promise<void> {
|
|
490
575
|
if (!config.linearApiKey) return;
|
|
491
576
|
|
|
577
|
+
if (config.linearTeamId && !forceSelection) {
|
|
578
|
+
consola.info(`Using saved Linear team ${config.linearTeamId}.`);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
492
582
|
printStep("Linear team/project", [
|
|
493
583
|
"Aegis will fetch your Linear teams so you can pick one by name.",
|
|
494
584
|
"Project ID is optional. Leave it blank to watch the whole team.",
|
|
@@ -565,6 +655,22 @@ function formatLinearTeam(team: LinearTeam): string {
|
|
|
565
655
|
}
|
|
566
656
|
|
|
567
657
|
async function setupAutomation(config: AegisCliConfig): Promise<void> {
|
|
658
|
+
if (hasAutomationConfig(config)) {
|
|
659
|
+
printStep("Steps 5-7: automation already configured", [
|
|
660
|
+
`GitHub labels: ${config.bugLabel}, ${config.readyLabel}.`,
|
|
661
|
+
`Mode: ${config.automationMode}.`,
|
|
662
|
+
`Auth: ${formatAgentAuthMethod(config.agentAuthMethod)}.`,
|
|
663
|
+
`Model: ${config.agentModel}.`,
|
|
664
|
+
config.agentAuthMethod === "codex-account"
|
|
665
|
+
? "Codex account: connected."
|
|
666
|
+
: config.openaiApiKey
|
|
667
|
+
? `OpenAI key: ${maskSecret(config.openaiApiKey)}.`
|
|
668
|
+
: "OpenAI key: not configured.",
|
|
669
|
+
]);
|
|
670
|
+
const reconfigure = await promptConfirm("Reconfigure labels, mode, or model?", false);
|
|
671
|
+
if (!reconfigure) return;
|
|
672
|
+
}
|
|
673
|
+
|
|
568
674
|
printStep("Step 5: GitHub labels Aegis should watch", [
|
|
569
675
|
"Aegis only picks up open issues with both labels.",
|
|
570
676
|
"Default labels are bug and ready to implement.",
|
|
@@ -588,36 +694,139 @@ async function setupAutomation(config: AegisCliConfig): Promise<void> {
|
|
|
588
694
|
],
|
|
589
695
|
config.automationMode ?? "plan-first",
|
|
590
696
|
);
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
"
|
|
697
|
+
await setupAgentAuth(config);
|
|
698
|
+
printStep("Step 7b: AI model", [
|
|
699
|
+
"Choose after auth, because Codex account models use openai-codex/... and API-key models use openai/...",
|
|
700
|
+
"Use GPT-5.5 unless you know you want a cheaper or faster model.",
|
|
594
701
|
"Custom models need provider access and their provider key configured manually.",
|
|
595
702
|
]);
|
|
596
|
-
config.agentModel = await promptAgentModel(config.agentModel);
|
|
703
|
+
config.agentModel = await promptAgentModel(config.agentModel, config.agentAuthMethod);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async function setupAgentAuth(config: AegisCliConfig): Promise<void> {
|
|
707
|
+
printStep("Step 7: coding model account", [
|
|
708
|
+
"Choose Codex account if you want to use your ChatGPT/Codex subscription, like Sandcastle.",
|
|
709
|
+
"Choose OpenAI API key if you want normal platform API billing.",
|
|
710
|
+
"Aegis asks this before model selection because each auth method has a different model prefix.",
|
|
711
|
+
]);
|
|
712
|
+
config.agentAuthMethod = await promptSelect<NonNullable<AegisCliConfig["agentAuthMethod"]>>(
|
|
713
|
+
"Model authentication:",
|
|
714
|
+
[
|
|
715
|
+
{ label: "Codex account / ChatGPT login (recommended)", value: "codex-account" },
|
|
716
|
+
{ label: "OpenAI API key", value: "openai-api-key" },
|
|
717
|
+
],
|
|
718
|
+
config.agentAuthMethod ?? DEFAULT_AGENT_AUTH_METHOD,
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
if (config.agentAuthMethod === "codex-account") {
|
|
722
|
+
await setupCodexAccountAuth(config);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
await setupOpenAiApiKeyAuth(config);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async function setupCodexAccountAuth(config: AegisCliConfig): Promise<void> {
|
|
730
|
+
config.openaiApiKey = undefined;
|
|
731
|
+
if (config.codexOauthCredentials) {
|
|
732
|
+
consola.info("Using saved Codex account credentials.");
|
|
733
|
+
if (!config.agentModel?.startsWith("openai-codex/")) {
|
|
734
|
+
config.agentModel = DEFAULT_CODEX_AGENT_MODEL;
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
printStep("Connect Codex account", [
|
|
740
|
+
"A browser window will open for OpenAI/Codex login.",
|
|
741
|
+
"Aegis saves OAuth credentials to .aegis/.env and deploys them as a Cloudflare secret.",
|
|
742
|
+
"Only use this on a Worker you control. To disconnect later, run aegis disconnect and revoke Codex access in OpenAI settings.",
|
|
743
|
+
]);
|
|
744
|
+
const { loginOpenAICodex } = await import("@mariozechner/pi-ai/oauth");
|
|
745
|
+
const credentials = await loginOpenAICodex({
|
|
746
|
+
onAuth: ({ url, instructions }) => {
|
|
747
|
+
consola.info(instructions ?? "Complete OpenAI login in your browser.");
|
|
748
|
+
consola.info(`Open: ${url}`);
|
|
749
|
+
openBrowser(url);
|
|
750
|
+
},
|
|
751
|
+
onPrompt: async (prompt) => await promptText(prompt.message, prompt.placeholder),
|
|
752
|
+
onProgress: (message) => consola.info(message),
|
|
753
|
+
onManualCodeInput: async () =>
|
|
754
|
+
await promptText(
|
|
755
|
+
"Paste OpenAI auth code or final redirect URL if the browser did not finish:",
|
|
756
|
+
undefined,
|
|
757
|
+
),
|
|
758
|
+
originator: "aegis",
|
|
759
|
+
});
|
|
760
|
+
config.codexOauthCredentials = encodeBase64Json(credentials);
|
|
761
|
+
if (!config.agentModel?.startsWith("openai-codex/")) {
|
|
762
|
+
config.agentModel = DEFAULT_CODEX_AGENT_MODEL;
|
|
763
|
+
}
|
|
764
|
+
consola.success("Codex account connected.");
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
async function setupOpenAiApiKeyAuth(config: AegisCliConfig): Promise<void> {
|
|
768
|
+
config.codexOauthCredentials = undefined;
|
|
769
|
+
if (!config.agentModel?.startsWith("openai/")) {
|
|
770
|
+
config.agentModel = DEFAULT_OPENAI_API_AGENT_MODEL;
|
|
771
|
+
}
|
|
597
772
|
printStep("OpenAI API key", [
|
|
598
|
-
"Paste your OpenAI API key
|
|
773
|
+
"Paste your OpenAI API key.",
|
|
599
774
|
"It is stored in .aegis/.env and later copied to Cloudflare secrets during deploy.",
|
|
600
|
-
"
|
|
775
|
+
"Use Codex account instead if you do not want to manage an API key.",
|
|
601
776
|
]);
|
|
602
|
-
|
|
777
|
+
if (config.openaiApiKey) {
|
|
778
|
+
consola.info(`Using saved OpenAI API key ${maskSecret(config.openaiApiKey)}.`);
|
|
779
|
+
} else {
|
|
780
|
+
config.openaiApiKey = await promptText("OpenAI API key:", config.openaiApiKey);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function formatAgentAuthMethod(method: AegisCliConfig["agentAuthMethod"]): string {
|
|
785
|
+
if (method === "codex-account") return "Codex account";
|
|
786
|
+
if (method === "openai-api-key") return "OpenAI API key";
|
|
787
|
+
return "not configured";
|
|
603
788
|
}
|
|
604
789
|
|
|
605
|
-
async function promptAgentModel(
|
|
606
|
-
|
|
607
|
-
|
|
790
|
+
async function promptAgentModel(
|
|
791
|
+
current: string | undefined,
|
|
792
|
+
authMethod: AegisCliConfig["agentAuthMethod"],
|
|
793
|
+
): Promise<string> {
|
|
794
|
+
const options =
|
|
795
|
+
authMethod === "openai-api-key" ? OPENAI_API_AGENT_MODEL_OPTIONS : CODEX_AGENT_MODEL_OPTIONS;
|
|
796
|
+
const defaultModel =
|
|
797
|
+
authMethod === "openai-api-key" ? DEFAULT_OPENAI_API_AGENT_MODEL : DEFAULT_CODEX_AGENT_MODEL;
|
|
798
|
+
const selectedModel = current ?? defaultModel;
|
|
799
|
+
const knownValues = options.map((option) => option.value);
|
|
608
800
|
const customValue = "__custom__";
|
|
609
801
|
const selected = await promptSelect(
|
|
610
802
|
"Flue model:",
|
|
611
|
-
[...
|
|
803
|
+
[...options, { label: "Custom Flue provider/model string", value: customValue }],
|
|
612
804
|
(knownValues.includes(selectedModel as (typeof knownValues)[number])
|
|
613
805
|
? selectedModel
|
|
614
|
-
: customValue) as (typeof
|
|
806
|
+
: customValue) as (typeof options)[number]["value"] | typeof customValue,
|
|
615
807
|
);
|
|
616
808
|
if (selected !== customValue) return selected;
|
|
617
809
|
return await promptText("Custom Flue model:", selectedModel);
|
|
618
810
|
}
|
|
619
811
|
|
|
620
812
|
async function setupProductionContext(config: AegisCliConfig): Promise<void> {
|
|
813
|
+
if (hasProductionContextConfig(config)) {
|
|
814
|
+
printStep("Step 8: production context already configured", [
|
|
815
|
+
`Context profile: ${config.contextProfile}.`,
|
|
816
|
+
config.databaseUrl
|
|
817
|
+
? "Read-only database URL: configured."
|
|
818
|
+
: "Read-only database URL: not set.",
|
|
819
|
+
config.vercelToken
|
|
820
|
+
? `Vercel token: ${maskSecret(config.vercelToken)}.`
|
|
821
|
+
: "Vercel token: not set.",
|
|
822
|
+
config.vercelProjectId
|
|
823
|
+
? `Vercel project: ${config.vercelProjectId}.`
|
|
824
|
+
: "Vercel project: not set.",
|
|
825
|
+
]);
|
|
826
|
+
const reconfigure = await promptConfirm("Reconfigure production context?", false);
|
|
827
|
+
if (!reconfigure) return;
|
|
828
|
+
}
|
|
829
|
+
|
|
621
830
|
printStep("Step 8: production context", [
|
|
622
831
|
"Start with Minimal. It uses only issue text and repo code.",
|
|
623
832
|
"Production context lets Aegis inspect read-only database data and Vercel logs.",
|
|
@@ -648,6 +857,18 @@ async function setupProductionContext(config: AegisCliConfig): Promise<void> {
|
|
|
648
857
|
}
|
|
649
858
|
|
|
650
859
|
async function setupTelegram(config: AegisCliConfig): Promise<void> {
|
|
860
|
+
if (hasTelegramConfig(config)) {
|
|
861
|
+
printStep("Step 9: Telegram already connected", [
|
|
862
|
+
`Bot token: ${maskSecret(config.telegramBotToken)}.`,
|
|
863
|
+
`Chat ID: ${config.telegramChatId}.`,
|
|
864
|
+
config.telegramWebhookSecret
|
|
865
|
+
? "Telegram webhook secret: configured."
|
|
866
|
+
: "Telegram webhook secret: not configured.",
|
|
867
|
+
]);
|
|
868
|
+
const reconfigure = await promptConfirm("Reconfigure Telegram?", false);
|
|
869
|
+
if (!reconfigure) return;
|
|
870
|
+
}
|
|
871
|
+
|
|
651
872
|
printStep("Step 9: optional Telegram approval pings", [
|
|
652
873
|
"Use this if you want Aegis to message you when it needs approval.",
|
|
653
874
|
"For the first local dry-run, choose no.",
|
|
@@ -663,22 +884,40 @@ async function setupTelegram(config: AegisCliConfig): Promise<void> {
|
|
|
663
884
|
"Create a bot with BotFather, paste the bot token, then paste your chat ID.",
|
|
664
885
|
"Aegis will send short approval summaries to that chat.",
|
|
665
886
|
]);
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
config.telegramWebhookSecret
|
|
677
|
-
|
|
678
|
-
|
|
887
|
+
if (config.telegramBotToken) {
|
|
888
|
+
consola.info(`Using saved Telegram bot token ${maskSecret(config.telegramBotToken)}.`);
|
|
889
|
+
} else {
|
|
890
|
+
config.telegramBotToken = await promptText("Telegram bot token:", config.telegramBotToken);
|
|
891
|
+
}
|
|
892
|
+
if (config.telegramChatId) {
|
|
893
|
+
consola.info(`Using saved Telegram chat ID ${config.telegramChatId}.`);
|
|
894
|
+
} else {
|
|
895
|
+
config.telegramChatId = await promptText("Telegram chat ID:", config.telegramChatId);
|
|
896
|
+
}
|
|
897
|
+
if (config.telegramWebhookSecret) {
|
|
898
|
+
consola.info("Telegram webhook secret is already configured.");
|
|
899
|
+
} else {
|
|
900
|
+
printStep("Telegram webhook secret", [
|
|
901
|
+
"This protects Telegram webhook calls after deploy.",
|
|
902
|
+
"Choose yes and Aegis will generate one.",
|
|
903
|
+
]);
|
|
904
|
+
const generateSecret = await promptConfirm("Generate a Telegram webhook secret?", true);
|
|
905
|
+
config.telegramWebhookSecret = generateSecret
|
|
906
|
+
? crypto.randomUUID()
|
|
907
|
+
: await promptText("Telegram webhook secret:", config.telegramWebhookSecret);
|
|
908
|
+
}
|
|
679
909
|
}
|
|
680
910
|
|
|
681
911
|
async function setupWorker(config: AegisCliConfig): Promise<void> {
|
|
912
|
+
if (config.workerUrl) {
|
|
913
|
+
printStep("Step 10: Worker URL already saved", [
|
|
914
|
+
`Using ${config.workerUrl}.`,
|
|
915
|
+
"Deploy updates this automatically when possible.",
|
|
916
|
+
]);
|
|
917
|
+
const reconfigure = await promptConfirm("Reconfigure Worker URL?", false);
|
|
918
|
+
if (!reconfigure) return;
|
|
919
|
+
}
|
|
920
|
+
|
|
682
921
|
printStep("Step 10: Worker URL", [
|
|
683
922
|
"Leave this blank before your first deploy.",
|
|
684
923
|
"After deploy, Aegis saves the Worker URL automatically.",
|
package/src/cli/state.ts
CHANGED
|
@@ -110,12 +110,18 @@ export function loadConfig(): AegisCliConfig {
|
|
|
110
110
|
case "VERCEL_TEAM_ID":
|
|
111
111
|
config.vercelTeamId = value;
|
|
112
112
|
break;
|
|
113
|
+
case "AGENT_AUTH_METHOD":
|
|
114
|
+
config.agentAuthMethod = value as AegisCliConfig["agentAuthMethod"];
|
|
115
|
+
break;
|
|
113
116
|
case "AGENT_MODEL":
|
|
114
117
|
config.agentModel = value;
|
|
115
118
|
break;
|
|
116
119
|
case "OPENAI_API_KEY":
|
|
117
120
|
config.openaiApiKey = value;
|
|
118
121
|
break;
|
|
122
|
+
case "CODEX_OAUTH_CREDENTIALS_B64":
|
|
123
|
+
config.codexOauthCredentials = value;
|
|
124
|
+
break;
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
127
|
return config;
|
|
@@ -159,8 +165,10 @@ export function saveConfig(config: AegisCliConfig): void {
|
|
|
159
165
|
add("VERCEL_TOKEN", config.vercelToken);
|
|
160
166
|
add("VERCEL_PROJECT_ID", config.vercelProjectId);
|
|
161
167
|
add("VERCEL_TEAM_ID", config.vercelTeamId);
|
|
168
|
+
add("AGENT_AUTH_METHOD", config.agentAuthMethod);
|
|
162
169
|
add("AGENT_MODEL", config.agentModel);
|
|
163
170
|
add("OPENAI_API_KEY", config.openaiApiKey);
|
|
171
|
+
add("CODEX_OAUTH_CREDENTIALS_B64", config.codexOauthCredentials);
|
|
164
172
|
mkdirSync(dirname(envFile), { recursive: true });
|
|
165
173
|
writeFileSync(envFile, `${lines.join("\n")}\n`);
|
|
166
174
|
}
|
package/src/shared/config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_AGENT_AUTH_METHOD,
|
|
2
3
|
DEFAULT_AGENT_MODEL,
|
|
3
4
|
DEFAULT_APPROVAL_REQUIRED_LABELS,
|
|
4
5
|
DEFAULT_APPROVAL_REQUIRED_PATHS,
|
|
@@ -64,6 +65,7 @@ export function configFromBindings(env: Bindings): AegisConfig {
|
|
|
64
65
|
DEFAULT_LOG_LOOKBACK_MINUTES,
|
|
65
66
|
),
|
|
66
67
|
},
|
|
68
|
+
agentAuthMethod: env.AGENT_AUTH_METHOD,
|
|
67
69
|
model: env.AGENT_MODEL,
|
|
68
70
|
});
|
|
69
71
|
}
|
|
@@ -107,6 +109,7 @@ export function configFromCli(cli: AegisCliConfig): AegisConfig {
|
|
|
107
109
|
vercelTeamId: cli.vercelTeamId,
|
|
108
110
|
logLookbackMinutes: DEFAULT_LOG_LOOKBACK_MINUTES,
|
|
109
111
|
},
|
|
112
|
+
agentAuthMethod: cli.agentAuthMethod,
|
|
110
113
|
model: cli.agentModel,
|
|
111
114
|
});
|
|
112
115
|
}
|
|
@@ -135,6 +138,7 @@ function normalizeConfig(input: Partial<AegisConfig> & { monitoredRepo: string }
|
|
|
135
138
|
linear: input.linear,
|
|
136
139
|
telegram: input.telegram,
|
|
137
140
|
production: input.production ?? { logLookbackMinutes: DEFAULT_LOG_LOOKBACK_MINUTES },
|
|
141
|
+
agentAuthMethod: input.agentAuthMethod ?? DEFAULT_AGENT_AUTH_METHOD,
|
|
138
142
|
model: input.model ?? DEFAULT_AGENT_MODEL,
|
|
139
143
|
};
|
|
140
144
|
}
|
package/src/shared/constants.ts
CHANGED
|
@@ -5,8 +5,53 @@ export const DEFAULT_NEEDS_INFO_LABEL = "aegis:needs-info";
|
|
|
5
5
|
export const DEFAULT_BLOCKED_LABEL = "aegis:blocked";
|
|
6
6
|
export const DEFAULT_PR_OPENED_LABEL = "aegis:pr-opened";
|
|
7
7
|
export const DEFAULT_BASE_BRANCH = "main";
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
8
|
+
export const DEFAULT_AGENT_AUTH_METHOD = "codex-account";
|
|
9
|
+
export const DEFAULT_CODEX_AGENT_MODEL = "openai-codex/gpt-5.5";
|
|
10
|
+
export const DEFAULT_OPENAI_API_AGENT_MODEL = "openai/gpt-5.5";
|
|
11
|
+
export const DEFAULT_AGENT_MODEL = DEFAULT_CODEX_AGENT_MODEL;
|
|
12
|
+
export const CODEX_AGENT_MODEL_OPTIONS = [
|
|
13
|
+
{
|
|
14
|
+
label: "Codex GPT-5.5 (recommended)",
|
|
15
|
+
value: "openai-codex/gpt-5.5",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
label: "Codex GPT-5.4",
|
|
19
|
+
value: "openai-codex/gpt-5.4",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
label: "Codex GPT-5.4 Mini",
|
|
23
|
+
value: "openai-codex/gpt-5.4-mini",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: "Codex GPT-5.3 Codex",
|
|
27
|
+
value: "openai-codex/gpt-5.3-codex",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: "Codex GPT-5.3 Codex Spark",
|
|
31
|
+
value: "openai-codex/gpt-5.3-codex-spark",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: "Codex GPT-5.2 Codex",
|
|
35
|
+
value: "openai-codex/gpt-5.2-codex",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: "Codex GPT-5.2",
|
|
39
|
+
value: "openai-codex/gpt-5.2",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: "Codex GPT-5.1 Codex Max",
|
|
43
|
+
value: "openai-codex/gpt-5.1-codex-max",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: "Codex GPT-5.1 Codex Mini",
|
|
47
|
+
value: "openai-codex/gpt-5.1-codex-mini",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: "Codex GPT-5.1",
|
|
51
|
+
value: "openai-codex/gpt-5.1",
|
|
52
|
+
},
|
|
53
|
+
] as const;
|
|
54
|
+
export const OPENAI_API_AGENT_MODEL_OPTIONS = [
|
|
10
55
|
{
|
|
11
56
|
label: "OpenAI GPT-5.5 (recommended)",
|
|
12
57
|
value: "openai/gpt-5.5",
|
package/src/shared/types.ts
CHANGED
|
@@ -8,6 +8,8 @@ export type AutomationMode = "plan-first" | "auto-low-risk";
|
|
|
8
8
|
|
|
9
9
|
export type ContextProfile = "minimal" | "production";
|
|
10
10
|
|
|
11
|
+
export type AgentAuthMethod = "codex-account" | "openai-api-key";
|
|
12
|
+
|
|
11
13
|
export type WorkItemStatus =
|
|
12
14
|
| "ready"
|
|
13
15
|
| "needs-info"
|
|
@@ -176,6 +178,7 @@ export type AegisConfig = {
|
|
|
176
178
|
vercelTeamId?: string;
|
|
177
179
|
logLookbackMinutes: number;
|
|
178
180
|
};
|
|
181
|
+
agentAuthMethod: AgentAuthMethod;
|
|
179
182
|
model: string;
|
|
180
183
|
};
|
|
181
184
|
|
|
@@ -219,8 +222,10 @@ export type Bindings = {
|
|
|
219
222
|
VERCEL_PROJECT_ID?: string;
|
|
220
223
|
VERCEL_TEAM_ID?: string;
|
|
221
224
|
VERCEL_LOG_LOOKBACK_MINUTES?: string;
|
|
225
|
+
AGENT_AUTH_METHOD?: AgentAuthMethod;
|
|
222
226
|
AGENT_MODEL?: string;
|
|
223
227
|
OPENAI_API_KEY?: string;
|
|
228
|
+
CODEX_OAUTH_CREDENTIALS_B64?: string;
|
|
224
229
|
};
|
|
225
230
|
|
|
226
231
|
export type AegisCliConfig = {
|
|
@@ -252,6 +257,8 @@ export type AegisCliConfig = {
|
|
|
252
257
|
vercelToken?: string;
|
|
253
258
|
vercelProjectId?: string;
|
|
254
259
|
vercelTeamId?: string;
|
|
260
|
+
agentAuthMethod?: AgentAuthMethod;
|
|
255
261
|
agentModel?: string;
|
|
256
262
|
openaiApiKey?: string;
|
|
263
|
+
codexOauthCredentials?: string;
|
|
257
264
|
};
|