@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.
@@ -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.0",
3
+ "version": "0.1.2",
4
4
  "description": "Inkeep CLI tool",
5
5
  "type": "module",
6
- "main": "./dist/exports.js",
6
+ "main": "./dist/index.js",
7
7
  "bin": {
8
8
  "inkeep": "./dist/index.js"
9
9
  },
10
10
  "exports": {
11
- ".": "./dist/exports.js",
12
- "./config": "./dist/exports.js"
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.37.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
- "tsx": "^4.20.5",
47
+ "tsup": "^8.5.0",
57
48
  "typescript": "^5.9.2",
58
49
  "vitest": "^3.2.4"
59
50
  },
60
51
  "engines": {
61
- "node": ">=20.x"
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/agent-framework.git",
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,4 +0,0 @@
1
- export declare function mcpListCommand(options?: {
2
- verbose?: boolean;
3
- format?: 'table' | 'tree';
4
- }): Promise<void>;
@@ -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
- }
@@ -1,5 +0,0 @@
1
- export interface McpStartOptions {
2
- detached?: boolean;
3
- verbose?: boolean;
4
- }
5
- export declare function mcpStartCommand(graphPath: string, options: McpStartOptions): Promise<void>;