UNPKG

@botpress/adk-cli

Version:

Command-line interface for the Botpress Agent Development Kit (ADK)

997 lines (995 loc) 34.6 kB
// @bun import { AgentProjectPatcher } from "./chunk-a38y2kh3.js"; import { radioButtons } from "./chunk-vn4dmn3x.js"; import { select } from "./chunk-seyt2a5p.js"; import { adkLogo } from "./chunk-5agyx08n.js"; import { execAsync } from "./chunk-bmbzs4s8.js"; import { userInput } from "./chunk-tefbm840.js"; import"./chunk-bexyahs9.js"; import { ADK_IMPORT_DEPENDENCIES_FILE, archiveDefaultDirectoryName, extractProjectFiles, prepareImportDirectory, readAdkArchive, removeImportDependenciesFile } from "./chunk-73xfqhym.js"; import"./chunk-8n02v208.js"; import { createBot, deleteBot, findProjectNameExactMatch, getBot, getClient, listBots, listWorkspaces, validateBotName, validateWorkspace, writeAgentLink } from "./chunk-33cg4pm3.js"; import { ensureJsonOnlyFormat } from "./chunk-7sfagm12.js"; import"./chunk-tt572q4r.js"; import"./chunk-3ahwp6fe.js"; import { DependencyManager } from "./chunk-5zm2mgt9.js"; import"./chunk-9e2nksab.js"; import { bind, bold, box, fg, getActiveTheme, mount, router, signal, t, text } from "./chunk-m2h26j5f.js"; import"./chunk-8gqzjqmb.js"; import"./chunk-ty7sdgd4.js"; import { PACKAGE_MANAGER_LOCK_FILES, detectPackageManagers, getPreferredPackageManager } from "./chunk-nbasj5jm.js"; import"./chunk-kk3h6qaj.js"; import { createCliLogger } from "./chunk-gzwt1qdr.js"; import { CLI_VERSION, EXPECTED_RUNTIME_VERSION } from "./chunk-nxy2ya5r.js"; import"./chunk-wzj4dc7n.js"; import { AdkError, AgentProject, ConfigWriter, auth } from "./chunk-p0hjqn4r.js"; import"./chunk-np5wcwfv.js"; import"./chunk-dq2xpa24.js"; import"./chunk-6w0knnta.js"; import"./chunk-40x04ckt.js"; import"./chunk-t76d8fxx.js"; import"./chunk-nh2akp42.js"; import"./chunk-0fdvzjbh.js"; import"./chunk-2a5b6azq.js"; import"./chunk-vay209b5.js"; import"./chunk-3xrpxgq4.js"; import"./chunk-rfm3jr1m.js"; import"./chunk-w346ejn9.js"; import"./chunk-knvm2anf.js"; import"./chunk-65h5trb5.js"; import"./chunk-s2akeqpw.js"; import"./chunk-6771vrjp.js"; import"./chunk-g8mm42v1.js"; import"./chunk-50hzjdck.js"; import"./chunk-nn2jb0x0.js"; import"./chunk-v8xvth6j.js"; import"./chunk-kkk13rcb.js"; import"./chunk-ytpp1kam.js"; import"./chunk-na956zz3.js"; import"./chunk-f4bw8q7c.js"; import"./chunk-0v8vgrns.js"; import"./chunk-54qt5g7m.js"; import"./chunk-dhs2bg35.js"; // src/commands/adk-import.ts import { existsSync } from "fs"; import path from "path"; var IMPORT_DEV_BOT_URL = "http://localhost:3000"; function getSetupDropdownMaxItems() { const rows = process.stdout.rows ?? 24; return Math.max(3, Math.min(6, rows - 17)); } async function adkImport(archivePath, directory, options = {}) { ensureJsonOnlyFormat(options.format); const logger = createCliLogger({ format: options.format }); const isJson = options.format === "json"; const archive = await readAdkArchive(archivePath); const destination = path.resolve(process.cwd(), directory ?? archiveDefaultDirectoryName(archive.manifest.projectName)); const credentials = await auth.getActiveCredentials(); const apiUrl = options.apiUrl ?? credentials.apiUrl; if (!apiUrl) { throw new AdkError({ code: "PROFILE_API_URL_MISSING", message: "Selected profile is missing an API URL. Run `adk login` with an API URL before importing.", expected: true }); } if (isJson && !options.workspace) { throw new AdkError({ code: "WORKSPACE_REQUIRED", message: "--format json requires --workspace <workspaceId>.", expected: true }); } const packageManagers = detectPackageManagers(); const availablePackageManagers = packageManagers.filter((pm) => pm.available); if (availablePackageManagers.length === 0) { throw noPackageManagerError(); } if (options.packageManager) { resolvePackageManagerOption(options.packageManager, availablePackageManagers); } const canPrompt = !isJson && process.stdin.isTTY && process.stdout.isTTY; if (canPrompt) { let appRef = null; let importResult; let interactiveError; const app = await mount((renderer, scope) => importSetup(renderer, scope, { archive, destination, credentials, apiUrl, initialWorkspaceId: options.workspace, initialBotId: options.bot, initialDevId: options.dev, initialPackageManager: options.packageManager, packageManagers, force: options.force, onComplete: (result2) => { importResult = result2; setTimeout(() => appRef?.unmount(), 160); }, onError: (message) => { if (!interactiveError) { interactiveError = message; } setTimeout(() => appRef?.unmount(), 160); } }), { exitOnCtrlC: true, screenMode: "alternate-screen" }); appRef = app; await app.waitUntilExit(); if (interactiveError) { logger.error(`Error: ${interactiveError}`); process.exitCode = 1; return; } if (importResult) { logger.info(`Imported ${importResult.projectName} into ${importResult.directory}`); } return; } const target = await resolveImportTarget({ requestedWorkspaceId: options.workspace, requestedBotId: options.bot, requestedDevId: options.dev, credentials, apiUrl, defaultName: archive.manifest.projectName }); if (!options.force) { throw new AdkError({ code: "IMPORT_CONFIRMATION_REQUIRED", message: "Import writes links and applies dependency snapshots. Pass --force to confirm in non-interactive mode.", expected: true }); } const result = await executeImport({ archive, destination, credentials, apiUrl, workspace: target.workspace, botName: target.botName, prodBot: target.prodBot, devBot: target.devBot, requestedPackageManager: options.packageManager, packageManagers }); logger.info(`Imported ${archive.manifest.projectName} into ${destination}`).result(result); } async function resolveImportTarget(options) { let workspace; let prodBot; if (options.requestedBotId) { const bot = await getBot(options.requestedBotId, options.requestedWorkspaceId, options.apiUrl, options.credentials); assertProdBotTarget(bot); prodBot = bot; workspace = await validateWorkspace(bot.workspaceId, options.apiUrl, options.credentials); } else { workspace = await resolveDestinationWorkspace({ requestedWorkspaceId: options.requestedWorkspaceId, credentials: options.credentials, apiUrl: options.apiUrl }); } let devBot; if (options.requestedDevId) { const bot = await getBot(options.requestedDevId, workspace.id, options.apiUrl, options.credentials); assertDevBotTarget(bot); devBot = bot; } return { workspace, botName: await resolveBotName({ defaultName: prodBot?.name ?? options.defaultName }), prodBot, devBot }; } async function resolveDestinationWorkspace(options) { if (options.requestedWorkspaceId) { return await validateWorkspace(options.requestedWorkspaceId, options.apiUrl, options.credentials); } const workspaces = await listWorkspaces(options.apiUrl, options.credentials); if (workspaces.length === 0) { throw new AdkError({ code: "WORKSPACE_NOT_FOUND", message: "No workspaces are available for the selected profile.", expected: true }); } if (workspaces.length === 1) { return workspaces[0]; } throw new AdkError({ code: "WORKSPACE_REQUIRED", message: "Multiple workspaces are available. Pass --workspace <workspaceId> in non-interactive mode.", expected: true }); } async function resolveBotName(options) { return validateImportBotName(options.defaultName); } function importSetup(renderer, scope, deps) { const theme = getActiveTheme(); const view = signal("loading"); const messages = signal([]); const selectionVersion = signal(0); const availablePackageManagers = deps.packageManagers.filter((pm) => pm.available); const singlePackageManager = availablePackageManagers.length === 1 ? availablePackageManagers[0] : null; const archiveLockFile = getArchivePackageManagerLockFile(deps.archive); const archivePreferredPackageManager = archiveLockFile ? getPackageManagerForLockFile(archiveLockFile, availablePackageManagers) : null; let workspaces = []; let selectedWorkspace = null; let bots = []; let selectedProdBot = null; let selectedDevBot = null; let selectedPackageManager = archivePreferredPackageManager ?? (deps.initialPackageManager ? resolvePackageManagerOption(deps.initialPackageManager, availablePackageManagers) : null); let matchedBot = null; let selectedBotName = ""; let botSelectionComplete = false; let importResult; let errorMsg = ""; const maxDropdownItems = getSetupDropdownMaxItems(); const bumpSelectionVersion = () => selectionVersion.set(selectionVersion() + 1); const addMessage = (m) => messages.set([...messages(), m]); const fail = (err) => { errorMsg = err instanceof Error ? err.message : String(err); view.set("error"); deps.onError?.(errorMsg); }; const goToBotSelection = async () => { selectedBotName = selectedProdBot?.name ?? deps.archive.manifest.projectName; if (selectedProdBot) { goToPackageManager(); return; } try { view.set("loading"); bots = await listBots(selectedWorkspace.id, deps.apiUrl, deps.credentials); const exactMatch = findProjectNameExactMatch(bots, deps.archive.manifest.projectName); if (exactMatch) { if (deps.force) { handleBotSelect(exactMatch); return; } matchedBot = exactMatch; view.set("confirm-bot"); return; } view.set("bots"); } catch (err) { fail(err); } }; const chooseWorkspace = async (workspace) => { try { selectedWorkspace = workspace; bumpSelectionVersion(); if (deps.initialDevId && !selectedDevBot) { const devBot = await getBot(deps.initialDevId, workspace.id, deps.apiUrl, deps.credentials); assertDevBotTarget(devBot); selectedDevBot = devBot; bumpSelectionVersion(); } await goToBotSelection(); } catch (err) { fail(err); } }; const handleBotSelect = (bot) => { selectedProdBot = bot; selectedBotName = bot.name; botSelectionComplete = true; bumpSelectionVersion(); goToPackageManager(); }; const handleCreateNewBot = () => { selectedProdBot = null; selectedBotName = deps.archive.manifest.projectName; botSelectionComplete = false; bumpSelectionVersion(); view.set("create-bot"); }; const confirmOrRun = (name) => { selectedBotName = name; botSelectionComplete = true; bumpSelectionVersion(); goToPackageManager(); }; const goToPackageManager = () => { if (availablePackageManagers.length === 0) { fail(noPackageManagerError()); return; } if (!selectedPackageManager) { if (archiveLockFile && !archivePreferredPackageManager) { fail(packageManagerForLockFileUnavailableError(archiveLockFile, availablePackageManagers)); return; } selectedPackageManager = archivePreferredPackageManager; bumpSelectionVersion(); } if (deps.force) { runImport(); return; } if (selectedPackageManager) { view.set("confirm"); return; } if (singlePackageManager) { selectedPackageManager = singlePackageManager; bumpSelectionVersion(); view.set("confirm"); return; } view.set("package-manager"); }; const handlePackageManagerSelect = (value) => { selectedPackageManager = resolvePackageManagerOption(value, availablePackageManagers); bumpSelectionVersion(); if (deps.force) { runImport(); return; } view.set("confirm"); }; const runImport = async () => { if (!selectedWorkspace) { fail(new Error("No destination workspace selected.")); return; } if (!selectedPackageManager) { selectedPackageManager = archivePreferredPackageManager ?? singlePackageManager; bumpSelectionVersion(); } try { view.set("importing"); importResult = await executeImport({ archive: deps.archive, destination: deps.destination, credentials: deps.credentials, apiUrl: deps.apiUrl, workspace: selectedWorkspace, botName: selectedBotName, prodBot: selectedProdBot, devBot: selectedDevBot, packageManager: selectedPackageManager, packageManagers: deps.packageManagers }); view.set("done"); deps.onComplete(importResult); } catch (err) { fail(err); } }; (async () => { try { if (deps.initialWorkspaceId) { const workspace = await validateWorkspace(deps.initialWorkspaceId, deps.apiUrl, deps.credentials); if (deps.initialBotId) { const prodBot = await getBot(deps.initialBotId, workspace.id, deps.apiUrl, deps.credentials); assertProdBotTarget(prodBot); selectedProdBot = prodBot; selectedBotName = prodBot.name; botSelectionComplete = true; bumpSelectionVersion(); } await chooseWorkspace(workspace); return; } if (deps.initialBotId) { const prodBot = await getBot(deps.initialBotId, undefined, deps.apiUrl, deps.credentials); assertProdBotTarget(prodBot); const workspace = await validateWorkspace(prodBot.workspaceId, deps.apiUrl, deps.credentials); selectedProdBot = prodBot; selectedBotName = prodBot.name; botSelectionComplete = true; bumpSelectionVersion(); await chooseWorkspace(workspace); return; } const ws = await listWorkspaces(deps.apiUrl, deps.credentials); if (ws.length === 0) { throw new AdkError({ code: "WORKSPACE_NOT_FOUND", message: "No workspaces are available for the selected profile.", expected: true }); } if (ws.length === 1) { selectedWorkspace = ws[0]; bumpSelectionVersion(); addMessage({ text: `Auto-linking to ${ws[0].name}`, color: theme.status.success }); await chooseWorkspace(ws[0]); return; } workspaces = ws; view.set("workspaces"); } catch (err) { fail(err); } })(); const messagesBox = box(renderer, { flexDirection: "column" }); scope.add(bind(() => { for (const child of [...messagesBox.getChildren()]) { messagesBox.remove(child.id); child.destroyRecursively(); } const msgs = messages(); if (msgs.length > 0) { messagesBox.add(box(renderer, { flexDirection: "column", marginBottom: 1 }, msgs.map((m) => text(renderer, t`${fg(m.color)(m.text)}`)))); } }, [messages])); const selectionsBox = box(renderer, { flexDirection: "column" }); const renderSelectionLine = (label, value) => text(renderer, t`${fg(theme.status.success)(theme.symbols.checkmark)} ${fg(theme.text.dim)(label)} ${fg(theme.text.primary)(value)}`); scope.add(bind(() => { for (const child of [...selectionsBox.getChildren()]) { selectionsBox.remove(child.id); child.destroyRecursively(); } selectionsBox.add(renderSelectionLine("Import", deps.archive.manifest.projectName)); selectionsBox.add(renderSelectionLine("Directory", deps.destination)); if (selectedWorkspace) { selectionsBox.add(renderSelectionLine("Workspace", selectedWorkspace.name ?? selectedWorkspace.id)); } if (selectedProdBot && botSelectionComplete) { selectionsBox.add(renderSelectionLine("Bot", `${selectedProdBot.name} (${selectedProdBot.id})`)); } else if (botSelectionComplete && selectedBotName) { selectionsBox.add(renderSelectionLine("Bot", `${selectedBotName} (new)`)); } if (selectedDevBot && botSelectionComplete) { selectionsBox.add(renderSelectionLine("Dev bot", `${selectedDevBot.name} (${selectedDevBot.id})`)); } else if (botSelectionComplete && deps.archive.dependencyStates.dev) { selectionsBox.add(renderSelectionLine("Dev bot", `${getImportedDevBotName(selectedBotName)} (new)`)); } else if (botSelectionComplete) { selectionsBox.add(renderSelectionLine("Dev bot", "None linked")); } if (selectedPackageManager) { selectionsBox.add(renderSelectionLine("Package manager", selectedPackageManager.name)); } }, [view, selectionVersion])); const body = router(renderer, scope, view, (v, viewScope) => { if (v === "loading") { return box(renderer, { flexDirection: "column" }, [text(renderer, t`${fg(theme.status.info)("Loading\u2026")}`)]); } if (v === "error") { return box(renderer, { flexDirection: "column" }, [ text(renderer, t`${fg(theme.status.error)(`${theme.symbols.cross} ${errorMsg}`)}`), text(renderer, t`${fg(theme.text.dim)(deps.onError ? "Exiting\u2026" : "Press Ctrl-C to exit.")}`) ]); } if (v === "workspaces") { const dropdownOptions = workspaces.map((ws) => ({ id: ws.id, label: `${ws.name} (${ws.handle}) - ${ws.plan || "Free"}`, value: ws.id, description: ws.handle })); return box(renderer, { flexDirection: "column" }, [ userInput(renderer, viewScope, { prompt: "Select a workspace (type to search):", placeholder: "Start typing to search workspaces...", maxDropdownItems, dropdownConfigs: [{ trigger: "", options: dropdownOptions, behavior: "submit" }], onSubmit: (value) => { const workspace = workspaces.find((w) => w.id === value); if (workspace) chooseWorkspace(workspace); }, validate: (value) => workspaces.find((w) => w.id === value) ? null : "Please select a workspace from the dropdown" }) ]); } if (v === "bots") { const norm = deps.archive.manifest.projectName.toLowerCase(); const sortedBots = [...bots].sort((a, b) => { const aMatch = a.name.toLowerCase() === norm ? -1 : 0; const bMatch = b.name.toLowerCase() === norm ? -1 : 0; return aMatch - bMatch; }); const dropdownOptions = [ { id: "create-new", label: "Create a new bot", value: "create-new", description: "Create a new bot in this workspace" }, ...sortedBots.map((bot) => { const shortId = `${bot.id.slice(0, 4)}...${bot.id.slice(-4)}`; const createdAt = new Date(bot.createdAt).toLocaleDateString(); const isMatch = bot.name.toLowerCase() === norm; return { id: bot.id, label: isMatch ? `${theme.symbols.pointer} ${bot.name}` : bot.name, value: bot.id, description: `ID: ${shortId} \u2022 Created: ${createdAt}` }; }) ]; return box(renderer, { flexDirection: "column" }, [ userInput(renderer, viewScope, { prompt: "Select a bot or create a new one (type to search):", placeholder: "Start typing to search bots...", maxDropdownItems, dropdownConfigs: [{ trigger: "", options: dropdownOptions, behavior: "submit" }], onSubmit: (value) => { if (value === "create-new") { handleCreateNewBot(); return; } const bot = bots.find((b) => b.id === value); if (bot) handleBotSelect(bot); }, validate: (value) => value === "create-new" || bots.find((b) => b.id === value) ? null : "Please select a bot from the dropdown", onCancel: () => { if (workspaces.length > 1) view.set("workspaces"); } }) ]); } if (v === "create-bot") { return box(renderer, { flexDirection: "column" }, [ userInput(renderer, viewScope, { prompt: "Enter a name for your new bot:", placeholder: "My awesome bot", initialValue: selectedBotName, onSubmit: (name) => confirmOrRun(name), validate: validateBotName, onCancel: () => view.set("bots") }) ]); } if (v === "confirm-bot") { const bot = matchedBot; return box(renderer, { flexDirection: "column" }, [ text(renderer, t`${fg(theme.text.primary)("Found a bot matching your project name:")}`), text(renderer, t`${fg(theme.accent.cyan)(` Name: ${bot.name}`)}`), text(renderer, t`${fg(theme.text.dim)(` ID: ${bot.id}`)}`), radioButtons(renderer, viewScope, { options: [ { id: "yes", label: "Yes, import into this bot", value: "yes" }, { id: "no", label: "No, create a new bot or import into another bot", value: "no" } ], onSubmit: (value) => { if (value === "yes") handleBotSelect(matchedBot); else view.set("bots"); }, onCancel: () => view.set("bots") }) ]); } if (v === "package-manager") { return box(renderer, { flexDirection: "column" }, [ text(renderer, t`${fg(theme.text.primary)(bold("Select a package manager:"))}`), select(renderer, viewScope, { initialSelected: selectedPackageManager?.command ?? archivePreferredPackageManager?.command, options: availablePackageManagers.map((pm) => ({ id: pm.command, label: pm.name, value: pm.command, description: `Install with ${pm.command}` })), onSubmit: handlePackageManagerSelect, onCancel: () => view.set("bots") }) ]); } if (v === "confirm") { return box(renderer, { flexDirection: "column" }, [ radioButtons(renderer, viewScope, { options: [ { id: "yes", label: "Import and link this project", value: "yes" }, { id: "no", label: "Cancel import", value: "no" } ], onSubmit: (value) => { if (value === "yes") runImport(); else deps.onComplete(); }, onCancel: () => view.set("bots") }) ]); } if (v === "importing") { return box(renderer, { flexDirection: "column" }, [ text(renderer, t`${fg(theme.status.info)("Installing packages, importing project, and linking bots\u2026")}`) ]); } const result = importResult; return box(renderer, { flexDirection: "column" }, [ text(renderer, t`${fg(theme.status.success)(`${theme.symbols.checkmark} Successfully imported ADK project!`)}`), text(renderer, t`${fg(theme.text.dim)(`Directory: ${result?.directory ?? deps.destination}`)}`), text(renderer, t`${fg(theme.text.dim)(`Workspace: ${selectedWorkspace?.name ?? result?.workspaceId}`)}`), text(renderer, t`${fg(theme.text.dim)(`Prod Bot ID: ${result?.botId ?? ""}`)}`), ...result?.devId ? [text(renderer, t`${fg(theme.text.dim)(`Dev Bot ID: ${result.devId}`)}`)] : [], box(renderer, { marginTop: 1 }, [ text(renderer, t`${fg(theme.status.info)("Code was restored and linked. Run 'adk deploy' when ready.")}`) ]) ]); }); return box(renderer, { flexDirection: "column" }, [ box(renderer, { paddingX: 1, paddingY: 1 }, [ adkLogo(renderer, { title: "Botpress ADK", subtitle: `v${CLI_VERSION} \u2022 Import project`, logoColor: theme.accent.purple, titleColor: theme.text.primary, subtitleColor: theme.text.dim }) ]), box(renderer, { paddingX: 1, flexDirection: "column" }, [selectionsBox]), box(renderer, { paddingX: 1, flexDirection: "column" }, [messagesBox]), box(renderer, { marginTop: 1, paddingX: 1, flexDirection: "column" }, [body]) ]); } async function executeImport(options) { await prepareImportDirectory(options.destination); await extractProjectFiles(options.archive, options.destination); const packagePatchRun = await new AgentProjectPatcher({ projectPath: options.destination, fromVersion: options.archive.manifest.adkVersion, toVersion: CLI_VERSION, runtimePackageVersion: EXPECTED_RUNTIME_VERSION, patchIds: ["runtime-package-versions"], runPackageInstall: false }).apply(); assertImportedPackagePatchSucceeded(packagePatchRun); const packageManager = resolveImportPackageManager({ destination: options.destination, archive: options.archive, requestedPackageManager: options.requestedPackageManager, selectedPackageManager: options.packageManager, packageManagers: options.packageManagers }); const packageInstall = await installProjectDependencies(options.destination, packageManager); const project = await AgentProject.load(options.destination, { noCache: true }); let prodBot = options.prodBot ?? undefined; let devBot = options.devBot ?? undefined; const createdBots = []; try { if (!prodBot) { prodBot = await createBot({ workspaceId: options.workspace.id, name: options.botName, apiUrl: options.apiUrl, credentials: options.credentials }); createdBots.push(prodBot); } if (!devBot && options.archive.dependencyStates.dev) { devBot = await createBot({ workspaceId: options.workspace.id, name: getImportedDevBotName(options.botName), apiUrl: options.apiUrl, credentials: options.credentials, dev: true, url: IMPORT_DEV_BOT_URL }); createdBots.push(devBot); } await writeAgentLink({ project, workspaceId: options.workspace.id, botId: prodBot.id, ...devBot ? { devBotId: devBot.id } : {}, botName: options.botName, apiUrl: options.apiUrl, credentials: options.credentials }); } catch (error) { await cleanupUnlinkedImportBots({ bots: createdBots, workspaceId: options.workspace.id, apiUrl: options.apiUrl, credentials: options.credentials }); throw error; } const client = await getClient({ workspaceId: options.workspace.id, apiUrl: options.apiUrl, credentials: options.credentials }); const applyResults = {}; for (const env of ["prod", "dev"]) { const state = options.archive.dependencyStates[env]; if (!state) { continue; } const botId = env === "prod" ? prodBot.id : devBot?.id; if (!botId) { continue; } const dm = new DependencyManager({ projectPath: options.destination, env, client, botId }); applyResults[env] = await dm.applyState(state, { yes: true }); } assertImportedDependenciesApplied(applyResults, options.destination); if (Object.keys(options.archive.dependencyStates).length > 0) { await new ConfigWriter(options.destination).removeDependenciesField(); } await removeImportDependenciesFile(options.destination); return { ok: true, directory: options.destination, projectName: options.archive.manifest.projectName, workspaceId: options.workspace.id, botId: prodBot.id, ...devBot ? { devId: devBot.id } : {}, packageInstall, dependencySnapshots: options.archive.manifest.dependencySnapshots, dependenciesApplied: summarizeApplyResults(applyResults) }; } function isMissingPackageJsonPatchBlock(patch) { return patch.id === "runtime-package-versions" && patch.details?.packageJsonMissing === true; } function assertImportedPackagePatchSucceeded(result) { const failed = result.failed[0]; if (failed) { throw new AdkError({ code: "INVALID_CONFIG", message: `Failed to normalize imported package.json: ${failed.error ?? failed.reason}`, expected: true }); } const blocked = result.blocked.find((patch) => !isMissingPackageJsonPatchBlock(patch)); if (blocked) { throw new AdkError({ code: "INVALID_CONFIG", message: `Invalid package.json in imported project: ${blocked.reason}`, expected: true }); } } async function cleanupUnlinkedImportBots(options) { for (const bot of [...options.bots].reverse()) { try { await deleteBot({ workspaceId: options.workspaceId, botId: bot.id, apiUrl: options.apiUrl, credentials: options.credentials }); } catch {} } } function resolveImportPackageManager(options) { const availablePackageManagers = options.packageManagers.filter((pm) => pm.available); if (availablePackageManagers.length === 0) { throw noPackageManagerError(); } const lockFile = getArchivePackageManagerLockFile(options.archive) ?? getExtractedPackageManagerLockFile(options.destination); if (lockFile) { const packageManager = getPackageManagerForLockFile(lockFile, availablePackageManagers); if (!packageManager) { throw packageManagerForLockFileUnavailableError(lockFile, availablePackageManagers); } return packageManager; } if (options.selectedPackageManager?.available) { return options.selectedPackageManager; } if (options.requestedPackageManager) { return resolvePackageManagerOption(options.requestedPackageManager, availablePackageManagers); } const preferred = getPreferredPackageManager(options.destination, availablePackageManagers); if (!preferred) { throw noPackageManagerError(); } return preferred; } async function installProjectDependencies(projectPath, packageManager) { try { await execAsync(packageManager.installCommand, { cwd: projectPath, env: { ...process.env } }); return { attempted: true, success: true, packageManager: packageManager.command, command: packageManager.installCommand }; } catch (error) { throw new AdkError({ code: "PACKAGE_INSTALL_FAILED", message: `Failed to install dependencies with ${packageManager.command}: ${formatCommandError(error)}. The project was restored to ${projectPath}; delete that directory before retrying the import.`, expected: true, cause: error }); } } function resolvePackageManagerOption(value, availablePackageManagers) { const normalized = value.trim().toLowerCase(); const packageManager = availablePackageManagers.find((pm) => pm.command.toLowerCase() === normalized || pm.name.toLowerCase() === normalized); if (!packageManager) { throw new AdkError({ code: "NO_PACKAGE_MANAGER", message: `Package manager "${value}" is not available. Install it or choose one of: ${availablePackageManagers.map((pm) => pm.command).join(", ")}`, expected: true }); } return packageManager; } function getArchivePackageManagerLockFile(archive) { const rootFiles = new Set(archive.projectFiles.filter((file) => !file.relativePath.includes("/")).map((file) => file.relativePath)); for (const lockFile of PACKAGE_MANAGER_LOCK_FILES) { if (rootFiles.has(lockFile.fileName)) { return lockFile; } } return null; } function getExtractedPackageManagerLockFile(projectPath) { for (const lockFile of PACKAGE_MANAGER_LOCK_FILES) { if (existsSync(path.join(projectPath, lockFile.fileName))) { return lockFile; } } return null; } function getPackageManagerForLockFile(lockFile, availablePackageManagers) { return availablePackageManagers.find((pm) => pm.command === lockFile.command) ?? null; } function packageManagerForLockFileUnavailableError(lockFile, availablePackageManagers) { return new AdkError({ code: "NO_PACKAGE_MANAGER", message: `Archive contains ${lockFile.fileName}, so import must use ${lockFile.command}, but ${lockFile.command} is not available. Available package managers: ${availablePackageManagers.map((pm) => pm.command).join(", ") || "none"}`, expected: true }); } function noPackageManagerError() { return new AdkError({ code: "NO_PACKAGE_MANAGER", message: "No package manager found. Please install npm, pnpm, bun, or yarn.", expected: true }); } function formatCommandError(error) { if (error && typeof error === "object") { const stderr = error.stderr; if (typeof stderr === "string" && stderr.trim()) { return stderr.trim(); } const stdout = error.stdout; if (typeof stdout === "string" && stdout.trim()) { return stdout.trim(); } } return error instanceof Error ? error.message : String(error); } function validateImportBotName(name) { const error = validateBotName(name); if (error) { throw new AdkError({ code: "INVALID_BOT_NAME", message: error, expected: true }); } return name.trim(); } function assertProdBotTarget(bot) { if (bot.dev) { throw new AdkError({ code: "INVALID_IMPORT_TARGET", message: "Cannot import into a dev bot as the production bot. Use --dev for dev bots.", expected: true }); } } function assertDevBotTarget(bot) { if (!bot.dev) { throw new AdkError({ code: "INVALID_IMPORT_TARGET", message: "Cannot import dev dependencies into a production bot. Use a dev bot with --dev.", expected: true }); } } function getImportedDevBotName(botName) { const suffix = " (dev)"; const maxBaseLength = 50 - suffix.length; const base = botName.trim().slice(0, maxBaseLength).trimEnd(); return `${base || "Imported agent"}${suffix}`; } function assertImportedDependenciesApplied(results, destination) { const failures = ["prod", "dev"].map((env) => ({ env, errors: results[env]?.errors ?? [] })).filter(({ errors }) => errors.length > 0); if (failures.length === 0) { return; } const summary = failures.map(({ env, errors }) => `${env}: ${errors.map((error) => `${error.action.type}:${error.action.alias} ${error.message}`).join(", ")}`).join("; "); const dependencyFile = path.join(path.resolve(destination), ADK_IMPORT_DEPENDENCIES_FILE); throw new AdkError({ code: "DEPENDENCY_IMPORT_FAILED", message: `Failed to apply imported dependency snapshots: ${summary}`, suggestion: `Fix the dependency errors and retry from ${dependencyFile}. The import dependency file was not removed.`, expected: true, details: { failures } }); } function summarizeApplyResults(results) { const summary = {}; for (const env of ["prod", "dev"]) { const result = results[env]; if (!result) { continue; } summary[env] = { applied: result.applied.length, skipped: result.skipped.length, errors: result.errors.length }; } return summary; } export { importSetup, adkImport };