@inkeep/agents-cli 0.39.5 → 0.41.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.
Files changed (80) hide show
  1. package/dist/_virtual/rolldown_runtime.js +7 -0
  2. package/dist/api.js +185 -0
  3. package/dist/commands/add.js +139 -0
  4. package/dist/commands/config.js +86 -0
  5. package/dist/commands/dev.js +259 -0
  6. package/dist/commands/init.js +360 -0
  7. package/dist/commands/list-agents.js +56 -0
  8. package/dist/commands/login.js +179 -0
  9. package/dist/commands/logout.js +56 -0
  10. package/dist/commands/profile.js +276 -0
  11. package/dist/{component-parser2.js → commands/pull-v3/component-parser.js} +16 -3
  12. package/dist/commands/pull-v3/component-updater.js +710 -0
  13. package/dist/commands/pull-v3/components/agent-generator.js +241 -0
  14. package/dist/commands/pull-v3/components/artifact-component-generator.js +143 -0
  15. package/dist/commands/pull-v3/components/context-config-generator.js +190 -0
  16. package/dist/commands/pull-v3/components/credential-generator.js +89 -0
  17. package/dist/commands/pull-v3/components/data-component-generator.js +102 -0
  18. package/dist/commands/pull-v3/components/environment-generator.js +170 -0
  19. package/dist/commands/pull-v3/components/external-agent-generator.js +75 -0
  20. package/dist/commands/pull-v3/components/function-tool-generator.js +94 -0
  21. package/dist/commands/pull-v3/components/mcp-tool-generator.js +86 -0
  22. package/dist/commands/pull-v3/components/project-generator.js +145 -0
  23. package/dist/commands/pull-v3/components/status-component-generator.js +92 -0
  24. package/dist/commands/pull-v3/components/sub-agent-generator.js +285 -0
  25. package/dist/commands/pull-v3/index.js +510 -0
  26. package/dist/commands/pull-v3/introspect-generator.js +278 -0
  27. package/dist/commands/pull-v3/llm-content-merger.js +192 -0
  28. package/dist/{new-component-generator.js → commands/pull-v3/new-component-generator.js} +14 -3
  29. package/dist/commands/pull-v3/project-comparator.js +914 -0
  30. package/dist/{project-index-generator.js → commands/pull-v3/project-index-generator.js} +1 -2
  31. package/dist/{project-validator.js → commands/pull-v3/project-validator.js} +4 -4
  32. package/dist/commands/pull-v3/targeted-typescript-placeholders.js +173 -0
  33. package/dist/commands/pull-v3/utils/component-registry.js +369 -0
  34. package/dist/commands/pull-v3/utils/component-tracker.js +165 -0
  35. package/dist/commands/pull-v3/utils/generator-utils.js +146 -0
  36. package/dist/commands/pull-v3/utils/model-provider-detector.js +44 -0
  37. package/dist/commands/push.js +326 -0
  38. package/dist/commands/status.js +89 -0
  39. package/dist/commands/update.js +97 -0
  40. package/dist/commands/whoami.js +38 -0
  41. package/dist/config.js +0 -1
  42. package/dist/env.js +30 -0
  43. package/dist/exports.js +3 -0
  44. package/dist/index.js +28 -196514
  45. package/dist/instrumentation.js +47 -0
  46. package/dist/types/agent.js +1 -0
  47. package/dist/types/tsx.d.d.ts +1 -0
  48. package/dist/utils/background-version-check.js +19 -0
  49. package/dist/utils/ci-environment.js +87 -0
  50. package/dist/utils/cli-pipeline.js +158 -0
  51. package/dist/utils/config.js +290 -0
  52. package/dist/utils/credentials.js +132 -0
  53. package/dist/utils/environment-loader.js +28 -0
  54. package/dist/utils/file-finder.js +62 -0
  55. package/dist/utils/json-comparator.js +185 -0
  56. package/dist/utils/json-comparison.js +232 -0
  57. package/dist/utils/mcp-runner.js +120 -0
  58. package/dist/utils/model-config.js +182 -0
  59. package/dist/utils/package-manager.js +58 -0
  60. package/dist/utils/profile-config.js +85 -0
  61. package/dist/utils/profiles/index.js +4 -0
  62. package/dist/utils/profiles/profile-manager.js +219 -0
  63. package/dist/utils/profiles/types.js +62 -0
  64. package/dist/utils/project-directory.js +33 -0
  65. package/dist/utils/project-loader.js +29 -0
  66. package/dist/utils/schema-introspection.js +44 -0
  67. package/dist/utils/templates.js +198 -0
  68. package/dist/utils/tsx-loader.js +27 -0
  69. package/dist/utils/url.js +26 -0
  70. package/dist/utils/version-check.js +79 -0
  71. package/package.json +9 -24
  72. package/dist/component-parser.js +0 -4
  73. package/dist/component-updater.js +0 -4
  74. package/dist/config2.js +0 -4
  75. package/dist/credential-stores.js +0 -4
  76. package/dist/environment-generator.js +0 -4
  77. package/dist/nodefs.js +0 -27
  78. package/dist/opfs-ahp.js +0 -368
  79. package/dist/project-loader.js +0 -4
  80. package/dist/tsx-loader.js +0 -4
@@ -0,0 +1,47 @@
1
+ import { registerOTel } from "@vercel/otel";
2
+ import { LangfuseExporter } from "langfuse-vercel";
3
+
4
+ //#region src/instrumentation.ts
5
+ /**
6
+ * OpenTelemetry instrumentation setup for Langfuse
7
+ * This file sets up LLM observability for the CLI using Langfuse
8
+ *
9
+ * Initialization happens automatically when this file is imported
10
+ * in the entry point (index.ts), before any AI SDK calls are made.
11
+ */
12
+ const langfuseEnabled = process.env.LANGFUSE_ENABLED === "true";
13
+ const langfuseSecretKey = process.env.LANGFUSE_SECRET_KEY;
14
+ const langfusePublicKey = process.env.LANGFUSE_PUBLIC_KEY;
15
+ /**
16
+ * Check if Langfuse is properly configured
17
+ */
18
+ function isLangfuseConfigured() {
19
+ return !!(langfuseEnabled && langfuseSecretKey && langfusePublicKey);
20
+ }
21
+ /**
22
+ * Initialize OpenTelemetry with Langfuse exporter
23
+ * This will be called automatically when the module is imported
24
+ */
25
+ function initializeInstrumentation() {
26
+ if (!isLangfuseConfigured()) {
27
+ if (process.env.DEBUG) console.log("[Langfuse] Tracing disabled - missing configuration");
28
+ return;
29
+ }
30
+ try {
31
+ registerOTel({
32
+ serviceName: "inkeep-agents-cli",
33
+ traceExporter: new LangfuseExporter({
34
+ secretKey: langfuseSecretKey,
35
+ publicKey: langfusePublicKey,
36
+ baseUrl: process.env.LANGFUSE_BASEURL || "https://cloud.langfuse.com"
37
+ })
38
+ });
39
+ if (process.env.DEBUG) console.log("[Langfuse] Tracing initialized successfully");
40
+ } catch (error) {
41
+ console.warn("[Langfuse] Failed to initialize tracing:", error);
42
+ }
43
+ }
44
+ initializeInstrumentation();
45
+
46
+ //#endregion
47
+ export { initializeInstrumentation, isLangfuseConfigured };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,19 @@
1
+ import { checkForUpdate } from "./version-check.js";
2
+ import chalk from "chalk";
3
+
4
+ //#region src/utils/background-version-check.ts
5
+ /**
6
+ * Perform a non-blocking version check and display a warning if an update is available
7
+ * This function runs in the background and will not block command execution
8
+ */
9
+ function performBackgroundVersionCheck() {
10
+ checkForUpdate().then((versionInfo) => {
11
+ if (versionInfo.needsUpdate) {
12
+ console.log(chalk.yellow(`\n⚠️ A new version of @inkeep/agents-cli is available: ${versionInfo.latest} (current: ${versionInfo.current})`));
13
+ console.log(chalk.gray(" Run `inkeep update` to upgrade to the latest version\n"));
14
+ }
15
+ }).catch(() => {});
16
+ }
17
+
18
+ //#endregion
19
+ export { performBackgroundVersionCheck };
@@ -0,0 +1,87 @@
1
+ import { checkKeychainAvailability } from "./credentials.js";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import chalk from "chalk";
5
+ import { homedir } from "node:os";
6
+
7
+ //#region src/utils/ci-environment.ts
8
+ async function detectCIEnvironment() {
9
+ if (process.env.INKEEP_CI === "true") return {
10
+ isCI: true,
11
+ reason: "INKEEP_CI=true"
12
+ };
13
+ if (process.env.CI === "true" || process.env.CI === "1") return {
14
+ isCI: true,
15
+ reason: "CI environment detected"
16
+ };
17
+ if (process.env.GITHUB_ACTIONS === "true") return {
18
+ isCI: true,
19
+ reason: "GitHub Actions detected"
20
+ };
21
+ if (process.env.GITLAB_CI === "true") return {
22
+ isCI: true,
23
+ reason: "GitLab CI detected"
24
+ };
25
+ if (process.env.JENKINS_URL) return {
26
+ isCI: true,
27
+ reason: "Jenkins detected"
28
+ };
29
+ if (process.env.CIRCLECI === "true") return {
30
+ isCI: true,
31
+ reason: "CircleCI detected"
32
+ };
33
+ const profilesExist = existsSync(join(homedir(), ".inkeep", "profiles.yaml"));
34
+ const { available: keychainAvailable } = await checkKeychainAvailability();
35
+ if (!profilesExist && !keychainAvailable) return {
36
+ isCI: true,
37
+ reason: "no keychain available"
38
+ };
39
+ if (!keychainAvailable && process.env.INKEEP_API_KEY) return {
40
+ isCI: true,
41
+ reason: "no keychain available, using API key"
42
+ };
43
+ return {
44
+ isCI: false,
45
+ reason: ""
46
+ };
47
+ }
48
+ function loadCIEnvironmentConfig() {
49
+ const apiKey = process.env.INKEEP_API_KEY;
50
+ const manageApiUrl = process.env.INKEEP_MANAGE_API_URL;
51
+ const runApiUrl = process.env.INKEEP_RUN_API_URL;
52
+ const environment = process.env.INKEEP_ENVIRONMENT || "production";
53
+ const tenantId = process.env.INKEEP_TENANT_ID;
54
+ if (!apiKey) return null;
55
+ return {
56
+ isCI: true,
57
+ apiKey,
58
+ manageApiUrl: manageApiUrl || "https://manage-api.inkeep.com",
59
+ runApiUrl: runApiUrl || "https://run-api.inkeep.com",
60
+ environment,
61
+ tenantId
62
+ };
63
+ }
64
+ function logCIConfig(config, reason) {
65
+ console.log(chalk.yellow(`CI mode detected (${reason})`));
66
+ console.log(chalk.gray(` Remote: ${config.manageApiUrl}`));
67
+ console.log(chalk.gray(` Environment: ${config.environment}`));
68
+ console.log(chalk.gray(` Auth: API key (INKEEP_API_KEY)`));
69
+ if (config.tenantId) console.log(chalk.gray(` Tenant: ${config.tenantId}`));
70
+ }
71
+ function getAuthHeaders(config, isCI = false) {
72
+ const headers = {};
73
+ if (isCI && config.apiKey) headers["X-API-Key"] = config.apiKey;
74
+ else if (config.accessToken) headers["Authorization"] = `Bearer ${config.accessToken}`;
75
+ return headers;
76
+ }
77
+ function validateCIConfig(config) {
78
+ const errors = [];
79
+ if (!config.apiKey) errors.push("INKEEP_API_KEY environment variable is required in CI mode");
80
+ return {
81
+ valid: errors.length === 0,
82
+ errors
83
+ };
84
+ }
85
+
86
+ //#endregion
87
+ export { detectCIEnvironment, getAuthHeaders, loadCIEnvironmentConfig, logCIConfig, validateCIConfig };
@@ -0,0 +1,158 @@
1
+ import { getCredentialExpiryInfo, loadCredentials } from "./credentials.js";
2
+ import { ProfileManager } from "./profiles/profile-manager.js";
3
+ import "./profiles/index.js";
4
+ import { detectCIEnvironment, loadCIEnvironmentConfig, logCIConfig } from "./ci-environment.js";
5
+ import { validateConfiguration } from "./config.js";
6
+ import * as p from "@clack/prompts";
7
+ import chalk from "chalk";
8
+
9
+ //#region src/utils/cli-pipeline.ts
10
+ /**
11
+ * Standard pipeline for initializing CLI commands
12
+ *
13
+ * This function provides a consistent way to:
14
+ * 1. Load profile configuration (if available)
15
+ * 2. Load and validate configuration from inkeep.config.ts
16
+ * 3. Merge profile config with file config (profile takes precedence)
17
+ * 4. Handle errors with user-friendly messages
18
+ * 5. Optionally display progress with spinners
19
+ * 6. Log configuration sources for debugging
20
+ *
21
+ * Configuration precedence: CLI flag > profile > config.ts defaults
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * export async function myCommand(options: MyOptions) {
26
+ * const { config, profile, isAuthenticated } = await initializeCommand({
27
+ * configPath: options.config,
28
+ * profileName: options.profile,
29
+ * showSpinner: true,
30
+ * spinnerText: 'Loading configuration...',
31
+ * logConfig: true
32
+ * });
33
+ *
34
+ * // Your command logic here...
35
+ * }
36
+ * ```
37
+ */
38
+ async function initializeCommand(options = {}) {
39
+ const { configPath, tag, profileName, showSpinner = false, spinnerText = "Loading configuration...", logConfig = true, quiet = false } = options;
40
+ const s = showSpinner ? p.spinner() : void 0;
41
+ if (s) s.start(spinnerText);
42
+ try {
43
+ const ciDetection = await detectCIEnvironment();
44
+ const ciConfig = ciDetection.isCI ? loadCIEnvironmentConfig() : null;
45
+ if (ciDetection.isCI) {
46
+ const config$1 = await validateConfiguration(configPath, tag);
47
+ if (ciConfig) {
48
+ if (ciConfig.manageApiUrl) config$1.agentsManageApiUrl = ciConfig.manageApiUrl;
49
+ if (ciConfig.runApiUrl) config$1.agentsRunApiUrl = ciConfig.runApiUrl;
50
+ if (ciConfig.apiKey) config$1.agentsManageApiKey = ciConfig.apiKey;
51
+ if (ciConfig.tenantId) config$1.tenantId = ciConfig.tenantId;
52
+ }
53
+ if (s) s.stop("Configuration loaded");
54
+ if (logConfig && !quiet) if (ciConfig) logCIConfig(ciConfig, ciDetection.reason);
55
+ else {
56
+ console.log(chalk.yellow(`CI mode detected (${ciDetection.reason})`));
57
+ console.log(chalk.gray(` Using config file settings (no INKEEP_API_KEY set)`));
58
+ console.log(chalk.gray(` Remote: ${config$1.agentsManageApiUrl}`));
59
+ }
60
+ return {
61
+ config: config$1,
62
+ isAuthenticated: !!ciConfig?.apiKey,
63
+ isCI: true,
64
+ ciConfig: ciConfig ?? void 0
65
+ };
66
+ }
67
+ let profile;
68
+ let isAuthenticated = false;
69
+ let authExpiry;
70
+ let profileAccessToken;
71
+ let profileOrganizationId;
72
+ try {
73
+ const profileManager = new ProfileManager();
74
+ if (profileName) {
75
+ const foundProfile = profileManager.getProfile(profileName);
76
+ if (!foundProfile) throw new Error(`Profile '${profileName}' not found.`);
77
+ profile = foundProfile;
78
+ } else profile = profileManager.getActiveProfile();
79
+ if (profile) {
80
+ const credentials = await loadCredentials(profile.credential);
81
+ if (credentials) {
82
+ const expiryInfo = getCredentialExpiryInfo(credentials);
83
+ if (!expiryInfo.isExpired) {
84
+ profileAccessToken = credentials.accessToken;
85
+ profileOrganizationId = credentials.organizationId;
86
+ isAuthenticated = true;
87
+ authExpiry = expiryInfo.expiresIn;
88
+ }
89
+ }
90
+ }
91
+ } catch {}
92
+ const config = await validateConfiguration(configPath, tag);
93
+ if (profile) {
94
+ config.agentsManageApiUrl = profile.remote.manageApi;
95
+ config.agentsRunApiUrl = profile.remote.runApi;
96
+ config.manageUiUrl = profile.remote.manageUi;
97
+ if (profileAccessToken) config.agentsManageApiKey = profileAccessToken;
98
+ if (profileOrganizationId) config.tenantId = profileOrganizationId;
99
+ }
100
+ if (s) s.stop("Configuration loaded");
101
+ if (logConfig && !quiet) if (profile) {
102
+ const expiryText = authExpiry ? ` (expires in ${authExpiry})` : "";
103
+ const authStatus = isAuthenticated ? chalk.green("authenticated") + expiryText : chalk.yellow("not authenticated");
104
+ console.log(chalk.gray(`Using profile: ${chalk.cyan(profile.name)}`));
105
+ console.log(chalk.gray(` Remote: ${config.agentsManageApiUrl}`));
106
+ console.log(chalk.gray(` Environment: ${profile.environment}`));
107
+ console.log(chalk.gray(` Auth: ${authStatus}`));
108
+ } else {
109
+ console.log(chalk.gray("Configuration:"));
110
+ console.log(chalk.gray(` • Tenant ID: ${config.tenantId}`));
111
+ console.log(chalk.gray(` • Manage API URL: ${config.agentsManageApiUrl}`));
112
+ console.log(chalk.gray(` • Run API URL: ${config.agentsRunApiUrl}`));
113
+ if (config.sources.configFile) console.log(chalk.gray(` • Config file: ${config.sources.configFile}`));
114
+ }
115
+ return {
116
+ config,
117
+ profile,
118
+ isAuthenticated,
119
+ authExpiry,
120
+ isCI: false
121
+ };
122
+ } catch (error) {
123
+ if (s) s.stop("Configuration failed");
124
+ console.error(chalk.red("Error:"), error.message);
125
+ if (error.message.includes("Profile") && error.message.includes("not found")) console.log(chalk.yellow("\nHint: Run \"inkeep profile list\" to see available profiles."));
126
+ else if (error.message.includes("No configuration found")) {
127
+ console.log(chalk.yellow("\nHint: Create a configuration file by running:"));
128
+ console.log(chalk.gray(" inkeep init"));
129
+ } else if (error.message.includes("Config file not found")) console.log(chalk.yellow("\nHint: Check that your config file path is correct"));
130
+ else if (error.message.includes("tenantId") || error.message.includes("API URL")) {
131
+ console.log(chalk.yellow("\nHint: Ensure your inkeep.config.ts has all required fields:"));
132
+ console.log(chalk.gray(" - tenantId"));
133
+ console.log(chalk.gray(" - agentsManageApiUrl (or agentsManageApi.url)"));
134
+ console.log(chalk.gray(" - agentsRunApiUrl (or agentsRunApi.url)"));
135
+ }
136
+ process.exit(1);
137
+ }
138
+ }
139
+ /**
140
+ * Lightweight config loader without spinners or logging
141
+ * Useful for commands that need config but handle their own UI
142
+ */
143
+ async function loadCommandConfig(configPath, profileName) {
144
+ try {
145
+ return await initializeCommand({
146
+ configPath,
147
+ profileName,
148
+ showSpinner: false,
149
+ logConfig: false
150
+ });
151
+ } catch (error) {
152
+ console.error(chalk.red("Configuration error:"), error.message);
153
+ process.exit(1);
154
+ }
155
+ }
156
+
157
+ //#endregion
158
+ export { initializeCommand, loadCommandConfig };
@@ -0,0 +1,290 @@
1
+ import { loadCredentials } from "./credentials.js";
2
+ import { importWithTypeScriptSupport } from "./tsx-loader.js";
3
+ import { getLogger } from "@inkeep/agents-core";
4
+ import { existsSync, readdirSync, statSync } from "node:fs";
5
+ import { dirname, join, relative, resolve } from "node:path";
6
+
7
+ //#region src/utils/config.ts
8
+ const logger = getLogger("config");
9
+ /**
10
+ * Masks sensitive values in config for safe logging
11
+ * @internal Exported for testing purposes
12
+ */
13
+ function maskSensitiveConfig(config) {
14
+ if (!config) return config;
15
+ const masked = { ...config };
16
+ if (masked.agentsManageApiKey) masked.agentsManageApiKey = `***${masked.agentsManageApiKey.slice(-4)}`;
17
+ if (masked.agentsRunApiKey) masked.agentsRunApiKey = `***${masked.agentsRunApiKey.slice(-4)}`;
18
+ return masked;
19
+ }
20
+ /**
21
+ * Type guard to check if config uses nested format
22
+ */
23
+ function isNestedConfig(config) {
24
+ return config && (config.agentsManageApi !== void 0 || config.agentsRunApi !== void 0);
25
+ }
26
+ /**
27
+ * Normalize config from either flat or nested format to internal format
28
+ */
29
+ function normalizeConfig(config) {
30
+ if (isNestedConfig(config)) return {
31
+ tenantId: config.tenantId,
32
+ agentsManageApiUrl: config.agentsManageApi?.url,
33
+ agentsRunApiUrl: config.agentsRunApi?.url,
34
+ agentsManageApiKey: config.agentsManageApi?.apiKey,
35
+ agentsRunApiKey: config.agentsRunApi?.apiKey,
36
+ manageUiUrl: config.manageUiUrl,
37
+ outputDirectory: config.outputDirectory
38
+ };
39
+ return {
40
+ tenantId: config.tenantId,
41
+ agentsManageApiUrl: config.agentsManageApiUrl,
42
+ agentsRunApiUrl: config.agentsRunApiUrl,
43
+ manageUiUrl: config.manageUiUrl,
44
+ outputDirectory: config.outputDirectory
45
+ };
46
+ }
47
+ /**
48
+ * Get config file names for a given tag
49
+ * @param tag - Optional tag for environment-specific config (e.g., 'prod', 'staging')
50
+ * @returns Array of config file names to search for
51
+ */
52
+ function getConfigFileNames(tag) {
53
+ if (tag) return [`${tag}.__inkeep.config.ts__`, `${tag}.__inkeep.config.js__`];
54
+ return [
55
+ "inkeep.config.ts",
56
+ "inkeep.config.js",
57
+ ".inkeeprc.ts",
58
+ ".inkeeprc.js"
59
+ ];
60
+ }
61
+ /**
62
+ * Search for config file in current directory and parent directories
63
+ * @param startPath - Directory to start searching from (defaults to current working directory)
64
+ * @param tag - Optional tag for environment-specific config (e.g., 'prod', 'staging')
65
+ * @returns Path to config file or null if not found
66
+ */
67
+ function findConfigFile(startPath = process.cwd(), tag) {
68
+ let currentPath = resolve(startPath);
69
+ const root = "/";
70
+ const configNames = getConfigFileNames(tag);
71
+ while (currentPath !== root) {
72
+ for (const configName of configNames) {
73
+ const configPath = join(currentPath, configName);
74
+ if (existsSync(configPath)) return configPath;
75
+ }
76
+ const parentPath = dirname(currentPath);
77
+ if (parentPath === currentPath) break;
78
+ currentPath = parentPath;
79
+ }
80
+ return null;
81
+ }
82
+ /**
83
+ * Find all project config files recursively in a directory
84
+ * @param rootDir - Root directory to search
85
+ * @param tag - Optional tag for environment-specific config
86
+ * @param excludeDirs - Directories to exclude from search
87
+ * @returns Array of found config file paths
88
+ */
89
+ function findAllConfigFiles(rootDir, tag, excludeDirs = [
90
+ "node_modules",
91
+ ".git",
92
+ "dist",
93
+ "build",
94
+ ".temp-validation"
95
+ ]) {
96
+ const configFiles = [];
97
+ const configNames = getConfigFileNames(tag);
98
+ function scanDirectory(dir) {
99
+ if (!existsSync(dir)) return;
100
+ let items;
101
+ try {
102
+ items = readdirSync(dir);
103
+ } catch {
104
+ return;
105
+ }
106
+ for (const item of items) {
107
+ const fullPath = join(dir, item);
108
+ const relativePath = relative(rootDir, fullPath);
109
+ if (excludeDirs.some((excl) => item === excl || relativePath.startsWith(`${excl}/`))) continue;
110
+ try {
111
+ const stat = statSync(fullPath);
112
+ if (stat.isDirectory()) scanDirectory(fullPath);
113
+ else if (stat.isFile() && configNames.includes(item)) configFiles.push(fullPath);
114
+ } catch {}
115
+ }
116
+ }
117
+ scanDirectory(rootDir);
118
+ return configFiles.sort();
119
+ }
120
+ /**
121
+ * Extract project ID from a loaded config
122
+ * The project ID can be specified directly or needs to be loaded from the project
123
+ * @param configPath - Path to the config file
124
+ * @returns Project ID or null if not found
125
+ */
126
+ async function extractProjectIdFromConfig(configPath) {
127
+ try {
128
+ const module = await importWithTypeScriptSupport(configPath);
129
+ const rawConfig = module.default || module.config;
130
+ if (!rawConfig) return null;
131
+ if (rawConfig.projectId) return rawConfig.projectId;
132
+ return null;
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+ /**
138
+ * Find project config and extract project information
139
+ * This walks up directories to find the nearest config file
140
+ * @param startPath - Directory to start searching from
141
+ * @param tag - Optional tag for environment-specific config
142
+ * @returns Project config result or null if not found
143
+ */
144
+ async function findProjectConfig(startPath = process.cwd(), tag) {
145
+ const configPath = findConfigFile(startPath, tag);
146
+ if (!configPath) return null;
147
+ return {
148
+ configPath,
149
+ projectDir: dirname(configPath),
150
+ projectId: await extractProjectIdFromConfig(configPath)
151
+ };
152
+ }
153
+ /**
154
+ * Load config file from disk and normalize it
155
+ * This is the core config loading logic used by all CLI commands
156
+ *
157
+ * @param configPath - Optional explicit path to config file
158
+ * @param tag - Optional tag for environment-specific config (e.g., 'prod', 'staging')
159
+ * @returns Normalized config or null if not found
160
+ */
161
+ async function loadConfigFromFile(configPath, tag) {
162
+ logger.info({
163
+ fromPath: configPath,
164
+ tag
165
+ }, `Loading config file`);
166
+ let resolvedPath;
167
+ if (configPath) {
168
+ resolvedPath = resolve(process.cwd(), configPath);
169
+ if (!existsSync(resolvedPath)) throw new Error(`Config file not found: ${resolvedPath}`);
170
+ } else {
171
+ resolvedPath = findConfigFile(process.cwd(), tag);
172
+ if (!resolvedPath) {
173
+ if (tag) {
174
+ const taggedFileName = getConfigFileNames(tag)[0];
175
+ throw new Error(`Tagged config file not found: ${taggedFileName}\nCreate this file or use a different --tag value.`);
176
+ }
177
+ return null;
178
+ }
179
+ }
180
+ try {
181
+ const module = await importWithTypeScriptSupport(resolvedPath);
182
+ const rawConfig = module.default || module.config;
183
+ if (!rawConfig) throw new Error(`No config exported from ${resolvedPath}`);
184
+ const config = normalizeConfig(rawConfig);
185
+ logger.info({ config: maskSensitiveConfig(config) }, `Loaded config values`);
186
+ return config;
187
+ } catch (error) {
188
+ if (error instanceof Error && error.message.includes("Tagged config file not found")) throw error;
189
+ console.warn(`Warning: Failed to load config file ${resolvedPath}:`, error);
190
+ return null;
191
+ }
192
+ }
193
+ /**
194
+ * Main config loader - single source of truth for loading inkeep.config.ts
195
+ * This is the ONLY function that should be used to load configuration across all CLI commands.
196
+ *
197
+ * Configuration priority (highest to lowest):
198
+ * 1. CLI flags (handled by caller)
199
+ * 2. Config file (inkeep.config.ts)
200
+ * 3. Default values
201
+ *
202
+ * @param configPath - Optional explicit path to config file
203
+ * @param tag - Optional tag for environment-specific config (e.g., 'prod', 'staging')
204
+ * @returns Normalized configuration with defaults applied
205
+ */
206
+ async function loadConfig(configPath, tag) {
207
+ const config = {
208
+ agentsManageApiUrl: "http://localhost:3002",
209
+ agentsRunApiUrl: "http://localhost:3003",
210
+ manageUiUrl: "http://localhost:3000"
211
+ };
212
+ const fileConfig = await loadConfigFromFile(configPath, tag);
213
+ if (fileConfig) {
214
+ Object.keys(fileConfig).forEach((key) => {
215
+ const value = fileConfig[key];
216
+ if (value !== void 0) config[key] = value;
217
+ });
218
+ logger.info({ mergedConfig: maskSensitiveConfig(config) }, `Config loaded from file`);
219
+ } else logger.info({ config: maskSensitiveConfig(config) }, `Using default config (no config file found)`);
220
+ return config;
221
+ }
222
+ /**
223
+ * Validates configuration loaded from inkeep.config.ts file
224
+ * This is the ONLY way to configure the CLI - no CLI flags for URLs/keys
225
+ *
226
+ * Configuration priority:
227
+ * 1. Config file (inkeep.config.ts or --config path/to/config.ts)
228
+ * 2. CLI credentials (from `inkeep login`) - for API key and tenant ID fallback
229
+ * 3. Default values (http://localhost:3002, http://localhost:3003)
230
+ *
231
+ * Note: API URLs and keys are loaded ONLY from the config file, NOT from environment
232
+ * variables or CLI flags. This ensures explicit control over where the CLI connects.
233
+ *
234
+ * Secrets (API keys, bypass tokens) CAN be loaded from .env files in the working directory
235
+ * and parent directories via the config file's environment variable references.
236
+ *
237
+ * @param configPath - explicit path to config file (from --config parameter)
238
+ * @param tag - optional tag for environment-specific config (e.g., 'prod', 'staging')
239
+ * @returns configuration with tenantId, agentsManageApiUrl, agentsRunApiUrl, and source info
240
+ */
241
+ async function validateConfiguration(configPath, tag) {
242
+ const config = await loadConfig(configPath, tag);
243
+ const actualConfigFile = configPath || findConfigFile(process.cwd(), tag);
244
+ let cliCredentials = null;
245
+ try {
246
+ const credentials = await loadCredentials();
247
+ if (credentials && credentials.accessToken && credentials.organizationId) {
248
+ cliCredentials = {
249
+ accessToken: credentials.accessToken,
250
+ organizationId: credentials.organizationId
251
+ };
252
+ logger.info({}, "CLI credentials available for fallback");
253
+ }
254
+ } catch {
255
+ logger.debug({}, "Could not load CLI credentials");
256
+ }
257
+ if (!config.agentsManageApiKey && cliCredentials) {
258
+ config.agentsManageApiKey = cliCredentials.accessToken;
259
+ logger.info({}, "Using CLI session token as API key");
260
+ }
261
+ if (!config.tenantId && cliCredentials) {
262
+ config.tenantId = cliCredentials.organizationId;
263
+ logger.info({}, "Using CLI organization ID as tenant ID");
264
+ }
265
+ if (!config.tenantId) {
266
+ if (actualConfigFile) throw new Error(`Tenant ID is missing from configuration file: ${actualConfigFile}\nPlease ensure your config file exports a valid configuration with tenantId,
267
+ or run "inkeep login" to authenticate with Inkeep Cloud.`);
268
+ throw new Error("No configuration found. Please:\n 1. Run \"inkeep login\" to authenticate with Inkeep Cloud\n 2. Or create \"inkeep.config.ts\" by running \"inkeep init\"\n 3. Or provide --config to specify a config file path");
269
+ }
270
+ if (!config.agentsManageApiUrl) throw new Error(`Agents Management API URL is missing from config file${actualConfigFile ? `: ${actualConfigFile}` : ""}\nPlease add agentsManageApiUrl to your configuration file`);
271
+ if (!config.agentsRunApiUrl) throw new Error(`Agents Run API URL is missing from config file${actualConfigFile ? `: ${actualConfigFile}` : ""}\nPlease add agentsRunApiUrl to your configuration file`);
272
+ const sources = {
273
+ tenantId: cliCredentials && !actualConfigFile ? "CLI login (organization ID)" : actualConfigFile ? `config file (${actualConfigFile})` : "default",
274
+ agentsManageApiUrl: actualConfigFile ? `config file (${actualConfigFile})` : "default value",
275
+ agentsRunApiUrl: actualConfigFile ? `config file (${actualConfigFile})` : "default value"
276
+ };
277
+ if (actualConfigFile) sources.configFile = actualConfigFile;
278
+ return {
279
+ tenantId: config.tenantId,
280
+ agentsManageApiUrl: config.agentsManageApiUrl,
281
+ agentsRunApiUrl: config.agentsRunApiUrl,
282
+ agentsManageApiKey: config.agentsManageApiKey,
283
+ agentsRunApiKey: config.agentsRunApiKey,
284
+ manageUiUrl: config.manageUiUrl,
285
+ sources
286
+ };
287
+ }
288
+
289
+ //#endregion
290
+ export { extractProjectIdFromConfig, findAllConfigFiles, findConfigFile, findProjectConfig, getConfigFileNames, loadConfig, loadConfigFromFile, maskSensitiveConfig, validateConfiguration };