@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.
- package/LICENSE.md +51 -0
- package/README.md +512 -0
- package/dist/__tests__/api.test.d.ts +1 -0
- package/dist/__tests__/api.test.js +257 -0
- package/dist/__tests__/cli.test.d.ts +1 -0
- package/dist/__tests__/cli.test.js +153 -0
- package/dist/__tests__/commands/config.test.d.ts +1 -0
- package/dist/__tests__/commands/config.test.js +154 -0
- package/dist/__tests__/commands/init.test.d.ts +1 -0
- package/dist/__tests__/commands/init.test.js +186 -0
- package/dist/__tests__/commands/pull-retry.test.d.ts +1 -0
- package/dist/__tests__/commands/pull-retry.test.js +156 -0
- package/dist/__tests__/commands/pull.test.d.ts +1 -0
- package/dist/__tests__/commands/pull.test.js +54 -0
- package/dist/__tests__/commands/push-spinner.test.d.ts +1 -0
- package/dist/__tests__/commands/push-spinner.test.js +127 -0
- package/dist/__tests__/commands/push.test.d.ts +1 -0
- package/dist/__tests__/commands/push.test.js +265 -0
- package/dist/__tests__/config-validation.test.d.ts +1 -0
- package/dist/__tests__/config-validation.test.js +106 -0
- package/dist/__tests__/package.test.d.ts +1 -0
- package/dist/__tests__/package.test.js +82 -0
- package/dist/__tests__/utils/json-comparator.test.d.ts +1 -0
- package/dist/__tests__/utils/json-comparator.test.js +174 -0
- package/dist/__tests__/utils/port-manager.test.d.ts +1 -0
- package/dist/__tests__/utils/port-manager.test.js +144 -0
- package/dist/__tests__/utils/ts-loader.test.d.ts +1 -0
- package/dist/__tests__/utils/ts-loader.test.js +233 -0
- package/dist/api.d.ts +23 -0
- package/dist/api.js +140 -0
- package/dist/commands/chat-enhanced.d.ts +7 -0
- package/dist/commands/chat-enhanced.js +396 -0
- package/dist/commands/chat.d.ts +5 -0
- package/dist/commands/chat.js +125 -0
- package/dist/commands/config.d.ts +6 -0
- package/dist/commands/config.js +128 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +171 -0
- package/dist/commands/list-graphs.d.ts +6 -0
- package/dist/commands/list-graphs.js +131 -0
- package/dist/commands/mcp-list.d.ts +4 -0
- package/dist/commands/mcp-list.js +156 -0
- package/dist/commands/mcp-start-simple.d.ts +5 -0
- package/dist/commands/mcp-start-simple.js +193 -0
- package/dist/commands/mcp-start.d.ts +5 -0
- package/dist/commands/mcp-start.js +217 -0
- package/dist/commands/mcp-status.d.ts +1 -0
- package/dist/commands/mcp-status.js +96 -0
- package/dist/commands/mcp-stop.d.ts +5 -0
- package/dist/commands/mcp-stop.js +160 -0
- package/dist/commands/pull.d.ts +15 -0
- package/dist/commands/pull.js +313 -0
- package/dist/commands/pull.llm-generate.d.ts +10 -0
- package/dist/commands/pull.llm-generate.js +184 -0
- package/dist/commands/push.d.ts +6 -0
- package/dist/commands/push.js +268 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.js +292 -0
- package/dist/exports.d.ts +2 -0
- package/dist/exports.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +98 -0
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.js +3 -0
- package/dist/types/graph.d.ts +10 -0
- package/dist/types/graph.js +1 -0
- package/dist/utils/json-comparator.d.ts +60 -0
- package/dist/utils/json-comparator.js +222 -0
- package/dist/utils/mcp-runner.d.ts +6 -0
- package/dist/utils/mcp-runner.js +147 -0
- package/dist/utils/port-manager.d.ts +43 -0
- package/dist/utils/port-manager.js +92 -0
- package/dist/utils/ts-loader.d.ts +5 -0
- package/dist/utils/ts-loader.js +146 -0
- package/package.json +77 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
/**
|
|
3
|
+
* Manages dynamic port allocation for local MCP servers
|
|
4
|
+
*/
|
|
5
|
+
export class PortManager {
|
|
6
|
+
static instance;
|
|
7
|
+
static BASE_PORT = 3100;
|
|
8
|
+
static MAX_PORT = 3200;
|
|
9
|
+
allocatedPorts = new Set();
|
|
10
|
+
constructor() { }
|
|
11
|
+
static getInstance() {
|
|
12
|
+
if (!PortManager.instance) {
|
|
13
|
+
PortManager.instance = new PortManager();
|
|
14
|
+
}
|
|
15
|
+
return PortManager.instance;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Allocate an available port in the configured range
|
|
19
|
+
*/
|
|
20
|
+
async allocatePort(preferredPort) {
|
|
21
|
+
// If a preferred port is provided and available, use it
|
|
22
|
+
if (preferredPort) {
|
|
23
|
+
const isAvailable = await this.isPortAvailable(preferredPort);
|
|
24
|
+
if (isAvailable && !this.allocatedPorts.has(preferredPort)) {
|
|
25
|
+
this.allocatedPorts.add(preferredPort);
|
|
26
|
+
return preferredPort;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Find next available port in range
|
|
30
|
+
for (let port = PortManager.BASE_PORT; port <= PortManager.MAX_PORT; port++) {
|
|
31
|
+
if (this.allocatedPorts.has(port)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const isAvailable = await this.isPortAvailable(port);
|
|
35
|
+
if (isAvailable) {
|
|
36
|
+
this.allocatedPorts.add(port);
|
|
37
|
+
return port;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`No available ports in range ${PortManager.BASE_PORT}-${PortManager.MAX_PORT}`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Release a previously allocated port
|
|
44
|
+
*/
|
|
45
|
+
releasePort(port) {
|
|
46
|
+
this.allocatedPorts.delete(port);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Release all allocated ports
|
|
50
|
+
*/
|
|
51
|
+
releaseAll() {
|
|
52
|
+
this.allocatedPorts.clear();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if a specific port is available
|
|
56
|
+
*/
|
|
57
|
+
async isPortAvailable(port) {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const server = createServer();
|
|
60
|
+
server.once('error', () => {
|
|
61
|
+
resolve(false);
|
|
62
|
+
});
|
|
63
|
+
server.once('listening', () => {
|
|
64
|
+
server.close(() => {
|
|
65
|
+
resolve(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
server.listen(port, '127.0.0.1');
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get list of currently allocated ports
|
|
73
|
+
*/
|
|
74
|
+
getAllocatedPorts() {
|
|
75
|
+
return Array.from(this.allocatedPorts).sort((a, b) => a - b);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get port allocation statistics
|
|
79
|
+
*/
|
|
80
|
+
getStats() {
|
|
81
|
+
const totalPorts = PortManager.MAX_PORT - PortManager.BASE_PORT + 1;
|
|
82
|
+
return {
|
|
83
|
+
allocated: this.allocatedPorts.size,
|
|
84
|
+
available: totalPorts - this.allocatedPorts.size,
|
|
85
|
+
range: {
|
|
86
|
+
min: PortManager.BASE_PORT,
|
|
87
|
+
max: PortManager.MAX_PORT,
|
|
88
|
+
},
|
|
89
|
+
ports: this.getAllocatedPorts(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
/**
|
|
7
|
+
* Loads a TypeScript module and returns its exports
|
|
8
|
+
* This works by creating a temporary loader script that tsx can execute
|
|
9
|
+
*/
|
|
10
|
+
export async function loadTypeScriptModule(filePath) {
|
|
11
|
+
const tempDir = join(tmpdir(), 'inkeep-cli-loader');
|
|
12
|
+
mkdirSync(tempDir, { recursive: true });
|
|
13
|
+
const loaderPath = join(tempDir, 'loader.mjs');
|
|
14
|
+
const outputPath = join(tempDir, 'output.json');
|
|
15
|
+
// Create a loader script that will import the module and serialize its exports
|
|
16
|
+
const loaderScript = `
|
|
17
|
+
import { writeFileSync } from 'fs';
|
|
18
|
+
|
|
19
|
+
async function loadModule() {
|
|
20
|
+
try {
|
|
21
|
+
const module = await import('${pathToFileURL(filePath).href}');
|
|
22
|
+
|
|
23
|
+
// Serialize the module exports (functions can't be serialized, so we just mark them)
|
|
24
|
+
const serializable = {};
|
|
25
|
+
|
|
26
|
+
for (const [key, value] of Object.entries(module)) {
|
|
27
|
+
if (typeof value === 'function') {
|
|
28
|
+
// Mark functions
|
|
29
|
+
serializable[key] = { __type: 'function', name: value.name || key };
|
|
30
|
+
} else if (value && typeof value === 'object') {
|
|
31
|
+
// For objects, check if they have specific methods/properties
|
|
32
|
+
const obj = { __type: 'object' };
|
|
33
|
+
|
|
34
|
+
// Check for config-like properties (for inkeep.config.ts files)
|
|
35
|
+
if (value.tenantId) obj.tenantId = value.tenantId;
|
|
36
|
+
if (value.projectId) obj.projectId = value.projectId;
|
|
37
|
+
if (value.apiUrl) obj.apiUrl = value.apiUrl;
|
|
38
|
+
if (value.outputDirectory) obj.outputDirectory = value.outputDirectory;
|
|
39
|
+
if (value.modelSettings) obj.modelSettings = value.modelSettings;
|
|
40
|
+
|
|
41
|
+
// Check for tool-like properties
|
|
42
|
+
if (value.id) obj.id = value.id;
|
|
43
|
+
if (value.name) obj.name = value.name;
|
|
44
|
+
if (value.description) obj.description = value.description;
|
|
45
|
+
if (value.serverUrl) obj.serverUrl = value.serverUrl;
|
|
46
|
+
if (value.port) obj.port = value.port;
|
|
47
|
+
if (value.deployment) obj.deployment = value.deployment;
|
|
48
|
+
if (value.transport) obj.transport = value.transport;
|
|
49
|
+
if (typeof value.execute === 'function') obj.hasExecute = true;
|
|
50
|
+
if (typeof value.init === 'function') obj.hasInit = true;
|
|
51
|
+
if (typeof value.getServerUrl === 'function') obj.hasGetServerUrl = true;
|
|
52
|
+
if (typeof value.getId === 'function') {
|
|
53
|
+
try {
|
|
54
|
+
obj.graphId = value.getId();
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if it's an array
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
obj.__type = 'array';
|
|
61
|
+
obj.items = value.map(item => {
|
|
62
|
+
if (item && typeof item === 'object') {
|
|
63
|
+
const itemObj = {};
|
|
64
|
+
if (item.id) itemObj.id = item.id;
|
|
65
|
+
if (item.name) itemObj.name = item.name;
|
|
66
|
+
if (item.description) itemObj.description = item.description;
|
|
67
|
+
if (item.serverUrl) itemObj.serverUrl = item.serverUrl;
|
|
68
|
+
if (item.port) itemObj.port = item.port;
|
|
69
|
+
if (item.deployment) itemObj.deployment = item.deployment;
|
|
70
|
+
if (item.transport) itemObj.transport = item.transport;
|
|
71
|
+
if (typeof item.execute === 'function') itemObj.hasExecute = true;
|
|
72
|
+
if (typeof item.init === 'function') itemObj.hasInit = true;
|
|
73
|
+
if (typeof item.getServerUrl === 'function') itemObj.hasGetServerUrl = true;
|
|
74
|
+
return itemObj;
|
|
75
|
+
}
|
|
76
|
+
return item;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
serializable[key] = obj;
|
|
81
|
+
} else {
|
|
82
|
+
serializable[key] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
writeFileSync('${outputPath}', JSON.stringify(serializable, null, 2));
|
|
87
|
+
process.exit(0);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Failed to load module:', error.message);
|
|
90
|
+
writeFileSync('${outputPath}', JSON.stringify({ __error: error.message }));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
loadModule();
|
|
96
|
+
`;
|
|
97
|
+
try {
|
|
98
|
+
// Write the loader script
|
|
99
|
+
writeFileSync(loaderPath, loaderScript);
|
|
100
|
+
// Execute the loader script with tsx, passing environment variables
|
|
101
|
+
// Load the .env file from the graph directory if it exists
|
|
102
|
+
const graphDir = dirname(filePath);
|
|
103
|
+
const envPath = join(graphDir, '..', '.env');
|
|
104
|
+
const envVars = { ...process.env };
|
|
105
|
+
// If no environment variables are set, use test defaults
|
|
106
|
+
if (!process.env.ENVIRONMENT) {
|
|
107
|
+
envVars.ENVIRONMENT = 'test';
|
|
108
|
+
envVars.DB_FILE_NAME = ':memory:'; // Use in-memory DB for CLI testing
|
|
109
|
+
envVars.INKEEP_TENANT_ID = 'test-tenant';
|
|
110
|
+
envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || 'test-key';
|
|
111
|
+
}
|
|
112
|
+
// Use node directly with tsx for better performance in CI
|
|
113
|
+
// Try to find tsx in node_modules first for better performance
|
|
114
|
+
const tsxPath = join(process.cwd(), 'node_modules', '.bin', 'tsx');
|
|
115
|
+
const command = require('fs').existsSync(tsxPath) ? tsxPath : 'tsx';
|
|
116
|
+
execFileSync(command, [loaderPath], {
|
|
117
|
+
cwd: process.cwd(), // Use cwd which is in the pnpm workspace
|
|
118
|
+
stdio: 'pipe',
|
|
119
|
+
encoding: 'utf-8',
|
|
120
|
+
env: envVars,
|
|
121
|
+
timeout: 15000, // Reduced to 15 second timeout for faster failure detection
|
|
122
|
+
windowsHide: true, // Hide console window on Windows
|
|
123
|
+
});
|
|
124
|
+
// Read the output
|
|
125
|
+
const outputJson = readFileSync(outputPath, 'utf-8');
|
|
126
|
+
const output = JSON.parse(outputJson);
|
|
127
|
+
// Clean up with retries to handle file locks
|
|
128
|
+
rmSync(tempDir, { recursive: true, force: true, maxRetries: 3 });
|
|
129
|
+
if (output.__error) {
|
|
130
|
+
throw new Error(output.__error);
|
|
131
|
+
}
|
|
132
|
+
return output;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
// Clean up on error
|
|
136
|
+
try {
|
|
137
|
+
rmSync(tempDir, { recursive: true, force: true, maxRetries: 3 });
|
|
138
|
+
}
|
|
139
|
+
catch { }
|
|
140
|
+
// Add more context to timeout errors
|
|
141
|
+
if (error.code === 'ETIMEDOUT' || error.message?.includes('timed out')) {
|
|
142
|
+
throw new Error(`TypeScript module loading timed out for ${filePath}: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@inkeep/agents-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Inkeep CLI tool",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/exports.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"inkeep": "./dist/index.js"
|
|
9
|
+
},
|
|
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"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"cli",
|
|
27
|
+
"inkeep",
|
|
28
|
+
"agent"
|
|
29
|
+
],
|
|
30
|
+
"author": "Inkeep <support@inkeep.com>",
|
|
31
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@ai-sdk/anthropic": "2.0.2",
|
|
34
|
+
"@ai-sdk/openai": "2.0.11",
|
|
35
|
+
"@babel/parser": "^7.23.0",
|
|
36
|
+
"@babel/types": "^7.23.0",
|
|
37
|
+
"@inkeep/agents-core": "workspace:*",
|
|
38
|
+
"@libsql/client": "^0.15.7",
|
|
39
|
+
"ai": "5.0.11",
|
|
40
|
+
"ast-types": "^0.14.2",
|
|
41
|
+
"chalk": "^5.3.0",
|
|
42
|
+
"cli-table3": "^0.6.3",
|
|
43
|
+
"commander": "^14.0.0",
|
|
44
|
+
"dotenv": "^17.2.1",
|
|
45
|
+
"drizzle-orm": "^0.37.0",
|
|
46
|
+
"inquirer": "^9.2.12",
|
|
47
|
+
"inquirer-autocomplete-prompt": "^3.0.1",
|
|
48
|
+
"ora": "^8.0.1",
|
|
49
|
+
"recast": "^0.23.0",
|
|
50
|
+
"ts-morph": "^26.0.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/inquirer": "^9.0.7",
|
|
54
|
+
"@types/node": "^20.10.0",
|
|
55
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
56
|
+
"tsx": "^4.20.5",
|
|
57
|
+
"typescript": "^5.9.2",
|
|
58
|
+
"vitest": "^3.2.4"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=20.x"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "restricted",
|
|
65
|
+
"registry": "https://registry.npmjs.org/"
|
|
66
|
+
},
|
|
67
|
+
"files": [
|
|
68
|
+
"dist",
|
|
69
|
+
"README.md",
|
|
70
|
+
"LICENSE.md"
|
|
71
|
+
],
|
|
72
|
+
"repository": {
|
|
73
|
+
"type": "git",
|
|
74
|
+
"url": "git+https://github.com/inkeep/agent-framework.git",
|
|
75
|
+
"directory": "agents-cli"
|
|
76
|
+
}
|
|
77
|
+
}
|