geeto 0.10.1 → 0.12.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 (124) hide show
  1. package/README.md +36 -7
  2. package/lib/api/codex-sdk.d.ts +15 -0
  3. package/lib/api/codex-sdk.d.ts.map +1 -0
  4. package/lib/api/codex-sdk.js +180 -0
  5. package/lib/api/codex-sdk.js.map +1 -0
  6. package/lib/api/codex.d.ts +9 -0
  7. package/lib/api/codex.d.ts.map +1 -0
  8. package/lib/api/codex.js +33 -0
  9. package/lib/api/codex.js.map +1 -0
  10. package/lib/api/copilot-sdk.d.ts.map +1 -1
  11. package/lib/api/copilot-sdk.js +2 -2
  12. package/lib/api/copilot-sdk.js.map +1 -1
  13. package/lib/api/gemini-sdk.d.ts.map +1 -1
  14. package/lib/api/gemini-sdk.js +2 -2
  15. package/lib/api/gemini-sdk.js.map +1 -1
  16. package/lib/api/groq-sdk.d.ts.map +1 -1
  17. package/lib/api/groq-sdk.js +2 -2
  18. package/lib/api/groq-sdk.js.map +1 -1
  19. package/lib/api/openrouter-sdk.d.ts.map +1 -1
  20. package/lib/api/openrouter-sdk.js +2 -2
  21. package/lib/api/openrouter-sdk.js.map +1 -1
  22. package/lib/cli/input.d.ts.map +1 -1
  23. package/lib/cli/input.js +47 -7
  24. package/lib/cli/input.js.map +1 -1
  25. package/lib/core/codex-sdk-setup.d.ts +10 -0
  26. package/lib/core/codex-sdk-setup.d.ts.map +1 -0
  27. package/lib/core/codex-sdk-setup.js +210 -0
  28. package/lib/core/codex-sdk-setup.js.map +1 -0
  29. package/lib/core/setup.d.ts +2 -1
  30. package/lib/core/setup.d.ts.map +1 -1
  31. package/lib/core/setup.js +19 -0
  32. package/lib/core/setup.js.map +1 -1
  33. package/lib/index.js +37 -4
  34. package/lib/index.js.map +1 -1
  35. package/lib/types/index.d.ts +11 -1
  36. package/lib/types/index.d.ts.map +1 -1
  37. package/lib/utils/ai-text.d.ts +4 -0
  38. package/lib/utils/ai-text.d.ts.map +1 -1
  39. package/lib/utils/ai-text.js +27 -0
  40. package/lib/utils/ai-text.js.map +1 -1
  41. package/lib/utils/ai-workflow.d.ts +1 -1
  42. package/lib/utils/ai-workflow.d.ts.map +1 -1
  43. package/lib/utils/ai-workflow.js +7 -1
  44. package/lib/utils/ai-workflow.js.map +1 -1
  45. package/lib/utils/branch-naming.d.ts +1 -1
  46. package/lib/utils/branch-naming.d.ts.map +1 -1
  47. package/lib/utils/branch-naming.js +5 -0
  48. package/lib/utils/branch-naming.js.map +1 -1
  49. package/lib/utils/config.d.ts +10 -1
  50. package/lib/utils/config.d.ts.map +1 -1
  51. package/lib/utils/config.js +56 -1
  52. package/lib/utils/config.js.map +1 -1
  53. package/lib/utils/git-ai.d.ts +7 -7
  54. package/lib/utils/git-ai.d.ts.map +1 -1
  55. package/lib/utils/git-ai.js +126 -7
  56. package/lib/utils/git-ai.js.map +1 -1
  57. package/lib/utils/git.js +2 -2
  58. package/lib/utils/git.js.map +1 -1
  59. package/lib/utils/prompts-embedded.d.ts.map +1 -1
  60. package/lib/utils/prompts-embedded.js +9 -2
  61. package/lib/utils/prompts-embedded.js.map +1 -1
  62. package/lib/utils/state.d.ts.map +1 -1
  63. package/lib/utils/state.js +2 -1
  64. package/lib/utils/state.js.map +1 -1
  65. package/lib/utils/update-checker.d.ts +16 -0
  66. package/lib/utils/update-checker.d.ts.map +1 -0
  67. package/lib/utils/update-checker.js +290 -0
  68. package/lib/utils/update-checker.js.map +1 -0
  69. package/lib/version.d.ts +1 -1
  70. package/lib/version.js +1 -1
  71. package/lib/workflows/ai-provider.d.ts +2 -1
  72. package/lib/workflows/ai-provider.d.ts.map +1 -1
  73. package/lib/workflows/ai-provider.js +7 -1
  74. package/lib/workflows/ai-provider.js.map +1 -1
  75. package/lib/workflows/alias.d.ts.map +1 -1
  76. package/lib/workflows/alias.js +40 -6
  77. package/lib/workflows/alias.js.map +1 -1
  78. package/lib/workflows/branch-helpers.d.ts.map +1 -1
  79. package/lib/workflows/branch-helpers.js +10 -1
  80. package/lib/workflows/branch-helpers.js.map +1 -1
  81. package/lib/workflows/branch.d.ts.map +1 -1
  82. package/lib/workflows/branch.js +58 -13
  83. package/lib/workflows/branch.js.map +1 -1
  84. package/lib/workflows/commit.d.ts +1 -1
  85. package/lib/workflows/commit.d.ts.map +1 -1
  86. package/lib/workflows/commit.js +109 -13
  87. package/lib/workflows/commit.js.map +1 -1
  88. package/lib/workflows/doctor.d.ts.map +1 -1
  89. package/lib/workflows/doctor.js +41 -4
  90. package/lib/workflows/doctor.js.map +1 -1
  91. package/lib/workflows/issue.d.ts.map +1 -1
  92. package/lib/workflows/issue.js +1 -1
  93. package/lib/workflows/issue.js.map +1 -1
  94. package/lib/workflows/main-helpers.d.ts +2 -1
  95. package/lib/workflows/main-helpers.d.ts.map +1 -1
  96. package/lib/workflows/main-helpers.js +11 -0
  97. package/lib/workflows/main-helpers.js.map +1 -1
  98. package/lib/workflows/pr.d.ts.map +1 -1
  99. package/lib/workflows/pr.js +52 -6
  100. package/lib/workflows/pr.js.map +1 -1
  101. package/lib/workflows/release-merge.d.ts.map +1 -1
  102. package/lib/workflows/release-merge.js +23 -3
  103. package/lib/workflows/release-merge.js.map +1 -1
  104. package/lib/workflows/release-sync.d.ts.map +1 -1
  105. package/lib/workflows/release-sync.js +13 -3
  106. package/lib/workflows/release-sync.js.map +1 -1
  107. package/lib/workflows/release.d.ts.map +1 -1
  108. package/lib/workflows/release.js +37 -3
  109. package/lib/workflows/release.js.map +1 -1
  110. package/lib/workflows/repo-settings.d.ts.map +1 -1
  111. package/lib/workflows/repo-settings.js +23 -3
  112. package/lib/workflows/repo-settings.js.map +1 -1
  113. package/lib/workflows/reword.d.ts.map +1 -1
  114. package/lib/workflows/reword.js +79 -13
  115. package/lib/workflows/reword.js.map +1 -1
  116. package/lib/workflows/settings.d.ts.map +1 -1
  117. package/lib/workflows/settings.js +386 -39
  118. package/lib/workflows/settings.js.map +1 -1
  119. package/lib/workflows/update.d.ts +2 -0
  120. package/lib/workflows/update.d.ts.map +1 -0
  121. package/lib/workflows/update.js +29 -0
  122. package/lib/workflows/update.js.map +1 -0
  123. package/package.json +2 -1
  124. package/prompts/commit-message-prompt.md +9 -2
@@ -6,7 +6,7 @@ import path from 'node:path';
6
6
  import { askQuestion, confirm } from '../cli/input.js';
7
7
  import { multiSelect, select } from '../cli/menu.js';
8
8
  import { colors } from '../utils/colors.js';
9
- import { getBranchStrategyConfig, getProtectedBranches, GLOBAL_GEETO_DIR, hasGeminiConfig, hasTrelloConfig, saveBranchStrategyConfig, } from '../utils/config.js';
9
+ import { getBranchStrategyConfig, getCommitConfig, getProtectedBranches, GLOBAL_GEETO_DIR, hasGeminiConfig, hasTrelloConfig, saveBranchStrategyConfig, saveCommitConfig, } from '../utils/config.js';
10
10
  import { log } from '../utils/logging.js';
11
11
  import { ScrambleProgress } from '../utils/scramble.js';
12
12
  const configDirPath = () => path.join(process.cwd(), '.geeto');
@@ -35,7 +35,7 @@ const moveConfigToGlobal = (name) => {
35
35
  return false;
36
36
  }
37
37
  };
38
- const AI_PROVIDERS = ['gemini', 'openrouter', 'groq', 'github', 'gitlab'];
38
+ const AI_PROVIDERS = ['gemini', 'openrouter', 'groq', 'codex', 'github', 'gitlab'];
39
39
  const globalConfigPath = (name) => path.join(GLOBAL_GEETO_DIR, `${name}.toml`);
40
40
  const isConfigGlobal = (name) => existsSync(globalConfigPath(name));
41
41
  const globalProviders = () => AI_PROVIDERS.filter((p) => isConfigGlobal(p));
@@ -78,6 +78,7 @@ const handleGlobalConfigSetting = async () => {
78
78
  { label: 'Gemini', value: 'gemini' },
79
79
  { label: 'OpenRouter', value: 'openrouter' },
80
80
  { label: 'Groq', value: 'groq' },
81
+ { label: 'Codex', value: 'codex' },
81
82
  { label: 'GitHub Copilot', value: 'github' },
82
83
  { label: 'GitLab', value: 'gitlab' },
83
84
  { label: 'Back', value: 'back' },
@@ -103,6 +104,11 @@ const handleGlobalConfigSetting = async () => {
103
104
  setupGroqConfigInteractive();
104
105
  break;
105
106
  }
107
+ case 'codex': {
108
+ const { setupCodexConfigInteractive } = await import('../core/codex-sdk-setup.js');
109
+ await setupCodexConfigInteractive();
110
+ break;
111
+ }
106
112
  case 'github': {
107
113
  const { setupGithubConfigInteractive } = await import('../core/github-setup.js');
108
114
  setupGithubConfigInteractive();
@@ -168,6 +174,17 @@ const runInteractiveSetup = async (name) => {
168
174
  }
169
175
  return;
170
176
  }
177
+ if (name === 'codex') {
178
+ const { setupCodexConfigInteractive } = await import('../core/codex-sdk-setup.js');
179
+ const codexSetupSuccess = await setupCodexConfigInteractive();
180
+ if (codexSetupSuccess) {
181
+ log.success('Codex integration configured!');
182
+ }
183
+ else {
184
+ log.warn('Codex setup failed or cancelled.');
185
+ }
186
+ return;
187
+ }
171
188
  const { setupOpenRouterConfigInteractive } = await import('../core/openrouter-setup.js');
172
189
  const openRouterSetupSuccess = setupOpenRouterConfigInteractive();
173
190
  if (openRouterSetupSuccess) {
@@ -562,6 +579,58 @@ const syncGroqModels = async () => {
562
579
  log.warn(`Groq model sync failed: ${msg}`);
563
580
  }
564
581
  };
582
+ // Sync Codex models (fetch from SDK & persist user favorites)
583
+ const syncCodexModels = async () => {
584
+ try {
585
+ let sdkModule = null;
586
+ try {
587
+ sdkModule = await import('../api/codex-sdk.js');
588
+ }
589
+ catch {
590
+ log.warn('Codex unavailable. Configure Codex first.');
591
+ return;
592
+ }
593
+ const sdk = sdkModule;
594
+ if (!sdk || typeof sdk.getCodexModels !== 'function') {
595
+ log.warn('Codex unavailable. Configure Codex first.');
596
+ return;
597
+ }
598
+ if (typeof sdk.isAvailable === 'function' && !sdk.isAvailable()) {
599
+ const { setupCodexConfigInteractive } = await import('../core/codex-sdk-setup.js');
600
+ const setupOk = await setupCodexConfigInteractive();
601
+ if (!setupOk)
602
+ return;
603
+ }
604
+ const spinner = new ScrambleProgress();
605
+ spinner.start(['Fetching Codex models...']);
606
+ const models = (await sdk.getCodexModels());
607
+ spinner.stop();
608
+ if (!Array.isArray(models) || models.length === 0) {
609
+ log.warn('No Codex models found.');
610
+ return;
611
+ }
612
+ const defaults = models.map((m) => m.value);
613
+ const selected = await multiSelect('Pick your favorite Codex models:', models, defaults);
614
+ if (!selected || selected.length === 0) {
615
+ log.info('No models selected. Sync cancelled.');
616
+ return;
617
+ }
618
+ const simple = selected.map((val, idx) => {
619
+ const detail = models.find((m) => m.value === val);
620
+ return { label: `${idx + 1}. ${detail?.label ?? val}`, value: val };
621
+ });
622
+ const fsModule = await import('node:fs');
623
+ const outDir = path.join(process.cwd(), '.geeto');
624
+ await fsModule.promises.mkdir(outDir, { recursive: true });
625
+ const outFile = path.join(outDir, 'codex-model.json');
626
+ await fsModule.promises.writeFile(outFile, JSON.stringify(simple, null, 2));
627
+ log.success(`Saved ${simple.length} Codex model(s) to .geeto/codex-model.json`);
628
+ }
629
+ catch (error) {
630
+ const msg = error instanceof Error ? error.message : String(error);
631
+ log.warn(`Codex model sync failed: ${msg}`);
632
+ }
633
+ };
565
634
  // Sync Copilot models (fetch from SDK & persist user favorites)
566
635
  const syncCopilotModels = async () => {
567
636
  try {
@@ -645,6 +714,7 @@ const handleModelResetSetting = async () => {
645
714
  { label: 'Gemini', value: 'gemini' },
646
715
  { label: 'OpenRouter', value: 'openrouter' },
647
716
  { label: 'Groq', value: 'groq' },
717
+ { label: 'Codex', value: 'codex' },
648
718
  { label: 'Back to settings menu', value: 'back' },
649
719
  ]);
650
720
  if (resetChoice === 'back') {
@@ -663,6 +733,9 @@ const handleModelResetSetting = async () => {
663
733
  if (resetChoice === 'groq') {
664
734
  await syncGroqModels();
665
735
  }
736
+ if (resetChoice === 'codex') {
737
+ await syncCodexModels();
738
+ }
666
739
  log.success('Model sync completed!');
667
740
  }
668
741
  catch (error) {
@@ -681,6 +754,7 @@ const handleChangeModelSetting = async () => {
681
754
  { label: 'GitHub Copilot', value: 'copilot' },
682
755
  { label: 'OpenRouter', value: 'openrouter' },
683
756
  { label: 'Groq', value: 'groq' },
757
+ { label: 'Codex', value: 'codex' },
684
758
  { label: 'Back to settings menu', value: 'back' },
685
759
  ];
686
760
  const chosenProv = await select('Choose provider to change model for:', provOptions);
@@ -712,6 +786,7 @@ const handleChangeModelSetting = async () => {
712
786
  copilotModel: undefined,
713
787
  openrouterModel: undefined,
714
788
  geminiModel: undefined,
789
+ codexModel: undefined,
715
790
  };
716
791
  base.aiProvider = chosenProv;
717
792
  switch (chosenProv) {
@@ -738,6 +813,15 @@ const handleChangeModelSetting = async () => {
738
813
  base.copilotModel = undefined;
739
814
  base.openrouterModel = undefined;
740
815
  base.geminiModel = undefined;
816
+ base.codexModel = undefined;
817
+ break;
818
+ }
819
+ case 'codex': {
820
+ base.codexModel = picked;
821
+ base.copilotModel = undefined;
822
+ base.openrouterModel = undefined;
823
+ base.geminiModel = undefined;
824
+ base.groqModel = undefined;
741
825
  break;
742
826
  }
743
827
  default: {
@@ -746,8 +830,13 @@ const handleChangeModelSetting = async () => {
746
830
  }
747
831
  base.timestamp = now;
748
832
  saveState(base);
749
- const providerLabel = { gemini: 'Gemini', copilot: 'Copilot', openrouter: 'OpenRouter', groq: 'Groq' }[chosenProv] ??
750
- chosenProv;
833
+ const providerLabel = {
834
+ gemini: 'Gemini',
835
+ copilot: 'Copilot',
836
+ openrouter: 'OpenRouter',
837
+ groq: 'Groq',
838
+ codex: 'Codex',
839
+ }[chosenProv] ?? chosenProv;
751
840
  log.success(`Set ${providerLabel} model to: ${picked}`);
752
841
  // Done; do not go back to settings menu
753
842
  return false;
@@ -945,8 +1034,47 @@ const handleGroqSetting = async () => {
945
1034
  }
946
1035
  return false;
947
1036
  };
1037
+ const handleCodexSetting = async () => {
1038
+ const { hasCodexConfig } = await import('../utils/config.js');
1039
+ const hasConfig = hasCodexConfig();
1040
+ if (!hasConfig) {
1041
+ await runInteractiveSetup('codex');
1042
+ return false;
1043
+ }
1044
+ const action = await select('Codex integration is already configured. What would you like to do?', [
1045
+ { label: 'Reconfigure (check local installation)', value: 'reconfigure' },
1046
+ { label: 'Remove configuration', value: 'remove' },
1047
+ { label: 'Back to settings menu', value: 'back' },
1048
+ ]);
1049
+ switch (action) {
1050
+ case 'reconfigure': {
1051
+ log.info('Reconfiguring Codex integration...');
1052
+ if (removeConfigFile('codex'))
1053
+ log.info('Cleared existing Codex configuration');
1054
+ await runInteractiveSetup('codex');
1055
+ log.success('Codex integration reconfigured!');
1056
+ break;
1057
+ }
1058
+ case 'remove': {
1059
+ const confirmRemove = confirm('Are you sure you want to remove Codex configuration?');
1060
+ if (!confirmRemove)
1061
+ return false;
1062
+ if (removeConfigFile('codex')) {
1063
+ log.success('Codex configuration removed!');
1064
+ }
1065
+ else {
1066
+ log.info('No Codex configuration found to remove');
1067
+ }
1068
+ break;
1069
+ }
1070
+ case 'back': {
1071
+ return true;
1072
+ }
1073
+ }
1074
+ return false;
1075
+ };
948
1076
  const handleSaveGlobalAiConfig = () => {
949
- const providers = ['gemini', 'openrouter', 'groq'];
1077
+ const providers = ['gemini', 'openrouter', 'groq', 'codex'];
950
1078
  const local = providers.filter((p) => isConfigLocal(p));
951
1079
  if (local.length === 0) {
952
1080
  log.info('No local AI config found — already global or not configured.');
@@ -970,73 +1098,286 @@ const handleSaveGlobalAiConfig = () => {
970
1098
  log.success(`Done — ${saved} config(s) saved to ${GLOBAL_GEETO_DIR}`);
971
1099
  return false;
972
1100
  };
1101
+ const handleCommitStyleSetting = async () => {
1102
+ const current = getCommitConfig();
1103
+ while (true) {
1104
+ const commitChoice = await select('Commit settings:', [
1105
+ {
1106
+ label: `Style (${current?.tone ?? 'technical'})`,
1107
+ value: 'tone',
1108
+ },
1109
+ {
1110
+ label: `Subject length (${current?.subjectLength ?? 72} chars)`,
1111
+ value: 'subject-length',
1112
+ },
1113
+ {
1114
+ label: `Body style (${current?.style ?? 'multiline'})`,
1115
+ value: 'body-style',
1116
+ },
1117
+ { label: 'Back to settings menu', value: 'back' },
1118
+ ]);
1119
+ if (commitChoice === 'back')
1120
+ return true;
1121
+ if (commitChoice === 'tone') {
1122
+ const toneChoice = await select('Commit tone:', [
1123
+ { label: 'Technical (Precise and specific)', value: 'technical' },
1124
+ { label: 'Concise (Short and compact)', value: 'concise' },
1125
+ { label: 'Descriptive (Natural and explanatory)', value: 'descriptive' },
1126
+ { label: 'Back', value: 'back' },
1127
+ ]);
1128
+ if (toneChoice === 'back')
1129
+ continue;
1130
+ saveCommitConfig({
1131
+ style: current?.style ?? 'multiline',
1132
+ subjectLength: current?.subjectLength ?? 72,
1133
+ tone: toneChoice,
1134
+ });
1135
+ log.success(`Commit tone set to ${toneChoice}`);
1136
+ return false;
1137
+ }
1138
+ if (commitChoice === 'subject-length') {
1139
+ const lengthChoice = await select(`Subject length (current: ${current?.subjectLength ?? 72}):`, [
1140
+ { label: '50 chars (conventional commits standard)', value: '50' },
1141
+ { label: '72 chars (git standard)', value: '72' },
1142
+ { label: '100 chars (modern projects)', value: '100' },
1143
+ { label: 'Back', value: 'back' },
1144
+ ]);
1145
+ if (lengthChoice === 'back')
1146
+ continue;
1147
+ saveCommitConfig({
1148
+ style: current?.style ?? 'multiline',
1149
+ subjectLength: Number.parseInt(lengthChoice, 10),
1150
+ tone: current?.tone,
1151
+ });
1152
+ log.success(`Subject length set to ${lengthChoice} chars`);
1153
+ return false;
1154
+ }
1155
+ if (commitChoice === 'body-style') {
1156
+ const bodyChoice = await select('Body style:', [
1157
+ { label: 'Multiline (subject + body with blank line)', value: 'multiline' },
1158
+ { label: 'Singleline (subject only, no body)', value: 'singleline' },
1159
+ { label: 'Back', value: 'back' },
1160
+ ]);
1161
+ if (bodyChoice === 'back')
1162
+ continue;
1163
+ saveCommitConfig({
1164
+ style: bodyChoice,
1165
+ subjectLength: current?.subjectLength ?? 72,
1166
+ tone: current?.tone,
1167
+ });
1168
+ log.success(`Body style set to ${bodyChoice}`);
1169
+ return false;
1170
+ }
1171
+ }
1172
+ };
973
1173
  export const showSettingsMenu = async () => {
974
1174
  while (true) {
975
1175
  log.info('Settings Menu');
976
1176
  const hasGlobalConfig = globalProviders().length > 0;
1177
+ const hasLocalGeetoFolder = existsSync(configDirPath());
1178
+ const hasLocalAiConfig = ['gemini', 'openrouter', 'groq', 'codex'].some((p) => isConfigLocal(p));
977
1179
  const menuOptions = [
978
- { label: 'Branch', value: '_branch', disabled: true },
979
- { label: ' Branch prefix (dev#name / dev/name)', value: 'prefix' },
980
- { label: ' Branch separator (hyphen / underscore)', value: 'separator' },
981
- { label: ' Protected branches', value: 'protected' },
982
- { label: 'AI', value: '_ai', disabled: true },
983
- { label: ' Active model (switch provider & model)', value: 'change-model' },
984
- { label: ' Saved models (manage favorites per provider)', value: 'models' },
985
- { label: ' Move local AI config to global (~/.geeto/)', value: 'save-global' },
1180
+ { label: 'AI', value: '_ai' },
1181
+ { label: 'Branch', value: '_branch' },
1182
+ { label: 'Commit', value: '_commit' },
1183
+ { label: 'Setup', value: '_setup' },
1184
+ { label: 'System', value: '_system' },
1185
+ { label: 'Back', value: 'back' },
986
1186
  ];
987
- if (hasGlobalConfig) {
988
- menuOptions.push({ label: ' Manage global config (~/.geeto/)', value: 'global-config' });
989
- }
990
- menuOptions.push({ label: 'Setup', value: '_setup', disabled: true }, { label: ' GitHub Copilot', value: 'copilot' }, { label: ' Gemini', value: 'gemini' }, { label: ' OpenRouter', value: 'openrouter' }, { label: ' Groq', value: 'groq' }, { label: ' Trello', value: 'trello' }, { label: 'System', value: '_system', disabled: true }, { label: ' Installation info', value: 'where' }, { label: ' Uninstall geeto', value: 'uninstall' }, { label: 'Back', value: 'back' });
991
1187
  const settingChoice = await select('Settings:', menuOptions);
992
1188
  if (settingChoice === 'back') {
993
1189
  break;
994
1190
  }
1191
+ if (settingChoice === '_branch') {
1192
+ const branchConfig = getBranchStrategyConfig();
1193
+ const branchChoice = await select('Branch settings:', [
1194
+ { label: 'Branch prefix (dev#name / dev/name)', value: 'prefix' },
1195
+ { label: 'Branch separator (hyphen / underscore)', value: 'separator' },
1196
+ { label: `Max words (${branchConfig?.maxWords ?? 3})`, value: 'max-words' },
1197
+ { label: 'Protected branches', value: 'protected' },
1198
+ { label: 'Back', value: 'back' },
1199
+ ]);
1200
+ if (branchChoice === 'back')
1201
+ continue;
1202
+ if (branchChoice === 'prefix') {
1203
+ const back = await handlePrefixFormatSetting();
1204
+ if (back)
1205
+ continue;
1206
+ }
1207
+ if (branchChoice === 'separator') {
1208
+ const back = await handleSeparatorSetting();
1209
+ if (back)
1210
+ continue;
1211
+ }
1212
+ if (branchChoice === 'max-words') {
1213
+ const wordChoice = await select(`Max words in branch name (current: ${branchConfig?.maxWords ?? 3}):`, [
1214
+ { label: '1 word', value: '1' },
1215
+ { label: '2 words', value: '2' },
1216
+ { label: '3 words', value: '3' },
1217
+ { label: 'Back', value: 'back' },
1218
+ ]);
1219
+ if (wordChoice === 'back')
1220
+ continue;
1221
+ const updated = branchConfig ?? { separator: '-' };
1222
+ updated.maxWords = Number.parseInt(wordChoice, 10);
1223
+ saveBranchStrategyConfig(updated);
1224
+ log.success(`Branch max words set to ${wordChoice}`);
1225
+ // fall through to "Configure another setting?"
1226
+ }
1227
+ if (branchChoice === 'protected') {
1228
+ const back = await handleProtectedBranchesSetting();
1229
+ if (back)
1230
+ continue;
1231
+ }
1232
+ // fall through to "Configure another setting?"
1233
+ }
1234
+ if (settingChoice === '_ai') {
1235
+ const aiChoice = await select('AI settings:', [
1236
+ { label: 'Active model (switch provider & model)', value: 'change-model' },
1237
+ { label: 'Saved models (manage favorites per provider)', value: 'models' },
1238
+ { label: 'Back', value: 'back' },
1239
+ ]);
1240
+ if (aiChoice === 'back')
1241
+ continue;
1242
+ if (aiChoice === 'change-model') {
1243
+ const back = await handleChangeModelSetting();
1244
+ if (back)
1245
+ continue;
1246
+ }
1247
+ if (aiChoice === 'models') {
1248
+ const back = await handleModelResetSetting();
1249
+ if (back)
1250
+ continue;
1251
+ }
1252
+ // fall through to "Configure another setting?"
1253
+ }
1254
+ if (settingChoice === '_commit') {
1255
+ const back = await handleCommitStyleSetting();
1256
+ if (back)
1257
+ continue;
1258
+ // fall through to "Configure another setting?"
1259
+ }
1260
+ if (settingChoice === '_setup') {
1261
+ const setupChoice = await select('Setup:', [
1262
+ { label: 'GitHub Copilot', value: 'copilot' },
1263
+ { label: 'Gemini', value: 'gemini' },
1264
+ { label: 'OpenRouter', value: 'openrouter' },
1265
+ { label: 'Groq', value: 'groq' },
1266
+ { label: 'Codex', value: 'codex' },
1267
+ { label: 'Trello', value: 'trello' },
1268
+ { label: 'Back', value: 'back' },
1269
+ ]);
1270
+ if (setupChoice === 'back')
1271
+ continue;
1272
+ if (setupChoice === 'copilot') {
1273
+ const back = await handleCopilotSetting();
1274
+ if (back)
1275
+ continue;
1276
+ }
1277
+ if (setupChoice === 'gemini') {
1278
+ const back = await handleGeminiSetting();
1279
+ if (back)
1280
+ continue;
1281
+ }
1282
+ if (setupChoice === 'openrouter') {
1283
+ const back = await handleOpenRouterSetting();
1284
+ if (back)
1285
+ continue;
1286
+ }
1287
+ if (setupChoice === 'groq') {
1288
+ const back = await handleGroqSetting();
1289
+ if (back)
1290
+ continue;
1291
+ }
1292
+ if (setupChoice === 'codex') {
1293
+ const back = await handleCodexSetting();
1294
+ if (back)
1295
+ continue;
1296
+ }
1297
+ if (setupChoice === 'trello') {
1298
+ const back = await handleTrelloSetting();
1299
+ if (back)
1300
+ continue;
1301
+ }
1302
+ // fall through to "Configure another setting?"
1303
+ }
1304
+ if (settingChoice === '_system') {
1305
+ const systemOptions = [
1306
+ { label: 'Installation info', value: 'where' },
1307
+ { label: 'Uninstall geeto', value: 'uninstall' },
1308
+ ];
1309
+ if (hasLocalGeetoFolder && hasLocalAiConfig) {
1310
+ systemOptions.push({
1311
+ label: 'Move local AI config to global (~/.geeto/)',
1312
+ value: 'save-global',
1313
+ });
1314
+ }
1315
+ if (hasLocalGeetoFolder && hasGlobalConfig) {
1316
+ systemOptions.push({ label: 'Manage global config (~/.geeto/)', value: 'global-config' });
1317
+ }
1318
+ systemOptions.push({ label: 'Back', value: 'back' });
1319
+ const systemChoice = await select('System:', systemOptions);
1320
+ if (systemChoice === 'back')
1321
+ continue;
1322
+ switch (systemChoice) {
1323
+ case 'where': {
1324
+ const { handleWhereInstalled } = await import('./doctor.js');
1325
+ await handleWhereInstalled();
1326
+ // fall through to "Configure another setting?"
1327
+ break;
1328
+ }
1329
+ case 'uninstall': {
1330
+ const { handleUninstall } = await import('./doctor.js');
1331
+ await handleUninstall();
1332
+ process.exit(0);
1333
+ break;
1334
+ }
1335
+ case 'save-global': {
1336
+ handleSaveGlobalAiConfig();
1337
+ // fall through to "Configure another setting?"
1338
+ break;
1339
+ }
1340
+ case 'global-config': {
1341
+ const back = await handleGlobalConfigSetting();
1342
+ if (back)
1343
+ continue;
1344
+ // fall through to "Configure another setting?"
1345
+ break;
1346
+ }
1347
+ // No default
1348
+ }
1349
+ // fall through to "Configure another setting?"
1350
+ }
1351
+ // Legacy flat handlers — kept for safety
995
1352
  if (settingChoice === 'prefix') {
996
1353
  const back = await handlePrefixFormatSetting();
997
- if (back) {
1354
+ if (back)
998
1355
  continue;
999
- }
1000
1356
  }
1001
1357
  if (settingChoice === 'separator') {
1002
1358
  const back = await handleSeparatorSetting();
1003
- if (back) {
1359
+ if (back)
1004
1360
  continue;
1005
- }
1006
1361
  }
1007
1362
  if (settingChoice === 'protected') {
1008
1363
  const back = await handleProtectedBranchesSetting();
1009
- if (back) {
1364
+ if (back)
1010
1365
  continue;
1011
- }
1012
1366
  }
1013
1367
  if (settingChoice === 'models') {
1014
1368
  const back = await handleModelResetSetting();
1015
- if (back) {
1369
+ if (back)
1016
1370
  continue;
1017
- }
1018
- }
1019
- if (settingChoice === 'save-global') {
1020
- handleSaveGlobalAiConfig();
1021
- }
1022
- if (settingChoice === 'global-config') {
1023
- const back = await handleGlobalConfigSetting();
1024
- if (back) {
1025
- continue;
1026
- }
1027
1371
  }
1028
1372
  if (settingChoice === 'change-model') {
1029
1373
  const back = await handleChangeModelSetting();
1030
- if (back) {
1031
- // user chose to go back from within handler — return to settings menu
1374
+ if (back)
1032
1375
  continue;
1033
- }
1034
1376
  }
1035
1377
  if (settingChoice === 'copilot') {
1036
1378
  const back = await handleCopilotSetting();
1037
- if (back) {
1379
+ if (back)
1038
1380
  continue;
1039
- }
1040
1381
  }
1041
1382
  if (settingChoice === 'gemini') {
1042
1383
  const back = await handleGeminiSetting();
@@ -1062,6 +1403,12 @@ export const showSettingsMenu = async () => {
1062
1403
  continue;
1063
1404
  }
1064
1405
  }
1406
+ if (settingChoice === 'codex') {
1407
+ const back = await handleCodexSetting();
1408
+ if (back) {
1409
+ continue;
1410
+ }
1411
+ }
1065
1412
  if (settingChoice === 'where') {
1066
1413
  const { handleWhereInstalled } = await import('./doctor.js');
1067
1414
  await handleWhereInstalled();