@skroyc/librarian 0.1.0 → 0.2.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.
- package/README.md +4 -16
- package/dist/agents/context-schema.d.ts +1 -1
- package/dist/agents/context-schema.d.ts.map +1 -1
- package/dist/agents/context-schema.js +5 -2
- package/dist/agents/context-schema.js.map +1 -1
- package/dist/agents/react-agent.d.ts.map +1 -1
- package/dist/agents/react-agent.js +63 -170
- package/dist/agents/react-agent.js.map +1 -1
- package/dist/agents/tool-runtime.d.ts.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -49
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +115 -69
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +246 -150
- package/dist/index.js.map +1 -1
- package/dist/tools/file-finding.tool.d.ts +1 -1
- package/dist/tools/file-finding.tool.d.ts.map +1 -1
- package/dist/tools/file-finding.tool.js +70 -130
- package/dist/tools/file-finding.tool.js.map +1 -1
- package/dist/tools/file-listing.tool.d.ts +7 -1
- package/dist/tools/file-listing.tool.d.ts.map +1 -1
- package/dist/tools/file-listing.tool.js +96 -80
- package/dist/tools/file-listing.tool.js.map +1 -1
- package/dist/tools/file-reading.tool.d.ts +4 -1
- package/dist/tools/file-reading.tool.d.ts.map +1 -1
- package/dist/tools/file-reading.tool.js +107 -45
- package/dist/tools/file-reading.tool.js.map +1 -1
- package/dist/tools/grep-content.tool.d.ts +13 -1
- package/dist/tools/grep-content.tool.d.ts.map +1 -1
- package/dist/tools/grep-content.tool.js +186 -144
- package/dist/tools/grep-content.tool.js.map +1 -1
- package/dist/utils/error-utils.d.ts +9 -0
- package/dist/utils/error-utils.d.ts.map +1 -0
- package/dist/utils/error-utils.js +61 -0
- package/dist/utils/error-utils.js.map +1 -0
- package/dist/utils/file-utils.d.ts +1 -0
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +81 -9
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/format-utils.d.ts +25 -0
- package/dist/utils/format-utils.d.ts.map +1 -0
- package/dist/utils/format-utils.js +111 -0
- package/dist/utils/format-utils.js.map +1 -0
- package/dist/utils/gitignore-service.d.ts +10 -0
- package/dist/utils/gitignore-service.d.ts.map +1 -0
- package/dist/utils/gitignore-service.js +91 -0
- package/dist/utils/gitignore-service.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +35 -34
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/path-utils.js +3 -3
- package/dist/utils/path-utils.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/context-schema.ts +5 -2
- package/src/agents/react-agent.ts +694 -784
- package/src/agents/tool-runtime.ts +4 -4
- package/src/cli.ts +95 -57
- package/src/config.ts +192 -90
- package/src/index.ts +402 -180
- package/src/tools/file-finding.tool.ts +198 -310
- package/src/tools/file-listing.tool.ts +245 -202
- package/src/tools/file-reading.tool.ts +225 -138
- package/src/tools/grep-content.tool.ts +387 -307
- package/src/utils/error-utils.ts +95 -0
- package/src/utils/file-utils.ts +104 -19
- package/src/utils/format-utils.ts +190 -0
- package/src/utils/gitignore-service.ts +123 -0
- package/src/utils/logger.ts +112 -77
- package/src/utils/path-utils.ts +3 -3
package/src/config.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { mkdir } from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parse, stringify } from "yaml";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { LibrarianConfig } from "./index.js";
|
|
7
|
+
import { logger } from "./utils/logger.js";
|
|
8
|
+
import { expandTilde } from "./utils/path-utils.js";
|
|
9
9
|
|
|
10
10
|
const TechnologySchema = z.object({
|
|
11
11
|
repo: z.string().optional(),
|
|
12
12
|
name: z.string().optional(), // For README style
|
|
13
|
-
branch: z.string().default(
|
|
13
|
+
branch: z.string().default("main"),
|
|
14
14
|
description: z.string().optional(),
|
|
15
15
|
});
|
|
16
16
|
|
|
@@ -19,17 +19,37 @@ const GroupSchema = z.record(z.string(), TechnologySchema);
|
|
|
19
19
|
const ConfigSchema = z.object({
|
|
20
20
|
technologies: z.record(z.string(), GroupSchema).optional(),
|
|
21
21
|
repositories: z.record(z.string(), z.string()).optional(), // For backward compatibility
|
|
22
|
-
aiProvider: z
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
aiProvider: z
|
|
23
|
+
.object({
|
|
24
|
+
type: z.enum([
|
|
25
|
+
"openai",
|
|
26
|
+
"anthropic",
|
|
27
|
+
"google",
|
|
28
|
+
"openai-compatible",
|
|
29
|
+
"anthropic-compatible",
|
|
30
|
+
"claude-code",
|
|
31
|
+
"gemini-cli",
|
|
32
|
+
]),
|
|
33
|
+
apiKey: z.string().optional(), // Optional - will be loaded from .env or not needed for claude-code/gemini-cli
|
|
34
|
+
model: z.string().optional(),
|
|
35
|
+
baseURL: z.string().optional(),
|
|
36
|
+
})
|
|
37
|
+
.optional(),
|
|
28
38
|
// Support README style keys
|
|
29
|
-
llm_provider: z
|
|
39
|
+
llm_provider: z
|
|
40
|
+
.enum([
|
|
41
|
+
"openai",
|
|
42
|
+
"anthropic",
|
|
43
|
+
"google",
|
|
44
|
+
"openai-compatible",
|
|
45
|
+
"anthropic-compatible",
|
|
46
|
+
"claude-code",
|
|
47
|
+
"gemini-cli",
|
|
48
|
+
])
|
|
49
|
+
.optional(),
|
|
30
50
|
llm_model: z.string().optional(),
|
|
31
51
|
base_url: z.string().optional(),
|
|
32
|
-
workingDir: z.string().default(
|
|
52
|
+
workingDir: z.string().default("./librarian_work"),
|
|
33
53
|
repos_path: z.string().optional(),
|
|
34
54
|
});
|
|
35
55
|
|
|
@@ -38,33 +58,35 @@ const ConfigSchema = z.object({
|
|
|
38
58
|
* Supports simple KEY=VALUE format, comments (#), and quoted values
|
|
39
59
|
*/
|
|
40
60
|
async function loadEnvFile(envPath: string): Promise<Record<string, string>> {
|
|
41
|
-
logger.debug(
|
|
61
|
+
logger.debug("CONFIG", "Loading .env file", {
|
|
62
|
+
envPath: envPath.replace(os.homedir(), "~"),
|
|
63
|
+
});
|
|
42
64
|
|
|
43
65
|
const envFile = Bun.file(envPath);
|
|
44
66
|
|
|
45
67
|
// Check if .env file exists
|
|
46
68
|
if (!(await envFile.exists())) {
|
|
47
|
-
logger.debug(
|
|
69
|
+
logger.debug("CONFIG", ".env file not found, continuing without it");
|
|
48
70
|
return {};
|
|
49
71
|
}
|
|
50
72
|
|
|
51
|
-
logger.info(
|
|
73
|
+
logger.info("CONFIG", ".env file found and loading");
|
|
52
74
|
|
|
53
75
|
// Read file content
|
|
54
76
|
const content = await envFile.text();
|
|
55
77
|
const env: Record<string, string> = {};
|
|
56
78
|
|
|
57
79
|
// Parse line by line
|
|
58
|
-
for (const line of content.split(
|
|
80
|
+
for (const line of content.split("\n")) {
|
|
59
81
|
const trimmed = line.trim();
|
|
60
82
|
|
|
61
83
|
// Skip empty lines and comments
|
|
62
|
-
if (!trimmed || trimmed.startsWith(
|
|
84
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
63
85
|
continue;
|
|
64
86
|
}
|
|
65
87
|
|
|
66
88
|
// Parse KEY=VALUE
|
|
67
|
-
const equalsIndex = trimmed.indexOf(
|
|
89
|
+
const equalsIndex = trimmed.indexOf("=");
|
|
68
90
|
if (equalsIndex === -1) {
|
|
69
91
|
continue; // Skip malformed lines
|
|
70
92
|
}
|
|
@@ -73,58 +95,88 @@ async function loadEnvFile(envPath: string): Promise<Record<string, string>> {
|
|
|
73
95
|
let value = trimmed.slice(equalsIndex + 1).trim();
|
|
74
96
|
|
|
75
97
|
// Remove quotes from value
|
|
76
|
-
if (
|
|
77
|
-
|
|
98
|
+
if (
|
|
99
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
100
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
101
|
+
) {
|
|
78
102
|
value = value.slice(1, -1);
|
|
79
103
|
}
|
|
80
104
|
|
|
81
105
|
env[key] = value;
|
|
82
106
|
}
|
|
83
107
|
|
|
84
|
-
logger.debug(
|
|
108
|
+
logger.debug("CONFIG", `.env loaded: ${Object.keys(env).length} variables`);
|
|
85
109
|
return env;
|
|
86
110
|
}
|
|
87
111
|
|
|
88
|
-
function validateApiKey(
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
function validateApiKey(
|
|
113
|
+
config: LibrarianConfig,
|
|
114
|
+
envPath: string,
|
|
115
|
+
errors: string[]
|
|
116
|
+
): void {
|
|
117
|
+
const isCliProvider =
|
|
118
|
+
config.aiProvider.type === "claude-code" ||
|
|
119
|
+
config.aiProvider.type === "gemini-cli";
|
|
120
|
+
if (
|
|
121
|
+
!isCliProvider &&
|
|
122
|
+
(!config.aiProvider.apiKey || config.aiProvider.apiKey.trim() === "")
|
|
123
|
+
) {
|
|
91
124
|
const errorMsg = `API key is missing or empty. Please set LIBRARIAN_API_KEY in ${envPath}`;
|
|
92
125
|
errors.push(errorMsg);
|
|
93
|
-
logger.debug(
|
|
126
|
+
logger.debug("CONFIG", "Validation failed: API key missing", {
|
|
127
|
+
envPath: envPath.replace(os.homedir(), "~"),
|
|
128
|
+
});
|
|
94
129
|
}
|
|
95
130
|
}
|
|
96
131
|
|
|
97
|
-
function validateBaseUrlForCompatibleProviders(
|
|
98
|
-
|
|
99
|
-
|
|
132
|
+
function validateBaseUrlForCompatibleProviders(
|
|
133
|
+
config: LibrarianConfig,
|
|
134
|
+
errors: string[]
|
|
135
|
+
): void {
|
|
136
|
+
if (
|
|
137
|
+
config.aiProvider.type === "openai-compatible" &&
|
|
138
|
+
!config.aiProvider.baseURL
|
|
139
|
+
) {
|
|
140
|
+
const errorMsg = "base_url is required for openai-compatible providers";
|
|
100
141
|
errors.push(errorMsg);
|
|
101
|
-
logger.debug(
|
|
142
|
+
logger.debug(
|
|
143
|
+
"CONFIG",
|
|
144
|
+
"Validation failed: base_url missing for openai-compatible provider"
|
|
145
|
+
);
|
|
102
146
|
}
|
|
103
147
|
|
|
104
|
-
if (config.aiProvider.type ===
|
|
148
|
+
if (config.aiProvider.type === "anthropic-compatible") {
|
|
105
149
|
if (!config.aiProvider.baseURL) {
|
|
106
|
-
const errorMsg =
|
|
150
|
+
const errorMsg =
|
|
151
|
+
"base_url is required for anthropic-compatible providers";
|
|
107
152
|
errors.push(errorMsg);
|
|
108
|
-
logger.debug(
|
|
153
|
+
logger.debug(
|
|
154
|
+
"CONFIG",
|
|
155
|
+
"Validation failed: base_url missing for anthropic-compatible provider"
|
|
156
|
+
);
|
|
109
157
|
}
|
|
110
158
|
if (!config.aiProvider.model) {
|
|
111
|
-
const errorMsg =
|
|
159
|
+
const errorMsg = "model is required for anthropic-compatible providers";
|
|
112
160
|
errors.push(errorMsg);
|
|
113
|
-
logger.debug(
|
|
161
|
+
logger.debug(
|
|
162
|
+
"CONFIG",
|
|
163
|
+
"Validation failed: model missing for anthropic-compatible provider"
|
|
164
|
+
);
|
|
114
165
|
}
|
|
115
166
|
}
|
|
116
167
|
}
|
|
117
168
|
|
|
118
169
|
function validateReposPath(config: LibrarianConfig, errors: string[]): void {
|
|
119
170
|
if (!config.repos_path) {
|
|
120
|
-
errors.push(
|
|
171
|
+
errors.push("repos_path is required in configuration");
|
|
121
172
|
}
|
|
122
173
|
}
|
|
123
174
|
|
|
124
175
|
function validateTechnologies(config: LibrarianConfig, errors: string[]): void {
|
|
125
|
-
const hasTechnologies =
|
|
176
|
+
const hasTechnologies =
|
|
177
|
+
config.technologies && Object.keys(config.technologies).length > 0;
|
|
126
178
|
if (!hasTechnologies) {
|
|
127
|
-
errors.push(
|
|
179
|
+
errors.push("No technologies defined in configuration");
|
|
128
180
|
// Continue to validate group names even if empty - this is intentional
|
|
129
181
|
// to catch path traversal in group names if any are defined
|
|
130
182
|
}
|
|
@@ -134,26 +186,38 @@ function validateTechnologies(config: LibrarianConfig, errors: string[]): void {
|
|
|
134
186
|
}
|
|
135
187
|
|
|
136
188
|
for (const [groupName, group] of Object.entries(config.technologies)) {
|
|
137
|
-
if (groupName.includes(
|
|
189
|
+
if (groupName.includes("..")) {
|
|
138
190
|
errors.push(`Group name "${groupName}" contains invalid path characters`);
|
|
139
|
-
logger.debug(
|
|
191
|
+
logger.debug(
|
|
192
|
+
"CONFIG",
|
|
193
|
+
"Validation failed: group name contains path traversal",
|
|
194
|
+
{ groupName }
|
|
195
|
+
);
|
|
140
196
|
}
|
|
141
197
|
|
|
142
198
|
for (const [techName, tech] of Object.entries(group)) {
|
|
143
199
|
if (!tech.repo) {
|
|
144
200
|
const errorMsg = `Technology "${techName}" in group "${groupName}" is missing required "repo" field`;
|
|
145
201
|
errors.push(errorMsg);
|
|
146
|
-
logger.debug(
|
|
202
|
+
logger.debug("CONFIG", "Validation failed: missing repo field", {
|
|
203
|
+
techName,
|
|
204
|
+
groupName,
|
|
205
|
+
});
|
|
147
206
|
continue;
|
|
148
207
|
}
|
|
149
208
|
|
|
150
|
-
const isRemoteUrl =
|
|
151
|
-
|
|
152
|
-
const
|
|
209
|
+
const isRemoteUrl =
|
|
210
|
+
tech.repo.startsWith("http://") || tech.repo.startsWith("https://");
|
|
211
|
+
const isFileUrl = tech.repo.startsWith("file://");
|
|
212
|
+
const hasProtocol = tech.repo.includes("://");
|
|
153
213
|
if (!(isRemoteUrl || isFileUrl) && hasProtocol) {
|
|
154
214
|
const errorMsg = `Technology "${techName}" has invalid repo URL: ${tech.repo}. Must be http://, https://, file://, or a local path`;
|
|
155
215
|
errors.push(errorMsg);
|
|
156
|
-
logger.debug(
|
|
216
|
+
logger.debug("CONFIG", "Validation failed: invalid repo URL", {
|
|
217
|
+
techName,
|
|
218
|
+
groupName,
|
|
219
|
+
repoUrl: tech.repo,
|
|
220
|
+
});
|
|
157
221
|
}
|
|
158
222
|
}
|
|
159
223
|
}
|
|
@@ -161,14 +225,16 @@ function validateTechnologies(config: LibrarianConfig, errors: string[]): void {
|
|
|
161
225
|
|
|
162
226
|
function reportErrors(errors: string[]): void {
|
|
163
227
|
if (errors.length > 0) {
|
|
164
|
-
logger.error(
|
|
165
|
-
|
|
228
|
+
logger.error("CONFIG", "Configuration validation failed", undefined, {
|
|
229
|
+
errorCount: errors.length,
|
|
230
|
+
});
|
|
231
|
+
console.error("Configuration validation failed:");
|
|
166
232
|
for (const err of errors) {
|
|
167
233
|
console.error(` - ${err}`);
|
|
168
234
|
}
|
|
169
235
|
process.exit(1);
|
|
170
236
|
} else {
|
|
171
|
-
logger.info(
|
|
237
|
+
logger.info("CONFIG", "Configuration validation passed");
|
|
172
238
|
}
|
|
173
239
|
}
|
|
174
240
|
|
|
@@ -176,7 +242,7 @@ function reportErrors(errors: string[]): void {
|
|
|
176
242
|
* Validate the configuration and exit with error if invalid
|
|
177
243
|
*/
|
|
178
244
|
function validateConfig(config: LibrarianConfig, envPath: string): void {
|
|
179
|
-
logger.debug(
|
|
245
|
+
logger.debug("CONFIG", "Validating configuration");
|
|
180
246
|
const errors: string[] = [];
|
|
181
247
|
|
|
182
248
|
validateApiKey(config, envPath, errors);
|
|
@@ -187,9 +253,11 @@ function validateConfig(config: LibrarianConfig, envPath: string): void {
|
|
|
187
253
|
reportErrors(errors);
|
|
188
254
|
}
|
|
189
255
|
|
|
190
|
-
type TechnologiesType = z.infer<typeof ConfigSchema>[
|
|
256
|
+
type TechnologiesType = z.infer<typeof ConfigSchema>["technologies"];
|
|
191
257
|
|
|
192
|
-
function normalizeTechnologies(
|
|
258
|
+
function normalizeTechnologies(
|
|
259
|
+
technologies: TechnologiesType
|
|
260
|
+
): TechnologiesType {
|
|
193
261
|
if (!technologies) {
|
|
194
262
|
return undefined;
|
|
195
263
|
}
|
|
@@ -197,93 +265,127 @@ function normalizeTechnologies(technologies: TechnologiesType): TechnologiesType
|
|
|
197
265
|
for (const group of Object.values(technologies ?? {})) {
|
|
198
266
|
for (const tech of Object.values(group ?? {})) {
|
|
199
267
|
if (!tech.repo && tech.name) {
|
|
200
|
-
logger.debug(
|
|
268
|
+
logger.debug(
|
|
269
|
+
"CONFIG",
|
|
270
|
+
'Normalizing technology: using "name" as "repo"',
|
|
271
|
+
{ name: tech.name }
|
|
272
|
+
);
|
|
201
273
|
tech.repo = tech.name;
|
|
202
274
|
}
|
|
203
275
|
}
|
|
204
276
|
}
|
|
205
|
-
logger.debug(
|
|
277
|
+
logger.debug("CONFIG", "Technologies normalized", {
|
|
278
|
+
groupCount: Object.keys(technologies).length,
|
|
279
|
+
});
|
|
206
280
|
return technologies;
|
|
207
281
|
}
|
|
208
282
|
|
|
209
|
-
function buildAiProvider(
|
|
283
|
+
function buildAiProvider(
|
|
284
|
+
validatedConfig: z.infer<typeof ConfigSchema>,
|
|
285
|
+
envVars: Record<string, string>
|
|
286
|
+
): LibrarianConfig["aiProvider"] {
|
|
210
287
|
if (validatedConfig.aiProvider) {
|
|
211
288
|
const { type, model, baseURL } = validatedConfig.aiProvider;
|
|
212
289
|
return {
|
|
213
290
|
type,
|
|
214
|
-
apiKey:
|
|
291
|
+
apiKey:
|
|
292
|
+
validatedConfig.aiProvider.apiKey || envVars.LIBRARIAN_API_KEY || "",
|
|
215
293
|
...(model && { model }),
|
|
216
|
-
...(baseURL && { baseURL })
|
|
294
|
+
...(baseURL && { baseURL }),
|
|
217
295
|
};
|
|
218
296
|
}
|
|
219
297
|
|
|
220
298
|
if (validatedConfig.llm_provider) {
|
|
221
|
-
logger.debug(
|
|
299
|
+
logger.debug("CONFIG", "Using README-style llm_* keys for AI provider");
|
|
222
300
|
return {
|
|
223
301
|
type: validatedConfig.llm_provider,
|
|
224
|
-
apiKey: envVars.LIBRARIAN_API_KEY ||
|
|
302
|
+
apiKey: envVars.LIBRARIAN_API_KEY || "",
|
|
225
303
|
...(validatedConfig.llm_model && { model: validatedConfig.llm_model }),
|
|
226
|
-
...(validatedConfig.base_url && { baseURL: validatedConfig.base_url })
|
|
304
|
+
...(validatedConfig.base_url && { baseURL: validatedConfig.base_url }),
|
|
227
305
|
};
|
|
228
306
|
}
|
|
229
307
|
|
|
230
|
-
logger.error(
|
|
231
|
-
console.error(
|
|
308
|
+
logger.error("CONFIG", "AI provider is required in configuration");
|
|
309
|
+
console.error(
|
|
310
|
+
"Configuration error: llm_provider (or aiProvider) is required in config.yaml"
|
|
311
|
+
);
|
|
232
312
|
process.exit(1);
|
|
233
313
|
}
|
|
234
314
|
|
|
235
|
-
export async function loadConfig(
|
|
236
|
-
|
|
315
|
+
export async function loadConfig(
|
|
316
|
+
configPath?: string
|
|
317
|
+
): Promise<LibrarianConfig> {
|
|
318
|
+
const defaultPath = path.join(
|
|
319
|
+
os.homedir(),
|
|
320
|
+
".config",
|
|
321
|
+
"librarian",
|
|
322
|
+
"config.yaml"
|
|
323
|
+
);
|
|
237
324
|
const actualPath = configPath ? expandTilde(configPath) : defaultPath;
|
|
238
325
|
|
|
239
|
-
logger.info(
|
|
326
|
+
logger.info("CONFIG", "Loading configuration", {
|
|
327
|
+
configPath: actualPath.replace(os.homedir(), "~"),
|
|
328
|
+
});
|
|
240
329
|
|
|
241
330
|
const configDir = path.dirname(actualPath);
|
|
242
|
-
const envPath = path.join(configDir,
|
|
331
|
+
const envPath = path.join(configDir, ".env");
|
|
243
332
|
const envVars = await loadEnvFile(envPath);
|
|
244
333
|
|
|
245
334
|
if (!(await Bun.file(actualPath).exists())) {
|
|
246
|
-
logger.info(
|
|
335
|
+
logger.info("CONFIG", "Config file not found, creating default config");
|
|
247
336
|
try {
|
|
248
337
|
await createDefaultConfig(actualPath);
|
|
249
|
-
logger.info(
|
|
338
|
+
logger.info("CONFIG", "Default config created successfully");
|
|
250
339
|
} catch (error) {
|
|
251
|
-
logger.error(
|
|
252
|
-
|
|
340
|
+
logger.error(
|
|
341
|
+
"CONFIG",
|
|
342
|
+
"Failed to create default config",
|
|
343
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
344
|
+
{ actualPath: actualPath.replace(os.homedir(), "~") }
|
|
345
|
+
);
|
|
346
|
+
console.error(
|
|
347
|
+
`Failed to create default config at ${actualPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
348
|
+
);
|
|
253
349
|
process.exit(1);
|
|
254
350
|
}
|
|
255
351
|
}
|
|
256
352
|
|
|
257
353
|
const configFileContent = await Bun.file(actualPath).text();
|
|
258
|
-
logger.debug(
|
|
354
|
+
logger.debug("CONFIG", "Config file read successfully", {
|
|
355
|
+
fileSize: configFileContent.length,
|
|
356
|
+
});
|
|
259
357
|
|
|
260
358
|
const parsedConfig = parse(configFileContent);
|
|
261
359
|
const validatedConfig = ConfigSchema.parse(parsedConfig);
|
|
262
|
-
logger.debug(
|
|
360
|
+
logger.debug("CONFIG", "Config schema validation passed");
|
|
263
361
|
|
|
264
362
|
const technologies = normalizeTechnologies(validatedConfig.technologies);
|
|
265
363
|
const aiProvider = buildAiProvider(validatedConfig, envVars);
|
|
266
364
|
|
|
267
|
-
logger.debug(
|
|
365
|
+
logger.debug("CONFIG", "API key source", {
|
|
268
366
|
fromEnv: !!envVars.LIBRARIAN_API_KEY,
|
|
269
|
-
fromConfig: !!validatedConfig.aiProvider?.apiKey
|
|
367
|
+
fromConfig: !!validatedConfig.aiProvider?.apiKey,
|
|
270
368
|
});
|
|
271
369
|
|
|
272
370
|
const config = {
|
|
273
371
|
...validatedConfig,
|
|
274
372
|
technologies: technologies || { default: {} },
|
|
275
373
|
aiProvider,
|
|
276
|
-
repos_path: validatedConfig.repos_path
|
|
277
|
-
|
|
374
|
+
repos_path: validatedConfig.repos_path
|
|
375
|
+
? expandTilde(validatedConfig.repos_path)
|
|
376
|
+
: undefined,
|
|
377
|
+
workingDir: expandTilde(validatedConfig.workingDir),
|
|
278
378
|
} as LibrarianConfig;
|
|
279
379
|
|
|
280
380
|
validateConfig(config, envPath);
|
|
281
381
|
|
|
282
|
-
logger.info(
|
|
382
|
+
logger.info("CONFIG", "Config loaded successfully", {
|
|
283
383
|
aiProviderType: config.aiProvider.type,
|
|
284
384
|
model: config.aiProvider.model,
|
|
285
385
|
techGroupsCount: Object.keys(config.technologies || {}).length,
|
|
286
|
-
reposPath: config.repos_path
|
|
386
|
+
reposPath: config.repos_path
|
|
387
|
+
? config.repos_path.replace(os.homedir(), "~")
|
|
388
|
+
: "workingDir",
|
|
287
389
|
});
|
|
288
390
|
|
|
289
391
|
return config;
|
|
@@ -295,15 +397,15 @@ export async function createDefaultConfig(configPath: string): Promise<void> {
|
|
|
295
397
|
aiProvider: {
|
|
296
398
|
type: "openai-compatible",
|
|
297
399
|
model: "grok-code",
|
|
298
|
-
baseURL: "https://opencode.ai/zen/v1"
|
|
299
|
-
}
|
|
400
|
+
baseURL: "https://opencode.ai/zen/v1",
|
|
401
|
+
},
|
|
300
402
|
};
|
|
301
403
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
404
|
+
// Ensure directory exists
|
|
405
|
+
const configDir = path.dirname(configPath);
|
|
406
|
+
await mkdir(configDir, { recursive: true });
|
|
305
407
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
408
|
+
// Write YAML file
|
|
409
|
+
const yamlString = stringify(defaultConfig);
|
|
410
|
+
await Bun.write(configPath, yamlString);
|
|
411
|
+
}
|