@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
343 lines (339 loc) • 12.1 kB
JavaScript
// @bun
import {
findAgentRoot
} from "./chunk-kk3h6qaj.js";
import {
AdkError,
AgentProject,
auth,
getProjectClient
} from "./chunk-p0hjqn4r.js";
import {
Uk
} from "./chunk-3xrpxgq4.js";
// src/utils/command-context.ts
import fs from "fs/promises";
import path from "path";
function normalizeApiUrl(apiUrl) {
return apiUrl.replace(/\/+$/, "");
}
function sameApiUrl(a, b) {
return !!a && !!b && normalizeApiUrl(a) === normalizeApiUrl(b);
}
function createCommandCredentials(credentials, apiUrl, scope = {}) {
return {
token: credentials.token,
apiUrl,
...scope.workspaceId ? { workspaceId: scope.workspaceId } : {},
...scope.botId ? { botId: scope.botId } : {}
};
}
function apiUrlSourceReason(source, apiUrl) {
switch (source.type) {
case "option":
return `${source.option} was set to ${apiUrl}`;
case "project-local":
return `${source.file} overrides this project to ${apiUrl}`;
case "project":
return `${source.file} links this project to ${apiUrl}`;
case "selected-profile":
return `no linked project was found, so this command uses selected profile "${source.profileName}"`;
}
}
function apiUrlSourceLabel(source) {
switch (source.type) {
case "option":
return source.option;
case "project-local":
case "project":
return source.file;
case "selected-profile":
return `selected profile "${source.profileName}"`;
}
}
function profileApiUrlMismatchError(details) {
const profileApiUrlDisplay = details.profileApiUrl ?? "<missing>";
const sourceLabel = apiUrlSourceLabel(details.apiUrlSource);
const targetLabel = details.apiUrlSource.type === "option" ? "This command targets:" : "This project targets:";
const suggestion = `Use --profile <profile-name>, run \`adk profiles set <profile-name>\`, or login with \`adk login --api-url ${details.targetApiUrl} --profile <profile-name>\`.`;
const summary = `Selected profile "${details.profileName}" targets ${profileApiUrlDisplay}, but this command targets ${details.targetApiUrl}.`;
const message = [
"Profile/API URL mismatch",
"",
targetLabel,
` ${details.targetApiUrl}`,
` from ${sourceLabel}`,
"",
`But selected profile "${details.profileName}" targets:`,
` ${profileApiUrlDisplay}`,
"",
`Use a profile for ${details.targetApiUrl}:`,
" adk profiles set <profile-name>",
` adk login --api-url ${details.targetApiUrl} --profile <profile-name>`,
"",
"Or re-run this command with:",
" --profile <profile-name>"
].join(`
`);
const errorDetails = {
profileName: details.profileName,
profileApiUrl: details.profileApiUrl,
targetApiUrl: details.targetApiUrl,
targetReason: details.targetReason,
apiUrlSource: details.apiUrlSource
};
const error = new AdkError({
code: "PROFILE_API_URL_MISMATCH",
message,
expected: true,
details: errorDetails
});
error.json = {
success: false,
error: {
code: "PROFILE_API_URL_MISMATCH",
message: summary,
suggestion,
details: errorDetails
}
};
return error;
}
function missingTargetBotError(target) {
if (target === "dev") {
return new AdkError({
code: "NO_DEV_BOT",
message: "No dev bot ID found in agent.local.json. Run `adk dev` first to deploy a development bot.",
expected: true,
suggestion: "Run `adk dev` first."
});
}
return new AdkError({
code: "BOT_NOT_LINKED",
message: "No botId found in agent.json. Please run `adk link` to link your agent to a bot.",
expected: true,
suggestion: "Run `adk link` first."
});
}
function hasRequirement(requirements, requirement) {
return requirements.has(requirement);
}
function resolveTargetBotId(project, target) {
if (target === "dev") {
return project.agentInfo?.devId;
}
return project.agentInfo?.botId;
}
function profileMatchesCredentials(profile, credentials) {
const profileApiUrl = profile.credentials.apiUrl;
const credentialsApiUrl = credentials.apiUrl;
if (profile.credentials.token !== credentials.token) {
return false;
}
if (profileApiUrl || credentialsApiUrl) {
return sameApiUrl(profileApiUrl, credentialsApiUrl);
}
return true;
}
async function resolveSelectedProfile() {
const credentials = await auth.getActiveCredentials();
const profiles = await auth.listProfiles().catch(() => []);
const profile = profiles.find((candidate) => profileMatchesCredentials(candidate, credentials)) ?? profiles.find((candidate) => candidate.credentials.token === credentials.token);
const profileName = profile?.name ?? await auth.getCurrentProfile().catch(() => "current");
return {
name: profileName,
credentials,
...credentials.apiUrl ? { apiUrl: credentials.apiUrl } : {},
...profile?.email ? { email: profile.email } : {},
...profile?.displayName ? { displayName: profile.displayName } : {},
...profile?.accountId ? { accountId: profile.accountId } : {}
};
}
async function readJsonObject(filePath) {
try {
const parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
} catch {
return;
}
}
async function projectApiUrlSource(agentRoot, apiUrl) {
const localPath = path.join(agentRoot, "agent.local.json");
const local = await readJsonObject(localPath);
if (sameApiUrl(typeof local?.apiUrl === "string" ? local.apiUrl : undefined, apiUrl)) {
return { type: "project-local", file: "agent.local.json", path: localPath };
}
return { type: "project", file: "agent.json", path: path.join(agentRoot, "agent.json") };
}
function validateProfileApiUrl(options) {
if (sameApiUrl(options.selectedProfile.credentials.apiUrl, options.apiUrl)) {
return;
}
throw profileApiUrlMismatchError({
profileName: options.selectedProfile.name,
profileApiUrl: options.selectedProfile.credentials.apiUrl ?? null,
targetApiUrl: options.apiUrl,
targetReason: options.targetReason,
apiUrlSource: options.apiUrlSource
});
}
function projectNotFoundError() {
return new AdkError({
code: "PROJECT_NOT_FOUND",
message: `No ADK agent root found in the current directory or its parents.
` + "Run `adk help` to see all commands\n" + "Run `adk init` to create a new agent",
expected: true
});
}
async function resolveCommandContext(options = {}) {
const cwd = options.cwd ?? process.cwd();
const target = options.target ?? "dev";
const requirements = new Set(options.require ?? ["project", "credentials"]);
const agentRoot = await findAgentRoot(cwd);
if (!agentRoot) {
throw projectNotFoundError();
}
const project = await AgentProject.load(agentRoot, options.projectLoadOptions);
const selectedProfile = await resolveSelectedProfile();
const apiUrl = options.apiUrl ?? project.agentInfo?.apiUrl;
if (!apiUrl) {
throw new AdkError({
code: "PROJECT_API_URL_MISSING",
message: "No API URL found for this project. Run `adk link` to link the project first.",
expected: true,
suggestion: "Run `adk link` first."
});
}
const apiUrlSource = options.apiUrl ? { type: "option", option: "--api-url" } : await projectApiUrlSource(agentRoot, apiUrl);
const targetReason = apiUrlSourceReason(apiUrlSource, apiUrl);
validateProfileApiUrl({
selectedProfile,
apiUrl,
apiUrlSource,
targetReason
});
const workspaceId = project.agentInfo?.workspaceId;
const botId = resolveTargetBotId(project, target);
if (hasRequirement(requirements, "workspace") && !workspaceId) {
throw new AdkError({
code: "WORKSPACE_ID_MISSING",
message: "No workspace ID found in agent.json. Please run `adk link` to link your agent first.",
expected: true,
suggestion: "Run `adk link` first."
});
}
if (hasRequirement(requirements, "bot") && !botId) {
throw missingTargetBotError(target);
}
const credentials = createCommandCredentials(selectedProfile.credentials, apiUrl, {
...workspaceId ? { workspaceId } : {},
...botId ? { botId } : {}
});
const shouldCreateClient = options.createClient ?? !!workspaceId;
const client = shouldCreateClient && workspaceId ? await getProjectClient({
project,
credentials,
workspaceId,
...botId ? { botId } : {}
}) : undefined;
return {
cwd,
target,
agentRoot,
project,
apiUrl,
apiUrlSource,
targetReason,
selectedProfile,
credentials,
...workspaceId ? { workspaceId } : {},
...botId ? { botId } : {},
...client ? { client } : {}
};
}
async function resolveLinkContext(options = {}) {
const cwd = options.cwd ?? process.cwd();
const agentRoot = await findAgentRoot(cwd);
if (!agentRoot) {
throw projectNotFoundError();
}
const project = await AgentProject.load(agentRoot, options.projectLoadOptions);
const selectedProfile = await resolveSelectedProfile();
const projectApiUrl = project.agentInfo?.apiUrl;
const apiUrl = options.apiUrl ?? projectApiUrl ?? selectedProfile.credentials.apiUrl;
if (!apiUrl) {
throw new AdkError({
code: "LINK_API_URL_MISSING",
message: "No API URL found for adk link. Pass --api-url or login with a profile that has an API URL before linking.",
expected: true,
suggestion: "Run `adk login --api-url <api-url> --profile <profile-name>` or pass `--api-url <api-url>`."
});
}
const apiUrlSource = options.apiUrl ? { type: "option", option: "--api-url" } : projectApiUrl ? await projectApiUrlSource(agentRoot, projectApiUrl) : { type: "selected-profile", profileName: selectedProfile.name };
const targetReason = apiUrlSource.type === "selected-profile" ? `agent.json has no apiUrl, so adk link uses selected profile "${selectedProfile.name}"` : apiUrlSourceReason(apiUrlSource, apiUrl);
validateProfileApiUrl({
selectedProfile,
apiUrl,
apiUrlSource,
targetReason
});
const credentials = createCommandCredentials(selectedProfile.credentials, apiUrl);
return {
cwd,
agentRoot,
project,
apiUrl,
apiUrlSource,
targetReason,
selectedProfile,
credentials
};
}
async function resolveEnvironmentRemoteContext(options = {}) {
const cwd = options.cwd ?? process.cwd();
const agentRoot = await findAgentRoot(cwd);
const project = agentRoot ? await AgentProject.load(agentRoot, options.projectLoadOptions) : undefined;
const selectedProfile = await resolveSelectedProfile();
const projectApiUrl = project?.agentInfo?.apiUrl;
const apiUrl = projectApiUrl ?? selectedProfile.credentials.apiUrl;
if (!apiUrl) {
throw new AdkError({
code: "PROFILE_API_URL_MISSING",
message: `Selected profile "${selectedProfile.name}" is missing an API URL.`,
expected: true,
suggestion: `Run \`adk login --profile ${selectedProfile.name}\` again.`
});
}
const apiUrlSource = projectApiUrl ? await projectApiUrlSource(agentRoot, projectApiUrl) : { type: "selected-profile", profileName: selectedProfile.name };
const targetReason = apiUrlSourceReason(apiUrlSource, apiUrl);
validateProfileApiUrl({
selectedProfile,
apiUrl,
apiUrlSource,
targetReason
});
const profileWorkspaceId = selectedProfile.credentials.workspaceId;
const credentials = createCommandCredentials(selectedProfile.credentials, apiUrl, {
...profileWorkspaceId ? { workspaceId: profileWorkspaceId } : {}
});
const client = options.createClient ? new Uk({
token: credentials.token,
apiUrl: credentials.apiUrl,
...credentials.workspaceId ? { workspaceId: credentials.workspaceId } : {},
headers: {
"x-multiple-integrations": "true"
}
}) : undefined;
return {
cwd,
...agentRoot ? { agentRoot } : {},
...project ? { project } : {},
apiUrl,
apiUrlSource,
targetReason,
selectedProfile,
credentials,
...client ? { client } : {}
};
}
export { profileApiUrlMismatchError, resolveCommandContext, resolveLinkContext, resolveEnvironmentRemoteContext };