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.
@@ -0,0 +1,358 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+
6
+ import {
7
+ loadUserConfig,
8
+ saveUserConfig,
9
+ deleteUserConfig,
10
+ updateUserConfig,
11
+ addCustomModel,
12
+ removeCustomModel,
13
+ getAllModels,
14
+ getDisplayConfig,
15
+ getConfigPath,
16
+ configExists,
17
+ type CustomModel,
18
+ } from '../../config/user-config.js';
19
+ import { runSetupWizard } from '../setup-wizard.js';
20
+
21
+ /**
22
+ * Create config command group
23
+ */
24
+ export function createConfigCommand(): Command {
25
+ const configCmd = new Command('config')
26
+ .description('Manage Family AI Agent configuration');
27
+
28
+ // config setup - Run interactive setup wizard
29
+ configCmd
30
+ .command('setup')
31
+ .description('Run interactive setup wizard')
32
+ .action(async () => {
33
+ await runSetupWizard();
34
+ });
35
+
36
+ // config show - Display current configuration
37
+ configCmd
38
+ .command('show')
39
+ .description('Display current configuration')
40
+ .action(() => {
41
+ const config = getDisplayConfig();
42
+
43
+ if (!config) {
44
+ console.log(chalk.yellow('\nNo configuration found.'));
45
+ console.log(chalk.gray('Run `family-ai-agent config setup` to create one.\n'));
46
+ return;
47
+ }
48
+
49
+ console.log(chalk.bold('\nšŸ“‹ Current Configuration\n'));
50
+ console.log(chalk.white('Location: ') + chalk.gray(getConfigPath()));
51
+ console.log();
52
+
53
+ // API Settings
54
+ console.log(chalk.bold.cyan('API Settings'));
55
+ console.log(` API Key: ${chalk.gray(config.apiKey as string)}`);
56
+ console.log(` Base URL: ${chalk.gray(config.baseUrl as string)}`);
57
+ console.log();
58
+
59
+ // Model Settings
60
+ console.log(chalk.bold.cyan('Models'));
61
+ console.log(` Default: ${chalk.green(config.defaultModel as string)}`);
62
+ console.log(` Fast: ${chalk.green(config.fastModel as string)}`);
63
+ console.log(` Embedding: ${chalk.green(config.embeddingModel as string)}`);
64
+ console.log();
65
+
66
+ // Safety Settings
67
+ console.log(chalk.bold.cyan('Safety'));
68
+ console.log(
69
+ ` Filters: ${(config.enableSafetyFilters as boolean) ? chalk.green('Enabled') : chalk.yellow('Disabled')}`
70
+ );
71
+ console.log(
72
+ ` Logging: ${(config.enableAuditLogging as boolean) ? chalk.green('Enabled') : chalk.yellow('Disabled')}`
73
+ );
74
+ console.log();
75
+
76
+ // Custom Models
77
+ const customModels = config.customModels as CustomModel[];
78
+ if (customModels && customModels.length > 0) {
79
+ console.log(chalk.bold.cyan('Custom Models'));
80
+ for (const model of customModels) {
81
+ console.log(` - ${chalk.white(model.name)} (${chalk.gray(model.id)})`);
82
+ }
83
+ console.log();
84
+ }
85
+
86
+ // Timestamps
87
+ if (config.createdAt || config.updatedAt) {
88
+ console.log(chalk.bold.cyan('Metadata'));
89
+ if (config.createdAt) {
90
+ console.log(` Created: ${chalk.gray(new Date(config.createdAt as string).toLocaleString())}`);
91
+ }
92
+ if (config.updatedAt) {
93
+ console.log(` Updated: ${chalk.gray(new Date(config.updatedAt as string).toLocaleString())}`);
94
+ }
95
+ console.log();
96
+ }
97
+ });
98
+
99
+ // config set <key> <value> - Set a configuration value
100
+ configCmd
101
+ .command('set <key> <value>')
102
+ .description('Set a configuration value')
103
+ .action(async (key: string, value: string) => {
104
+ if (!configExists()) {
105
+ console.log(chalk.yellow('\nNo configuration found.'));
106
+ console.log(chalk.gray('Run `family-ai-agent config setup` first.\n'));
107
+ return;
108
+ }
109
+
110
+ const validKeys = [
111
+ 'apiKey',
112
+ 'baseUrl',
113
+ 'defaultModel',
114
+ 'fastModel',
115
+ 'embeddingModel',
116
+ 'enableSafetyFilters',
117
+ 'enableAuditLogging',
118
+ ];
119
+
120
+ // Map common aliases
121
+ const keyAliases: Record<string, string> = {
122
+ 'api-key': 'apiKey',
123
+ 'api_key': 'apiKey',
124
+ 'key': 'apiKey',
125
+ 'base-url': 'baseUrl',
126
+ 'base_url': 'baseUrl',
127
+ 'url': 'baseUrl',
128
+ 'model': 'defaultModel',
129
+ 'default-model': 'defaultModel',
130
+ 'default_model': 'defaultModel',
131
+ 'fast-model': 'fastModel',
132
+ 'fast_model': 'fastModel',
133
+ 'fast': 'fastModel',
134
+ 'embedding': 'embeddingModel',
135
+ 'embedding-model': 'embeddingModel',
136
+ 'safety': 'enableSafetyFilters',
137
+ 'filters': 'enableSafetyFilters',
138
+ 'logging': 'enableAuditLogging',
139
+ 'audit': 'enableAuditLogging',
140
+ };
141
+
142
+ const normalizedKey = keyAliases[key.toLowerCase()] || key;
143
+
144
+ if (!validKeys.includes(normalizedKey)) {
145
+ console.log(chalk.red(`\nUnknown configuration key: ${key}`));
146
+ console.log(chalk.gray(`Valid keys: ${validKeys.join(', ')}\n`));
147
+ return;
148
+ }
149
+
150
+ const spinner = ora('Updating configuration...').start();
151
+
152
+ try {
153
+ // Handle boolean values
154
+ let parsedValue: unknown = value;
155
+ if (normalizedKey.startsWith('enable')) {
156
+ parsedValue = ['true', '1', 'yes', 'on'].includes(value.toLowerCase());
157
+ }
158
+
159
+ updateUserConfig({ [normalizedKey]: parsedValue });
160
+ spinner.succeed(`Set ${normalizedKey} = ${value}`);
161
+ } catch (error) {
162
+ spinner.fail('Failed to update configuration');
163
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
164
+ }
165
+ });
166
+
167
+ // config models - List available models
168
+ configCmd
169
+ .command('models')
170
+ .description('List available models')
171
+ .action(() => {
172
+ const models = getAllModels();
173
+
174
+ console.log(chalk.bold('\nšŸ“¦ Available Models\n'));
175
+
176
+ // Group by type
177
+ const builtIn = models.filter((m) => !m.isCustom);
178
+ const custom = models.filter((m) => m.isCustom);
179
+
180
+ console.log(chalk.bold.cyan('Built-in Models'));
181
+ for (const model of builtIn) {
182
+ console.log(` ${chalk.white(model.name)}`);
183
+ console.log(` ${chalk.gray(model.id)}`);
184
+ }
185
+
186
+ if (custom.length > 0) {
187
+ console.log();
188
+ console.log(chalk.bold.cyan('Custom Models'));
189
+ for (const model of custom) {
190
+ console.log(` ${chalk.white(model.name)}`);
191
+ console.log(` ${chalk.gray(model.id)}`);
192
+ }
193
+ }
194
+
195
+ console.log();
196
+ console.log(chalk.gray('Tip: Use `config add-model` to add a custom model.'));
197
+ console.log();
198
+ });
199
+
200
+ // config add-model - Add a custom model
201
+ configCmd
202
+ .command('add-model')
203
+ .description('Add a custom model')
204
+ .action(async () => {
205
+ if (!configExists()) {
206
+ console.log(chalk.yellow('\nNo configuration found.'));
207
+ console.log(chalk.gray('Run `family-ai-agent config setup` first.\n'));
208
+ return;
209
+ }
210
+
211
+ console.log(chalk.bold('\nāž• Add Custom Model\n'));
212
+
213
+ try {
214
+ const answers = await inquirer.prompt<{
215
+ id: string;
216
+ name: string;
217
+ contextWindow: number;
218
+ maxOutput: number;
219
+ description: string;
220
+ }>([
221
+ {
222
+ type: 'input',
223
+ name: 'id',
224
+ message: 'Model ID (e.g., provider/model-name):',
225
+ validate: (input: string) => {
226
+ if (!input || input.trim().length === 0) {
227
+ return 'Model ID is required';
228
+ }
229
+ return true;
230
+ },
231
+ },
232
+ {
233
+ type: 'input',
234
+ name: 'name',
235
+ message: 'Display name:',
236
+ validate: (input: string) => {
237
+ if (!input || input.trim().length === 0) {
238
+ return 'Display name is required';
239
+ }
240
+ return true;
241
+ },
242
+ },
243
+ {
244
+ type: 'number',
245
+ name: 'contextWindow',
246
+ message: 'Context window size:',
247
+ default: 8192,
248
+ },
249
+ {
250
+ type: 'number',
251
+ name: 'maxOutput',
252
+ message: 'Max output tokens:',
253
+ default: 4096,
254
+ },
255
+ {
256
+ type: 'input',
257
+ name: 'description',
258
+ message: 'Description (optional):',
259
+ default: '',
260
+ },
261
+ ]);
262
+
263
+ const model: CustomModel = {
264
+ id: answers.id,
265
+ name: answers.name,
266
+ contextWindow: answers.contextWindow,
267
+ maxOutput: answers.maxOutput,
268
+ description: answers.description || undefined,
269
+ };
270
+
271
+ addCustomModel(model);
272
+ console.log(chalk.green(`\nāœ“ Added custom model: ${model.name}\n`));
273
+ } catch (error) {
274
+ if ((error as { name?: string }).name === 'ExitPromptError') {
275
+ console.log(chalk.yellow('\nCancelled.\n'));
276
+ return;
277
+ }
278
+ throw error;
279
+ }
280
+ });
281
+
282
+ // config remove-model <id> - Remove a custom model
283
+ configCmd
284
+ .command('remove-model <id>')
285
+ .description('Remove a custom model')
286
+ .action(async (id: string) => {
287
+ if (!configExists()) {
288
+ console.log(chalk.yellow('\nNo configuration found.\n'));
289
+ return;
290
+ }
291
+
292
+ const config = loadUserConfig();
293
+ const model = config?.customModels.find((m) => m.id === id);
294
+
295
+ if (!model) {
296
+ console.log(chalk.yellow(`\nModel not found: ${id}\n`));
297
+ return;
298
+ }
299
+
300
+ const { confirm } = await inquirer.prompt<{ confirm: boolean }>([
301
+ {
302
+ type: 'confirm',
303
+ name: 'confirm',
304
+ message: `Remove custom model "${model.name}"?`,
305
+ default: false,
306
+ },
307
+ ]);
308
+
309
+ if (confirm) {
310
+ removeCustomModel(id);
311
+ console.log(chalk.green(`\nāœ“ Removed model: ${model.name}\n`));
312
+ } else {
313
+ console.log(chalk.gray('\nCancelled.\n'));
314
+ }
315
+ });
316
+
317
+ // config reset - Reset configuration
318
+ configCmd
319
+ .command('reset')
320
+ .description('Reset configuration to defaults')
321
+ .action(async () => {
322
+ if (!configExists()) {
323
+ console.log(chalk.yellow('\nNo configuration to reset.\n'));
324
+ return;
325
+ }
326
+
327
+ const { confirm } = await inquirer.prompt<{ confirm: boolean }>([
328
+ {
329
+ type: 'confirm',
330
+ name: 'confirm',
331
+ message: chalk.red('Are you sure you want to reset all configuration?'),
332
+ default: false,
333
+ },
334
+ ]);
335
+
336
+ if (confirm) {
337
+ deleteUserConfig();
338
+ console.log(chalk.green('\nāœ“ Configuration reset.\n'));
339
+ console.log(chalk.gray('Run `family-ai-agent config setup` to reconfigure.\n'));
340
+ } else {
341
+ console.log(chalk.gray('\nCancelled.\n'));
342
+ }
343
+ });
344
+
345
+ // config path - Show config file path
346
+ configCmd
347
+ .command('path')
348
+ .description('Show configuration file path')
349
+ .action(() => {
350
+ console.log(chalk.white('\nConfiguration file:'));
351
+ console.log(chalk.cyan(getConfigPath()));
352
+ console.log();
353
+ });
354
+
355
+ return configCmd;
356
+ }
357
+
358
+ export default createConfigCommand;
package/src/cli/index.ts CHANGED
@@ -6,17 +6,20 @@ import chalk from 'chalk';
6
6
  import ora from 'ora';
7
7
  import boxen from 'boxen';
8
8
 
9
- import { runOrchestrator, streamOrchestrator } from '../core/orchestrator/graph.js';
9
+ import { runOrchestrator } from '../core/orchestrator/graph.js';
10
10
  import { getVectorStore } from '../memory/longterm/vector-store.js';
11
11
  import { getConversationMemory } from '../memory/conversation/index.js';
12
12
  import { getKnowledgeBase } from '../memory/knowledge-base/index.js';
13
13
  import { validateInput } from '../safety/guardrails/input-guardrail.js';
14
14
  import { validateOutput } from '../safety/guardrails/output-guardrail.js';
15
15
  import { initDatabase, closePool } from '../database/client.js';
16
- import { config, isDevelopment } from '../config/index.js';
16
+ import { config, isConfigured, hasUserConfig, getUserConfigPath } from '../config/index.js';
17
17
  import { createLogger } from '../utils/logger.js';
18
18
  import { nanoid } from 'nanoid';
19
19
 
20
+ import { runSetupWizard, checkAndRunSetup } from './setup-wizard.js';
21
+ import { createConfigCommand } from './commands/config.js';
22
+
20
23
  const logger = createLogger('CLI');
21
24
  const program = new Command();
22
25
 
@@ -38,15 +41,80 @@ function showBanner(): void {
38
41
  console.log(banner);
39
42
  }
40
43
 
44
+ // Check if configured and prompt setup if needed
45
+ async function ensureConfigured(): Promise<boolean> {
46
+ if (isConfigured()) {
47
+ return true;
48
+ }
49
+
50
+ console.log(chalk.yellow('\nāš ļø No API key configured.\n'));
51
+
52
+ const { action } = await inquirer.prompt<{ action: string }>([
53
+ {
54
+ type: 'list',
55
+ name: 'action',
56
+ message: 'What would you like to do?',
57
+ choices: [
58
+ { name: 'Run setup wizard', value: 'setup' },
59
+ { name: 'Enter API key directly', value: 'quick' },
60
+ { name: 'Exit', value: 'exit' },
61
+ ],
62
+ },
63
+ ]);
64
+
65
+ if (action === 'exit') {
66
+ console.log(chalk.gray('\nRun `family-ai-agent config setup` when ready.\n'));
67
+ return false;
68
+ }
69
+
70
+ if (action === 'quick') {
71
+ const { apiKey } = await inquirer.prompt<{ apiKey: string }>([
72
+ {
73
+ type: 'password',
74
+ name: 'apiKey',
75
+ message: 'Enter your OpenRouter API Key:',
76
+ mask: '*',
77
+ validate: (input: string) => {
78
+ if (!input || input.trim().length === 0) {
79
+ return 'API key is required';
80
+ }
81
+ return true;
82
+ },
83
+ },
84
+ ]);
85
+
86
+ const { runQuickSetup } = await import('./setup-wizard.js');
87
+ const success = await runQuickSetup(apiKey);
88
+
89
+ if (success) {
90
+ console.log(chalk.green('\nāœ“ Configuration saved! Please restart the application.\n'));
91
+ }
92
+ return false; // Need restart to pick up new config
93
+ }
94
+
95
+ // Full setup wizard
96
+ const success = await runSetupWizard();
97
+ if (success) {
98
+ console.log(chalk.gray('\nPlease restart the application to apply the new configuration.\n'));
99
+ }
100
+ return false; // Need restart to pick up new config
101
+ }
102
+
41
103
  // Interactive chat mode
42
104
  async function startInteractiveChat(): Promise<void> {
43
105
  showBanner();
44
106
 
107
+ // Show config status
108
+ if (hasUserConfig()) {
109
+ console.log(chalk.gray(`\nConfig: ${getUserConfigPath()}`));
110
+ }
111
+ console.log(chalk.gray(`Model: ${config.DEFAULT_MODEL}`));
112
+
45
113
  const threadId = `thread-${nanoid(8)}`;
46
114
  const conversationMemory = getConversationMemory();
47
115
  const vectorStore = getVectorStore();
48
116
 
49
- console.log(chalk.gray(`\nSession: ${threadId}`));
117
+ console.log(chalk.gray(`Session: ${threadId}`));
50
118
  console.log(chalk.gray('Type "exit" or "quit" to end the session'));
51
119
  console.log(chalk.gray('Type "clear" to start a new conversation'));
52
120
  console.log(chalk.gray('Type "memory" to view recent memories'));
@@ -250,15 +318,21 @@ async function runQuery(query: string): Promise<void> {
250
318
 
251
319
  // Main program setup
252
320
  program
253
- .name('family-ai')
321
+ .name('family-ai-agent')
254
322
  .description('Family AI Agent - Your AI Family for All Tasks')
255
- .version('1.0.0');
323
+ .version('1.0.2');
324
+
325
+ // Add config command group
326
+ program.addCommand(createConfigCommand());
256
327
 
257
328
  program
258
329
  .command('chat')
259
330
  .description('Start interactive chat mode')
260
331
  .action(async () => {
261
332
  try {
333
+ if (!(await ensureConfigured())) {
334
+ process.exit(0);
335
+ }
262
336
  await initDatabase();
263
337
  await startInteractiveChat();
264
338
  } catch (error) {
@@ -274,6 +348,9 @@ program
274
348
  .description('Ask a single question')
275
349
  .action(async (query: string) => {
276
350
  try {
351
+ if (!(await ensureConfigured())) {
352
+ process.exit(0);
353
+ }
277
354
  await initDatabase();
278
355
  await runQuery(query);
279
356
  } catch (error) {
@@ -288,6 +365,10 @@ program
288
365
  .command('upload <filepath>')
289
366
  .description('Upload a document to the knowledge base')
290
367
  .action(async (filepath: string) => {
368
+ if (!(await ensureConfigured())) {
369
+ process.exit(0);
370
+ }
371
+
291
372
  const spinner = ora('Uploading document...').start();
292
373
 
293
374
  try {
@@ -321,6 +402,10 @@ program
321
402
  .description('Search the knowledge base')
322
403
  .option('-l, --limit <number>', 'Maximum results', '5')
323
404
  .action(async (query: string, options: { limit: string }) => {
405
+ if (!(await ensureConfigured())) {
406
+ process.exit(0);
407
+ }
408
+
324
409
  const spinner = ora('Searching...').start();
325
410
 
326
411
  try {
@@ -357,22 +442,36 @@ program
357
442
  .command('status')
358
443
  .description('Show system status')
359
444
  .action(async () => {
445
+ console.log(chalk.bold('\nšŸ¤– Family AI Agent Status\n'));
446
+
447
+ // Config status
448
+ if (hasUserConfig()) {
449
+ console.log(chalk.white('Config:'), chalk.green('User config loaded'));
450
+ console.log(chalk.gray(` ${getUserConfigPath()}`));
451
+ } else if (isConfigured()) {
452
+ console.log(chalk.white('Config:'), chalk.yellow('Using environment variables'));
453
+ } else {
454
+ console.log(chalk.white('Config:'), chalk.red('Not configured'));
455
+ console.log(chalk.gray(' Run `family-ai-agent config setup` to configure'));
456
+ }
457
+
458
+ console.log(chalk.white('API Key:'), isConfigured() ? chalk.green('Set') : chalk.red('Missing'));
459
+ console.log(chalk.white('Default Model:'), chalk.cyan(config.DEFAULT_MODEL));
460
+ console.log(chalk.white('Fast Model:'), chalk.cyan(config.FAST_MODEL));
461
+ console.log(chalk.white('Safety Filters:'), config.ENABLE_CONTENT_FILTER ? chalk.green('Enabled') : chalk.yellow('Disabled'));
462
+ console.log(chalk.white('Audit Logging:'), config.ENABLE_AUDIT_LOGGING ? chalk.green('Enabled') : chalk.yellow('Disabled'));
463
+
464
+ // Database status
360
465
  try {
361
466
  await initDatabase();
362
-
363
- console.log(chalk.bold('\nšŸ¤– Family AI Agent Status\n'));
364
467
  console.log(chalk.white('Database:'), chalk.green('Connected'));
365
- console.log(chalk.white('Environment:'), chalk.cyan(config.NODE_ENV));
366
- console.log(chalk.white('Default Model:'), chalk.cyan(config.DEFAULT_MODEL));
367
- console.log(chalk.white('Safety Filters:'), config.ENABLE_CONTENT_FILTER ? chalk.green('Enabled') : chalk.yellow('Disabled'));
368
- console.log(chalk.white('Audit Logging:'), config.ENABLE_AUDIT_LOGGING ? chalk.green('Enabled') : chalk.yellow('Disabled'));
369
- console.log();
370
- } catch (error) {
468
+ } catch {
371
469
  console.log(chalk.white('Database:'), chalk.red('Disconnected'));
372
- console.error(error instanceof Error ? error.message : 'Unknown error');
373
470
  } finally {
374
471
  await closePool();
375
472
  }
473
+
474
+ console.log();
376
475
  });
377
476
 
378
477
  // Handle unhandled rejections
@@ -386,7 +485,18 @@ program.parse();
386
485
 
387
486
  // If no command specified, start interactive chat
388
487
  if (!process.argv.slice(2).length) {
389
- initDatabase()
390
- .then(() => startInteractiveChat())
391
- .finally(() => closePool());
488
+ (async () => {
489
+ try {
490
+ if (!(await ensureConfigured())) {
491
+ process.exit(0);
492
+ }
493
+ await initDatabase();
494
+ await startInteractiveChat();
495
+ } catch (error) {
496
+ console.error(chalk.red('Failed to start:'), error);
497
+ process.exit(1);
498
+ } finally {
499
+ await closePool();
500
+ }
501
+ })();
392
502
  }