@inkeep/agents-cli 0.1.5 → 0.1.6

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 (59) hide show
  1. package/SUPPLEMENTAL_TERMS.md +40 -0
  2. package/dist/commands/create.d.ts +12 -0
  3. package/dist/commands/create.js +865 -0
  4. package/dist/config.d.ts +4 -4
  5. package/dist/index.js +5306 -20407
  6. package/package.json +15 -6
  7. package/dist/__tests__/api.test.d.ts +0 -1
  8. package/dist/__tests__/api.test.js +0 -257
  9. package/dist/__tests__/cli.test.d.ts +0 -1
  10. package/dist/__tests__/cli.test.js +0 -153
  11. package/dist/__tests__/commands/config.test.d.ts +0 -1
  12. package/dist/__tests__/commands/config.test.js +0 -154
  13. package/dist/__tests__/commands/init.test.d.ts +0 -1
  14. package/dist/__tests__/commands/init.test.js +0 -186
  15. package/dist/__tests__/commands/pull.test.d.ts +0 -1
  16. package/dist/__tests__/commands/pull.test.js +0 -54
  17. package/dist/__tests__/commands/push-spinner.test.d.ts +0 -1
  18. package/dist/__tests__/commands/push-spinner.test.js +0 -127
  19. package/dist/__tests__/commands/push.test.d.ts +0 -1
  20. package/dist/__tests__/commands/push.test.js +0 -265
  21. package/dist/__tests__/config-validation.test.d.ts +0 -1
  22. package/dist/__tests__/config-validation.test.js +0 -98
  23. package/dist/__tests__/package.test.d.ts +0 -1
  24. package/dist/__tests__/package.test.js +0 -82
  25. package/dist/__tests__/utils/json-comparator.test.d.ts +0 -1
  26. package/dist/__tests__/utils/json-comparator.test.js +0 -174
  27. package/dist/__tests__/utils/ts-loader.test.d.ts +0 -1
  28. package/dist/__tests__/utils/ts-loader.test.js +0 -232
  29. package/dist/api.d.ts +0 -23
  30. package/dist/api.js +0 -140
  31. package/dist/commands/chat-enhanced.d.ts +0 -7
  32. package/dist/commands/chat-enhanced.js +0 -396
  33. package/dist/commands/chat.d.ts +0 -5
  34. package/dist/commands/chat.js +0 -125
  35. package/dist/commands/config.d.ts +0 -6
  36. package/dist/commands/config.js +0 -128
  37. package/dist/commands/init.d.ts +0 -5
  38. package/dist/commands/init.js +0 -171
  39. package/dist/commands/list-graphs.d.ts +0 -6
  40. package/dist/commands/list-graphs.js +0 -131
  41. package/dist/commands/pull.d.ts +0 -15
  42. package/dist/commands/pull.js +0 -305
  43. package/dist/commands/pull.llm-generate.d.ts +0 -10
  44. package/dist/commands/pull.llm-generate.js +0 -184
  45. package/dist/commands/push.d.ts +0 -6
  46. package/dist/commands/push.js +0 -268
  47. package/dist/exports.d.ts +0 -2
  48. package/dist/exports.js +0 -2
  49. package/dist/index.js.map +0 -7
  50. package/dist/types/config.d.ts +0 -9
  51. package/dist/types/config.js +0 -3
  52. package/dist/types/graph.d.ts +0 -10
  53. package/dist/types/graph.js +0 -1
  54. package/dist/utils/json-comparator.d.ts +0 -60
  55. package/dist/utils/json-comparator.js +0 -222
  56. package/dist/utils/mcp-runner.d.ts +0 -6
  57. package/dist/utils/mcp-runner.js +0 -147
  58. package/dist/utils/ts-loader.d.ts +0 -5
  59. package/dist/utils/ts-loader.js +0 -145
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/agents-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Inkeep CLI tool",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -10,6 +10,7 @@
10
10
  "exports": {
11
11
  ".": "./dist/index.js",
12
12
  "./config": "./dist/config.js",
13
+ "./commands/create": "./dist/commands/create.js",
13
14
  "./package.json": "./package.json"
14
15
  },
15
16
  "keywords": [
@@ -24,6 +25,7 @@
24
25
  "@ai-sdk/openai": "2.0.11",
25
26
  "@babel/parser": "^7.23.0",
26
27
  "@babel/types": "^7.23.0",
28
+ "@clack/prompts": "^0.11.0",
27
29
  "@libsql/client": "^0.15.7",
28
30
  "ai": "5.0.11",
29
31
  "ast-types": "^0.14.2",
@@ -32,16 +34,19 @@
32
34
  "commander": "^14.0.0",
33
35
  "dotenv": "^17.2.1",
34
36
  "drizzle-orm": "^0.44.5",
37
+ "fs-extra": "^11.2.0",
35
38
  "inquirer": "^9.2.12",
36
39
  "inquirer-autocomplete-prompt": "^3.0.1",
37
40
  "ora": "^8.0.1",
41
+ "picocolors": "^1.1.1",
38
42
  "recast": "^0.23.0",
39
43
  "ts-morph": "^26.0.0",
40
44
  "tsx": "^4.20.5",
41
- "@inkeep/agents-core": "^0.1.4",
42
- "@inkeep/agents-manage-ui": "^0.1.4"
45
+ "@inkeep/agents-core": "^0.1.6",
46
+ "@inkeep/agents-manage-ui": "^0.1.6"
43
47
  },
44
48
  "devDependencies": {
49
+ "@types/fs-extra": "^11.0.4",
45
50
  "@types/inquirer": "^9.0.7",
46
51
  "@types/node": "^20.10.0",
47
52
  "@vitest/coverage-v8": "^3.2.4",
@@ -49,17 +54,21 @@
49
54
  "typescript": "^5.9.2",
50
55
  "vitest": "^3.2.4"
51
56
  },
57
+ "peerDependencies": {
58
+ "zod": "^4.1.5"
59
+ },
52
60
  "engines": {
53
61
  "node": ">=22.0.0"
54
62
  },
55
63
  "publishConfig": {
56
- "access": "restricted",
64
+ "access": "public",
57
65
  "registry": "https://registry.npmjs.org/"
58
66
  },
59
67
  "files": [
60
68
  "dist",
61
69
  "README.md",
62
- "LICENSE.md"
70
+ "LICENSE.md",
71
+ "SUPPLEMENTAL_TERMS.md"
63
72
  ],
64
73
  "repository": {
65
74
  "type": "git",
@@ -68,7 +77,7 @@
68
77
  },
69
78
  "scripts": {
70
79
  "build": "tsup",
71
- "dev": "ENVIRONMENT=development tsup",
80
+ "dev": "MODE=watch tsup",
72
81
  "test": "node -e \"process.exit(process.env.CI ? 0 : 1)\" && vitest --run --config vitest.config.ci.ts || vitest --run",
73
82
  "test:debug": "vitest --run --reporter=verbose --no-coverage",
74
83
  "test:watch": "vitest",
@@ -1 +0,0 @@
1
- export {};
@@ -1,257 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { ManagementApiClient, ExecutionApiClient } from '../api.js';
3
- // Mock fetch globally
4
- const mockFetch = vi.fn();
5
- global.fetch = mockFetch;
6
- // Mock config module
7
- vi.mock('../config.js', () => ({
8
- getApiUrl: vi.fn(async (override) => override || 'http://localhost:3002'),
9
- getManagementApiUrl: vi.fn(async (override) => override || 'http://localhost:3002'),
10
- getExecutionApiUrl: vi.fn(async (override) => override || 'http://localhost:3003'),
11
- getTenantId: vi.fn(async () => 'test-tenant-id'),
12
- getProjectId: vi.fn(async () => 'test-project-id'),
13
- loadConfig: vi.fn(),
14
- }));
15
- describe('ApiClient', () => {
16
- let apiClient;
17
- let executionApiClient;
18
- beforeEach(async () => {
19
- apiClient = await ManagementApiClient.create();
20
- executionApiClient = await ExecutionApiClient.create();
21
- mockFetch.mockClear();
22
- vi.clearAllMocks();
23
- });
24
- afterEach(() => {
25
- vi.clearAllMocks();
26
- });
27
- describe('create', () => {
28
- it('should use default API URL when none provided', async () => {
29
- const client = await ManagementApiClient.create();
30
- expect(client).toBeDefined();
31
- });
32
- it('should use provided API URL', async () => {
33
- const customUrl = 'http://custom.example.com';
34
- const client = await ManagementApiClient.create(customUrl);
35
- expect(client).toBeDefined();
36
- });
37
- });
38
- describe('listGraphs', () => {
39
- it('should fetch and return graphs list successfully', async () => {
40
- const mockGraphs = [
41
- { id: 'graph1', name: 'Test Graph 1' },
42
- { id: 'graph2', name: 'Test Graph 2' },
43
- ];
44
- mockFetch.mockResolvedValueOnce({
45
- ok: true,
46
- json: async () => ({ data: mockGraphs }),
47
- });
48
- const result = await apiClient.listGraphs();
49
- expect(mockFetch).toHaveBeenCalledWith('http://localhost:3002/tenants/test-tenant-id/crud/projects/test-project-id/agent-graphs', {
50
- method: 'GET',
51
- headers: {
52
- 'Content-Type': 'application/json',
53
- },
54
- });
55
- expect(result).toEqual(mockGraphs);
56
- });
57
- it('should return empty array when no data field in response', async () => {
58
- mockFetch.mockResolvedValueOnce({
59
- ok: true,
60
- json: async () => ({}),
61
- });
62
- const result = await apiClient.listGraphs();
63
- expect(result).toEqual([]);
64
- });
65
- it('should throw error when request fails', async () => {
66
- mockFetch.mockResolvedValueOnce({
67
- ok: false,
68
- statusText: 'Not Found',
69
- });
70
- await expect(apiClient.listGraphs()).rejects.toThrow('Failed to list graphs: Not Found');
71
- });
72
- it('should throw error when tenant ID is not configured', async () => {
73
- const { getTenantId } = await import('../config.js');
74
- vi.mocked(getTenantId).mockResolvedValueOnce(undefined);
75
- const client = await ManagementApiClient.create();
76
- await expect(client.listGraphs()).rejects.toThrow('No tenant ID configured. Please run: inkeep init');
77
- });
78
- });
79
- describe('getGraph', () => {
80
- it('should return graph when found in list', async () => {
81
- const mockGraphs = [
82
- { id: 'graph1', name: 'Test Graph 1' },
83
- { id: 'graph2', name: 'Test Graph 2' },
84
- ];
85
- mockFetch.mockResolvedValueOnce({
86
- ok: true,
87
- json: async () => ({ data: mockGraphs }),
88
- });
89
- const result = await apiClient.getGraph('graph1');
90
- expect(result).toEqual({ id: 'graph1', name: 'Test Graph 1' });
91
- });
92
- it('should return null when graph not found', async () => {
93
- const mockGraphs = [{ id: 'graph1', name: 'Test Graph 1' }];
94
- mockFetch.mockResolvedValueOnce({
95
- ok: true,
96
- json: async () => ({ data: mockGraphs }),
97
- });
98
- const result = await apiClient.getGraph('nonexistent');
99
- expect(result).toBeNull();
100
- });
101
- });
102
- describe('pushGraph', () => {
103
- it('should push graph successfully', async () => {
104
- const graphDefinition = {
105
- id: 'test-graph',
106
- name: 'Test Graph',
107
- description: 'A test graph',
108
- };
109
- const expectedResponse = {
110
- id: 'test-graph',
111
- name: 'Test Graph',
112
- tenantId: 'test-tenant-id',
113
- };
114
- mockFetch.mockResolvedValueOnce({
115
- ok: true,
116
- json: async () => ({ data: expectedResponse }),
117
- });
118
- const result = await apiClient.pushGraph(graphDefinition);
119
- expect(mockFetch).toHaveBeenCalledWith('http://localhost:3002/tenants/test-tenant-id/crud/projects/test-project-id/graph/test-graph', {
120
- method: 'PUT',
121
- headers: {
122
- 'Content-Type': 'application/json',
123
- },
124
- body: JSON.stringify({
125
- ...graphDefinition,
126
- tenantId: 'test-tenant-id',
127
- }),
128
- });
129
- expect(result).toEqual(expectedResponse);
130
- });
131
- it('should throw error when graph has no id', async () => {
132
- const graphDefinition = {
133
- name: 'Test Graph',
134
- description: 'A test graph without id',
135
- };
136
- await expect(apiClient.pushGraph(graphDefinition)).rejects.toThrow('Graph must have an id property');
137
- });
138
- it('should throw error when push request fails', async () => {
139
- const graphDefinition = {
140
- id: 'test-graph',
141
- name: 'Test Graph',
142
- };
143
- mockFetch.mockResolvedValueOnce({
144
- ok: false,
145
- statusText: 'Bad Request',
146
- text: async () => 'Invalid graph definition',
147
- });
148
- await expect(apiClient.pushGraph(graphDefinition)).rejects.toThrow('Failed to push graph: Bad Request\nInvalid graph definition');
149
- });
150
- });
151
- describe('chatCompletion', () => {
152
- it('should return streaming response when content type is event-stream', async () => {
153
- const messages = [{ role: 'user', content: 'Hello' }];
154
- const mockStream = new ReadableStream();
155
- mockFetch.mockResolvedValueOnce({
156
- ok: true,
157
- headers: {
158
- get: (key) => (key === 'content-type' ? 'text/event-stream' : null),
159
- },
160
- body: mockStream,
161
- });
162
- const result = await executionApiClient.chatCompletion('test-graph', messages);
163
- expect(mockFetch).toHaveBeenCalled();
164
- expect(result).toBe(mockStream);
165
- });
166
- it('should return text response when content type is not event-stream', async () => {
167
- const messages = [{ role: 'user', content: 'Hello' }];
168
- const mockResponse = {
169
- choices: [
170
- {
171
- message: {
172
- content: 'Hello! How can I help you?',
173
- },
174
- },
175
- ],
176
- };
177
- mockFetch.mockResolvedValueOnce({
178
- ok: true,
179
- headers: {
180
- get: (key) => (key === 'content-type' ? 'application/json' : null),
181
- },
182
- json: async () => mockResponse,
183
- });
184
- const result = await executionApiClient.chatCompletion('test-graph', messages);
185
- expect(result).toBe('Hello! How can I help you?');
186
- });
187
- it('should handle response with result field instead of choices', async () => {
188
- const messages = [{ role: 'user', content: 'Hello' }];
189
- const mockResponse = {
190
- result: 'This is the result',
191
- };
192
- mockFetch.mockResolvedValueOnce({
193
- ok: true,
194
- headers: {
195
- get: (key) => (key === 'content-type' ? 'application/json' : null),
196
- },
197
- json: async () => mockResponse,
198
- });
199
- const result = await executionApiClient.chatCompletion('test-graph', messages);
200
- expect(result).toBe('This is the result');
201
- });
202
- it('should include conversation ID when provided', async () => {
203
- const messages = [{ role: 'user', content: 'Hello' }];
204
- const conversationId = 'conv-123';
205
- const mockStream = new ReadableStream();
206
- mockFetch.mockResolvedValueOnce({
207
- ok: true,
208
- headers: {
209
- get: (key) => (key === 'content-type' ? 'text/event-stream' : null),
210
- },
211
- body: mockStream,
212
- });
213
- await executionApiClient.chatCompletion('test-graph', messages, conversationId);
214
- expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
215
- body: JSON.stringify({
216
- model: 'gpt-4o-mini',
217
- messages,
218
- conversationId,
219
- stream: true,
220
- }),
221
- }));
222
- });
223
- it('should throw error when chat request fails', async () => {
224
- const messages = [{ role: 'user', content: 'Hello' }];
225
- mockFetch.mockResolvedValueOnce({
226
- ok: false,
227
- statusText: 'Unauthorized',
228
- text: async () => 'Invalid credentials',
229
- });
230
- await expect(executionApiClient.chatCompletion('test-graph', messages)).rejects.toThrow('Chat request failed: Unauthorized\nInvalid credentials');
231
- });
232
- it('should return empty string when no content in response', async () => {
233
- const messages = [{ role: 'user', content: 'Hello' }];
234
- mockFetch.mockResolvedValueOnce({
235
- ok: true,
236
- headers: {
237
- get: (key) => (key === 'content-type' ? 'application/json' : null),
238
- },
239
- json: async () => ({}),
240
- });
241
- const result = await executionApiClient.chatCompletion('test-graph', messages);
242
- expect(result).toBe('');
243
- });
244
- });
245
- describe('checkTenantId', () => {
246
- it('should throw error for all methods when tenant ID is not configured', async () => {
247
- const { getTenantId } = await import('../config.js');
248
- vi.mocked(getTenantId).mockResolvedValue(undefined);
249
- const client = await ManagementApiClient.create();
250
- const execClient = await ExecutionApiClient.create();
251
- await expect(client.listGraphs()).rejects.toThrow('No tenant ID configured. Please run: inkeep init');
252
- await expect(client.getGraph('test')).rejects.toThrow('No tenant ID configured. Please run: inkeep init');
253
- await expect(client.pushGraph({ id: 'test' })).rejects.toThrow('No tenant ID configured. Please run: inkeep init');
254
- await expect(execClient.chatCompletion('test', [])).rejects.toThrow();
255
- });
256
- });
257
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,153 +0,0 @@
1
- import { execSync } from 'node:child_process';
2
- import { readFileSync, existsSync } from 'node:fs';
3
- import { dirname, join } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = dirname(__filename);
8
- // Path to the compiled CLI
9
- const cliPath = join(__dirname, '..', '..', 'dist', 'index.js');
10
- // Helper function to execute CLI commands
11
- function runCli(args) {
12
- // Check if CLI binary exists before attempting to run
13
- if (!existsSync(cliPath)) {
14
- return {
15
- stdout: '',
16
- stderr: `CLI binary not found at ${cliPath}`,
17
- exitCode: 1,
18
- };
19
- }
20
- try {
21
- const stdout = execSync(`node ${cliPath} ${args.join(' ')}`, {
22
- encoding: 'utf8',
23
- stdio: ['pipe', 'pipe', 'pipe'],
24
- timeout: 15000, // Increased to 15 second timeout for CI
25
- killSignal: 'SIGTERM', // Use SIGTERM first for cleaner shutdown
26
- windowsHide: true, // Hide windows on Windows
27
- env: {
28
- ...process.env,
29
- NODE_ENV: 'test',
30
- CI: 'true', // Signal to CLI that it's running in CI
31
- NODE_OPTIONS: '--max-old-space-size=256', // Limit memory usage
32
- },
33
- });
34
- return { stdout, stderr: '', exitCode: 0 };
35
- }
36
- catch (error) {
37
- // Handle timeout specifically
38
- if (error.code === 'TIMEOUT') {
39
- return {
40
- stdout: error.stdout || '',
41
- stderr: 'Command timed out',
42
- exitCode: 124, // Standard timeout exit code
43
- };
44
- }
45
- return {
46
- stdout: error.stdout || '',
47
- stderr: error.stderr || error.message || 'Unknown error',
48
- exitCode: error.status || 1,
49
- };
50
- }
51
- }
52
- describe('Inkeep CLI', () => {
53
- beforeEach(() => {
54
- // Mock console methods to capture output during tests
55
- vi.spyOn(console, 'log').mockImplementation(() => { });
56
- vi.spyOn(console, 'error').mockImplementation(() => { });
57
- });
58
- afterEach(async () => {
59
- vi.restoreAllMocks();
60
- // Small delay to allow processes to fully terminate
61
- await new Promise(resolve => setTimeout(resolve, 100));
62
- // Force garbage collection to clean up any hanging references
63
- if (global.gc) {
64
- global.gc();
65
- }
66
- });
67
- describe('--version command', () => {
68
- it('should display the version number', () => {
69
- const result = runCli(['--version']);
70
- expect(result.exitCode).toBe(0);
71
- expect(result.stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/);
72
- });
73
- it('should match the version in package.json', () => {
74
- const packageJsonPath = join(__dirname, '..', '..', 'package.json');
75
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
76
- const expectedVersion = packageJson.version;
77
- const result = runCli(['--version']);
78
- expect(result.stdout.trim()).toBe(expectedVersion);
79
- });
80
- });
81
- describe('push command', () => {
82
- it('should require config path argument', () => {
83
- const result = runCli(['push']);
84
- expect(result.exitCode).toBe(1);
85
- expect(result.stderr).toContain("error: missing required argument 'graph-path'");
86
- });
87
- it('should accept --api-url option', () => {
88
- const result = runCli(['push', 'non-existent.js', '--api-url', 'http://example.com']);
89
- // Will fail because file doesn't exist, but should accept the option
90
- expect(result.exitCode).toBe(1);
91
- // Should fail for file not found, not for invalid option
92
- expect(result.stderr).not.toContain('unknown option');
93
- });
94
- });
95
- describe('chat command', () => {
96
- it('should accept optional graph-id argument', () => {
97
- const result = runCli(['chat', '--help']);
98
- expect(result.exitCode).toBe(0);
99
- expect(result.stdout).toContain('[graph-id]');
100
- expect(result.stdout).toContain('Start an interactive chat session');
101
- });
102
- });
103
- describe('list-graphs command', () => {
104
- it('should accept --url option', () => {
105
- const result = runCli(['list-graphs', '--help']);
106
- expect(result.exitCode).toBe(0);
107
- expect(result.stdout).toContain('List all available graphs');
108
- expect(result.stdout).toContain('--api-url');
109
- });
110
- });
111
- describe('--help command', () => {
112
- it('should display help information', () => {
113
- const result = runCli(['--help']);
114
- expect(result.exitCode).toBe(0);
115
- expect(result.stdout).toContain('CLI tool for Inkeep Agent Framework');
116
- expect(result.stdout).toContain('Commands:');
117
- expect(result.stdout).toContain('push');
118
- expect(result.stdout).toContain('chat');
119
- expect(result.stdout).toContain('tenant');
120
- expect(result.stdout).toContain('list-graphs');
121
- });
122
- it('should display help for push command', () => {
123
- const result = runCli(['push', '--help']);
124
- expect(result.exitCode).toBe(0);
125
- expect(result.stdout).toContain('Push a graph configuration');
126
- expect(result.stdout).toContain('--api-url');
127
- });
128
- it('should display help for chat command', () => {
129
- const result = runCli(['chat', '--help']);
130
- expect(result.exitCode).toBe(0);
131
- expect(result.stdout).toContain('Start an interactive chat session');
132
- expect(result.stdout).toContain('[graph-id]');
133
- });
134
- });
135
- describe('invalid commands', () => {
136
- it('should show error for unknown command', () => {
137
- const result = runCli(['unknown-command']);
138
- expect(result.exitCode).toBe(1);
139
- expect(result.stderr).toContain('error: unknown command');
140
- });
141
- });
142
- describe('CLI structure', () => {
143
- it('should have correct CLI name', () => {
144
- const result = runCli(['--help']);
145
- expect(result.stdout).toContain('inkeep');
146
- });
147
- it('should be executable', () => {
148
- // This test ensures the CLI can be executed without throwing
149
- const result = runCli(['--version']);
150
- expect(result.exitCode).toBe(0);
151
- });
152
- });
153
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,154 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { configGetCommand, configListCommand, configSetCommand } from '../../commands/config.js';
4
- // Mock fs functions
5
- vi.mock('node:fs', async () => {
6
- const actual = await vi.importActual('node:fs');
7
- return {
8
- ...actual,
9
- existsSync: vi.fn(),
10
- readFileSync: vi.fn(),
11
- writeFileSync: vi.fn(),
12
- };
13
- });
14
- describe('Config Command', () => {
15
- let consoleLogSpy;
16
- let consoleErrorSpy;
17
- let processExitSpy;
18
- beforeEach(() => {
19
- consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
20
- consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
21
- processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
22
- throw new Error('process.exit called');
23
- });
24
- vi.clearAllMocks();
25
- });
26
- afterEach(() => {
27
- consoleLogSpy.mockRestore();
28
- consoleErrorSpy.mockRestore();
29
- processExitSpy.mockRestore();
30
- });
31
- describe('configGetCommand', () => {
32
- it('should display all config when no key is provided', async () => {
33
- const mockConfig = `import { defineConfig } from '@inkeep/agents-cli';
34
-
35
- export default defineConfig({
36
- tenantId: 'test-tenant-123',
37
- apiUrl: 'https://api.example.com',
38
- });`;
39
- vi.mocked(existsSync).mockReturnValue(true);
40
- vi.mocked(readFileSync).mockReturnValue(mockConfig);
41
- await configGetCommand();
42
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Current configuration'));
43
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Tenant ID'), 'test-tenant-123');
44
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('API URL'), 'https://api.example.com');
45
- });
46
- it('should display specific config value when key is provided', async () => {
47
- const mockConfig = `import { defineConfig } from '@inkeep/agents-cli';
48
-
49
- export default defineConfig({
50
- tenantId: 'test-tenant-123',
51
- apiUrl: 'https://api.example.com',
52
- });`;
53
- vi.mocked(existsSync).mockReturnValue(true);
54
- vi.mocked(readFileSync).mockReturnValue(mockConfig);
55
- await configGetCommand('tenantId');
56
- expect(consoleLogSpy).toHaveBeenCalledWith('test-tenant-123');
57
- });
58
- it('should error when config file does not exist', async () => {
59
- vi.mocked(existsSync).mockReturnValue(false);
60
- await expect(configGetCommand()).rejects.toThrow('process.exit called');
61
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('No configuration file found'));
62
- expect(processExitSpy).toHaveBeenCalledWith(1);
63
- });
64
- it('should error for unknown configuration key', async () => {
65
- const mockConfig = `import { defineConfig } from '@inkeep/agents-cli';
66
-
67
- export default defineConfig({
68
- tenantId: 'test-tenant-123',
69
- });`;
70
- vi.mocked(existsSync).mockReturnValue(true);
71
- vi.mocked(readFileSync).mockReturnValue(mockConfig);
72
- await expect(configGetCommand('unknownKey')).rejects.toThrow('process.exit called');
73
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Unknown configuration key: unknownKey'));
74
- });
75
- });
76
- describe('configSetCommand', () => {
77
- it('should update tenantId in existing config file', async () => {
78
- const originalConfig = `import { defineConfig } from '@inkeep/agents-cli';
79
-
80
- export default defineConfig({
81
- tenantId: 'old-tenant',
82
- apiUrl: 'http://localhost:3002',
83
- });`;
84
- const expectedConfig = `import { defineConfig } from '@inkeep/agents-cli';
85
-
86
- export default defineConfig({
87
- tenantId: 'new-tenant-456',
88
- apiUrl: 'http://localhost:3002',
89
- });`;
90
- vi.mocked(existsSync).mockReturnValue(true);
91
- vi.mocked(readFileSync).mockReturnValue(originalConfig);
92
- await configSetCommand('tenantId', 'new-tenant-456');
93
- expect(writeFileSync).toHaveBeenCalledWith(expect.stringContaining('inkeep.config.ts'), expectedConfig);
94
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.any(String), // The checkmark
95
- expect.stringContaining('Updated tenantId'), expect.stringContaining('new-tenant-456'));
96
- });
97
- it('should update apiUrl in existing config file', async () => {
98
- const originalConfig = `import { defineConfig } from '@inkeep/agents-cli';
99
-
100
- export default defineConfig({
101
- tenantId: 'test-tenant',
102
- apiUrl: 'http://localhost:3002',
103
- });`;
104
- vi.mocked(existsSync).mockReturnValue(true);
105
- vi.mocked(readFileSync).mockReturnValue(originalConfig);
106
- await configSetCommand('apiUrl', 'https://api.example.com');
107
- expect(writeFileSync).toHaveBeenCalledWith(expect.stringContaining('inkeep.config.ts'), expect.stringContaining("apiUrl: 'https://api.example.com'"));
108
- });
109
- it('should create new config file if it does not exist', async () => {
110
- vi.mocked(existsSync).mockReturnValue(false);
111
- await configSetCommand('tenantId', 'new-tenant');
112
- expect(writeFileSync).toHaveBeenCalledWith(expect.stringContaining('inkeep.config.ts'), expect.stringContaining("tenantId: 'new-tenant'"));
113
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.any(String), // The checkmark
114
- expect.stringContaining('Created config file and set tenantId to'), expect.any(String) // The value
115
- );
116
- });
117
- it('should validate apiUrl format', async () => {
118
- vi.mocked(existsSync).mockReturnValue(false);
119
- await expect(configSetCommand('apiUrl', 'not-a-url')).rejects.toThrow('process.exit called');
120
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid URL format'));
121
- expect(processExitSpy).toHaveBeenCalledWith(1);
122
- });
123
- it('should reject invalid configuration keys', async () => {
124
- await expect(configSetCommand('invalidKey', 'value')).rejects.toThrow('process.exit called');
125
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid configuration key: invalidKey'));
126
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Available keys: tenantId, apiUrl'));
127
- });
128
- it('should add missing tenantId to config', async () => {
129
- const originalConfig = `import { defineConfig } from '@inkeep/agents-cli';
130
-
131
- export default defineConfig({
132
- apiUrl: 'http://localhost:3002',
133
- });`;
134
- vi.mocked(existsSync).mockReturnValue(true);
135
- vi.mocked(readFileSync).mockReturnValue(originalConfig);
136
- await configSetCommand('tenantId', 'new-tenant');
137
- expect(writeFileSync).toHaveBeenCalledWith(expect.stringContaining('inkeep.config.ts'), expect.stringContaining("tenantId: 'new-tenant'"));
138
- });
139
- });
140
- describe('configListCommand', () => {
141
- it('should call configGetCommand without a key', async () => {
142
- const mockConfig = `import { defineConfig } from '@inkeep/agents-cli';
143
-
144
- export default defineConfig({
145
- tenantId: 'test-tenant',
146
- apiUrl: 'http://localhost:3002',
147
- });`;
148
- vi.mocked(existsSync).mockReturnValue(true);
149
- vi.mocked(readFileSync).mockReturnValue(mockConfig);
150
- await configListCommand();
151
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Current configuration'));
152
- });
153
- });
154
- });
@@ -1 +0,0 @@
1
- export {};