UNPKG

@inkeep/agents-cli

Version:
1,428 lines (1,410 loc) 73.9 kB
#!/usr/bin/env node var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // ../node_modules/.pnpm/tsup@8.5.0_jiti@2.5.1_postcss@8.5.6_tsx@4.20.5_typescript@5.9.2_yaml@2.8.1/node_modules/tsup/assets/esm_shims.js import path from "path"; import { fileURLToPath } from "url"; var init_esm_shims = __esm({ "../node_modules/.pnpm/tsup@8.5.0_jiti@2.5.1_postcss@8.5.6_tsx@4.20.5_typescript@5.9.2_yaml@2.8.1/node_modules/tsup/assets/esm_shims.js"() { "use strict"; } }); // src/utils/tsx-loader.ts import { extname } from "path"; import { pathToFileURL } from "url"; import { register } from "tsx/esm/api"; async function importWithTypeScriptSupport(filePath) { const ext = extname(filePath); if (ext === ".ts") { try { const unregister = register(); try { const fileUrl2 = pathToFileURL(filePath).href; const module = await import(fileUrl2); return module; } finally { unregister(); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes("Dynamic require") || errorMessage.includes("tsx") || errorMessage.includes("register")) { throw new Error( `Failed to load TypeScript file ${filePath}. Make sure tsx is installed: ${errorMessage}` ); } throw error; } } const fileUrl = pathToFileURL(filePath).href; return await import(fileUrl); } var init_tsx_loader = __esm({ "src/utils/tsx-loader.ts"() { "use strict"; init_esm_shims(); } }); // src/utils/config.ts import { existsSync as existsSync4 } from "fs"; import { dirname as dirname3, join as join4, resolve as resolve2 } from "path"; import dotenv from "dotenv"; function findConfigFile(startPath = process.cwd()) { let currentPath = resolve2(startPath); const root = "/"; const configNames = ["inkeep.config.ts", "inkeep.config.js", ".inkeeprc.ts", ".inkeeprc.js"]; while (currentPath !== root) { for (const configName of configNames) { const configPath = join4(currentPath, configName); if (existsSync4(configPath)) { return configPath; } } const parentPath = dirname3(currentPath); if (parentPath === currentPath) { break; } currentPath = parentPath; } return null; } async function loadConfigFromFile(configPath) { let resolvedPath; if (configPath) { resolvedPath = resolve2(process.cwd(), configPath); if (!existsSync4(resolvedPath)) { throw new Error(`Config file not found: ${resolvedPath}`); } } else { resolvedPath = findConfigFile(); if (!resolvedPath) { return null; } } try { const module = await importWithTypeScriptSupport(resolvedPath); const config = module.default || module.config; if (!config) { throw new Error(`No config exported from ${resolvedPath}`); } return config; } catch (error) { console.warn(`Warning: Failed to load config file ${resolvedPath}:`, error); return null; } } async function loadConfig(configPath) { const config = { agentsManageApiUrl: "http://localhost:3002", agentsRunApiUrl: "http://localhost:3003", manageUiUrl: "http://localhost:3000" }; const fileConfig = await loadConfigFromFile(configPath); if (fileConfig) { Object.assign(config, fileConfig); } if (process.env.INKEEP_AGENTS_MANAGE_API_URL) { config.agentsManageApiUrl = process.env.INKEEP_AGENTS_MANAGE_API_URL; } if (process.env.INKEEP_AGENTS_RUN_API_URL) { config.agentsRunApiUrl = process.env.INKEEP_AGENTS_RUN_API_URL; } return config; } async function getTenantId(configPath) { const config = await loadConfig(configPath); return config.tenantId; } async function getProjectId(configPath) { const config = await loadConfig(configPath); return config.projectId || "default"; } async function getAgentsManageApiUrl(overrideUrl, configPath) { if (overrideUrl) { return overrideUrl; } const config = await loadConfig(configPath); return config.agentsManageApiUrl || "http://localhost:3002"; } async function getAgentsRunApiUrl(overrideUrl, configPath) { if (overrideUrl) { return overrideUrl; } const config = await loadConfig(configPath); return config.agentsRunApiUrl || "http://localhost:3003"; } async function validateConfiguration(tenantIdFlag, agentsManageApiUrlFlag, agentsRunApiUrlFlag, configFilePath) { if (configFilePath && tenantIdFlag) { throw new Error( "Invalid configuration combination:\nCannot use --config-file-path with --tenant-id.\nPlease use either:\n 1. --config-file-path alone\n 2. --tenant-id with --agents-manage-api-url and --agents-run-api-url\n 3. Default config file (inkeep.config.ts)" ); } if (configFilePath) { const config2 = await loadConfig(configFilePath); const tenantId2 = config2.tenantId; const projectId2 = config2.projectId || "default"; const agentsManageApiUrl2 = agentsManageApiUrlFlag || config2.agentsManageApiUrl; const agentsRunApiUrl2 = agentsRunApiUrlFlag || config2.agentsRunApiUrl; if (!tenantId2) { throw new Error( `Tenant ID is missing from configuration file: ${configFilePath} Please ensure your config file exports a valid configuration with tenantId.` ); } if (!agentsManageApiUrl2) { throw new Error( `Agents Manage API URL is missing from configuration file: ${configFilePath} Please ensure your config file exports a valid configuration with agentsManageApiUrl.` ); } if (!agentsRunApiUrl2) { throw new Error( `Agents Run API URL is missing from configuration file: ${configFilePath} Please ensure your config file exports a valid configuration with agentsRunApiUrl.` ); } const sources2 = { tenantId: `config file (${configFilePath})`, projectId: config2.projectId ? `config file (${configFilePath})` : "default", agentsManageApiUrl: agentsManageApiUrlFlag ? "command-line flag (--agents-manage-api-url)" : `config file (${configFilePath})`, agentsRunApiUrl: agentsRunApiUrlFlag ? "command-line flag (--agents-run-api-url)" : `config file (${configFilePath})`, configFile: configFilePath }; return { tenantId: tenantId2, projectId: projectId2, agentsManageApiUrl: agentsManageApiUrl2, agentsRunApiUrl: agentsRunApiUrl2, manageUiUrl: config2.manageUiUrl, modelSettings: config2.modelSettings || void 0, sources: sources2 }; } if (tenantIdFlag && agentsManageApiUrlFlag && agentsRunApiUrlFlag) { const sources2 = { tenantId: "command-line flag (--tenant-id)", projectId: "default", agentsManageApiUrl: "command-line flag (--agents-manage-api-url)", agentsRunApiUrl: "command-line flag (--agents-run-api-url)" }; return { tenantId: tenantIdFlag, projectId: "default", agentsManageApiUrl: agentsManageApiUrlFlag, agentsRunApiUrl: agentsRunApiUrlFlag, manageUiUrl: void 0, modelSettings: void 0, sources: sources2 }; } if (tenantIdFlag && !agentsManageApiUrlFlag && !agentsRunApiUrlFlag) { throw new Error( "Invalid configuration:\n--tenant-id requires --agents-manage-api-url and --agents-run-api-url to be provided as well.\nPlease provide both --tenant-id and --agents-manage-api-url and --agents-run-api-url together." ); } const config = await loadConfig(); const tenantId = config.tenantId; const projectId = config.projectId || "default"; const agentsManageApiUrl = agentsManageApiUrlFlag || config.agentsManageApiUrl; const agentsRunApiUrl = agentsRunApiUrlFlag || config.agentsRunApiUrl; if (!tenantId) { const configFile2 = findConfigFile(); if (!configFile2) { throw new Error( 'No configuration found. Please use one of:\n 1. Create "inkeep.config.ts" by running "inkeep init"\n 2. Provide --config-file-path to specify a config file\n 3. Provide both --tenant-id and --agents-manage-api-url and --agents-run-api-url flags\n 4. Set INKEEP_AGENTS_MANAGE_API_URL and INKEEP_AGENTS_RUN_API_URL environment variables' ); } else { throw new Error( `Tenant ID is missing from configuration file: ${configFile2} Please either: 1. Update your configuration file with a tenantId 2. Provide both --tenant-id and --agents-manage-api-url and --agents-run-api-url flags ` ); } } if (!agentsManageApiUrl) { throw new Error( "Agents Management API URL is missing. Please either:\n 1. Provide --agents-manage-api-url flag\n 2. Set INKEEP_AGENTS_MANAGE_API_URL environment variable\n 3. Add agentsManageApiUrl to your configuration file" ); } if (!agentsRunApiUrl) { throw new Error( "Agents Run API URL is missing. Please either:\n 1. Provide --agents-run-api-url flag\n 2. Set INKEEP_AGENTS_RUN_API_URL environment variable\n 3. Add agentsRunApiUrl to your configuration file" ); } const configFile = findConfigFile(); let agentsManageApiUrlSource = configFile ? `config file (${configFile})` : "default"; let agentsRunApiUrlSource = configFile ? `config file (${configFile})` : "default"; if (agentsManageApiUrlFlag) { agentsManageApiUrlSource = "command-line flag (--agents-manage-api-url)"; } else if (process.env.INKEEP_AGENTS_MANAGE_API_URL === agentsManageApiUrl) { agentsManageApiUrlSource = "environment variable (INKEEP_AGENTS_MANAGE_API_URL)"; } else if (agentsManageApiUrl === "http://localhost:3002" && !configFile) { agentsManageApiUrlSource = "default value"; } if (agentsRunApiUrlFlag) { agentsRunApiUrlSource = "command-line flag (--agents-run-api-url)"; } else if (process.env.INKEEP_AGENTS_RUN_API_URL === agentsRunApiUrl) { agentsRunApiUrlSource = "environment variable (INKEEP_AGENTS_RUN_API_URL)"; } else if (agentsRunApiUrl === "http://localhost:3003" && !configFile) { agentsRunApiUrlSource = "default value"; } const sources = { tenantId: `config file (${configFile})`, projectId: config.projectId ? configFile ? `config file (${configFile})` : "config" : "default", agentsManageApiUrl: agentsManageApiUrlSource, agentsRunApiUrl: agentsRunApiUrlSource, configFile: configFile || void 0 }; return { tenantId, projectId, agentsManageApiUrl, agentsRunApiUrl, manageUiUrl: config.manageUiUrl, modelSettings: config.modelSettings || void 0, sources }; } var init_config = __esm({ "src/utils/config.ts"() { "use strict"; init_esm_shims(); init_tsx_loader(); dotenv.config({ quiet: true }); } }); // src/api.ts var BaseApiClient, ManagementApiClient, ExecutionApiClient; var init_api = __esm({ "src/api.ts"() { "use strict"; init_esm_shims(); init_config(); BaseApiClient = class { apiUrl; tenantId; projectId; constructor(apiUrl, tenantId, projectId) { this.apiUrl = apiUrl; this.tenantId = tenantId; this.projectId = projectId; } checkTenantId() { if (!this.tenantId) { throw new Error("No tenant ID configured. Please run: inkeep init"); } return this.tenantId; } getTenantId() { return this.tenantId; } getProjectId() { return this.projectId; } getApiUrl() { return this.apiUrl; } }; ManagementApiClient = class _ManagementApiClient extends BaseApiClient { constructor(apiUrl, tenantId, projectId) { super(apiUrl, tenantId, projectId); } static async create(apiUrl, configPath, tenantIdOverride, projectIdOverride) { const resolvedApiUrl = await getAgentsManageApiUrl(apiUrl, configPath); const tenantId = tenantIdOverride || await getTenantId(configPath); const projectId = projectIdOverride || await getProjectId(configPath); return new _ManagementApiClient(resolvedApiUrl, tenantId, projectId); } async listGraphs() { const tenantId = this.checkTenantId(); const projectId = this.getProjectId(); const response = await fetch( `${this.apiUrl}/tenants/${tenantId}/crud/projects/${projectId}/agent-graphs`, { method: "GET", headers: { "Content-Type": "application/json", ...process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET && { Authorization: `Bearer ${process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET}` } } } ); if (!response.ok) { throw new Error(`Failed to list graphs: ${response.statusText}`); } const data = await response.json(); return data.data || []; } async getGraph(graphId) { const graphs = await this.listGraphs(); const graph = graphs.find((g) => g.id === graphId); return graph || null; } async pushGraph(graphDefinition) { const tenantId = this.checkTenantId(); const projectId = this.getProjectId(); graphDefinition.tenantId = tenantId; const graphId = graphDefinition.id; if (!graphId) { throw new Error("Graph must have an id property"); } const response = await fetch( `${this.apiUrl}/tenants/${tenantId}/crud/projects/${projectId}/graph/${graphId}`, { method: "PUT", headers: { "Content-Type": "application/json", ...process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET && { Authorization: `Bearer ${process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET}` } }, body: JSON.stringify(graphDefinition) } ); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to push graph: ${response.statusText} ${errorText}`); } const data = await response.json(); return data.data; } }; ExecutionApiClient = class _ExecutionApiClient extends BaseApiClient { constructor(apiUrl, tenantId, projectId) { super(apiUrl, tenantId, projectId); } static async create(apiUrl, configPath, tenantIdOverride, projectIdOverride) { const resolvedApiUrl = await getAgentsRunApiUrl(apiUrl, configPath); const tenantId = tenantIdOverride || await getTenantId(configPath); const projectId = projectIdOverride || await getProjectId(configPath); return new _ExecutionApiClient(resolvedApiUrl, tenantId, projectId); } async chatCompletion(graphId, messages, conversationId) { const response = await fetch(`${this.apiUrl}/v1/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "text/event-stream", ...process.env.INKEEP_AGENTS_RUN_API_BYPASS_SECRET && { Authorization: `Bearer ${process.env.INKEEP_AGENTS_RUN_API_BYPASS_SECRET}` }, "x-inkeep-tenant-id": this.tenantId || "test-tenant-id", "x-inkeep-project-id": this.projectId, "x-inkeep-graph-id": graphId }, body: JSON.stringify({ model: "gpt-4o-mini", // Required but will be overridden by graph config messages, conversationId, stream: true }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Chat request failed: ${response.statusText} ${errorText}`); } const contentType = response.headers.get("content-type"); if (contentType?.includes("text/event-stream")) { if (!response.body) { throw new Error("No response body for streaming request"); } return response.body; } else { const data = await response.json(); return data.choices?.[0]?.message?.content || data.result || ""; } } }; } }); // src/commands/chat-enhanced.ts var chat_enhanced_exports = {}; __export(chat_enhanced_exports, { chatCommandEnhanced: () => chatCommandEnhanced }); import * as readline from "readline"; import chalk7 from "chalk"; import inquirer3 from "inquirer"; import ora5 from "ora"; async function chatCommandEnhanced(graphIdInput, options) { let config; try { config = await validateConfiguration( options?.tenantId, options?.agentsManageApiUrl, options?.agentsRunApiUrl, options?.configFilePath ); } catch (error) { console.error(chalk7.red(error.message)); process.exit(1); } console.log(chalk7.gray("Using configuration:")); console.log(chalk7.gray(` \u2022 Tenant ID: ${config.sources.tenantId}`)); console.log(chalk7.gray(` \u2022 Management API: ${config.sources.agentsManageApiUrl}`)); console.log(chalk7.gray(` \u2022 Execution API: ${config.sources.agentsRunApiUrl}`)); console.log(); const managementApi = await ManagementApiClient.create( config.agentsManageApiUrl, options?.configFilePath, config.tenantId ); const executionApi = await ExecutionApiClient.create( config.agentsRunApiUrl, options?.configFilePath, config.tenantId ); let graphId = graphIdInput; if (!graphId) { const spinner2 = ora5("Fetching available graphs...").start(); try { const graphs = await managementApi.listGraphs(); spinner2.stop(); if (graphs.length === 0) { console.error( chalk7.red("No graphs available. Define graphs in your project and run: inkeep push") ); process.exit(1); } const graphChoices = graphs.map((g) => ({ name: `${chalk7.cyan(g.id)} - ${g.name || "Unnamed Graph"}`, value: g.id, short: g.id, searchText: `${g.id} ${g.name || ""}`.toLowerCase() })); const answer = await inquirer3.prompt([ { type: "list", name: "graphId", message: "Select a graph to chat with:", choices: graphChoices, pageSize: 10 } ]); graphId = answer.graphId; } catch (error) { spinner2.fail("Failed to fetch graphs"); console.error(chalk7.red("Error:"), error instanceof Error ? error.message : error); process.exit(1); } } const spinner = ora5("Connecting to graph...").start(); try { if (!graphId) { throw new Error("No graph selected"); } const graph = await managementApi.getGraph(graphId); if (!graph) { spinner.fail(`Graph "${graphId}" not found`); const graphs = await managementApi.listGraphs(); if (graphs.length > 0) { console.log(chalk7.yellow("\nAvailable graphs:")); graphs.forEach((g) => { console.log(chalk7.gray(` \u2022 ${g.id} - ${g.name || "Unnamed"}`)); }); console.log(chalk7.gray('\nRun "inkeep chat" without arguments for interactive selection')); } else { console.log(chalk7.yellow("\nNo graphs found. Please define graphs and push your project.")); } process.exit(1); } spinner.succeed(`Connected to graph: ${chalk7.green(graph.name || graphId)}`); if (graph.description) { console.log(chalk7.gray(`Description: ${graph.description}`)); } if (graph.defaultAgentId || graph.default_agent_id) { console.log(chalk7.gray(`Default Agent: ${graph.defaultAgentId || graph.default_agent_id}`)); } } catch (error) { spinner.fail("Failed to connect to graph"); console.error(chalk7.red("Error:"), error instanceof Error ? error.message : error); process.exit(1); } const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: chalk7.cyan("You> ") }); const conversationId = `cli-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const messages = []; let debugMode = false; console.log(chalk7.gray('\n\u{1F4AC} Chat session started. Type "exit" or press Ctrl+C to quit.')); console.log(chalk7.gray("Commands: help, clear, history, reset, debug\n")); async function handleStreamingResponse(stream, showDebug = false) { const decoder = new TextDecoder(); const reader = stream.getReader(); let buffer = ""; let responseContent = ""; const debugOperations = []; let hasStartedResponse = false; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { if (line.startsWith("data: ")) { const data = line.slice(6); if (data === "[DONE]") continue; try { const parsed = JSON.parse(data); const content = parsed.choices?.[0]?.delta?.content; if (content) { let currentPos = 0; while (currentPos < content.length) { if (content.substring(currentPos).startsWith('{"type":"data-operation"')) { let braceCount = 0; let jsonEnd = currentPos; for (let i = currentPos; i < content.length; i++) { if (content[i] === "{") braceCount++; if (content[i] === "}") { braceCount--; if (braceCount === 0) { jsonEnd = i + 1; break; } } } const jsonStr = content.substring(currentPos, jsonEnd); try { const dataOp = JSON.parse(jsonStr); debugOperations.push(dataOp); if (showDebug && dataOp.type === "data-operation") { const opType = dataOp.data?.type || "unknown"; const ctx = dataOp.data?.ctx || {}; let ctxDisplay = ""; if (opType === "agent_thinking" || opType === "iteration_start") { ctxDisplay = ctx.agent || JSON.stringify(ctx); } else if (opType === "task_creation") { ctxDisplay = `agent: ${ctx.agent}`; } else { ctxDisplay = JSON.stringify(ctx); } if (opType === "completion" && hasStartedResponse) { console.log(""); } console.log(chalk7.gray(` [${opType}] ${ctxDisplay}`)); } currentPos = jsonEnd; } catch { if (!hasStartedResponse) { process.stdout.write(chalk7.green("Assistant> ")); hasStartedResponse = true; } process.stdout.write(content[currentPos]); responseContent += content[currentPos]; currentPos++; } } else { if (!hasStartedResponse) { process.stdout.write(chalk7.green("Assistant> ")); hasStartedResponse = true; } process.stdout.write(content[currentPos]); responseContent += content[currentPos]; currentPos++; } } } } catch { } } } } } finally { reader.releaseLock(); } if (hasStartedResponse) { console.log("\n"); } else { console.log(`${chalk7.green("Assistant> ") + chalk7.gray("(no response)")} `); } return responseContent; } rl.on("line", async (input) => { const trimmedInput = input.trim(); const command = trimmedInput.toLowerCase().replace(/^\//, ""); if (command === "exit") { console.log(chalk7.gray("Goodbye! \u{1F44B}")); rl.close(); process.exit(0); } if (command === "clear") { console.clear(); console.log(chalk7.gray("Screen cleared. Conversation context preserved.\n")); rl.prompt(); return; } if (command === "help") { console.log(chalk7.cyan("\n\u{1F4DA} Available commands:")); console.log(chalk7.gray(" \u2022 exit - End the chat session")); console.log(chalk7.gray(" \u2022 clear - Clear the screen (preserves context)")); console.log(chalk7.gray(" \u2022 history - Show conversation history")); console.log(chalk7.gray(" \u2022 reset - Reset conversation context")); console.log(chalk7.gray(" \u2022 debug - Toggle debug mode (show/hide data operations)")); console.log(chalk7.gray(" \u2022 help - Show this help message")); console.log(chalk7.gray("\n Commands can be prefixed with / (e.g., /help)\n")); rl.prompt(); return; } if (command === "debug") { debugMode = !debugMode; console.log(chalk7.yellow(` \u{1F527} Debug mode: ${debugMode ? "ON" : "OFF"}`)); if (debugMode) { console.log(chalk7.gray("Data operations will be shown during responses.\n")); } else { console.log(chalk7.gray("Data operations are hidden.\n")); } rl.prompt(); return; } if (command === "history") { console.log(chalk7.cyan("\n\u{1F4DC} Conversation History:")); if (messages.length === 0) { console.log(chalk7.gray(" (No messages yet)\n")); } else { messages.forEach((msg, idx) => { const role = msg.role === "user" ? chalk7.blue("You") : chalk7.green("Assistant"); const preview = msg.content.substring(0, 100); const suffix = msg.content.length > 100 ? "..." : ""; console.log(` ${idx + 1}. ${role}: ${preview}${suffix}`); }); console.log(); } rl.prompt(); return; } if (command === "reset") { messages.length = 0; console.log(chalk7.yellow("\u26A0\uFE0F Conversation context has been reset.\n")); rl.prompt(); return; } if (!trimmedInput) { rl.prompt(); return; } messages.push({ role: "user", content: trimmedInput }); try { if (!graphId) throw new Error("No graph selected"); const response = await executionApi.chatCompletion(graphId, messages, conversationId); let assistantResponse; if (typeof response === "string") { console.log(chalk7.green("Assistant>"), response); assistantResponse = response; } else { assistantResponse = await handleStreamingResponse(response, debugMode); } messages.push({ role: "assistant", content: assistantResponse }); } catch (error) { console.error(chalk7.red("Error:"), error instanceof Error ? error.message : error); } rl.prompt(); }); rl.on("close", () => { console.log(chalk7.gray("\n\u{1F4CA} Session Summary:")); console.log(chalk7.gray(` \u2022 Graph: ${graphId}`)); console.log(chalk7.gray(` \u2022 Messages: ${messages.length}`)); console.log(chalk7.gray(` \u2022 Duration: ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}`)); console.log(chalk7.gray("\nChat session ended.")); process.exit(0); }); rl.prompt(); } var init_chat_enhanced = __esm({ "src/commands/chat-enhanced.ts"() { "use strict"; init_esm_shims(); init_api(); init_config(); } }); // src/index.ts init_esm_shims(); import { readFileSync as readFileSync3 } from "fs"; import { dirname as dirname4, join as join10 } from "path"; import { fileURLToPath as fileURLToPath2 } from "url"; import { Command } from "commander"; // src/commands/config.ts init_esm_shims(); import { existsSync, readFileSync, writeFileSync } from "fs"; import { join } from "path"; import chalk from "chalk"; async function configGetCommand(key, options) { const configPath = options?.configFilePath || join(process.cwd(), "inkeep.config.ts"); if (!existsSync(configPath)) { console.error(chalk.red("No configuration file found.")); console.log( chalk.gray( 'Run "inkeep init" to create one, or specify a config file with --config-file-path' ) ); process.exit(1); } try { const content = readFileSync(configPath, "utf-8"); const tenantIdMatch = content.match(/tenantId:\s*['"]([^'"]+)['"]/); const apiUrlMatch = content.match(/apiUrl:\s*['"]([^'"]+)['"]/); const config = { tenantId: tenantIdMatch ? tenantIdMatch[1] : void 0, apiUrl: apiUrlMatch ? apiUrlMatch[1] : void 0 }; if (key) { const value = config[key]; if (value !== void 0) { console.log(value); } else { console.error(chalk.red(`Unknown configuration key: ${key}`)); console.log(chalk.gray("Available keys: tenantId, apiUrl")); process.exit(1); } } else { console.log(chalk.cyan("Current configuration:")); console.log(chalk.gray(" Config file:"), configPath); console.log(chalk.gray(" Tenant ID:"), config.tenantId || chalk.yellow("(not set)")); console.log(chalk.gray(" API URL:"), config.apiUrl || chalk.yellow("(not set)")); } } catch (error) { console.error(chalk.red("Failed to read configuration:"), error); process.exit(1); } } async function configSetCommand(key, value, options) { const configPath = options?.configFilePath || join(process.cwd(), "inkeep.config.ts"); if (!["tenantId", "apiUrl"].includes(key)) { console.error(chalk.red(`Invalid configuration key: ${key}`)); console.log(chalk.gray("Available keys: tenantId, apiUrl")); process.exit(1); } if (key === "apiUrl") { try { new URL(value); } catch { console.error(chalk.red("Invalid URL format")); process.exit(1); } } if (!existsSync(configPath)) { const configContent = `import { defineConfig } from '@inkeep/agents-cli'; export default defineConfig({ tenantId: '${key === "tenantId" ? value : ""}', projectId: '${key === "projectId" ? value : "default"}', apiUrl: '${key === "apiUrl" ? value : "http://localhost:3002"}', }); `; try { writeFileSync(configPath, configContent); console.log(chalk.green("\u2713"), `Created config file and set ${key} to:`, chalk.cyan(value)); } catch (error) { console.error(chalk.red("Failed to create config file:"), error); process.exit(1); } } else { try { let content = readFileSync(configPath, "utf-8"); if (key === "tenantId") { if (content.includes("tenantId:")) { content = content.replace(/tenantId:\s*['"][^'"]*['"]/, `tenantId: '${value}'`); } else { content = content.replace( /defineConfig\s*\(\s*{/, `defineConfig({ tenantId: '${value}',` ); } } else if (key === "projectId") { if (content.includes("projectId:")) { content = content.replace(/projectId:\s*['"][^'"]*['"]/, `projectId: '${value}'`); } else { content = content.replace( /(tenantId:\s*['"][^'"]*['"]),?/, `$1, projectId: '${value}',` ); } } else if (key === "apiUrl") { if (content.includes("apiUrl:")) { content = content.replace(/apiUrl:\s*['"][^'"]*['"]/, `apiUrl: '${value}'`); } else { content = content.replace( /defineConfig\s*\(\s*{/, `defineConfig({ apiUrl: '${value}',` ); } } writeFileSync(configPath, content); console.log(chalk.green("\u2713"), `Updated ${key} to:`, chalk.cyan(value)); } catch (error) { console.error(chalk.red("Failed to update config file:"), error); process.exit(1); } } } async function configListCommand(options) { await configGetCommand(void 0, options); } // src/commands/dev.ts init_esm_shims(); import { fork } from "child_process"; import { existsSync as existsSync2 } from "fs"; import { createRequire } from "module"; import { dirname, join as join2 } from "path"; import chalk2 from "chalk"; import ora from "ora"; var require2 = createRequire(import.meta.url); function resolveWebRuntime() { try { const pkg = require2.resolve("@inkeep/agents-manage-ui/package.json"); const root = dirname(pkg); return join2(root, ".next/standalone/agents-manage-ui"); } catch (err) { throw new Error(`Could not find @inkeep/agents-manage-ui package. ${err}`); } } function startWebApp({ port = 3e3, host = "localhost" }) { const spinner = ora("Starting dashboard server...").start(); try { const rt = resolveWebRuntime(); const entry = join2(rt, "server.js"); console.log(entry); if (!existsSync2(entry)) { spinner.fail("Dashboard server not found"); console.error( chalk2.red("The dashboard server has not been built yet. Please run the following commands:") ); console.error(chalk2.yellow(" cd agents-manage-ui")); console.error(chalk2.yellow(" pnpm build")); console.error(chalk2.yellow(" pnpm start")); process.exit(1); } spinner.succeed("Starting dashboard server..."); const child = fork(entry, [], { cwd: rt, env: { ...process.env, NODE_ENV: "production", PORT: String(port), HOSTNAME: host }, stdio: "inherit" }); console.log(chalk2.green(`\u{1F680} Dashboard server started at http://${host}:${port}`)); console.log(chalk2.gray("Press Ctrl+C to stop the server")); process.on("SIGINT", () => { console.log(chalk2.yellow("\n\u{1F6D1} Stopping dashboard server...")); child.kill("SIGINT"); process.exit(0); }); process.on("SIGTERM", () => { child.kill("SIGTERM"); process.exit(0); }); return child; } catch (error) { spinner.fail("Failed to start dashboard server"); console.error(chalk2.red("Error:"), error instanceof Error ? error.message : "Unknown error"); process.exit(1); } } async function devCommand(options) { const { port = 3e3, host = "localhost" } = options; console.log(chalk2.blue("Inkeep Dashboard Server")); console.log(chalk2.gray(`Starting server on ${host}:${port}`)); console.log(""); startWebApp({ port, host }); } // src/commands/init.ts init_esm_shims(); import { existsSync as existsSync3, readdirSync, writeFileSync as writeFileSync2 } from "fs"; import { basename, dirname as dirname2, join as join3, resolve } from "path"; import chalk3 from "chalk"; import inquirer2 from "inquirer"; // src/utils/model-config.ts init_esm_shims(); import inquirer from "inquirer"; async function promptForModelConfiguration() { const { providers } = await inquirer.prompt([ { type: "checkbox", name: "providers", message: "Which AI providers would you like to configure?", choices: [ { name: "Anthropic (Claude)", value: "anthropic" }, { name: "OpenAI (GPT)", value: "openai" } ], validate: (input) => { if (input.length === 0) { return "Please select at least one provider"; } return true; } } ]); const anthropicModels = [ { name: "Claude Opus 4.1", value: "anthropic/claude-opus-4-1-20250805" }, { name: "Claude Sonnet 4", value: "anthropic/claude-sonnet-4-20250514" } ]; const openaiModels = [ { name: "GPT-5", value: "openai/gpt-5-2025-08-07" }, { name: "GPT-5 Mini", value: "openai/gpt-5-mini-2025-08-07" }, { name: "GPT-5 Nano", value: "openai/gpt-5-nano-2025-08-07" }, { name: "GPT-4.1", value: "openai/gpt-4.1-2025-04-14" }, { name: "GPT-4.1 Mini", value: "openai/gpt-4.1-mini-2025-04-14" }, { name: "GPT-4.1 Nano", value: "openai/gpt-4.1-nano-2025-04-14" } ]; const availableModels = []; if (providers.includes("anthropic")) { availableModels.push(...anthropicModels); } if (providers.includes("openai")) { availableModels.push(...openaiModels); } const modelAnswers = await inquirer.prompt([ { type: "list", name: "baseModel", message: "Select your default model for general tasks (required):", choices: availableModels }, { type: "confirm", name: "configureOptionalModels", message: "Would you like to configure optional models for structured output and summaries?", default: false } ]); let optionalModels = {}; if (modelAnswers.configureOptionalModels) { const optionalChoices = [...availableModels, { name: "Use base model", value: null }]; optionalModels = await inquirer.prompt([ { type: "list", name: "structuredOutputModel", message: "Select your model for structured output tasks (or use base model):", choices: optionalChoices }, { type: "list", name: "summarizerModel", message: "Select your model for summaries and quick tasks (or use base model):", choices: optionalChoices } ]); } const modelSettings = { base: { model: modelAnswers.baseModel } }; if (optionalModels.structuredOutputModel) { modelSettings.structuredOutput = { model: optionalModels.structuredOutputModel }; } if (optionalModels.summarizerModel) { modelSettings.summarizer = { model: optionalModels.summarizerModel }; } return { modelSettings }; } // src/commands/init.ts function findProjectRoot(startPath) { let currentPath = resolve(startPath); const root = dirname2(currentPath); const rootIndicators = [ "package.json", ".git", ".gitignore", "tsconfig.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml" ]; while (currentPath !== root) { const files = readdirSync(currentPath); if (rootIndicators.some((indicator) => files.includes(indicator))) { return currentPath; } const parentPath = dirname2(currentPath); if (parentPath === currentPath) { break; } currentPath = parentPath; } return startPath; } async function initCommand(options) { let configPath; if (options?.path) { const resolvedPath = resolve(process.cwd(), options.path); if (options.path.endsWith(".ts") || options.path.endsWith(".js")) { configPath = resolvedPath; } else { configPath = join3(resolvedPath, "inkeep.config.ts"); } } else { const projectRoot = findProjectRoot(process.cwd()); const suggestedPath = join3(projectRoot, "inkeep.config.ts"); if (options?.interactive === false) { configPath = suggestedPath; } else { const { confirmedPath } = await inquirer2.prompt([ { type: "input", name: "confirmedPath", message: "Where should the config file be created?", default: suggestedPath, validate: (input) => { if (!input || input.trim() === "") { return "Path is required"; } const dir = input.endsWith(".ts") || input.endsWith(".js") ? dirname2(input) : input; const resolvedDir = resolve(process.cwd(), dir); if (!existsSync3(resolvedDir)) { return `Directory does not exist: ${resolvedDir}`; } return true; } } ]); const resolvedPath = resolve(process.cwd(), confirmedPath); configPath = confirmedPath.endsWith(".ts") || confirmedPath.endsWith(".js") ? resolvedPath : join3(resolvedPath, "inkeep.config.ts"); } } if (existsSync3(configPath)) { const { overwrite } = await inquirer2.prompt([ { type: "confirm", name: "overwrite", message: `${basename(configPath)} already exists at this location. Do you want to overwrite it?`, default: false } ]); if (!overwrite) { console.log(chalk3.yellow("Init cancelled.")); return; } } const answers = await inquirer2.prompt([ { type: "input", name: "tenantId", message: "Enter your tenant ID:", validate: (input) => { if (!input || input.trim() === "") { return "Tenant ID is required"; } return true; } }, { type: "input", name: "projectId", message: "Enter your project ID:", default: "default", validate: (input) => { if (!input || input.trim() === "") { return "Project ID is required"; } return true; } }, { type: "input", name: "apiUrl", message: "Enter the API URL:", default: "http://localhost:3002", validate: (input) => { try { new URL(input); return true; } catch { return "Please enter a valid URL"; } } } ]); const { modelSettings } = await promptForModelConfiguration(); const configContent = `import { defineConfig } from '@inkeep/agents-cli/config'; export default defineConfig({ tenantId: '${answers.tenantId}', projectId: '${answers.projectId}', agentsManageApiUrl: '${answers.apiUrl}', agentsRunApiUrl: '${answers.apiUrl}', modelSettings: ${JSON.stringify(modelSettings, null, 2)}, }); `; try { writeFileSync2(configPath, configContent); console.log(chalk3.green("\u2713"), `Created ${chalk3.cyan(configPath)}`); console.log(chalk3.gray("\nYou can now use the Inkeep CLI commands.")); console.log(chalk3.gray("For example: inkeep list-graphs")); const configDir = dirname2(configPath); if (configDir !== process.cwd()) { console.log(chalk3.gray(` Note: Config file created in ${configDir}`)); console.log( chalk3.gray(`Use --config ${configPath} with commands, or run commands from that directory.`) ); } } catch (error) { console.error(chalk3.red("Failed to create config file:"), error); process.exit(1); } } // src/commands/list-graphs.ts init_esm_shims(); init_api(); init_config(); import chalk4 from "chalk"; import Table from "cli-table3"; import ora2 from "ora"; async function listGraphsCommand(options) { let config; try { config = await validateConfiguration( options.tenantId, options.agentsManageApiUrl, void 0, // agentsRunApiUrl not needed for list-graphs options.configFilePath ); } catch (error) { console.error(chalk4.red(error.message)); process.exit(1); } console.log(chalk4.gray("Using configuration:")); console.log(chalk4.gray(` \u2022 Tenant ID: ${config.sources.tenantId}`)); console.log(chalk4.gray(` \u2022 API URL: ${config.sources.agentsManageApiUrl}`)); console.log(); const api = await ManagementApiClient.create( config.agentsManageApiUrl, options.configFilePath, config.tenantId ); const spinner = ora2("Fetching graphs...").start(); try { const graphs = await api.listGraphs(); spinner.succeed(`Found ${graphs.length} graph(s)`); if (graphs.length === 0) { console.log( chalk4.gray("No graphs found. Define graphs in your project and run: inkeep push") ); return; } const table = new Table({ head: [ chalk4.cyan("Graph ID"), chalk4.cyan("Name"), chalk4.cyan("Default Agent"), chalk4.cyan("Created") ], style: { head: [], border: [] } }); for (const graph of graphs) { const createdDate = graph.createdAt ? new Date(graph.createdAt).toLocaleDateString() : "Unknown"; table.push([ graph.id || "", graph.name || graph.id || "", graph.defaultAgentId || chalk4.gray("None"), createdDate ]); } console.log(` ${table.toString()}`); } catch (error) { spinner.fail("Failed to fetch graphs"); console.error(chalk4.red("Error:"), error instanceof Error ? error.message : error); process.exit(1); } } // src/commands/pull.ts init_esm_shims(); init_tsx_loader(); import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "fs"; import { join as join7 } from "path"; import chalk5 from "chalk"; import ora3 from "ora"; // src/utils/project-directory.ts init_esm_shims(); import { existsSync as existsSync5 } from "fs"; import { join as join5, resolve as resolve3 } from "path"; import { findUp } from "find-up"; async function findProjectDirectory(projectId) { if (projectId) { if (projectId.includes("/") || projectId.includes("\\")) { const projectPath = resolve3(process.cwd(), projectId); if (existsSync5(join5(projectPath, "inkeep.config.ts"))) { return projectPath; } } else { const projectPath = join5(process.cwd(), projectId); if (existsSync5(join5(projectPath, "inkeep.config.ts"))) { return projectPath; } } return null; } const configPath = await findUp("inkeep.config.ts"); if (configPath) { return resolve3(configPath, ".."); } return null; } // src/utils/file-finder.ts init_esm_shims(); import { existsSync as existsSync6, readdirSync as readdirSync2, statSync } from "fs"; import { join as join6, relative } from "path"; function findAllTypeScriptFiles(rootDir, excludeDirs = []) { const tsFiles = []; function scanDirectory(dir) { if (!existsSync6(dir)) { return; } const items = readdirSync2(dir); for (const item of items) { const fullPath = join6(dir, item); const relativePath = relative(rootDir, fullPath); const isExcludedDir = excludeDirs.some( (excludeDir) => relativePath === excludeDir || relativePath.startsWith(`${excludeDir}/`) ); if (isExcludedDir) { continue; } const stat = statSync(fullPath); if (stat.isDirectory()) { scanDirectory(fullPath); } else if (stat.isFile() && item.endsWith(".ts")) { tsFiles.push(fullPath); } } } scanDirectory(rootDir); return tsFiles.sort(); } function categorizeTypeScriptFiles(files, rootDir) { const indexFile = files.find((file) => file.endsWith("/index.ts") || file === join6(rootDir, "index.ts")) || null; const configFiles = []; const graphFiles = []; const agentFiles = []; const toolFiles = []; const otherFiles = []; for (const file of files) { const fileName = file.split("/").pop() || ""; const relativePath = relative(rootDir, file); if (file === indexFile) { continue; } if (fileName.includes(".config.") || fileName.includes(".env.") || fileName === "inkeep.config.ts") { configFiles.push(file); } else if (fileName.includes(".graph.") || relativePath.includes("graphs/")) { graphFiles.push(file); } else if (fileName.includes("agent") || fileName.includes("Agent") || relativePath.includes("agents/")) { agentFiles.push(file); } else if (fileName.includes("tool") || fileName.includes("Tool") || relativePath.includes("tools/")) { toolFiles.push(file); } else { otherFiles.push(file); } } return { indexFile, configFiles, graphFiles, agentFiles, toolFiles, otherFiles }; } // src/commands/pull.llm-generate.ts init_esm_shims(); import { anthropic, createAnthropic } from "@ai-sdk/anthropic"; import { createOpenAI, openai } from "@ai-sdk/openai"; import { generateText } from "ai"; function createModel(config) { if (!config.model) { throw new Error("Model configuration is required for pull command"); } const modelString = config.model; const providerOptions = config.providerOptions; const { provider, modelName } = parseModelString(modelString); switch (provider) { case "anthropic": if (providerOptions) { const provider2 = createAnthropic(providerOptions); return provider2(modelName); } return anthropic(modelName); case "openai": if (providerOptions) { const provider2 = createOpenAI(providerOptions); return provider2(modelName); } return openai(modelName); default: throw new Error(`Unsupported provider: ${provider}`); } } function parseModelString(modelString) { if (modelString.includes("/")) { const [provider, ...modelParts] = modelString.split("/"); return { provider: provider.toLowerCase(), modelName: modelParts.join("/") }; } return { provider: "anthropic", modelName: modelString }; } async function generateTypeScriptFileWithLLM(graphData, graphId, outputFilePath, modelSettings, retryContext) { const fs = await import("fs"); let existingContent = ""; let fileExists = false; try { existingContent = fs.readFileSync(outputFilePath, "utf-8"); fileExists = true; } catch { fileExists = false; } const model = createModel(modelSettings); const prompt = createPrompt(graphData, graphId, existingContent, fileExists, retryContext); try { const { text } = await generateText({ model, prompt, temperature: 0.1, // Low temperature for consistent code generation maxOutputTokens: 16e3 // Increased to handle large TypeScript files }); fs.writeFileSync(outputFilePath, text, "utf-8"); console.log(`\u2705 Successfully generated TypeScript file: ${outputFilePath}`); } catch (error) { console.error("\u274C Error generating TypeScript file with LLM:", error); throw error; } } function createPrompt(graphData, graphId, existingContent