@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { configGetCommand, configListCommand, configSetCommand } from './commands/config.js';
|
|
7
|
+
import { initCommand } from './commands/init.js';
|
|
8
|
+
import { listGraphsCommand } from './commands/list-graphs.js';
|
|
9
|
+
import { pullCommand } from './commands/pull.js';
|
|
10
|
+
import { pushCommand } from './commands/push.js';
|
|
11
|
+
// Get the current directory for ESM
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
// Read package.json to get version
|
|
15
|
+
const packageJsonPath = join(__dirname, '..', 'package.json');
|
|
16
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
17
|
+
const program = new Command();
|
|
18
|
+
program
|
|
19
|
+
.name('inkeep')
|
|
20
|
+
.description('CLI tool for Inkeep Agent Framework')
|
|
21
|
+
.version(packageJson.version);
|
|
22
|
+
// Init command
|
|
23
|
+
program
|
|
24
|
+
.command('init [path]')
|
|
25
|
+
.description('Initialize a new Inkeep configuration file')
|
|
26
|
+
.option('--no-interactive', 'Skip interactive path selection')
|
|
27
|
+
.action(async (path, options) => {
|
|
28
|
+
await initCommand({ path, ...options });
|
|
29
|
+
});
|
|
30
|
+
// Config command with subcommands
|
|
31
|
+
const configCommand = program.command('config').description('Manage Inkeep configuration');
|
|
32
|
+
configCommand
|
|
33
|
+
.command('get [key]')
|
|
34
|
+
.description('Get configuration value(s)')
|
|
35
|
+
.option('--config-file-path <path>', 'Path to configuration file')
|
|
36
|
+
.action(async (key, options) => {
|
|
37
|
+
await configGetCommand(key, options);
|
|
38
|
+
});
|
|
39
|
+
configCommand
|
|
40
|
+
.command('set <key> <value>')
|
|
41
|
+
.description('Set a configuration value')
|
|
42
|
+
.option('--config-file-path <path>', 'Path to configuration file')
|
|
43
|
+
.action(async (key, value, options) => {
|
|
44
|
+
await configSetCommand(key, value, options);
|
|
45
|
+
});
|
|
46
|
+
configCommand
|
|
47
|
+
.command('list')
|
|
48
|
+
.description('List all configuration values')
|
|
49
|
+
.option('--config-file-path <path>', 'Path to configuration file')
|
|
50
|
+
.action(async (options) => {
|
|
51
|
+
await configListCommand(options);
|
|
52
|
+
});
|
|
53
|
+
// Push command
|
|
54
|
+
program
|
|
55
|
+
.command('push <graph-path>')
|
|
56
|
+
.description('Push a graph configuration to the backend')
|
|
57
|
+
.option('--tenant-id <tenant-id>', 'Tenant ID (use with --api-url)')
|
|
58
|
+
.option('--api-url <api-url>', 'API URL (use with --tenant-id or alone to override config)')
|
|
59
|
+
.option('--config-file-path <path>', 'Path to configuration file (alternative to --tenant-id/--api-url)')
|
|
60
|
+
.action(async (graphPath, options) => {
|
|
61
|
+
await pushCommand(graphPath, options);
|
|
62
|
+
});
|
|
63
|
+
// Pull command
|
|
64
|
+
program
|
|
65
|
+
.command('pull <graph-id>')
|
|
66
|
+
.description('Pull a graph configuration from the backend and generate TypeScript file')
|
|
67
|
+
.option('--tenant-id <tenant-id>', 'Tenant ID (use with --api-url)')
|
|
68
|
+
.option('--api-url <api-url>', 'API URL (use with --tenant-id or alone to override config)')
|
|
69
|
+
.option('--config-file-path <path>', 'Path to configuration file (alternative to --tenant-id/--api-url)')
|
|
70
|
+
.option('--output-path <path>', 'Output directory for the generated file (overrides config)')
|
|
71
|
+
.option('--json', 'Output as JSON file instead of TypeScript')
|
|
72
|
+
.action(async (graphId, options) => {
|
|
73
|
+
await pullCommand(graphId, options);
|
|
74
|
+
});
|
|
75
|
+
// Chat command
|
|
76
|
+
program
|
|
77
|
+
.command('chat [graph-id]')
|
|
78
|
+
.description('Start an interactive chat session with a graph (interactive selection if no ID provided)')
|
|
79
|
+
.option('--tenant-id <tenant-id>', 'Tenant ID (use with --api-url)')
|
|
80
|
+
.option('--api-url <api-url>', 'API URL (use with --tenant-id or alone to override config)')
|
|
81
|
+
.option('--config-file-path <path>', 'Path to configuration file (alternative to --tenant-id/--api-url)')
|
|
82
|
+
.action(async (graphId, options) => {
|
|
83
|
+
// Import the enhanced version with autocomplete
|
|
84
|
+
const { chatCommandEnhanced } = await import('./commands/chat-enhanced.js');
|
|
85
|
+
await chatCommandEnhanced(graphId, options);
|
|
86
|
+
});
|
|
87
|
+
// List graphs command
|
|
88
|
+
program
|
|
89
|
+
.command('list-graphs')
|
|
90
|
+
.description('List all available graphs for the current tenant')
|
|
91
|
+
.option('--tenant-id <tenant-id>', 'Tenant ID (use with --api-url)')
|
|
92
|
+
.option('--api-url <api-url>', 'API URL (use with --tenant-id or alone to override config)')
|
|
93
|
+
.option('--config-file-path <path>', 'Path to configuration file (alternative to --tenant-id/--api-url)')
|
|
94
|
+
.action(async (options) => {
|
|
95
|
+
await listGraphsCommand(options);
|
|
96
|
+
});
|
|
97
|
+
// Parse command line arguments
|
|
98
|
+
program.parse();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ModelSettings } from '@inkeep/agents-core';
|
|
2
|
+
export interface InkeepConfig {
|
|
3
|
+
tenantId: string;
|
|
4
|
+
projectId: string;
|
|
5
|
+
apiUrl?: string;
|
|
6
|
+
outputDirectory?: string;
|
|
7
|
+
modelSettings?: ModelSettings;
|
|
8
|
+
}
|
|
9
|
+
export declare function defineConfig(config: InkeepConfig): InkeepConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for JSON comparison
|
|
3
|
+
*/
|
|
4
|
+
export interface ComparisonOptions {
|
|
5
|
+
/** Whether to ignore order in arrays (default: true) */
|
|
6
|
+
ignoreArrayOrder?: boolean;
|
|
7
|
+
/** Whether to ignore case in string comparisons (default: false) */
|
|
8
|
+
ignoreCase?: boolean;
|
|
9
|
+
/** Whether to ignore whitespace differences (default: false) */
|
|
10
|
+
ignoreWhitespace?: boolean;
|
|
11
|
+
/** Custom key paths to ignore during comparison */
|
|
12
|
+
ignorePaths?: string[];
|
|
13
|
+
/** Whether to show detailed differences (default: false) */
|
|
14
|
+
showDetails?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Result of JSON comparison
|
|
18
|
+
*/
|
|
19
|
+
export interface ComparisonResult {
|
|
20
|
+
/** Whether the objects are equivalent */
|
|
21
|
+
isEqual: boolean;
|
|
22
|
+
/** List of differences found */
|
|
23
|
+
differences: Difference[];
|
|
24
|
+
/** Summary statistics */
|
|
25
|
+
stats: {
|
|
26
|
+
totalKeys: number;
|
|
27
|
+
differentKeys: number;
|
|
28
|
+
missingKeys: number;
|
|
29
|
+
extraKeys: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Represents a difference between two objects
|
|
34
|
+
*/
|
|
35
|
+
export interface Difference {
|
|
36
|
+
/** Path to the differing value */
|
|
37
|
+
path: string;
|
|
38
|
+
/** Type of difference */
|
|
39
|
+
type: 'different' | 'missing' | 'extra' | 'type_mismatch';
|
|
40
|
+
/** Value in the first object */
|
|
41
|
+
value1?: any;
|
|
42
|
+
/** Value in the second object */
|
|
43
|
+
value2?: any;
|
|
44
|
+
/** Description of the difference */
|
|
45
|
+
description: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Compare two JSON objects for structural equivalence
|
|
49
|
+
* Handles arrays with different ordering and nested objects
|
|
50
|
+
*/
|
|
51
|
+
export declare function compareJsonObjects(obj1: any, obj2: any, options?: ComparisonOptions): ComparisonResult;
|
|
52
|
+
/**
|
|
53
|
+
* Create a normalized version of a JSON object for comparison
|
|
54
|
+
* This can be useful for creating a canonical representation
|
|
55
|
+
*/
|
|
56
|
+
export declare function normalizeJsonObject(obj: any, options?: ComparisonOptions): any;
|
|
57
|
+
/**
|
|
58
|
+
* Get a summary of differences in a human-readable format
|
|
59
|
+
*/
|
|
60
|
+
export declare function getDifferenceSummary(result: ComparisonResult): string;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare two JSON objects for structural equivalence
|
|
3
|
+
* Handles arrays with different ordering and nested objects
|
|
4
|
+
*/
|
|
5
|
+
export function compareJsonObjects(obj1, obj2, options = {}) {
|
|
6
|
+
const { ignoreArrayOrder = true, ignoreCase = false, ignoreWhitespace = false, ignorePaths = [], } = options;
|
|
7
|
+
const differences = [];
|
|
8
|
+
const stats = {
|
|
9
|
+
totalKeys: 0,
|
|
10
|
+
differentKeys: 0,
|
|
11
|
+
missingKeys: 0,
|
|
12
|
+
extraKeys: 0,
|
|
13
|
+
};
|
|
14
|
+
function normalizeValue(value) {
|
|
15
|
+
if (typeof value === 'string') {
|
|
16
|
+
let normalized = value;
|
|
17
|
+
if (ignoreCase) {
|
|
18
|
+
normalized = normalized.toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
if (ignoreWhitespace) {
|
|
21
|
+
normalized = normalized.trim().replace(/\s+/g, ' ');
|
|
22
|
+
}
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function shouldIgnorePath(path) {
|
|
28
|
+
return ignorePaths.some((ignorePath) => {
|
|
29
|
+
if (ignorePath.endsWith('*')) {
|
|
30
|
+
return path.startsWith(ignorePath.slice(0, -1));
|
|
31
|
+
}
|
|
32
|
+
return path === ignorePath;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function compareValues(value1, value2, path = '') {
|
|
36
|
+
if (shouldIgnorePath(path)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
// Handle null/undefined
|
|
40
|
+
if (value1 === null || value1 === undefined) {
|
|
41
|
+
if (value2 === null || value2 === undefined) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
differences.push({
|
|
45
|
+
path,
|
|
46
|
+
type: 'different',
|
|
47
|
+
value1,
|
|
48
|
+
value2,
|
|
49
|
+
description: `Null/undefined mismatch: ${value1} vs ${value2}`,
|
|
50
|
+
});
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (value2 === null || value2 === undefined) {
|
|
54
|
+
differences.push({
|
|
55
|
+
path,
|
|
56
|
+
type: 'different',
|
|
57
|
+
value1,
|
|
58
|
+
value2,
|
|
59
|
+
description: `Null/undefined mismatch: ${value1} vs ${value2}`,
|
|
60
|
+
});
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
// Handle different types
|
|
64
|
+
if (typeof value1 !== typeof value2) {
|
|
65
|
+
differences.push({
|
|
66
|
+
path,
|
|
67
|
+
type: 'type_mismatch',
|
|
68
|
+
value1,
|
|
69
|
+
value2,
|
|
70
|
+
description: `Type mismatch: ${typeof value1} vs ${typeof value2}`,
|
|
71
|
+
});
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// Handle primitives
|
|
75
|
+
if (typeof value1 !== 'object') {
|
|
76
|
+
const normalized1 = normalizeValue(value1);
|
|
77
|
+
const normalized2 = normalizeValue(value2);
|
|
78
|
+
if (normalized1 !== normalized2) {
|
|
79
|
+
differences.push({
|
|
80
|
+
path,
|
|
81
|
+
type: 'different',
|
|
82
|
+
value1,
|
|
83
|
+
value2,
|
|
84
|
+
description: `Value mismatch: ${value1} vs ${value2}`,
|
|
85
|
+
});
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
// Handle arrays
|
|
91
|
+
if (Array.isArray(value1) && Array.isArray(value2)) {
|
|
92
|
+
if (value1.length !== value2.length) {
|
|
93
|
+
differences.push({
|
|
94
|
+
path,
|
|
95
|
+
type: 'different',
|
|
96
|
+
value1: value1.length,
|
|
97
|
+
value2: value2.length,
|
|
98
|
+
description: `Array length mismatch: ${value1.length} vs ${value2.length}`,
|
|
99
|
+
});
|
|
100
|
+
return false; // Array length mismatch is a fundamental difference
|
|
101
|
+
}
|
|
102
|
+
if (ignoreArrayOrder) {
|
|
103
|
+
// Compare arrays ignoring order
|
|
104
|
+
const sorted1 = [...value1].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
105
|
+
const sorted2 = [...value2].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
106
|
+
for (let i = 0; i < sorted1.length; i++) {
|
|
107
|
+
compareValues(sorted1[i], sorted2[i], `${path}[${i}]`); // Don't return false, just collect differences
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Compare arrays in order
|
|
112
|
+
for (let i = 0; i < value1.length; i++) {
|
|
113
|
+
compareValues(value1[i], value2[i], `${path}[${i}]`); // Don't return false, just collect differences
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
// Handle objects
|
|
119
|
+
if (typeof value1 === 'object' && typeof value2 === 'object') {
|
|
120
|
+
const keys1 = Object.keys(value1);
|
|
121
|
+
const keys2 = Object.keys(value2);
|
|
122
|
+
const allKeys = new Set([...keys1, ...keys2]);
|
|
123
|
+
stats.totalKeys += allKeys.size;
|
|
124
|
+
for (const key of allKeys) {
|
|
125
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
126
|
+
if (!keys1.includes(key)) {
|
|
127
|
+
differences.push({
|
|
128
|
+
path: currentPath,
|
|
129
|
+
type: 'missing',
|
|
130
|
+
value2: value2[key],
|
|
131
|
+
description: `Missing key in first object: ${key}`,
|
|
132
|
+
});
|
|
133
|
+
stats.missingKeys++;
|
|
134
|
+
continue; // Continue checking other keys instead of returning false
|
|
135
|
+
}
|
|
136
|
+
if (!keys2.includes(key)) {
|
|
137
|
+
differences.push({
|
|
138
|
+
path: currentPath,
|
|
139
|
+
type: 'extra',
|
|
140
|
+
value1: value1[key],
|
|
141
|
+
description: `Extra key in first object: ${key}`,
|
|
142
|
+
});
|
|
143
|
+
stats.extraKeys++;
|
|
144
|
+
continue; // Continue checking other keys instead of returning false
|
|
145
|
+
}
|
|
146
|
+
if (!compareValues(value1[key], value2[key], currentPath)) {
|
|
147
|
+
stats.differentKeys++;
|
|
148
|
+
// Don't return false here, continue checking other keys
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
compareValues(obj1, obj2);
|
|
156
|
+
return {
|
|
157
|
+
isEqual: differences.length === 0,
|
|
158
|
+
differences,
|
|
159
|
+
stats,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Create a normalized version of a JSON object for comparison
|
|
164
|
+
* This can be useful for creating a canonical representation
|
|
165
|
+
*/
|
|
166
|
+
export function normalizeJsonObject(obj, options = {}) {
|
|
167
|
+
const { ignoreArrayOrder = true, ignoreCase = false, ignoreWhitespace = false } = options;
|
|
168
|
+
function normalizeValue(value) {
|
|
169
|
+
if (typeof value === 'string') {
|
|
170
|
+
let normalized = value;
|
|
171
|
+
if (ignoreCase) {
|
|
172
|
+
normalized = normalized.toLowerCase();
|
|
173
|
+
}
|
|
174
|
+
if (ignoreWhitespace) {
|
|
175
|
+
normalized = normalized.trim().replace(/\s+/g, ' ');
|
|
176
|
+
}
|
|
177
|
+
return normalized;
|
|
178
|
+
}
|
|
179
|
+
if (Array.isArray(value)) {
|
|
180
|
+
const normalizedArray = value.map(normalizeValue);
|
|
181
|
+
if (ignoreArrayOrder) {
|
|
182
|
+
return normalizedArray.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
183
|
+
}
|
|
184
|
+
return normalizedArray;
|
|
185
|
+
}
|
|
186
|
+
if (typeof value === 'object' && value !== null) {
|
|
187
|
+
const normalizedObj = {};
|
|
188
|
+
const sortedKeys = Object.keys(value).sort();
|
|
189
|
+
for (const key of sortedKeys) {
|
|
190
|
+
normalizedObj[key] = normalizeValue(value[key]);
|
|
191
|
+
}
|
|
192
|
+
return normalizedObj;
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
return normalizeValue(obj);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get a summary of differences in a human-readable format
|
|
200
|
+
*/
|
|
201
|
+
export function getDifferenceSummary(result) {
|
|
202
|
+
if (result.isEqual) {
|
|
203
|
+
return '✅ Objects are equivalent';
|
|
204
|
+
}
|
|
205
|
+
const { differences, stats } = result;
|
|
206
|
+
const summary = [`❌ Objects differ (${differences.length} differences found)`];
|
|
207
|
+
summary.push(`📊 Statistics:`);
|
|
208
|
+
summary.push(` • Total keys: ${stats.totalKeys}`);
|
|
209
|
+
summary.push(` • Different values: ${stats.differentKeys}`);
|
|
210
|
+
summary.push(` • Missing keys: ${stats.missingKeys}`);
|
|
211
|
+
summary.push(` • Extra keys: ${stats.extraKeys}`);
|
|
212
|
+
if (differences.length > 0) {
|
|
213
|
+
summary.push(`\n🔍 Differences:`);
|
|
214
|
+
differences.slice(0, 10).forEach((diff, index) => {
|
|
215
|
+
summary.push(` ${index + 1}. ${diff.path}: ${diff.description}`);
|
|
216
|
+
});
|
|
217
|
+
if (differences.length > 10) {
|
|
218
|
+
summary.push(` ... and ${differences.length - 10} more differences`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return summary.join('\n');
|
|
222
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Runner - Loads and starts MCP servers from a graph file
|
|
4
|
+
* This is executed as a subprocess by the CLI
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import http from 'node:http';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
const MCP_DIR = join(homedir(), '.inkeep', 'mcp');
|
|
11
|
+
const REGISTRY_FILE = join(MCP_DIR, 'servers.json');
|
|
12
|
+
// Ensure MCP directory exists
|
|
13
|
+
if (!existsSync(MCP_DIR)) {
|
|
14
|
+
mkdirSync(MCP_DIR, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
async function startServers(graphPath) {
|
|
17
|
+
try {
|
|
18
|
+
// Import the graph module
|
|
19
|
+
const module = await import(graphPath);
|
|
20
|
+
// Get servers
|
|
21
|
+
const servers = module.servers || module.tools || [];
|
|
22
|
+
// Get graph ID
|
|
23
|
+
let graphId = 'unknown';
|
|
24
|
+
if (module.graph && typeof module.graph.getId === 'function') {
|
|
25
|
+
graphId = module.graph.getId();
|
|
26
|
+
}
|
|
27
|
+
const registeredServers = [];
|
|
28
|
+
let nextPort = 3100;
|
|
29
|
+
// Start each server
|
|
30
|
+
for (const server of servers) {
|
|
31
|
+
if (!server)
|
|
32
|
+
continue;
|
|
33
|
+
// Get server metadata
|
|
34
|
+
const name = server.name || 'unnamed';
|
|
35
|
+
const id = server.id || name;
|
|
36
|
+
const description = server.description || '';
|
|
37
|
+
// Check deployment type
|
|
38
|
+
const isLocal = typeof server.execute === 'function' ||
|
|
39
|
+
typeof server.init === 'function' ||
|
|
40
|
+
!server.serverUrl;
|
|
41
|
+
if (isLocal) {
|
|
42
|
+
// Start local server
|
|
43
|
+
const port = server.port || nextPort++;
|
|
44
|
+
// Initialize if needed
|
|
45
|
+
if (typeof server.init === 'function') {
|
|
46
|
+
await server.init();
|
|
47
|
+
}
|
|
48
|
+
// Create HTTP server for local MCP
|
|
49
|
+
if (typeof server.execute === 'function') {
|
|
50
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
51
|
+
if (req.method === 'POST' && req.url === '/mcp') {
|
|
52
|
+
let body = '';
|
|
53
|
+
req.on('data', (chunk) => (body += chunk));
|
|
54
|
+
req.on('end', async () => {
|
|
55
|
+
try {
|
|
56
|
+
const params = JSON.parse(body);
|
|
57
|
+
const result = await server.execute(params);
|
|
58
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
59
|
+
res.end(JSON.stringify({ result }));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
63
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
res.writeHead(404);
|
|
69
|
+
res.end('Not Found');
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
httpServer.listen(port, () => {
|
|
73
|
+
console.log(JSON.stringify({
|
|
74
|
+
type: 'server_started',
|
|
75
|
+
name,
|
|
76
|
+
port,
|
|
77
|
+
deployment: 'local',
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
registeredServers.push({
|
|
82
|
+
pid: process.pid,
|
|
83
|
+
graphId,
|
|
84
|
+
toolId: id,
|
|
85
|
+
name,
|
|
86
|
+
port,
|
|
87
|
+
deployment: 'local',
|
|
88
|
+
transport: 'http',
|
|
89
|
+
command: graphPath,
|
|
90
|
+
startedAt: new Date().toISOString(),
|
|
91
|
+
description,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Register remote server
|
|
96
|
+
registeredServers.push({
|
|
97
|
+
pid: process.pid,
|
|
98
|
+
graphId,
|
|
99
|
+
toolId: id,
|
|
100
|
+
name,
|
|
101
|
+
serverUrl: server.serverUrl || server.getServerUrl?.(),
|
|
102
|
+
deployment: 'remote',
|
|
103
|
+
transport: server.transport || 'http',
|
|
104
|
+
command: graphPath,
|
|
105
|
+
startedAt: new Date().toISOString(),
|
|
106
|
+
description,
|
|
107
|
+
});
|
|
108
|
+
console.log(JSON.stringify({
|
|
109
|
+
type: 'server_registered',
|
|
110
|
+
name,
|
|
111
|
+
serverUrl: server.serverUrl,
|
|
112
|
+
deployment: 'remote',
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Save to registry
|
|
117
|
+
writeFileSync(REGISTRY_FILE, JSON.stringify({ servers: registeredServers }, null, 2));
|
|
118
|
+
console.log(JSON.stringify({
|
|
119
|
+
type: 'all_started',
|
|
120
|
+
count: registeredServers.length,
|
|
121
|
+
}));
|
|
122
|
+
// Keep process alive
|
|
123
|
+
process.stdin.resume();
|
|
124
|
+
// Handle shutdown
|
|
125
|
+
process.on('SIGINT', () => {
|
|
126
|
+
console.log(JSON.stringify({ type: 'shutting_down' }));
|
|
127
|
+
process.exit(0);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error(JSON.stringify({
|
|
132
|
+
type: 'error',
|
|
133
|
+
message: error.message,
|
|
134
|
+
}));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Get graph path from command line
|
|
139
|
+
const graphPath = process.argv[2];
|
|
140
|
+
if (!graphPath) {
|
|
141
|
+
console.error(JSON.stringify({
|
|
142
|
+
type: 'error',
|
|
143
|
+
message: 'Graph path is required',
|
|
144
|
+
}));
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
startServers(graphPath);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages dynamic port allocation for local MCP servers
|
|
3
|
+
*/
|
|
4
|
+
export declare class PortManager {
|
|
5
|
+
private static instance;
|
|
6
|
+
private static readonly BASE_PORT;
|
|
7
|
+
private static readonly MAX_PORT;
|
|
8
|
+
private allocatedPorts;
|
|
9
|
+
private constructor();
|
|
10
|
+
static getInstance(): PortManager;
|
|
11
|
+
/**
|
|
12
|
+
* Allocate an available port in the configured range
|
|
13
|
+
*/
|
|
14
|
+
allocatePort(preferredPort?: number): Promise<number>;
|
|
15
|
+
/**
|
|
16
|
+
* Release a previously allocated port
|
|
17
|
+
*/
|
|
18
|
+
releasePort(port: number): void;
|
|
19
|
+
/**
|
|
20
|
+
* Release all allocated ports
|
|
21
|
+
*/
|
|
22
|
+
releaseAll(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a specific port is available
|
|
25
|
+
*/
|
|
26
|
+
private isPortAvailable;
|
|
27
|
+
/**
|
|
28
|
+
* Get list of currently allocated ports
|
|
29
|
+
*/
|
|
30
|
+
getAllocatedPorts(): number[];
|
|
31
|
+
/**
|
|
32
|
+
* Get port allocation statistics
|
|
33
|
+
*/
|
|
34
|
+
getStats(): {
|
|
35
|
+
allocated: number;
|
|
36
|
+
available: number;
|
|
37
|
+
range: {
|
|
38
|
+
min: number;
|
|
39
|
+
max: number;
|
|
40
|
+
};
|
|
41
|
+
ports: number[];
|
|
42
|
+
};
|
|
43
|
+
}
|