@inkeep/agents-cli 0.1.0 ā 0.1.2
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/LICENSE.md +22 -17
- package/README.md +1 -13
- package/dist/__tests__/config-validation.test.js +7 -15
- package/dist/__tests__/utils/ts-loader.test.js +0 -1
- package/dist/commands/pull.js +0 -8
- package/dist/config.d.ts +6 -36
- package/dist/config.js +9 -292
- package/dist/index.d.ts +2 -2
- package/dist/index.js +37359 -96
- package/dist/index.js.map +7 -0
- package/dist/utils/ts-loader.js +0 -1
- package/package.json +22 -21
- package/dist/__tests__/commands/pull-retry.test.d.ts +0 -1
- package/dist/__tests__/commands/pull-retry.test.js +0 -156
- package/dist/__tests__/utils/port-manager.test.d.ts +0 -1
- package/dist/__tests__/utils/port-manager.test.js +0 -144
- package/dist/commands/mcp-list.d.ts +0 -4
- package/dist/commands/mcp-list.js +0 -156
- package/dist/commands/mcp-start-simple.d.ts +0 -5
- package/dist/commands/mcp-start-simple.js +0 -193
- package/dist/commands/mcp-start.d.ts +0 -5
- package/dist/commands/mcp-start.js +0 -217
- package/dist/commands/mcp-status.d.ts +0 -1
- package/dist/commands/mcp-status.js +0 -96
- package/dist/commands/mcp-stop.d.ts +0 -5
- package/dist/commands/mcp-stop.js +0 -160
- package/dist/utils/port-manager.d.ts +0 -43
- package/dist/utils/port-manager.js +0 -92
package/dist/utils/ts-loader.js
CHANGED
|
@@ -106,7 +106,6 @@ loadModule();
|
|
|
106
106
|
if (!process.env.ENVIRONMENT) {
|
|
107
107
|
envVars.ENVIRONMENT = 'test';
|
|
108
108
|
envVars.DB_FILE_NAME = ':memory:'; // Use in-memory DB for CLI testing
|
|
109
|
-
envVars.INKEEP_TENANT_ID = 'test-tenant';
|
|
110
109
|
envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || 'test-key';
|
|
111
110
|
}
|
|
112
111
|
// Use node directly with tsx for better performance in CI
|
package/package.json
CHANGED
|
@@ -1,26 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkeep/agents-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Inkeep CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"inkeep": "./dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
|
-
".": "./dist/
|
|
12
|
-
"./config": "./dist/
|
|
13
|
-
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "tsc",
|
|
16
|
-
"dev": "tsc --watch",
|
|
17
|
-
"start": "node ./dist/index.js",
|
|
18
|
-
"test": "node -e \"process.exit(process.env.CI ? 0 : 1)\" && vitest --run --config vitest.config.ci.ts || vitest --run",
|
|
19
|
-
"test:debug": "vitest --run --reporter=verbose --no-coverage",
|
|
20
|
-
"test:watch": "vitest",
|
|
21
|
-
"test:coverage": "node -e \"process.exit(process.env.CI ? 0 : 1)\" && vitest --run --coverage --config vitest.config.ci.ts || vitest --run --coverage",
|
|
22
|
-
"test:ci": "vitest --run --config vitest.config.ci.ts",
|
|
23
|
-
"typecheck": "tsc --noEmit --project tsconfig.typecheck.json"
|
|
11
|
+
".": "./dist/index.js",
|
|
12
|
+
"./config": "./dist/config.js",
|
|
13
|
+
"./package.json": "./package.json"
|
|
24
14
|
},
|
|
25
15
|
"keywords": [
|
|
26
16
|
"cli",
|
|
@@ -34,7 +24,6 @@
|
|
|
34
24
|
"@ai-sdk/openai": "2.0.11",
|
|
35
25
|
"@babel/parser": "^7.23.0",
|
|
36
26
|
"@babel/types": "^7.23.0",
|
|
37
|
-
"@inkeep/agents-core": "workspace:*",
|
|
38
27
|
"@libsql/client": "^0.15.7",
|
|
39
28
|
"ai": "5.0.11",
|
|
40
29
|
"ast-types": "^0.14.2",
|
|
@@ -42,23 +31,25 @@
|
|
|
42
31
|
"cli-table3": "^0.6.3",
|
|
43
32
|
"commander": "^14.0.0",
|
|
44
33
|
"dotenv": "^17.2.1",
|
|
45
|
-
"drizzle-orm": "^0.
|
|
34
|
+
"drizzle-orm": "^0.44.5",
|
|
46
35
|
"inquirer": "^9.2.12",
|
|
47
36
|
"inquirer-autocomplete-prompt": "^3.0.1",
|
|
48
37
|
"ora": "^8.0.1",
|
|
49
38
|
"recast": "^0.23.0",
|
|
50
|
-
"ts-morph": "^26.0.0"
|
|
39
|
+
"ts-morph": "^26.0.0",
|
|
40
|
+
"tsx": "^4.20.5",
|
|
41
|
+
"@inkeep/agents-core": "^0.1.4"
|
|
51
42
|
},
|
|
52
43
|
"devDependencies": {
|
|
53
44
|
"@types/inquirer": "^9.0.7",
|
|
54
45
|
"@types/node": "^20.10.0",
|
|
55
46
|
"@vitest/coverage-v8": "^3.2.4",
|
|
56
|
-
"
|
|
47
|
+
"tsup": "^8.5.0",
|
|
57
48
|
"typescript": "^5.9.2",
|
|
58
49
|
"vitest": "^3.2.4"
|
|
59
50
|
},
|
|
60
51
|
"engines": {
|
|
61
|
-
"node": ">=
|
|
52
|
+
"node": ">=22.0.0"
|
|
62
53
|
},
|
|
63
54
|
"publishConfig": {
|
|
64
55
|
"access": "restricted",
|
|
@@ -71,7 +62,17 @@
|
|
|
71
62
|
],
|
|
72
63
|
"repository": {
|
|
73
64
|
"type": "git",
|
|
74
|
-
"url": "git+https://github.com/inkeep/
|
|
65
|
+
"url": "git+https://github.com/inkeep/agents.git",
|
|
75
66
|
"directory": "agents-cli"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsup",
|
|
70
|
+
"dev": "ENVIRONMENT=development tsup",
|
|
71
|
+
"test": "node -e \"process.exit(process.env.CI ? 0 : 1)\" && vitest --run --config vitest.config.ci.ts || vitest --run",
|
|
72
|
+
"test:debug": "vitest --run --reporter=verbose --no-coverage",
|
|
73
|
+
"test:watch": "vitest",
|
|
74
|
+
"test:coverage": "node -e \"process.exit(process.env.CI ? 0 : 1)\" && vitest --run --coverage --config vitest.config.ci.ts || vitest --run --coverage",
|
|
75
|
+
"test:ci": "vitest --run --config vitest.config.ci.ts",
|
|
76
|
+
"typecheck": "tsc --noEmit --project tsconfig.typecheck.json"
|
|
76
77
|
}
|
|
77
78
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { pullCommand } from '../../commands/pull.js';
|
|
3
|
-
// Mock dependencies
|
|
4
|
-
vi.mock('../../config.js', () => ({
|
|
5
|
-
validateConfiguration: vi.fn().mockResolvedValue({
|
|
6
|
-
tenantId: 'test-tenant',
|
|
7
|
-
projectId: 'test-project',
|
|
8
|
-
apiUrl: 'http://test-api.com',
|
|
9
|
-
sources: {
|
|
10
|
-
tenantId: 'env',
|
|
11
|
-
projectId: 'env',
|
|
12
|
-
apiUrl: 'env',
|
|
13
|
-
},
|
|
14
|
-
modelConfig: {
|
|
15
|
-
model: 'anthropic/claude-3-5-sonnet-20241022',
|
|
16
|
-
providerOptions: { anthropic: {} },
|
|
17
|
-
},
|
|
18
|
-
}),
|
|
19
|
-
}));
|
|
20
|
-
vi.mock('../../commands/pull.llm-generate.js', () => ({
|
|
21
|
-
generateTypeScriptFileWithLLM: vi.fn(),
|
|
22
|
-
}));
|
|
23
|
-
vi.mock('../../utils/graph-converter.js', () => ({
|
|
24
|
-
convertTypeScriptToJson: vi.fn(),
|
|
25
|
-
}));
|
|
26
|
-
vi.mock('../../utils/json-comparator.js', () => ({
|
|
27
|
-
compareJsonObjects: vi.fn(),
|
|
28
|
-
getDifferenceSummary: vi.fn(),
|
|
29
|
-
}));
|
|
30
|
-
vi.mock('node:fs', () => ({
|
|
31
|
-
existsSync: vi.fn().mockReturnValue(true),
|
|
32
|
-
mkdirSync: vi.fn(),
|
|
33
|
-
writeFileSync: vi.fn(),
|
|
34
|
-
}));
|
|
35
|
-
vi.mock('node:path', () => ({
|
|
36
|
-
join: vi.fn((...args) => args.join('/')),
|
|
37
|
-
resolve: vi.fn((...args) => args.join('/')),
|
|
38
|
-
}));
|
|
39
|
-
describe('pull command retry functionality', () => {
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
vi.clearAllMocks();
|
|
42
|
-
});
|
|
43
|
-
it('should retry LLM generation when validation fails', async () => {
|
|
44
|
-
const { generateTypeScriptFileWithLLM } = await import('../../commands/pull.llm-generate.js');
|
|
45
|
-
const { convertTypeScriptToJson } = await import('../../commands/pull.js');
|
|
46
|
-
const { compareJsonObjects } = await import('../../utils/json-comparator.js');
|
|
47
|
-
// Mock fetch to return graph data
|
|
48
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
49
|
-
ok: true,
|
|
50
|
-
json: () => Promise.resolve({
|
|
51
|
-
data: {
|
|
52
|
-
id: 'test-graph',
|
|
53
|
-
name: 'Test Graph',
|
|
54
|
-
agents: [],
|
|
55
|
-
},
|
|
56
|
-
}),
|
|
57
|
-
});
|
|
58
|
-
// Mock validation to fail first time, then succeed
|
|
59
|
-
vi.mocked(compareJsonObjects)
|
|
60
|
-
.mockReturnValueOnce({
|
|
61
|
-
isEqual: false,
|
|
62
|
-
differences: [
|
|
63
|
-
{
|
|
64
|
-
path: 'agents[0].id',
|
|
65
|
-
type: 'different',
|
|
66
|
-
value1: 'agent1',
|
|
67
|
-
value2: 'agent2',
|
|
68
|
-
description: 'ID mismatch',
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
stats: { totalKeys: 1, differentKeys: 1, missingKeys: 0, extraKeys: 0 },
|
|
72
|
-
})
|
|
73
|
-
.mockReturnValueOnce({
|
|
74
|
-
isEqual: true,
|
|
75
|
-
differences: [],
|
|
76
|
-
stats: { totalKeys: 1, differentKeys: 0, missingKeys: 0, extraKeys: 0 },
|
|
77
|
-
});
|
|
78
|
-
vi.mocked(convertTypeScriptToJson).mockResolvedValue({
|
|
79
|
-
id: 'test-graph',
|
|
80
|
-
name: 'Test Graph',
|
|
81
|
-
agents: [],
|
|
82
|
-
tools: [],
|
|
83
|
-
contextConfigs: [],
|
|
84
|
-
credentialReferences: [],
|
|
85
|
-
});
|
|
86
|
-
vi.mocked(generateTypeScriptFileWithLLM).mockResolvedValue(undefined);
|
|
87
|
-
// Mock process.exit to prevent actual exit
|
|
88
|
-
const originalExit = process.exit;
|
|
89
|
-
process.exit = vi.fn();
|
|
90
|
-
try {
|
|
91
|
-
await pullCommand('test-graph', { maxRetries: 2 });
|
|
92
|
-
// Verify that generateTypeScriptFileWithLLM was called twice (initial + retry)
|
|
93
|
-
expect(generateTypeScriptFileWithLLM).toHaveBeenCalledTimes(2);
|
|
94
|
-
// Verify retry context was passed on second call
|
|
95
|
-
const secondCall = vi.mocked(generateTypeScriptFileWithLLM).mock.calls[1];
|
|
96
|
-
expect(secondCall[4]).toEqual({
|
|
97
|
-
attempt: 2,
|
|
98
|
-
maxRetries: 2,
|
|
99
|
-
previousDifferences: ['agents[0].id: ID mismatch'],
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
finally {
|
|
103
|
-
process.exit = originalExit;
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
it('should fail after max retries exceeded', async () => {
|
|
107
|
-
const { generateTypeScriptFileWithLLM } = await import('../../commands/pull.llm-generate.js');
|
|
108
|
-
const { convertTypeScriptToJson } = await import('../../commands/pull.js');
|
|
109
|
-
const { compareJsonObjects } = await import('../../utils/json-comparator.js');
|
|
110
|
-
// Mock fetch to return graph data
|
|
111
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
112
|
-
ok: true,
|
|
113
|
-
json: () => Promise.resolve({
|
|
114
|
-
data: {
|
|
115
|
-
id: 'test-graph',
|
|
116
|
-
name: 'Test Graph',
|
|
117
|
-
agents: [],
|
|
118
|
-
},
|
|
119
|
-
}),
|
|
120
|
-
});
|
|
121
|
-
// Mock validation to always fail
|
|
122
|
-
vi.mocked(compareJsonObjects).mockReturnValue({
|
|
123
|
-
isEqual: false,
|
|
124
|
-
differences: [
|
|
125
|
-
{
|
|
126
|
-
path: 'agents[0].id',
|
|
127
|
-
type: 'different',
|
|
128
|
-
value1: 'agent1',
|
|
129
|
-
value2: 'agent2',
|
|
130
|
-
description: 'ID mismatch',
|
|
131
|
-
},
|
|
132
|
-
],
|
|
133
|
-
stats: { totalKeys: 1, differentKeys: 1, missingKeys: 0, extraKeys: 0 },
|
|
134
|
-
});
|
|
135
|
-
vi.mocked(convertTypeScriptToJson).mockResolvedValue({
|
|
136
|
-
id: 'test-graph',
|
|
137
|
-
name: 'Test Graph',
|
|
138
|
-
agents: [],
|
|
139
|
-
tools: [],
|
|
140
|
-
contextConfigs: [],
|
|
141
|
-
credentialReferences: [],
|
|
142
|
-
});
|
|
143
|
-
vi.mocked(generateTypeScriptFileWithLLM).mockResolvedValue(undefined);
|
|
144
|
-
// Mock process.exit to prevent actual exit
|
|
145
|
-
const originalExit = process.exit;
|
|
146
|
-
process.exit = vi.fn();
|
|
147
|
-
try {
|
|
148
|
-
await pullCommand('test-graph', { maxRetries: 2 });
|
|
149
|
-
// Verify that generateTypeScriptFileWithLLM was called the maximum number of times
|
|
150
|
-
expect(generateTypeScriptFileWithLLM).toHaveBeenCalledTimes(2);
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
process.exit = originalExit;
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { createServer } from 'node:net';
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import { PortManager } from '../../utils/port-manager.js';
|
|
4
|
-
describe('PortManager', () => {
|
|
5
|
-
let portManager;
|
|
6
|
-
let allocatedPorts = [];
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
portManager = PortManager.getInstance();
|
|
9
|
-
// Clean up any existing allocations
|
|
10
|
-
portManager.releaseAll();
|
|
11
|
-
allocatedPorts = [];
|
|
12
|
-
});
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
// Clean up any ports we allocated during tests
|
|
15
|
-
for (const port of allocatedPorts) {
|
|
16
|
-
portManager.releasePort(port);
|
|
17
|
-
}
|
|
18
|
-
portManager.releaseAll();
|
|
19
|
-
});
|
|
20
|
-
describe('getInstance', () => {
|
|
21
|
-
it('should return a singleton instance', () => {
|
|
22
|
-
const instance1 = PortManager.getInstance();
|
|
23
|
-
const instance2 = PortManager.getInstance();
|
|
24
|
-
expect(instance1).toBe(instance2);
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
describe('allocatePort', () => {
|
|
28
|
-
it('should allocate a port in the valid range', async () => {
|
|
29
|
-
const port = await portManager.allocatePort();
|
|
30
|
-
allocatedPorts.push(port);
|
|
31
|
-
expect(port).toBeGreaterThanOrEqual(3100);
|
|
32
|
-
expect(port).toBeLessThanOrEqual(3200);
|
|
33
|
-
expect(portManager.getAllocatedPorts()).toContain(port);
|
|
34
|
-
});
|
|
35
|
-
it('should allocate preferred port when available', async () => {
|
|
36
|
-
const preferredPort = 3150;
|
|
37
|
-
const port = await portManager.allocatePort(preferredPort);
|
|
38
|
-
allocatedPorts.push(port);
|
|
39
|
-
expect(port).toBe(preferredPort);
|
|
40
|
-
expect(portManager.getAllocatedPorts()).toContain(port);
|
|
41
|
-
});
|
|
42
|
-
it('should allocate different port when preferred port is not available', async () => {
|
|
43
|
-
// Create a server on preferred port to make it unavailable
|
|
44
|
-
const preferredPort = 3151;
|
|
45
|
-
const server = createServer();
|
|
46
|
-
return new Promise((resolve) => {
|
|
47
|
-
server.listen(preferredPort, '127.0.0.1', async () => {
|
|
48
|
-
try {
|
|
49
|
-
const port = await portManager.allocatePort(preferredPort);
|
|
50
|
-
allocatedPorts.push(port);
|
|
51
|
-
expect(port).not.toBe(preferredPort);
|
|
52
|
-
expect(port).toBeGreaterThanOrEqual(3100);
|
|
53
|
-
expect(port).toBeLessThanOrEqual(3200);
|
|
54
|
-
server.close(() => resolve());
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
server.close(() => {
|
|
58
|
-
throw error;
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
it('should not allocate the same port twice', async () => {
|
|
65
|
-
const port1 = await portManager.allocatePort();
|
|
66
|
-
const port2 = await portManager.allocatePort();
|
|
67
|
-
allocatedPorts.push(port1, port2);
|
|
68
|
-
expect(port1).not.toBe(port2);
|
|
69
|
-
expect(portManager.getAllocatedPorts()).toContain(port1);
|
|
70
|
-
expect(portManager.getAllocatedPorts()).toContain(port2);
|
|
71
|
-
});
|
|
72
|
-
it('should throw error when no ports are available', async () => {
|
|
73
|
-
// Allocate all ports in the range (101 ports: 3100-3200 inclusive)
|
|
74
|
-
const allPorts = [];
|
|
75
|
-
for (let i = 3100; i <= 3200; i++) {
|
|
76
|
-
try {
|
|
77
|
-
const port = await portManager.allocatePort();
|
|
78
|
-
allPorts.push(port);
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
break; // Stop when we can't allocate more
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
allocatedPorts.push(...allPorts);
|
|
85
|
-
// Try to allocate one more port
|
|
86
|
-
await expect(portManager.allocatePort()).rejects.toThrow('No available ports in range 3100-3200');
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
describe('releasePort', () => {
|
|
90
|
-
it('should release an allocated port', async () => {
|
|
91
|
-
const port = await portManager.allocatePort();
|
|
92
|
-
expect(portManager.getAllocatedPorts()).toContain(port);
|
|
93
|
-
portManager.releasePort(port);
|
|
94
|
-
expect(portManager.getAllocatedPorts()).not.toContain(port);
|
|
95
|
-
});
|
|
96
|
-
it('should allow releasing a non-allocated port without error', () => {
|
|
97
|
-
expect(() => portManager.releasePort(9999)).not.toThrow();
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
describe('releaseAll', () => {
|
|
101
|
-
it('should release all allocated ports', async () => {
|
|
102
|
-
const port1 = await portManager.allocatePort();
|
|
103
|
-
const port2 = await portManager.allocatePort();
|
|
104
|
-
expect(portManager.getAllocatedPorts()).toHaveLength(2);
|
|
105
|
-
portManager.releaseAll();
|
|
106
|
-
expect(portManager.getAllocatedPorts()).toHaveLength(0);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
describe('getAllocatedPorts', () => {
|
|
110
|
-
it('should return empty array when no ports allocated', () => {
|
|
111
|
-
expect(portManager.getAllocatedPorts()).toEqual([]);
|
|
112
|
-
});
|
|
113
|
-
it('should return sorted array of allocated ports', async () => {
|
|
114
|
-
const port1 = await portManager.allocatePort(3120);
|
|
115
|
-
const port2 = await portManager.allocatePort(3110);
|
|
116
|
-
allocatedPorts.push(port1, port2);
|
|
117
|
-
const allocated = portManager.getAllocatedPorts();
|
|
118
|
-
expect(allocated).toEqual([3110, 3120]);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
describe('getStats', () => {
|
|
122
|
-
it('should return correct statistics when no ports allocated', () => {
|
|
123
|
-
const stats = portManager.getStats();
|
|
124
|
-
expect(stats).toEqual({
|
|
125
|
-
allocated: 0,
|
|
126
|
-
available: 101, // 3100-3200 inclusive = 101 ports
|
|
127
|
-
range: { min: 3100, max: 3200 },
|
|
128
|
-
ports: [],
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
it('should return correct statistics with allocated ports', async () => {
|
|
132
|
-
const port1 = await portManager.allocatePort();
|
|
133
|
-
const port2 = await portManager.allocatePort();
|
|
134
|
-
allocatedPorts.push(port1, port2);
|
|
135
|
-
const stats = portManager.getStats();
|
|
136
|
-
expect(stats.allocated).toBe(2);
|
|
137
|
-
expect(stats.available).toBe(99);
|
|
138
|
-
expect(stats.range).toEqual({ min: 3100, max: 3200 });
|
|
139
|
-
expect(stats.ports).toHaveLength(2);
|
|
140
|
-
expect(stats.ports).toContain(port1);
|
|
141
|
-
expect(stats.ports).toContain(port2);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
});
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import Table from 'cli-table3';
|
|
6
|
-
const MCP_DIR = join(homedir(), '.inkeep', 'mcp');
|
|
7
|
-
const REGISTRY_FILE = join(MCP_DIR, 'servers.json');
|
|
8
|
-
function loadRegistry() {
|
|
9
|
-
if (!existsSync(REGISTRY_FILE)) {
|
|
10
|
-
return { servers: [] };
|
|
11
|
-
}
|
|
12
|
-
try {
|
|
13
|
-
return JSON.parse(readFileSync(REGISTRY_FILE, 'utf-8'));
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return { servers: [] };
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function isProcessRunning(pid) {
|
|
20
|
-
try {
|
|
21
|
-
process.kill(pid, 0);
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function formatUptime(ms) {
|
|
29
|
-
const seconds = Math.floor(ms / 1000);
|
|
30
|
-
const minutes = Math.floor(seconds / 60);
|
|
31
|
-
const hours = Math.floor(minutes / 60);
|
|
32
|
-
const days = Math.floor(hours / 24);
|
|
33
|
-
if (days > 0)
|
|
34
|
-
return `${days}d ${hours % 24}h`;
|
|
35
|
-
if (hours > 0)
|
|
36
|
-
return `${hours}h ${minutes % 60}m`;
|
|
37
|
-
if (minutes > 0)
|
|
38
|
-
return `${minutes}m`;
|
|
39
|
-
return `${seconds}s`;
|
|
40
|
-
}
|
|
41
|
-
function getStatusIcon(isRunning, deployment) {
|
|
42
|
-
if (deployment === 'remote') {
|
|
43
|
-
return 'šµ'; // Remote servers are always "connected"
|
|
44
|
-
}
|
|
45
|
-
return isRunning ? 'š¢' : 'š“';
|
|
46
|
-
}
|
|
47
|
-
function getDeploymentIcon(deployment) {
|
|
48
|
-
return deployment === 'local' ? 'š ' : 'āļø';
|
|
49
|
-
}
|
|
50
|
-
export async function mcpListCommand(options) {
|
|
51
|
-
const registry = loadRegistry();
|
|
52
|
-
if (registry.servers.length === 0) {
|
|
53
|
-
console.log(chalk.gray('No MCP servers registered'));
|
|
54
|
-
console.log(chalk.gray('\nStart servers with: inkeep mcp start <graph-file>'));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
// Group servers by graph
|
|
58
|
-
const serversByGraph = new Map();
|
|
59
|
-
for (const server of registry.servers) {
|
|
60
|
-
const servers = serversByGraph.get(server.graphId) || [];
|
|
61
|
-
servers.push(server);
|
|
62
|
-
serversByGraph.set(server.graphId, servers);
|
|
63
|
-
}
|
|
64
|
-
if (options?.format === 'tree' || !options?.format) {
|
|
65
|
-
// Tree format (default)
|
|
66
|
-
console.log(chalk.bold('\nš” MCP Servers Overview'));
|
|
67
|
-
console.log(chalk.gray('ā'.repeat(70)) + '\n');
|
|
68
|
-
let totalRunning = 0;
|
|
69
|
-
let totalStopped = 0;
|
|
70
|
-
let totalRemote = 0;
|
|
71
|
-
for (const [graphId, servers] of serversByGraph) {
|
|
72
|
-
console.log(chalk.bold.cyan(`Graph: ${graphId}`));
|
|
73
|
-
for (const server of servers) {
|
|
74
|
-
const isRunning = server.deployment === 'local' ? isProcessRunning(server.pid) : true;
|
|
75
|
-
const uptime = isRunning
|
|
76
|
-
? formatUptime(new Date().getTime() - new Date(server.startedAt).getTime())
|
|
77
|
-
: 'stopped';
|
|
78
|
-
if (server.deployment === 'remote') {
|
|
79
|
-
totalRemote++;
|
|
80
|
-
}
|
|
81
|
-
else if (isRunning) {
|
|
82
|
-
totalRunning++;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
totalStopped++;
|
|
86
|
-
}
|
|
87
|
-
const statusIcon = getStatusIcon(isRunning, server.deployment);
|
|
88
|
-
const deploymentIcon = getDeploymentIcon(server.deployment);
|
|
89
|
-
let line = `āā ${statusIcon} ${chalk.white(server.name.padEnd(16))} | ${deploymentIcon} ${server.deployment.padEnd(6)} | `;
|
|
90
|
-
if (server.deployment === 'local') {
|
|
91
|
-
line += chalk.cyan(`:${server.port}`.padEnd(6));
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
line += chalk.blue(server.serverUrl?.substring(0, 25) + '...');
|
|
95
|
-
}
|
|
96
|
-
line += ` | ${server.transport?.toUpperCase().padEnd(4) || 'IPC '.padEnd(4)}`;
|
|
97
|
-
line += ` | ${isRunning ? chalk.green(uptime.padEnd(8)) : chalk.red('Stopped '.padEnd(8))}`;
|
|
98
|
-
console.log(line);
|
|
99
|
-
if (options?.verbose && server.description) {
|
|
100
|
-
console.log(chalk.gray(`ā āā ${server.description}`));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
console.log();
|
|
104
|
-
}
|
|
105
|
-
// Summary
|
|
106
|
-
console.log(chalk.gray('Summary: ') +
|
|
107
|
-
chalk.green(`${totalRunning} running`) +
|
|
108
|
-
', ' +
|
|
109
|
-
chalk.red(`${totalStopped} stopped`) +
|
|
110
|
-
', ' +
|
|
111
|
-
chalk.blue(`${totalRemote} remote`));
|
|
112
|
-
}
|
|
113
|
-
else if (options.format === 'table') {
|
|
114
|
-
// Table format
|
|
115
|
-
const table = new Table({
|
|
116
|
-
head: [
|
|
117
|
-
chalk.cyan('Status'),
|
|
118
|
-
chalk.cyan('Name'),
|
|
119
|
-
chalk.cyan('Graph'),
|
|
120
|
-
chalk.cyan('Type'),
|
|
121
|
-
chalk.cyan('Location'),
|
|
122
|
-
chalk.cyan('Transport'),
|
|
123
|
-
chalk.cyan('Uptime'),
|
|
124
|
-
],
|
|
125
|
-
style: {
|
|
126
|
-
head: [],
|
|
127
|
-
border: ['gray'],
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
for (const server of registry.servers) {
|
|
131
|
-
const isRunning = server.deployment === 'local' ? isProcessRunning(server.pid) : true;
|
|
132
|
-
const uptime = isRunning
|
|
133
|
-
? formatUptime(new Date().getTime() - new Date(server.startedAt).getTime())
|
|
134
|
-
: 'N/A';
|
|
135
|
-
const statusIcon = getStatusIcon(isRunning, server.deployment);
|
|
136
|
-
const location = server.deployment === 'local'
|
|
137
|
-
? chalk.cyan(`:${server.port}`)
|
|
138
|
-
: chalk.blue(server.serverUrl?.substring(0, 30) || 'N/A');
|
|
139
|
-
table.push([
|
|
140
|
-
statusIcon,
|
|
141
|
-
server.name,
|
|
142
|
-
server.graphId,
|
|
143
|
-
server.deployment,
|
|
144
|
-
location,
|
|
145
|
-
server.transport?.toUpperCase() || 'IPC',
|
|
146
|
-
isRunning ? chalk.green(uptime) : chalk.red('Stopped'),
|
|
147
|
-
]);
|
|
148
|
-
}
|
|
149
|
-
console.log(chalk.bold('\nš” MCP Servers\n'));
|
|
150
|
-
console.log(table.toString());
|
|
151
|
-
}
|
|
152
|
-
console.log(chalk.gray('\nCommands:'));
|
|
153
|
-
console.log(chalk.gray(' ⢠Start servers: inkeep mcp start <graph-file>'));
|
|
154
|
-
console.log(chalk.gray(' ⢠Stop servers: inkeep mcp stop'));
|
|
155
|
-
console.log(chalk.gray(' ⢠Server status: inkeep mcp status'));
|
|
156
|
-
}
|