@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
997 lines (995 loc) • 34.6 kB
JavaScript
// @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
};