@octocodeai/config 17.0.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/LICENSE +21 -0
- package/README.md +466 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +263 -0
- package/dist/cli.js.map +7 -0
- package/dist/config/defaults.d.ts +16 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/resolver.d.ts +3 -0
- package/dist/config/resolver.d.ts.map +1 -0
- package/dist/config/resolverCache.d.ts +13 -0
- package/dist/config/resolverCache.d.ts.map +1 -0
- package/dist/config/resolverSections.d.ts +16 -0
- package/dist/config/resolverSections.d.ts.map +1 -0
- package/dist/config/runtimeSurface.d.ts +23 -0
- package/dist/config/runtimeSurface.d.ts.map +1 -0
- package/dist/config/types.d.ts +103 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/validator.d.ts +3 -0
- package/dist/config/validator.d.ts.map +1 -0
- package/dist/home.d.ts +2 -0
- package/dist/home.d.ts.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +814 -0
- package/dist/index.js.map +7 -0
- package/dist/tokens/envTokens.d.ts +24 -0
- package/dist/tokens/envTokens.d.ts.map +1 -0
- package/dist/tokens/index.d.ts +3 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/types.d.ts +6 -0
- package/dist/tokens/types.d.ts.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path3 from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/home.ts
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
function getOctocodeHome(env = process.env) {
|
|
9
|
+
const override = env["OCTOCODE_HOME"];
|
|
10
|
+
if (override && override.trim()) return path.resolve(override.trim());
|
|
11
|
+
const home = os.homedir();
|
|
12
|
+
const platform = os.platform();
|
|
13
|
+
if (platform === "win32") {
|
|
14
|
+
const appData = env["APPDATA"] ?? path.join(home, "AppData", "Roaming");
|
|
15
|
+
return path.join(appData, ".octocode");
|
|
16
|
+
}
|
|
17
|
+
if (platform === "darwin") return path.join(home, ".octocode");
|
|
18
|
+
const xdg = env["XDG_CONFIG_HOME"] ?? path.join(home, ".config");
|
|
19
|
+
return path.join(xdg, ".octocode");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/config/types.ts
|
|
23
|
+
var CONFIG_SCHEMA_VERSION = 1;
|
|
24
|
+
var CONFIG_FILE_NAME = ".octocoderc";
|
|
25
|
+
|
|
26
|
+
// src/config/defaults.ts
|
|
27
|
+
var DEFAULT_GITHUB_CONFIG = {
|
|
28
|
+
apiUrl: "https://api.github.com"
|
|
29
|
+
};
|
|
30
|
+
var DEFAULT_LOCAL_CONFIG = {
|
|
31
|
+
enabled: true,
|
|
32
|
+
enableClone: false,
|
|
33
|
+
allowedPaths: [],
|
|
34
|
+
workspaceRoot: void 0
|
|
35
|
+
};
|
|
36
|
+
var DEFAULT_TOOLS_CONFIG = {
|
|
37
|
+
enabled: null,
|
|
38
|
+
enableAdditional: null,
|
|
39
|
+
disabled: null
|
|
40
|
+
};
|
|
41
|
+
var DEFAULT_NETWORK_CONFIG = {
|
|
42
|
+
timeout: 3e4,
|
|
43
|
+
maxRetries: 3
|
|
44
|
+
};
|
|
45
|
+
var DEFAULT_LSP_CONFIG = {
|
|
46
|
+
configPath: void 0
|
|
47
|
+
};
|
|
48
|
+
var DEFAULT_OUTPUT_CONFIG = {
|
|
49
|
+
format: "yaml",
|
|
50
|
+
pagination: {
|
|
51
|
+
defaultCharLength: 2e4
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var DEFAULT_SESSION_CONFIG = {
|
|
55
|
+
/** Stats persistence is opt-in: set OCTOCODE_ENABLE_STATS=1 to enable. */
|
|
56
|
+
enableStats: false
|
|
57
|
+
};
|
|
58
|
+
var DEFAULT_CONFIG = {
|
|
59
|
+
version: 1,
|
|
60
|
+
github: DEFAULT_GITHUB_CONFIG,
|
|
61
|
+
local: DEFAULT_LOCAL_CONFIG,
|
|
62
|
+
tools: DEFAULT_TOOLS_CONFIG,
|
|
63
|
+
network: DEFAULT_NETWORK_CONFIG,
|
|
64
|
+
lsp: DEFAULT_LSP_CONFIG,
|
|
65
|
+
output: DEFAULT_OUTPUT_CONFIG,
|
|
66
|
+
session: DEFAULT_SESSION_CONFIG
|
|
67
|
+
};
|
|
68
|
+
var MIN_TIMEOUT = 5e3;
|
|
69
|
+
var MAX_TIMEOUT = 3e5;
|
|
70
|
+
var MIN_RETRIES = 0;
|
|
71
|
+
var MAX_RETRIES = 10;
|
|
72
|
+
var MIN_OUTPUT_DEFAULT_CHAR_LENGTH = 1e3;
|
|
73
|
+
var MAX_OUTPUT_DEFAULT_CHAR_LENGTH = 5e4;
|
|
74
|
+
|
|
75
|
+
// src/config/runtimeSurface.ts
|
|
76
|
+
var SURFACE_KEY = "__octocodeRuntimeSurface__";
|
|
77
|
+
function setRuntimeSurface(surface) {
|
|
78
|
+
globalThis[SURFACE_KEY] = surface;
|
|
79
|
+
}
|
|
80
|
+
function getRuntimeSurface() {
|
|
81
|
+
return globalThis[SURFACE_KEY] ?? "mcp";
|
|
82
|
+
}
|
|
83
|
+
function _resetRuntimeSurface() {
|
|
84
|
+
delete globalThis[SURFACE_KEY];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/config/validator.ts
|
|
88
|
+
function validateUrl(url, field) {
|
|
89
|
+
if (url === void 0 || url === null) return null;
|
|
90
|
+
if (typeof url !== "string") {
|
|
91
|
+
return `${field}: Must be a string`;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const parsed = new URL(url);
|
|
95
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
96
|
+
return `${field}: Only http/https URLs allowed`;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
} catch {
|
|
100
|
+
return `${field}: Invalid URL format`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function validateNumberRange(value, field, min, max) {
|
|
104
|
+
if (value === void 0 || value === null) return null;
|
|
105
|
+
if (typeof value !== "number" || isNaN(value)) {
|
|
106
|
+
return `${field}: Must be a number`;
|
|
107
|
+
}
|
|
108
|
+
if (value < min || value > max) {
|
|
109
|
+
return `${field}: Must be between ${min} and ${max}`;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function validateBoolean(value, field) {
|
|
114
|
+
if (value === void 0 || value === null) return null;
|
|
115
|
+
if (typeof value !== "boolean") {
|
|
116
|
+
return `${field}: Must be a boolean`;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function validateStringArray(value, field) {
|
|
121
|
+
if (value === void 0 || value === null) return null;
|
|
122
|
+
if (!Array.isArray(value)) {
|
|
123
|
+
return `${field}: Must be an array`;
|
|
124
|
+
}
|
|
125
|
+
for (let i = 0; i < value.length; i++) {
|
|
126
|
+
if (typeof value[i] !== "string") {
|
|
127
|
+
return `${field}[${i}]: Must be a string`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function validateAllowedPathElements(paths) {
|
|
133
|
+
const errors = [];
|
|
134
|
+
for (let i = 0; i < paths.length; i++) {
|
|
135
|
+
const p = paths[i];
|
|
136
|
+
if (typeof p !== "string") continue;
|
|
137
|
+
if (p.trim() === "") {
|
|
138
|
+
errors.push(`local.allowedPaths[${i}]: empty or whitespace-only path`);
|
|
139
|
+
} else if (!p.startsWith("/") && !p.startsWith("~")) {
|
|
140
|
+
errors.push(
|
|
141
|
+
`local.allowedPaths[${i}]: must be absolute path or start with ~ (got "${p}")`
|
|
142
|
+
);
|
|
143
|
+
} else if (p.includes("..")) {
|
|
144
|
+
errors.push(
|
|
145
|
+
`local.allowedPaths[${i}]: path traversal (..) not allowed (got "${p}")`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return errors;
|
|
150
|
+
}
|
|
151
|
+
function validateNullableStringArray(value, field) {
|
|
152
|
+
if (value === void 0) return null;
|
|
153
|
+
if (value === null) return null;
|
|
154
|
+
return validateStringArray(value, field);
|
|
155
|
+
}
|
|
156
|
+
function validateString(value, field) {
|
|
157
|
+
if (value === void 0 || value === null) return null;
|
|
158
|
+
if (typeof value !== "string") {
|
|
159
|
+
return `${field}: Must be a string`;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function validateGitHub(github, errors) {
|
|
164
|
+
if (github === void 0 || github === null) return;
|
|
165
|
+
if (typeof github !== "object" || Array.isArray(github)) {
|
|
166
|
+
errors.push("github: Must be an object");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const gh = github;
|
|
170
|
+
const apiUrlError = validateUrl(gh.apiUrl, "github.apiUrl");
|
|
171
|
+
if (apiUrlError) errors.push(apiUrlError);
|
|
172
|
+
}
|
|
173
|
+
function validateLocal(local, errors) {
|
|
174
|
+
if (local === void 0 || local === null) return;
|
|
175
|
+
if (typeof local !== "object" || Array.isArray(local)) {
|
|
176
|
+
errors.push("local: Must be an object");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const loc = local;
|
|
180
|
+
const enabledError = validateBoolean(loc.enabled, "local.enabled");
|
|
181
|
+
if (enabledError) errors.push(enabledError);
|
|
182
|
+
const enableCloneError = validateBoolean(
|
|
183
|
+
loc.enableClone,
|
|
184
|
+
"local.enableClone"
|
|
185
|
+
);
|
|
186
|
+
if (enableCloneError) errors.push(enableCloneError);
|
|
187
|
+
const allowedPathsError = validateStringArray(
|
|
188
|
+
loc.allowedPaths,
|
|
189
|
+
"local.allowedPaths"
|
|
190
|
+
);
|
|
191
|
+
if (allowedPathsError) {
|
|
192
|
+
errors.push(allowedPathsError);
|
|
193
|
+
} else if (Array.isArray(loc.allowedPaths)) {
|
|
194
|
+
const pathErrors = validateAllowedPathElements(
|
|
195
|
+
loc.allowedPaths
|
|
196
|
+
);
|
|
197
|
+
errors.push(...pathErrors);
|
|
198
|
+
}
|
|
199
|
+
if (loc.workspaceRoot !== void 0 && loc.workspaceRoot !== null) {
|
|
200
|
+
const workspaceRootError = validateString(
|
|
201
|
+
loc.workspaceRoot,
|
|
202
|
+
"local.workspaceRoot"
|
|
203
|
+
);
|
|
204
|
+
if (workspaceRootError) {
|
|
205
|
+
errors.push(workspaceRootError);
|
|
206
|
+
} else if (typeof loc.workspaceRoot === "string" && !loc.workspaceRoot.startsWith("/") && !loc.workspaceRoot.startsWith("~")) {
|
|
207
|
+
errors.push(
|
|
208
|
+
'local.workspaceRoot: must be an absolute path or start with ~ (got "' + loc.workspaceRoot + '")'
|
|
209
|
+
);
|
|
210
|
+
} else if (typeof loc.workspaceRoot === "string" && loc.workspaceRoot.includes("..")) {
|
|
211
|
+
errors.push(
|
|
212
|
+
'local.workspaceRoot: path traversal (..) not allowed (got "' + loc.workspaceRoot + '")'
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function validateTools(tools, errors) {
|
|
218
|
+
if (tools === void 0 || tools === null) return;
|
|
219
|
+
if (typeof tools !== "object" || Array.isArray(tools)) {
|
|
220
|
+
errors.push("tools: Must be an object");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const t = tools;
|
|
224
|
+
const enabledError = validateNullableStringArray(t.enabled, "tools.enabled");
|
|
225
|
+
if (enabledError) errors.push(enabledError);
|
|
226
|
+
const enableAdditionalError = validateNullableStringArray(
|
|
227
|
+
t.enableAdditional,
|
|
228
|
+
"tools.enableAdditional"
|
|
229
|
+
);
|
|
230
|
+
if (enableAdditionalError) errors.push(enableAdditionalError);
|
|
231
|
+
const disabledError = validateNullableStringArray(
|
|
232
|
+
t.disabled,
|
|
233
|
+
"tools.disabled"
|
|
234
|
+
);
|
|
235
|
+
if (disabledError) errors.push(disabledError);
|
|
236
|
+
}
|
|
237
|
+
function validateNetwork(network, errors) {
|
|
238
|
+
if (network === void 0 || network === null) return;
|
|
239
|
+
if (typeof network !== "object" || Array.isArray(network)) {
|
|
240
|
+
errors.push("network: Must be an object");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const net = network;
|
|
244
|
+
const timeoutError = validateNumberRange(
|
|
245
|
+
net.timeout,
|
|
246
|
+
"network.timeout",
|
|
247
|
+
MIN_TIMEOUT,
|
|
248
|
+
MAX_TIMEOUT
|
|
249
|
+
);
|
|
250
|
+
if (timeoutError) errors.push(timeoutError);
|
|
251
|
+
const retriesError = validateNumberRange(
|
|
252
|
+
net.maxRetries,
|
|
253
|
+
"network.maxRetries",
|
|
254
|
+
MIN_RETRIES,
|
|
255
|
+
MAX_RETRIES
|
|
256
|
+
);
|
|
257
|
+
if (retriesError) errors.push(retriesError);
|
|
258
|
+
}
|
|
259
|
+
function validateLsp(lsp, errors) {
|
|
260
|
+
if (lsp === void 0 || lsp === null) return;
|
|
261
|
+
if (typeof lsp !== "object" || Array.isArray(lsp)) {
|
|
262
|
+
errors.push("lsp: Must be an object");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const l = lsp;
|
|
266
|
+
const configPathError = validateString(l.configPath, "lsp.configPath");
|
|
267
|
+
if (configPathError) errors.push(configPathError);
|
|
268
|
+
}
|
|
269
|
+
function validateOutput(output, errors) {
|
|
270
|
+
if (output === void 0 || output === null) return;
|
|
271
|
+
if (typeof output !== "object" || Array.isArray(output)) {
|
|
272
|
+
errors.push("output: Must be an object");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const out = output;
|
|
276
|
+
if (out.format !== void 0) {
|
|
277
|
+
if (typeof out.format !== "string") {
|
|
278
|
+
errors.push("output.format: Must be a string");
|
|
279
|
+
} else if (!["yaml", "json"].includes(out.format)) {
|
|
280
|
+
errors.push("output.format: Must be one of: yaml, json");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (out.pagination !== void 0 && out.pagination !== null) {
|
|
284
|
+
if (typeof out.pagination !== "object" || Array.isArray(out.pagination)) {
|
|
285
|
+
errors.push("output.pagination: Must be an object");
|
|
286
|
+
} else {
|
|
287
|
+
const pagination = out.pagination;
|
|
288
|
+
const defaultCharLengthError = validateNumberRange(
|
|
289
|
+
pagination.defaultCharLength,
|
|
290
|
+
"output.pagination.defaultCharLength",
|
|
291
|
+
MIN_OUTPUT_DEFAULT_CHAR_LENGTH,
|
|
292
|
+
MAX_OUTPUT_DEFAULT_CHAR_LENGTH
|
|
293
|
+
);
|
|
294
|
+
if (defaultCharLengthError) errors.push(defaultCharLengthError);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function validateConfig(config) {
|
|
299
|
+
const errors = [];
|
|
300
|
+
const warnings = [];
|
|
301
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
302
|
+
return {
|
|
303
|
+
valid: false,
|
|
304
|
+
errors: ["Configuration must be a JSON object"],
|
|
305
|
+
warnings: []
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const cfg = config;
|
|
309
|
+
if (cfg.version !== void 0) {
|
|
310
|
+
if (typeof cfg.version !== "number" || !Number.isInteger(cfg.version)) {
|
|
311
|
+
errors.push("version: Must be an integer");
|
|
312
|
+
} else if (cfg.version > CONFIG_SCHEMA_VERSION) {
|
|
313
|
+
warnings.push(
|
|
314
|
+
`version: Config version ${cfg.version} is newer than supported version ${CONFIG_SCHEMA_VERSION}`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
validateGitHub(cfg.github, errors);
|
|
319
|
+
validateLocal(cfg.local, errors);
|
|
320
|
+
validateTools(cfg.tools, errors);
|
|
321
|
+
validateNetwork(cfg.network, errors);
|
|
322
|
+
validateLsp(cfg.lsp, errors);
|
|
323
|
+
validateOutput(cfg.output, errors);
|
|
324
|
+
const knownKeys = /* @__PURE__ */ new Set([
|
|
325
|
+
"$schema",
|
|
326
|
+
"version",
|
|
327
|
+
"github",
|
|
328
|
+
"local",
|
|
329
|
+
"tools",
|
|
330
|
+
"network",
|
|
331
|
+
"lsp",
|
|
332
|
+
"output"
|
|
333
|
+
]);
|
|
334
|
+
for (const key of Object.keys(cfg)) {
|
|
335
|
+
if (!knownKeys.has(key)) {
|
|
336
|
+
warnings.push(`Unknown configuration key: ${key}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
valid: errors.length === 0,
|
|
341
|
+
errors,
|
|
342
|
+
warnings,
|
|
343
|
+
config: errors.length === 0 ? config : void 0
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/config/loader.ts
|
|
348
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
349
|
+
import path2 from "node:path";
|
|
350
|
+
function stripJson5Features(content) {
|
|
351
|
+
let result = "";
|
|
352
|
+
let i = 0;
|
|
353
|
+
let inString = false;
|
|
354
|
+
let stringChar = "";
|
|
355
|
+
while (i < content.length) {
|
|
356
|
+
const char = content[i];
|
|
357
|
+
const nextChar = content[i + 1];
|
|
358
|
+
if (!inString && (char === '"' || char === "'")) {
|
|
359
|
+
inString = true;
|
|
360
|
+
stringChar = char;
|
|
361
|
+
result += char;
|
|
362
|
+
i++;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (inString) {
|
|
366
|
+
result += char;
|
|
367
|
+
if (char === "\\" && i + 1 < content.length) {
|
|
368
|
+
result += content[i + 1];
|
|
369
|
+
i += 2;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (char === stringChar) inString = false;
|
|
373
|
+
i++;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (char === "/" && nextChar === "/") {
|
|
377
|
+
while (i < content.length && content[i] !== "\n") i++;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (char === "/" && nextChar === "*") {
|
|
381
|
+
i += 2;
|
|
382
|
+
while (i < content.length - 1) {
|
|
383
|
+
if (content[i] === "*" && content[i + 1] === "/") {
|
|
384
|
+
i += 2;
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
i++;
|
|
388
|
+
}
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
result += char;
|
|
392
|
+
i++;
|
|
393
|
+
}
|
|
394
|
+
return result.replace(/,(\s*[}\]])/g, "$1");
|
|
395
|
+
}
|
|
396
|
+
function parseJson5(content) {
|
|
397
|
+
return JSON.parse(stripJson5Features(content));
|
|
398
|
+
}
|
|
399
|
+
function getConfigFilePath(home = getOctocodeHome()) {
|
|
400
|
+
return path2.join(home, ".octocoderc");
|
|
401
|
+
}
|
|
402
|
+
function configExists(home) {
|
|
403
|
+
return existsSync(getConfigFilePath(home));
|
|
404
|
+
}
|
|
405
|
+
function loadConfigSync(home) {
|
|
406
|
+
const filePath = getConfigFilePath(home);
|
|
407
|
+
if (!existsSync(filePath)) {
|
|
408
|
+
return { success: false, error: "Config file does not exist", path: filePath };
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
const content = readFileSync(filePath, "utf-8");
|
|
412
|
+
if (!content.trim()) {
|
|
413
|
+
return { success: true, config: {}, path: filePath };
|
|
414
|
+
}
|
|
415
|
+
const parsed = parseJson5(content);
|
|
416
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
417
|
+
return {
|
|
418
|
+
success: false,
|
|
419
|
+
error: "Config file has invalid structure: must be a JSON object",
|
|
420
|
+
path: filePath
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return { success: true, config: parsed, path: filePath };
|
|
424
|
+
} catch (e) {
|
|
425
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
426
|
+
return { success: false, error: `Failed to parse config file: ${message}`, path: filePath };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async function loadConfig(home) {
|
|
430
|
+
return loadConfigSync(home);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/config/resolverSections.ts
|
|
434
|
+
function parseBooleanEnv(value) {
|
|
435
|
+
if (value === void 0 || value === null) return void 0;
|
|
436
|
+
const trimmed = value.trim().toLowerCase();
|
|
437
|
+
if (trimmed === "") return void 0;
|
|
438
|
+
if (trimmed === "true" || trimmed === "1") return true;
|
|
439
|
+
if (trimmed === "false" || trimmed === "0") return false;
|
|
440
|
+
return void 0;
|
|
441
|
+
}
|
|
442
|
+
function parseIntEnv(value) {
|
|
443
|
+
if (value === void 0 || value === null) return void 0;
|
|
444
|
+
const trimmed = value.trim();
|
|
445
|
+
if (trimmed === "") return void 0;
|
|
446
|
+
const parsed = parseInt(trimmed, 10);
|
|
447
|
+
if (isNaN(parsed)) return void 0;
|
|
448
|
+
return parsed;
|
|
449
|
+
}
|
|
450
|
+
function parseStringArrayEnv(value) {
|
|
451
|
+
if (value === void 0 || value === null) return void 0;
|
|
452
|
+
const trimmed = value.trim();
|
|
453
|
+
if (trimmed === "") return void 0;
|
|
454
|
+
return trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
455
|
+
}
|
|
456
|
+
function resolveGitHub(fileConfig) {
|
|
457
|
+
const envApiUrl = process.env.GITHUB_API_URL?.trim();
|
|
458
|
+
return {
|
|
459
|
+
apiUrl: envApiUrl || fileConfig?.apiUrl || DEFAULT_GITHUB_CONFIG.apiUrl
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function resolveLocal(fileConfig) {
|
|
463
|
+
const isCli = getRuntimeSurface() === "cli";
|
|
464
|
+
const envEnableLocal = parseBooleanEnv(process.env.ENABLE_LOCAL);
|
|
465
|
+
const envEnableClone = parseBooleanEnv(process.env.ENABLE_CLONE);
|
|
466
|
+
const envAllowedPaths = parseStringArrayEnv(process.env.ALLOWED_PATHS);
|
|
467
|
+
const envWorkspaceRoot = process.env.WORKSPACE_ROOT?.trim() || void 0;
|
|
468
|
+
return {
|
|
469
|
+
// Local tools are on by default. ENABLE_LOCAL=false (or local.enabled=false)
|
|
470
|
+
// is an explicit opt-out for users who want to remove the local surface.
|
|
471
|
+
enabled: envEnableLocal ?? fileConfig?.enabled ?? DEFAULT_LOCAL_CONFIG.enabled,
|
|
472
|
+
// Clone: an explicit ENABLE_CLONE (env) or .octocoderc value wins for both
|
|
473
|
+
// surfaces, so `false` disables everywhere. Otherwise the default is
|
|
474
|
+
// surface-specific: ENABLED for the CLI, DISABLED for the MCP server.
|
|
475
|
+
enableClone: envEnableClone ?? fileConfig?.enableClone ?? (isCli ? true : DEFAULT_LOCAL_CONFIG.enableClone),
|
|
476
|
+
allowedPaths: envAllowedPaths ?? fileConfig?.allowedPaths ?? DEFAULT_LOCAL_CONFIG.allowedPaths,
|
|
477
|
+
workspaceRoot: envWorkspaceRoot ?? fileConfig?.workspaceRoot ?? DEFAULT_LOCAL_CONFIG.workspaceRoot
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function resolveTools(fileConfig) {
|
|
481
|
+
const envToolsToRun = parseStringArrayEnv(process.env.TOOLS_TO_RUN);
|
|
482
|
+
const envEnableTools = parseStringArrayEnv(process.env.ENABLE_TOOLS);
|
|
483
|
+
const envDisableTools = parseStringArrayEnv(process.env.DISABLE_TOOLS);
|
|
484
|
+
return {
|
|
485
|
+
enabled: envToolsToRun ?? fileConfig?.enabled ?? DEFAULT_TOOLS_CONFIG.enabled,
|
|
486
|
+
enableAdditional: envEnableTools ?? fileConfig?.enableAdditional ?? DEFAULT_TOOLS_CONFIG.enableAdditional,
|
|
487
|
+
disabled: envDisableTools ?? fileConfig?.disabled ?? DEFAULT_TOOLS_CONFIG.disabled
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function resolveNetwork(fileConfig) {
|
|
491
|
+
const envTimeout = parseIntEnv(process.env.REQUEST_TIMEOUT);
|
|
492
|
+
const envMaxRetries = parseIntEnv(process.env.MAX_RETRIES);
|
|
493
|
+
let timeout = envTimeout ?? fileConfig?.timeout ?? DEFAULT_NETWORK_CONFIG.timeout;
|
|
494
|
+
timeout = Math.max(MIN_TIMEOUT, Math.min(MAX_TIMEOUT, timeout));
|
|
495
|
+
let maxRetries = envMaxRetries ?? fileConfig?.maxRetries ?? DEFAULT_NETWORK_CONFIG.maxRetries;
|
|
496
|
+
maxRetries = Math.max(MIN_RETRIES, Math.min(MAX_RETRIES, maxRetries));
|
|
497
|
+
return { timeout, maxRetries };
|
|
498
|
+
}
|
|
499
|
+
function resolveLsp(fileConfig) {
|
|
500
|
+
const envConfigPath = process.env.OCTOCODE_LSP_CONFIG?.trim() || void 0;
|
|
501
|
+
return {
|
|
502
|
+
configPath: envConfigPath ?? fileConfig?.configPath ?? DEFAULT_LSP_CONFIG.configPath
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function resolveSession() {
|
|
506
|
+
const envEnableStats = parseBooleanEnv(process.env.OCTOCODE_ENABLE_STATS);
|
|
507
|
+
return {
|
|
508
|
+
enableStats: envEnableStats ?? DEFAULT_SESSION_CONFIG.enableStats
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
var VALID_OUTPUT_FORMATS = /* @__PURE__ */ new Set(["yaml", "json"]);
|
|
512
|
+
function resolveOutput(fileConfig) {
|
|
513
|
+
const envFormat = process.env.OCTOCODE_OUTPUT_FORMAT?.trim().toLowerCase();
|
|
514
|
+
const envDefaultCharLength = parseIntEnv(
|
|
515
|
+
process.env.OCTOCODE_OUTPUT_DEFAULT_CHAR_LENGTH
|
|
516
|
+
);
|
|
517
|
+
const resolved = envFormat || fileConfig?.format || DEFAULT_OUTPUT_CONFIG.format;
|
|
518
|
+
const configuredDefaultCharLength = envDefaultCharLength ?? fileConfig?.pagination?.defaultCharLength ?? DEFAULT_OUTPUT_CONFIG.pagination.defaultCharLength;
|
|
519
|
+
const clampedDefaultCharLength = Math.max(
|
|
520
|
+
MIN_OUTPUT_DEFAULT_CHAR_LENGTH,
|
|
521
|
+
Math.min(MAX_OUTPUT_DEFAULT_CHAR_LENGTH, configuredDefaultCharLength)
|
|
522
|
+
);
|
|
523
|
+
return {
|
|
524
|
+
format: VALID_OUTPUT_FORMATS.has(resolved) ? resolved : DEFAULT_OUTPUT_CONFIG.format,
|
|
525
|
+
pagination: {
|
|
526
|
+
defaultCharLength: clampedDefaultCharLength
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/config/resolverCache.ts
|
|
532
|
+
function buildResolvedConfig(fileConfig, configPath) {
|
|
533
|
+
const hasFile = fileConfig !== void 0;
|
|
534
|
+
const hasEnvOverrides = process.env.GITHUB_API_URL !== void 0 || process.env.ENABLE_LOCAL !== void 0 || process.env.ENABLE_CLONE !== void 0 || process.env.ALLOWED_PATHS !== void 0 || process.env.WORKSPACE_ROOT !== void 0 || process.env.TOOLS_TO_RUN !== void 0 || process.env.ENABLE_TOOLS !== void 0 || process.env.DISABLE_TOOLS !== void 0 || process.env.REQUEST_TIMEOUT !== void 0 || process.env.MAX_RETRIES !== void 0 || process.env.OCTOCODE_LSP_CONFIG !== void 0 || process.env.OCTOCODE_OUTPUT_FORMAT !== void 0 || process.env.OCTOCODE_OUTPUT_DEFAULT_CHAR_LENGTH !== void 0 || process.env.OCTOCODE_ENABLE_STATS !== void 0;
|
|
535
|
+
let source;
|
|
536
|
+
if (hasFile && hasEnvOverrides) {
|
|
537
|
+
source = "mixed";
|
|
538
|
+
} else if (hasFile) {
|
|
539
|
+
source = "file";
|
|
540
|
+
} else {
|
|
541
|
+
source = "defaults";
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
version: fileConfig?.version ?? DEFAULT_CONFIG.version,
|
|
545
|
+
github: resolveGitHub(fileConfig?.github),
|
|
546
|
+
local: resolveLocal(fileConfig?.local),
|
|
547
|
+
tools: resolveTools(fileConfig?.tools),
|
|
548
|
+
network: resolveNetwork(fileConfig?.network),
|
|
549
|
+
lsp: resolveLsp(fileConfig?.lsp),
|
|
550
|
+
output: resolveOutput(fileConfig?.output),
|
|
551
|
+
session: resolveSession(),
|
|
552
|
+
source,
|
|
553
|
+
configPath: hasFile ? configPath : void 0
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function resolveConfigSync() {
|
|
557
|
+
const loadResult = loadConfigSync();
|
|
558
|
+
if (loadResult.success && loadResult.config) {
|
|
559
|
+
const validation = validateConfig(loadResult.config);
|
|
560
|
+
if (!validation.valid) {
|
|
561
|
+
return buildResolvedConfig(void 0);
|
|
562
|
+
}
|
|
563
|
+
return buildResolvedConfig(loadResult.config, loadResult.path);
|
|
564
|
+
}
|
|
565
|
+
return buildResolvedConfig(void 0);
|
|
566
|
+
}
|
|
567
|
+
async function resolveConfig() {
|
|
568
|
+
return resolveConfigSync();
|
|
569
|
+
}
|
|
570
|
+
var cachedConfig = null;
|
|
571
|
+
var cacheTimestamp = 0;
|
|
572
|
+
var CACHE_TTL_MS = 6e4;
|
|
573
|
+
function getConfigSync() {
|
|
574
|
+
const now = Date.now();
|
|
575
|
+
if (cachedConfig && now - cacheTimestamp < CACHE_TTL_MS) {
|
|
576
|
+
return cachedConfig;
|
|
577
|
+
}
|
|
578
|
+
cachedConfig = resolveConfigSync();
|
|
579
|
+
cacheTimestamp = now;
|
|
580
|
+
return cachedConfig;
|
|
581
|
+
}
|
|
582
|
+
async function getConfig() {
|
|
583
|
+
return getConfigSync();
|
|
584
|
+
}
|
|
585
|
+
async function reloadConfig() {
|
|
586
|
+
invalidateConfigCache();
|
|
587
|
+
return getConfig();
|
|
588
|
+
}
|
|
589
|
+
function invalidateConfigCache() {
|
|
590
|
+
cachedConfig = null;
|
|
591
|
+
cacheTimestamp = 0;
|
|
592
|
+
}
|
|
593
|
+
function _resetConfigCache() {
|
|
594
|
+
cachedConfig = null;
|
|
595
|
+
cacheTimestamp = 0;
|
|
596
|
+
}
|
|
597
|
+
function _getCacheState() {
|
|
598
|
+
return {
|
|
599
|
+
cached: cachedConfig !== null,
|
|
600
|
+
timestamp: cacheTimestamp
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/config/resolver.ts
|
|
605
|
+
function getConfigValue(keyPath) {
|
|
606
|
+
const config = getConfigSync();
|
|
607
|
+
const parts = keyPath.split(".");
|
|
608
|
+
let current = config;
|
|
609
|
+
for (const part of parts) {
|
|
610
|
+
if (current === null || current === void 0 || typeof current !== "object") return void 0;
|
|
611
|
+
current = current[part];
|
|
612
|
+
}
|
|
613
|
+
return current;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/tokens/envTokens.ts
|
|
617
|
+
var ENV_TOKEN_VARS = [
|
|
618
|
+
"OCTOCODE_TOKEN",
|
|
619
|
+
"GH_TOKEN",
|
|
620
|
+
"GITHUB_TOKEN",
|
|
621
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN"
|
|
622
|
+
];
|
|
623
|
+
function getTokenFromEnv(env = process.env) {
|
|
624
|
+
for (const envVar of ENV_TOKEN_VARS) {
|
|
625
|
+
const token = env[envVar];
|
|
626
|
+
if (token && token.trim()) return token.trim();
|
|
627
|
+
}
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
function getEnvTokenSource(env = process.env) {
|
|
631
|
+
for (const envVar of ENV_TOKEN_VARS) {
|
|
632
|
+
const token = env[envVar];
|
|
633
|
+
if (token && token.trim()) return `env:${envVar}`;
|
|
634
|
+
}
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
function hasEnvToken(env = process.env) {
|
|
638
|
+
return getTokenFromEnv(env) !== null;
|
|
639
|
+
}
|
|
640
|
+
function resolveEnvToken(env = process.env) {
|
|
641
|
+
for (const envVar of ENV_TOKEN_VARS) {
|
|
642
|
+
const token = env[envVar];
|
|
643
|
+
if (token?.trim()) {
|
|
644
|
+
return {
|
|
645
|
+
token: token.trim(),
|
|
646
|
+
source: `env:${envVar}`
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/index.ts
|
|
654
|
+
var PROTECTED_KEYS = /* @__PURE__ */ new Set([
|
|
655
|
+
"PATH",
|
|
656
|
+
"HOME",
|
|
657
|
+
"SHELL",
|
|
658
|
+
"USER",
|
|
659
|
+
"LOGNAME",
|
|
660
|
+
"PWD",
|
|
661
|
+
"TMPDIR",
|
|
662
|
+
"NODE_OPTIONS",
|
|
663
|
+
"OCTOCODE_TOKEN",
|
|
664
|
+
"GH_TOKEN",
|
|
665
|
+
"GITHUB_TOKEN",
|
|
666
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
667
|
+
"PYTHON"
|
|
668
|
+
]);
|
|
669
|
+
function parseEnv(text) {
|
|
670
|
+
const out = {};
|
|
671
|
+
if (!text) return out;
|
|
672
|
+
for (const rawLine of text.split("\n")) {
|
|
673
|
+
const trimmed = rawLine.trim();
|
|
674
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
675
|
+
const normalized = trimmed.startsWith("export ") ? trimmed.slice("export ".length).trim() : trimmed;
|
|
676
|
+
const eq = normalized.indexOf("=");
|
|
677
|
+
if (eq === -1) continue;
|
|
678
|
+
const key = normalized.slice(0, eq).trim();
|
|
679
|
+
if (!key) continue;
|
|
680
|
+
out[key] = normalized.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
681
|
+
}
|
|
682
|
+
return out;
|
|
683
|
+
}
|
|
684
|
+
function readTextIfExists(filePath) {
|
|
685
|
+
try {
|
|
686
|
+
return fs.readFileSync(filePath, "utf8");
|
|
687
|
+
} catch {
|
|
688
|
+
return "";
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function loadOctocodeEnv({
|
|
692
|
+
home,
|
|
693
|
+
cwd,
|
|
694
|
+
trusted = false
|
|
695
|
+
} = {}) {
|
|
696
|
+
const map = {};
|
|
697
|
+
const sources = {};
|
|
698
|
+
if (home) {
|
|
699
|
+
for (const [k, v] of Object.entries(parseEnv(readTextIfExists(path3.join(home, ".env"))))) {
|
|
700
|
+
map[k] = v;
|
|
701
|
+
sources[k] = "global";
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (cwd && trusted) {
|
|
705
|
+
for (const [k, v] of Object.entries(
|
|
706
|
+
parseEnv(readTextIfExists(path3.join(cwd, ".octocode", ".env")))
|
|
707
|
+
)) {
|
|
708
|
+
map[k] = v;
|
|
709
|
+
sources[k] = "project";
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return { map, sources };
|
|
713
|
+
}
|
|
714
|
+
function applyOctocodeEnv(map, { env = process.env } = {}) {
|
|
715
|
+
const applied = [];
|
|
716
|
+
const skippedProtected = [];
|
|
717
|
+
const skippedExisting = [];
|
|
718
|
+
for (const [key, value] of Object.entries(map ?? {})) {
|
|
719
|
+
if (PROTECTED_KEYS.has(key)) {
|
|
720
|
+
skippedProtected.push(key);
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
const existing = env[key];
|
|
724
|
+
if (existing !== void 0 && existing !== "") {
|
|
725
|
+
skippedExisting.push(key);
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
env[key] = value;
|
|
729
|
+
applied.push(key);
|
|
730
|
+
}
|
|
731
|
+
return { applied, skippedProtected, skippedExisting };
|
|
732
|
+
}
|
|
733
|
+
function propagateOctocodeEnv({
|
|
734
|
+
home = getOctocodeHome(),
|
|
735
|
+
cwd,
|
|
736
|
+
trusted = false,
|
|
737
|
+
env = process.env
|
|
738
|
+
} = {}) {
|
|
739
|
+
const { map, sources } = loadOctocodeEnv({ home, cwd, trusted });
|
|
740
|
+
const result = applyOctocodeEnv(map, { env });
|
|
741
|
+
return { ...result, sources, keys: Object.keys(map) };
|
|
742
|
+
}
|
|
743
|
+
function isStatsEnabled(env = process.env) {
|
|
744
|
+
const v = env["OCTOCODE_ENABLE_STATS"];
|
|
745
|
+
return v === "1" || v === "true";
|
|
746
|
+
}
|
|
747
|
+
function loadOctocoderc(home = getOctocodeHome()) {
|
|
748
|
+
const result = loadConfigSync(home);
|
|
749
|
+
if (result.success) return result.config ? result.config : {};
|
|
750
|
+
if (result.error && result.error !== "Config file does not exist") {
|
|
751
|
+
process.stderr.write(`[octocode-config] Failed to parse .octocoderc: ${result.error}
|
|
752
|
+
`);
|
|
753
|
+
}
|
|
754
|
+
return {};
|
|
755
|
+
}
|
|
756
|
+
export {
|
|
757
|
+
CONFIG_FILE_NAME,
|
|
758
|
+
CONFIG_SCHEMA_VERSION,
|
|
759
|
+
DEFAULT_CONFIG,
|
|
760
|
+
DEFAULT_GITHUB_CONFIG,
|
|
761
|
+
DEFAULT_LOCAL_CONFIG,
|
|
762
|
+
DEFAULT_LSP_CONFIG,
|
|
763
|
+
DEFAULT_NETWORK_CONFIG,
|
|
764
|
+
DEFAULT_OUTPUT_CONFIG,
|
|
765
|
+
DEFAULT_SESSION_CONFIG,
|
|
766
|
+
DEFAULT_TOOLS_CONFIG,
|
|
767
|
+
ENV_TOKEN_VARS,
|
|
768
|
+
MAX_OUTPUT_DEFAULT_CHAR_LENGTH,
|
|
769
|
+
MAX_RETRIES,
|
|
770
|
+
MAX_TIMEOUT,
|
|
771
|
+
MIN_OUTPUT_DEFAULT_CHAR_LENGTH,
|
|
772
|
+
MIN_RETRIES,
|
|
773
|
+
MIN_TIMEOUT,
|
|
774
|
+
PROTECTED_KEYS,
|
|
775
|
+
_getCacheState,
|
|
776
|
+
_resetConfigCache,
|
|
777
|
+
_resetRuntimeSurface,
|
|
778
|
+
applyOctocodeEnv,
|
|
779
|
+
configExists,
|
|
780
|
+
getConfig,
|
|
781
|
+
getConfigFilePath,
|
|
782
|
+
getConfigSync,
|
|
783
|
+
getConfigValue,
|
|
784
|
+
getEnvTokenSource,
|
|
785
|
+
getOctocodeHome,
|
|
786
|
+
getRuntimeSurface,
|
|
787
|
+
getTokenFromEnv,
|
|
788
|
+
hasEnvToken,
|
|
789
|
+
invalidateConfigCache,
|
|
790
|
+
isStatsEnabled,
|
|
791
|
+
loadConfig,
|
|
792
|
+
loadConfigSync,
|
|
793
|
+
loadOctocodeEnv,
|
|
794
|
+
loadOctocoderc,
|
|
795
|
+
parseBooleanEnv,
|
|
796
|
+
parseEnv,
|
|
797
|
+
parseIntEnv,
|
|
798
|
+
parseStringArrayEnv,
|
|
799
|
+
propagateOctocodeEnv,
|
|
800
|
+
reloadConfig,
|
|
801
|
+
resolveConfig,
|
|
802
|
+
resolveConfigSync,
|
|
803
|
+
resolveEnvToken,
|
|
804
|
+
resolveGitHub,
|
|
805
|
+
resolveLocal,
|
|
806
|
+
resolveLsp,
|
|
807
|
+
resolveNetwork,
|
|
808
|
+
resolveOutput,
|
|
809
|
+
resolveSession,
|
|
810
|
+
resolveTools,
|
|
811
|
+
setRuntimeSurface,
|
|
812
|
+
validateConfig
|
|
813
|
+
};
|
|
814
|
+
//# sourceMappingURL=index.js.map
|