@inkeep/agents-cli 0.1.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 (75) hide show
  1. package/LICENSE.md +51 -0
  2. package/README.md +512 -0
  3. package/dist/__tests__/api.test.d.ts +1 -0
  4. package/dist/__tests__/api.test.js +257 -0
  5. package/dist/__tests__/cli.test.d.ts +1 -0
  6. package/dist/__tests__/cli.test.js +153 -0
  7. package/dist/__tests__/commands/config.test.d.ts +1 -0
  8. package/dist/__tests__/commands/config.test.js +154 -0
  9. package/dist/__tests__/commands/init.test.d.ts +1 -0
  10. package/dist/__tests__/commands/init.test.js +186 -0
  11. package/dist/__tests__/commands/pull-retry.test.d.ts +1 -0
  12. package/dist/__tests__/commands/pull-retry.test.js +156 -0
  13. package/dist/__tests__/commands/pull.test.d.ts +1 -0
  14. package/dist/__tests__/commands/pull.test.js +54 -0
  15. package/dist/__tests__/commands/push-spinner.test.d.ts +1 -0
  16. package/dist/__tests__/commands/push-spinner.test.js +127 -0
  17. package/dist/__tests__/commands/push.test.d.ts +1 -0
  18. package/dist/__tests__/commands/push.test.js +265 -0
  19. package/dist/__tests__/config-validation.test.d.ts +1 -0
  20. package/dist/__tests__/config-validation.test.js +106 -0
  21. package/dist/__tests__/package.test.d.ts +1 -0
  22. package/dist/__tests__/package.test.js +82 -0
  23. package/dist/__tests__/utils/json-comparator.test.d.ts +1 -0
  24. package/dist/__tests__/utils/json-comparator.test.js +174 -0
  25. package/dist/__tests__/utils/port-manager.test.d.ts +1 -0
  26. package/dist/__tests__/utils/port-manager.test.js +144 -0
  27. package/dist/__tests__/utils/ts-loader.test.d.ts +1 -0
  28. package/dist/__tests__/utils/ts-loader.test.js +233 -0
  29. package/dist/api.d.ts +23 -0
  30. package/dist/api.js +140 -0
  31. package/dist/commands/chat-enhanced.d.ts +7 -0
  32. package/dist/commands/chat-enhanced.js +396 -0
  33. package/dist/commands/chat.d.ts +5 -0
  34. package/dist/commands/chat.js +125 -0
  35. package/dist/commands/config.d.ts +6 -0
  36. package/dist/commands/config.js +128 -0
  37. package/dist/commands/init.d.ts +5 -0
  38. package/dist/commands/init.js +171 -0
  39. package/dist/commands/list-graphs.d.ts +6 -0
  40. package/dist/commands/list-graphs.js +131 -0
  41. package/dist/commands/mcp-list.d.ts +4 -0
  42. package/dist/commands/mcp-list.js +156 -0
  43. package/dist/commands/mcp-start-simple.d.ts +5 -0
  44. package/dist/commands/mcp-start-simple.js +193 -0
  45. package/dist/commands/mcp-start.d.ts +5 -0
  46. package/dist/commands/mcp-start.js +217 -0
  47. package/dist/commands/mcp-status.d.ts +1 -0
  48. package/dist/commands/mcp-status.js +96 -0
  49. package/dist/commands/mcp-stop.d.ts +5 -0
  50. package/dist/commands/mcp-stop.js +160 -0
  51. package/dist/commands/pull.d.ts +15 -0
  52. package/dist/commands/pull.js +313 -0
  53. package/dist/commands/pull.llm-generate.d.ts +10 -0
  54. package/dist/commands/pull.llm-generate.js +184 -0
  55. package/dist/commands/push.d.ts +6 -0
  56. package/dist/commands/push.js +268 -0
  57. package/dist/config.d.ts +43 -0
  58. package/dist/config.js +292 -0
  59. package/dist/exports.d.ts +2 -0
  60. package/dist/exports.js +2 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +98 -0
  63. package/dist/types/config.d.ts +9 -0
  64. package/dist/types/config.js +3 -0
  65. package/dist/types/graph.d.ts +10 -0
  66. package/dist/types/graph.js +1 -0
  67. package/dist/utils/json-comparator.d.ts +60 -0
  68. package/dist/utils/json-comparator.js +222 -0
  69. package/dist/utils/mcp-runner.d.ts +6 -0
  70. package/dist/utils/mcp-runner.js +147 -0
  71. package/dist/utils/port-manager.d.ts +43 -0
  72. package/dist/utils/port-manager.js +92 -0
  73. package/dist/utils/ts-loader.d.ts +5 -0
  74. package/dist/utils/ts-loader.js +146 -0
  75. package/package.json +77 -0
@@ -0,0 +1,396 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, extname, resolve } from 'node:path';
4
+ import * as readline from 'node:readline';
5
+ import { fileURLToPath } from 'node:url';
6
+ import chalk from 'chalk';
7
+ import inquirer from 'inquirer';
8
+ import ora from 'ora';
9
+ import { ManagementApiClient, ExecutionApiClient } from '../api.js';
10
+ import { validateConfiguration } from '../config.js';
11
+ export async function chatCommandEnhanced(graphIdInput, options) {
12
+ // Check if we need to re-run with tsx for TypeScript config files
13
+ if (!process.env.TSX_RUNNING) {
14
+ // Helper function to find config file
15
+ function findConfigFile(startPath = process.cwd()) {
16
+ let currentPath = resolve(startPath);
17
+ const root = '/';
18
+ const configNames = ['inkeep.config.ts', 'inkeep.config.js', '.inkeeprc.ts', '.inkeeprc.js'];
19
+ while (currentPath !== root) {
20
+ // Check for config files at this level
21
+ for (const configName of configNames) {
22
+ const configPath = resolve(currentPath, configName);
23
+ if (existsSync(configPath)) {
24
+ return configPath;
25
+ }
26
+ }
27
+ const parentPath = dirname(currentPath);
28
+ if (parentPath === currentPath) {
29
+ break; // Reached filesystem root
30
+ }
31
+ currentPath = parentPath;
32
+ }
33
+ return null;
34
+ }
35
+ // Determine if we have a TypeScript config that needs tsx
36
+ let configPath = null;
37
+ if (options?.configFilePath) {
38
+ // User specified a config path
39
+ configPath = resolve(process.cwd(), options.configFilePath);
40
+ if (!existsSync(configPath)) {
41
+ // Config file doesn't exist, let the normal flow handle the error
42
+ configPath = null;
43
+ }
44
+ }
45
+ else {
46
+ // Search for config file
47
+ configPath = findConfigFile();
48
+ }
49
+ // If we found a TypeScript config file, re-run with tsx
50
+ if (configPath && extname(configPath) === '.ts') {
51
+ // Re-run this command with tsx
52
+ const __filename = fileURLToPath(import.meta.url);
53
+ const __dirname = dirname(__filename);
54
+ const cliPath = resolve(__dirname, '../index.js');
55
+ const args = [cliPath, 'chat'];
56
+ if (graphIdInput)
57
+ args.push(graphIdInput);
58
+ if (options?.tenantId)
59
+ args.push('--tenant-id', options.tenantId);
60
+ if (options?.managementApiUrl)
61
+ args.push('--management-api-url', options.managementApiUrl);
62
+ if (options?.executionApiUrl)
63
+ args.push('--execution-api-url', options.executionApiUrl);
64
+ if (options?.configFilePath)
65
+ args.push('--config-file-path', options.configFilePath);
66
+ const child = spawn('npx', ['tsx', ...args], {
67
+ cwd: process.cwd(),
68
+ stdio: 'inherit',
69
+ env: { ...process.env, TSX_RUNNING: '1' },
70
+ });
71
+ child.on('error', (error) => {
72
+ console.error(chalk.red('Failed to load TypeScript configuration:'), error.message);
73
+ process.exit(1);
74
+ });
75
+ child.on('exit', (code) => {
76
+ process.exit(code || 0);
77
+ });
78
+ return;
79
+ }
80
+ }
81
+ // Validate configuration
82
+ let config;
83
+ try {
84
+ config = await validateConfiguration(options?.tenantId, options?.managementApiUrl, options?.executionApiUrl, options?.configFilePath);
85
+ }
86
+ catch (error) {
87
+ console.error(chalk.red(error.message));
88
+ process.exit(1);
89
+ }
90
+ // Log configuration sources for debugging
91
+ console.log(chalk.gray('Using configuration:'));
92
+ console.log(chalk.gray(` • Tenant ID: ${config.sources.tenantId}`));
93
+ console.log(chalk.gray(` • Management API: ${config.sources.managementApiUrl}`));
94
+ console.log(chalk.gray(` • Execution API: ${config.sources.executionApiUrl}`));
95
+ console.log();
96
+ const managementApi = await ManagementApiClient.create(config.managementApiUrl, options?.configFilePath, config.tenantId);
97
+ const executionApi = await ExecutionApiClient.create(config.executionApiUrl, options?.configFilePath, config.tenantId);
98
+ let graphId = graphIdInput;
99
+ // If no graph ID provided, show autocomplete selection
100
+ if (!graphId) {
101
+ const spinner = ora('Fetching available graphs...').start();
102
+ try {
103
+ const graphs = await managementApi.listGraphs();
104
+ spinner.stop();
105
+ if (graphs.length === 0) {
106
+ console.error(chalk.red('No graphs available. Push a graph first with: inkeep push <graph-path>'));
107
+ process.exit(1);
108
+ }
109
+ // Create searchable source for autocomplete
110
+ const graphChoices = graphs.map((g) => ({
111
+ name: `${chalk.cyan(g.id)} - ${g.name || 'Unnamed Graph'}`,
112
+ value: g.id,
113
+ short: g.id,
114
+ searchText: `${g.id} ${g.name || ''}`.toLowerCase(),
115
+ }));
116
+ // Use list prompt for interactive selection
117
+ const answer = await inquirer.prompt([
118
+ {
119
+ type: 'list',
120
+ name: 'graphId',
121
+ message: 'Select a graph to chat with:',
122
+ choices: graphChoices,
123
+ pageSize: 10,
124
+ },
125
+ ]);
126
+ graphId = answer.graphId;
127
+ }
128
+ catch (error) {
129
+ spinner.fail('Failed to fetch graphs');
130
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
131
+ process.exit(1);
132
+ }
133
+ }
134
+ // Check if graph exists
135
+ const spinner = ora('Connecting to graph...').start();
136
+ try {
137
+ const graph = await managementApi.getGraph(graphId);
138
+ if (!graph) {
139
+ spinner.fail(`Graph "${graphId}" not found`);
140
+ // Show available graphs
141
+ const graphs = await managementApi.listGraphs();
142
+ if (graphs.length > 0) {
143
+ console.log(chalk.yellow('\nAvailable graphs:'));
144
+ graphs.forEach((g) => {
145
+ console.log(chalk.gray(` • ${g.id} - ${g.name || 'Unnamed'}`));
146
+ });
147
+ console.log(chalk.gray('\nRun "inkeep chat" without arguments for interactive selection'));
148
+ }
149
+ else {
150
+ console.log(chalk.yellow('\nNo graphs found. Please create and push a graph first.'));
151
+ console.log(chalk.gray('Example: inkeep push ./my-graph.js'));
152
+ }
153
+ process.exit(1);
154
+ }
155
+ spinner.succeed(`Connected to graph: ${chalk.green(graph.name || graphId)}`);
156
+ // Display graph details
157
+ if (graph.description) {
158
+ console.log(chalk.gray(`Description: ${graph.description}`));
159
+ }
160
+ if (graph.defaultAgentId || graph.default_agent_id) {
161
+ console.log(chalk.gray(`Default Agent: ${graph.defaultAgentId || graph.default_agent_id}`));
162
+ }
163
+ }
164
+ catch (error) {
165
+ spinner.fail('Failed to connect to graph');
166
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
167
+ process.exit(1);
168
+ }
169
+ // Create readline interface for chat
170
+ const rl = readline.createInterface({
171
+ input: process.stdin,
172
+ output: process.stdout,
173
+ prompt: chalk.cyan('You> '),
174
+ });
175
+ // Generate a conversation ID for this session
176
+ const conversationId = `cli-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
177
+ const messages = [];
178
+ let debugMode = false;
179
+ console.log(chalk.gray('\n💬 Chat session started. Type "exit" or press Ctrl+C to quit.'));
180
+ console.log(chalk.gray('Commands: help, clear, history, reset, debug\n'));
181
+ // Function to handle streaming response
182
+ async function handleStreamingResponse(stream, showDebug = false) {
183
+ const decoder = new TextDecoder();
184
+ const reader = stream.getReader();
185
+ let buffer = '';
186
+ let responseContent = '';
187
+ const debugOperations = [];
188
+ let hasStartedResponse = false;
189
+ try {
190
+ while (true) {
191
+ const { done, value } = await reader.read();
192
+ if (done)
193
+ break;
194
+ buffer += decoder.decode(value, { stream: true });
195
+ const lines = buffer.split('\n');
196
+ buffer = lines.pop() || '';
197
+ for (const line of lines) {
198
+ if (line.startsWith('data: ')) {
199
+ const data = line.slice(6);
200
+ if (data === '[DONE]')
201
+ continue;
202
+ try {
203
+ const parsed = JSON.parse(data);
204
+ // Handle OpenAI-style streaming chunks
205
+ const content = parsed.choices?.[0]?.delta?.content;
206
+ if (content) {
207
+ // Process content character by character to extract JSON objects
208
+ let currentPos = 0;
209
+ while (currentPos < content.length) {
210
+ // Check if we're at the start of a JSON data operation
211
+ if (content.substring(currentPos).startsWith('{"type":"data-operation"')) {
212
+ // Find the matching closing brace
213
+ let braceCount = 0;
214
+ let jsonEnd = currentPos;
215
+ for (let i = currentPos; i < content.length; i++) {
216
+ if (content[i] === '{')
217
+ braceCount++;
218
+ if (content[i] === '}') {
219
+ braceCount--;
220
+ if (braceCount === 0) {
221
+ jsonEnd = i + 1;
222
+ break;
223
+ }
224
+ }
225
+ }
226
+ // Extract and parse the JSON object
227
+ const jsonStr = content.substring(currentPos, jsonEnd);
228
+ try {
229
+ const dataOp = JSON.parse(jsonStr);
230
+ debugOperations.push(dataOp);
231
+ // Show debug info if enabled
232
+ if (showDebug && dataOp.type === 'data-operation') {
233
+ const opType = dataOp.data?.type || 'unknown';
234
+ const ctx = dataOp.data?.ctx || {};
235
+ // Format context based on operation type
236
+ let ctxDisplay = '';
237
+ if (opType === 'agent_thinking' || opType === 'iteration_start') {
238
+ ctxDisplay = ctx.agent || JSON.stringify(ctx);
239
+ }
240
+ else if (opType === 'task_creation') {
241
+ ctxDisplay = `agent: ${ctx.agent}`;
242
+ }
243
+ else {
244
+ ctxDisplay = JSON.stringify(ctx);
245
+ }
246
+ // Add newline before completion operations that come after text
247
+ if (opType === 'completion' && hasStartedResponse) {
248
+ console.log(''); // Add newline before completion
249
+ }
250
+ console.log(chalk.gray(` [${opType}] ${ctxDisplay}`));
251
+ }
252
+ currentPos = jsonEnd;
253
+ }
254
+ catch {
255
+ // Failed to parse, treat as regular content
256
+ if (!hasStartedResponse) {
257
+ process.stdout.write(chalk.green('Assistant> '));
258
+ hasStartedResponse = true;
259
+ }
260
+ process.stdout.write(content[currentPos]);
261
+ responseContent += content[currentPos];
262
+ currentPos++;
263
+ }
264
+ }
265
+ else {
266
+ // Regular text content
267
+ if (!hasStartedResponse) {
268
+ process.stdout.write(chalk.green('Assistant> '));
269
+ hasStartedResponse = true;
270
+ }
271
+ process.stdout.write(content[currentPos]);
272
+ responseContent += content[currentPos];
273
+ currentPos++;
274
+ }
275
+ }
276
+ }
277
+ }
278
+ catch {
279
+ // Ignore parse errors
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }
285
+ finally {
286
+ reader.releaseLock();
287
+ }
288
+ // Add final newline if we had content
289
+ if (hasStartedResponse) {
290
+ console.log('\n');
291
+ }
292
+ else {
293
+ console.log(chalk.green('Assistant> ') + chalk.gray('(no response)') + '\n');
294
+ }
295
+ return responseContent;
296
+ }
297
+ // Handle user input
298
+ rl.on('line', async (input) => {
299
+ const trimmedInput = input.trim();
300
+ const command = trimmedInput.toLowerCase().replace(/^\//, '');
301
+ if (command === 'exit') {
302
+ console.log(chalk.gray('Goodbye! 👋'));
303
+ rl.close();
304
+ process.exit(0);
305
+ }
306
+ if (command === 'clear') {
307
+ console.clear();
308
+ console.log(chalk.gray('Screen cleared. Conversation context preserved.\n'));
309
+ rl.prompt();
310
+ return;
311
+ }
312
+ if (command === 'help') {
313
+ console.log(chalk.cyan('\n📚 Available commands:'));
314
+ console.log(chalk.gray(' • exit - End the chat session'));
315
+ console.log(chalk.gray(' • clear - Clear the screen (preserves context)'));
316
+ console.log(chalk.gray(' • history - Show conversation history'));
317
+ console.log(chalk.gray(' • reset - Reset conversation context'));
318
+ console.log(chalk.gray(' • debug - Toggle debug mode (show/hide data operations)'));
319
+ console.log(chalk.gray(' • help - Show this help message'));
320
+ console.log(chalk.gray('\n Commands can be prefixed with / (e.g., /help)\n'));
321
+ rl.prompt();
322
+ return;
323
+ }
324
+ if (command === 'debug') {
325
+ debugMode = !debugMode;
326
+ console.log(chalk.yellow(`\n🔧 Debug mode: ${debugMode ? 'ON' : 'OFF'}`));
327
+ if (debugMode) {
328
+ console.log(chalk.gray('Data operations will be shown during responses.\n'));
329
+ }
330
+ else {
331
+ console.log(chalk.gray('Data operations are hidden.\n'));
332
+ }
333
+ rl.prompt();
334
+ return;
335
+ }
336
+ if (command === 'history') {
337
+ console.log(chalk.cyan('\n📜 Conversation History:'));
338
+ if (messages.length === 0) {
339
+ console.log(chalk.gray(' (No messages yet)\n'));
340
+ }
341
+ else {
342
+ messages.forEach((msg, idx) => {
343
+ const role = msg.role === 'user' ? chalk.blue('You') : chalk.green('Assistant');
344
+ const preview = msg.content.substring(0, 100);
345
+ const suffix = msg.content.length > 100 ? '...' : '';
346
+ console.log(` ${idx + 1}. ${role}: ${preview}${suffix}`);
347
+ });
348
+ console.log();
349
+ }
350
+ rl.prompt();
351
+ return;
352
+ }
353
+ if (command === 'reset') {
354
+ messages.length = 0;
355
+ console.log(chalk.yellow('⚠️ Conversation context has been reset.\n'));
356
+ rl.prompt();
357
+ return;
358
+ }
359
+ if (!trimmedInput) {
360
+ rl.prompt();
361
+ return;
362
+ }
363
+ // Add user message to history
364
+ messages.push({ role: 'user', content: trimmedInput });
365
+ try {
366
+ // Send message to API using execution API
367
+ const response = await executionApi.chatCompletion(graphId, messages, conversationId);
368
+ let assistantResponse;
369
+ if (typeof response === 'string') {
370
+ // Non-streaming response
371
+ console.log(chalk.green('Assistant>'), response);
372
+ assistantResponse = response;
373
+ }
374
+ else {
375
+ // Streaming response
376
+ assistantResponse = await handleStreamingResponse(response, debugMode);
377
+ }
378
+ // Add assistant response to history
379
+ messages.push({ role: 'assistant', content: assistantResponse });
380
+ }
381
+ catch (error) {
382
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
383
+ }
384
+ rl.prompt();
385
+ });
386
+ rl.on('close', () => {
387
+ console.log(chalk.gray('\n📊 Session Summary:'));
388
+ console.log(chalk.gray(` • Graph: ${graphId}`));
389
+ console.log(chalk.gray(` • Messages: ${messages.length}`));
390
+ console.log(chalk.gray(` • Duration: ${new Date().toLocaleTimeString()}`));
391
+ console.log(chalk.gray('\nChat session ended.'));
392
+ process.exit(0);
393
+ });
394
+ // Initial prompt
395
+ rl.prompt();
396
+ }
@@ -0,0 +1,5 @@
1
+ export interface ChatOptions {
2
+ url?: string;
3
+ config?: string;
4
+ }
5
+ export declare function chatCommand(graphId: string, options: ChatOptions): Promise<void>;
@@ -0,0 +1,125 @@
1
+ import * as readline from 'node:readline';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { ManagementApiClient, ExecutionApiClient } from '../api.js';
5
+ export async function chatCommand(graphId, options) {
6
+ const managementApi = await ManagementApiClient.create(options.url, options.config);
7
+ const executionApi = await ExecutionApiClient.create(options.url, options.config);
8
+ // Check if graph exists using management API
9
+ const spinner = ora('Connecting to graph...').start();
10
+ try {
11
+ const graph = await managementApi.getGraph(graphId);
12
+ if (!graph) {
13
+ spinner.fail(`Graph "${graphId}" not found`);
14
+ process.exit(1);
15
+ }
16
+ spinner.succeed(`Connected to graph: ${graph.name || graphId}`);
17
+ }
18
+ catch (error) {
19
+ spinner.fail('Failed to connect to graph');
20
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
21
+ process.exit(1);
22
+ }
23
+ // Create readline interface
24
+ const rl = readline.createInterface({
25
+ input: process.stdin,
26
+ output: process.stdout,
27
+ prompt: chalk.cyan('You> '),
28
+ });
29
+ // Generate a conversation ID for this session
30
+ const conversationId = `cli-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
31
+ const messages = [];
32
+ console.log(chalk.gray('\n💬 Chat session started. Type "exit" or press Ctrl+C to quit.\n'));
33
+ // Function to handle streaming response
34
+ async function handleStreamingResponse(stream) {
35
+ const decoder = new TextDecoder();
36
+ const reader = stream.getReader();
37
+ let buffer = '';
38
+ let responseContent = '';
39
+ process.stdout.write(chalk.green('Assistant> '));
40
+ try {
41
+ while (true) {
42
+ const { done, value } = await reader.read();
43
+ if (done)
44
+ break;
45
+ buffer += decoder.decode(value, { stream: true });
46
+ const lines = buffer.split('\n');
47
+ buffer = lines.pop() || '';
48
+ for (const line of lines) {
49
+ if (line.startsWith('data: ')) {
50
+ const data = line.slice(6);
51
+ if (data === '[DONE]')
52
+ continue;
53
+ try {
54
+ const parsed = JSON.parse(data);
55
+ const content = parsed.choices?.[0]?.delta?.content;
56
+ if (content) {
57
+ // Debug logging
58
+ if (process.env.DEBUG) {
59
+ console.error('Content received:', content);
60
+ }
61
+ // Filter out data operation JSON messages
62
+ if (!content.startsWith('{"type":"data-operation"')) {
63
+ process.stdout.write(content);
64
+ responseContent += content;
65
+ }
66
+ }
67
+ }
68
+ catch (err) {
69
+ // Log parse errors for debugging
70
+ if (process.env.DEBUG) {
71
+ console.error('Parse error:', err, 'Data:', data);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ finally {
79
+ reader.releaseLock();
80
+ }
81
+ console.log('\n');
82
+ return responseContent;
83
+ }
84
+ // Set up tab completion for graph IDs
85
+ rl.on('line', async (input) => {
86
+ const trimmedInput = input.trim();
87
+ if (trimmedInput.toLowerCase() === 'exit') {
88
+ console.log(chalk.gray('Goodbye! 👋'));
89
+ rl.close();
90
+ process.exit(0);
91
+ }
92
+ if (!trimmedInput) {
93
+ rl.prompt();
94
+ return;
95
+ }
96
+ // Add user message to history
97
+ messages.push({ role: 'user', content: trimmedInput });
98
+ try {
99
+ // Send message to API using execution API
100
+ const response = await executionApi.chatCompletion(graphId, messages, conversationId);
101
+ let assistantResponse;
102
+ if (typeof response === 'string') {
103
+ // Non-streaming response
104
+ console.log(chalk.green('Assistant>'), response);
105
+ assistantResponse = response;
106
+ }
107
+ else {
108
+ // Streaming response
109
+ assistantResponse = await handleStreamingResponse(response);
110
+ }
111
+ // Add assistant response to history
112
+ messages.push({ role: 'assistant', content: assistantResponse });
113
+ }
114
+ catch (error) {
115
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
116
+ }
117
+ rl.prompt();
118
+ });
119
+ rl.on('close', () => {
120
+ console.log(chalk.gray('\nChat session ended.'));
121
+ process.exit(0);
122
+ });
123
+ // Initial prompt
124
+ rl.prompt();
125
+ }
@@ -0,0 +1,6 @@
1
+ export interface ConfigOptions {
2
+ configFilePath?: string;
3
+ }
4
+ export declare function configGetCommand(key?: string, options?: ConfigOptions): Promise<void>;
5
+ export declare function configSetCommand(key: string, value: string, options?: ConfigOptions): Promise<void>;
6
+ export declare function configListCommand(options?: ConfigOptions): Promise<void>;
@@ -0,0 +1,128 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import chalk from 'chalk';
4
+ export async function configGetCommand(key, options) {
5
+ const configPath = options?.configFilePath || join(process.cwd(), 'inkeep.config.ts');
6
+ if (!existsSync(configPath)) {
7
+ console.error(chalk.red('No configuration file found.'));
8
+ console.log(chalk.gray('Run "inkeep init" to create one, or specify a config file with --config-file-path'));
9
+ process.exit(1);
10
+ }
11
+ try {
12
+ const content = readFileSync(configPath, 'utf-8');
13
+ // Parse the config file to extract values
14
+ const tenantIdMatch = content.match(/tenantId:\s*['"]([^'"]+)['"]/);
15
+ const apiUrlMatch = content.match(/apiUrl:\s*['"]([^'"]+)['"]/);
16
+ const config = {
17
+ tenantId: tenantIdMatch ? tenantIdMatch[1] : undefined,
18
+ apiUrl: apiUrlMatch ? apiUrlMatch[1] : undefined,
19
+ };
20
+ if (key) {
21
+ // Get specific key
22
+ const value = config[key];
23
+ if (value !== undefined) {
24
+ console.log(value);
25
+ }
26
+ else {
27
+ console.error(chalk.red(`Unknown configuration key: ${key}`));
28
+ console.log(chalk.gray('Available keys: tenantId, apiUrl'));
29
+ process.exit(1);
30
+ }
31
+ }
32
+ else {
33
+ // Display all config
34
+ console.log(chalk.cyan('Current configuration:'));
35
+ console.log(chalk.gray(' Config file:'), configPath);
36
+ console.log(chalk.gray(' Tenant ID:'), config.tenantId || chalk.yellow('(not set)'));
37
+ console.log(chalk.gray(' API URL:'), config.apiUrl || chalk.yellow('(not set)'));
38
+ }
39
+ }
40
+ catch (error) {
41
+ console.error(chalk.red('Failed to read configuration:'), error);
42
+ process.exit(1);
43
+ }
44
+ }
45
+ export async function configSetCommand(key, value, options) {
46
+ const configPath = options?.configFilePath || join(process.cwd(), 'inkeep.config.ts');
47
+ // Validate the key
48
+ if (!['tenantId', 'apiUrl'].includes(key)) {
49
+ console.error(chalk.red(`Invalid configuration key: ${key}`));
50
+ console.log(chalk.gray('Available keys: tenantId, apiUrl'));
51
+ process.exit(1);
52
+ }
53
+ // Validate apiUrl if setting it
54
+ if (key === 'apiUrl') {
55
+ try {
56
+ new URL(value);
57
+ }
58
+ catch {
59
+ console.error(chalk.red('Invalid URL format'));
60
+ process.exit(1);
61
+ }
62
+ }
63
+ if (!existsSync(configPath)) {
64
+ // Create a new config file if it doesn't exist
65
+ const configContent = `import { defineConfig } from '@inkeep/agents-cli';
66
+
67
+ export default defineConfig({
68
+ tenantId: '${key === 'tenantId' ? value : ''}',
69
+ projectId: '${key === 'projectId' ? value : 'default'}',
70
+ apiUrl: '${key === 'apiUrl' ? value : 'http://localhost:3002'}',
71
+ });
72
+ `;
73
+ try {
74
+ writeFileSync(configPath, configContent);
75
+ console.log(chalk.green('✓'), `Created config file and set ${key} to:`, chalk.cyan(value));
76
+ }
77
+ catch (error) {
78
+ console.error(chalk.red('Failed to create config file:'), error);
79
+ process.exit(1);
80
+ }
81
+ }
82
+ else {
83
+ // Update existing config file
84
+ try {
85
+ let content = readFileSync(configPath, 'utf-8');
86
+ if (key === 'tenantId') {
87
+ // Update or add tenantId
88
+ if (content.includes('tenantId:')) {
89
+ content = content.replace(/tenantId:\s*['"][^'"]*['"]/, `tenantId: '${value}'`);
90
+ }
91
+ else {
92
+ // Add tenantId to the config object
93
+ content = content.replace(/defineConfig\s*\(\s*{/, `defineConfig({\n tenantId: '${value}',`);
94
+ }
95
+ }
96
+ else if (key === 'projectId') {
97
+ // Update or add projectId
98
+ if (content.includes('projectId:')) {
99
+ content = content.replace(/projectId:\s*['"][^'"]*['"]/, `projectId: '${value}'`);
100
+ }
101
+ else {
102
+ // Add projectId after tenantId
103
+ content = content.replace(/(tenantId:\s*['"][^'"]*['"]),?/, `$1,\n projectId: '${value}',`);
104
+ }
105
+ }
106
+ else if (key === 'apiUrl') {
107
+ // Update or add apiUrl
108
+ if (content.includes('apiUrl:')) {
109
+ content = content.replace(/apiUrl:\s*['"][^'"]*['"]/, `apiUrl: '${value}'`);
110
+ }
111
+ else {
112
+ // Add apiUrl to the config object
113
+ content = content.replace(/defineConfig\s*\(\s*{/, `defineConfig({\n apiUrl: '${value}',`);
114
+ }
115
+ }
116
+ writeFileSync(configPath, content);
117
+ console.log(chalk.green('✓'), `Updated ${key} to:`, chalk.cyan(value));
118
+ }
119
+ catch (error) {
120
+ console.error(chalk.red('Failed to update config file:'), error);
121
+ process.exit(1);
122
+ }
123
+ }
124
+ }
125
+ export async function configListCommand(options) {
126
+ // Alias for configGetCommand without a key
127
+ await configGetCommand(undefined, options);
128
+ }
@@ -0,0 +1,5 @@
1
+ export interface InitOptions {
2
+ path?: string;
3
+ interactive?: boolean;
4
+ }
5
+ export declare function initCommand(options?: InitOptions): Promise<void>;