@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.
Files changed (35) hide show
  1. package/.flue/app.ts +35 -3
  2. package/README.md +16 -10
  3. package/dist-node/cli/commands/deploy.js +2 -0
  4. package/dist-node/cli/commands/deploy.js.map +1 -1
  5. package/dist-node/cli/commands/disconnect.d.ts.map +1 -1
  6. package/dist-node/cli/commands/disconnect.js +18 -4
  7. package/dist-node/cli/commands/disconnect.js.map +1 -1
  8. package/dist-node/cli/commands/init.d.ts.map +1 -1
  9. package/dist-node/cli/commands/init.js +5 -2
  10. package/dist-node/cli/commands/init.js.map +1 -1
  11. package/dist-node/cli/commands/setup.d.ts.map +1 -1
  12. package/dist-node/cli/commands/setup.js +260 -46
  13. package/dist-node/cli/commands/setup.js.map +1 -1
  14. package/dist-node/cli/state.d.ts.map +1 -1
  15. package/dist-node/cli/state.js +8 -0
  16. package/dist-node/cli/state.js.map +1 -1
  17. package/dist-node/shared/config.d.ts.map +1 -1
  18. package/dist-node/shared/config.js +4 -1
  19. package/dist-node/shared/config.js.map +1 -1
  20. package/dist-node/shared/constants.d.ts +36 -2
  21. package/dist-node/shared/constants.d.ts.map +1 -1
  22. package/dist-node/shared/constants.js +47 -2
  23. package/dist-node/shared/constants.js.map +1 -1
  24. package/dist-node/shared/types.d.ts +6 -0
  25. package/dist-node/shared/types.d.ts.map +1 -1
  26. package/docs/SETUP.md +19 -10
  27. package/package.json +2 -1
  28. package/src/cli/commands/deploy.ts +2 -0
  29. package/src/cli/commands/disconnect.ts +17 -4
  30. package/src/cli/commands/init.ts +5 -2
  31. package/src/cli/commands/setup.ts +304 -65
  32. package/src/cli/state.ts +8 -0
  33. package/src/shared/config.ts +4 -0
  34. package/src/shared/constants.ts +47 -2
  35. package/src/shared/types.ts +7 -0
@@ -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
- AGENT_MODEL=openai/gpt-5.5
11
- OPENAI_API_KEY=sk-...
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 { AGENT_MODEL_OPTIONS, DEFAULT_AGENT_MODEL } from "../../shared/constants.js";
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
- printStep("Linear API key", [
449
- "Open Linear settings, create a personal API key, then paste it here.",
450
- "Aegis uses it to read ready bug tickets and comment with status updates.",
451
- ]);
452
- config.linearApiKey = await promptText("Linear API key:", config.linearApiKey);
453
- await setupLinearTeam(config);
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
- printStep("Linear webhook secret", [
456
- "This protects Linear webhook calls after deploy.",
457
- "Choose yes and Aegis will generate one.",
458
- ]);
459
- const generateSecret = await promptConfirm(
460
- "Generate a Linear webhook signing secret?",
461
- !config.linearWebhookSecret,
462
- );
463
- config.linearWebhookSecret = generateSecret
464
- ? crypto.randomUUID()
465
- : await promptText("Linear webhook signing secret:", config.linearWebhookSecret);
466
- printStep("Linear statuses and labels", [
467
- "These names must match your Linear workflow.",
468
- "If your team uses the default names, press Enter through these prompts.",
469
- ]);
470
- config.linearReadyStatus = await promptText(
471
- "Linear ready status name:",
472
- config.linearReadyStatus ?? "Ready to Implement",
473
- );
474
- config.linearInProgressStatus = await promptText(
475
- "Linear in-progress status name:",
476
- config.linearInProgressStatus ?? "In Progress",
477
- );
478
- config.linearNeedsInfoStatus = await promptText(
479
- "Linear needs-info status name:",
480
- config.linearNeedsInfoStatus ?? "Needs Info",
481
- );
482
- config.linearBlockedStatus = await promptText(
483
- "Linear blocked status name:",
484
- config.linearBlockedStatus ?? "Blocked",
485
- );
486
- config.linearBugLabel = await promptText("Linear bug label:", config.linearBugLabel ?? "bug");
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
- printStep("Step 7: AI model", [
592
- "Use GPT-5.5 unless you know you want another OpenAI/Codex model.",
593
- "Flue model names look like provider/model-id, for example openai/gpt-5.5.",
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 if using an openai/... model.",
773
+ "Paste your OpenAI API key.",
599
774
  "It is stored in .aegis/.env and later copied to Cloudflare secrets during deploy.",
600
- "Leave blank only if you configured a different model provider yourself.",
775
+ "Use Codex account instead if you do not want to manage an API key.",
601
776
  ]);
602
- config.openaiApiKey = await promptText("OpenAI API key:", config.openaiApiKey);
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(current: string | undefined): Promise<string> {
606
- const selectedModel = current ?? DEFAULT_AGENT_MODEL;
607
- const knownValues = AGENT_MODEL_OPTIONS.map((option) => option.value);
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
- [...AGENT_MODEL_OPTIONS, { label: "Custom Flue provider/model string", value: customValue }],
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 AGENT_MODEL_OPTIONS)[number]["value"] | typeof customValue,
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
- config.telegramBotToken = await promptText("Telegram bot token:", config.telegramBotToken);
667
- config.telegramChatId = await promptText("Telegram chat ID:", config.telegramChatId);
668
- printStep("Telegram webhook secret", [
669
- "This protects Telegram webhook calls after deploy.",
670
- "Choose yes and Aegis will generate one.",
671
- ]);
672
- const generateSecret = await promptConfirm(
673
- "Generate a Telegram webhook secret?",
674
- !config.telegramWebhookSecret,
675
- );
676
- config.telegramWebhookSecret = generateSecret
677
- ? crypto.randomUUID()
678
- : await promptText("Telegram webhook secret:", config.telegramWebhookSecret);
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
  }
@@ -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
  }
@@ -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 DEFAULT_AGENT_MODEL = "openai/gpt-5.5";
9
- export const AGENT_MODEL_OPTIONS = [
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",
@@ -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
  };