@indemn/cli 1.0.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.
Files changed (126) hide show
  1. package/.claude-plugin/plugin.json +13 -0
  2. package/.mcp.json +9 -0
  3. package/README.md +225 -0
  4. package/dist/cli/agents.d.ts +3 -0
  5. package/dist/cli/agents.d.ts.map +1 -0
  6. package/dist/cli/agents.js +187 -0
  7. package/dist/cli/agents.js.map +1 -0
  8. package/dist/cli/auth.d.ts +3 -0
  9. package/dist/cli/auth.d.ts.map +1 -0
  10. package/dist/cli/auth.js +161 -0
  11. package/dist/cli/auth.js.map +1 -0
  12. package/dist/cli/chat.d.ts +3 -0
  13. package/dist/cli/chat.d.ts.map +1 -0
  14. package/dist/cli/chat.js +142 -0
  15. package/dist/cli/chat.js.map +1 -0
  16. package/dist/cli/config.d.ts +3 -0
  17. package/dist/cli/config.d.ts.map +1 -0
  18. package/dist/cli/config.js +119 -0
  19. package/dist/cli/config.js.map +1 -0
  20. package/dist/cli/eval.d.ts +3 -0
  21. package/dist/cli/eval.d.ts.map +1 -0
  22. package/dist/cli/eval.js +717 -0
  23. package/dist/cli/eval.js.map +1 -0
  24. package/dist/cli/functions.d.ts +3 -0
  25. package/dist/cli/functions.d.ts.map +1 -0
  26. package/dist/cli/functions.js +346 -0
  27. package/dist/cli/functions.js.map +1 -0
  28. package/dist/cli/index.d.ts +3 -0
  29. package/dist/cli/index.d.ts.map +1 -0
  30. package/dist/cli/index.js +64 -0
  31. package/dist/cli/index.js.map +1 -0
  32. package/dist/cli/kb.d.ts +3 -0
  33. package/dist/cli/kb.d.ts.map +1 -0
  34. package/dist/cli/kb.js +377 -0
  35. package/dist/cli/kb.js.map +1 -0
  36. package/dist/cli/utils/output.d.ts +6 -0
  37. package/dist/cli/utils/output.d.ts.map +1 -0
  38. package/dist/cli/utils/output.js +36 -0
  39. package/dist/cli/utils/output.js.map +1 -0
  40. package/dist/mcp/server.d.ts +2 -0
  41. package/dist/mcp/server.d.ts.map +1 -0
  42. package/dist/mcp/server.js +15 -0
  43. package/dist/mcp/server.js.map +1 -0
  44. package/dist/mcp/tools.d.ts +4 -0
  45. package/dist/mcp/tools.d.ts.map +1 -0
  46. package/dist/mcp/tools.js +814 -0
  47. package/dist/mcp/tools.js.map +1 -0
  48. package/dist/sdk/agents.d.ts +13 -0
  49. package/dist/sdk/agents.d.ts.map +1 -0
  50. package/dist/sdk/agents.js +27 -0
  51. package/dist/sdk/agents.js.map +1 -0
  52. package/dist/sdk/auth.d.ts +18 -0
  53. package/dist/sdk/auth.d.ts.map +1 -0
  54. package/dist/sdk/auth.js +98 -0
  55. package/dist/sdk/auth.js.map +1 -0
  56. package/dist/sdk/chat.d.ts +17 -0
  57. package/dist/sdk/chat.d.ts.map +1 -0
  58. package/dist/sdk/chat.js +113 -0
  59. package/dist/sdk/chat.js.map +1 -0
  60. package/dist/sdk/client.d.ts +36 -0
  61. package/dist/sdk/client.d.ts.map +1 -0
  62. package/dist/sdk/client.js +219 -0
  63. package/dist/sdk/client.js.map +1 -0
  64. package/dist/sdk/config.d.ts +11 -0
  65. package/dist/sdk/config.d.ts.map +1 -0
  66. package/dist/sdk/config.js +20 -0
  67. package/dist/sdk/config.js.map +1 -0
  68. package/dist/sdk/eval.d.ts +13 -0
  69. package/dist/sdk/eval.d.ts.map +1 -0
  70. package/dist/sdk/eval.js +36 -0
  71. package/dist/sdk/eval.js.map +1 -0
  72. package/dist/sdk/functions.d.ts +30 -0
  73. package/dist/sdk/functions.d.ts.map +1 -0
  74. package/dist/sdk/functions.js +59 -0
  75. package/dist/sdk/functions.js.map +1 -0
  76. package/dist/sdk/index.d.ts +12 -0
  77. package/dist/sdk/index.d.ts.map +1 -0
  78. package/dist/sdk/index.js +12 -0
  79. package/dist/sdk/index.js.map +1 -0
  80. package/dist/sdk/kb.d.ts +30 -0
  81. package/dist/sdk/kb.d.ts.map +1 -0
  82. package/dist/sdk/kb.js +102 -0
  83. package/dist/sdk/kb.js.map +1 -0
  84. package/dist/sdk/rubrics.d.ts +14 -0
  85. package/dist/sdk/rubrics.d.ts.map +1 -0
  86. package/dist/sdk/rubrics.js +29 -0
  87. package/dist/sdk/rubrics.js.map +1 -0
  88. package/dist/sdk/test-sets.d.ts +14 -0
  89. package/dist/sdk/test-sets.d.ts.map +1 -0
  90. package/dist/sdk/test-sets.js +29 -0
  91. package/dist/sdk/test-sets.js.map +1 -0
  92. package/dist/sdk/types.d.ts +305 -0
  93. package/dist/sdk/types.d.ts.map +1 -0
  94. package/dist/sdk/types.js +3 -0
  95. package/dist/sdk/types.js.map +1 -0
  96. package/package.json +40 -0
  97. package/skills/agent-setup/SKILL.md +131 -0
  98. package/skills/eval-orchestration/SKILL.md +257 -0
  99. package/skills/rubric-creation/SKILL.md +163 -0
  100. package/skills/test-set-creation/SKILL.md +250 -0
  101. package/src/cli/agents.ts +206 -0
  102. package/src/cli/auth.ts +170 -0
  103. package/src/cli/chat.ts +161 -0
  104. package/src/cli/config.ts +131 -0
  105. package/src/cli/eval.ts +750 -0
  106. package/src/cli/functions.ts +330 -0
  107. package/src/cli/index.ts +68 -0
  108. package/src/cli/kb.ts +412 -0
  109. package/src/cli/utils/output.ts +42 -0
  110. package/src/mcp/server.ts +17 -0
  111. package/src/mcp/tools.ts +971 -0
  112. package/src/sdk/agents.ts +47 -0
  113. package/src/sdk/auth.ts +132 -0
  114. package/src/sdk/chat.ts +133 -0
  115. package/src/sdk/client.ts +238 -0
  116. package/src/sdk/config.ts +28 -0
  117. package/src/sdk/eval.ts +57 -0
  118. package/src/sdk/functions.ts +103 -0
  119. package/src/sdk/index.ts +11 -0
  120. package/src/sdk/kb.ts +183 -0
  121. package/src/sdk/rubrics.ts +35 -0
  122. package/src/sdk/test-sets.ts +35 -0
  123. package/src/sdk/types.ts +347 -0
  124. package/test/fixtures/test-rubric.json +66 -0
  125. package/test/fixtures/test-testset.json +55 -0
  126. package/tsconfig.json +19 -0
@@ -0,0 +1,206 @@
1
+ import { Command } from 'commander';
2
+ import ora from 'ora';
3
+ import { IndemnClient } from '../sdk/client.js';
4
+ import { AgentsSDK } from '../sdk/agents.js';
5
+ import { formatTable, printJson, printError, printSuccess, confirm } from './utils/output.js';
6
+
7
+ function formatChannels(channels: unknown[]): string {
8
+ if (!channels || channels.length === 0) return 'WEB';
9
+ return channels.map((c) =>
10
+ typeof c === 'string' ? c : (c as Record<string, string>).name || (c as Record<string, string>).type || String(c)
11
+ ).join(', ');
12
+ }
13
+
14
+ export function registerAgentCommands(program: Command): void {
15
+ const agents = program.command('agents').description('Manage AI agents');
16
+
17
+ agents
18
+ .command('list')
19
+ .description('List all agents')
20
+ .option('--json', 'Output as JSON')
21
+ .option('--limit <n>', 'Limit number of results')
22
+ .option('--page <n>', 'Page number (use with --limit)')
23
+ .action(async (options) => {
24
+ try {
25
+ const client = new IndemnClient();
26
+ const sdk = new AgentsSDK(client);
27
+ const spinner = ora('Fetching agents...').start();
28
+
29
+ let agentList = await sdk.listAgents();
30
+ spinner.stop();
31
+
32
+ if (options.page && options.limit) {
33
+ const page = parseInt(options.page, 10);
34
+ const limit = parseInt(options.limit, 10);
35
+ agentList = agentList.slice((page - 1) * limit, page * limit);
36
+ } else if (options.limit) {
37
+ agentList = agentList.slice(0, parseInt(options.limit, 10));
38
+ }
39
+
40
+ if (options.json) {
41
+ printJson(agentList);
42
+ return;
43
+ }
44
+
45
+ if (agentList.length === 0) {
46
+ console.log('No agents found.');
47
+ return;
48
+ }
49
+
50
+ const rows = agentList.map((a) => [
51
+ a._id,
52
+ a.name || '',
53
+ formatChannels(a.channels || []),
54
+ a.updatedAt ? new Date(a.updatedAt).toLocaleDateString() : '',
55
+ ]);
56
+
57
+ console.log(formatTable(['ID', 'Name', 'Channels', 'Updated'], rows));
58
+ } catch (err) {
59
+ printError(err);
60
+ process.exit(1);
61
+ }
62
+ });
63
+
64
+ agents
65
+ .command('get')
66
+ .argument('<id>', 'Agent ID')
67
+ .description('Get agent details')
68
+ .option('--json', 'Output as JSON')
69
+ .action(async (id: string, options) => {
70
+ try {
71
+ const client = new IndemnClient();
72
+ const sdk = new AgentsSDK(client);
73
+ const spinner = ora('Fetching agent...').start();
74
+
75
+ const agent = await sdk.getAgent(id);
76
+ spinner.stop();
77
+
78
+ if (options.json) {
79
+ printJson(agent);
80
+ return;
81
+ }
82
+
83
+ console.log(`ID: ${agent._id}`);
84
+ console.log(`Name: ${agent.name}`);
85
+ console.log(`Description: ${agent.description || '(none)'}`);
86
+ console.log(`Channels: ${formatChannels(agent.channels || [])}`);
87
+ console.log(`Webhook URL: ${agent.webhook_url || '(none)'}`);
88
+ console.log(`Webhook: ${agent.webhook_enabled ? 'enabled' : 'disabled'}`);
89
+ console.log(`Project: ${agent.id_project || '(none)'}`);
90
+ console.log(`Created: ${agent.createdAt || ''}`);
91
+ console.log(`Updated: ${agent.updatedAt || ''}`);
92
+ } catch (err) {
93
+ printError(err);
94
+ process.exit(1);
95
+ }
96
+ });
97
+
98
+ agents
99
+ .command('create')
100
+ .description('Create a new agent')
101
+ .requiredOption('--name <name>', 'Agent name')
102
+ .option('--channels <channels>', 'Comma-separated channels', 'WEB')
103
+ .action(async (options) => {
104
+ try {
105
+ const client = new IndemnClient();
106
+ const sdk = new AgentsSDK(client);
107
+ const spinner = ora('Creating agent...').start();
108
+
109
+ const channels = options.channels.split(',').map((c: string) => c.trim());
110
+ const agent = await sdk.createAgent({ name: options.name, channels });
111
+ spinner.stop();
112
+
113
+ printSuccess(`Agent created: ${agent._id}`);
114
+ console.log(` Name: ${agent.name}`);
115
+ console.log(` Channels: ${formatChannels(agent.channels || [])}`);
116
+ } catch (err) {
117
+ printError(err);
118
+ process.exit(1);
119
+ }
120
+ });
121
+
122
+ agents
123
+ .command('update')
124
+ .argument('<id>', 'Agent ID')
125
+ .description('Update an agent')
126
+ .option('--name <name>', 'New agent name')
127
+ .option('--description <description>', 'Agent description')
128
+ .option('--webhook-url <url>', 'Webhook URL')
129
+ .action(async (id: string, options) => {
130
+ try {
131
+ const input: Record<string, unknown> = {};
132
+ if (options.name) input.name = options.name;
133
+ if (options.description) input.description = options.description;
134
+ if (options.webhookUrl) input.webhook_url = options.webhookUrl;
135
+
136
+ if (Object.keys(input).length === 0) {
137
+ printError('No update options provided. Use --name, --description, or --webhook-url.');
138
+ process.exit(1);
139
+ }
140
+
141
+ const client = new IndemnClient();
142
+ const sdk = new AgentsSDK(client);
143
+ const spinner = ora('Updating agent...').start();
144
+
145
+ const agent = await sdk.updateAgent(id, input);
146
+ spinner.stop();
147
+
148
+ printSuccess(`Agent ${agent._id} updated.`);
149
+ } catch (err) {
150
+ printError(err);
151
+ process.exit(1);
152
+ }
153
+ });
154
+
155
+ agents
156
+ .command('delete')
157
+ .argument('<id>', 'Agent ID')
158
+ .description('Delete an agent')
159
+ .action(async (id: string) => {
160
+ try {
161
+ const confirmed = await confirm(`Delete agent ${id}? This cannot be undone.`);
162
+ if (!confirmed) {
163
+ console.log('Cancelled.');
164
+ return;
165
+ }
166
+
167
+ const client = new IndemnClient();
168
+ const sdk = new AgentsSDK(client);
169
+ const spinner = ora('Deleting agent...').start();
170
+
171
+ await sdk.deleteAgent(id);
172
+ spinner.stop();
173
+
174
+ printSuccess(`Agent ${id} deleted.`);
175
+ } catch (err) {
176
+ printError(err);
177
+ process.exit(1);
178
+ }
179
+ });
180
+
181
+ agents
182
+ .command('clone')
183
+ .argument('<id>', 'Agent ID to clone')
184
+ .description('Clone an agent')
185
+ .action(async (id: string) => {
186
+ try {
187
+ const client = new IndemnClient();
188
+ const sdk = new AgentsSDK(client);
189
+ const spinner = ora('Cloning agent...').start();
190
+
191
+ const result = await sdk.cloneAgent(id);
192
+ spinner.stop();
193
+
194
+ // Clone returns { new_agent_id, new_agent_name } or an Agent object
195
+ const raw = result as unknown as Record<string, unknown>;
196
+ const cloneId = raw.new_agent_id || result._id;
197
+ const cloneName = raw.new_agent_name || result.name;
198
+ printSuccess(`Agent cloned: ${cloneId}`);
199
+ console.log(` Name: ${cloneName}`);
200
+ console.log(` Source: ${id}`);
201
+ } catch (err) {
202
+ printError(err);
203
+ process.exit(1);
204
+ }
205
+ });
206
+ }
@@ -0,0 +1,170 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import * as readline from 'readline';
5
+ import { login, logout, whoami } from '../sdk/auth.js';
6
+ import type { Environment } from '../sdk/types.js';
7
+ import { printError, printSuccess, printJson } from './utils/output.js';
8
+
9
+ function prompt(question: string): Promise<string> {
10
+ const rl = readline.createInterface({
11
+ input: process.stdin,
12
+ output: process.stdout,
13
+ });
14
+ return new Promise((resolve) => {
15
+ rl.question(question, (answer) => {
16
+ rl.close();
17
+ resolve(answer.trim());
18
+ });
19
+ });
20
+ }
21
+
22
+ function promptPassword(question: string): Promise<string> {
23
+ return new Promise((resolve) => {
24
+ const rl = readline.createInterface({
25
+ input: process.stdin,
26
+ output: process.stdout,
27
+ });
28
+
29
+ // Disable echoing by writing to stdout manually
30
+ process.stdout.write(question);
31
+ const stdin = process.stdin;
32
+ const wasRaw = stdin.isRaw;
33
+
34
+ if (stdin.isTTY) {
35
+ stdin.setRawMode(true);
36
+ }
37
+
38
+ let password = '';
39
+ const onData = (char: Buffer) => {
40
+ const c = char.toString('utf8');
41
+ if (c === '\n' || c === '\r' || c === '\u0004') {
42
+ if (stdin.isTTY) {
43
+ stdin.setRawMode(wasRaw ?? false);
44
+ }
45
+ stdin.removeListener('data', onData);
46
+ process.stdout.write('\n');
47
+ rl.close();
48
+ resolve(password);
49
+ } else if (c === '\u0003') {
50
+ // Ctrl+C
51
+ if (stdin.isTTY) {
52
+ stdin.setRawMode(wasRaw ?? false);
53
+ }
54
+ stdin.removeListener('data', onData);
55
+ rl.close();
56
+ process.exit(1);
57
+ } else if (c === '\u007f' || c === '\b') {
58
+ // Backspace
59
+ if (password.length > 0) {
60
+ password = password.slice(0, -1);
61
+ process.stdout.write('\b \b');
62
+ }
63
+ } else {
64
+ password += c;
65
+ process.stdout.write('*');
66
+ }
67
+ };
68
+
69
+ stdin.on('data', onData);
70
+ });
71
+ }
72
+
73
+ export function registerAuthCommands(program: Command): void {
74
+ program
75
+ .command('login')
76
+ .description('Authenticate with Indemn platform')
77
+ .option('--env <environment>', 'Target environment', 'dev')
78
+ .action(async (options) => {
79
+ try {
80
+ const env = options.env as Environment;
81
+ if (env !== 'dev' && env !== 'production') {
82
+ printError('Environment must be "dev" or "production"');
83
+ process.exit(1);
84
+ }
85
+
86
+ const email = await prompt('Email: ');
87
+ if (!email) {
88
+ printError('Email is required');
89
+ process.exit(1);
90
+ }
91
+
92
+ const password = await promptPassword('Password: ');
93
+ if (!password) {
94
+ printError('Password is required');
95
+ process.exit(1);
96
+ }
97
+
98
+ const spinner = ora('Authenticating...').start();
99
+
100
+ try {
101
+ const result = await login(email, password, env, {
102
+ mfaCodePrompt: async () => {
103
+ spinner.stop();
104
+ console.log(chalk.yellow('MFA code sent to your email.'));
105
+ const code = await prompt('MFA Code: ');
106
+ if (!code) {
107
+ throw new Error('MFA code is required');
108
+ }
109
+ spinner.start('Verifying MFA code...');
110
+ return code;
111
+ },
112
+ });
113
+ spinner.succeed('Authenticated successfully');
114
+ console.log(chalk.dim(` Org: ${result.orgId}`));
115
+ console.log(chalk.dim(` Environment: ${result.environment}`));
116
+ console.log(chalk.dim(` API Key: ${result.apiKey.substring(0, 12)}...`));
117
+ } catch (err) {
118
+ spinner.fail('Authentication failed');
119
+ throw err;
120
+ }
121
+ } catch (err) {
122
+ printError(err);
123
+ process.exit(1);
124
+ }
125
+ });
126
+
127
+ program
128
+ .command('logout')
129
+ .description('Clear stored credentials')
130
+ .action(() => {
131
+ try {
132
+ logout();
133
+ printSuccess('Logged out successfully. Credentials cleared.');
134
+ } catch (err) {
135
+ printError(err);
136
+ process.exit(1);
137
+ }
138
+ });
139
+
140
+ program
141
+ .command('whoami')
142
+ .description('Display current user and environment info')
143
+ .option('--json', 'Output as JSON')
144
+ .action((options) => {
145
+ try {
146
+ const info = whoami();
147
+ if (!info) {
148
+ printError('Not authenticated. Run `indemn login` first.');
149
+ process.exit(1);
150
+ }
151
+
152
+ if (options.json) {
153
+ printJson(info);
154
+ return;
155
+ }
156
+
157
+ console.log(chalk.bold('Current Authentication'));
158
+ console.log(` API Key: ${info.apiKey}`);
159
+ console.log(` Org ID: ${info.orgId}`);
160
+ console.log(` User ID: ${info.userId}`);
161
+ console.log(` Environment: ${info.environment}`);
162
+ console.log(` Copilot: ${info.hosts.copilot}`);
163
+ console.log(` Evaluations: ${info.hosts.evaluations}`);
164
+ console.log(` Middleware: ${info.hosts.middleware}`);
165
+ } catch (err) {
166
+ printError(err);
167
+ process.exit(1);
168
+ }
169
+ });
170
+ }
@@ -0,0 +1,161 @@
1
+ import { Command } from 'commander';
2
+ import * as readline from 'readline';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { IndemnClient } from '../sdk/client.js';
6
+ import { AgentsSDK } from '../sdk/agents.js';
7
+ import { ChatSession } from '../sdk/chat.js';
8
+ import { printError } from './utils/output.js';
9
+
10
+ export function registerChatCommand(program: Command): void {
11
+ program
12
+ .command('chat')
13
+ .argument('<agent-id>', 'Agent ID to chat with')
14
+ .description('Interactive chat with an agent (or send a single message with --message)')
15
+ .option('--session <session-id>', 'Resume existing session')
16
+ .option('--message <text>', 'Send a single message and exit (non-interactive)')
17
+ .action(async (agentId: string, options) => {
18
+ try {
19
+ const client = new IndemnClient();
20
+ const agentsSDK = new AgentsSDK(client);
21
+
22
+ const fetchSpinner = ora('Connecting to agent...').start();
23
+
24
+ const agent = await agentsSDK.getAgent(agentId);
25
+ const hosts = client.getHosts();
26
+
27
+ const session = new ChatSession(agent._id, hosts.middleware, options.session);
28
+
29
+ // Track whether we're currently receiving a streamed response
30
+ let receiving = false;
31
+ let streamedResponse = false;
32
+
33
+ // Handle stream chunks — write directly to stdout
34
+ session.on('stream', (data: { text?: string; isFirstChunk?: boolean; streamFinished?: boolean }) => {
35
+ if (data.isFirstChunk) {
36
+ receiving = true;
37
+ streamedResponse = true;
38
+ process.stdout.write(chalk.cyan('Agent: '));
39
+ }
40
+ if (data.text) {
41
+ process.stdout.write(chalk.cyan(data.text));
42
+ }
43
+ if (data.streamFinished) {
44
+ process.stdout.write('\n');
45
+ receiving = false;
46
+ }
47
+ });
48
+
49
+ // Handle non-streamed bot messages (skip if already printed via streaming)
50
+ session.on('message', (text: string) => {
51
+ if (streamedResponse) {
52
+ streamedResponse = false;
53
+ return;
54
+ }
55
+ if (!receiving) {
56
+ console.log(chalk.cyan(`Agent: ${text}`));
57
+ }
58
+ });
59
+
60
+ session.on('disconnected', (reason: string) => {
61
+ console.log(chalk.dim(`\nDisconnected: ${reason}`));
62
+ process.exit(0);
63
+ });
64
+
65
+ // Connect and get session ID
66
+ const sessionId = await session.connect();
67
+ fetchSpinner.stop();
68
+
69
+ console.log(chalk.dim(`Connected to ${agent.name}`));
70
+ console.log(chalk.dim(`Session: ${sessionId}`));
71
+
72
+ // Non-interactive mode: send one message, wait for response, exit
73
+ if (options.message) {
74
+ // Wait for greeting to finish streaming
75
+ await new Promise<void>((resolve) => {
76
+ const check = setInterval(() => {
77
+ if (!receiving) {
78
+ clearInterval(check);
79
+ resolve();
80
+ }
81
+ }, 100);
82
+ // Timeout after 8s even if no greeting
83
+ setTimeout(() => { clearInterval(check); resolve(); }, 8000);
84
+ });
85
+
86
+ console.log(chalk.green(`You: ${options.message}`));
87
+ session.send(options.message);
88
+
89
+ // Wait for response to finish streaming
90
+ await new Promise<void>((resolve) => {
91
+ let started = false;
92
+ const check = setInterval(() => {
93
+ if (receiving) started = true;
94
+ if (started && !receiving) {
95
+ clearInterval(check);
96
+ resolve();
97
+ }
98
+ }, 100);
99
+ // Timeout after 30s
100
+ setTimeout(() => { clearInterval(check); resolve(); }, 30000);
101
+ });
102
+
103
+ session.disconnect();
104
+ process.exit(0);
105
+ }
106
+
107
+ // Interactive mode
108
+ console.log(chalk.dim('Type your message and press Enter. Ctrl+C to exit.\n'));
109
+
110
+ const rl = readline.createInterface({
111
+ input: process.stdin,
112
+ output: process.stdout,
113
+ prompt: chalk.green('You: '),
114
+ });
115
+
116
+ rl.prompt();
117
+
118
+ rl.on('line', (line) => {
119
+ const message = line.trim();
120
+ if (!message) {
121
+ rl.prompt();
122
+ return;
123
+ }
124
+
125
+ session.send(message);
126
+
127
+ // Wait briefly before showing prompt again to let response start streaming
128
+ setTimeout(() => {
129
+ if (!receiving) {
130
+ rl.prompt();
131
+ } else {
132
+ // Re-prompt after stream finishes
133
+ const checkDone = setInterval(() => {
134
+ if (!receiving) {
135
+ clearInterval(checkDone);
136
+ rl.prompt();
137
+ }
138
+ }, 100);
139
+ }
140
+ }, 200);
141
+ });
142
+
143
+ rl.on('close', () => {
144
+ console.log(chalk.dim('\nClosing session...'));
145
+ session.disconnect();
146
+ process.exit(0);
147
+ });
148
+
149
+ // Handle Ctrl+C
150
+ process.on('SIGINT', () => {
151
+ console.log(chalk.dim('\nClosing session...'));
152
+ session.disconnect();
153
+ rl.close();
154
+ process.exit(0);
155
+ });
156
+ } catch (err) {
157
+ printError(err);
158
+ process.exit(1);
159
+ }
160
+ });
161
+ }
@@ -0,0 +1,131 @@
1
+ import { Command } from 'commander';
2
+ import * as fs from 'fs';
3
+ import ora from 'ora';
4
+ import { IndemnClient } from '../sdk/client.js';
5
+ import { ConfigSDK } from '../sdk/config.js';
6
+ import { printJson, printError, printSuccess } from './utils/output.js';
7
+
8
+ export function registerConfigCommands(program: Command): void {
9
+ const config = program.command('config').description('Manage agent configuration');
10
+
11
+ config
12
+ .command('get')
13
+ .argument('<agent-id>', 'Agent ID')
14
+ .description('Get agent configuration')
15
+ .option('--field <field>', 'Get specific field (prompt, greeting, model)')
16
+ .option('--json', 'Output as JSON')
17
+ .action(async (agentId: string, options) => {
18
+ try {
19
+ const client = new IndemnClient();
20
+ const sdk = new ConfigSDK(client);
21
+ const spinner = ora('Fetching configuration...').start();
22
+
23
+ const cfg = await sdk.getConfig(agentId);
24
+ spinner.stop();
25
+
26
+ if (options.field) {
27
+ const field = options.field as string;
28
+ let value: unknown;
29
+
30
+ switch (field) {
31
+ case 'prompt':
32
+ value = cfg.ai_config?.system_prompt;
33
+ break;
34
+ case 'greeting':
35
+ value = cfg.first_message;
36
+ break;
37
+ case 'model':
38
+ value = cfg.ai_config?.kb_configuration;
39
+ break;
40
+ default:
41
+ value = (cfg as Record<string, unknown>)[field];
42
+ }
43
+
44
+ if (value === undefined) {
45
+ console.log(`Field "${field}" is not set.`);
46
+ return;
47
+ }
48
+
49
+ if (options.json || typeof value === 'object') {
50
+ printJson(value);
51
+ } else {
52
+ console.log(String(value));
53
+ }
54
+ return;
55
+ }
56
+
57
+ if (options.json) {
58
+ printJson(cfg);
59
+ return;
60
+ }
61
+
62
+ // Pretty print the configuration
63
+ console.log('Agent Configuration:');
64
+ if (cfg.ai_config?.system_prompt) {
65
+ console.log('\nSystem Prompt:');
66
+ console.log(' ' + cfg.ai_config.system_prompt.replace(/\n/g, '\n '));
67
+ }
68
+ if (cfg.first_message) {
69
+ console.log('\nGreeting:');
70
+ if (cfg.first_message.text) {
71
+ console.log(` Text: ${cfg.first_message.text}`);
72
+ }
73
+ if (cfg.first_message.quick_replies?.length) {
74
+ console.log(` Quick Replies: ${cfg.first_message.quick_replies.map((q) => q.title).join(', ')}`);
75
+ }
76
+ }
77
+ if (cfg.ai_config?.kb_configuration) {
78
+ console.log('\nKB Configuration:');
79
+ printJson(cfg.ai_config.kb_configuration);
80
+ }
81
+ } catch (err) {
82
+ printError(err);
83
+ process.exit(1);
84
+ }
85
+ });
86
+
87
+ config
88
+ .command('set')
89
+ .argument('<agent-id>', 'Agent ID')
90
+ .description('Update agent configuration')
91
+ .option('--prompt <prompt>', 'Set system prompt text')
92
+ .option('--prompt-file <path>', 'Set system prompt from file')
93
+ .option('--model <model>', 'Set model name')
94
+ .action(async (agentId: string, options) => {
95
+ try {
96
+ const input: Record<string, unknown> = {};
97
+
98
+ if (options.promptFile) {
99
+ const filePath = options.promptFile as string;
100
+ if (!fs.existsSync(filePath)) {
101
+ printError(`File not found: ${filePath}`);
102
+ process.exit(1);
103
+ }
104
+ input.system_prompt = fs.readFileSync(filePath, 'utf-8');
105
+ } else if (options.prompt) {
106
+ input.system_prompt = options.prompt;
107
+ }
108
+
109
+ if (options.model) {
110
+ input.model = options.model;
111
+ }
112
+
113
+ if (Object.keys(input).length === 0) {
114
+ printError('No configuration options provided. Use --prompt, --prompt-file, or --model.');
115
+ process.exit(1);
116
+ }
117
+
118
+ const client = new IndemnClient();
119
+ const sdk = new ConfigSDK(client);
120
+ const spinner = ora('Updating configuration...').start();
121
+
122
+ await sdk.setConfig(agentId, input);
123
+ spinner.stop();
124
+
125
+ printSuccess(`Configuration updated for agent ${agentId}.`);
126
+ } catch (err) {
127
+ printError(err);
128
+ process.exit(1);
129
+ }
130
+ });
131
+ }