@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/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