@oisheek_c/codeforge 1.0.1

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 (141) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +7 -0
  3. package/apps/cli/bin/codeforge.cmd +2 -0
  4. package/apps/cli/src/bootstrap.js +115 -0
  5. package/apps/cli/src/cli.js +189 -0
  6. package/apps/cli/src/dev/git.js +17 -0
  7. package/apps/cli/src/dev/index.js +68 -0
  8. package/apps/cli/src/dev/provider.js +48 -0
  9. package/apps/cli/src/dev/scanner.js +14 -0
  10. package/apps/cli/src/index.js +31 -0
  11. package/bin/codeforge.js +2 -0
  12. package/package.json +63 -0
  13. package/packages/agent/contextBuilder.js +234 -0
  14. package/packages/agent/executor.js +1729 -0
  15. package/packages/agent/fallback.js +129 -0
  16. package/packages/agent/index.js +26 -0
  17. package/packages/agent/intent.js +73 -0
  18. package/packages/agent/modelSelector.js +469 -0
  19. package/packages/agent/planner.js +379 -0
  20. package/packages/agent/rag.js +412 -0
  21. package/packages/agent/ragSelector.js +570 -0
  22. package/packages/agent/router.js +121 -0
  23. package/packages/agent/thinking.js +54 -0
  24. package/packages/agent/toolCalls.js +258 -0
  25. package/packages/config/config.js +391 -0
  26. package/packages/config/defaults.js +72 -0
  27. package/packages/config/index.js +2 -0
  28. package/packages/core/approvalPolicy.js +140 -0
  29. package/packages/core/context.js +0 -0
  30. package/packages/core/message.js +0 -0
  31. package/packages/core/plan.js +0 -0
  32. package/packages/core/reasoning.js +0 -0
  33. package/packages/core/result.js +0 -0
  34. package/packages/core/retrieval.js +0 -0
  35. package/packages/core/tool.js +536 -0
  36. package/packages/git/branch.js +7 -0
  37. package/packages/git/commit.js +16 -0
  38. package/packages/git/diff.js +15 -0
  39. package/packages/git/git.js +35 -0
  40. package/packages/git/index.js +6 -0
  41. package/packages/git/remote.js +9 -0
  42. package/packages/git/status.js +11 -0
  43. package/packages/memory/index.js +0 -0
  44. package/packages/memory/project.js +0 -0
  45. package/packages/memory/session.js +0 -0
  46. package/packages/prompts/builder.js +43 -0
  47. package/packages/prompts/coder.js +38 -0
  48. package/packages/prompts/index.js +5 -0
  49. package/packages/prompts/planner.js +49 -0
  50. package/packages/prompts/reviewer.js +38 -0
  51. package/packages/prompts/system.js +94 -0
  52. package/packages/providers/index.js +31 -0
  53. package/packages/providers/openrouter.js +380 -0
  54. package/packages/reasoning/budget.js +0 -0
  55. package/packages/reasoning/chain.js +0 -0
  56. package/packages/reasoning/coder.js +0 -0
  57. package/packages/reasoning/evaluator.js +0 -0
  58. package/packages/reasoning/index.js +8 -0
  59. package/packages/reasoning/planner.js +0 -0
  60. package/packages/reasoning/reflector.js +0 -0
  61. package/packages/reasoning/reviewer.js +0 -0
  62. package/packages/reasoning/router.js +0 -0
  63. package/packages/retrieval/budget.js +92 -0
  64. package/packages/retrieval/cache.js +84 -0
  65. package/packages/retrieval/embeddings.js +147 -0
  66. package/packages/retrieval/graph.js +151 -0
  67. package/packages/retrieval/imports.js +74 -0
  68. package/packages/retrieval/index.js +9 -0
  69. package/packages/retrieval/parser/extractors/calls.js +43 -0
  70. package/packages/retrieval/parser/extractors/comments.js +49 -0
  71. package/packages/retrieval/parser/extractors/diagnostics.js +21 -0
  72. package/packages/retrieval/parser/extractors/exports.js +71 -0
  73. package/packages/retrieval/parser/extractors/imports.js +40 -0
  74. package/packages/retrieval/parser/extractors/index.js +12 -0
  75. package/packages/retrieval/parser/extractors/metrics.js +78 -0
  76. package/packages/retrieval/parser/extractors/symbols.js +51 -0
  77. package/packages/retrieval/parser/extractors/todos.js +53 -0
  78. package/packages/retrieval/parser/helpers.js +138 -0
  79. package/packages/retrieval/parser/index.js +10 -0
  80. package/packages/retrieval/parser/languages/c.js +14 -0
  81. package/packages/retrieval/parser/languages/cpp.js +21 -0
  82. package/packages/retrieval/parser/languages/csharp.js +14 -0
  83. package/packages/retrieval/parser/languages/go.js +14 -0
  84. package/packages/retrieval/parser/languages/index.js +12 -0
  85. package/packages/retrieval/parser/languages/java.js +14 -0
  86. package/packages/retrieval/parser/languages/javascript.js +14 -0
  87. package/packages/retrieval/parser/languages/php.js +14 -0
  88. package/packages/retrieval/parser/languages/python.js +14 -0
  89. package/packages/retrieval/parser/languages/ruby.js +14 -0
  90. package/packages/retrieval/parser/languages/rust.js +14 -0
  91. package/packages/retrieval/parser/languages/swift.js +14 -0
  92. package/packages/retrieval/parser/languages/typescript.js +14 -0
  93. package/packages/retrieval/parser/languages.js +218 -0
  94. package/packages/retrieval/parser/parseFile.js +79 -0
  95. package/packages/retrieval/parser/parseRepository.js +78 -0
  96. package/packages/retrieval/parser/queries/c.js +148 -0
  97. package/packages/retrieval/parser/queries/cpp.js +198 -0
  98. package/packages/retrieval/parser/queries/csharp.js +151 -0
  99. package/packages/retrieval/parser/queries/go.js +124 -0
  100. package/packages/retrieval/parser/queries/index.js +35 -0
  101. package/packages/retrieval/parser/queries/java.js +128 -0
  102. package/packages/retrieval/parser/queries/javascript.js +181 -0
  103. package/packages/retrieval/parser/queries/php.js +140 -0
  104. package/packages/retrieval/parser/queries/python.js +110 -0
  105. package/packages/retrieval/parser/queries/ruby.js +105 -0
  106. package/packages/retrieval/parser/queries/rust.js +175 -0
  107. package/packages/retrieval/parser/queries/swift.js +134 -0
  108. package/packages/retrieval/parser/queries/typescript.js +202 -0
  109. package/packages/retrieval/parser/registry.js +107 -0
  110. package/packages/retrieval/parser/traverse.js +82 -0
  111. package/packages/retrieval/parser/types.js +239 -0
  112. package/packages/retrieval/parser.js +78 -0
  113. package/packages/retrieval/ranker.js +73 -0
  114. package/packages/retrieval/retriever.js +910 -0
  115. package/packages/retrieval/symbols.js +145 -0
  116. package/packages/scanner/detect.js +69 -0
  117. package/packages/scanner/ignore.js +12 -0
  118. package/packages/scanner/index.js +2 -0
  119. package/packages/scanner/root.js +21 -0
  120. package/packages/scanner/scanner.js +35 -0
  121. package/packages/scanner/tree.js +45 -0
  122. package/packages/terminal/activity.js +514 -0
  123. package/packages/terminal/banner.js +34 -0
  124. package/packages/terminal/colors.js +16 -0
  125. package/packages/terminal/dashboard.js +94 -0
  126. package/packages/terminal/index.js +19 -0
  127. package/packages/terminal/logger.js +43 -0
  128. package/packages/terminal/prompt.js +11 -0
  129. package/packages/terminal/renderer.js +1209 -0
  130. package/packages/terminal/spinner.js +9 -0
  131. package/packages/tools/editFile.js +261 -0
  132. package/packages/tools/filesystem.js +82 -0
  133. package/packages/tools/index.js +24 -0
  134. package/packages/tools/readFile.js +101 -0
  135. package/packages/tools/registry.js +227 -0
  136. package/packages/tools/searchFiles.js +322 -0
  137. package/packages/tools/shell.js +352 -0
  138. package/packages/tools/writeFile.js +212 -0
  139. package/packages/utils/constants.js +0 -0
  140. package/packages/utils/helpers.js +0 -0
  141. package/packages/utils/logger.js +0 -0
@@ -0,0 +1,54 @@
1
+ const DEFAULT_OPTIONS = Object.freeze({
2
+ enabled: false,
3
+ mode: "standard",
4
+ budget: "medium",
5
+ });
6
+
7
+ export function configureThinking({
8
+ plan,
9
+ route,
10
+ }) {
11
+ if (!plan?.requiresThinking) {
12
+ return DEFAULT_OPTIONS;
13
+ }
14
+
15
+ let mode = "standard";
16
+ let budget = "medium";
17
+
18
+ switch (plan.intent) {
19
+ case "plan":
20
+ mode = "deep";
21
+ budget = "high";
22
+ break;
23
+
24
+ case "debug":
25
+ mode = "deep";
26
+ budget = "high";
27
+ break;
28
+
29
+ case "review":
30
+ mode = "deep";
31
+ budget = "high";
32
+ break;
33
+
34
+ case "refactor":
35
+ mode = "deep";
36
+ budget = "high";
37
+ break;
38
+
39
+ case "code":
40
+ mode = "balanced";
41
+ budget = "medium";
42
+ break;
43
+
44
+ default:
45
+ mode = "standard";
46
+ budget = "medium";
47
+ }
48
+
49
+ return {
50
+ enabled: Boolean(route?.thinking),
51
+ mode,
52
+ budget,
53
+ };
54
+ }
@@ -0,0 +1,258 @@
1
+ function createToolCallError(
2
+ code,
3
+ message,
4
+ details = null
5
+ ) {
6
+ const error = new Error(message);
7
+
8
+ error.code = code;
9
+ error.details = details;
10
+
11
+ return error;
12
+ }
13
+
14
+ function parseArguments(value) {
15
+ if (value == null || value === "") {
16
+ return {};
17
+ }
18
+
19
+ if (
20
+ typeof value === "object" &&
21
+ !Array.isArray(value)
22
+ ) {
23
+ return value;
24
+ }
25
+
26
+ if (typeof value !== "string") {
27
+ throw createToolCallError(
28
+ "invalid_tool_arguments",
29
+ "Tool call arguments must be a JSON object or JSON string."
30
+ );
31
+ }
32
+
33
+ let parsed;
34
+
35
+ try {
36
+ parsed = JSON.parse(value);
37
+ } catch {
38
+ throw createToolCallError(
39
+ "invalid_tool_arguments",
40
+ "Tool call arguments contain invalid JSON."
41
+ );
42
+ }
43
+
44
+ if (
45
+ parsed == null ||
46
+ typeof parsed !== "object" ||
47
+ Array.isArray(parsed)
48
+ ) {
49
+ throw createToolCallError(
50
+ "invalid_tool_arguments",
51
+ "Tool call arguments must resolve to an object."
52
+ );
53
+ }
54
+
55
+ return parsed;
56
+ }
57
+
58
+ export function normalizeToolCall(
59
+ toolCall
60
+ ) {
61
+ if (
62
+ !toolCall ||
63
+ typeof toolCall !== "object"
64
+ ) {
65
+ throw createToolCallError(
66
+ "invalid_tool_call",
67
+ "Tool call must be an object."
68
+ );
69
+ }
70
+
71
+ const id =
72
+ toolCall.id ?? null;
73
+
74
+ const type =
75
+ toolCall.type ?? "function";
76
+
77
+ const fn =
78
+ toolCall.function;
79
+
80
+ if (
81
+ !fn ||
82
+ typeof fn !== "object"
83
+ ) {
84
+ throw createToolCallError(
85
+ "invalid_tool_call",
86
+ "Tool call does not contain a function definition."
87
+ );
88
+ }
89
+
90
+ if (
91
+ typeof fn.name !== "string" ||
92
+ fn.name.trim().length === 0
93
+ ) {
94
+ throw createToolCallError(
95
+ "invalid_tool_name",
96
+ "Tool call does not contain a valid tool name."
97
+ );
98
+ }
99
+
100
+ return {
101
+ id,
102
+ type,
103
+ name: fn.name.trim(),
104
+ arguments:
105
+ parseArguments(
106
+ fn.arguments
107
+ ),
108
+ };
109
+ }
110
+
111
+ export function normalizeToolCalls(
112
+ toolCalls = []
113
+ ) {
114
+ if (toolCalls == null) {
115
+ return [];
116
+ }
117
+
118
+ if (!Array.isArray(toolCalls)) {
119
+ throw createToolCallError(
120
+ "invalid_tool_calls",
121
+ "Tool calls must be an array."
122
+ );
123
+ }
124
+
125
+ return toolCalls.map(
126
+ normalizeToolCall
127
+ );
128
+ }
129
+
130
+ export function resolveToolCalls(
131
+ toolCalls,
132
+ registry
133
+ ) {
134
+ const normalized =
135
+ normalizeToolCalls(toolCalls);
136
+
137
+ if (normalized.length === 0) {
138
+ return [];
139
+ }
140
+
141
+ if (
142
+ !registry ||
143
+ typeof registry.get !== "function"
144
+ ) {
145
+ throw createToolCallError(
146
+ "tool_registry_required",
147
+ "A tool registry is required to resolve tool calls."
148
+ );
149
+ }
150
+
151
+ return normalized.map(
152
+ (toolCall) => {
153
+ const tool =
154
+ registry.get(
155
+ toolCall.name
156
+ );
157
+
158
+ if (!tool) {
159
+ const availableTools =
160
+ typeof registry.describe === "function"
161
+ ? registry
162
+ .describe()
163
+ .map((item) => item?.name)
164
+ .filter(Boolean)
165
+ : [];
166
+
167
+ const availableToolText =
168
+ availableTools.length > 0
169
+ ? ` Available tools: ${availableTools.join(", ")}.`
170
+ : "";
171
+
172
+ throw createToolCallError(
173
+ "tool_not_found",
174
+ `Tool is not registered: ${toolCall.name}.${availableToolText} Use only registered tools.`,
175
+ {
176
+ tool:
177
+ toolCall.name,
178
+
179
+ availableTools,
180
+ }
181
+ );
182
+ }
183
+
184
+ return {
185
+ ...toolCall,
186
+ tool,
187
+ };
188
+ }
189
+ );
190
+
191
+ }
192
+
193
+ export function createToolCallFailure({
194
+ toolCall,
195
+ error,
196
+ }) {
197
+ const id =
198
+ typeof toolCall?.id === "string" &&
199
+ toolCall.id.trim().length > 0
200
+ ? toolCall.id
201
+ : `invalid_tool_call_${Date.now()}_${Math.random()
202
+ .toString(36)
203
+ .slice(2, 10)}`;
204
+
205
+ const name =
206
+ typeof toolCall?.function?.name === "string"
207
+ ? toolCall.function.name.trim()
208
+ : null;
209
+
210
+ return {
211
+ id,
212
+ name,
213
+ arguments: null,
214
+
215
+ result: {
216
+ success: false,
217
+ tool: name,
218
+
219
+ output: null,
220
+
221
+ error: {
222
+ code:
223
+ error?.code ??
224
+ "invalid_tool_call",
225
+
226
+ message:
227
+ error?.message ??
228
+ "Invalid tool call.",
229
+
230
+ details:
231
+ error?.details ?? null,
232
+ },
233
+
234
+ metadata: {
235
+ source: "model",
236
+ sideEffect: "none",
237
+ approval: "never",
238
+
239
+ authorization: {
240
+ allowed: false,
241
+ code:
242
+ error?.code ??
243
+ "invalid_tool_call",
244
+ reason:
245
+ error?.message ??
246
+ "Invalid tool call.",
247
+ requiresApproval: false,
248
+ },
249
+ },
250
+
251
+ timing: {
252
+ startedAt: Date.now(),
253
+ completedAt: Date.now(),
254
+ durationMs: 0,
255
+ },
256
+ },
257
+ };
258
+ }
@@ -0,0 +1,391 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+
5
+ import { defaultConfig } from "./defaults.js";
6
+
7
+ const CONFIG_DIR = path.join(os.homedir(), ".codeforge");
8
+ const CURRENT_CONFIG_VERSION = 2;
9
+
10
+ const MODEL_ROLES = [
11
+ "fast",
12
+ "general",
13
+ "coding",
14
+ "heavyCoding",
15
+ "planner",
16
+ "subagent",
17
+ "vision",
18
+ "documentVision",
19
+ "embedding",
20
+ "reranker",
21
+ "fallback",
22
+ "emergency",
23
+ ];
24
+
25
+ const SELECTOR_ROLES = [
26
+ "rag",
27
+ "model",
28
+ ];
29
+
30
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
31
+ const SESSION_FILE = path.join(CONFIG_DIR, "session.json");
32
+
33
+ const CACHE_DIR = path.join(CONFIG_DIR, "cache");
34
+ const LOGS_DIR = path.join(CONFIG_DIR, "logs");
35
+ const HISTORY_DIR = path.join(CONFIG_DIR, "history");
36
+
37
+ function ensureConfigDirectory() {
38
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
39
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
40
+ fs.mkdirSync(LOGS_DIR, { recursive: true });
41
+ fs.mkdirSync(HISTORY_DIR, { recursive: true });
42
+ }
43
+
44
+ function ensureConfigFile() {
45
+ if (!fs.existsSync(CONFIG_FILE)) {
46
+ fs.writeFileSync(
47
+ CONFIG_FILE,
48
+ JSON.stringify(
49
+ {
50
+ ...defaultConfig,
51
+ configVersion: CURRENT_CONFIG_VERSION,
52
+ },
53
+ null,
54
+ 2
55
+ ),
56
+ "utf8"
57
+ );
58
+ }
59
+ }
60
+
61
+ function ensureSessionFile() {
62
+ if (!fs.existsSync(SESSION_FILE)) {
63
+ fs.writeFileSync(
64
+ SESSION_FILE,
65
+ JSON.stringify({}, null, 2),
66
+ "utf8"
67
+ );
68
+ }
69
+ }
70
+
71
+ export function validateConfig(config) {
72
+ if (
73
+ !config ||
74
+ typeof config !== "object" ||
75
+ Array.isArray(config)
76
+ ) {
77
+ throw new TypeError(
78
+ "Configuration must be an object."
79
+ );
80
+ }
81
+
82
+ if (
83
+ typeof config.provider !== "string" ||
84
+ config.provider.trim().length === 0
85
+ ) {
86
+ throw new TypeError(
87
+ "Configuration provider must be a non-empty string."
88
+ );
89
+ }
90
+
91
+ if (
92
+ !config.models ||
93
+ typeof config.models !== "object" ||
94
+ Array.isArray(config.models)
95
+ ) {
96
+ throw new TypeError(
97
+ "Configuration models must be an object."
98
+ );
99
+ }
100
+
101
+ for (const role of MODEL_ROLES) {
102
+ if (
103
+ typeof config.models[role] !== "string" ||
104
+ config.models[role].trim().length === 0
105
+ ) {
106
+ throw new TypeError(
107
+ `Configuration model "${role}" must be a non-empty string.`
108
+ );
109
+ }
110
+ }
111
+
112
+ if (
113
+ !config.selectors ||
114
+ typeof config.selectors !== "object" ||
115
+ Array.isArray(config.selectors)
116
+ ) {
117
+ throw new TypeError(
118
+ "Configuration selectors must be an object."
119
+ );
120
+ }
121
+
122
+ if (
123
+ typeof config.selectors.provider !== "string" ||
124
+ config.selectors.provider.trim().length === 0
125
+ ) {
126
+ throw new TypeError(
127
+ "Configuration selector provider must be a non-empty string."
128
+ );
129
+ }
130
+
131
+ for (const field of SELECTOR_ROLES) {
132
+ if (
133
+ typeof config.selectors[field] !== "string" ||
134
+ config.selectors[field].trim().length === 0
135
+ ) {
136
+ throw new TypeError(
137
+ `Configuration selector "${field}" must be a non-empty string.`
138
+ );
139
+ }
140
+ }
141
+
142
+ for (const field of [
143
+ "maxTokens",
144
+ "modelMaxTokens",
145
+ ]) {
146
+ if (
147
+ !Number.isFinite(config.selectors[field]) ||
148
+ config.selectors[field] <= 0
149
+ ) {
150
+ throw new TypeError(
151
+ `Configuration selector "${field}" must be a positive number.`
152
+ );
153
+ }
154
+ }
155
+
156
+ if (!Number.isFinite(config.temperature)) {
157
+ throw new TypeError(
158
+ "Configuration temperature must be a finite number."
159
+ );
160
+ }
161
+
162
+ if (
163
+ !Number.isFinite(config.maxTokens) ||
164
+ config.maxTokens <= 0
165
+ ) {
166
+ throw new TypeError(
167
+ "Configuration maxTokens must be a positive number."
168
+ );
169
+ }
170
+
171
+ return true;
172
+ }
173
+
174
+ /**
175
+ * Migrate an existing configuration without destroying
176
+ * explicit user customizations.
177
+ *
178
+ * Legacy values that exactly match the old defaults are
179
+ * replaced with the current defaults.
180
+ *
181
+ * Values that differ from the old defaults are preserved.
182
+ */
183
+ export function migrateConfig(config) {
184
+ if (
185
+ !config ||
186
+ typeof config !== "object" ||
187
+ Array.isArray(config)
188
+ ) {
189
+ return {
190
+ config: {
191
+ ...defaultConfig,
192
+ configVersion: CURRENT_CONFIG_VERSION,
193
+ },
194
+ migrated: true,
195
+ };
196
+ }
197
+
198
+ const version = Number(config.configVersion ?? 0);
199
+
200
+ if (version >= CURRENT_CONFIG_VERSION) {
201
+ return {
202
+ config,
203
+ migrated: false,
204
+ };
205
+ }
206
+
207
+ const migratedConfig = {
208
+ ...defaultConfig,
209
+ ...config,
210
+
211
+ configVersion: CURRENT_CONFIG_VERSION,
212
+
213
+ models: {
214
+ ...defaultConfig.models,
215
+ ...(config.models ?? {}),
216
+ },
217
+
218
+ selectors: {
219
+ ...defaultConfig.selectors,
220
+ ...(config.selectors ?? {}),
221
+ },
222
+ };
223
+
224
+ return {
225
+ config: migratedConfig,
226
+ migrated: true,
227
+ };
228
+ }
229
+
230
+ function ensureConfig() {
231
+ ensureConfigDirectory();
232
+ ensureConfigFile();
233
+ }
234
+
235
+ function getEnvOverride(name, fallback) {
236
+ const value = process.env[name];
237
+
238
+ return typeof value === "string" && value.trim().length > 0
239
+ ? value
240
+ : fallback;
241
+ }
242
+
243
+ export function loadConfig() {
244
+ ensureConfig();
245
+
246
+ let config = {};
247
+
248
+ try {
249
+ config = JSON.parse(
250
+ fs.readFileSync(CONFIG_FILE, "utf8")
251
+ );
252
+ } catch {
253
+ config = {};
254
+ }
255
+
256
+ const migration = migrateConfig(config);
257
+ config = migration.config;
258
+
259
+ if (migration.migrated) {
260
+ saveConfig(config);
261
+ }
262
+
263
+ const configuredModels = {
264
+ ...defaultConfig.models,
265
+ ...(config.models ?? {}),
266
+ };
267
+
268
+ const configuredSelectors = {
269
+ ...defaultConfig.selectors,
270
+ ...(config.selectors ?? {}),
271
+ };
272
+
273
+ const models = {
274
+ fast: getEnvOverride(
275
+ "CODEFORGE_MODEL_FAST",
276
+ configuredModels.fast
277
+ ),
278
+
279
+ general: getEnvOverride(
280
+ "CODEFORGE_MODEL_GENERAL",
281
+ configuredModels.general
282
+ ),
283
+
284
+ coding: getEnvOverride(
285
+ "CODEFORGE_MODEL_CODING",
286
+ configuredModels.coding
287
+ ),
288
+
289
+ heavyCoding: getEnvOverride(
290
+ "CODEFORGE_MODEL_HEAVY_CODING",
291
+ configuredModels.heavyCoding
292
+ ),
293
+
294
+ planner: getEnvOverride(
295
+ "CODEFORGE_MODEL_PLANNER",
296
+ configuredModels.planner
297
+ ),
298
+
299
+ subagent: getEnvOverride(
300
+ "CODEFORGE_MODEL_SUBAGENT",
301
+ configuredModels.subagent
302
+ ),
303
+
304
+ vision: getEnvOverride(
305
+ "CODEFORGE_MODEL_VISION",
306
+ configuredModels.vision
307
+ ),
308
+
309
+ documentVision: getEnvOverride(
310
+ "CODEFORGE_MODEL_DOCUMENT_VISION",
311
+ configuredModels.documentVision
312
+ ),
313
+
314
+ embedding: getEnvOverride(
315
+ "CODEFORGE_MODEL_EMBEDDING",
316
+ configuredModels.embedding
317
+ ),
318
+
319
+ reranker: getEnvOverride(
320
+ "CODEFORGE_MODEL_RERANKER",
321
+ configuredModels.reranker
322
+ ),
323
+
324
+ fallback: getEnvOverride(
325
+ "CODEFORGE_MODEL_FALLBACK",
326
+ configuredModels.fallback
327
+ ),
328
+
329
+ emergency: getEnvOverride(
330
+ "CODEFORGE_MODEL_EMERGENCY",
331
+ configuredModels.emergency
332
+ ),
333
+ };
334
+ const selectors = {
335
+ provider: getEnvOverride(
336
+ "CODEFORGE_SELECTOR_PROVIDER",
337
+ configuredSelectors.provider
338
+ ),
339
+
340
+ rag: getEnvOverride(
341
+ "CODEFORGE_SELECTOR_RAG",
342
+ configuredSelectors.rag
343
+ ),
344
+
345
+ model: getEnvOverride(
346
+ "CODEFORGE_SELECTOR_MODEL",
347
+ configuredSelectors.model
348
+ ),
349
+
350
+ maxTokens: configuredSelectors.maxTokens,
351
+
352
+ modelMaxTokens:
353
+ configuredSelectors.modelMaxTokens,
354
+ };
355
+
356
+ return {
357
+ ...defaultConfig,
358
+ ...config,
359
+ configVersion: CURRENT_CONFIG_VERSION,
360
+ apiKey: getEnvOverride(
361
+ "OPENROUTER_API_KEY",
362
+ config.apiKey ?? ""
363
+ ),
364
+ models,
365
+ selectors,
366
+ };
367
+ }
368
+
369
+ export function saveConfig(config) {
370
+ ensureConfigDirectory();
371
+
372
+ fs.writeFileSync(
373
+ CONFIG_FILE,
374
+ JSON.stringify(config, null, 2),
375
+ "utf8"
376
+ );
377
+ }
378
+
379
+ export {
380
+ CONFIG_DIR,
381
+ CONFIG_FILE,
382
+ SESSION_FILE,
383
+ CACHE_DIR,
384
+ LOGS_DIR,
385
+ HISTORY_DIR,
386
+ MODEL_ROLES,
387
+ SELECTOR_ROLES,
388
+ ensureConfigDirectory,
389
+ ensureConfigFile,
390
+ ensureSessionFile,
391
+ };