@iqai/adk-cli
Version:
CLI tool for creating, running, and testing ADK agents
1,165 lines (1,149 loc) • 36.5 kB
JavaScript
// src/index.ts
import chalk5 from "chalk";
import { program } from "commander";
// package.json
var package_default = {
name: "@iqai/adk-cli",
version: "0.2.6",
description: "CLI tool for creating, running, and testing ADK agents",
main: "dist/index.js",
types: "dist/index.d.ts",
bin: {
adk: "./dist/index.mjs"
},
scripts: {
build: "tsup",
dev: "tsup --watch",
test: "vitest run",
"test:watch": "vitest"
},
repository: {
type: "git",
url: "https://github.com/IQAIcom/adk-ts.git",
directory: "packages/adk-cli"
},
keywords: [
"ai",
"llm",
"agent",
"cli",
"adk",
"typescript"
],
author: "IQAI",
license: "MIT",
dependencies: {
"@clack/prompts": "^0.11.0",
"@hono/node-server": "^1.18.1",
"@iqai/adk": "workspace:*",
chalk: "^5.4.1",
commander: "^12.1.0",
dedent: "^1.6.0",
giget: "^2.0.0",
hono: "^4.6.13",
marked: "^14.1.3",
"marked-terminal": "^7.2.1",
esbuild: "^0.23.0"
},
devDependencies: {
"@iqai/tsconfig": "workspace:*",
"@types/marked-terminal": "^6.1.1",
"@types/node": "^20.17.30",
tsup: "^8.4.0",
typescript: "^5.3.2",
vitest: "^3.1.3"
},
packageManager: "pnpm@9.0.0",
engines: {
node: ">=22.0"
},
files: [
"dist",
"!**/*.test.*",
"!**/*.json",
"CHANGELOG.md",
"LICENSE",
"README.md"
],
publishConfig: {
access: "public"
}
};
// src/commands/new.ts
import { existsSync } from "fs";
import { join } from "path";
import { confirm, intro, outro, select, spinner, text } from "@clack/prompts";
import chalk from "chalk";
import dedent from "dedent";
import { downloadTemplate } from "giget";
var templates = [
{
value: "simple-agent",
label: "\u{1F916} Simple Agent",
hint: "Basic agent with chat capabilities",
source: "github:IQAIcom/adk-ts/apps/starter-templates/simple-agent"
},
{
value: "discord-bot",
label: "\u{1F3AE} Discord Bot",
hint: "Agent integrated with Discord",
source: "github:IQAIcom/adk-ts/apps/starter-templates/discord-bot"
},
{
value: "telegram-bot",
label: "\u{1F4F1} Telegram Bot",
hint: "Agent integrated with Telegram",
source: "github:IQAIcom/adk-ts/apps/starter-templates/telegram-bot"
},
{
value: "hono-server",
label: "\u{1F680} Hono Server",
hint: "Web server with agent endpoints",
source: "github:IQAIcom/adk-ts/apps/starter-templates/hono-server"
},
{
value: "mcp-starter",
label: "\u{1F50C} MCP Integration",
hint: "Model Context Protocol server",
source: "github:IQAIcom/adk-ts/apps/starter-templates/mcp-starter"
}
];
var packageManagers = [
{ name: "npm", command: "npm", args: ["install"], label: "\u{1F4E6} npm" },
{ name: "pnpm", command: "pnpm", args: ["install"], label: "\u26A1 pnpm" },
{ name: "yarn", command: "yarn", args: ["install"], label: "\u{1F9F6} yarn" },
{ name: "bun", command: "bun", args: ["install"], label: "\u{1F35E} bun" }
];
async function detectAvailablePackageManagers() {
const { spawn } = await import("child_process");
const available = [];
for (const pm of packageManagers) {
try {
await new Promise((resolve2, reject) => {
const child = spawn(pm.command, ["--version"], {
stdio: "pipe"
});
child.on("close", (code) => {
if (code === 0) {
available.push(pm);
}
resolve2();
});
child.on("error", () => resolve2());
});
} catch {
}
}
return available.length > 0 ? available : [packageManagers[0]];
}
async function createProject(projectName, options) {
console.clear();
intro(chalk.magentaBright("\u{1F9E0} Create new ADK-TS project"));
let finalProjectName = projectName;
if (!finalProjectName) {
const response = await text({
message: "What is your project name?",
placeholder: "my-adk-project",
validate: (value) => {
if (!value) return "Project name is required";
if (value.includes(" ")) return "Project name cannot contain spaces";
if (existsSync(value)) return `Directory "${value}" already exists`;
return void 0;
}
});
if (typeof response === "symbol") {
outro("Operation cancelled");
process.exit(0);
}
finalProjectName = response;
}
let selectedTemplate = options?.template;
if (!selectedTemplate || !templates.find((t) => t.value === selectedTemplate)) {
const framework = await select({
message: "Which template would you like to use?",
options: templates.map((t) => ({
value: t.value,
label: t.label,
hint: t.hint
}))
});
if (typeof framework === "symbol") {
outro("Operation cancelled");
process.exit(0);
}
selectedTemplate = framework;
}
const template = templates.find((t) => t.value === selectedTemplate);
if (!template) {
outro("Invalid template selected");
process.exit(1);
}
if (existsSync(finalProjectName)) {
outro(chalk.red(`Directory "${finalProjectName}" already exists`));
process.exit(1);
}
const s = spinner();
s.start("Downloading template...");
try {
await downloadTemplate(template.source, {
dir: finalProjectName,
registry: "gh"
});
s.stop("Template downloaded!");
} catch (error) {
s.stop("Failed to download template");
outro(chalk.red(`Error: ${error}`));
process.exit(1);
}
const availablePackageManagers = await detectAvailablePackageManagers();
let selectedPackageManager;
if (availablePackageManagers.length === 1) {
selectedPackageManager = availablePackageManagers[0];
} else {
const packageManagerChoice = await select({
message: "Which package manager would you like to use?",
options: availablePackageManagers.map((pm) => ({
value: pm.name,
label: pm.label
}))
});
if (typeof packageManagerChoice === "symbol") {
outro("Operation cancelled");
process.exit(0);
}
selectedPackageManager = availablePackageManagers.find(
(pm) => pm.name === packageManagerChoice
);
}
const shouldInstall = await confirm({
message: "Install dependencies?",
initialValue: true
});
if (typeof shouldInstall === "symbol") {
outro("Operation cancelled");
process.exit(0);
}
if (shouldInstall) {
const s2 = spinner();
s2.start(`Installing dependencies with ${selectedPackageManager.name}...`);
const { spawn } = await import("child_process");
const projectPath = join(process.cwd(), finalProjectName);
try {
await new Promise((resolve2, reject) => {
const child = spawn(
selectedPackageManager.command,
selectedPackageManager.args,
{
cwd: projectPath,
stdio: "pipe"
}
);
child.on("close", (code) => {
if (code === 0) {
resolve2();
} else {
reject(new Error(`Package installation failed with code ${code}`));
}
});
child.on("error", reject);
});
s2.stop("Dependencies installed!");
} catch (error) {
s2.stop("Failed to install dependencies");
console.log(
chalk.yellow("\nYou can install dependencies manually by running:")
);
console.log(
chalk.cyan(
`cd ${finalProjectName} && ${selectedPackageManager.command} ${selectedPackageManager.args.join(" ")}`
)
);
}
}
outro(
chalk.green(dedent`
🎉 Project created successfully!
Next steps:
${chalk.cyan(`cd ${finalProjectName}`)}
${shouldInstall ? "" : chalk.cyan(`${selectedPackageManager.command} ${selectedPackageManager.args.join(" ")}`)}
${chalk.cyan("npm run dev")} or ${chalk.cyan("yarn dev")} or ${chalk.cyan("pnpm dev")}
Happy coding! 🚀
`)
);
}
// src/commands/run.ts
import * as p from "@clack/prompts";
import { log, spinner as spinner2 } from "@clack/prompts";
import chalk3 from "chalk";
import { marked } from "marked";
import { markedTerminal } from "marked-terminal";
// src/commands/serve.ts
import { existsSync as existsSync3 } from "fs";
import { resolve } from "path";
import chalk2 from "chalk";
// src/server/index.ts
import { serve } from "@hono/node-server";
import { InMemorySessionService } from "@iqai/adk";
import { Hono } from "hono";
// src/server/routes.ts
import { cors } from "hono/cors";
function setupRoutes(app, agentManager, sessionManager, agentsDir) {
app.use("/*", cors());
app.get("/health", (c) => c.json({ status: "ok" }));
app.get("/api/agents", (c) => {
const agentsList = Array.from(
agentManager.getAgents().values()
).map((agent) => ({
path: agent.absolutePath,
name: agent.name,
directory: agent.absolutePath,
relativePath: agent.relativePath
}));
return c.json({ agents: agentsList });
});
app.post("/api/agents/refresh", (c) => {
agentManager.scanAgents(agentsDir);
const agentsList = Array.from(
agentManager.getAgents().values()
).map((agent) => ({
path: agent.absolutePath,
name: agent.name,
directory: agent.absolutePath,
relativePath: agent.relativePath
}));
return c.json({ agents: agentsList });
});
app.get("/api/agents/:id/messages", async (c) => {
const agentPath = decodeURIComponent(c.req.param("id"));
const loadedAgent = agentManager.getLoadedAgents().get(agentPath);
if (!loadedAgent) {
return c.json({ messages: [] });
}
const messages = await sessionManager.getSessionMessages(loadedAgent);
const response = { messages };
return c.json(response);
});
app.post("/api/agents/:id/message", async (c) => {
const agentPath = decodeURIComponent(c.req.param("id"));
const { message, attachments } = await c.req.json();
const response = await agentManager.sendMessageToAgent(
agentPath,
message,
attachments
);
const messageResponse = { response };
return c.json(messageResponse);
});
}
// src/server/services.ts
import {
existsSync as existsSync2,
mkdirSync,
readFileSync,
readdirSync,
statSync,
unlinkSync
} from "fs";
import { dirname, join as join2, relative } from "path";
import { pathToFileURL } from "url";
import { AgentBuilder } from "@iqai/adk";
var AgentScanner = class {
constructor(quiet = false) {
this.quiet = quiet;
}
scanAgents(agentsDir, loadedAgents) {
const agents = /* @__PURE__ */ new Map();
const scanDir = !agentsDir || !existsSync2(agentsDir) ? process.cwd() : agentsDir;
const shouldSkipDirectory = (dirName) => {
const skipDirs = [
"node_modules",
".git",
".next",
"dist",
"build",
".turbo",
"coverage",
".vscode",
".idea"
];
return skipDirs.includes(dirName);
};
const scanDirectory = (dir) => {
if (!existsSync2(dir)) return;
const items = readdirSync(dir);
for (const item of items) {
const fullPath = join2(dir, item);
const stat = statSync(fullPath);
if (stat.isDirectory()) {
if (!shouldSkipDirectory(item)) {
scanDirectory(fullPath);
}
} else if (item === "agent.ts" || item === "agent.js") {
const relativePath = relative(scanDir, dir);
const loadedAgent = loadedAgents.get(relativePath);
let agentName = relativePath.split("/").pop() || "unknown";
if (loadedAgent?.agent?.name) {
agentName = loadedAgent.agent.name;
} else {
try {
const agentFilePath = join2(dir, item);
agentName = this.extractAgentNameFromFile(agentFilePath) || agentName;
} catch {
}
}
agents.set(relativePath, {
relativePath,
name: agentName,
absolutePath: dir,
instance: loadedAgent?.agent
});
}
}
};
scanDirectory(scanDir);
if (!this.quiet) {
console.log(`\u2705 Agent scan complete. Found ${agents.size} agents.`);
}
return agents;
}
extractAgentNameFromFile(filePath) {
try {
const content = readFileSync(filePath, "utf-8");
const nameMatch = content.match(/name\s*:\s*["']([^"']+)["']/);
if (nameMatch?.[1]) {
return nameMatch[1];
}
return null;
} catch {
return null;
}
}
};
var AgentLoader = class {
constructor(quiet = false) {
this.quiet = quiet;
}
/**
* Import a TypeScript file by compiling it on-demand
*/
async importTypeScriptFile(filePath) {
const startDir = dirname(filePath);
let projectRoot = startDir;
while (projectRoot !== "/" && projectRoot !== dirname(projectRoot)) {
if (existsSync2(join2(projectRoot, "package.json")) || existsSync2(join2(projectRoot, ".env"))) {
break;
}
projectRoot = dirname(projectRoot);
}
if (projectRoot === "/") {
projectRoot = startDir;
}
try {
const { build } = await import("esbuild");
const cacheDir = join2(projectRoot, ".adk-cache");
if (!existsSync2(cacheDir)) {
mkdirSync(cacheDir, { recursive: true });
}
const outFile = join2(cacheDir, `agent-${Date.now()}.mjs`);
const plugin = {
name: "externalize-bare-imports",
setup(build2) {
build2.onResolve({ filter: /.*/ }, (args) => {
if (args.path.startsWith(".") || args.path.startsWith("/") || args.path.startsWith("..")) {
return;
}
return { path: args.path, external: true };
});
}
};
const tsconfigPath = join2(projectRoot, "tsconfig.json");
await build({
entryPoints: [filePath],
outfile: outFile,
bundle: true,
format: "esm",
platform: "node",
target: ["node22"],
sourcemap: false,
logLevel: "silent",
plugins: [plugin],
absWorkingDir: projectRoot,
// Use tsconfig if present for path aliases
...existsSync2(tsconfigPath) ? { tsconfig: tsconfigPath } : {}
});
const mod = await import(`${pathToFileURL(outFile).href}?t=${Date.now()}`);
let agentExport = mod?.agent;
if (!agentExport && mod?.default) {
agentExport = mod.default.agent ?? mod.default;
}
try {
unlinkSync(outFile);
} catch {
}
if (agentExport) {
const isPrimitive = (v) => v == null || ["string", "number", "boolean"].includes(typeof v);
if (isPrimitive(agentExport)) {
if (!this.quiet) {
console.log(
`\u2139\uFE0F Ignoring primitive 'agent' export in ${filePath}; scanning module for factory...`
);
}
} else {
if (!this.quiet) {
console.log(`\u2705 TS agent imported via esbuild: ${filePath}`);
}
return { agent: agentExport };
}
}
return mod;
} catch (e) {
throw new Error(
`Failed to import TS agent via esbuild: ${e instanceof Error ? e.message : String(e)}`
);
}
}
loadEnvironmentVariables(agentFilePath) {
let projectRoot = dirname(agentFilePath);
while (projectRoot !== "/" && projectRoot !== dirname(projectRoot)) {
if (existsSync2(join2(projectRoot, "package.json")) || existsSync2(join2(projectRoot, ".env"))) {
break;
}
projectRoot = dirname(projectRoot);
}
const envFiles = [
".env.local",
".env.development.local",
".env.production.local",
".env.development",
".env.production",
".env"
];
for (const envFile of envFiles) {
const envPath = join2(projectRoot, envFile);
if (existsSync2(envPath)) {
try {
const envContent = readFileSync(envPath, "utf8");
const envLines = envContent.split("\n");
for (const line of envLines) {
const trimmedLine = line.trim();
if (trimmedLine && !trimmedLine.startsWith("#")) {
const [key, ...valueParts] = trimmedLine.split("=");
if (key && valueParts.length > 0) {
const value = valueParts.join("=").replace(/^"(.*)"$/, "$1");
if (!process.env[key.trim()]) {
process.env[key.trim()] = value.trim();
}
}
}
}
} catch (error) {
console.warn(
`\u26A0\uFE0F Warning: Could not load ${envFile} file: ${error instanceof Error ? error.message : String(error)}`
);
}
}
}
}
// Minimal resolution logic for agent exports: supports
// 1) export const agent = new LlmAgent(...)
// 2) export function agent() { return new LlmAgent(...) }
// 3) export async function agent() { return new LlmAgent(...) }
// 4) default export (object or function) returning or containing .agent
async resolveAgentExport(mod) {
let candidate = mod?.agent ?? mod?.default?.agent ?? mod?.default ?? mod;
const isLikelyAgentInstance = (obj) => obj && typeof obj === "object" && typeof obj.name === "string";
const isPrimitive = (v) => v == null || ["string", "number", "boolean"].includes(typeof v);
const invokeMaybe = async (fn) => {
let out = fn();
if (out && typeof out === "object" && "then" in out) {
out = await out;
}
return out;
};
if (!isLikelyAgentInstance(candidate) && isPrimitive(candidate) || !isLikelyAgentInstance(candidate) && candidate && candidate === mod) {
candidate = mod;
for (const [key, value] of Object.entries(mod)) {
if (key === "default") continue;
const keyLower = key.toLowerCase();
if (isPrimitive(value)) continue;
if (isLikelyAgentInstance(value)) {
candidate = value;
break;
}
if (value && typeof value === "object" && value.agent && isLikelyAgentInstance(value.agent)) {
candidate = value.agent;
break;
}
if (typeof value === "function" && (/(agent|build|create)/i.test(keyLower) || value.name && /(agent|build|create)/i.test(value.name.toLowerCase()))) {
try {
const maybe = await invokeMaybe(value);
if (isLikelyAgentInstance(maybe)) {
candidate = maybe;
break;
}
if (maybe && typeof maybe === "object" && maybe.agent && isLikelyAgentInstance(maybe.agent)) {
candidate = maybe.agent;
break;
}
} catch (e) {
}
}
}
}
if (typeof candidate === "function") {
try {
candidate = await invokeMaybe(candidate);
} catch (e) {
throw new Error(
`Failed executing exported agent function: ${e instanceof Error ? e.message : String(e)}`
);
}
}
if (candidate && typeof candidate === "object" && candidate.agent && isLikelyAgentInstance(candidate.agent)) {
candidate = candidate.agent;
}
if (candidate?.agent && isLikelyAgentInstance(candidate.agent)) {
candidate = candidate.agent;
}
if (!candidate || !isLikelyAgentInstance(candidate)) {
throw new Error(
"No agent export resolved (expected variable, function, or function returning an agent)"
);
}
return { agent: candidate };
}
};
var AgentManager = class {
constructor(sessionService, quiet = false) {
this.sessionService = sessionService;
this.quiet = quiet;
this.scanner = new AgentScanner(quiet);
this.loader = new AgentLoader(quiet);
}
agents = /* @__PURE__ */ new Map();
loadedAgents = /* @__PURE__ */ new Map();
scanner;
loader;
getAgents() {
return this.agents;
}
getLoadedAgents() {
return this.loadedAgents;
}
scanAgents(agentsDir) {
this.agents = this.scanner.scanAgents(agentsDir, this.loadedAgents);
}
async startAgent(agentPath) {
const agent = this.agents.get(agentPath);
if (!agent) {
throw new Error(`Agent not found: ${agentPath}`);
}
if (this.loadedAgents.has(agentPath)) {
return;
}
try {
let agentFilePath = join2(agent.absolutePath, "agent.js");
if (!existsSync2(agentFilePath)) {
agentFilePath = join2(agent.absolutePath, "agent.ts");
}
if (!existsSync2(agentFilePath)) {
throw new Error(
`No agent.js or agent.ts file found in ${agent.absolutePath}`
);
}
this.loader.loadEnvironmentVariables(agentFilePath);
const agentFileUrl = pathToFileURL(agentFilePath).href;
const agentModule = agentFilePath.endsWith(".ts") ? await this.loader.importTypeScriptFile(agentFilePath) : await import(agentFileUrl);
const resolved = await this.loader.resolveAgentExport(agentModule);
const exportedAgent = resolved.agent;
if (!exportedAgent?.name) {
throw new Error(
`Invalid agent export in ${agentFilePath}. Expected an LlmAgent instance with a name property.`
);
}
const agentBuilder = AgentBuilder.create(exportedAgent.name).withAgent(exportedAgent).withSessionService(this.sessionService, {
userId: `user_${agentPath}`,
appName: "adk-server"
});
const { runner, session } = await agentBuilder.build();
const loadedAgent = {
agent: exportedAgent,
runner,
sessionId: session.id,
userId: `user_${agentPath}`,
appName: "adk-server"
};
this.loadedAgents.set(agentPath, loadedAgent);
agent.instance = exportedAgent;
agent.name = exportedAgent.name;
} catch (error) {
console.error(`\u274C Failed to load agent "${agent.name}":`, error);
throw new Error(
`Failed to load agent: ${error instanceof Error ? error.message : String(error)}`
);
}
}
async stopAgent(agentPath) {
this.loadedAgents.delete(agentPath);
const agent = this.agents.get(agentPath);
if (agent) {
agent.instance = void 0;
}
}
async sendMessageToAgent(agentPath, message, attachments) {
if (!this.loadedAgents.has(agentPath)) {
await this.startAgent(agentPath);
}
const loadedAgent = this.loadedAgents.get(agentPath);
if (!loadedAgent) {
throw new Error("Agent failed to start");
}
try {
if (attachments && attachments.length > 0) {
const request = {
parts: [
{ text: message },
...attachments.map((file) => ({
inlineData: {
mimeType: file.mimeType,
data: file.data
}
}))
]
};
const response2 = await loadedAgent.runner.ask(request);
return response2;
}
const response = await loadedAgent.runner.ask(message);
return response;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(
`Error sending message to agent ${agentPath}:`,
errorMessage
);
throw new Error(`Failed to send message to agent: ${errorMessage}`);
}
}
stopAllAgents() {
for (const [agentPath] of this.loadedAgents.entries()) {
this.stopAgent(agentPath);
}
}
};
var SessionManager = class {
constructor(sessionService) {
this.sessionService = sessionService;
}
async getSessionMessages(loadedAgent) {
try {
const session = await this.sessionService.getSession(
loadedAgent.appName,
loadedAgent.userId,
loadedAgent.sessionId
);
if (!session || !session.events) {
return [];
}
const messages = session.events.map((event, index) => ({
id: index + 1,
type: event.author === "user" ? "user" : "assistant",
content: event.content?.parts?.map(
(part) => typeof part === "object" && "text" in part ? part.text : ""
).join("") || "",
timestamp: new Date(event.timestamp || Date.now()).toISOString()
}));
return messages;
} catch (error) {
console.error("Error fetching messages:", error);
return [];
}
}
};
// src/server/index.ts
var ADKServer = class {
agentManager;
sessionManager;
sessionService;
app;
server;
config;
constructor(agentsDir, port = 8042, host = "localhost", quiet = false) {
this.config = { agentsDir, port, host, quiet };
this.sessionService = new InMemorySessionService();
this.agentManager = new AgentManager(this.sessionService, quiet);
this.sessionManager = new SessionManager(this.sessionService);
this.app = new Hono();
setupRoutes(this.app, this.agentManager, this.sessionManager, agentsDir);
this.agentManager.scanAgents(agentsDir);
}
async start() {
return new Promise((resolve2) => {
this.server = serve({
fetch: this.app.fetch,
port: this.config.port,
hostname: this.config.host
});
setTimeout(() => {
resolve2();
}, 100);
});
}
async stop() {
return new Promise((resolve2) => {
this.agentManager.stopAllAgents();
if (this.server) {
this.server.close();
}
resolve2();
});
}
getPort() {
return this.config.port;
}
};
// src/commands/serve.ts
async function serveCommand(options = {}) {
const port = options.port || 8042;
const host = options.host || "localhost";
const agentsDir = resolve(options.dir || ".");
if (!existsSync3(agentsDir)) {
console.error(chalk2.red(`\u274C Directory not found: ${agentsDir}`));
process.exit(1);
}
if (!options.quiet) {
console.log(chalk2.blue(`\u{1F680} ADK Server starting on http://${host}:${port}`));
}
const server = new ADKServer(agentsDir, port, host, options.quiet);
try {
await server.start();
if (!options.quiet) {
console.log(chalk2.green("\u2705 Server ready"));
}
const cleanup = async () => {
if (!options.quiet) {
console.log(chalk2.yellow("\n\u{1F6D1} Stopping server..."));
}
await server.stop();
process.exit(0);
};
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
return server;
} catch (error) {
console.error(chalk2.red("\u274C Failed to start ADK server:"), error);
process.exit(1);
}
}
// src/commands/run.ts
marked.use(markedTerminal());
async function renderMarkdown(text3) {
try {
const result = await marked(text3);
return typeof result === "string" ? result : text3;
} catch (error) {
return text3;
}
}
var AgentChatClient = class {
apiUrl;
selectedAgent = null;
constructor(apiUrl) {
this.apiUrl = apiUrl;
}
async connect() {
try {
const response = await fetch(`${this.apiUrl}/health`);
if (!response.ok) {
throw new Error("Connection failed");
}
} catch (error) {
throw new Error("\u274C Connection failed");
}
}
async fetchAgents() {
try {
const response = await fetch(`${this.apiUrl}/api/agents`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (Array.isArray(data)) {
return data;
}
if (data && Array.isArray(data.agents)) {
return data.agents;
}
throw new Error(`Unexpected response format: ${JSON.stringify(data)}`);
} catch (error) {
throw new Error(
`Failed to fetch agents: ${error instanceof Error ? error.message : String(error)}`
);
}
}
async selectAgent() {
const agents = await this.fetchAgents();
if (agents.length === 0) {
throw new Error("No agents found in the current directory");
}
if (agents.length === 1) {
return agents[0];
}
const selectedAgent = await p.select({
message: "Choose an agent to chat with:",
options: agents.map((agent) => ({
label: agent.name,
value: agent,
hint: agent.relativePath
}))
});
if (p.isCancel(selectedAgent)) {
p.cancel("Operation cancelled");
process.exit(0);
}
return selectedAgent;
}
// No-op: agents are auto-loaded on message; keeping method removed
async sendMessage(message) {
if (!this.selectedAgent) {
throw new Error("No agent selected");
}
const s = spinner2();
s.start("\u{1F916} Thinking...");
try {
const response = await fetch(
`${this.apiUrl}/api/agents/${encodeURIComponent(this.selectedAgent.relativePath)}/message`,
{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ message })
}
);
if (!response.ok) {
const errorText = await response.text();
s.stop("\u274C Failed to send message");
throw new Error(`Failed to send message: ${errorText}`);
}
const result = await response.json();
s.stop("\u{1F916} Assistant:");
if (result.response) {
const formattedResponse = await renderMarkdown(result.response);
log.message(formattedResponse.trim());
}
} catch (error) {
log.error("Failed to send message");
}
}
async startChat() {
if (!this.selectedAgent) {
throw new Error("Agent not selected");
}
while (true) {
try {
const message = await p.text({
message: "\u{1F4AC} Message:",
placeholder: "Type your message here..."
});
if (p.isCancel(message)) {
break;
}
if (message.trim()) {
await this.sendMessage(message.trim());
}
} catch (error) {
console.error(chalk3.red("Error in chat:"), error);
break;
}
}
}
disconnect() {
}
setSelectedAgent(agent) {
this.selectedAgent = agent;
}
};
async function runAgent(agentPath, options = {}) {
const envVerbose = process.env.ADK_VERBOSE;
const isVerbose = options.verbose ?? (envVerbose === "1" || envVerbose === "true");
if (options.server) {
const apiPort = 8042;
const host = options.host || "localhost";
console.log(chalk3.blue("\u{1F680} Starting ADK Server..."));
const serveOptions = {
port: apiPort,
dir: process.cwd(),
host,
quiet: !isVerbose
};
try {
const server = await serveCommand(serveOptions);
console.log(chalk3.cyan("Press Ctrl+C to stop the server"));
process.on("SIGINT", async () => {
console.log(chalk3.yellow("\n\u{1F6D1} Stopping server..."));
await server.stop();
process.exit(0);
});
return new Promise(() => {
});
} catch (error) {
console.error(chalk3.red("\u274C Failed to start server"));
process.exit(1);
}
}
const apiUrl = `http://${options.host || "localhost"}:8042`;
p.intro("\u{1F916} ADK Agent Chat");
try {
const healthResponse = await fetch(`${apiUrl}/health`).catch(() => null);
if (!healthResponse || !healthResponse.ok) {
const serverSpinner = spinner2();
serverSpinner.start("\u{1F680} Starting server...");
const serveOptions = {
port: 8042,
// Use new default port
dir: process.cwd(),
host: options.host || "localhost",
quiet: !isVerbose
};
await serveCommand(serveOptions);
await new Promise((resolve2) => setTimeout(resolve2, 1e3));
serverSpinner.stop("\u2705 Server ready");
}
const client = new AgentChatClient(apiUrl);
await client.connect();
const agentSpinner = spinner2();
agentSpinner.start("\u{1F50D} Scanning for agents...");
const agents = await client.fetchAgents();
let selectedAgent;
if (agents.length === 0) {
agentSpinner.stop("\u274C No agents found");
p.cancel("No agents found in the current directory");
process.exit(1);
} else if (agents.length === 1) {
selectedAgent = agents[0];
agentSpinner.stop(`\u{1F916} Found agent: ${selectedAgent.name}`);
} else {
agentSpinner.stop(`\u{1F916} Found ${agents.length} agents`);
const choice = await p.select({
message: "Choose an agent to chat with:",
options: agents.map((agent) => ({
label: agent.name,
value: agent,
hint: agent.relativePath
}))
});
if (p.isCancel(choice)) {
p.cancel("Operation cancelled");
process.exit(0);
}
selectedAgent = choice;
}
client.setSelectedAgent(selectedAgent);
await client.startChat();
client.disconnect();
p.outro("Chat ended");
} catch (error) {
p.cancel(
`Error: ${error instanceof Error ? error.message : String(error)}`
);
process.exit(1);
}
}
// src/commands/web.ts
import chalk4 from "chalk";
async function webCommand(options = {}) {
const apiPort = options.port || 8042;
const webPort = options.webPort || 3e3;
const host = options.host || "localhost";
const useLocal = options.local || false;
const webUrl = options.webUrl || "https://adk-web.iqai.com";
console.log(chalk4.blue("\u{1F310} Starting ADK Web Interface..."));
const serveOptions = {
port: apiPort,
dir: options.dir,
host,
quiet: true
};
await serveCommand(serveOptions);
let webAppUrl;
if (useLocal) {
if (apiPort === 8042) {
webAppUrl = `http://${host}:${webPort}`;
} else {
webAppUrl = `http://${host}:${webPort}?port=${apiPort}`;
}
} else {
if (apiPort === 8042) {
webAppUrl = webUrl;
} else {
webAppUrl = `${webUrl}?port=${apiPort}`;
}
}
console.log(chalk4.cyan(`\u{1F517} Open this URL in your browser: ${webAppUrl}`));
console.log(chalk4.gray(` API Server: http://${host}:${apiPort}`));
console.log(chalk4.cyan("Press Ctrl+C to stop the API server"));
}
// src/index.ts
program.name("adk").description(package_default.description).version(package_default.version);
program.command("new").description("Create a new ADK project").argument("[project-name]", "Name of the project to create").option(
"-t, --template <template>",
"Template to use (simple-agent, discord-bot, telegram-bot, hono-server, mcp-starter)"
).action(async (projectName, options) => {
try {
await createProject(projectName, options);
} catch (error) {
console.error(chalk5.red("Error creating project:"), error);
process.exit(1);
}
});
program.command("run").description("Start an interactive chat with an agent").argument(
"[agent-path]",
"Path to specific agent (optional - will show selector if multiple agents found)"
).option("-s, --server", "Start ADK server only (without chat interface)").option(
"-h, --host <host>",
"Host for server (when using --server)",
"localhost"
).action(async (agentPath, options) => {
try {
await runAgent(agentPath, options);
} catch (error) {
console.error(chalk5.red("Error running agent:"), error);
process.exit(1);
}
});
program.command("web").description("Start a web interface for testing agents").option("-p, --port <port>", "Port for API server", "8042").option("--web-port <port>", "Port for web app (when using --local)", "3000").option("-h, --host <host>", "Host for servers", "localhost").option(
"-d, --dir <directory>",
"Directory to scan for agents (default: current directory)",
"."
).option(
"--local",
"Run local web app instead of opening production URL",
false
).option(
"--web-url <url>",
"URL of the web application (used when not --local)",
"https://adk-web.iqai.com"
).action(async (options) => {
try {
await webCommand(options);
} catch (error) {
console.error(chalk5.red("Error starting web UI:"), error);
process.exit(1);
}
});
program.command("serve").description("Start an API server for agent management").option("-p, --port <port>", "Port for the server", "8042").option("-h, --host <host>", "Host for the server", "localhost").option(
"-d, --dir <directory>",
"Directory to scan for agents (default: current directory)",
"."
).action(async (options) => {
try {
await serveCommand(options);
} catch (error) {
console.error(chalk5.red("Error starting server:"), error);
process.exit(1);
}
});
program.parse();
//# sourceMappingURL=index.mjs.map