@toothfairyai/cli 1.4.0 → 1.5.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/bin/toothfairy.js CHANGED
@@ -3951,5 +3951,4017 @@ program
3951
3951
  }
3952
3952
  });
3953
3953
 
3954
+ // Agent commands
3955
+ program
3956
+ .command('create-agent')
3957
+ .description('Create a new agent')
3958
+ .option('--label <label>', 'Agent label')
3959
+ .option('--description <description>', 'Agent description')
3960
+ .option('--emoji <emoji>', 'Agent emoji')
3961
+ .option('--mode <mode>', 'Agent mode (retriever|coder|chatter|planner|computer|voice|accuracy)')
3962
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
3963
+ .option('-v, --verbose', 'Show detailed information')
3964
+ .action(async (options, command) => {
3965
+ try {
3966
+ const globalOptions = command.parent.opts();
3967
+ const config = loadConfig(globalOptions.config);
3968
+ validateConfiguration(config);
3969
+
3970
+ const api = new ToothFairyAPI(
3971
+ config.baseUrl,
3972
+ config.aiUrl,
3973
+ config.aiStreamUrl,
3974
+ config.apiKey,
3975
+ config.workspaceId,
3976
+ globalOptions.verbose || options.verbose
3977
+ );
3978
+
3979
+ const spinner = ora('Creating agent...').start();
3980
+ const result = await api._makeRequest('POST', 'agent/create', {
3981
+ label: options.label,
3982
+ description: options.description,
3983
+ emoji: options.emoji,
3984
+ mode: options.mode,
3985
+ });
3986
+ spinner.stop();
3987
+
3988
+ if (options.output === 'json') {
3989
+ console.log(JSON.stringify(result, null, 2));
3990
+ } else {
3991
+ console.log(chalk.green.bold('✅ Agent created successfully!'));
3992
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
3993
+ }
3994
+ } catch (error) {
3995
+ console.error(chalk.red(`Error creating agent: ${error.message}`));
3996
+ process.exit(1);
3997
+ }
3998
+ });
3999
+
4000
+ program
4001
+ .command('list-agents')
4002
+ .description('List all agents')
4003
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4004
+ .option('-v, --verbose', 'Show detailed information')
4005
+ .action(async (options, command) => {
4006
+ try {
4007
+ const globalOptions = command.parent.opts();
4008
+ const config = loadConfig(globalOptions.config);
4009
+ validateConfiguration(config);
4010
+
4011
+ const api = new ToothFairyAPI(
4012
+ config.baseUrl,
4013
+ config.aiUrl,
4014
+ config.aiStreamUrl,
4015
+ config.apiKey,
4016
+ config.workspaceId,
4017
+ globalOptions.verbose || options.verbose
4018
+ );
4019
+
4020
+ const spinner = ora('Fetching agents...').start();
4021
+ const result = await api._makeRequest('GET', 'agent/list');
4022
+ spinner.stop();
4023
+
4024
+ if (options.output === 'json') {
4025
+ console.log(JSON.stringify(result, null, 2));
4026
+ } else {
4027
+ const agents = Array.isArray(result) ? result : result.items || [];
4028
+ console.log(chalk.green.bold(`Found ${agents.length} agent(s)`));
4029
+ agents.forEach(agent => {
4030
+ console.log(chalk.cyan(` • ${agent.label || 'Unnamed'} (${agent.id})`));
4031
+ });
4032
+ }
4033
+ } catch (error) {
4034
+ console.error(chalk.red(`Error listing agents: ${error.message}`));
4035
+ process.exit(1);
4036
+ }
4037
+ });
4038
+
4039
+ program
4040
+ .command('get-agent')
4041
+ .description('Get details of a specific agent')
4042
+ .argument('<id>', 'Agent ID')
4043
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4044
+ .option('-v, --verbose', 'Show detailed information')
4045
+ .action(async (agentId, options, command) => {
4046
+ try {
4047
+ const globalOptions = command.parent.opts();
4048
+ const config = loadConfig(globalOptions.config);
4049
+ validateConfiguration(config);
4050
+
4051
+ const api = new ToothFairyAPI(
4052
+ config.baseUrl,
4053
+ config.aiUrl,
4054
+ config.aiStreamUrl,
4055
+ config.apiKey,
4056
+ config.workspaceId,
4057
+ globalOptions.verbose || options.verbose
4058
+ );
4059
+
4060
+ const spinner = ora('Fetching agent...').start();
4061
+ const result = await api._makeRequest('GET', `agent/get/${agentId}`);
4062
+ spinner.stop();
4063
+
4064
+ if (options.output === 'json') {
4065
+ console.log(JSON.stringify(result, null, 2));
4066
+ } else {
4067
+ console.log(chalk.green.bold('Agent Details'));
4068
+ console.log(chalk.dim(`ID: ${result.id}`));
4069
+ console.log(chalk.dim(`Label: ${result.label}`));
4070
+ console.log(chalk.dim(`Description: ${result.description || 'N/A'}`));
4071
+ console.log(chalk.dim(`Mode: ${result.mode || 'N/A'}`));
4072
+ }
4073
+ } catch (error) {
4074
+ console.error(chalk.red(`Error getting agent: ${error.message}`));
4075
+ process.exit(1);
4076
+ }
4077
+ });
4078
+
4079
+ program
4080
+ .command('update-agent')
4081
+ .description('Update an existing agent')
4082
+ .argument('<id>', 'Agent ID')
4083
+ .option('--label <label>', 'Agent label')
4084
+ .option('--description <description>', 'Agent description')
4085
+ .option('--emoji <emoji>', 'Agent emoji')
4086
+ .option('--mode <mode>', 'Agent mode')
4087
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4088
+ .option('-v, --verbose', 'Show detailed information')
4089
+ .action(async (agentId, options, command) => {
4090
+ try {
4091
+ const globalOptions = command.parent.opts();
4092
+ const config = loadConfig(globalOptions.config);
4093
+ validateConfiguration(config);
4094
+
4095
+ const api = new ToothFairyAPI(
4096
+ config.baseUrl,
4097
+ config.aiUrl,
4098
+ config.aiStreamUrl,
4099
+ config.apiKey,
4100
+ config.workspaceId,
4101
+ globalOptions.verbose || options.verbose
4102
+ );
4103
+
4104
+ const spinner = ora('Updating agent...').start();
4105
+ const result = await api._makeRequest('POST', `agent/update/${agentId}`, {
4106
+ label: options.label,
4107
+ description: options.description,
4108
+ emoji: options.emoji,
4109
+ mode: options.mode,
4110
+ });
4111
+ spinner.stop();
4112
+
4113
+ if (options.output === 'json') {
4114
+ console.log(JSON.stringify(result, null, 2));
4115
+ } else {
4116
+ console.log(chalk.green.bold('✅ Agent updated successfully!'));
4117
+ }
4118
+ } catch (error) {
4119
+ console.error(chalk.red(`Error updating agent: ${error.message}`));
4120
+ process.exit(1);
4121
+ }
4122
+ });
4123
+
4124
+ program
4125
+ .command('delete-agent')
4126
+ .description('Delete an agent')
4127
+ .argument('<id>', 'Agent ID')
4128
+ .option('--confirm', 'Skip confirmation prompt')
4129
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4130
+ .option('-v, --verbose', 'Show detailed information')
4131
+ .action(async (agentId, options, command) => {
4132
+ try {
4133
+ const globalOptions = command.parent.opts();
4134
+ const config = loadConfig(globalOptions.config);
4135
+ validateConfiguration(config);
4136
+
4137
+ if (!options.confirm) {
4138
+ const readline = require('readline');
4139
+ const rl = readline.createInterface({
4140
+ input: process.stdin,
4141
+ output: process.stdout,
4142
+ });
4143
+
4144
+ const answer = await new Promise((resolve) => {
4145
+ rl.question(
4146
+ chalk.yellow(`⚠️ Are you sure you want to delete agent ${agentId}? (y/N): `),
4147
+ resolve
4148
+ );
4149
+ });
4150
+ rl.close();
4151
+
4152
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
4153
+ console.log(chalk.gray('Deletion cancelled.'));
4154
+ process.exit(0);
4155
+ }
4156
+ }
4157
+
4158
+ const api = new ToothFairyAPI(
4159
+ config.baseUrl,
4160
+ config.aiUrl,
4161
+ config.aiStreamUrl,
4162
+ config.apiKey,
4163
+ config.workspaceId,
4164
+ globalOptions.verbose || options.verbose
4165
+ );
4166
+
4167
+ const spinner = ora('Deleting agent...').start();
4168
+ const result = await api._makeRequest('DELETE', `agent/delete/${agentId}`);
4169
+ spinner.stop();
4170
+
4171
+ if (options.output === 'json') {
4172
+ console.log(JSON.stringify(result, null, 2));
4173
+ } else {
4174
+ console.log(chalk.green.bold('✅ Agent deleted successfully!'));
4175
+ }
4176
+ } catch (error) {
4177
+ console.error(chalk.red(`Error deleting agent: ${error.message}`));
4178
+ process.exit(1);
4179
+ }
4180
+ });
4181
+
4182
+ // Function commands
4183
+ program
4184
+ .command('create-function')
4185
+ .description('Create a new agent function')
4186
+ .option('--name <name>', 'Function name')
4187
+ .option('--description <description>', 'Function description')
4188
+ .option('--code <code>', 'Function code')
4189
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4190
+ .option('-v, --verbose', 'Show detailed information')
4191
+ .action(async (options, command) => {
4192
+ try {
4193
+ const globalOptions = command.parent.opts();
4194
+ const config = loadConfig(globalOptions.config);
4195
+ validateConfiguration(config);
4196
+
4197
+ const api = new ToothFairyAPI(
4198
+ config.baseUrl,
4199
+ config.aiUrl,
4200
+ config.aiStreamUrl,
4201
+ config.apiKey,
4202
+ config.workspaceId,
4203
+ globalOptions.verbose || options.verbose
4204
+ );
4205
+
4206
+ const spinner = ora('Creating function...').start();
4207
+ const result = await api._makeRequest('POST', 'function/create', {
4208
+ name: options.name,
4209
+ description: options.description,
4210
+ code: options.code,
4211
+ });
4212
+ spinner.stop();
4213
+
4214
+ if (options.output === 'json') {
4215
+ console.log(JSON.stringify(result, null, 2));
4216
+ } else {
4217
+ console.log(chalk.green.bold('✅ Function created successfully!'));
4218
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
4219
+ }
4220
+ } catch (error) {
4221
+ console.error(chalk.red(`Error creating function: ${error.message}`));
4222
+ process.exit(1);
4223
+ }
4224
+ });
4225
+
4226
+ program
4227
+ .command('list-functions')
4228
+ .description('List all functions')
4229
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4230
+ .option('-v, --verbose', 'Show detailed information')
4231
+ .action(async (options, command) => {
4232
+ try {
4233
+ const globalOptions = command.parent.opts();
4234
+ const config = loadConfig(globalOptions.config);
4235
+ validateConfiguration(config);
4236
+
4237
+ const api = new ToothFairyAPI(
4238
+ config.baseUrl,
4239
+ config.aiUrl,
4240
+ config.aiStreamUrl,
4241
+ config.apiKey,
4242
+ config.workspaceId,
4243
+ globalOptions.verbose || options.verbose
4244
+ );
4245
+
4246
+ const spinner = ora('Fetching functions...').start();
4247
+ const result = await api._makeRequest('GET', 'function/list');
4248
+ spinner.stop();
4249
+
4250
+ if (options.output === 'json') {
4251
+ console.log(JSON.stringify(result, null, 2));
4252
+ } else {
4253
+ const functions = Array.isArray(result) ? result : result.items || [];
4254
+ console.log(chalk.green.bold(`Found ${functions.length} function(s)`));
4255
+ functions.forEach(fn => {
4256
+ console.log(chalk.cyan(` • ${fn.name || 'Unnamed'} (${fn.id})`));
4257
+ });
4258
+ }
4259
+ } catch (error) {
4260
+ console.error(chalk.red(`Error listing functions: ${error.message}`));
4261
+ process.exit(1);
4262
+ }
4263
+ });
4264
+
4265
+ program
4266
+ .command('get-function')
4267
+ .description('Get details of a specific function')
4268
+ .argument('<id>', 'Function ID')
4269
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4270
+ .option('-v, --verbose', 'Show detailed information')
4271
+ .action(async (functionId, options, command) => {
4272
+ try {
4273
+ const globalOptions = command.parent.opts();
4274
+ const config = loadConfig(globalOptions.config);
4275
+ validateConfiguration(config);
4276
+
4277
+ const api = new ToothFairyAPI(
4278
+ config.baseUrl,
4279
+ config.aiUrl,
4280
+ config.aiStreamUrl,
4281
+ config.apiKey,
4282
+ config.workspaceId,
4283
+ globalOptions.verbose || options.verbose
4284
+ );
4285
+
4286
+ const spinner = ora('Fetching function...').start();
4287
+ const result = await api._makeRequest('GET', `function/get/${functionId}`);
4288
+ spinner.stop();
4289
+
4290
+ if (options.output === 'json') {
4291
+ console.log(JSON.stringify(result, null, 2));
4292
+ } else {
4293
+ console.log(chalk.green.bold('Function Details'));
4294
+ console.log(chalk.dim(`ID: ${result.id}`));
4295
+ console.log(chalk.dim(`Name: ${result.name}`));
4296
+ console.log(chalk.dim(`Description: ${result.description || 'N/A'}`));
4297
+ }
4298
+ } catch (error) {
4299
+ console.error(chalk.red(`Error getting function: ${error.message}`));
4300
+ process.exit(1);
4301
+ }
4302
+ });
4303
+
4304
+ program
4305
+ .command('update-function')
4306
+ .description('Update an existing function')
4307
+ .argument('<id>', 'Function ID')
4308
+ .option('--name <name>', 'Function name')
4309
+ .option('--description <description>', 'Function description')
4310
+ .option('--code <code>', 'Function code')
4311
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4312
+ .option('-v, --verbose', 'Show detailed information')
4313
+ .action(async (functionId, options, command) => {
4314
+ try {
4315
+ const globalOptions = command.parent.opts();
4316
+ const config = loadConfig(globalOptions.config);
4317
+ validateConfiguration(config);
4318
+
4319
+ const api = new ToothFairyAPI(
4320
+ config.baseUrl,
4321
+ config.aiUrl,
4322
+ config.aiStreamUrl,
4323
+ config.apiKey,
4324
+ config.workspaceId,
4325
+ globalOptions.verbose || options.verbose
4326
+ );
4327
+
4328
+ const spinner = ora('Updating function...').start();
4329
+ const result = await api._makeRequest('POST', `function/update/${functionId}`, {
4330
+ name: options.name,
4331
+ description: options.description,
4332
+ code: options.code,
4333
+ });
4334
+ spinner.stop();
4335
+
4336
+ if (options.output === 'json') {
4337
+ console.log(JSON.stringify(result, null, 2));
4338
+ } else {
4339
+ console.log(chalk.green.bold('✅ Function updated successfully!'));
4340
+ }
4341
+ } catch (error) {
4342
+ console.error(chalk.red(`Error updating function: ${error.message}`));
4343
+ process.exit(1);
4344
+ }
4345
+ });
4346
+
4347
+ program
4348
+ .command('delete-function')
4349
+ .description('Delete a function')
4350
+ .argument('<id>', 'Function ID')
4351
+ .option('--confirm', 'Skip confirmation prompt')
4352
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4353
+ .option('-v, --verbose', 'Show detailed information')
4354
+ .action(async (functionId, options, command) => {
4355
+ try {
4356
+ const globalOptions = command.parent.opts();
4357
+ const config = loadConfig(globalOptions.config);
4358
+ validateConfiguration(config);
4359
+
4360
+ if (!options.confirm) {
4361
+ const readline = require('readline');
4362
+ const rl = readline.createInterface({
4363
+ input: process.stdin,
4364
+ output: process.stdout,
4365
+ });
4366
+
4367
+ const answer = await new Promise((resolve) => {
4368
+ rl.question(
4369
+ chalk.yellow(`⚠️ Are you sure you want to delete function ${functionId}? (y/N): `),
4370
+ resolve
4371
+ );
4372
+ });
4373
+ rl.close();
4374
+
4375
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
4376
+ console.log(chalk.gray('Deletion cancelled.'));
4377
+ process.exit(0);
4378
+ }
4379
+ }
4380
+
4381
+ const api = new ToothFairyAPI(
4382
+ config.baseUrl,
4383
+ config.aiUrl,
4384
+ config.aiStreamUrl,
4385
+ config.apiKey,
4386
+ config.workspaceId,
4387
+ globalOptions.verbose || options.verbose
4388
+ );
4389
+
4390
+ const spinner = ora('Deleting function...').start();
4391
+ const result = await api._makeRequest('DELETE', `function/delete/${functionId}`);
4392
+ spinner.stop();
4393
+
4394
+ if (options.output === 'json') {
4395
+ console.log(JSON.stringify(result, null, 2));
4396
+ } else {
4397
+ console.log(chalk.green.bold('✅ Function deleted successfully!'));
4398
+ }
4399
+ } catch (error) {
4400
+ console.error(chalk.red(`Error deleting function: ${error.message}`));
4401
+ process.exit(1);
4402
+ }
4403
+ });
4404
+
4405
+ // Channel commands
4406
+ program
4407
+ .command('create-channel')
4408
+ .description('Create a new channel')
4409
+ .option('--name <name>', 'Channel name')
4410
+ .option('--type <type>', 'Channel type')
4411
+ .option('--config <config>', 'Channel configuration (JSON)')
4412
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4413
+ .option('-v, --verbose', 'Show detailed information')
4414
+ .action(async (options, command) => {
4415
+ try {
4416
+ const globalOptions = command.parent.opts();
4417
+ const config = loadConfig(globalOptions.config);
4418
+ validateConfiguration(config);
4419
+
4420
+ const api = new ToothFairyAPI(
4421
+ config.baseUrl,
4422
+ config.aiUrl,
4423
+ config.aiStreamUrl,
4424
+ config.apiKey,
4425
+ config.workspaceId,
4426
+ globalOptions.verbose || options.verbose
4427
+ );
4428
+
4429
+ let channelConfig = {};
4430
+ if (options.config) {
4431
+ try {
4432
+ channelConfig = JSON.parse(options.config);
4433
+ } catch (e) {
4434
+ console.error(chalk.red('Invalid JSON in config'));
4435
+ process.exit(1);
4436
+ }
4437
+ }
4438
+
4439
+ const spinner = ora('Creating channel...').start();
4440
+ const result = await api._makeRequest('POST', 'channel/create', {
4441
+ name: options.name,
4442
+ type: options.type,
4443
+ config: channelConfig,
4444
+ });
4445
+ spinner.stop();
4446
+
4447
+ if (options.output === 'json') {
4448
+ console.log(JSON.stringify(result, null, 2));
4449
+ } else {
4450
+ console.log(chalk.green.bold('✅ Channel created successfully!'));
4451
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
4452
+ }
4453
+ } catch (error) {
4454
+ console.error(chalk.red(`Error creating channel: ${error.message}`));
4455
+ process.exit(1);
4456
+ }
4457
+ });
4458
+
4459
+ program
4460
+ .command('list-channels')
4461
+ .description('List all channels')
4462
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4463
+ .option('-v, --verbose', 'Show detailed information')
4464
+ .action(async (options, command) => {
4465
+ try {
4466
+ const globalOptions = command.parent.opts();
4467
+ const config = loadConfig(globalOptions.config);
4468
+ validateConfiguration(config);
4469
+
4470
+ const api = new ToothFairyAPI(
4471
+ config.baseUrl,
4472
+ config.aiUrl,
4473
+ config.aiStreamUrl,
4474
+ config.apiKey,
4475
+ config.workspaceId,
4476
+ globalOptions.verbose || options.verbose
4477
+ );
4478
+
4479
+ const spinner = ora('Fetching channels...').start();
4480
+ const result = await api._makeRequest('GET', 'channel/list');
4481
+ spinner.stop();
4482
+
4483
+ if (options.output === 'json') {
4484
+ console.log(JSON.stringify(result, null, 2));
4485
+ } else {
4486
+ const channels = Array.isArray(result) ? result : result.items || [];
4487
+ console.log(chalk.green.bold(`Found ${channels.length} channel(s)`));
4488
+ channels.forEach(ch => {
4489
+ console.log(chalk.cyan(` • ${ch.name || 'Unnamed'} (${ch.id})`));
4490
+ });
4491
+ }
4492
+ } catch (error) {
4493
+ console.error(chalk.red(`Error listing channels: ${error.message}`));
4494
+ process.exit(1);
4495
+ }
4496
+ });
4497
+
4498
+ program
4499
+ .command('get-channel')
4500
+ .description('Get details of a specific channel')
4501
+ .argument('<id>', 'Channel ID')
4502
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4503
+ .option('-v, --verbose', 'Show detailed information')
4504
+ .action(async (channelId, options, command) => {
4505
+ try {
4506
+ const globalOptions = command.parent.opts();
4507
+ const config = loadConfig(globalOptions.config);
4508
+ validateConfiguration(config);
4509
+
4510
+ const api = new ToothFairyAPI(
4511
+ config.baseUrl,
4512
+ config.aiUrl,
4513
+ config.aiStreamUrl,
4514
+ config.apiKey,
4515
+ config.workspaceId,
4516
+ globalOptions.verbose || options.verbose
4517
+ );
4518
+
4519
+ const spinner = ora('Fetching channel...').start();
4520
+ const result = await api._makeRequest('GET', `channel/get/${channelId}`);
4521
+ spinner.stop();
4522
+
4523
+ if (options.output === 'json') {
4524
+ console.log(JSON.stringify(result, null, 2));
4525
+ } else {
4526
+ console.log(chalk.green.bold('Channel Details'));
4527
+ console.log(chalk.dim(`ID: ${result.id}`));
4528
+ console.log(chalk.dim(`Name: ${result.name}`));
4529
+ console.log(chalk.dim(`Type: ${result.type || 'N/A'}`));
4530
+ }
4531
+ } catch (error) {
4532
+ console.error(chalk.red(`Error getting channel: ${error.message}`));
4533
+ process.exit(1);
4534
+ }
4535
+ });
4536
+
4537
+ program
4538
+ .command('update-channel')
4539
+ .description('Update an existing channel')
4540
+ .argument('<id>', 'Channel ID')
4541
+ .option('--name <name>', 'Channel name')
4542
+ .option('--type <type>', 'Channel type')
4543
+ .option('--config <config>', 'Channel configuration (JSON)')
4544
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4545
+ .option('-v, --verbose', 'Show detailed information')
4546
+ .action(async (channelId, options, command) => {
4547
+ try {
4548
+ const globalOptions = command.parent.opts();
4549
+ const config = loadConfig(globalOptions.config);
4550
+ validateConfiguration(config);
4551
+
4552
+ const api = new ToothFairyAPI(
4553
+ config.baseUrl,
4554
+ config.aiUrl,
4555
+ config.aiStreamUrl,
4556
+ config.apiKey,
4557
+ config.workspaceId,
4558
+ globalOptions.verbose || options.verbose
4559
+ );
4560
+
4561
+ let channelConfig = {};
4562
+ if (options.config) {
4563
+ try {
4564
+ channelConfig = JSON.parse(options.config);
4565
+ } catch (e) {
4566
+ console.error(chalk.red('Invalid JSON in config'));
4567
+ process.exit(1);
4568
+ }
4569
+ }
4570
+
4571
+ const spinner = ora('Updating channel...').start();
4572
+ const result = await api._makeRequest('POST', 'channel/update', {
4573
+ id: channelId,
4574
+ name: options.name,
4575
+ type: options.type,
4576
+ config: channelConfig,
4577
+ });
4578
+ spinner.stop();
4579
+
4580
+ if (options.output === 'json') {
4581
+ console.log(JSON.stringify(result, null, 2));
4582
+ } else {
4583
+ console.log(chalk.green.bold('✅ Channel updated successfully!'));
4584
+ }
4585
+ } catch (error) {
4586
+ console.error(chalk.red(`Error updating channel: ${error.message}`));
4587
+ process.exit(1);
4588
+ }
4589
+ });
4590
+
4591
+ program
4592
+ .command('delete-channel')
4593
+ .description('Delete a channel')
4594
+ .argument('<id>', 'Channel ID')
4595
+ .option('--confirm', 'Skip confirmation prompt')
4596
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4597
+ .option('-v, --verbose', 'Show detailed information')
4598
+ .action(async (channelId, options, command) => {
4599
+ try {
4600
+ const globalOptions = command.parent.opts();
4601
+ const config = loadConfig(globalOptions.config);
4602
+ validateConfiguration(config);
4603
+
4604
+ if (!options.confirm) {
4605
+ const readline = require('readline');
4606
+ const rl = readline.createInterface({
4607
+ input: process.stdin,
4608
+ output: process.stdout,
4609
+ });
4610
+
4611
+ const answer = await new Promise((resolve) => {
4612
+ rl.question(
4613
+ chalk.yellow(`⚠️ Are you sure you want to delete channel ${channelId}? (y/N): `),
4614
+ resolve
4615
+ );
4616
+ });
4617
+ rl.close();
4618
+
4619
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
4620
+ console.log(chalk.gray('Deletion cancelled.'));
4621
+ process.exit(0);
4622
+ }
4623
+ }
4624
+
4625
+ const api = new ToothFairyAPI(
4626
+ config.baseUrl,
4627
+ config.aiUrl,
4628
+ config.aiStreamUrl,
4629
+ config.apiKey,
4630
+ config.workspaceId,
4631
+ globalOptions.verbose || options.verbose
4632
+ );
4633
+
4634
+ const spinner = ora('Deleting channel...').start();
4635
+ const result = await api._makeRequest('DELETE', `channel/delete/${channelId}`);
4636
+ spinner.stop();
4637
+
4638
+ if (options.output === 'json') {
4639
+ console.log(JSON.stringify(result, null, 2));
4640
+ } else {
4641
+ console.log(chalk.green.bold('✅ Channel deleted successfully!'));
4642
+ }
4643
+ } catch (error) {
4644
+ console.error(chalk.red(`Error deleting channel: ${error.message}`));
4645
+ process.exit(1);
4646
+ }
4647
+ });
4648
+
4649
+ // Billing command
4650
+ program
4651
+ .command('billing')
4652
+ .description('Get monthly billing costs')
4653
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4654
+ .option('-v, --verbose', 'Show detailed information')
4655
+ .action(async (options, command) => {
4656
+ try {
4657
+ const globalOptions = command.parent.opts();
4658
+ const config = loadConfig(globalOptions.config);
4659
+ validateConfiguration(config);
4660
+
4661
+ const api = new ToothFairyAPI(
4662
+ config.baseUrl,
4663
+ config.aiUrl,
4664
+ config.aiStreamUrl,
4665
+ config.apiKey,
4666
+ config.workspaceId,
4667
+ globalOptions.verbose || options.verbose
4668
+ );
4669
+
4670
+ const spinner = ora('Fetching billing information...').start();
4671
+ const result = await api._makeRequest('GET', 'billing/monthCosts');
4672
+ spinner.stop();
4673
+
4674
+ if (options.output === 'json') {
4675
+ console.log(JSON.stringify(result, null, 2));
4676
+ } else {
4677
+ console.log(chalk.green.bold('💰 Monthly Billing'));
4678
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
4679
+ }
4680
+ } catch (error) {
4681
+ console.error(chalk.red(`Error fetching billing: ${error.message}`));
4682
+ process.exit(1);
4683
+ }
4684
+ });
4685
+
4686
+ // Embedding command
4687
+ program
4688
+ .command('get-embedding')
4689
+ .description('Get embedding for text')
4690
+ .argument('<text>', 'Text to get embedding for')
4691
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4692
+ .option('-v, --verbose', 'Show detailed information')
4693
+ .action(async (text, options, command) => {
4694
+ try {
4695
+ const globalOptions = command.parent.opts();
4696
+ const config = loadConfig(globalOptions.config);
4697
+ validateConfiguration(config);
4698
+
4699
+ const api = new ToothFairyAPI(
4700
+ config.baseUrl,
4701
+ config.aiUrl,
4702
+ config.aiStreamUrl,
4703
+ config.apiKey,
4704
+ config.workspaceId,
4705
+ globalOptions.verbose || options.verbose
4706
+ );
4707
+
4708
+ const spinner = ora('Generating embedding...').start();
4709
+ const result = await api._makeRequest('GET', 'embedding/get', { text });
4710
+ spinner.stop();
4711
+
4712
+ if (options.output === 'json') {
4713
+ console.log(JSON.stringify(result, null, 2));
4714
+ } else {
4715
+ console.log(chalk.green.bold('🔢 Embedding Generated'));
4716
+ console.log(chalk.dim(`Dimensions: ${result.embedding?.length || 'N/A'}`));
4717
+ }
4718
+ } catch (error) {
4719
+ console.error(chalk.red(`Error getting embedding: ${error.message}`));
4720
+ process.exit(1);
4721
+ }
4722
+ });
4723
+
4724
+ // Dictionary commands
4725
+ program
4726
+ .command('get-dictionary')
4727
+ .description('Get dictionary entry')
4728
+ .argument('<id>', 'Dictionary ID')
4729
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4730
+ .option('-v, --verbose', 'Show detailed information')
4731
+ .action(async (dictId, options, command) => {
4732
+ try {
4733
+ const globalOptions = command.parent.opts();
4734
+ const config = loadConfig(globalOptions.config);
4735
+ validateConfiguration(config);
4736
+
4737
+ const api = new ToothFairyAPI(
4738
+ config.baseUrl,
4739
+ config.aiUrl,
4740
+ config.aiStreamUrl,
4741
+ config.apiKey,
4742
+ config.workspaceId,
4743
+ globalOptions.verbose || options.verbose
4744
+ );
4745
+
4746
+ const spinner = ora('Fetching dictionary...').start();
4747
+ const result = await api._makeRequest('GET', `dictionary/get/${dictId}`);
4748
+ spinner.stop();
4749
+
4750
+ if (options.output === 'json') {
4751
+ console.log(JSON.stringify(result, null, 2));
4752
+ } else {
4753
+ console.log(chalk.green.bold('Dictionary Entry'));
4754
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
4755
+ }
4756
+ } catch (error) {
4757
+ console.error(chalk.red(`Error getting dictionary: ${error.message}`));
4758
+ process.exit(1);
4759
+ }
4760
+ });
4761
+
4762
+ program
4763
+ .command('list-dictionaries')
4764
+ .description('List all dictionaries')
4765
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4766
+ .option('-v, --verbose', 'Show detailed information')
4767
+ .action(async (options, command) => {
4768
+ try {
4769
+ const globalOptions = command.parent.opts();
4770
+ const config = loadConfig(globalOptions.config);
4771
+ validateConfiguration(config);
4772
+
4773
+ const api = new ToothFairyAPI(
4774
+ config.baseUrl,
4775
+ config.aiUrl,
4776
+ config.aiStreamUrl,
4777
+ config.apiKey,
4778
+ config.workspaceId,
4779
+ globalOptions.verbose || options.verbose
4780
+ );
4781
+
4782
+ const spinner = ora('Fetching dictionaries...').start();
4783
+ const result = await api._makeRequest('GET', 'dictionary/list');
4784
+ spinner.stop();
4785
+
4786
+ if (options.output === 'json') {
4787
+ console.log(JSON.stringify(result, null, 2));
4788
+ } else {
4789
+ const dicts = Array.isArray(result) ? result : result.items || [];
4790
+ console.log(chalk.green.bold(`Found ${dicts.length} dictionary/ies`));
4791
+ dicts.forEach(d => {
4792
+ console.log(chalk.cyan(` • ${d.id}`));
4793
+ });
4794
+ }
4795
+ } catch (error) {
4796
+ console.error(chalk.red(`Error listing dictionaries: ${error.message}`));
4797
+ process.exit(1);
4798
+ }
4799
+ });
4800
+
4801
+ // Connection commands
4802
+ program
4803
+ .command('list-connections')
4804
+ .description('List all connections')
4805
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4806
+ .option('-v, --verbose', 'Show detailed information')
4807
+ .action(async (options, command) => {
4808
+ try {
4809
+ const globalOptions = command.parent.opts();
4810
+ const config = loadConfig(globalOptions.config);
4811
+ validateConfiguration(config);
4812
+
4813
+ const api = new ToothFairyAPI(
4814
+ config.baseUrl,
4815
+ config.aiUrl,
4816
+ config.aiStreamUrl,
4817
+ config.apiKey,
4818
+ config.workspaceId,
4819
+ globalOptions.verbose || options.verbose
4820
+ );
4821
+
4822
+ const spinner = ora('Fetching connections...').start();
4823
+ const result = await api._makeRequest('GET', 'connection/list');
4824
+ spinner.stop();
4825
+
4826
+ if (options.output === 'json') {
4827
+ console.log(JSON.stringify(result, null, 2));
4828
+ } else {
4829
+ const connections = Array.isArray(result) ? result : result.items || [];
4830
+ console.log(chalk.green.bold(`Found ${connections.length} connection(s)`));
4831
+ connections.forEach(c => {
4832
+ console.log(chalk.cyan(` • ${c.id}`));
4833
+ });
4834
+ }
4835
+ } catch (error) {
4836
+ console.error(chalk.red(`Error listing connections: ${error.message}`));
4837
+ process.exit(1);
4838
+ }
4839
+ });
4840
+
4841
+ program
4842
+ .command('get-connection')
4843
+ .description('Get details of a specific connection')
4844
+ .argument('<id>', 'Connection ID')
4845
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4846
+ .option('-v, --verbose', 'Show detailed information')
4847
+ .action(async (connectionId, options, command) => {
4848
+ try {
4849
+ const globalOptions = command.parent.opts();
4850
+ const config = loadConfig(globalOptions.config);
4851
+ validateConfiguration(config);
4852
+
4853
+ const api = new ToothFairyAPI(
4854
+ config.baseUrl,
4855
+ config.aiUrl,
4856
+ config.aiStreamUrl,
4857
+ config.apiKey,
4858
+ config.workspaceId,
4859
+ globalOptions.verbose || options.verbose
4860
+ );
4861
+
4862
+ const spinner = ora('Fetching connection...').start();
4863
+ const result = await api._makeRequest('GET', `connection/get/${connectionId}`);
4864
+ spinner.stop();
4865
+
4866
+ if (options.output === 'json') {
4867
+ console.log(JSON.stringify(result, null, 2));
4868
+ } else {
4869
+ console.log(chalk.green.bold('Connection Details'));
4870
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
4871
+ }
4872
+ } catch (error) {
4873
+ console.error(chalk.red(`Error getting connection: ${error.message}`));
4874
+ process.exit(1);
4875
+ }
4876
+ });
4877
+
4878
+ program
4879
+ .command('delete-connection')
4880
+ .description('Delete a connection')
4881
+ .argument('<id>', 'Connection ID')
4882
+ .option('--confirm', 'Skip confirmation prompt')
4883
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4884
+ .option('-v, --verbose', 'Show detailed information')
4885
+ .action(async (connectionId, options, command) => {
4886
+ try {
4887
+ const globalOptions = command.parent.opts();
4888
+ const config = loadConfig(globalOptions.config);
4889
+ validateConfiguration(config);
4890
+
4891
+ if (!options.confirm) {
4892
+ const readline = require('readline');
4893
+ const rl = readline.createInterface({
4894
+ input: process.stdin,
4895
+ output: process.stdout,
4896
+ });
4897
+
4898
+ const answer = await new Promise((resolve) => {
4899
+ rl.question(
4900
+ chalk.yellow(`⚠️ Are you sure you want to delete connection ${connectionId}? (y/N): `),
4901
+ resolve
4902
+ );
4903
+ });
4904
+ rl.close();
4905
+
4906
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
4907
+ console.log(chalk.gray('Deletion cancelled.'));
4908
+ process.exit(0);
4909
+ }
4910
+ }
4911
+
4912
+ const api = new ToothFairyAPI(
4913
+ config.baseUrl,
4914
+ config.aiUrl,
4915
+ config.aiStreamUrl,
4916
+ config.apiKey,
4917
+ config.workspaceId,
4918
+ globalOptions.verbose || options.verbose
4919
+ );
4920
+
4921
+ const spinner = ora('Deleting connection...').start();
4922
+ const result = await api._makeRequest('DELETE', `connection/delete/${connectionId}`);
4923
+ spinner.stop();
4924
+
4925
+ if (options.output === 'json') {
4926
+ console.log(JSON.stringify(result, null, 2));
4927
+ } else {
4928
+ console.log(chalk.green.bold('✅ Connection deleted successfully!'));
4929
+ }
4930
+ } catch (error) {
4931
+ console.error(chalk.red(`Error deleting connection: ${error.message}`));
4932
+ process.exit(1);
4933
+ }
4934
+ });
4935
+
4936
+ // Member commands
4937
+ program
4938
+ .command('list-members')
4939
+ .description('List all members')
4940
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4941
+ .option('-v, --verbose', 'Show detailed information')
4942
+ .action(async (options, command) => {
4943
+ try {
4944
+ const globalOptions = command.parent.opts();
4945
+ const config = loadConfig(globalOptions.config);
4946
+ validateConfiguration(config);
4947
+
4948
+ const api = new ToothFairyAPI(
4949
+ config.baseUrl,
4950
+ config.aiUrl,
4951
+ config.aiStreamUrl,
4952
+ config.apiKey,
4953
+ config.workspaceId,
4954
+ globalOptions.verbose || options.verbose
4955
+ );
4956
+
4957
+ const spinner = ora('Fetching members...').start();
4958
+ const result = await api._makeRequest('GET', 'member/list');
4959
+ spinner.stop();
4960
+
4961
+ if (options.output === 'json') {
4962
+ console.log(JSON.stringify(result, null, 2));
4963
+ } else {
4964
+ const members = Array.isArray(result) ? result : result.items || [];
4965
+ console.log(chalk.green.bold(`Found ${members.length} member(s)`));
4966
+ members.forEach(m => {
4967
+ console.log(chalk.cyan(` • ${m.name || m.email || m.id}`));
4968
+ });
4969
+ }
4970
+ } catch (error) {
4971
+ console.error(chalk.red(`Error listing members: ${error.message}`));
4972
+ process.exit(1);
4973
+ }
4974
+ });
4975
+
4976
+ program
4977
+ .command('get-member')
4978
+ .description('Get details of a specific member')
4979
+ .argument('<id>', 'Member ID')
4980
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
4981
+ .option('-v, --verbose', 'Show detailed information')
4982
+ .action(async (memberId, options, command) => {
4983
+ try {
4984
+ const globalOptions = command.parent.opts();
4985
+ const config = loadConfig(globalOptions.config);
4986
+ validateConfiguration(config);
4987
+
4988
+ const api = new ToothFairyAPI(
4989
+ config.baseUrl,
4990
+ config.aiUrl,
4991
+ config.aiStreamUrl,
4992
+ config.apiKey,
4993
+ config.workspaceId,
4994
+ globalOptions.verbose || options.verbose
4995
+ );
4996
+
4997
+ const spinner = ora('Fetching member...').start();
4998
+ const result = await api._makeRequest('GET', `member/get/${memberId}`);
4999
+ spinner.stop();
5000
+
5001
+ if (options.output === 'json') {
5002
+ console.log(JSON.stringify(result, null, 2));
5003
+ } else {
5004
+ console.log(chalk.green.bold('Member Details'));
5005
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
5006
+ }
5007
+ } catch (error) {
5008
+ console.error(chalk.red(`Error getting member: ${error.message}`));
5009
+ process.exit(1);
5010
+ }
5011
+ });
5012
+
5013
+ program
5014
+ .command('update-member')
5015
+ .description('Update a member')
5016
+ .argument('<id>', 'Member ID')
5017
+ .option('--name <name>', 'Member name')
5018
+ .option('--email <email>', 'Member email')
5019
+ .option('--role <role>', 'Member role')
5020
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5021
+ .option('-v, --verbose', 'Show detailed information')
5022
+ .action(async (memberId, options, command) => {
5023
+ try {
5024
+ const globalOptions = command.parent.opts();
5025
+ const config = loadConfig(globalOptions.config);
5026
+ validateConfiguration(config);
5027
+
5028
+ const api = new ToothFairyAPI(
5029
+ config.baseUrl,
5030
+ config.aiUrl,
5031
+ config.aiStreamUrl,
5032
+ config.apiKey,
5033
+ config.workspaceId,
5034
+ globalOptions.verbose || options.verbose
5035
+ );
5036
+
5037
+ const spinner = ora('Updating member...').start();
5038
+ const result = await api._makeRequest('POST', 'member/update', {
5039
+ id: memberId,
5040
+ name: options.name,
5041
+ email: options.email,
5042
+ role: options.role,
5043
+ });
5044
+ spinner.stop();
5045
+
5046
+ if (options.output === 'json') {
5047
+ console.log(JSON.stringify(result, null, 2));
5048
+ } else {
5049
+ console.log(chalk.green.bold('✅ Member updated successfully!'));
5050
+ }
5051
+ } catch (error) {
5052
+ console.error(chalk.red(`Error updating member: ${error.message}`));
5053
+ process.exit(1);
5054
+ }
5055
+ });
5056
+
5057
+ program
5058
+ .command('delete-member')
5059
+ .description('Delete a member')
5060
+ .argument('<id>', 'Member ID')
5061
+ .option('--confirm', 'Skip confirmation prompt')
5062
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5063
+ .option('-v, --verbose', 'Show detailed information')
5064
+ .action(async (memberId, options, command) => {
5065
+ try {
5066
+ const globalOptions = command.parent.opts();
5067
+ const config = loadConfig(globalOptions.config);
5068
+ validateConfiguration(config);
5069
+
5070
+ if (!options.confirm) {
5071
+ const readline = require('readline');
5072
+ const rl = readline.createInterface({
5073
+ input: process.stdin,
5074
+ output: process.stdout,
5075
+ });
5076
+
5077
+ const answer = await new Promise((resolve) => {
5078
+ rl.question(
5079
+ chalk.yellow(`⚠️ Are you sure you want to delete member ${memberId}? (y/N): `),
5080
+ resolve
5081
+ );
5082
+ });
5083
+ rl.close();
5084
+
5085
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
5086
+ console.log(chalk.gray('Deletion cancelled.'));
5087
+ process.exit(0);
5088
+ }
5089
+ }
5090
+
5091
+ const api = new ToothFairyAPI(
5092
+ config.baseUrl,
5093
+ config.aiUrl,
5094
+ config.aiStreamUrl,
5095
+ config.apiKey,
5096
+ config.workspaceId,
5097
+ globalOptions.verbose || options.verbose
5098
+ );
5099
+
5100
+ const spinner = ora('Deleting member...').start();
5101
+ const result = await api._makeRequest('DELETE', `member/delete/${memberId}`);
5102
+ spinner.stop();
5103
+
5104
+ if (options.output === 'json') {
5105
+ console.log(JSON.stringify(result, null, 2));
5106
+ } else {
5107
+ console.log(chalk.green.bold('✅ Member deleted successfully!'));
5108
+ }
5109
+ } catch (error) {
5110
+ console.error(chalk.red(`Error deleting member: ${error.message}`));
5111
+ process.exit(1);
5112
+ }
5113
+ });
5114
+
5115
+ // Site commands
5116
+ program
5117
+ .command('list-sites')
5118
+ .description('List all sites')
5119
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5120
+ .option('-v, --verbose', 'Show detailed information')
5121
+ .action(async (options, command) => {
5122
+ try {
5123
+ const globalOptions = command.parent.opts();
5124
+ const config = loadConfig(globalOptions.config);
5125
+ validateConfiguration(config);
5126
+
5127
+ const api = new ToothFairyAPI(
5128
+ config.baseUrl,
5129
+ config.aiUrl,
5130
+ config.aiStreamUrl,
5131
+ config.apiKey,
5132
+ config.workspaceId,
5133
+ globalOptions.verbose || options.verbose
5134
+ );
5135
+
5136
+ const spinner = ora('Fetching sites...').start();
5137
+ const result = await api._makeRequest('GET', 'site/list');
5138
+ spinner.stop();
5139
+
5140
+ if (options.output === 'json') {
5141
+ console.log(JSON.stringify(result, null, 2));
5142
+ } else {
5143
+ const sites = Array.isArray(result) ? result : result.items || [];
5144
+ console.log(chalk.green.bold(`Found ${sites.length} site(s)`));
5145
+ sites.forEach(s => {
5146
+ console.log(chalk.cyan(` • ${s.name || s.id}`));
5147
+ });
5148
+ }
5149
+ } catch (error) {
5150
+ console.error(chalk.red(`Error listing sites: ${error.message}`));
5151
+ process.exit(1);
5152
+ }
5153
+ });
5154
+
5155
+ program
5156
+ .command('get-site')
5157
+ .description('Get details of a specific site')
5158
+ .argument('<id>', 'Site ID')
5159
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5160
+ .option('-v, --verbose', 'Show detailed information')
5161
+ .action(async (siteId, options, command) => {
5162
+ try {
5163
+ const globalOptions = command.parent.opts();
5164
+ const config = loadConfig(globalOptions.config);
5165
+ validateConfiguration(config);
5166
+
5167
+ const api = new ToothFairyAPI(
5168
+ config.baseUrl,
5169
+ config.aiUrl,
5170
+ config.aiStreamUrl,
5171
+ config.apiKey,
5172
+ config.workspaceId,
5173
+ globalOptions.verbose || options.verbose
5174
+ );
5175
+
5176
+ const spinner = ora('Fetching site...').start();
5177
+ const result = await api._makeRequest('GET', `site/get/${siteId}`);
5178
+ spinner.stop();
5179
+
5180
+ if (options.output === 'json') {
5181
+ console.log(JSON.stringify(result, null, 2));
5182
+ } else {
5183
+ console.log(chalk.green.bold('Site Details'));
5184
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
5185
+ }
5186
+ } catch (error) {
5187
+ console.error(chalk.red(`Error getting site: ${error.message}`));
5188
+ process.exit(1);
5189
+ }
5190
+ });
5191
+
5192
+ program
5193
+ .command('update-site')
5194
+ .description('Update a site')
5195
+ .argument('<id>', 'Site ID')
5196
+ .option('--name <name>', 'Site name')
5197
+ .option('--url <url>', 'Site URL')
5198
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5199
+ .option('-v, --verbose', 'Show detailed information')
5200
+ .action(async (siteId, options, command) => {
5201
+ try {
5202
+ const globalOptions = command.parent.opts();
5203
+ const config = loadConfig(globalOptions.config);
5204
+ validateConfiguration(config);
5205
+
5206
+ const api = new ToothFairyAPI(
5207
+ config.baseUrl,
5208
+ config.aiUrl,
5209
+ config.aiStreamUrl,
5210
+ config.apiKey,
5211
+ config.workspaceId,
5212
+ globalOptions.verbose || options.verbose
5213
+ );
5214
+
5215
+ const spinner = ora('Updating site...').start();
5216
+ const result = await api._makeRequest('POST', 'site/update', {
5217
+ id: siteId,
5218
+ name: options.name,
5219
+ url: options.url,
5220
+ });
5221
+ spinner.stop();
5222
+
5223
+ if (options.output === 'json') {
5224
+ console.log(JSON.stringify(result, null, 2));
5225
+ } else {
5226
+ console.log(chalk.green.bold('✅ Site updated successfully!'));
5227
+ }
5228
+ } catch (error) {
5229
+ console.error(chalk.red(`Error updating site: ${error.message}`));
5230
+ process.exit(1);
5231
+ }
5232
+ });
5233
+
5234
+ program
5235
+ .command('delete-site')
5236
+ .description('Delete a site')
5237
+ .argument('<id>', 'Site ID')
5238
+ .option('--confirm', 'Skip confirmation prompt')
5239
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5240
+ .option('-v, --verbose', 'Show detailed information')
5241
+ .action(async (siteId, options, command) => {
5242
+ try {
5243
+ const globalOptions = command.parent.opts();
5244
+ const config = loadConfig(globalOptions.config);
5245
+ validateConfiguration(config);
5246
+
5247
+ if (!options.confirm) {
5248
+ const readline = require('readline');
5249
+ const rl = readline.createInterface({
5250
+ input: process.stdin,
5251
+ output: process.stdout,
5252
+ });
5253
+
5254
+ const answer = await new Promise((resolve) => {
5255
+ rl.question(
5256
+ chalk.yellow(`⚠️ Are you sure you want to delete site ${siteId}? (y/N): `),
5257
+ resolve
5258
+ );
5259
+ });
5260
+ rl.close();
5261
+
5262
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
5263
+ console.log(chalk.gray('Deletion cancelled.'));
5264
+ process.exit(0);
5265
+ }
5266
+ }
5267
+
5268
+ const api = new ToothFairyAPI(
5269
+ config.baseUrl,
5270
+ config.aiUrl,
5271
+ config.aiStreamUrl,
5272
+ config.apiKey,
5273
+ config.workspaceId,
5274
+ globalOptions.verbose || options.verbose
5275
+ );
5276
+
5277
+ const spinner = ora('Deleting site...').start();
5278
+ const result = await api._makeRequest('DELETE', `site/delete/${siteId}`);
5279
+ spinner.stop();
5280
+
5281
+ if (options.output === 'json') {
5282
+ console.log(JSON.stringify(result, null, 2));
5283
+ } else {
5284
+ console.log(chalk.green.bold('✅ Site deleted successfully!'));
5285
+ }
5286
+ } catch (error) {
5287
+ console.error(chalk.red(`Error deleting site: ${error.message}`));
5288
+ process.exit(1);
5289
+ }
5290
+ });
5291
+
5292
+ // Stream commands
5293
+ program
5294
+ .command('list-streams')
5295
+ .description('List all streams')
5296
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5297
+ .option('-v, --verbose', 'Show detailed information')
5298
+ .action(async (options, command) => {
5299
+ try {
5300
+ const globalOptions = command.parent.opts();
5301
+ const config = loadConfig(globalOptions.config);
5302
+ validateConfiguration(config);
5303
+
5304
+ const api = new ToothFairyAPI(
5305
+ config.baseUrl,
5306
+ config.aiUrl,
5307
+ config.aiStreamUrl,
5308
+ config.apiKey,
5309
+ config.workspaceId,
5310
+ globalOptions.verbose || options.verbose
5311
+ );
5312
+
5313
+ const spinner = ora('Fetching streams...').start();
5314
+ const result = await api._makeRequest('GET', 'stream/list');
5315
+ spinner.stop();
5316
+
5317
+ if (options.output === 'json') {
5318
+ console.log(JSON.stringify(result, null, 2));
5319
+ } else {
5320
+ const streams = Array.isArray(result) ? result : result.items || [];
5321
+ console.log(chalk.green.bold(`Found ${streams.length} stream(s)`));
5322
+ streams.forEach(s => {
5323
+ console.log(chalk.cyan(` • ${s.id}`));
5324
+ });
5325
+ }
5326
+ } catch (error) {
5327
+ console.error(chalk.red(`Error listing streams: ${error.message}`));
5328
+ process.exit(1);
5329
+ }
5330
+ });
5331
+
5332
+ program
5333
+ .command('get-stream')
5334
+ .description('Get details of a specific stream')
5335
+ .argument('<id>', 'Stream ID')
5336
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5337
+ .option('-v, --verbose', 'Show detailed information')
5338
+ .action(async (streamId, options, command) => {
5339
+ try {
5340
+ const globalOptions = command.parent.opts();
5341
+ const config = loadConfig(globalOptions.config);
5342
+ validateConfiguration(config);
5343
+
5344
+ const api = new ToothFairyAPI(
5345
+ config.baseUrl,
5346
+ config.aiUrl,
5347
+ config.aiStreamUrl,
5348
+ config.apiKey,
5349
+ config.workspaceId,
5350
+ globalOptions.verbose || options.verbose
5351
+ );
5352
+
5353
+ const spinner = ora('Fetching stream...').start();
5354
+ const result = await api._makeRequest('GET', `stream/get/${streamId}`);
5355
+ spinner.stop();
5356
+
5357
+ if (options.output === 'json') {
5358
+ console.log(JSON.stringify(result, null, 2));
5359
+ } else {
5360
+ console.log(chalk.green.bold('Stream Details'));
5361
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
5362
+ }
5363
+ } catch (error) {
5364
+ console.error(chalk.red(`Error getting stream: ${error.message}`));
5365
+ process.exit(1);
5366
+ }
5367
+ });
5368
+
5369
+ // Request commands
5370
+ program
5371
+ .command('list-requests')
5372
+ .description('List all requests')
5373
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5374
+ .option('-v, --verbose', 'Show detailed information')
5375
+ .action(async (options, command) => {
5376
+ try {
5377
+ const globalOptions = command.parent.opts();
5378
+ const config = loadConfig(globalOptions.config);
5379
+ validateConfiguration(config);
5380
+
5381
+ const api = new ToothFairyAPI(
5382
+ config.baseUrl,
5383
+ config.aiUrl,
5384
+ config.aiStreamUrl,
5385
+ config.apiKey,
5386
+ config.workspaceId,
5387
+ globalOptions.verbose || options.verbose
5388
+ );
5389
+
5390
+ const spinner = ora('Fetching requests...').start();
5391
+ const result = await api._makeRequest('GET', 'request/list');
5392
+ spinner.stop();
5393
+
5394
+ if (options.output === 'json') {
5395
+ console.log(JSON.stringify(result, null, 2));
5396
+ } else {
5397
+ const requests = Array.isArray(result) ? result : result.items || [];
5398
+ console.log(chalk.green.bold(`Found ${requests.length} request(s)`));
5399
+ requests.forEach(r => {
5400
+ console.log(chalk.cyan(` • ${r.id}`));
5401
+ });
5402
+ }
5403
+ } catch (error) {
5404
+ console.error(chalk.red(`Error listing requests: ${error.message}`));
5405
+ process.exit(1);
5406
+ }
5407
+ });
5408
+
5409
+ program
5410
+ .command('get-request')
5411
+ .description('Get details of a specific request')
5412
+ .argument('<id>', 'Request ID')
5413
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5414
+ .option('-v, --verbose', 'Show detailed information')
5415
+ .action(async (requestId, options, command) => {
5416
+ try {
5417
+ const globalOptions = command.parent.opts();
5418
+ const config = loadConfig(globalOptions.config);
5419
+ validateConfiguration(config);
5420
+
5421
+ const api = new ToothFairyAPI(
5422
+ config.baseUrl,
5423
+ config.aiUrl,
5424
+ config.aiStreamUrl,
5425
+ config.apiKey,
5426
+ config.workspaceId,
5427
+ globalOptions.verbose || options.verbose
5428
+ );
5429
+
5430
+ const spinner = ora('Fetching request...').start();
5431
+ const result = await api._makeRequest('GET', `request/get/${requestId}`);
5432
+ spinner.stop();
5433
+
5434
+ if (options.output === 'json') {
5435
+ console.log(JSON.stringify(result, null, 2));
5436
+ } else {
5437
+ console.log(chalk.green.bold('Request Details'));
5438
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
5439
+ }
5440
+ } catch (error) {
5441
+ console.error(chalk.red(`Error getting request: ${error.message}`));
5442
+ process.exit(1);
5443
+ }
5444
+ });
5445
+
5446
+ // Secret commands
5447
+ program
5448
+ .command('create-secret')
5449
+ .description('Create a new secret')
5450
+ .option('--name <name>', 'Secret name')
5451
+ .option('--value <value>', 'Secret value')
5452
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5453
+ .option('-v, --verbose', 'Show detailed information')
5454
+ .action(async (options, command) => {
5455
+ try {
5456
+ const globalOptions = command.parent.opts();
5457
+ const config = loadConfig(globalOptions.config);
5458
+ validateConfiguration(config);
5459
+
5460
+ const api = new ToothFairyAPI(
5461
+ config.baseUrl,
5462
+ config.aiUrl,
5463
+ config.aiStreamUrl,
5464
+ config.apiKey,
5465
+ config.workspaceId,
5466
+ globalOptions.verbose || options.verbose
5467
+ );
5468
+
5469
+ const spinner = ora('Creating secret...').start();
5470
+ const result = await api._makeRequest('POST', 'secret/create', {
5471
+ name: options.name,
5472
+ value: options.value,
5473
+ });
5474
+ spinner.stop();
5475
+
5476
+ if (options.output === 'json') {
5477
+ console.log(JSON.stringify(result, null, 2));
5478
+ } else {
5479
+ console.log(chalk.green.bold('✅ Secret created successfully!'));
5480
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
5481
+ }
5482
+ } catch (error) {
5483
+ console.error(chalk.red(`Error creating secret: ${error.message}`));
5484
+ process.exit(1);
5485
+ }
5486
+ });
5487
+
5488
+ program
5489
+ .command('delete-secret')
5490
+ .description('Delete a secret')
5491
+ .argument('<id>', 'Secret ID')
5492
+ .option('--confirm', 'Skip confirmation prompt')
5493
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5494
+ .option('-v, --verbose', 'Show detailed information')
5495
+ .action(async (secretId, options, command) => {
5496
+ try {
5497
+ const globalOptions = command.parent.opts();
5498
+ const config = loadConfig(globalOptions.config);
5499
+ validateConfiguration(config);
5500
+
5501
+ if (!options.confirm) {
5502
+ const readline = require('readline');
5503
+ const rl = readline.createInterface({
5504
+ input: process.stdin,
5505
+ output: process.stdout,
5506
+ });
5507
+
5508
+ const answer = await new Promise((resolve) => {
5509
+ rl.question(
5510
+ chalk.yellow(`⚠️ Are you sure you want to delete secret ${secretId}? (y/N): `),
5511
+ resolve
5512
+ );
5513
+ });
5514
+ rl.close();
5515
+
5516
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
5517
+ console.log(chalk.gray('Deletion cancelled.'));
5518
+ process.exit(0);
5519
+ }
5520
+ }
5521
+
5522
+ const api = new ToothFairyAPI(
5523
+ config.baseUrl,
5524
+ config.aiUrl,
5525
+ config.aiStreamUrl,
5526
+ config.apiKey,
5527
+ config.workspaceId,
5528
+ globalOptions.verbose || options.verbose
5529
+ );
5530
+
5531
+ const spinner = ora('Deleting secret...').start();
5532
+ const result = await api._makeRequest('DELETE', `secret/delete/${secretId}`);
5533
+ spinner.stop();
5534
+
5535
+ if (options.output === 'json') {
5536
+ console.log(JSON.stringify(result, null, 2));
5537
+ } else {
5538
+ console.log(chalk.green.bold('✅ Secret deleted successfully!'));
5539
+ }
5540
+ } catch (error) {
5541
+ console.error(chalk.red(`Error deleting secret: ${error.message}`));
5542
+ process.exit(1);
5543
+ }
5544
+ });
5545
+
5546
+ // Hook commands
5547
+ program
5548
+ .command('create-hook')
5549
+ .description('Create a new hook')
5550
+ .option('--name <name>', 'Hook name')
5551
+ .option('--url <url>', 'Hook URL')
5552
+ .option('--events <events>', 'Events to trigger on (comma-separated)')
5553
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5554
+ .option('-v, --verbose', 'Show detailed information')
5555
+ .action(async (options, command) => {
5556
+ try {
5557
+ const globalOptions = command.parent.opts();
5558
+ const config = loadConfig(globalOptions.config);
5559
+ validateConfiguration(config);
5560
+
5561
+ const api = new ToothFairyAPI(
5562
+ config.baseUrl,
5563
+ config.aiUrl,
5564
+ config.aiStreamUrl,
5565
+ config.apiKey,
5566
+ config.workspaceId,
5567
+ globalOptions.verbose || options.verbose
5568
+ );
5569
+
5570
+ const events = options.events ? options.events.split(',').map(e => e.trim()) : [];
5571
+
5572
+ const spinner = ora('Creating hook...').start();
5573
+ const result = await api._makeRequest('POST', 'hook/create', {
5574
+ name: options.name,
5575
+ url: options.url,
5576
+ events,
5577
+ });
5578
+ spinner.stop();
5579
+
5580
+ if (options.output === 'json') {
5581
+ console.log(JSON.stringify(result, null, 2));
5582
+ } else {
5583
+ console.log(chalk.green.bold('✅ Hook created successfully!'));
5584
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
5585
+ }
5586
+ } catch (error) {
5587
+ console.error(chalk.red(`Error creating hook: ${error.message}`));
5588
+ process.exit(1);
5589
+ }
5590
+ });
5591
+
5592
+ program
5593
+ .command('list-hooks')
5594
+ .description('List all hooks')
5595
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5596
+ .option('-v, --verbose', 'Show detailed information')
5597
+ .action(async (options, command) => {
5598
+ try {
5599
+ const globalOptions = command.parent.opts();
5600
+ const config = loadConfig(globalOptions.config);
5601
+ validateConfiguration(config);
5602
+
5603
+ const api = new ToothFairyAPI(
5604
+ config.baseUrl,
5605
+ config.aiUrl,
5606
+ config.aiStreamUrl,
5607
+ config.apiKey,
5608
+ config.workspaceId,
5609
+ globalOptions.verbose || options.verbose
5610
+ );
5611
+
5612
+ const spinner = ora('Fetching hooks...').start();
5613
+ const result = await api._makeRequest('GET', 'hook/list');
5614
+ spinner.stop();
5615
+
5616
+ if (options.output === 'json') {
5617
+ console.log(JSON.stringify(result, null, 2));
5618
+ } else {
5619
+ const hooks = Array.isArray(result) ? result : result.items || [];
5620
+ console.log(chalk.green.bold(`Found ${hooks.length} hook(s)`));
5621
+ hooks.forEach(h => {
5622
+ console.log(chalk.cyan(` • ${h.name || 'Unnamed'} (${h.id})`));
5623
+ });
5624
+ }
5625
+ } catch (error) {
5626
+ console.error(chalk.red(`Error listing hooks: ${error.message}`));
5627
+ process.exit(1);
5628
+ }
5629
+ });
5630
+
5631
+ program
5632
+ .command('get-hook')
5633
+ .description('Get details of a specific hook')
5634
+ .argument('<id>', 'Hook ID')
5635
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5636
+ .option('-v, --verbose', 'Show detailed information')
5637
+ .action(async (hookId, options, command) => {
5638
+ try {
5639
+ const globalOptions = command.parent.opts();
5640
+ const config = loadConfig(globalOptions.config);
5641
+ validateConfiguration(config);
5642
+
5643
+ const api = new ToothFairyAPI(
5644
+ config.baseUrl,
5645
+ config.aiUrl,
5646
+ config.aiStreamUrl,
5647
+ config.apiKey,
5648
+ config.workspaceId,
5649
+ globalOptions.verbose || options.verbose
5650
+ );
5651
+
5652
+ const spinner = ora('Fetching hook...').start();
5653
+ const result = await api._makeRequest('GET', `hook/get/${hookId}`);
5654
+ spinner.stop();
5655
+
5656
+ if (options.output === 'json') {
5657
+ console.log(JSON.stringify(result, null, 2));
5658
+ } else {
5659
+ console.log(chalk.green.bold('Hook Details'));
5660
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
5661
+ }
5662
+ } catch (error) {
5663
+ console.error(chalk.red(`Error getting hook: ${error.message}`));
5664
+ process.exit(1);
5665
+ }
5666
+ });
5667
+
5668
+ program
5669
+ .command('update-hook')
5670
+ .description('Update an existing hook')
5671
+ .argument('<id>', 'Hook ID')
5672
+ .option('--name <name>', 'Hook name')
5673
+ .option('--url <url>', 'Hook URL')
5674
+ .option('--events <events>', 'Events to trigger on (comma-separated)')
5675
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5676
+ .option('-v, --verbose', 'Show detailed information')
5677
+ .action(async (hookId, options, command) => {
5678
+ try {
5679
+ const globalOptions = command.parent.opts();
5680
+ const config = loadConfig(globalOptions.config);
5681
+ validateConfiguration(config);
5682
+
5683
+ const api = new ToothFairyAPI(
5684
+ config.baseUrl,
5685
+ config.aiUrl,
5686
+ config.aiStreamUrl,
5687
+ config.apiKey,
5688
+ config.workspaceId,
5689
+ globalOptions.verbose || options.verbose
5690
+ );
5691
+
5692
+ const events = options.events ? options.events.split(',').map(e => e.trim()) : [];
5693
+
5694
+ const spinner = ora('Updating hook...').start();
5695
+ const result = await api._makeRequest('POST', `hook/update/${hookId}`, {
5696
+ name: options.name,
5697
+ url: options.url,
5698
+ events,
5699
+ });
5700
+ spinner.stop();
5701
+
5702
+ if (options.output === 'json') {
5703
+ console.log(JSON.stringify(result, null, 2));
5704
+ } else {
5705
+ console.log(chalk.green.bold('✅ Hook updated successfully!'));
5706
+ }
5707
+ } catch (error) {
5708
+ console.error(chalk.red(`Error updating hook: ${error.message}`));
5709
+ process.exit(1);
5710
+ }
5711
+ });
5712
+
5713
+ program
5714
+ .command('delete-hook')
5715
+ .description('Delete a hook')
5716
+ .argument('<id>', 'Hook ID')
5717
+ .option('--confirm', 'Skip confirmation prompt')
5718
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5719
+ .option('-v, --verbose', 'Show detailed information')
5720
+ .action(async (hookId, options, command) => {
5721
+ try {
5722
+ const globalOptions = command.parent.opts();
5723
+ const config = loadConfig(globalOptions.config);
5724
+ validateConfiguration(config);
5725
+
5726
+ if (!options.confirm) {
5727
+ const readline = require('readline');
5728
+ const rl = readline.createInterface({
5729
+ input: process.stdin,
5730
+ output: process.stdout,
5731
+ });
5732
+
5733
+ const answer = await new Promise((resolve) => {
5734
+ rl.question(
5735
+ chalk.yellow(`⚠️ Are you sure you want to delete hook ${hookId}? (y/N): `),
5736
+ resolve
5737
+ );
5738
+ });
5739
+ rl.close();
5740
+
5741
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
5742
+ console.log(chalk.gray('Deletion cancelled.'));
5743
+ process.exit(0);
5744
+ }
5745
+ }
5746
+
5747
+ const api = new ToothFairyAPI(
5748
+ config.baseUrl,
5749
+ config.aiUrl,
5750
+ config.aiStreamUrl,
5751
+ config.apiKey,
5752
+ config.workspaceId,
5753
+ globalOptions.verbose || options.verbose
5754
+ );
5755
+
5756
+ const spinner = ora('Deleting hook...').start();
5757
+ const result = await api._makeRequest('DELETE', `hook/delete/${hookId}`);
5758
+ spinner.stop();
5759
+
5760
+ if (options.output === 'json') {
5761
+ console.log(JSON.stringify(result, null, 2));
5762
+ } else {
5763
+ console.log(chalk.green.bold('✅ Hook deleted successfully!'));
5764
+ }
5765
+ } catch (error) {
5766
+ console.error(chalk.red(`Error deleting hook: ${error.message}`));
5767
+ process.exit(1);
5768
+ }
5769
+ });
5770
+
5771
+ // Benchmark commands
5772
+ program
5773
+ .command('create-benchmark')
5774
+ .description('Create a new benchmark')
5775
+ .option('--name <name>', 'Benchmark name')
5776
+ .option('--description <description>', 'Benchmark description')
5777
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5778
+ .option('-v, --verbose', 'Show detailed information')
5779
+ .action(async (options, command) => {
5780
+ try {
5781
+ const globalOptions = command.parent.opts();
5782
+ const config = loadConfig(globalOptions.config);
5783
+ validateConfiguration(config);
5784
+
5785
+ const api = new ToothFairyAPI(
5786
+ config.baseUrl,
5787
+ config.aiUrl,
5788
+ config.aiStreamUrl,
5789
+ config.apiKey,
5790
+ config.workspaceId,
5791
+ globalOptions.verbose || options.verbose
5792
+ );
5793
+
5794
+ const spinner = ora('Creating benchmark...').start();
5795
+ const result = await api._makeRequest('POST', 'benchmark/create', {
5796
+ name: options.name,
5797
+ description: options.description,
5798
+ });
5799
+ spinner.stop();
5800
+
5801
+ if (options.output === 'json') {
5802
+ console.log(JSON.stringify(result, null, 2));
5803
+ } else {
5804
+ console.log(chalk.green.bold('✅ Benchmark created successfully!'));
5805
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
5806
+ }
5807
+ } catch (error) {
5808
+ console.error(chalk.red(`Error creating benchmark: ${error.message}`));
5809
+ process.exit(1);
5810
+ }
5811
+ });
5812
+
5813
+ program
5814
+ .command('list-benchmarks')
5815
+ .description('List all benchmarks')
5816
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5817
+ .option('-v, --verbose', 'Show detailed information')
5818
+ .action(async (options, command) => {
5819
+ try {
5820
+ const globalOptions = command.parent.opts();
5821
+ const config = loadConfig(globalOptions.config);
5822
+ validateConfiguration(config);
5823
+
5824
+ const api = new ToothFairyAPI(
5825
+ config.baseUrl,
5826
+ config.aiUrl,
5827
+ config.aiStreamUrl,
5828
+ config.apiKey,
5829
+ config.workspaceId,
5830
+ globalOptions.verbose || options.verbose
5831
+ );
5832
+
5833
+ const spinner = ora('Fetching benchmarks...').start();
5834
+ const result = await api._makeRequest('GET', 'benchmark/list');
5835
+ spinner.stop();
5836
+
5837
+ if (options.output === 'json') {
5838
+ console.log(JSON.stringify(result, null, 2));
5839
+ } else {
5840
+ const benchmarks = Array.isArray(result) ? result : result.items || [];
5841
+ console.log(chalk.green.bold(`Found ${benchmarks.length} benchmark(s)`));
5842
+ benchmarks.forEach(b => {
5843
+ console.log(chalk.cyan(` • ${b.name || 'Unnamed'} (${b.id})`));
5844
+ });
5845
+ }
5846
+ } catch (error) {
5847
+ console.error(chalk.red(`Error listing benchmarks: ${error.message}`));
5848
+ process.exit(1);
5849
+ }
5850
+ });
5851
+
5852
+ program
5853
+ .command('get-benchmark')
5854
+ .description('Get details of a specific benchmark')
5855
+ .argument('<id>', 'Benchmark ID')
5856
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5857
+ .option('-v, --verbose', 'Show detailed information')
5858
+ .action(async (benchmarkId, options, command) => {
5859
+ try {
5860
+ const globalOptions = command.parent.opts();
5861
+ const config = loadConfig(globalOptions.config);
5862
+ validateConfiguration(config);
5863
+
5864
+ const api = new ToothFairyAPI(
5865
+ config.baseUrl,
5866
+ config.aiUrl,
5867
+ config.aiStreamUrl,
5868
+ config.apiKey,
5869
+ config.workspaceId,
5870
+ globalOptions.verbose || options.verbose
5871
+ );
5872
+
5873
+ const spinner = ora('Fetching benchmark...').start();
5874
+ const result = await api._makeRequest('GET', `benchmark/get/${benchmarkId}`);
5875
+ spinner.stop();
5876
+
5877
+ if (options.output === 'json') {
5878
+ console.log(JSON.stringify(result, null, 2));
5879
+ } else {
5880
+ console.log(chalk.green.bold('Benchmark Details'));
5881
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
5882
+ }
5883
+ } catch (error) {
5884
+ console.error(chalk.red(`Error getting benchmark: ${error.message}`));
5885
+ process.exit(1);
5886
+ }
5887
+ });
5888
+
5889
+ program
5890
+ .command('update-benchmark')
5891
+ .description('Update an existing benchmark')
5892
+ .argument('<id>', 'Benchmark ID')
5893
+ .option('--name <name>', 'Benchmark name')
5894
+ .option('--description <description>', 'Benchmark description')
5895
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5896
+ .option('-v, --verbose', 'Show detailed information')
5897
+ .action(async (benchmarkId, options, command) => {
5898
+ try {
5899
+ const globalOptions = command.parent.opts();
5900
+ const config = loadConfig(globalOptions.config);
5901
+ validateConfiguration(config);
5902
+
5903
+ const api = new ToothFairyAPI(
5904
+ config.baseUrl,
5905
+ config.aiUrl,
5906
+ config.aiStreamUrl,
5907
+ config.apiKey,
5908
+ config.workspaceId,
5909
+ globalOptions.verbose || options.verbose
5910
+ );
5911
+
5912
+ const spinner = ora('Updating benchmark...').start();
5913
+ const result = await api._makeRequest('POST', `benchmark/update/${benchmarkId}`, {
5914
+ name: options.name,
5915
+ description: options.description,
5916
+ });
5917
+ spinner.stop();
5918
+
5919
+ if (options.output === 'json') {
5920
+ console.log(JSON.stringify(result, null, 2));
5921
+ } else {
5922
+ console.log(chalk.green.bold('✅ Benchmark updated successfully!'));
5923
+ }
5924
+ } catch (error) {
5925
+ console.error(chalk.red(`Error updating benchmark: ${error.message}`));
5926
+ process.exit(1);
5927
+ }
5928
+ });
5929
+
5930
+ program
5931
+ .command('delete-benchmark')
5932
+ .description('Delete a benchmark')
5933
+ .argument('<id>', 'Benchmark ID')
5934
+ .option('--confirm', 'Skip confirmation prompt')
5935
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5936
+ .option('-v, --verbose', 'Show detailed information')
5937
+ .action(async (benchmarkId, options, command) => {
5938
+ try {
5939
+ const globalOptions = command.parent.opts();
5940
+ const config = loadConfig(globalOptions.config);
5941
+ validateConfiguration(config);
5942
+
5943
+ if (!options.confirm) {
5944
+ const readline = require('readline');
5945
+ const rl = readline.createInterface({
5946
+ input: process.stdin,
5947
+ output: process.stdout,
5948
+ });
5949
+
5950
+ const answer = await new Promise((resolve) => {
5951
+ rl.question(
5952
+ chalk.yellow(`⚠️ Are you sure you want to delete benchmark ${benchmarkId}? (y/N): `),
5953
+ resolve
5954
+ );
5955
+ });
5956
+ rl.close();
5957
+
5958
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
5959
+ console.log(chalk.gray('Deletion cancelled.'));
5960
+ process.exit(0);
5961
+ }
5962
+ }
5963
+
5964
+ const api = new ToothFairyAPI(
5965
+ config.baseUrl,
5966
+ config.aiUrl,
5967
+ config.aiStreamUrl,
5968
+ config.apiKey,
5969
+ config.workspaceId,
5970
+ globalOptions.verbose || options.verbose
5971
+ );
5972
+
5973
+ const spinner = ora('Deleting benchmark...').start();
5974
+ const result = await api._makeRequest('DELETE', `benchmark/delete/${benchmarkId}`);
5975
+ spinner.stop();
5976
+
5977
+ if (options.output === 'json') {
5978
+ console.log(JSON.stringify(result, null, 2));
5979
+ } else {
5980
+ console.log(chalk.green.bold('✅ Benchmark deleted successfully!'));
5981
+ }
5982
+ } catch (error) {
5983
+ console.error(chalk.red(`Error deleting benchmark: ${error.message}`));
5984
+ process.exit(1);
5985
+ }
5986
+ });
5987
+
5988
+ // Scheduled Job commands
5989
+ program
5990
+ .command('create-scheduled-job')
5991
+ .description('Create a new scheduled job')
5992
+ .option('--name <name>', 'Job name')
5993
+ .option('--schedule <schedule>', 'Cron schedule expression')
5994
+ .option('--task <task>', 'Task to execute')
5995
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
5996
+ .option('-v, --verbose', 'Show detailed information')
5997
+ .action(async (options, command) => {
5998
+ try {
5999
+ const globalOptions = command.parent.opts();
6000
+ const config = loadConfig(globalOptions.config);
6001
+ validateConfiguration(config);
6002
+
6003
+ const api = new ToothFairyAPI(
6004
+ config.baseUrl,
6005
+ config.aiUrl,
6006
+ config.aiStreamUrl,
6007
+ config.apiKey,
6008
+ config.workspaceId,
6009
+ globalOptions.verbose || options.verbose
6010
+ );
6011
+
6012
+ const spinner = ora('Creating scheduled job...').start();
6013
+ const result = await api._makeRequest('POST', 'scheduled_job/create', {
6014
+ name: options.name,
6015
+ schedule: options.schedule,
6016
+ task: options.task,
6017
+ });
6018
+ spinner.stop();
6019
+
6020
+ if (options.output === 'json') {
6021
+ console.log(JSON.stringify(result, null, 2));
6022
+ } else {
6023
+ console.log(chalk.green.bold('✅ Scheduled job created successfully!'));
6024
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
6025
+ }
6026
+ } catch (error) {
6027
+ console.error(chalk.red(`Error creating scheduled job: ${error.message}`));
6028
+ process.exit(1);
6029
+ }
6030
+ });
6031
+
6032
+ program
6033
+ .command('list-scheduled-jobs')
6034
+ .description('List all scheduled jobs')
6035
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6036
+ .option('-v, --verbose', 'Show detailed information')
6037
+ .action(async (options, command) => {
6038
+ try {
6039
+ const globalOptions = command.parent.opts();
6040
+ const config = loadConfig(globalOptions.config);
6041
+ validateConfiguration(config);
6042
+
6043
+ const api = new ToothFairyAPI(
6044
+ config.baseUrl,
6045
+ config.aiUrl,
6046
+ config.aiStreamUrl,
6047
+ config.apiKey,
6048
+ config.workspaceId,
6049
+ globalOptions.verbose || options.verbose
6050
+ );
6051
+
6052
+ const spinner = ora('Fetching scheduled jobs...').start();
6053
+ const result = await api._makeRequest('GET', 'scheduled_job/list');
6054
+ spinner.stop();
6055
+
6056
+ if (options.output === 'json') {
6057
+ console.log(JSON.stringify(result, null, 2));
6058
+ } else {
6059
+ const jobs = Array.isArray(result) ? result : result.items || [];
6060
+ console.log(chalk.green.bold(`Found ${jobs.length} scheduled job(s)`));
6061
+ jobs.forEach(j => {
6062
+ console.log(chalk.cyan(` • ${j.name || 'Unnamed'} (${j.id})`));
6063
+ });
6064
+ }
6065
+ } catch (error) {
6066
+ console.error(chalk.red(`Error listing scheduled jobs: ${error.message}`));
6067
+ process.exit(1);
6068
+ }
6069
+ });
6070
+
6071
+ program
6072
+ .command('get-scheduled-job')
6073
+ .description('Get details of a specific scheduled job')
6074
+ .argument('<id>', 'Scheduled Job ID')
6075
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6076
+ .option('-v, --verbose', 'Show detailed information')
6077
+ .action(async (jobId, options, command) => {
6078
+ try {
6079
+ const globalOptions = command.parent.opts();
6080
+ const config = loadConfig(globalOptions.config);
6081
+ validateConfiguration(config);
6082
+
6083
+ const api = new ToothFairyAPI(
6084
+ config.baseUrl,
6085
+ config.aiUrl,
6086
+ config.aiStreamUrl,
6087
+ config.apiKey,
6088
+ config.workspaceId,
6089
+ globalOptions.verbose || options.verbose
6090
+ );
6091
+
6092
+ const spinner = ora('Fetching scheduled job...').start();
6093
+ const result = await api._makeRequest('GET', `scheduled_job/get/${jobId}`);
6094
+ spinner.stop();
6095
+
6096
+ if (options.output === 'json') {
6097
+ console.log(JSON.stringify(result, null, 2));
6098
+ } else {
6099
+ console.log(chalk.green.bold('Scheduled Job Details'));
6100
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
6101
+ }
6102
+ } catch (error) {
6103
+ console.error(chalk.red(`Error getting scheduled job: ${error.message}`));
6104
+ process.exit(1);
6105
+ }
6106
+ });
6107
+
6108
+ program
6109
+ .command('update-scheduled-job')
6110
+ .description('Update an existing scheduled job')
6111
+ .argument('<id>', 'Scheduled Job ID')
6112
+ .option('--name <name>', 'Job name')
6113
+ .option('--schedule <schedule>', 'Cron schedule expression')
6114
+ .option('--task <task>', 'Task to execute')
6115
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6116
+ .option('-v, --verbose', 'Show detailed information')
6117
+ .action(async (jobId, options, command) => {
6118
+ try {
6119
+ const globalOptions = command.parent.opts();
6120
+ const config = loadConfig(globalOptions.config);
6121
+ validateConfiguration(config);
6122
+
6123
+ const api = new ToothFairyAPI(
6124
+ config.baseUrl,
6125
+ config.aiUrl,
6126
+ config.aiStreamUrl,
6127
+ config.apiKey,
6128
+ config.workspaceId,
6129
+ globalOptions.verbose || options.verbose
6130
+ );
6131
+
6132
+ const spinner = ora('Updating scheduled job...').start();
6133
+ const result = await api._makeRequest('POST', `scheduled_job/update/${jobId}`, {
6134
+ name: options.name,
6135
+ schedule: options.schedule,
6136
+ task: options.task,
6137
+ });
6138
+ spinner.stop();
6139
+
6140
+ if (options.output === 'json') {
6141
+ console.log(JSON.stringify(result, null, 2));
6142
+ } else {
6143
+ console.log(chalk.green.bold('✅ Scheduled job updated successfully!'));
6144
+ }
6145
+ } catch (error) {
6146
+ console.error(chalk.red(`Error updating scheduled job: ${error.message}`));
6147
+ process.exit(1);
6148
+ }
6149
+ });
6150
+
6151
+ program
6152
+ .command('delete-scheduled-job')
6153
+ .description('Delete a scheduled job')
6154
+ .argument('<id>', 'Scheduled Job ID')
6155
+ .option('--confirm', 'Skip confirmation prompt')
6156
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6157
+ .option('-v, --verbose', 'Show detailed information')
6158
+ .action(async (jobId, options, command) => {
6159
+ try {
6160
+ const globalOptions = command.parent.opts();
6161
+ const config = loadConfig(globalOptions.config);
6162
+ validateConfiguration(config);
6163
+
6164
+ if (!options.confirm) {
6165
+ const readline = require('readline');
6166
+ const rl = readline.createInterface({
6167
+ input: process.stdin,
6168
+ output: process.stdout,
6169
+ });
6170
+
6171
+ const answer = await new Promise((resolve) => {
6172
+ rl.question(
6173
+ chalk.yellow(`⚠️ Are you sure you want to delete scheduled job ${jobId}? (y/N): `),
6174
+ resolve
6175
+ );
6176
+ });
6177
+ rl.close();
6178
+
6179
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
6180
+ console.log(chalk.gray('Deletion cancelled.'));
6181
+ process.exit(0);
6182
+ }
6183
+ }
6184
+
6185
+ const api = new ToothFairyAPI(
6186
+ config.baseUrl,
6187
+ config.aiUrl,
6188
+ config.aiStreamUrl,
6189
+ config.apiKey,
6190
+ config.workspaceId,
6191
+ globalOptions.verbose || options.verbose
6192
+ );
6193
+
6194
+ const spinner = ora('Deleting scheduled job...').start();
6195
+ const result = await api._makeRequest('DELETE', `scheduled_job/delete/${jobId}`);
6196
+ spinner.stop();
6197
+
6198
+ if (options.output === 'json') {
6199
+ console.log(JSON.stringify(result, null, 2));
6200
+ } else {
6201
+ console.log(chalk.green.bold('✅ Scheduled job deleted successfully!'));
6202
+ }
6203
+ } catch (error) {
6204
+ console.error(chalk.red(`Error deleting scheduled job: ${error.message}`));
6205
+ process.exit(1);
6206
+ }
6207
+ });
6208
+
6209
+ // Authorisation commands
6210
+ program
6211
+ .command('create-authorisation')
6212
+ .description('Create a new authorisation')
6213
+ .option('--name <name>', 'Authorisation name')
6214
+ .option('--permissions <permissions>', 'Permissions (JSON)')
6215
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6216
+ .option('-v, --verbose', 'Show detailed information')
6217
+ .action(async (options, command) => {
6218
+ try {
6219
+ const globalOptions = command.parent.opts();
6220
+ const config = loadConfig(globalOptions.config);
6221
+ validateConfiguration(config);
6222
+
6223
+ const api = new ToothFairyAPI(
6224
+ config.baseUrl,
6225
+ config.aiUrl,
6226
+ config.aiStreamUrl,
6227
+ config.apiKey,
6228
+ config.workspaceId,
6229
+ globalOptions.verbose || options.verbose
6230
+ );
6231
+
6232
+ let permissions = {};
6233
+ if (options.permissions) {
6234
+ try {
6235
+ permissions = JSON.parse(options.permissions);
6236
+ } catch (e) {
6237
+ console.error(chalk.red('Invalid JSON in permissions'));
6238
+ process.exit(1);
6239
+ }
6240
+ }
6241
+
6242
+ const spinner = ora('Creating authorisation...').start();
6243
+ const result = await api._makeRequest('POST', 'authorisation/create', {
6244
+ name: options.name,
6245
+ permissions,
6246
+ });
6247
+ spinner.stop();
6248
+
6249
+ if (options.output === 'json') {
6250
+ console.log(JSON.stringify(result, null, 2));
6251
+ } else {
6252
+ console.log(chalk.green.bold('✅ Authorisation created successfully!'));
6253
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
6254
+ }
6255
+ } catch (error) {
6256
+ console.error(chalk.red(`Error creating authorisation: ${error.message}`));
6257
+ process.exit(1);
6258
+ }
6259
+ });
6260
+
6261
+ program
6262
+ .command('list-authorisations')
6263
+ .description('List all authorisations')
6264
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6265
+ .option('-v, --verbose', 'Show detailed information')
6266
+ .action(async (options, command) => {
6267
+ try {
6268
+ const globalOptions = command.parent.opts();
6269
+ const config = loadConfig(globalOptions.config);
6270
+ validateConfiguration(config);
6271
+
6272
+ const api = new ToothFairyAPI(
6273
+ config.baseUrl,
6274
+ config.aiUrl,
6275
+ config.aiStreamUrl,
6276
+ config.apiKey,
6277
+ config.workspaceId,
6278
+ globalOptions.verbose || options.verbose
6279
+ );
6280
+
6281
+ const spinner = ora('Fetching authorisations...').start();
6282
+ const result = await api._makeRequest('GET', 'authorisation/list');
6283
+ spinner.stop();
6284
+
6285
+ if (options.output === 'json') {
6286
+ console.log(JSON.stringify(result, null, 2));
6287
+ } else {
6288
+ const auths = Array.isArray(result) ? result : result.items || [];
6289
+ console.log(chalk.green.bold(`Found ${auths.length} authorisation(s)`));
6290
+ auths.forEach(a => {
6291
+ console.log(chalk.cyan(` • ${a.name || 'Unnamed'} (${a.id})`));
6292
+ });
6293
+ }
6294
+ } catch (error) {
6295
+ console.error(chalk.red(`Error listing authorisations: ${error.message}`));
6296
+ process.exit(1);
6297
+ }
6298
+ });
6299
+
6300
+ program
6301
+ .command('get-authorisation')
6302
+ .description('Get details of a specific authorisation')
6303
+ .argument('<id>', 'Authorisation ID')
6304
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6305
+ .option('-v, --verbose', 'Show detailed information')
6306
+ .action(async (authId, options, command) => {
6307
+ try {
6308
+ const globalOptions = command.parent.opts();
6309
+ const config = loadConfig(globalOptions.config);
6310
+ validateConfiguration(config);
6311
+
6312
+ const api = new ToothFairyAPI(
6313
+ config.baseUrl,
6314
+ config.aiUrl,
6315
+ config.aiStreamUrl,
6316
+ config.apiKey,
6317
+ config.workspaceId,
6318
+ globalOptions.verbose || options.verbose
6319
+ );
6320
+
6321
+ const spinner = ora('Fetching authorisation...').start();
6322
+ const result = await api._makeRequest('GET', `authorisation/get/${authId}`);
6323
+ spinner.stop();
6324
+
6325
+ if (options.output === 'json') {
6326
+ console.log(JSON.stringify(result, null, 2));
6327
+ } else {
6328
+ console.log(chalk.green.bold('Authorisation Details'));
6329
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
6330
+ }
6331
+ } catch (error) {
6332
+ console.error(chalk.red(`Error getting authorisation: ${error.message}`));
6333
+ process.exit(1);
6334
+ }
6335
+ });
6336
+
6337
+ program
6338
+ .command('update-authorisation')
6339
+ .description('Update an existing authorisation')
6340
+ .argument('<id>', 'Authorisation ID')
6341
+ .option('--name <name>', 'Authorisation name')
6342
+ .option('--permissions <permissions>', 'Permissions (JSON)')
6343
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6344
+ .option('-v, --verbose', 'Show detailed information')
6345
+ .action(async (authId, options, command) => {
6346
+ try {
6347
+ const globalOptions = command.parent.opts();
6348
+ const config = loadConfig(globalOptions.config);
6349
+ validateConfiguration(config);
6350
+
6351
+ const api = new ToothFairyAPI(
6352
+ config.baseUrl,
6353
+ config.aiUrl,
6354
+ config.aiStreamUrl,
6355
+ config.apiKey,
6356
+ config.workspaceId,
6357
+ globalOptions.verbose || options.verbose
6358
+ );
6359
+
6360
+ let permissions = {};
6361
+ if (options.permissions) {
6362
+ try {
6363
+ permissions = JSON.parse(options.permissions);
6364
+ } catch (e) {
6365
+ console.error(chalk.red('Invalid JSON in permissions'));
6366
+ process.exit(1);
6367
+ }
6368
+ }
6369
+
6370
+ const spinner = ora('Updating authorisation...').start();
6371
+ const result = await api._makeRequest('POST', 'authorisation/update', {
6372
+ id: authId,
6373
+ name: options.name,
6374
+ permissions,
6375
+ });
6376
+ spinner.stop();
6377
+
6378
+ if (options.output === 'json') {
6379
+ console.log(JSON.stringify(result, null, 2));
6380
+ } else {
6381
+ console.log(chalk.green.bold('✅ Authorisation updated successfully!'));
6382
+ }
6383
+ } catch (error) {
6384
+ console.error(chalk.red(`Error updating authorisation: ${error.message}`));
6385
+ process.exit(1);
6386
+ }
6387
+ });
6388
+
6389
+ program
6390
+ .command('delete-authorisation')
6391
+ .description('Delete an authorisation')
6392
+ .argument('<id>', 'Authorisation ID')
6393
+ .option('--confirm', 'Skip confirmation prompt')
6394
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6395
+ .option('-v, --verbose', 'Show detailed information')
6396
+ .action(async (authId, options, command) => {
6397
+ try {
6398
+ const globalOptions = command.parent.opts();
6399
+ const config = loadConfig(globalOptions.config);
6400
+ validateConfiguration(config);
6401
+
6402
+ if (!options.confirm) {
6403
+ const readline = require('readline');
6404
+ const rl = readline.createInterface({
6405
+ input: process.stdin,
6406
+ output: process.stdout,
6407
+ });
6408
+
6409
+ const answer = await new Promise((resolve) => {
6410
+ rl.question(
6411
+ chalk.yellow(`⚠️ Are you sure you want to delete authorisation ${authId}? (y/N): `),
6412
+ resolve
6413
+ );
6414
+ });
6415
+ rl.close();
6416
+
6417
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
6418
+ console.log(chalk.gray('Deletion cancelled.'));
6419
+ process.exit(0);
6420
+ }
6421
+ }
6422
+
6423
+ const api = new ToothFairyAPI(
6424
+ config.baseUrl,
6425
+ config.aiUrl,
6426
+ config.aiStreamUrl,
6427
+ config.apiKey,
6428
+ config.workspaceId,
6429
+ globalOptions.verbose || options.verbose
6430
+ );
6431
+
6432
+ const spinner = ora('Deleting authorisation...').start();
6433
+ const result = await api._makeRequest('DELETE', `authorisation/delete/${authId}`);
6434
+ spinner.stop();
6435
+
6436
+ if (options.output === 'json') {
6437
+ console.log(JSON.stringify(result, null, 2));
6438
+ } else {
6439
+ console.log(chalk.green.bold('✅ Authorisation deleted successfully!'));
6440
+ }
6441
+ } catch (error) {
6442
+ console.error(chalk.red(`Error deleting authorisation: ${error.message}`));
6443
+ process.exit(1);
6444
+ }
6445
+ });
6446
+
6447
+ // Charting Settings commands
6448
+ program
6449
+ .command('get-charting-settings')
6450
+ .description('Get charting settings')
6451
+ .argument('<id>', 'Settings ID')
6452
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6453
+ .option('-v, --verbose', 'Show detailed information')
6454
+ .action(async (settingsId, options, command) => {
6455
+ try {
6456
+ const globalOptions = command.parent.opts();
6457
+ const config = loadConfig(globalOptions.config);
6458
+ validateConfiguration(config);
6459
+
6460
+ const api = new ToothFairyAPI(
6461
+ config.baseUrl,
6462
+ config.aiUrl,
6463
+ config.aiStreamUrl,
6464
+ config.apiKey,
6465
+ config.workspaceId,
6466
+ globalOptions.verbose || options.verbose
6467
+ );
6468
+
6469
+ const spinner = ora('Fetching charting settings...').start();
6470
+ const result = await api._makeRequest('GET', `charting_settings/get/${settingsId}`);
6471
+ spinner.stop();
6472
+
6473
+ if (options.output === 'json') {
6474
+ console.log(JSON.stringify(result, null, 2));
6475
+ } else {
6476
+ console.log(chalk.green.bold('Charting Settings'));
6477
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
6478
+ }
6479
+ } catch (error) {
6480
+ console.error(chalk.red(`Error getting charting settings: ${error.message}`));
6481
+ process.exit(1);
6482
+ }
6483
+ });
6484
+
6485
+ program
6486
+ .command('update-charting-settings')
6487
+ .description('Update charting settings')
6488
+ .argument('<id>', 'Settings ID')
6489
+ .option('--config <config>', 'Settings configuration (JSON)')
6490
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6491
+ .option('-v, --verbose', 'Show detailed information')
6492
+ .action(async (settingsId, options, command) => {
6493
+ try {
6494
+ const globalOptions = command.parent.opts();
6495
+ const config = loadConfig(globalOptions.config);
6496
+ validateConfiguration(config);
6497
+
6498
+ const api = new ToothFairyAPI(
6499
+ config.baseUrl,
6500
+ config.aiUrl,
6501
+ config.aiStreamUrl,
6502
+ config.apiKey,
6503
+ config.workspaceId,
6504
+ globalOptions.verbose || options.verbose
6505
+ );
6506
+
6507
+ let settingsConfig = {};
6508
+ if (options.config) {
6509
+ try {
6510
+ settingsConfig = JSON.parse(options.config);
6511
+ } catch (e) {
6512
+ console.error(chalk.red('Invalid JSON in config'));
6513
+ process.exit(1);
6514
+ }
6515
+ }
6516
+
6517
+ const spinner = ora('Updating charting settings...').start();
6518
+ const result = await api._makeRequest('POST', 'charting_settings/update', {
6519
+ id: settingsId,
6520
+ ...settingsConfig,
6521
+ });
6522
+ spinner.stop();
6523
+
6524
+ if (options.output === 'json') {
6525
+ console.log(JSON.stringify(result, null, 2));
6526
+ } else {
6527
+ console.log(chalk.green.bold('✅ Charting settings updated successfully!'));
6528
+ }
6529
+ } catch (error) {
6530
+ console.error(chalk.red(`Error updating charting settings: ${error.message}`));
6531
+ process.exit(1);
6532
+ }
6533
+ });
6534
+
6535
+ // Embeddings Settings commands
6536
+ program
6537
+ .command('get-embeddings-settings')
6538
+ .description('Get embeddings settings')
6539
+ .argument('<id>', 'Settings ID')
6540
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6541
+ .option('-v, --verbose', 'Show detailed information')
6542
+ .action(async (settingsId, options, command) => {
6543
+ try {
6544
+ const globalOptions = command.parent.opts();
6545
+ const config = loadConfig(globalOptions.config);
6546
+ validateConfiguration(config);
6547
+
6548
+ const api = new ToothFairyAPI(
6549
+ config.baseUrl,
6550
+ config.aiUrl,
6551
+ config.aiStreamUrl,
6552
+ config.apiKey,
6553
+ config.workspaceId,
6554
+ globalOptions.verbose || options.verbose
6555
+ );
6556
+
6557
+ const spinner = ora('Fetching embeddings settings...').start();
6558
+ const result = await api._makeRequest('GET', `embeddings_settings/get/${settingsId}`);
6559
+ spinner.stop();
6560
+
6561
+ if (options.output === 'json') {
6562
+ console.log(JSON.stringify(result, null, 2));
6563
+ } else {
6564
+ console.log(chalk.green.bold('Embeddings Settings'));
6565
+ console.log(chalk.dim(JSON.stringify(result, null, 2)));
6566
+ }
6567
+ } catch (error) {
6568
+ console.error(chalk.red(`Error getting embeddings settings: ${error.message}`));
6569
+ process.exit(1);
6570
+ }
6571
+ });
6572
+
6573
+ program
6574
+ .command('update-embeddings-settings')
6575
+ .description('Update embeddings settings')
6576
+ .argument('<id>', 'Settings ID')
6577
+ .option('--config <config>', 'Settings configuration (JSON)')
6578
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6579
+ .option('-v, --verbose', 'Show detailed information')
6580
+ .action(async (settingsId, options, command) => {
6581
+ try {
6582
+ const globalOptions = command.parent.opts();
6583
+ const config = loadConfig(globalOptions.config);
6584
+ validateConfiguration(config);
6585
+
6586
+ const api = new ToothFairyAPI(
6587
+ config.baseUrl,
6588
+ config.aiUrl,
6589
+ config.aiStreamUrl,
6590
+ config.apiKey,
6591
+ config.workspaceId,
6592
+ globalOptions.verbose || options.verbose
6593
+ );
6594
+
6595
+ let settingsConfig = {};
6596
+ if (options.config) {
6597
+ try {
6598
+ settingsConfig = JSON.parse(options.config);
6599
+ } catch (e) {
6600
+ console.error(chalk.red('Invalid JSON in config'));
6601
+ process.exit(1);
6602
+ }
6603
+ }
6604
+
6605
+ const spinner = ora('Updating embeddings settings...').start();
6606
+ const result = await api._makeRequest('POST', 'embeddings_settings/update', {
6607
+ id: settingsId,
6608
+ ...settingsConfig,
6609
+ });
6610
+ spinner.stop();
6611
+
6612
+ if (options.output === 'json') {
6613
+ console.log(JSON.stringify(result, null, 2));
6614
+ } else {
6615
+ console.log(chalk.green.bold('✅ Embeddings settings updated successfully!'));
6616
+ }
6617
+ } catch (error) {
6618
+ console.error(chalk.red(`Error updating embeddings settings: ${error.message}`));
6619
+ process.exit(1);
6620
+ }
6621
+ });
6622
+
6623
+ // Authorization Management Commands
6624
+ program
6625
+ .command('create-authorization')
6626
+ .description('Create a new authorization')
6627
+ .option('--name <name>', 'Authorization name')
6628
+ .option('--type <type>', 'Authorization type')
6629
+ .option('--config <config>', 'Authorization configuration (JSON)')
6630
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6631
+ .option('-v, --verbose', 'Show detailed information')
6632
+ .action(async (options, command) => {
6633
+ try {
6634
+ const globalOptions = command.parent.opts();
6635
+ const config = loadConfig(globalOptions.config);
6636
+ validateConfiguration(config);
6637
+
6638
+ const api = new ToothFairyAPI(
6639
+ config.baseUrl,
6640
+ config.aiUrl,
6641
+ config.aiStreamUrl,
6642
+ config.apiKey,
6643
+ config.workspaceId,
6644
+ globalOptions.verbose || options.verbose
6645
+ );
6646
+
6647
+ let authConfig = {};
6648
+ if (options.config) {
6649
+ try {
6650
+ authConfig = JSON.parse(options.config);
6651
+ } catch (e) {
6652
+ console.error(chalk.red('Invalid JSON in config'));
6653
+ process.exit(1);
6654
+ }
6655
+ }
6656
+
6657
+ const spinner = ora('Creating authorization...').start();
6658
+ const result = await api.createAuthorization({
6659
+ name: options.name,
6660
+ type: options.type,
6661
+ ...authConfig,
6662
+ });
6663
+ spinner.stop();
6664
+
6665
+ if (options.output === 'json') {
6666
+ console.log(JSON.stringify(result, null, 2));
6667
+ } else {
6668
+ console.log(chalk.green.bold('✅ Authorization created successfully!'));
6669
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
6670
+ }
6671
+ } catch (error) {
6672
+ console.error(chalk.red(`Error creating authorization: ${error.message}`));
6673
+ process.exit(1);
6674
+ }
6675
+ });
6676
+
6677
+ program
6678
+ .command('list-authorizations')
6679
+ .description('List all authorizations')
6680
+ .option('--limit <number>', 'Maximum number to return', '50')
6681
+ .option('--offset <number>', 'Number to skip', '0')
6682
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6683
+ .option('-v, --verbose', 'Show detailed information')
6684
+ .action(async (options, command) => {
6685
+ try {
6686
+ const globalOptions = command.parent.opts();
6687
+ const config = loadConfig(globalOptions.config);
6688
+ validateConfiguration(config);
6689
+
6690
+ const api = new ToothFairyAPI(
6691
+ config.baseUrl,
6692
+ config.aiUrl,
6693
+ config.aiStreamUrl,
6694
+ config.apiKey,
6695
+ config.workspaceId,
6696
+ globalOptions.verbose || options.verbose
6697
+ );
6698
+
6699
+ const spinner = ora('Fetching authorizations...').start();
6700
+ const result = await api.listAuthorizations(
6701
+ parseInt(options.limit),
6702
+ parseInt(options.offset)
6703
+ );
6704
+ spinner.stop();
6705
+
6706
+ if (options.output === 'json') {
6707
+ console.log(JSON.stringify(result, null, 2));
6708
+ } else {
6709
+ const auths = Array.isArray(result) ? result : result.items || [];
6710
+ console.log(chalk.green.bold(`Found ${auths.length} authorization(s)`));
6711
+ auths.forEach(auth => {
6712
+ console.log(chalk.cyan(` • ${auth.name || 'Unnamed'} (${auth.id})`));
6713
+ });
6714
+ }
6715
+ } catch (error) {
6716
+ console.error(chalk.red(`Error listing authorizations: ${error.message}`));
6717
+ process.exit(1);
6718
+ }
6719
+ });
6720
+
6721
+ program
6722
+ .command('get-authorization')
6723
+ .description('Get details of a specific authorization')
6724
+ .argument('<id>', 'Authorization ID')
6725
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6726
+ .option('-v, --verbose', 'Show detailed information')
6727
+ .action(async (authId, options, command) => {
6728
+ try {
6729
+ const globalOptions = command.parent.opts();
6730
+ const config = loadConfig(globalOptions.config);
6731
+ validateConfiguration(config);
6732
+
6733
+ const api = new ToothFairyAPI(
6734
+ config.baseUrl,
6735
+ config.aiUrl,
6736
+ config.aiStreamUrl,
6737
+ config.apiKey,
6738
+ config.workspaceId,
6739
+ globalOptions.verbose || options.verbose
6740
+ );
6741
+
6742
+ const spinner = ora('Fetching authorization...').start();
6743
+ const result = await api.getAuthorization(authId);
6744
+ spinner.stop();
6745
+
6746
+ if (options.output === 'json') {
6747
+ console.log(JSON.stringify(result, null, 2));
6748
+ } else {
6749
+ console.log(chalk.green.bold('Authorization Details'));
6750
+ console.log(chalk.dim(`ID: ${result.id}`));
6751
+ console.log(chalk.dim(`Name: ${result.name || 'N/A'}`));
6752
+ console.log(chalk.dim(`Type: ${result.type || 'N/A'}`));
6753
+ }
6754
+ } catch (error) {
6755
+ console.error(chalk.red(`Error getting authorization: ${error.message}`));
6756
+ process.exit(1);
6757
+ }
6758
+ });
6759
+
6760
+ program
6761
+ .command('update-authorization')
6762
+ .description('Update an existing authorization')
6763
+ .option('--id <id>', 'Authorization ID')
6764
+ .option('--name <name>', 'Authorization name')
6765
+ .option('--type <type>', 'Authorization type')
6766
+ .option('--config <config>', 'Authorization configuration (JSON)')
6767
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6768
+ .option('-v, --verbose', 'Show detailed information')
6769
+ .action(async (options, command) => {
6770
+ try {
6771
+ const globalOptions = command.parent.opts();
6772
+ const config = loadConfig(globalOptions.config);
6773
+ validateConfiguration(config);
6774
+
6775
+ const api = new ToothFairyAPI(
6776
+ config.baseUrl,
6777
+ config.aiUrl,
6778
+ config.aiStreamUrl,
6779
+ config.apiKey,
6780
+ config.workspaceId,
6781
+ globalOptions.verbose || options.verbose
6782
+ );
6783
+
6784
+ let authConfig = {};
6785
+ if (options.config) {
6786
+ try {
6787
+ authConfig = JSON.parse(options.config);
6788
+ } catch (e) {
6789
+ console.error(chalk.red('Invalid JSON in config'));
6790
+ process.exit(1);
6791
+ }
6792
+ }
6793
+
6794
+ const spinner = ora('Updating authorization...').start();
6795
+ const result = await api.updateAuthorization({
6796
+ id: options.id,
6797
+ name: options.name,
6798
+ type: options.type,
6799
+ ...authConfig,
6800
+ });
6801
+ spinner.stop();
6802
+
6803
+ if (options.output === 'json') {
6804
+ console.log(JSON.stringify(result, null, 2));
6805
+ } else {
6806
+ console.log(chalk.green.bold('✅ Authorization updated successfully!'));
6807
+ }
6808
+ } catch (error) {
6809
+ console.error(chalk.red(`Error updating authorization: ${error.message}`));
6810
+ process.exit(1);
6811
+ }
6812
+ });
6813
+
6814
+ program
6815
+ .command('delete-authorization')
6816
+ .description('Delete an authorization')
6817
+ .argument('<id>', 'Authorization ID')
6818
+ .option('--confirm', 'Skip confirmation prompt')
6819
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6820
+ .option('-v, --verbose', 'Show detailed information')
6821
+ .action(async (authId, options, command) => {
6822
+ try {
6823
+ const globalOptions = command.parent.opts();
6824
+ const config = loadConfig(globalOptions.config);
6825
+ validateConfiguration(config);
6826
+
6827
+ const api = new ToothFairyAPI(
6828
+ config.baseUrl,
6829
+ config.aiUrl,
6830
+ config.aiStreamUrl,
6831
+ config.apiKey,
6832
+ config.workspaceId,
6833
+ globalOptions.verbose || options.verbose
6834
+ );
6835
+
6836
+ if (!options.confirm) {
6837
+ const readline = require('readline');
6838
+ const rl = readline.createInterface({
6839
+ input: process.stdin,
6840
+ output: process.stdout,
6841
+ });
6842
+
6843
+ const answer = await new Promise((resolve) => {
6844
+ rl.question(
6845
+ chalk.yellow(
6846
+ `⚠️ Are you sure you want to delete authorization ${authId}? (y/N): `
6847
+ ),
6848
+ resolve
6849
+ );
6850
+ });
6851
+
6852
+ rl.close();
6853
+
6854
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
6855
+ console.log(chalk.gray('Deletion cancelled.'));
6856
+ process.exit(0);
6857
+ }
6858
+ }
6859
+
6860
+ const spinner = ora('Deleting authorization...').start();
6861
+ const result = await api.deleteAuthorization(authId);
6862
+ spinner.stop();
6863
+
6864
+ if (options.output === 'json') {
6865
+ console.log(JSON.stringify(result, null, 2));
6866
+ } else {
6867
+ console.log(chalk.green.bold('✅ Authorization deleted successfully!'));
6868
+ }
6869
+ } catch (error) {
6870
+ console.error(chalk.red(`Error deleting authorization: ${error.message}`));
6871
+ process.exit(1);
6872
+ }
6873
+ });
6874
+
6875
+ // Benchmark Management Commands
6876
+ program
6877
+ .command('create-benchmark')
6878
+ .description('Create a new benchmark')
6879
+ .option('--name <name>', 'Benchmark name')
6880
+ .option('--description <description>', 'Benchmark description')
6881
+ .option('--questions <questions>', 'Questions JSON array')
6882
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6883
+ .option('-v, --verbose', 'Show detailed information')
6884
+ .action(async (options, command) => {
6885
+ try {
6886
+ const globalOptions = command.parent.opts();
6887
+ const config = loadConfig(globalOptions.config);
6888
+ validateConfiguration(config);
6889
+
6890
+ const api = new ToothFairyAPI(
6891
+ config.baseUrl,
6892
+ config.aiUrl,
6893
+ config.aiStreamUrl,
6894
+ config.apiKey,
6895
+ config.workspaceId,
6896
+ globalOptions.verbose || options.verbose
6897
+ );
6898
+
6899
+ let questions = [];
6900
+ if (options.questions) {
6901
+ try {
6902
+ questions = JSON.parse(options.questions);
6903
+ } catch (e) {
6904
+ console.error(chalk.red('Invalid JSON in questions'));
6905
+ process.exit(1);
6906
+ }
6907
+ }
6908
+
6909
+ const spinner = ora('Creating benchmark...').start();
6910
+ const result = await api.createBenchmark({
6911
+ name: options.name,
6912
+ description: options.description,
6913
+ questions: questions,
6914
+ });
6915
+ spinner.stop();
6916
+
6917
+ if (options.output === 'json') {
6918
+ console.log(JSON.stringify(result, null, 2));
6919
+ } else {
6920
+ console.log(chalk.green.bold('✅ Benchmark created successfully!'));
6921
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
6922
+ }
6923
+ } catch (error) {
6924
+ console.error(chalk.red(`Error creating benchmark: ${error.message}`));
6925
+ process.exit(1);
6926
+ }
6927
+ });
6928
+
6929
+ program
6930
+ .command('list-benchmarks')
6931
+ .description('List all benchmarks')
6932
+ .option('--limit <number>', 'Maximum number to return', '50')
6933
+ .option('--offset <number>', 'Number to skip', '0')
6934
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6935
+ .option('-v, --verbose', 'Show detailed information')
6936
+ .action(async (options, command) => {
6937
+ try {
6938
+ const globalOptions = command.parent.opts();
6939
+ const config = loadConfig(globalOptions.config);
6940
+ validateConfiguration(config);
6941
+
6942
+ const api = new ToothFairyAPI(
6943
+ config.baseUrl,
6944
+ config.aiUrl,
6945
+ config.aiStreamUrl,
6946
+ config.apiKey,
6947
+ config.workspaceId,
6948
+ globalOptions.verbose || options.verbose
6949
+ );
6950
+
6951
+ const spinner = ora('Fetching benchmarks...').start();
6952
+ const result = await api.listBenchmarks(
6953
+ parseInt(options.limit),
6954
+ parseInt(options.offset)
6955
+ );
6956
+ spinner.stop();
6957
+
6958
+ if (options.output === 'json') {
6959
+ console.log(JSON.stringify(result, null, 2));
6960
+ } else {
6961
+ const benchmarks = Array.isArray(result) ? result : result.items || [];
6962
+ console.log(chalk.green.bold(`Found ${benchmarks.length} benchmark(s)`));
6963
+ benchmarks.forEach(bm => {
6964
+ console.log(chalk.cyan(` • ${bm.name || 'Unnamed'} (${bm.id})`));
6965
+ });
6966
+ }
6967
+ } catch (error) {
6968
+ console.error(chalk.red(`Error listing benchmarks: ${error.message}`));
6969
+ process.exit(1);
6970
+ }
6971
+ });
6972
+
6973
+ program
6974
+ .command('get-benchmark')
6975
+ .description('Get details of a specific benchmark')
6976
+ .argument('<id>', 'Benchmark ID')
6977
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
6978
+ .option('-v, --verbose', 'Show detailed information')
6979
+ .action(async (benchmarkId, options, command) => {
6980
+ try {
6981
+ const globalOptions = command.parent.opts();
6982
+ const config = loadConfig(globalOptions.config);
6983
+ validateConfiguration(config);
6984
+
6985
+ const api = new ToothFairyAPI(
6986
+ config.baseUrl,
6987
+ config.aiUrl,
6988
+ config.aiStreamUrl,
6989
+ config.apiKey,
6990
+ config.workspaceId,
6991
+ globalOptions.verbose || options.verbose
6992
+ );
6993
+
6994
+ const spinner = ora('Fetching benchmark...').start();
6995
+ const result = await api.getBenchmark(benchmarkId);
6996
+ spinner.stop();
6997
+
6998
+ if (options.output === 'json') {
6999
+ console.log(JSON.stringify(result, null, 2));
7000
+ } else {
7001
+ console.log(chalk.green.bold('Benchmark Details'));
7002
+ console.log(chalk.dim(`ID: ${result.id}`));
7003
+ console.log(chalk.dim(`Name: ${result.name || 'N/A'}`));
7004
+ console.log(chalk.dim(`Description: ${result.description || 'N/A'}`));
7005
+ }
7006
+ } catch (error) {
7007
+ console.error(chalk.red(`Error getting benchmark: ${error.message}`));
7008
+ process.exit(1);
7009
+ }
7010
+ });
7011
+
7012
+ program
7013
+ .command('update-benchmark')
7014
+ .description('Update an existing benchmark')
7015
+ .option('--id <id>', 'Benchmark ID')
7016
+ .option('--name <name>', 'Benchmark name')
7017
+ .option('--description <description>', 'Benchmark description')
7018
+ .option('--questions <questions>', 'Questions JSON array')
7019
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7020
+ .option('-v, --verbose', 'Show detailed information')
7021
+ .action(async (options, command) => {
7022
+ try {
7023
+ const globalOptions = command.parent.opts();
7024
+ const config = loadConfig(globalOptions.config);
7025
+ validateConfiguration(config);
7026
+
7027
+ const api = new ToothFairyAPI(
7028
+ config.baseUrl,
7029
+ config.aiUrl,
7030
+ config.aiStreamUrl,
7031
+ config.apiKey,
7032
+ config.workspaceId,
7033
+ globalOptions.verbose || options.verbose
7034
+ );
7035
+
7036
+ let questions = undefined;
7037
+ if (options.questions) {
7038
+ try {
7039
+ questions = JSON.parse(options.questions);
7040
+ } catch (e) {
7041
+ console.error(chalk.red('Invalid JSON in questions'));
7042
+ process.exit(1);
7043
+ }
7044
+ }
7045
+
7046
+ const spinner = ora('Updating benchmark...').start();
7047
+ const result = await api.updateBenchmark({
7048
+ id: options.id,
7049
+ name: options.name,
7050
+ description: options.description,
7051
+ questions: questions,
7052
+ });
7053
+ spinner.stop();
7054
+
7055
+ if (options.output === 'json') {
7056
+ console.log(JSON.stringify(result, null, 2));
7057
+ } else {
7058
+ console.log(chalk.green.bold('✅ Benchmark updated successfully!'));
7059
+ }
7060
+ } catch (error) {
7061
+ console.error(chalk.red(`Error updating benchmark: ${error.message}`));
7062
+ process.exit(1);
7063
+ }
7064
+ });
7065
+
7066
+ program
7067
+ .command('delete-benchmark')
7068
+ .description('Delete a benchmark')
7069
+ .argument('<id>', 'Benchmark ID')
7070
+ .option('--confirm', 'Skip confirmation prompt')
7071
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7072
+ .option('-v, --verbose', 'Show detailed information')
7073
+ .action(async (benchmarkId, options, command) => {
7074
+ try {
7075
+ const globalOptions = command.parent.opts();
7076
+ const config = loadConfig(globalOptions.config);
7077
+ validateConfiguration(config);
7078
+
7079
+ const api = new ToothFairyAPI(
7080
+ config.baseUrl,
7081
+ config.aiUrl,
7082
+ config.aiStreamUrl,
7083
+ config.apiKey,
7084
+ config.workspaceId,
7085
+ globalOptions.verbose || options.verbose
7086
+ );
7087
+
7088
+ if (!options.confirm) {
7089
+ const readline = require('readline');
7090
+ const rl = readline.createInterface({
7091
+ input: process.stdin,
7092
+ output: process.stdout,
7093
+ });
7094
+
7095
+ const answer = await new Promise((resolve) => {
7096
+ rl.question(
7097
+ chalk.yellow(
7098
+ `⚠️ Are you sure you want to delete benchmark ${benchmarkId}? (y/N): `
7099
+ ),
7100
+ resolve
7101
+ );
7102
+ });
7103
+
7104
+ rl.close();
7105
+
7106
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
7107
+ console.log(chalk.gray('Deletion cancelled.'));
7108
+ process.exit(0);
7109
+ }
7110
+ }
7111
+
7112
+ const spinner = ora('Deleting benchmark...').start();
7113
+ const result = await api.deleteBenchmark(benchmarkId);
7114
+ spinner.stop();
7115
+
7116
+ if (options.output === 'json') {
7117
+ console.log(JSON.stringify(result, null, 2));
7118
+ } else {
7119
+ console.log(chalk.green.bold('✅ Benchmark deleted successfully!'));
7120
+ }
7121
+ } catch (error) {
7122
+ console.error(chalk.red(`Error deleting benchmark: ${error.message}`));
7123
+ process.exit(1);
7124
+ }
7125
+ });
7126
+
7127
+ // Billing Commands
7128
+ program
7129
+ .command('billing-month-costs')
7130
+ .description('Get monthly usage and cost information')
7131
+ .argument('<month>', 'Month number (1-12)')
7132
+ .argument('<year>', 'Year (4-digit)')
7133
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7134
+ .option('-v, --verbose', 'Show detailed information')
7135
+ .action(async (month, year, options, command) => {
7136
+ try {
7137
+ const globalOptions = command.parent.opts();
7138
+ const config = loadConfig(globalOptions.config);
7139
+ validateConfiguration(config);
7140
+
7141
+ const api = new ToothFairyAPI(
7142
+ config.baseUrl,
7143
+ config.aiUrl,
7144
+ config.aiStreamUrl,
7145
+ config.apiKey,
7146
+ config.workspaceId,
7147
+ globalOptions.verbose || options.verbose
7148
+ );
7149
+
7150
+ const monthNum = parseInt(month);
7151
+ const yearNum = parseInt(year);
7152
+
7153
+ if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
7154
+ console.error(chalk.red('Error: Month must be a number between 1 and 12'));
7155
+ process.exit(1);
7156
+ }
7157
+
7158
+ if (isNaN(yearNum) || yearNum < 2020 || yearNum > 2100) {
7159
+ console.error(chalk.red('Error: Year must be a valid 4-digit year'));
7160
+ process.exit(1);
7161
+ }
7162
+
7163
+ const spinner = ora('Fetching billing information...').start();
7164
+ const result = await api.getMonthCosts(monthNum, yearNum);
7165
+ spinner.stop();
7166
+
7167
+ if (options.output === 'json') {
7168
+ console.log(JSON.stringify(result, null, 2));
7169
+ } else {
7170
+ console.log(chalk.green.bold(`Billing Information for ${month}/${year}`));
7171
+ console.log();
7172
+
7173
+ if (result.apiUsage) {
7174
+ console.log(chalk.cyan('API Usage:'));
7175
+ if (result.apiUsage.totalUoI !== undefined) {
7176
+ console.log(chalk.dim(` Total Units of Interaction: ${result.apiUsage.totalUoI}`));
7177
+ }
7178
+ if (result.apiUsage.totalCostUSD !== undefined) {
7179
+ console.log(chalk.dim(` Total Cost: $${result.apiUsage.totalCostUSD}`));
7180
+ }
7181
+ }
7182
+
7183
+ if (result.trainingUsage) {
7184
+ console.log(chalk.cyan('Training Usage:'));
7185
+ console.log(chalk.dim(` See --verbose for details`));
7186
+ }
7187
+ }
7188
+ } catch (error) {
7189
+ console.error(chalk.red(`Error getting billing information: ${error.message}`));
7190
+ process.exit(1);
7191
+ }
7192
+ });
7193
+
7194
+ // Channel Management Commands
7195
+ program
7196
+ .command('create-channel')
7197
+ .description('Create a new communication channel')
7198
+ .option('--name <name>', 'Channel name')
7199
+ .option('--channel <channel>', 'Channel type (sms|whatsapp|email)')
7200
+ .option('--provider <provider>', 'Service provider')
7201
+ .option('--senderid <senderid>', 'Sender ID')
7202
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7203
+ .option('-v, --verbose', 'Show detailed information')
7204
+ .action(async (options, command) => {
7205
+ try {
7206
+ const globalOptions = command.parent.opts();
7207
+ const config = loadConfig(globalOptions.config);
7208
+ validateConfiguration(config);
7209
+
7210
+ const api = new ToothFairyAPI(
7211
+ config.baseUrl,
7212
+ config.aiUrl,
7213
+ config.aiStreamUrl,
7214
+ config.apiKey,
7215
+ config.workspaceId,
7216
+ globalOptions.verbose || options.verbose
7217
+ );
7218
+
7219
+ const spinner = ora('Creating channel...').start();
7220
+ const result = await api.createChannel({
7221
+ name: options.name,
7222
+ channel: options.channel,
7223
+ provider: options.provider,
7224
+ senderid: options.senderid,
7225
+ });
7226
+ spinner.stop();
7227
+
7228
+ if (options.output === 'json') {
7229
+ console.log(JSON.stringify(result, null, 2));
7230
+ } else {
7231
+ console.log(chalk.green.bold('✅ Channel created successfully!'));
7232
+ console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
7233
+ }
7234
+ } catch (error) {
7235
+ console.error(chalk.red(`Error creating channel: ${error.message}`));
7236
+ process.exit(1);
7237
+ }
7238
+ });
7239
+
7240
+ program
7241
+ .command('list-channels')
7242
+ .description('List all channels')
7243
+ .option('--limit <number>', 'Maximum number to return', '50')
7244
+ .option('--offset <number>', 'Number to skip', '0')
7245
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7246
+ .option('-v, --verbose', 'Show detailed information')
7247
+ .action(async (options, command) => {
7248
+ try {
7249
+ const globalOptions = command.parent.opts();
7250
+ const config = loadConfig(globalOptions.config);
7251
+ validateConfiguration(config);
7252
+
7253
+ const api = new ToothFairyAPI(
7254
+ config.baseUrl,
7255
+ config.aiUrl,
7256
+ config.aiStreamUrl,
7257
+ config.apiKey,
7258
+ config.workspaceId,
7259
+ globalOptions.verbose || options.verbose
7260
+ );
7261
+
7262
+ const spinner = ora('Fetching channels...').start();
7263
+ const result = await api.listChannels(
7264
+ parseInt(options.limit),
7265
+ parseInt(options.offset)
7266
+ );
7267
+ spinner.stop();
7268
+
7269
+ if (options.output === 'json') {
7270
+ console.log(JSON.stringify(result, null, 2));
7271
+ } else {
7272
+ const channels = Array.isArray(result) ? result : result.items || [];
7273
+ console.log(chalk.green.bold(`Found ${channels.length} channel(s)`));
7274
+ channels.forEach(ch => {
7275
+ console.log(chalk.cyan(` • ${ch.name || 'Unnamed'} (${ch.id})`));
7276
+ });
7277
+ }
7278
+ } catch (error) {
7279
+ console.error(chalk.red(`Error listing channels: ${error.message}`));
7280
+ process.exit(1);
7281
+ }
7282
+ });
7283
+
7284
+ program
7285
+ .command('get-channel')
7286
+ .description('Get details of a specific channel')
7287
+ .argument('<id>', 'Channel ID')
7288
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7289
+ .option('-v, --verbose', 'Show detailed information')
7290
+ .action(async (channelId, options, command) => {
7291
+ try {
7292
+ const globalOptions = command.parent.opts();
7293
+ const config = loadConfig(globalOptions.config);
7294
+ validateConfiguration(config);
7295
+
7296
+ const api = new ToothFairyAPI(
7297
+ config.baseUrl,
7298
+ config.aiUrl,
7299
+ config.aiStreamUrl,
7300
+ config.apiKey,
7301
+ config.workspaceId,
7302
+ globalOptions.verbose || options.verbose
7303
+ );
7304
+
7305
+ const spinner = ora('Fetching channel...').start();
7306
+ const result = await api.getChannel(channelId);
7307
+ spinner.stop();
7308
+
7309
+ if (options.output === 'json') {
7310
+ console.log(JSON.stringify(result, null, 2));
7311
+ } else {
7312
+ console.log(chalk.green.bold('Channel Details'));
7313
+ console.log(chalk.dim(`ID: ${result.id}`));
7314
+ console.log(chalk.dim(`Name: ${result.name || 'N/A'}`));
7315
+ console.log(chalk.dim(`Channel: ${result.channel || 'N/A'}`));
7316
+ console.log(chalk.dim(`Provider: ${result.provider || 'N/A'}`));
7317
+ }
7318
+ } catch (error) {
7319
+ console.error(chalk.red(`Error getting channel: ${error.message}`));
7320
+ process.exit(1);
7321
+ }
7322
+ });
7323
+
7324
+ program
7325
+ .command('update-channel')
7326
+ .description('Update an existing channel')
7327
+ .option('--id <id>', 'Channel ID')
7328
+ .option('--name <name>', 'Channel name')
7329
+ .option('--channel <channel>', 'Channel type')
7330
+ .option('--provider <provider>', 'Service provider')
7331
+ .option('--senderid <senderid>', 'Sender ID')
7332
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7333
+ .option('-v, --verbose', 'Show detailed information')
7334
+ .action(async (options, command) => {
7335
+ try {
7336
+ const globalOptions = command.parent.opts();
7337
+ const config = loadConfig(globalOptions.config);
7338
+ validateConfiguration(config);
7339
+
7340
+ const api = new ToothFairyAPI(
7341
+ config.baseUrl,
7342
+ config.aiUrl,
7343
+ config.aiStreamUrl,
7344
+ config.apiKey,
7345
+ config.workspaceId,
7346
+ globalOptions.verbose || options.verbose
7347
+ );
7348
+
7349
+ const spinner = ora('Updating channel...').start();
7350
+ const result = await api.updateChannel({
7351
+ id: options.id,
7352
+ name: options.name,
7353
+ channel: options.channel,
7354
+ provider: options.provider,
7355
+ senderid: options.senderid,
7356
+ });
7357
+ spinner.stop();
7358
+
7359
+ if (options.output === 'json') {
7360
+ console.log(JSON.stringify(result, null, 2));
7361
+ } else {
7362
+ console.log(chalk.green.bold('✅ Channel updated successfully!'));
7363
+ }
7364
+ } catch (error) {
7365
+ console.error(chalk.red(`Error updating channel: ${error.message}`));
7366
+ process.exit(1);
7367
+ }
7368
+ });
7369
+
7370
+ program
7371
+ .command('delete-channel')
7372
+ .description('Delete a channel')
7373
+ .argument('<id>', 'Channel ID')
7374
+ .option('--confirm', 'Skip confirmation prompt')
7375
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7376
+ .option('-v, --verbose', 'Show detailed information')
7377
+ .action(async (channelId, options, command) => {
7378
+ try {
7379
+ const globalOptions = command.parent.opts();
7380
+ const config = loadConfig(globalOptions.config);
7381
+ validateConfiguration(config);
7382
+
7383
+ const api = new ToothFairyAPI(
7384
+ config.baseUrl,
7385
+ config.aiUrl,
7386
+ config.aiStreamUrl,
7387
+ config.apiKey,
7388
+ config.workspaceId,
7389
+ globalOptions.verbose || options.verbose
7390
+ );
7391
+
7392
+ if (!options.confirm) {
7393
+ const readline = require('readline');
7394
+ const rl = readline.createInterface({
7395
+ input: process.stdin,
7396
+ output: process.stdout,
7397
+ });
7398
+
7399
+ const answer = await new Promise((resolve) => {
7400
+ rl.question(
7401
+ chalk.yellow(
7402
+ `⚠️ Are you sure you want to delete channel ${channelId}? (y/N): `
7403
+ ),
7404
+ resolve
7405
+ );
7406
+ });
7407
+
7408
+ rl.close();
7409
+
7410
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
7411
+ console.log(chalk.gray('Deletion cancelled.'));
7412
+ process.exit(0);
7413
+ }
7414
+ }
7415
+
7416
+ const spinner = ora('Deleting channel...').start();
7417
+ const result = await api.deleteChannel(channelId);
7418
+ spinner.stop();
7419
+
7420
+ if (options.output === 'json') {
7421
+ console.log(JSON.stringify(result, null, 2));
7422
+ } else {
7423
+ console.log(chalk.green.bold('✅ Channel deleted successfully!'));
7424
+ }
7425
+ } catch (error) {
7426
+ console.error(chalk.red(`Error deleting channel: ${error.message}`));
7427
+ process.exit(1);
7428
+ }
7429
+ });
7430
+
7431
+ // Connection Management Commands
7432
+ program
7433
+ .command('list-connections')
7434
+ .description('List all connections')
7435
+ .option('--limit <number>', 'Maximum number to return', '50')
7436
+ .option('--offset <number>', 'Number to skip', '0')
7437
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7438
+ .option('-v, --verbose', 'Show detailed information')
7439
+ .action(async (options, command) => {
7440
+ try {
7441
+ const globalOptions = command.parent.opts();
7442
+ const config = loadConfig(globalOptions.config);
7443
+ validateConfiguration(config);
7444
+
7445
+ const api = new ToothFairyAPI(
7446
+ config.baseUrl,
7447
+ config.aiUrl,
7448
+ config.aiStreamUrl,
7449
+ config.apiKey,
7450
+ config.workspaceId,
7451
+ globalOptions.verbose || options.verbose
7452
+ );
7453
+
7454
+ const spinner = ora('Fetching connections...').start();
7455
+ const result = await api.listConnections(
7456
+ parseInt(options.limit),
7457
+ parseInt(options.offset)
7458
+ );
7459
+ spinner.stop();
7460
+
7461
+ if (options.output === 'json') {
7462
+ console.log(JSON.stringify(result, null, 2));
7463
+ } else {
7464
+ const connections = Array.isArray(result) ? result : result.items || [];
7465
+ console.log(chalk.green.bold(`Found ${connections.length} connection(s)`));
7466
+ connections.forEach(conn => {
7467
+ console.log(chalk.cyan(` • ${conn.name || 'Unnamed'} (${conn.id})`));
7468
+ });
7469
+ }
7470
+ } catch (error) {
7471
+ console.error(chalk.red(`Error listing connections: ${error.message}`));
7472
+ process.exit(1);
7473
+ }
7474
+ });
7475
+
7476
+ program
7477
+ .command('get-connection')
7478
+ .description('Get details of a specific connection')
7479
+ .argument('<id>', 'Connection ID')
7480
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7481
+ .option('-v, --verbose', 'Show detailed information')
7482
+ .action(async (connectionId, options, command) => {
7483
+ try {
7484
+ const globalOptions = command.parent.opts();
7485
+ const config = loadConfig(globalOptions.config);
7486
+ validateConfiguration(config);
7487
+
7488
+ const api = new ToothFairyAPI(
7489
+ config.baseUrl,
7490
+ config.aiUrl,
7491
+ config.aiStreamUrl,
7492
+ config.apiKey,
7493
+ config.workspaceId,
7494
+ globalOptions.verbose || options.verbose
7495
+ );
7496
+
7497
+ const spinner = ora('Fetching connection...').start();
7498
+ const result = await api.getConnection(connectionId);
7499
+ spinner.stop();
7500
+
7501
+ if (options.output === 'json') {
7502
+ console.log(JSON.stringify(result, null, 2));
7503
+ } else {
7504
+ console.log(chalk.green.bold('Connection Details'));
7505
+ console.log(chalk.dim(`ID: ${result.id}`));
7506
+ console.log(chalk.dim(`Name: ${result.name || 'N/A'}`));
7507
+ console.log(chalk.dim(`Type: ${result.type || 'N/A'}`));
7508
+ console.log(chalk.dim(`Host: ${result.host || 'N/A'}`));
7509
+ }
7510
+ } catch (error) {
7511
+ console.error(chalk.red(`Error getting connection: ${error.message}`));
7512
+ process.exit(1);
7513
+ }
7514
+ });
7515
+
7516
+ program
7517
+ .command('delete-connection')
7518
+ .description('Delete a connection')
7519
+ .argument('<id>', 'Connection ID')
7520
+ .option('--confirm', 'Skip confirmation prompt')
7521
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7522
+ .option('-v, --verbose', 'Show detailed information')
7523
+ .action(async (connectionId, options, command) => {
7524
+ try {
7525
+ const globalOptions = command.parent.opts();
7526
+ const config = loadConfig(globalOptions.config);
7527
+ validateConfiguration(config);
7528
+
7529
+ const api = new ToothFairyAPI(
7530
+ config.baseUrl,
7531
+ config.aiUrl,
7532
+ config.aiStreamUrl,
7533
+ config.apiKey,
7534
+ config.workspaceId,
7535
+ globalOptions.verbose || options.verbose
7536
+ );
7537
+
7538
+ if (!options.confirm) {
7539
+ const readline = require('readline');
7540
+ const rl = readline.createInterface({
7541
+ input: process.stdin,
7542
+ output: process.stdout,
7543
+ });
7544
+
7545
+ const answer = await new Promise((resolve) => {
7546
+ rl.question(
7547
+ chalk.yellow(
7548
+ `⚠️ Are you sure you want to delete connection ${connectionId}? (y/N): `
7549
+ ),
7550
+ resolve
7551
+ );
7552
+ });
7553
+
7554
+ rl.close();
7555
+
7556
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
7557
+ console.log(chalk.gray('Deletion cancelled.'));
7558
+ process.exit(0);
7559
+ }
7560
+ }
7561
+
7562
+ const spinner = ora('Deleting connection...').start();
7563
+ const result = await api.deleteConnection(connectionId);
7564
+ spinner.stop();
7565
+
7566
+ if (options.output === 'json') {
7567
+ console.log(JSON.stringify(result, null, 2));
7568
+ } else {
7569
+ console.log(chalk.green.bold('✅ Connection deleted successfully!'));
7570
+ }
7571
+ } catch (error) {
7572
+ console.error(chalk.red(`Error deleting connection: ${error.message}`));
7573
+ process.exit(1);
7574
+ }
7575
+ });
7576
+
7577
+ // Dictionary Management Commands
7578
+ program
7579
+ .command('list-dictionaries')
7580
+ .description('List all dictionary entries')
7581
+ .option('--limit <number>', 'Maximum number to return', '50')
7582
+ .option('--offset <number>', 'Number to skip', '0')
7583
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7584
+ .option('-v, --verbose', 'Show detailed information')
7585
+ .action(async (options, command) => {
7586
+ try {
7587
+ const globalOptions = command.parent.opts();
7588
+ const config = loadConfig(globalOptions.config);
7589
+ validateConfiguration(config);
7590
+
7591
+ const api = new ToothFairyAPI(
7592
+ config.baseUrl,
7593
+ config.aiUrl,
7594
+ config.aiStreamUrl,
7595
+ config.apiKey,
7596
+ config.workspaceId,
7597
+ globalOptions.verbose || options.verbose
7598
+ );
7599
+
7600
+ const spinner = ora('Fetching dictionary entries...').start();
7601
+ const result = await api.listDictionaries(
7602
+ parseInt(options.limit),
7603
+ parseInt(options.offset)
7604
+ );
7605
+ spinner.stop();
7606
+
7607
+ if (options.output === 'json') {
7608
+ console.log(JSON.stringify(result, null, 2));
7609
+ } else {
7610
+ const dicts = Array.isArray(result) ? result : result.items || [];
7611
+ console.log(chalk.green.bold(`Found ${dicts.length} dictionary entr(y/ies)`));
7612
+ dicts.forEach(dict => {
7613
+ console.log(chalk.cyan(` • ${dict.sourceText || 'N/A'} → ${dict.targetText || 'N/A'} (${dict.id})`));
7614
+ });
7615
+ }
7616
+ } catch (error) {
7617
+ console.error(chalk.red(`Error listing dictionaries: ${error.message}`));
7618
+ process.exit(1);
7619
+ }
7620
+ });
7621
+
7622
+ program
7623
+ .command('get-dictionary')
7624
+ .description('Get details of a specific dictionary entry')
7625
+ .argument('<id>', 'Dictionary entry ID')
7626
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7627
+ .option('-v, --verbose', 'Show detailed information')
7628
+ .action(async (dictionaryId, options, command) => {
7629
+ try {
7630
+ const globalOptions = command.parent.opts();
7631
+ const config = loadConfig(globalOptions.config);
7632
+ validateConfiguration(config);
7633
+
7634
+ const api = new ToothFairyAPI(
7635
+ config.baseUrl,
7636
+ config.aiUrl,
7637
+ config.aiStreamUrl,
7638
+ config.apiKey,
7639
+ config.workspaceId,
7640
+ globalOptions.verbose || options.verbose
7641
+ );
7642
+
7643
+ const spinner = ora('Fetching dictionary entry...').start();
7644
+ const result = await api.getDictionary(dictionaryId);
7645
+ spinner.stop();
7646
+
7647
+ if (options.output === 'json') {
7648
+ console.log(JSON.stringify(result, null, 2));
7649
+ } else {
7650
+ console.log(chalk.green.bold('Dictionary Entry Details'));
7651
+ console.log(chalk.dim(`ID: ${result.id}`));
7652
+ console.log(chalk.dim(`Source: ${result.sourceText || 'N/A'}`));
7653
+ console.log(chalk.dim(`Target: ${result.targetText || 'N/A'}`));
7654
+ console.log(chalk.dim(`Source Language: ${result.sourceLanguage || 'N/A'}`));
7655
+ console.log(chalk.dim(`Target Language: ${result.targetLanguage || 'N/A'}`));
7656
+ }
7657
+ } catch (error) {
7658
+ console.error(chalk.red(`Error getting dictionary entry: ${error.message}`));
7659
+ process.exit(1);
7660
+ }
7661
+ });
7662
+
7663
+ // Embedding Management Commands
7664
+ program
7665
+ .command('get-embedding')
7666
+ .description('Get details of a specific embedding')
7667
+ .argument('<id>', 'Embedding ID')
7668
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7669
+ .option('-v, --verbose', 'Show detailed information')
7670
+ .action(async (embeddingId, options, command) => {
7671
+ try {
7672
+ const globalOptions = command.parent.opts();
7673
+ const config = loadConfig(globalOptions.config);
7674
+ validateConfiguration(config);
7675
+
7676
+ const api = new ToothFairyAPI(
7677
+ config.baseUrl,
7678
+ config.aiUrl,
7679
+ config.aiStreamUrl,
7680
+ config.apiKey,
7681
+ config.workspaceId,
7682
+ globalOptions.verbose || options.verbose
7683
+ );
7684
+
7685
+ const spinner = ora('Fetching embedding...').start();
7686
+ const result = await api.getEmbedding(embeddingId);
7687
+ spinner.stop();
7688
+
7689
+ if (options.output === 'json') {
7690
+ console.log(JSON.stringify(result, null, 2));
7691
+ } else {
7692
+ console.log(chalk.green.bold('Embedding Details'));
7693
+ console.log(chalk.dim(`ID: ${result.id}`));
7694
+ console.log(chalk.dim(`Chunk ID: ${result.chunk_id || 'N/A'}`));
7695
+ console.log(chalk.dim(`Title: ${result.title || 'N/A'}`));
7696
+ }
7697
+ } catch (error) {
7698
+ console.error(chalk.red(`Error getting embedding: ${error.message}`));
7699
+ process.exit(1);
7700
+ }
7701
+ });
7702
+
7703
+ // Settings Management Commands
7704
+ program
7705
+ .command('get-charting-settings')
7706
+ .description('Get charting settings for the workspace')
7707
+ .argument('<id>', 'Settings ID')
7708
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7709
+ .option('-v, --verbose', 'Show detailed information')
7710
+ .action(async (settingsId, options, command) => {
7711
+ try {
7712
+ const globalOptions = command.parent.opts();
7713
+ const config = loadConfig(globalOptions.config);
7714
+ validateConfiguration(config);
7715
+
7716
+ const api = new ToothFairyAPI(
7717
+ config.baseUrl,
7718
+ config.aiUrl,
7719
+ config.aiStreamUrl,
7720
+ config.apiKey,
7721
+ config.workspaceId,
7722
+ globalOptions.verbose || options.verbose
7723
+ );
7724
+
7725
+ const spinner = ora('Fetching charting settings...').start();
7726
+ const result = await api.getChartingSettings(settingsId);
7727
+ spinner.stop();
7728
+
7729
+ if (options.output === 'json') {
7730
+ console.log(JSON.stringify(result, null, 2));
7731
+ } else {
7732
+ console.log(chalk.green.bold('Charting Settings'));
7733
+ console.log(chalk.dim(`ID: ${result.id}`));
7734
+ console.log(chalk.dim(`Primary Color: ${result.primaryColor || 'N/A'}`));
7735
+ console.log(chalk.dim(`Secondary Color: ${result.secondaryColor || 'N/A'}`));
7736
+ }
7737
+ } catch (error) {
7738
+ console.error(chalk.red(`Error getting charting settings: ${error.message}`));
7739
+ process.exit(1);
7740
+ }
7741
+ });
7742
+
7743
+ program
7744
+ .command('update-charting-settings')
7745
+ .description('Update charting settings for the workspace')
7746
+ .argument('<id>', 'Settings ID')
7747
+ .option('--config <config>', 'Settings configuration (JSON)')
7748
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7749
+ .option('-v, --verbose', 'Show detailed information')
7750
+ .action(async (settingsId, options, command) => {
7751
+ try {
7752
+ const globalOptions = command.parent.opts();
7753
+ const config = loadConfig(globalOptions.config);
7754
+ validateConfiguration(config);
7755
+
7756
+ const api = new ToothFairyAPI(
7757
+ config.baseUrl,
7758
+ config.aiUrl,
7759
+ config.aiStreamUrl,
7760
+ config.apiKey,
7761
+ config.workspaceId,
7762
+ globalOptions.verbose || options.verbose
7763
+ );
7764
+
7765
+ let settingsConfig = {};
7766
+ if (options.config) {
7767
+ try {
7768
+ settingsConfig = JSON.parse(options.config);
7769
+ } catch (e) {
7770
+ console.error(chalk.red('Invalid JSON in config'));
7771
+ process.exit(1);
7772
+ }
7773
+ }
7774
+
7775
+ const spinner = ora('Updating charting settings...').start();
7776
+ const result = await api.updateChartingSettings({
7777
+ id: settingsId,
7778
+ ...settingsConfig,
7779
+ });
7780
+ spinner.stop();
7781
+
7782
+ if (options.output === 'json') {
7783
+ console.log(JSON.stringify(result, null, 2));
7784
+ } else {
7785
+ console.log(chalk.green.bold('✅ Charting settings updated successfully!'));
7786
+ }
7787
+ } catch (error) {
7788
+ console.error(chalk.red(`Error updating charting settings: ${error.message}`));
7789
+ process.exit(1);
7790
+ }
7791
+ });
7792
+
7793
+ program
7794
+ .command('get-embeddings-settings')
7795
+ .description('Get embeddings settings for the workspace')
7796
+ .argument('<id>', 'Settings ID')
7797
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7798
+ .option('-v, --verbose', 'Show detailed information')
7799
+ .action(async (settingsId, options, command) => {
7800
+ try {
7801
+ const globalOptions = command.parent.opts();
7802
+ const config = loadConfig(globalOptions.config);
7803
+ validateConfiguration(config);
7804
+
7805
+ const api = new ToothFairyAPI(
7806
+ config.baseUrl,
7807
+ config.aiUrl,
7808
+ config.aiStreamUrl,
7809
+ config.apiKey,
7810
+ config.workspaceId,
7811
+ globalOptions.verbose || options.verbose
7812
+ );
7813
+
7814
+ const spinner = ora('Fetching embeddings settings...').start();
7815
+ const result = await api.getEmbeddingsSettings(settingsId);
7816
+ spinner.stop();
7817
+
7818
+ if (options.output === 'json') {
7819
+ console.log(JSON.stringify(result, null, 2));
7820
+ } else {
7821
+ console.log(chalk.green.bold('Embeddings Settings'));
7822
+ console.log(chalk.dim(`ID: ${result.id}`));
7823
+ console.log(chalk.dim(`Max Chunk Words: ${result.maxChunkWords || 'N/A'}`));
7824
+ console.log(chalk.dim(`Chunking Strategy: ${result.chunkingStrategy || 'N/A'}`));
7825
+ }
7826
+ } catch (error) {
7827
+ console.error(chalk.red(`Error getting embeddings settings: ${error.message}`));
7828
+ process.exit(1);
7829
+ }
7830
+ });
7831
+
7832
+ program
7833
+ .command('update-embeddings-settings')
7834
+ .description('Update embeddings settings for the workspace')
7835
+ .argument('<id>', 'Settings ID')
7836
+ .option('--config <config>', 'Settings configuration (JSON)')
7837
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7838
+ .option('-v, --verbose', 'Show detailed information')
7839
+ .action(async (settingsId, options, command) => {
7840
+ try {
7841
+ const globalOptions = command.parent.opts();
7842
+ const config = loadConfig(globalOptions.config);
7843
+ validateConfiguration(config);
7844
+
7845
+ const api = new ToothFairyAPI(
7846
+ config.baseUrl,
7847
+ config.aiUrl,
7848
+ config.aiStreamUrl,
7849
+ config.apiKey,
7850
+ config.workspaceId,
7851
+ globalOptions.verbose || options.verbose
7852
+ );
7853
+
7854
+ let settingsConfig = {};
7855
+ if (options.config) {
7856
+ try {
7857
+ settingsConfig = JSON.parse(options.config);
7858
+ } catch (e) {
7859
+ console.error(chalk.red('Invalid JSON in config'));
7860
+ process.exit(1);
7861
+ }
7862
+ }
7863
+
7864
+ const spinner = ora('Updating embeddings settings...').start();
7865
+ const result = await api.updateEmbeddingsSettings({
7866
+ id: settingsId,
7867
+ ...settingsConfig,
7868
+ });
7869
+ spinner.stop();
7870
+
7871
+ if (options.output === 'json') {
7872
+ console.log(JSON.stringify(result, null, 2));
7873
+ } else {
7874
+ console.log(chalk.green.bold('✅ Embeddings settings updated successfully!'));
7875
+ }
7876
+ } catch (error) {
7877
+ console.error(chalk.red(`Error updating embeddings settings: ${error.message}`));
7878
+ process.exit(1);
7879
+ }
7880
+ });
7881
+
7882
+ // Stream Management Commands
7883
+ program
7884
+ .command('list-streams')
7885
+ .description('List all streams')
7886
+ .option('--limit <number>', 'Maximum number to return', '50')
7887
+ .option('--offset <number>', 'Number to skip', '0')
7888
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7889
+ .option('-v, --verbose', 'Show detailed information')
7890
+ .action(async (options, command) => {
7891
+ try {
7892
+ const globalOptions = command.parent.opts();
7893
+ const config = loadConfig(globalOptions.config);
7894
+ validateConfiguration(config);
7895
+
7896
+ const api = new ToothFairyAPI(
7897
+ config.baseUrl,
7898
+ config.aiUrl,
7899
+ config.aiStreamUrl,
7900
+ config.apiKey,
7901
+ config.workspaceId,
7902
+ globalOptions.verbose || options.verbose
7903
+ );
7904
+
7905
+ const spinner = ora('Fetching streams...').start();
7906
+ const result = await api.listStreams(
7907
+ parseInt(options.limit),
7908
+ parseInt(options.offset)
7909
+ );
7910
+ spinner.stop();
7911
+
7912
+ if (options.output === 'json') {
7913
+ console.log(JSON.stringify(result, null, 2));
7914
+ } else {
7915
+ const streams = Array.isArray(result) ? result : result.items || [];
7916
+ console.log(chalk.green.bold(`Found ${streams.length} stream(s)`));
7917
+ streams.forEach(stream => {
7918
+ console.log(chalk.cyan(` • ${stream.id}`));
7919
+ });
7920
+ }
7921
+ } catch (error) {
7922
+ console.error(chalk.red(`Error listing streams: ${error.message}`));
7923
+ process.exit(1);
7924
+ }
7925
+ });
7926
+
7927
+ program
7928
+ .command('get-stream')
7929
+ .description('Get details of a specific stream')
7930
+ .argument('<id>', 'Stream ID')
7931
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
7932
+ .option('-v, --verbose', 'Show detailed information')
7933
+ .action(async (streamId, options, command) => {
7934
+ try {
7935
+ const globalOptions = command.parent.opts();
7936
+ const config = loadConfig(globalOptions.config);
7937
+ validateConfiguration(config);
7938
+
7939
+ const api = new ToothFairyAPI(
7940
+ config.baseUrl,
7941
+ config.aiUrl,
7942
+ config.aiStreamUrl,
7943
+ config.apiKey,
7944
+ config.workspaceId,
7945
+ globalOptions.verbose || options.verbose
7946
+ );
7947
+
7948
+ const spinner = ora('Fetching stream...').start();
7949
+ const result = await api.getStream(streamId);
7950
+ spinner.stop();
7951
+
7952
+ if (options.output === 'json') {
7953
+ console.log(JSON.stringify(result, null, 2));
7954
+ } else {
7955
+ console.log(chalk.green.bold('Stream Details'));
7956
+ console.log(chalk.dim(`ID: ${result.id}`));
7957
+ console.log(chalk.dim(`Type: ${result.type || 'N/A'}`));
7958
+ console.log(chalk.dim(`Status: ${result.status || 'N/A'}`));
7959
+ }
7960
+ } catch (error) {
7961
+ console.error(chalk.red(`Error getting stream: ${error.message}`));
7962
+ process.exit(1);
7963
+ }
7964
+ });
7965
+
3954
7966
  // Parse command line arguments
3955
7967
  program.parse();