family-ai-agent 1.0.2 → 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 +1 -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,323 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { createLogger } from '../utils/logger.js';
|
|
6
|
+
|
|
7
|
+
const logger = createLogger('UserConfig');
|
|
8
|
+
|
|
9
|
+
// Config directory and file paths
|
|
10
|
+
const CONFIG_DIR_NAME = '.family-ai-agent';
|
|
11
|
+
const CONFIG_FILE_NAME = 'config.json';
|
|
12
|
+
|
|
13
|
+
// Custom model schema
|
|
14
|
+
const CustomModelSchema = z.object({
|
|
15
|
+
id: z.string().min(1),
|
|
16
|
+
name: z.string().min(1),
|
|
17
|
+
contextWindow: z.number().positive().default(8192),
|
|
18
|
+
maxOutput: z.number().positive().default(4096),
|
|
19
|
+
description: z.string().optional(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export type CustomModel = z.infer<typeof CustomModelSchema>;
|
|
23
|
+
|
|
24
|
+
// User config schema
|
|
25
|
+
const UserConfigSchema = z.object({
|
|
26
|
+
// API Configuration
|
|
27
|
+
apiKey: z.string().min(1, 'API key is required'),
|
|
28
|
+
baseUrl: z.string().url().default('https://openrouter.ai/api/v1'),
|
|
29
|
+
|
|
30
|
+
// Model Configuration
|
|
31
|
+
defaultModel: z.string().default('anthropic/claude-3.5-sonnet'),
|
|
32
|
+
fastModel: z.string().default('anthropic/claude-3-haiku'),
|
|
33
|
+
embeddingModel: z.string().default('openai/text-embedding-3-small'),
|
|
34
|
+
|
|
35
|
+
// Custom Models
|
|
36
|
+
customModels: z.array(CustomModelSchema).default([]),
|
|
37
|
+
|
|
38
|
+
// Optional Settings
|
|
39
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
40
|
+
maxTokens: z.number().positive().optional(),
|
|
41
|
+
|
|
42
|
+
// Feature Flags
|
|
43
|
+
enableSafetyFilters: z.boolean().default(true),
|
|
44
|
+
enableAuditLogging: z.boolean().default(true),
|
|
45
|
+
|
|
46
|
+
// Database (optional - uses defaults if not set)
|
|
47
|
+
database: z.object({
|
|
48
|
+
host: z.string().default('localhost'),
|
|
49
|
+
port: z.number().default(5432),
|
|
50
|
+
user: z.string().default('familyai'),
|
|
51
|
+
password: z.string().default('familyai123'),
|
|
52
|
+
name: z.string().default('familyai'),
|
|
53
|
+
}).optional(),
|
|
54
|
+
|
|
55
|
+
// Metadata
|
|
56
|
+
createdAt: z.string().datetime().optional(),
|
|
57
|
+
updatedAt: z.string().datetime().optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export type UserConfig = z.infer<typeof UserConfigSchema>;
|
|
61
|
+
|
|
62
|
+
// Partial config for updates
|
|
63
|
+
export type PartialUserConfig = Partial<Omit<UserConfig, 'createdAt' | 'updatedAt'>>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the config directory path
|
|
67
|
+
*/
|
|
68
|
+
export function getConfigDir(): string {
|
|
69
|
+
return join(homedir(), CONFIG_DIR_NAME);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the config file path
|
|
74
|
+
*/
|
|
75
|
+
export function getConfigPath(): string {
|
|
76
|
+
return join(getConfigDir(), CONFIG_FILE_NAME);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if config file exists
|
|
81
|
+
*/
|
|
82
|
+
export function configExists(): boolean {
|
|
83
|
+
return existsSync(getConfigPath());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Ensure config directory exists
|
|
88
|
+
*/
|
|
89
|
+
export function ensureConfigDir(): void {
|
|
90
|
+
const configDir = getConfigDir();
|
|
91
|
+
if (!existsSync(configDir)) {
|
|
92
|
+
mkdirSync(configDir, { recursive: true });
|
|
93
|
+
logger.debug('Config directory created', { path: configDir });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Load user config from file
|
|
99
|
+
*/
|
|
100
|
+
export function loadUserConfig(): UserConfig | null {
|
|
101
|
+
const configPath = getConfigPath();
|
|
102
|
+
|
|
103
|
+
if (!existsSync(configPath)) {
|
|
104
|
+
logger.debug('Config file not found', { path: configPath });
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const fileContent = readFileSync(configPath, 'utf-8');
|
|
110
|
+
const rawConfig = JSON.parse(fileContent);
|
|
111
|
+
const parsed = UserConfigSchema.safeParse(rawConfig);
|
|
112
|
+
|
|
113
|
+
if (!parsed.success) {
|
|
114
|
+
logger.warn('Config file validation failed', {
|
|
115
|
+
errors: parsed.error.format(),
|
|
116
|
+
});
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
logger.debug('Config loaded successfully');
|
|
121
|
+
return parsed.data;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
logger.error('Failed to load config file', {
|
|
124
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
125
|
+
});
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Save user config to file
|
|
132
|
+
*/
|
|
133
|
+
export function saveUserConfig(config: PartialUserConfig): UserConfig {
|
|
134
|
+
ensureConfigDir();
|
|
135
|
+
const configPath = getConfigPath();
|
|
136
|
+
|
|
137
|
+
// Load existing config if present
|
|
138
|
+
const existingConfig = loadUserConfig();
|
|
139
|
+
|
|
140
|
+
// Merge with existing config
|
|
141
|
+
const mergedConfig = {
|
|
142
|
+
...existingConfig,
|
|
143
|
+
...config,
|
|
144
|
+
updatedAt: new Date().toISOString(),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Add createdAt if new config
|
|
148
|
+
if (!existingConfig) {
|
|
149
|
+
mergedConfig.createdAt = new Date().toISOString();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Validate merged config
|
|
153
|
+
const parsed = UserConfigSchema.safeParse(mergedConfig);
|
|
154
|
+
if (!parsed.success) {
|
|
155
|
+
throw new Error(`Invalid config: ${JSON.stringify(parsed.error.format())}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Write to file
|
|
159
|
+
writeFileSync(configPath, JSON.stringify(parsed.data, null, 2), 'utf-8');
|
|
160
|
+
logger.info('Config saved successfully', { path: configPath });
|
|
161
|
+
|
|
162
|
+
return parsed.data;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Update specific config values
|
|
167
|
+
*/
|
|
168
|
+
export function updateUserConfig(updates: PartialUserConfig): UserConfig {
|
|
169
|
+
const existingConfig = loadUserConfig();
|
|
170
|
+
|
|
171
|
+
if (!existingConfig) {
|
|
172
|
+
throw new Error('No config file found. Run setup first.');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return saveUserConfig({
|
|
176
|
+
...existingConfig,
|
|
177
|
+
...updates,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Delete config file
|
|
183
|
+
*/
|
|
184
|
+
export function deleteUserConfig(): boolean {
|
|
185
|
+
const configPath = getConfigPath();
|
|
186
|
+
|
|
187
|
+
if (!existsSync(configPath)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
unlinkSync(configPath);
|
|
193
|
+
logger.info('Config deleted', { path: configPath });
|
|
194
|
+
return true;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
logger.error('Failed to delete config', {
|
|
197
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
198
|
+
});
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get a specific config value
|
|
205
|
+
*/
|
|
206
|
+
export function getConfigValue<K extends keyof UserConfig>(
|
|
207
|
+
key: K
|
|
208
|
+
): UserConfig[K] | undefined {
|
|
209
|
+
const config = loadUserConfig();
|
|
210
|
+
return config?.[key];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Add a custom model
|
|
215
|
+
*/
|
|
216
|
+
export function addCustomModel(model: CustomModel): UserConfig {
|
|
217
|
+
const config = loadUserConfig();
|
|
218
|
+
|
|
219
|
+
if (!config) {
|
|
220
|
+
throw new Error('No config file found. Run setup first.');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check if model already exists
|
|
224
|
+
const existingIndex = config.customModels.findIndex((m) => m.id === model.id);
|
|
225
|
+
|
|
226
|
+
if (existingIndex >= 0) {
|
|
227
|
+
// Update existing model
|
|
228
|
+
config.customModels[existingIndex] = model;
|
|
229
|
+
} else {
|
|
230
|
+
// Add new model
|
|
231
|
+
config.customModels.push(model);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return saveUserConfig(config);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Remove a custom model
|
|
239
|
+
*/
|
|
240
|
+
export function removeCustomModel(modelId: string): UserConfig {
|
|
241
|
+
const config = loadUserConfig();
|
|
242
|
+
|
|
243
|
+
if (!config) {
|
|
244
|
+
throw new Error('No config file found. Run setup first.');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
config.customModels = config.customModels.filter((m) => m.id !== modelId);
|
|
248
|
+
|
|
249
|
+
return saveUserConfig(config);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get all available models (built-in + custom)
|
|
254
|
+
*/
|
|
255
|
+
export function getAllModels(): Array<{ id: string; name: string; isCustom: boolean }> {
|
|
256
|
+
const config = loadUserConfig();
|
|
257
|
+
|
|
258
|
+
// Built-in models from OpenRouter
|
|
259
|
+
const builtInModels = [
|
|
260
|
+
{ id: 'anthropic/claude-3.5-sonnet', name: 'Claude 3.5 Sonnet', isCustom: false },
|
|
261
|
+
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', isCustom: false },
|
|
262
|
+
{ id: 'anthropic/claude-3-opus', name: 'Claude 3 Opus', isCustom: false },
|
|
263
|
+
{ id: 'openai/gpt-4-turbo', name: 'GPT-4 Turbo', isCustom: false },
|
|
264
|
+
{ id: 'openai/gpt-4o', name: 'GPT-4o', isCustom: false },
|
|
265
|
+
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', isCustom: false },
|
|
266
|
+
{ id: 'google/gemini-pro-1.5', name: 'Gemini Pro 1.5', isCustom: false },
|
|
267
|
+
{ id: 'meta-llama/llama-3.1-70b-instruct', name: 'Llama 3.1 70B', isCustom: false },
|
|
268
|
+
{ id: 'meta-llama/llama-3.1-8b-instruct', name: 'Llama 3.1 8B', isCustom: false },
|
|
269
|
+
{ id: 'mistralai/mistral-large', name: 'Mistral Large', isCustom: false },
|
|
270
|
+
{ id: 'deepseek/deepseek-chat', name: 'DeepSeek Chat', isCustom: false },
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
// Add custom models
|
|
274
|
+
const customModels = (config?.customModels || []).map((m) => ({
|
|
275
|
+
id: m.id,
|
|
276
|
+
name: m.name,
|
|
277
|
+
isCustom: true,
|
|
278
|
+
}));
|
|
279
|
+
|
|
280
|
+
return [...builtInModels, ...customModels];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Mask API key for display
|
|
285
|
+
*/
|
|
286
|
+
export function maskApiKey(apiKey: string): string {
|
|
287
|
+
if (apiKey.length <= 8) {
|
|
288
|
+
return '****';
|
|
289
|
+
}
|
|
290
|
+
return `${apiKey.slice(0, 4)}...${apiKey.slice(-4)}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Get config for display (with masked API key)
|
|
295
|
+
*/
|
|
296
|
+
export function getDisplayConfig(): Record<string, unknown> | null {
|
|
297
|
+
const config = loadUserConfig();
|
|
298
|
+
|
|
299
|
+
if (!config) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
...config,
|
|
305
|
+
apiKey: maskApiKey(config.apiKey),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export default {
|
|
310
|
+
getConfigDir,
|
|
311
|
+
getConfigPath,
|
|
312
|
+
configExists,
|
|
313
|
+
loadUserConfig,
|
|
314
|
+
saveUserConfig,
|
|
315
|
+
updateUserConfig,
|
|
316
|
+
deleteUserConfig,
|
|
317
|
+
getConfigValue,
|
|
318
|
+
addCustomModel,
|
|
319
|
+
removeCustomModel,
|
|
320
|
+
getAllModels,
|
|
321
|
+
maskApiKey,
|
|
322
|
+
getDisplayConfig,
|
|
323
|
+
};
|