@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,268 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname, extname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import { createDatabaseClient, getProject, createProject } from '@inkeep/agents-core';
|
|
9
|
+
import { ManagementApiClient } from '../api.js';
|
|
10
|
+
import { validateConfiguration } from '../config.js';
|
|
11
|
+
export async function pushCommand(graphPath, options) {
|
|
12
|
+
const spinner = ora('Loading graph configuration...').start();
|
|
13
|
+
try {
|
|
14
|
+
// Resolve the absolute path
|
|
15
|
+
const absolutePath = resolve(process.cwd(), graphPath);
|
|
16
|
+
// Check if file exists
|
|
17
|
+
if (!existsSync(absolutePath)) {
|
|
18
|
+
spinner.fail('Graph file not found');
|
|
19
|
+
console.error(chalk.red(`File not found: ${absolutePath}`));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
// Check if this is a TypeScript file
|
|
23
|
+
const ext = extname(absolutePath);
|
|
24
|
+
if (ext === '.ts') {
|
|
25
|
+
// For TypeScript files, we need to use tsx to run this entire command
|
|
26
|
+
// Check if we're already running under tsx
|
|
27
|
+
if (!process.env.TSX_RUNNING) {
|
|
28
|
+
// Stop spinner before spawning child process to avoid conflicts with interactive prompts
|
|
29
|
+
spinner.stop();
|
|
30
|
+
// Re-run this command with tsx
|
|
31
|
+
// Find the inkeep CLI executable
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
+
const __dirname = dirname(__filename);
|
|
34
|
+
const cliPath = resolve(__dirname, '../index.js');
|
|
35
|
+
const args = [cliPath, 'push', graphPath];
|
|
36
|
+
if (options.tenantId)
|
|
37
|
+
args.push('--tenant-id', options.tenantId);
|
|
38
|
+
if (options.managementApiUrl)
|
|
39
|
+
args.push('--management-api-url', options.managementApiUrl);
|
|
40
|
+
if (options.configFilePath)
|
|
41
|
+
args.push('--config-file-path', options.configFilePath);
|
|
42
|
+
const child = spawn('npx', ['tsx', ...args], {
|
|
43
|
+
cwd: process.cwd(),
|
|
44
|
+
stdio: 'inherit',
|
|
45
|
+
env: { ...process.env, TSX_RUNNING: '1' },
|
|
46
|
+
});
|
|
47
|
+
child.on('error', (error) => {
|
|
48
|
+
console.error(chalk.red('Failed to load TypeScript file'));
|
|
49
|
+
console.error(chalk.red('Error:'), error.message);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
|
52
|
+
child.on('exit', (code) => {
|
|
53
|
+
process.exit(code || 0);
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Now we can import the module (either JS directly, or TS via tsx)
|
|
59
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
60
|
+
const module = await import(fileUrl);
|
|
61
|
+
// Validate that exactly one graph is exported
|
|
62
|
+
const exports = Object.keys(module);
|
|
63
|
+
const graphExports = exports.filter((key) => {
|
|
64
|
+
const value = module[key];
|
|
65
|
+
// Check for AgentGraph-like objects (has required methods)
|
|
66
|
+
return (value &&
|
|
67
|
+
typeof value === 'object' &&
|
|
68
|
+
typeof value.init === 'function' &&
|
|
69
|
+
typeof value.getId === 'function' &&
|
|
70
|
+
typeof value.getName === 'function' &&
|
|
71
|
+
typeof value.getAgents === 'function');
|
|
72
|
+
});
|
|
73
|
+
if (graphExports.length === 0) {
|
|
74
|
+
spinner.fail('No AgentGraph exported from configuration file');
|
|
75
|
+
console.error(chalk.red('Configuration file must export at least one AgentGraph instance'));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
if (graphExports.length > 1) {
|
|
79
|
+
spinner.fail('Multiple AgentGraphs exported from configuration file');
|
|
80
|
+
console.error(chalk.red('Configuration file must export exactly one AgentGraph instance'));
|
|
81
|
+
console.error(chalk.yellow('Found exports:'), graphExports.join(', '));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
// Get the graph instance
|
|
85
|
+
const graphKey = graphExports[0];
|
|
86
|
+
const graph = module[graphKey];
|
|
87
|
+
spinner.text = 'Loading configuration...';
|
|
88
|
+
// Validate configuration
|
|
89
|
+
let config;
|
|
90
|
+
try {
|
|
91
|
+
config = await validateConfiguration(options.tenantId, options.managementApiUrl, undefined, // executionApiUrl not needed for push
|
|
92
|
+
options.configFilePath);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
spinner.fail('Configuration validation failed');
|
|
96
|
+
console.error(chalk.red(error.message));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
spinner.succeed('Configuration loaded');
|
|
100
|
+
// Log configuration sources for debugging
|
|
101
|
+
console.log(chalk.gray('Configuration sources:'));
|
|
102
|
+
console.log(chalk.gray(` • Tenant ID: ${config.sources.tenantId}`));
|
|
103
|
+
console.log(chalk.gray(` • Project ID: ${config.sources.projectId}`));
|
|
104
|
+
console.log(chalk.gray(` • API URL: ${config.sources.managementApiUrl}`));
|
|
105
|
+
const tenantId = config.tenantId;
|
|
106
|
+
const projectId = config.projectId;
|
|
107
|
+
const managementApiUrl = config.managementApiUrl;
|
|
108
|
+
// Check if project exists in the database
|
|
109
|
+
spinner.text = 'Validating project...';
|
|
110
|
+
// Use local.db to check if project exists
|
|
111
|
+
let dbUrl = process.env.DB_FILE_NAME || 'local.db';
|
|
112
|
+
// Convert relative path to absolute path for libsql
|
|
113
|
+
if (dbUrl !== ':memory:' &&
|
|
114
|
+
!dbUrl.startsWith('file:') &&
|
|
115
|
+
!dbUrl.startsWith('libsql:') &&
|
|
116
|
+
!dbUrl.startsWith('http')) {
|
|
117
|
+
const absolutePath = resolve(process.cwd(), dbUrl);
|
|
118
|
+
// Validate database file exists
|
|
119
|
+
if (!existsSync(absolutePath)) {
|
|
120
|
+
spinner.fail(`Database file not found: ${absolutePath}`);
|
|
121
|
+
console.error(chalk.red('Please ensure the database file exists or set DB_FILE_NAME environment variable'));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
dbUrl = `file:${absolutePath}`;
|
|
125
|
+
}
|
|
126
|
+
const dbClient = createDatabaseClient({ url: dbUrl });
|
|
127
|
+
const existingProject = await getProject(dbClient)({
|
|
128
|
+
scopes: { tenantId, projectId },
|
|
129
|
+
});
|
|
130
|
+
if (!existingProject) {
|
|
131
|
+
spinner.warn(`Project "${projectId}" does not exist`);
|
|
132
|
+
spinner.stop(); // Stop spinner before prompting
|
|
133
|
+
// Ask user if they want to create the project
|
|
134
|
+
const { shouldCreate } = await inquirer.prompt([
|
|
135
|
+
{
|
|
136
|
+
type: 'confirm',
|
|
137
|
+
name: 'shouldCreate',
|
|
138
|
+
message: `Project "${projectId}" does not exist. Would you like to create it?`,
|
|
139
|
+
default: true,
|
|
140
|
+
},
|
|
141
|
+
]);
|
|
142
|
+
if (!shouldCreate) {
|
|
143
|
+
console.log(chalk.yellow('Push cancelled. Project must exist before pushing a graph.'));
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
// Prompt for project details
|
|
147
|
+
const { projectName, projectDescription } = await inquirer.prompt([
|
|
148
|
+
{
|
|
149
|
+
type: 'input',
|
|
150
|
+
name: 'projectName',
|
|
151
|
+
message: 'Enter a name for the project:',
|
|
152
|
+
default: projectId,
|
|
153
|
+
validate: (input) => {
|
|
154
|
+
if (!input || input.trim() === '') {
|
|
155
|
+
return 'Project name is required';
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
type: 'input',
|
|
162
|
+
name: 'projectDescription',
|
|
163
|
+
message: 'Enter a description for the project (optional):',
|
|
164
|
+
default: '',
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
// Create the project
|
|
168
|
+
spinner.start('Creating project...');
|
|
169
|
+
try {
|
|
170
|
+
await createProject(dbClient)({
|
|
171
|
+
id: projectId,
|
|
172
|
+
tenantId: tenantId,
|
|
173
|
+
name: projectName,
|
|
174
|
+
description: projectDescription || 'No description provided',
|
|
175
|
+
});
|
|
176
|
+
spinner.succeed(`Project "${projectName}" created successfully`);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
spinner.fail('Failed to create project');
|
|
180
|
+
console.error(chalk.red('Error:'), error.message);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
spinner.succeed(`Project "${existingProject.name || projectId}" validated`);
|
|
186
|
+
}
|
|
187
|
+
// Create API client with validated configuration (not used directly since graph handles its own API calls)
|
|
188
|
+
await ManagementApiClient.create(config.managementApiUrl, options.configFilePath, config.tenantId);
|
|
189
|
+
// Inject configuration into the graph
|
|
190
|
+
if (typeof graph.setConfig === 'function') {
|
|
191
|
+
graph.setConfig(tenantId, projectId, managementApiUrl);
|
|
192
|
+
}
|
|
193
|
+
spinner.start('Initializing graph...');
|
|
194
|
+
// Initialize the graph (this will push to the backend)
|
|
195
|
+
await graph.init();
|
|
196
|
+
// Get graph details
|
|
197
|
+
const graphId = graph.getId();
|
|
198
|
+
const graphName = graph.getName();
|
|
199
|
+
const stats = graph.getStats();
|
|
200
|
+
spinner.succeed(`Graph "${graphName}" (${graphId}) pushed successfully`);
|
|
201
|
+
// Validate for dangling resources
|
|
202
|
+
const agents = graph.getAgents();
|
|
203
|
+
const warnings = [];
|
|
204
|
+
// Check for agents not referenced in any relationships
|
|
205
|
+
for (const agent of agents) {
|
|
206
|
+
const agentName = agent.getName();
|
|
207
|
+
let isReferenced = false;
|
|
208
|
+
// Check if this agent is referenced by any other agent
|
|
209
|
+
for (const otherAgent of agents) {
|
|
210
|
+
if (otherAgent === agent)
|
|
211
|
+
continue;
|
|
212
|
+
// Check transfers (only for internal agents)
|
|
213
|
+
if (typeof otherAgent.getTransfers === 'function') {
|
|
214
|
+
const transfers = otherAgent.getTransfers();
|
|
215
|
+
if (transfers.some((h) => h.getName() === agentName)) {
|
|
216
|
+
isReferenced = true;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Check delegates (only for internal agents)
|
|
221
|
+
if (typeof otherAgent.getDelegates === 'function') {
|
|
222
|
+
const delegates = otherAgent.getDelegates();
|
|
223
|
+
if (delegates.some((d) => d.getName() === agentName)) {
|
|
224
|
+
isReferenced = true;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Check if it's the default agent
|
|
230
|
+
const defaultAgent = graph.getDefaultAgent();
|
|
231
|
+
if (defaultAgent && defaultAgent.getName() === agentName) {
|
|
232
|
+
isReferenced = true;
|
|
233
|
+
}
|
|
234
|
+
if (!isReferenced && agents.length > 1) {
|
|
235
|
+
warnings.push(`Agent "${agentName}" is not referenced in any transfer or delegation relationships`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Display warnings if any
|
|
239
|
+
if (warnings.length > 0) {
|
|
240
|
+
console.log(chalk.yellow('\n⚠️ Warnings:'));
|
|
241
|
+
warnings.forEach((warning) => {
|
|
242
|
+
console.log(chalk.yellow(` • ${warning}`));
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// Display summary
|
|
246
|
+
console.log(chalk.cyan('\n📊 Graph Summary:'));
|
|
247
|
+
console.log(chalk.gray(` • Graph ID: ${graphId}`));
|
|
248
|
+
console.log(chalk.gray(` • Name: ${graphName}`));
|
|
249
|
+
console.log(chalk.gray(` • Agents: ${stats.agentCount}`));
|
|
250
|
+
console.log(chalk.gray(` • Tools: ${stats.toolCount}`));
|
|
251
|
+
console.log(chalk.gray(` • Relations: ${stats.relationCount}`));
|
|
252
|
+
// Provide next steps
|
|
253
|
+
console.log(chalk.green('\n✨ Next steps:'));
|
|
254
|
+
console.log(chalk.gray(` • Test your graph: inkeep chat ${graphId}`));
|
|
255
|
+
console.log(chalk.gray(` • View all graphs: inkeep list-graphs`));
|
|
256
|
+
console.log(chalk.gray(` • Get graph details: inkeep get-graph ${graphId}`));
|
|
257
|
+
// Force exit to avoid hanging due to OpenTelemetry or other background tasks
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
spinner.fail('Failed to push graph');
|
|
262
|
+
console.error(chalk.red('Error:'), error.message);
|
|
263
|
+
if (error.stack && process.env.DEBUG) {
|
|
264
|
+
console.error(chalk.gray(error.stack));
|
|
265
|
+
}
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ModelSettings } from '@inkeep/agents-core';
|
|
2
|
+
export interface InkeepConfig {
|
|
3
|
+
tenantId?: string;
|
|
4
|
+
projectId?: string;
|
|
5
|
+
managementApiUrl?: string;
|
|
6
|
+
executionApiUrl?: string;
|
|
7
|
+
outputDirectory?: string;
|
|
8
|
+
modelSettings?: ModelSettings;
|
|
9
|
+
}
|
|
10
|
+
export interface ValidatedConfiguration {
|
|
11
|
+
tenantId: string;
|
|
12
|
+
projectId: string;
|
|
13
|
+
managementApiUrl: string;
|
|
14
|
+
executionApiUrl: string;
|
|
15
|
+
outputDirectory?: string;
|
|
16
|
+
modelSettings?: ModelSettings;
|
|
17
|
+
sources: {
|
|
18
|
+
tenantId: string;
|
|
19
|
+
projectId: string;
|
|
20
|
+
managementApiUrl: string;
|
|
21
|
+
executionApiUrl: string;
|
|
22
|
+
configFile?: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function loadConfig(configPath?: string): Promise<InkeepConfig>;
|
|
26
|
+
export declare function getTenantId(configPath?: string): Promise<string | undefined>;
|
|
27
|
+
export declare function getProjectId(configPath?: string): Promise<string>;
|
|
28
|
+
export declare function getManagementApiUrl(overrideUrl?: string, configPath?: string): Promise<string>;
|
|
29
|
+
export declare function getExecutionApiUrl(overrideUrl?: string, configPath?: string): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Validates configuration according to these rules:
|
|
32
|
+
* 1. If --config-file-path is provided, use it (cannot be combined with --tenant-id)
|
|
33
|
+
* 2. If --tenant-id AND --management-api-url AND --execution-api-url are provided, use them (cannot be combined with --config-file-path)
|
|
34
|
+
* 3. If only --management-api-url and --execution-api-url are provided, it overrides the managementApiUrl and executionApiUrl from default config
|
|
35
|
+
* 4. Otherwise, look for default config file (inkeep.config.ts)
|
|
36
|
+
*
|
|
37
|
+
* @param tenantIdFlag - tenantId from command line flag
|
|
38
|
+
* @param managementApiUrlFlag - managementApiUrl from command line flag
|
|
39
|
+
* @param executionApiUrlFlag - executionApiUrl from command line flag
|
|
40
|
+
* @param configFilePath - explicit path to config file
|
|
41
|
+
* @returns configuration with tenantId, managementApiUrl, and sources used
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateConfiguration(tenantIdFlag?: string, managementApiUrlFlag?: string, executionApiUrlFlag?: string, configFilePath?: string): Promise<ValidatedConfiguration>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
// Load .env file from current directory if it exists
|
|
6
|
+
dotenv.config({ quiet: true });
|
|
7
|
+
/**
|
|
8
|
+
* Search for config file in current directory and parent directories
|
|
9
|
+
*/
|
|
10
|
+
function findConfigFile(startPath = process.cwd()) {
|
|
11
|
+
let currentPath = resolve(startPath);
|
|
12
|
+
const root = '/';
|
|
13
|
+
const configNames = ['inkeep.config.ts', 'inkeep.config.js', '.inkeeprc.ts', '.inkeeprc.js'];
|
|
14
|
+
while (currentPath !== root) {
|
|
15
|
+
// Check for config files at this level
|
|
16
|
+
for (const configName of configNames) {
|
|
17
|
+
const configPath = join(currentPath, configName);
|
|
18
|
+
if (existsSync(configPath)) {
|
|
19
|
+
return configPath;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const parentPath = dirname(currentPath);
|
|
23
|
+
if (parentPath === currentPath) {
|
|
24
|
+
break; // Reached filesystem root
|
|
25
|
+
}
|
|
26
|
+
currentPath = parentPath;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
async function loadConfigFromFile(configPath) {
|
|
31
|
+
let resolvedPath;
|
|
32
|
+
if (configPath) {
|
|
33
|
+
// User specified a config path
|
|
34
|
+
resolvedPath = resolve(process.cwd(), configPath);
|
|
35
|
+
if (!existsSync(resolvedPath)) {
|
|
36
|
+
throw new Error(`Config file not found: ${resolvedPath}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Search for config file
|
|
41
|
+
resolvedPath = findConfigFile();
|
|
42
|
+
if (!resolvedPath) {
|
|
43
|
+
// No config file found
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const fileUrl = pathToFileURL(resolvedPath).href;
|
|
49
|
+
const module = await import(fileUrl);
|
|
50
|
+
// Support both default export and named export
|
|
51
|
+
const config = module.default || module.config;
|
|
52
|
+
if (!config) {
|
|
53
|
+
throw new Error(`No config exported from ${resolvedPath}`);
|
|
54
|
+
}
|
|
55
|
+
return config;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
// If loading a TypeScript file fails, try using the ts-loader utility
|
|
59
|
+
try {
|
|
60
|
+
const { loadTypeScriptModule } = await import('./utils/ts-loader.js');
|
|
61
|
+
const module = await loadTypeScriptModule(resolvedPath);
|
|
62
|
+
return module.default || module.config || null;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
console.warn(`Warning: Failed to load config file ${resolvedPath}:`, error);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export async function loadConfig(configPath) {
|
|
71
|
+
// Default config
|
|
72
|
+
const config = {
|
|
73
|
+
managementApiUrl: 'http://localhost:3002',
|
|
74
|
+
executionApiUrl: 'http://localhost:3003',
|
|
75
|
+
};
|
|
76
|
+
// Try to load from inkeep.config.ts or specified config file
|
|
77
|
+
const fileConfig = await loadConfigFromFile(configPath);
|
|
78
|
+
if (fileConfig) {
|
|
79
|
+
Object.assign(config, fileConfig);
|
|
80
|
+
}
|
|
81
|
+
// Override with environment variables if present
|
|
82
|
+
if (process.env.INKEEP_TENANT_ID) {
|
|
83
|
+
config.tenantId = process.env.INKEEP_TENANT_ID;
|
|
84
|
+
}
|
|
85
|
+
if (process.env.INKEEP_PROJECT_ID) {
|
|
86
|
+
config.projectId = process.env.INKEEP_PROJECT_ID;
|
|
87
|
+
}
|
|
88
|
+
if (process.env.INKEEP_MANAGEMENT_API_URL) {
|
|
89
|
+
config.managementApiUrl = process.env.INKEEP_MANAGEMENT_API_URL;
|
|
90
|
+
}
|
|
91
|
+
if (process.env.INKEEP_EXECUTION_API_URL) {
|
|
92
|
+
config.executionApiUrl = process.env.INKEEP_EXECUTION_API_URL;
|
|
93
|
+
}
|
|
94
|
+
if (process.env.INKEEP_EXECUTION_API_URL) {
|
|
95
|
+
config.executionApiUrl = process.env.INKEEP_EXECUTION_API_URL;
|
|
96
|
+
}
|
|
97
|
+
return config;
|
|
98
|
+
}
|
|
99
|
+
export async function getTenantId(configPath) {
|
|
100
|
+
const config = await loadConfig(configPath);
|
|
101
|
+
return config.tenantId;
|
|
102
|
+
}
|
|
103
|
+
export async function getProjectId(configPath) {
|
|
104
|
+
const config = await loadConfig(configPath);
|
|
105
|
+
return config.projectId || 'default';
|
|
106
|
+
}
|
|
107
|
+
export async function getManagementApiUrl(overrideUrl, configPath) {
|
|
108
|
+
// Priority: override > config/env > default
|
|
109
|
+
if (overrideUrl) {
|
|
110
|
+
return overrideUrl;
|
|
111
|
+
}
|
|
112
|
+
const config = await loadConfig(configPath);
|
|
113
|
+
return config.managementApiUrl || 'http://localhost:3002';
|
|
114
|
+
}
|
|
115
|
+
export async function getExecutionApiUrl(overrideUrl, configPath) {
|
|
116
|
+
// Priority: override > config/env > default
|
|
117
|
+
if (overrideUrl) {
|
|
118
|
+
return overrideUrl;
|
|
119
|
+
}
|
|
120
|
+
const config = await loadConfig(configPath);
|
|
121
|
+
return config.executionApiUrl || 'http://localhost:3003';
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Validates configuration according to these rules:
|
|
125
|
+
* 1. If --config-file-path is provided, use it (cannot be combined with --tenant-id)
|
|
126
|
+
* 2. If --tenant-id AND --management-api-url AND --execution-api-url are provided, use them (cannot be combined with --config-file-path)
|
|
127
|
+
* 3. If only --management-api-url and --execution-api-url are provided, it overrides the managementApiUrl and executionApiUrl from default config
|
|
128
|
+
* 4. Otherwise, look for default config file (inkeep.config.ts)
|
|
129
|
+
*
|
|
130
|
+
* @param tenantIdFlag - tenantId from command line flag
|
|
131
|
+
* @param managementApiUrlFlag - managementApiUrl from command line flag
|
|
132
|
+
* @param executionApiUrlFlag - executionApiUrl from command line flag
|
|
133
|
+
* @param configFilePath - explicit path to config file
|
|
134
|
+
* @returns configuration with tenantId, managementApiUrl, and sources used
|
|
135
|
+
*/
|
|
136
|
+
export async function validateConfiguration(tenantIdFlag, managementApiUrlFlag, executionApiUrlFlag, configFilePath) {
|
|
137
|
+
// Validation: Cannot combine --config-file-path with --tenant-id
|
|
138
|
+
if (configFilePath && tenantIdFlag) {
|
|
139
|
+
throw new Error('Invalid configuration combination:\n' +
|
|
140
|
+
'Cannot use --config-file-path with --tenant-id.\n' +
|
|
141
|
+
'Please use either:\n' +
|
|
142
|
+
' 1. --config-file-path alone\n' +
|
|
143
|
+
' 2. --tenant-id with --management-api-url and --execution-api-url\n' +
|
|
144
|
+
' 3. Default config file (inkeep.config.ts)');
|
|
145
|
+
}
|
|
146
|
+
// Case 1: Explicit config file path provided
|
|
147
|
+
if (configFilePath) {
|
|
148
|
+
const config = await loadConfig(configFilePath);
|
|
149
|
+
const tenantId = config.tenantId;
|
|
150
|
+
const projectId = config.projectId || 'default';
|
|
151
|
+
const managementApiUrl = managementApiUrlFlag || config.managementApiUrl; // Allow ---management-api-url to override
|
|
152
|
+
const executionApiUrl = executionApiUrlFlag || config.executionApiUrl; // Allow --execution-api-url to override
|
|
153
|
+
if (!tenantId) {
|
|
154
|
+
throw new Error(`Tenant ID is missing from configuration file: ${configFilePath}\n` +
|
|
155
|
+
'Please ensure your config file exports a valid configuration with tenantId.');
|
|
156
|
+
}
|
|
157
|
+
if (!managementApiUrl) {
|
|
158
|
+
throw new Error(`Management API URL is missing from configuration file: ${configFilePath}\n` +
|
|
159
|
+
'Please ensure your config file exports a valid configuration with managementApiUrl.');
|
|
160
|
+
}
|
|
161
|
+
if (!executionApiUrl) {
|
|
162
|
+
throw new Error(`Execution API URL is missing from configuration file: ${configFilePath}\n` +
|
|
163
|
+
'Please ensure your config file exports a valid configuration with executionApiUrl.');
|
|
164
|
+
}
|
|
165
|
+
const sources = {
|
|
166
|
+
tenantId: `config file (${configFilePath})`,
|
|
167
|
+
projectId: config.projectId ? `config file (${configFilePath})` : 'default',
|
|
168
|
+
managementApiUrl: managementApiUrlFlag
|
|
169
|
+
? 'command-line flag (--management-api-url)'
|
|
170
|
+
: `config file (${configFilePath})`,
|
|
171
|
+
executionApiUrl: executionApiUrlFlag
|
|
172
|
+
? 'command-line flag (--execution-api-url)'
|
|
173
|
+
: `config file (${configFilePath})`,
|
|
174
|
+
configFile: configFilePath,
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
tenantId,
|
|
178
|
+
projectId,
|
|
179
|
+
managementApiUrl,
|
|
180
|
+
executionApiUrl,
|
|
181
|
+
modelSettings: config.modelSettings || undefined,
|
|
182
|
+
outputDirectory: config.outputDirectory,
|
|
183
|
+
sources,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Case 2: Both --tenant-id and --management-api-url and --execution-api-url provided
|
|
187
|
+
if (tenantIdFlag && managementApiUrlFlag && executionApiUrlFlag) {
|
|
188
|
+
const sources = {
|
|
189
|
+
tenantId: 'command-line flag (--tenant-id)',
|
|
190
|
+
projectId: 'default',
|
|
191
|
+
managementApiUrl: 'command-line flag (--management-api-url)',
|
|
192
|
+
executionApiUrl: 'command-line flag (--execution-api-url)',
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
tenantId: tenantIdFlag,
|
|
196
|
+
projectId: 'default',
|
|
197
|
+
managementApiUrl: managementApiUrlFlag,
|
|
198
|
+
executionApiUrl: executionApiUrlFlag,
|
|
199
|
+
modelSettings: undefined,
|
|
200
|
+
sources,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// Case 3: Only --tenant-id provided (invalid)
|
|
204
|
+
if (tenantIdFlag && !managementApiUrlFlag && !executionApiUrlFlag) {
|
|
205
|
+
throw new Error('Invalid configuration:\n' +
|
|
206
|
+
'--tenant-id requires --management-api-url and --execution-api-url to be provided as well.\n' +
|
|
207
|
+
'Please provide both --tenant-id and --management-api-url and --execution-api-url together.');
|
|
208
|
+
}
|
|
209
|
+
// Case 4: Try to load from default config file
|
|
210
|
+
const config = await loadConfig();
|
|
211
|
+
const tenantId = config.tenantId;
|
|
212
|
+
const projectId = config.projectId || 'default';
|
|
213
|
+
const managementApiUrl = managementApiUrlFlag || config.managementApiUrl; // Allow --management-api-url to override
|
|
214
|
+
const executionApiUrl = executionApiUrlFlag || config.executionApiUrl; // Allow --execution-api-url to override
|
|
215
|
+
if (!tenantId) {
|
|
216
|
+
// Check if a default config file exists
|
|
217
|
+
const configFile = findConfigFile();
|
|
218
|
+
if (!configFile) {
|
|
219
|
+
throw new Error('No configuration found. Please use one of:\n' +
|
|
220
|
+
' 1. Create "inkeep.config.ts" by running "inkeep init"\n' +
|
|
221
|
+
' 2. Provide --config-file-path to specify a config file\n' +
|
|
222
|
+
' 3. Provide both --tenant-id and --management-api-url and --execution-api-url flags\n' +
|
|
223
|
+
' 4. Set INKEEP_TENANT_ID and INKEEP_API_URL environment variables');
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
throw new Error(`Tenant ID is missing from configuration file: ${configFile}\n` +
|
|
227
|
+
'Please either:\n' +
|
|
228
|
+
' 1. Update your configuration file with a tenantId\n' +
|
|
229
|
+
' 2. Provide both --tenant-id and --management-api-url and --execution-api-url flags\n' +
|
|
230
|
+
' 3. Set INKEEP_TENANT_ID environment variable');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!managementApiUrl) {
|
|
234
|
+
throw new Error('Management API URL is missing. Please either:\n' +
|
|
235
|
+
' 1. Provide --management-api-url flag\n' +
|
|
236
|
+
' 2. Set INKEEP_MANAGEMENT_API_URL environment variable\n' +
|
|
237
|
+
' 3. Add managementApiUrl to your configuration file');
|
|
238
|
+
}
|
|
239
|
+
if (!executionApiUrl) {
|
|
240
|
+
throw new Error('Execution API URL is missing. Please either:\n' +
|
|
241
|
+
' 1. Provide --execution-api-url flag\n' +
|
|
242
|
+
' 2. Set INKEEP_EXECUTION_API_URL environment variable\n' +
|
|
243
|
+
' 3. Add executionApiUrl to your configuration file');
|
|
244
|
+
}
|
|
245
|
+
// Determine sources for Case 4
|
|
246
|
+
const configFile = findConfigFile();
|
|
247
|
+
let tenantIdSource = configFile ? `config file (${configFile})` : 'unknown';
|
|
248
|
+
let managementApiUrlSource = configFile ? `config file (${configFile})` : 'default';
|
|
249
|
+
let executionApiUrlSource = configFile ? `config file (${configFile})` : 'default';
|
|
250
|
+
// Check if environment variables were used
|
|
251
|
+
if (process.env.INKEEP_TENANT_ID === tenantId) {
|
|
252
|
+
tenantIdSource = 'environment variable (INKEEP_TENANT_ID)';
|
|
253
|
+
}
|
|
254
|
+
if (managementApiUrlFlag) {
|
|
255
|
+
managementApiUrlSource = 'command-line flag (--management-api-url)';
|
|
256
|
+
}
|
|
257
|
+
else if (process.env.INKEEP_MANAGEMENT_API_URL === managementApiUrl) {
|
|
258
|
+
managementApiUrlSource = 'environment variable (INKEEP_MANAGEMENT_API_URL)';
|
|
259
|
+
}
|
|
260
|
+
else if (managementApiUrl === 'http://localhost:3002' && !configFile) {
|
|
261
|
+
managementApiUrlSource = 'default value';
|
|
262
|
+
}
|
|
263
|
+
if (executionApiUrlFlag) {
|
|
264
|
+
executionApiUrlSource = 'command-line flag (--execution-api-url)';
|
|
265
|
+
}
|
|
266
|
+
else if (process.env.INKEEP_EXECUTION_API_URL === executionApiUrl) {
|
|
267
|
+
executionApiUrlSource = 'environment variable (INKEEP_EXECUTION_API_URL)';
|
|
268
|
+
}
|
|
269
|
+
else if (executionApiUrl === 'http://localhost:3003' && !configFile) {
|
|
270
|
+
executionApiUrlSource = 'default value';
|
|
271
|
+
}
|
|
272
|
+
const sources = {
|
|
273
|
+
tenantId: tenantIdSource,
|
|
274
|
+
projectId: config.projectId
|
|
275
|
+
? configFile
|
|
276
|
+
? `config file (${configFile})`
|
|
277
|
+
: 'config'
|
|
278
|
+
: 'default',
|
|
279
|
+
managementApiUrl: managementApiUrlSource,
|
|
280
|
+
executionApiUrl: executionApiUrlSource,
|
|
281
|
+
configFile: configFile || undefined,
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
tenantId,
|
|
285
|
+
projectId,
|
|
286
|
+
managementApiUrl,
|
|
287
|
+
executionApiUrl,
|
|
288
|
+
modelSettings: config.modelSettings || undefined,
|
|
289
|
+
outputDirectory: config.outputDirectory,
|
|
290
|
+
sources,
|
|
291
|
+
};
|
|
292
|
+
}
|
package/dist/exports.js
ADDED
package/dist/index.d.ts
ADDED