family-ai-agent 1.0.0 ā 1.0.3
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/cli/commands/config.d.ts +7 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +300 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/index.js +113 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/setup-wizard.d.ts +19 -0
- package/dist/cli/setup-wizard.d.ts.map +1 -0
- package/dist/cli/setup-wizard.js +294 -0
- package/dist/cli/setup-wizard.js.map +1 -0
- package/dist/config/index.d.ts +6 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +114 -5
- package/dist/config/index.js.map +1 -1
- package/dist/config/user-config.d.ts +203 -0
- package/dist/config/user-config.d.ts.map +1 -0
- package/dist/config/user-config.js +267 -0
- package/dist/config/user-config.js.map +1 -0
- package/package.json +4 -1
- package/src/cli/commands/config.ts +358 -0
- package/src/cli/index.ts +127 -17
- package/src/cli/setup-wizard.ts +343 -0
- package/src/config/index.ts +164 -5
- package/src/config/user-config.ts +323 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import boxen from 'boxen';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
saveUserConfig,
|
|
8
|
+
getAllModels,
|
|
9
|
+
getConfigPath,
|
|
10
|
+
type PartialUserConfig,
|
|
11
|
+
} from '../config/user-config.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Display welcome banner for setup
|
|
15
|
+
*/
|
|
16
|
+
function showSetupBanner(): void {
|
|
17
|
+
const banner = boxen(
|
|
18
|
+
chalk.bold.cyan('Family AI Agent Setup') +
|
|
19
|
+
'\n\n' +
|
|
20
|
+
chalk.white('Configure your AI agent in a few simple steps') +
|
|
21
|
+
'\n' +
|
|
22
|
+
chalk.gray('Your settings will be saved locally'),
|
|
23
|
+
{
|
|
24
|
+
padding: 1,
|
|
25
|
+
margin: 1,
|
|
26
|
+
borderStyle: 'round',
|
|
27
|
+
borderColor: 'cyan',
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
console.log(banner);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Test API connection
|
|
35
|
+
*/
|
|
36
|
+
async function testConnection(
|
|
37
|
+
apiKey: string,
|
|
38
|
+
baseUrl: string
|
|
39
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch(`${baseUrl}/models`, {
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: `Bearer ${apiKey}`,
|
|
44
|
+
'HTTP-Referer': 'https://family-ai-agent.local',
|
|
45
|
+
'X-Title': 'Family AI Agent Setup',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (response.ok) {
|
|
50
|
+
return { success: true };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const errorData = await response.json().catch(() => ({}));
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: (errorData as Record<string, unknown>)?.error?.toString() || `HTTP ${response.status}`,
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error: error instanceof Error ? error.message : 'Connection failed',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Run the interactive setup wizard
|
|
68
|
+
*/
|
|
69
|
+
export async function runSetupWizard(): Promise<boolean> {
|
|
70
|
+
showSetupBanner();
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// Step 1: API Key
|
|
74
|
+
console.log(chalk.bold('\nš Step 1: API Configuration\n'));
|
|
75
|
+
|
|
76
|
+
const { apiKey } = await inquirer.prompt<{ apiKey: string }>([
|
|
77
|
+
{
|
|
78
|
+
type: 'password',
|
|
79
|
+
name: 'apiKey',
|
|
80
|
+
message: 'Enter your OpenRouter API Key:',
|
|
81
|
+
mask: '*',
|
|
82
|
+
validate: (input: string) => {
|
|
83
|
+
if (!input || input.trim().length === 0) {
|
|
84
|
+
return 'API key is required';
|
|
85
|
+
}
|
|
86
|
+
if (input.length < 10) {
|
|
87
|
+
return 'API key seems too short';
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
// Step 2: Base URL
|
|
95
|
+
const { baseUrl } = await inquirer.prompt<{ baseUrl: string }>([
|
|
96
|
+
{
|
|
97
|
+
type: 'input',
|
|
98
|
+
name: 'baseUrl',
|
|
99
|
+
message: 'API Base URL:',
|
|
100
|
+
default: 'https://openrouter.ai/api/v1',
|
|
101
|
+
validate: (input: string) => {
|
|
102
|
+
try {
|
|
103
|
+
new URL(input);
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return 'Please enter a valid URL';
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// Test connection
|
|
113
|
+
const spinner = ora('Testing API connection...').start();
|
|
114
|
+
const testResult = await testConnection(apiKey, baseUrl);
|
|
115
|
+
|
|
116
|
+
if (!testResult.success) {
|
|
117
|
+
spinner.fail(`Connection failed: ${testResult.error}`);
|
|
118
|
+
|
|
119
|
+
const { continueAnyway } = await inquirer.prompt<{ continueAnyway: boolean }>([
|
|
120
|
+
{
|
|
121
|
+
type: 'confirm',
|
|
122
|
+
name: 'continueAnyway',
|
|
123
|
+
message: 'Continue with setup anyway?',
|
|
124
|
+
default: false,
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
if (!continueAnyway) {
|
|
129
|
+
console.log(chalk.yellow('\nSetup cancelled. Please check your API key and try again.\n'));
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
spinner.succeed('API connection successful!');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Step 3: Model Selection
|
|
137
|
+
console.log(chalk.bold('\nš¤ Step 2: Model Configuration\n'));
|
|
138
|
+
|
|
139
|
+
const models = getAllModels();
|
|
140
|
+
const modelChoices = [
|
|
141
|
+
...models.map((m) => ({
|
|
142
|
+
name: `${m.name} (${m.id})`,
|
|
143
|
+
value: m.id,
|
|
144
|
+
})),
|
|
145
|
+
{ name: chalk.gray('Enter custom model ID...'), value: '__custom__' },
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
const { defaultModel } = await inquirer.prompt<{ defaultModel: string }>([
|
|
149
|
+
{
|
|
150
|
+
type: 'list',
|
|
151
|
+
name: 'defaultModel',
|
|
152
|
+
message: 'Select your default model:',
|
|
153
|
+
choices: modelChoices,
|
|
154
|
+
default: 'anthropic/claude-3.5-sonnet',
|
|
155
|
+
},
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
let finalDefaultModel = defaultModel;
|
|
159
|
+
|
|
160
|
+
if (defaultModel === '__custom__') {
|
|
161
|
+
const { customModel } = await inquirer.prompt<{ customModel: string }>([
|
|
162
|
+
{
|
|
163
|
+
type: 'input',
|
|
164
|
+
name: 'customModel',
|
|
165
|
+
message: 'Enter custom model ID (e.g., provider/model-name):',
|
|
166
|
+
validate: (input: string) => {
|
|
167
|
+
if (!input || input.trim().length === 0) {
|
|
168
|
+
return 'Model ID is required';
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
finalDefaultModel = customModel;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Step 4: Fast Model (optional)
|
|
178
|
+
const { configureFastModel } = await inquirer.prompt<{ configureFastModel: boolean }>([
|
|
179
|
+
{
|
|
180
|
+
type: 'confirm',
|
|
181
|
+
name: 'configureFastModel',
|
|
182
|
+
message: 'Configure a separate fast model for quick tasks?',
|
|
183
|
+
default: true,
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
let fastModel = 'anthropic/claude-3-haiku';
|
|
188
|
+
|
|
189
|
+
if (configureFastModel) {
|
|
190
|
+
const { selectedFastModel } = await inquirer.prompt<{ selectedFastModel: string }>([
|
|
191
|
+
{
|
|
192
|
+
type: 'list',
|
|
193
|
+
name: 'selectedFastModel',
|
|
194
|
+
message: 'Select fast model:',
|
|
195
|
+
choices: modelChoices,
|
|
196
|
+
default: 'anthropic/claude-3-haiku',
|
|
197
|
+
},
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
if (selectedFastModel === '__custom__') {
|
|
201
|
+
const { customFastModel } = await inquirer.prompt<{ customFastModel: string }>([
|
|
202
|
+
{
|
|
203
|
+
type: 'input',
|
|
204
|
+
name: 'customFastModel',
|
|
205
|
+
message: 'Enter custom fast model ID:',
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
fastModel = customFastModel;
|
|
209
|
+
} else {
|
|
210
|
+
fastModel = selectedFastModel;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Step 5: Safety Settings
|
|
215
|
+
console.log(chalk.bold('\nš”ļø Step 3: Safety Settings\n'));
|
|
216
|
+
|
|
217
|
+
const { enableSafetyFilters, enableAuditLogging } = await inquirer.prompt<{
|
|
218
|
+
enableSafetyFilters: boolean;
|
|
219
|
+
enableAuditLogging: boolean;
|
|
220
|
+
}>([
|
|
221
|
+
{
|
|
222
|
+
type: 'confirm',
|
|
223
|
+
name: 'enableSafetyFilters',
|
|
224
|
+
message: 'Enable content safety filters?',
|
|
225
|
+
default: true,
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
type: 'confirm',
|
|
229
|
+
name: 'enableAuditLogging',
|
|
230
|
+
message: 'Enable audit logging?',
|
|
231
|
+
default: true,
|
|
232
|
+
},
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
// Save configuration
|
|
236
|
+
console.log();
|
|
237
|
+
const saveSpinner = ora('Saving configuration...').start();
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const config: PartialUserConfig = {
|
|
241
|
+
apiKey,
|
|
242
|
+
baseUrl,
|
|
243
|
+
defaultModel: finalDefaultModel,
|
|
244
|
+
fastModel,
|
|
245
|
+
embeddingModel: 'openai/text-embedding-3-small',
|
|
246
|
+
enableSafetyFilters,
|
|
247
|
+
enableAuditLogging,
|
|
248
|
+
customModels: [],
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
saveUserConfig(config);
|
|
252
|
+
saveSpinner.succeed('Configuration saved!');
|
|
253
|
+
|
|
254
|
+
// Show summary
|
|
255
|
+
console.log(
|
|
256
|
+
boxen(
|
|
257
|
+
chalk.bold.green('Setup Complete!') +
|
|
258
|
+
'\n\n' +
|
|
259
|
+
chalk.white('Configuration saved to:') +
|
|
260
|
+
'\n' +
|
|
261
|
+
chalk.gray(getConfigPath()) +
|
|
262
|
+
'\n\n' +
|
|
263
|
+
chalk.white('Default Model: ') +
|
|
264
|
+
chalk.cyan(finalDefaultModel) +
|
|
265
|
+
'\n' +
|
|
266
|
+
chalk.white('Fast Model: ') +
|
|
267
|
+
chalk.cyan(fastModel) +
|
|
268
|
+
'\n\n' +
|
|
269
|
+
chalk.gray('Run `family-ai-agent` to start chatting!'),
|
|
270
|
+
{
|
|
271
|
+
padding: 1,
|
|
272
|
+
margin: 1,
|
|
273
|
+
borderStyle: 'round',
|
|
274
|
+
borderColor: 'green',
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
return true;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
saveSpinner.fail('Failed to save configuration');
|
|
282
|
+
console.error(
|
|
283
|
+
chalk.red(error instanceof Error ? error.message : 'Unknown error')
|
|
284
|
+
);
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
// Handle Ctrl+C gracefully
|
|
289
|
+
if ((error as { name?: string }).name === 'ExitPromptError') {
|
|
290
|
+
console.log(chalk.yellow('\n\nSetup cancelled.\n'));
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Quick setup with minimal prompts
|
|
299
|
+
*/
|
|
300
|
+
export async function runQuickSetup(apiKey: string): Promise<boolean> {
|
|
301
|
+
const spinner = ora('Configuring with defaults...').start();
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const config: PartialUserConfig = {
|
|
305
|
+
apiKey,
|
|
306
|
+
baseUrl: 'https://openrouter.ai/api/v1',
|
|
307
|
+
defaultModel: 'anthropic/claude-3.5-sonnet',
|
|
308
|
+
fastModel: 'anthropic/claude-3-haiku',
|
|
309
|
+
embeddingModel: 'openai/text-embedding-3-small',
|
|
310
|
+
enableSafetyFilters: true,
|
|
311
|
+
enableAuditLogging: true,
|
|
312
|
+
customModels: [],
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
saveUserConfig(config);
|
|
316
|
+
spinner.succeed('Configuration saved with defaults!');
|
|
317
|
+
return true;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
spinner.fail('Failed to save configuration');
|
|
320
|
+
console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check if setup is needed and run wizard if so
|
|
327
|
+
*/
|
|
328
|
+
export async function checkAndRunSetup(): Promise<boolean> {
|
|
329
|
+
const { configExists } = await import('../config/user-config.js');
|
|
330
|
+
|
|
331
|
+
if (configExists()) {
|
|
332
|
+
return true; // Already configured
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log(chalk.yellow('\nā ļø No configuration found. Starting setup wizard...\n'));
|
|
336
|
+
return runSetupWizard();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export default {
|
|
340
|
+
runSetupWizard,
|
|
341
|
+
runQuickSetup,
|
|
342
|
+
checkAndRunSetup,
|
|
343
|
+
};
|
package/src/config/index.ts
CHANGED
|
@@ -1,11 +1,119 @@
|
|
|
1
1
|
import { config as dotenvConfig } from 'dotenv';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
3
6
|
|
|
4
7
|
dotenvConfig();
|
|
5
8
|
|
|
9
|
+
// User config path
|
|
10
|
+
const USER_CONFIG_PATH = join(homedir(), '.family-ai-agent', 'config.json');
|
|
11
|
+
|
|
12
|
+
// Try to load user config
|
|
13
|
+
function loadUserConfigFile(): Record<string, unknown> | null {
|
|
14
|
+
if (!existsSync(USER_CONFIG_PATH)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const content = readFileSync(USER_CONFIG_PATH, 'utf-8');
|
|
20
|
+
return JSON.parse(content);
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Get config value with priority: user config > env > default
|
|
27
|
+
function getConfigValue<T>(
|
|
28
|
+
userConfig: Record<string, unknown> | null,
|
|
29
|
+
userKey: string,
|
|
30
|
+
envKey: string,
|
|
31
|
+
envValue: string | undefined
|
|
32
|
+
): string | undefined {
|
|
33
|
+
// Priority 1: User config
|
|
34
|
+
if (userConfig && userKey in userConfig) {
|
|
35
|
+
const value = userConfig[userKey];
|
|
36
|
+
if (typeof value === 'string') return value;
|
|
37
|
+
if (typeof value === 'number') return String(value);
|
|
38
|
+
if (typeof value === 'boolean') return String(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Priority 2: Environment variable
|
|
42
|
+
return envValue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Load user config if available
|
|
46
|
+
const userConfig = loadUserConfigFile();
|
|
47
|
+
|
|
48
|
+
// Build merged environment for Zod schema
|
|
49
|
+
const mergedEnv = {
|
|
50
|
+
...process.env,
|
|
51
|
+
|
|
52
|
+
// Override with user config if available
|
|
53
|
+
OPENROUTER_API_KEY: getConfigValue(
|
|
54
|
+
userConfig,
|
|
55
|
+
'apiKey',
|
|
56
|
+
'OPENROUTER_API_KEY',
|
|
57
|
+
process.env.OPENROUTER_API_KEY
|
|
58
|
+
),
|
|
59
|
+
OPENROUTER_BASE_URL: getConfigValue(
|
|
60
|
+
userConfig,
|
|
61
|
+
'baseUrl',
|
|
62
|
+
'OPENROUTER_BASE_URL',
|
|
63
|
+
process.env.OPENROUTER_BASE_URL
|
|
64
|
+
),
|
|
65
|
+
DEFAULT_MODEL: getConfigValue(
|
|
66
|
+
userConfig,
|
|
67
|
+
'defaultModel',
|
|
68
|
+
'DEFAULT_MODEL',
|
|
69
|
+
process.env.DEFAULT_MODEL
|
|
70
|
+
),
|
|
71
|
+
FAST_MODEL: getConfigValue(
|
|
72
|
+
userConfig,
|
|
73
|
+
'fastModel',
|
|
74
|
+
'FAST_MODEL',
|
|
75
|
+
process.env.FAST_MODEL
|
|
76
|
+
),
|
|
77
|
+
EMBEDDING_MODEL: getConfigValue(
|
|
78
|
+
userConfig,
|
|
79
|
+
'embeddingModel',
|
|
80
|
+
'EMBEDDING_MODEL',
|
|
81
|
+
process.env.EMBEDDING_MODEL
|
|
82
|
+
),
|
|
83
|
+
ENABLE_CONTENT_FILTER: getConfigValue(
|
|
84
|
+
userConfig,
|
|
85
|
+
'enableSafetyFilters',
|
|
86
|
+
'ENABLE_CONTENT_FILTER',
|
|
87
|
+
process.env.ENABLE_CONTENT_FILTER
|
|
88
|
+
),
|
|
89
|
+
ENABLE_AUDIT_LOGGING: getConfigValue(
|
|
90
|
+
userConfig,
|
|
91
|
+
'enableAuditLogging',
|
|
92
|
+
'ENABLE_AUDIT_LOGGING',
|
|
93
|
+
process.env.ENABLE_AUDIT_LOGGING
|
|
94
|
+
),
|
|
95
|
+
|
|
96
|
+
// Database from user config
|
|
97
|
+
DB_HOST: userConfig?.database && typeof userConfig.database === 'object'
|
|
98
|
+
? (userConfig.database as Record<string, unknown>).host as string
|
|
99
|
+
: process.env.DB_HOST,
|
|
100
|
+
DB_PORT: userConfig?.database && typeof userConfig.database === 'object'
|
|
101
|
+
? String((userConfig.database as Record<string, unknown>).port)
|
|
102
|
+
: process.env.DB_PORT,
|
|
103
|
+
DB_USER: userConfig?.database && typeof userConfig.database === 'object'
|
|
104
|
+
? (userConfig.database as Record<string, unknown>).user as string
|
|
105
|
+
: process.env.DB_USER,
|
|
106
|
+
DB_PASSWORD: userConfig?.database && typeof userConfig.database === 'object'
|
|
107
|
+
? (userConfig.database as Record<string, unknown>).password as string
|
|
108
|
+
: process.env.DB_PASSWORD,
|
|
109
|
+
DB_NAME: userConfig?.database && typeof userConfig.database === 'object'
|
|
110
|
+
? (userConfig.database as Record<string, unknown>).name as string
|
|
111
|
+
: process.env.DB_NAME,
|
|
112
|
+
};
|
|
113
|
+
|
|
6
114
|
const envSchema = z.object({
|
|
7
|
-
// OpenRouter
|
|
8
|
-
OPENROUTER_API_KEY: z.string().min(1
|
|
115
|
+
// OpenRouter - now optional (can be set via user config)
|
|
116
|
+
OPENROUTER_API_KEY: z.string().min(1).optional(),
|
|
9
117
|
OPENROUTER_BASE_URL: z.string().url().default('https://openrouter.ai/api/v1'),
|
|
10
118
|
|
|
11
119
|
// Models
|
|
@@ -58,12 +166,48 @@ const envSchema = z.object({
|
|
|
58
166
|
type EnvConfig = z.infer<typeof envSchema>;
|
|
59
167
|
|
|
60
168
|
function loadConfig(): EnvConfig {
|
|
61
|
-
const parsed = envSchema.safeParse(
|
|
169
|
+
const parsed = envSchema.safeParse(mergedEnv);
|
|
62
170
|
|
|
63
171
|
if (!parsed.success) {
|
|
172
|
+
// More helpful error message
|
|
173
|
+
const errors = parsed.error.format();
|
|
64
174
|
console.error('Configuration validation failed:');
|
|
65
|
-
console.error(
|
|
66
|
-
|
|
175
|
+
console.error(errors);
|
|
176
|
+
|
|
177
|
+
// Check if it's just missing API key
|
|
178
|
+
if (!mergedEnv.OPENROUTER_API_KEY) {
|
|
179
|
+
// Don't throw - let CLI handle setup
|
|
180
|
+
return {
|
|
181
|
+
OPENROUTER_API_KEY: undefined,
|
|
182
|
+
OPENROUTER_BASE_URL: 'https://openrouter.ai/api/v1',
|
|
183
|
+
DEFAULT_MODEL: 'anthropic/claude-3.5-sonnet',
|
|
184
|
+
FAST_MODEL: 'anthropic/claude-3-haiku',
|
|
185
|
+
EMBEDDING_MODEL: 'openai/text-embedding-3-small',
|
|
186
|
+
DB_HOST: 'localhost',
|
|
187
|
+
DB_PORT: 5432,
|
|
188
|
+
DB_USER: 'familyai',
|
|
189
|
+
DB_PASSWORD: 'familyai123',
|
|
190
|
+
DB_NAME: 'familyai',
|
|
191
|
+
REDIS_HOST: 'localhost',
|
|
192
|
+
REDIS_PORT: 6379,
|
|
193
|
+
API_PORT: 3000,
|
|
194
|
+
API_HOST: '0.0.0.0',
|
|
195
|
+
ENABLE_CONTENT_FILTER: true,
|
|
196
|
+
ENABLE_PII_DETECTION: true,
|
|
197
|
+
ENABLE_AUDIT_LOGGING: true,
|
|
198
|
+
MAX_TOKENS_PER_REQUEST: 4096,
|
|
199
|
+
RATE_LIMIT_MAX: 100,
|
|
200
|
+
RATE_LIMIT_WINDOW_MS: 60000,
|
|
201
|
+
SANDBOX_ENABLED: true,
|
|
202
|
+
SANDBOX_TIMEOUT_MS: 30000,
|
|
203
|
+
SANDBOX_MEMORY_LIMIT_MB: 256,
|
|
204
|
+
LOG_LEVEL: 'info',
|
|
205
|
+
LOG_FORMAT: 'json',
|
|
206
|
+
NODE_ENV: 'development',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
throw new Error('Invalid configuration. Check your settings.');
|
|
67
211
|
}
|
|
68
212
|
|
|
69
213
|
return parsed.data;
|
|
@@ -71,6 +215,21 @@ function loadConfig(): EnvConfig {
|
|
|
71
215
|
|
|
72
216
|
export const config = loadConfig();
|
|
73
217
|
|
|
218
|
+
// Check if config is complete (has API key)
|
|
219
|
+
export const isConfigured = (): boolean => {
|
|
220
|
+
return Boolean(config.OPENROUTER_API_KEY);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Check if using user config file
|
|
224
|
+
export const hasUserConfig = (): boolean => {
|
|
225
|
+
return userConfig !== null;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Get user config path
|
|
229
|
+
export const getUserConfigPath = (): string => {
|
|
230
|
+
return USER_CONFIG_PATH;
|
|
231
|
+
};
|
|
232
|
+
|
|
74
233
|
export const getDatabaseUrl = (): string => {
|
|
75
234
|
if (config.DATABASE_URL) return config.DATABASE_URL;
|
|
76
235
|
return `postgresql://${config.DB_USER}:${config.DB_PASSWORD}@${config.DB_HOST}:${config.DB_PORT}/${config.DB_NAME}`;
|