@iqai/adk-cli
Version:
CLI tool for creating, running, and testing ADK-TS agents
668 lines • 27.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RunCommand = void 0;
const p = __importStar(require("@clack/prompts"));
const chalk_1 = __importDefault(require("chalk"));
const marked_1 = require("marked");
const markedTerminal = __importStar(require("marked-terminal"));
const nest_commander_1 = require("nest-commander");
const constants_1 = require("../common/constants");
const schema_1 = require("../common/schema");
const bootstrap_1 = require("../http/bootstrap");
// Setup markdown terminal renderer
const mt = markedTerminal.markedTerminal ?? markedTerminal;
marked_1.marked.use(mt());
// Console management for quiet mode
class ConsoleManager {
originals = null;
originalStdoutWrite = null;
originalStderrWrite = null;
originalSpawn = null;
verbose;
outputAllowed = false;
isDestroyed = false;
constructor(verbose) {
this.verbose = verbose;
// Ensure cleanup on process exit
process.on("exit", () => this.restore());
process.on("SIGINT", () => this.restore());
process.on("SIGTERM", () => this.restore());
}
hookConsole() {
if (this.verbose || this.originals || this.isDestroyed)
return;
try {
this.originals = {
log: console.log,
info: console.info,
warn: console.warn,
error: console.error,
debug: console.debug,
};
this.originalStdoutWrite = process.stdout.write.bind(process.stdout);
this.originalStderrWrite = process.stderr.write.bind(process.stderr);
// Smart console method replacement - allow errors and warnings in verbose mode
const shouldSilenceConsole = (level) => {
if (this.outputAllowed)
return false;
// Always allow error and warn messages to prevent diagnostic issues
return !["error", "warn"].includes(level);
};
const createConsoleFn = (level, original) => {
return ((...args) => {
if (!shouldSilenceConsole(level)) {
original.apply(console, args);
}
});
};
console.log = createConsoleFn("log", this.originals.log);
console.info = createConsoleFn("info", this.originals.info);
console.warn = createConsoleFn("warn", this.originals.warn);
console.error = createConsoleFn("error", this.originals.error);
console.debug = createConsoleFn("debug", this.originals.debug);
// Smart stdout silencing - allow certain output patterns
const shouldSilenceStdout = (chunk) => {
if (this.outputAllowed)
return false;
return !this.isImportantOutput(chunk);
};
process.stdout.write = ((chunk, encoding, callback) => {
if (shouldSilenceStdout(chunk))
return true;
return this.originalStdoutWrite(chunk, encoding, callback);
});
// Allow stderr for error messages but filter out non-critical output
process.stderr.write = ((chunk, encoding, callback) => {
if (this.outputAllowed) {
return this.originalStderrWrite(chunk, encoding, callback);
}
// Allow error-like content through stderr
const str = String(chunk).toLowerCase();
if (str.includes("error") ||
str.includes("warning") ||
str.includes("failed")) {
return this.originalStderrWrite(chunk, encoding, callback);
}
return true;
});
}
catch (error) {
// If console hooking fails, continue without it to avoid breaking the app
if (this.verbose) {
console.error("Failed to hook console:", error);
}
}
}
isImportantOutput(chunk) {
const str = String(chunk);
// Allow spinner characters and UI elements
const spinnerChars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
// Allow interactive prompts, errors, and control sequences
return (spinnerChars.some((char) => str.includes(char)) ||
str.includes("🤖") ||
str.includes("Thinking") ||
str.includes("\r") ||
str.includes("\x1b") ||
str.toLowerCase().includes("error") ||
str.toLowerCase().includes("warning") ||
str.includes("?") // Interactive prompts
);
}
hookChildProcessSilence() {
if (this.verbose || this.originalSpawn || this.isDestroyed)
return;
try {
const cp = require("node:child_process");
this.originalSpawn = cp.spawn;
const shouldSilenceProcess = (command, args) => {
if (!command)
return false;
const cmd = command.toLowerCase();
const allArgs = (args || []).map((a) => String(a).toLowerCase());
const fullCommand = [cmd, ...allArgs].join(" ");
// Only silence specific known noisy processes
const silencePatterns = [
"mcp-remote",
"@iqai/mcp",
"modelcontextprotocol",
"@modelcontextprotocol",
];
return silencePatterns.some((pattern) => fullCommand.includes(pattern));
};
cp.spawn = ((command, args, options) => {
try {
const shouldSilence = shouldSilenceProcess(command, Array.isArray(args) ? args : options?.args);
if (shouldSilence) {
// Determine the correct options object
const opts = Array.isArray(args) ? options || {} : args || {};
// Create safer stdio configuration
const currentStdio = opts.stdio;
let newStdio;
if (Array.isArray(currentStdio)) {
newStdio = [
currentStdio[0] || "pipe",
"pipe", // stdout to pipe (can be handled)
"pipe", // stderr to pipe (allow error monitoring)
];
}
else if (typeof currentStdio === "string") {
newStdio = ["pipe", "pipe", "pipe"];
}
else {
newStdio = ["pipe", "pipe", "pipe"];
}
const patchedOpts = { ...opts, stdio: newStdio };
if (Array.isArray(args)) {
return this.originalSpawn(command, args, patchedOpts);
}
return this.originalSpawn(command, patchedOpts);
}
}
catch (error) {
// Log error and continue with original spawn to avoid breaking functionality
if (this.verbose) {
console.error("Error in child process hook:", error);
}
}
return this.originalSpawn(command, args, options);
});
}
catch (error) {
if (this.verbose) {
console.error("Failed to hook child process:", error);
}
}
}
restore() {
if (this.isDestroyed)
return;
this.isDestroyed = true;
try {
if (this.originals) {
console.log = this.originals.log;
console.info = this.originals.info;
console.warn = this.originals.warn;
console.error = this.originals.error;
console.debug = this.originals.debug;
this.originals = null;
}
if (this.originalStdoutWrite) {
process.stdout.write = this.originalStdoutWrite;
this.originalStdoutWrite = null;
}
if (this.originalStderrWrite) {
process.stderr.write = this.originalStderrWrite;
this.originalStderrWrite = null;
}
if (this.originalSpawn) {
const cp = require("node:child_process");
cp.spawn = this.originalSpawn;
this.originalSpawn = null;
}
}
catch (error) {
// Use original console.error if available, fallback to process.stderr
if (this.originals?.error) {
this.originals.error("Error during ConsoleManager restore:", error);
}
else {
process.stderr.write(`Error during ConsoleManager restore: ${error}\n`);
}
}
}
writeOut(text) {
if (this.originalStdoutWrite) {
this.originalStdoutWrite(text);
}
else {
process.stdout.write(text);
}
}
writeErr(text) {
if (this.originalStderrWrite) {
this.originalStderrWrite(text);
}
else {
process.stderr.write(text);
}
}
async withAllowedOutput(fn) {
if (this.verbose || this.isDestroyed) {
return await fn();
}
const wasOutputAllowed = this.outputAllowed;
this.outputAllowed = true; // Allow output during this function
try {
return await fn();
}
finally {
this.outputAllowed = wasOutputAllowed; // Restore previous state
}
}
error(text) {
this.writeErr(`${chalk_1.default.red(text)}\n`);
}
renderMarkdown(text) {
const input = text ?? "";
const out = marked_1.marked.parse(input);
return typeof out === "string" ? out : String(out ?? "");
}
printAnswer(markdown) {
const rendered = this.renderMarkdown(markdown);
this.writeOut(`${(rendered || "").trim()}\n`);
}
}
class AgentChatClient {
apiUrl;
selectedAgent = null;
consoleManager;
constructor(apiUrl, consoleManager) {
this.apiUrl = apiUrl;
this.consoleManager = consoleManager;
}
async connect() {
try {
const response = await fetch(`${this.apiUrl}/health`).catch(() => null);
if (!response || !response.ok) {
throw new Error("Connection failed");
}
}
catch {
throw new Error("❌ 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];
}
return await this.consoleManager.withAllowedOutput(async () => {
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)) {
process.exit(0);
}
return choice;
});
}
async sendMessage(message) {
if (!this.selectedAgent) {
throw new Error("No agent selected");
}
await this.consoleManager.withAllowedOutput(async () => {
const spinner = p.spinner();
spinner.start("🤖 Thinking...");
// Save original methods for targeted silencing during the request
const savedStdout = process.stdout.write;
const savedStderr = process.stderr.write;
const savedConsoleLog = console.log;
const savedConsoleInfo = console.info;
const savedConsoleWarn = console.warn;
const savedConsoleError = console.error;
// Intelligent stdout filtering - allow spinner chars but block log messages
const spinnerChars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
const isSpinnerOutput = (chunk) => {
const str = String(chunk);
return (spinnerChars.some((char) => str.includes(char)) ||
str.includes("🤖 Thinking") ||
str.includes("\r") || // carriage returns for spinner updates
str.includes("\x1b") // ANSI escape codes
);
};
// Temporarily override output during the fetch
process.stdout.write = (chunk, encoding, callback) => {
if (isSpinnerOutput(chunk)) {
return savedStdout.call(process.stdout, chunk, encoding, callback);
}
return true; // Block everything else
};
process.stderr.write = (() => true); // Block all stderr
console.log = (() => { });
console.info = (() => { });
console.warn = (() => { });
console.error = (() => { });
try {
if (!this.selectedAgent?.relativePath) {
throw new Error("No agent selected or agent path not available");
}
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) {
spinner.stop("❌ Failed to send message");
const errorText = await response.text();
// Try to parse JSON error for nicer formatting
try {
const errorJson = JSON.parse(errorText);
if (errorJson.error && errorJson.message) {
let message = `\n❌ ${errorJson.error}\n${"━".repeat(40)}\n${errorJson.message}`;
if (errorJson.details && Array.isArray(errorJson.details)) {
const details = errorJson.details
.map((detail) => ` ${detail}`)
.join("\n");
message += `\n${details}`;
}
throw new Error(message);
}
}
catch {
// Not JSON → fall through
}
throw new Error(errorText);
}
const result = (await response.json());
spinner.stop(`🤖 ${result.agentName ?? "Assistant"}:`);
if (result.response) {
this.consoleManager.printAnswer(result.response);
}
}
catch (error) {
spinner.stop("❌ Error");
const errorMessage = error instanceof Error ? error.message : String(error);
this.consoleManager.error(`Failed to send message: ${errorMessage}`);
throw error;
}
finally {
// Restore all methods
process.stdout.write = savedStdout;
process.stderr.write = savedStderr;
console.log = savedConsoleLog;
console.info = savedConsoleInfo;
console.warn = savedConsoleWarn;
console.error = savedConsoleError;
}
});
}
async startChat() {
if (!this.selectedAgent) {
throw new Error("Agent not selected");
}
const sigintHandler = () => {
this.consoleManager.withAllowedOutput(async () => {
p.outro("Chat ended");
});
process.exit(0);
};
process.on("SIGINT", sigintHandler);
try {
while (true) {
try {
const input = await this.consoleManager.withAllowedOutput(async () => {
const res = await p.text({
message: "💬 Message:",
placeholder: "Type your message here... (type 'exit' or 'quit' to end)",
});
if (p.isCancel(res))
return "exit";
return typeof res === "symbol" ? String(res) : (res ?? "");
});
const trimmed = (input || "").trim();
if (["exit", "quit"].includes(trimmed.toLowerCase())) {
process.exit(0);
}
if (trimmed) {
await this.sendMessage(trimmed);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.consoleManager.error(`Error in chat: ${errorMessage}`);
process.exit(1);
}
}
}
finally {
process.removeListener("SIGINT", sigintHandler);
}
}
setSelectedAgent(agent) {
this.selectedAgent = agent;
}
}
let RunCommand = class RunCommand extends nest_commander_1.CommandRunner {
async run(passed, options) {
const agentPathArg = passed?.[0];
const env = schema_1.envSchema.parse(process.env);
const isVerbose = options?.verbose ?? env.ADK_VERBOSE;
const consoleManager = new ConsoleManager(isVerbose);
// Hook console and child process only in non-verbose mode
if (!isVerbose) {
consoleManager.hookConsole();
consoleManager.hookChildProcessSilence();
}
if (options?.server) {
const apiPort = constants_1.DEFAULT_API_PORT;
const host = options.host || "localhost";
if (isVerbose) {
console.log(chalk_1.default.blue("🚀 Starting ADK-TS Server..."));
}
const server = await (0, bootstrap_1.startHttpServer)({
port: apiPort,
host,
agentsDir: process.cwd(),
quiet: !isVerbose,
hotReload: options?.hot,
watchPaths: options?.watch,
});
if (isVerbose) {
console.log(chalk_1.default.cyan("Press Ctrl+C to stop the server"));
}
process.on("SIGINT", async () => {
console.log(chalk_1.default.yellow("\n🛑 Stopping server..."));
await server.stop();
process.exit(0);
});
await new Promise(() => { });
return;
}
// Interactive chat mode
const apiUrl = `http://${options?.host || "localhost"}:${constants_1.DEFAULT_API_PORT}`;
await consoleManager.withAllowedOutput(async () => {
p.intro("🤖 ADK-TS Agent Chat");
});
// Start server if not running
const healthResponse = await fetch(`${apiUrl}/health`).catch(() => null);
if (!healthResponse || !healthResponse.ok) {
await (0, bootstrap_1.startHttpServer)({
port: constants_1.DEFAULT_API_PORT,
host: options?.host || "localhost",
agentsDir: process.cwd(),
quiet: !isVerbose,
hotReload: options?.hot,
watchPaths: options?.watch,
});
await new Promise((resolve) => setTimeout(resolve, 1000));
}
const client = new AgentChatClient(apiUrl, consoleManager);
try {
await client.connect();
const agents = await client.fetchAgents();
let selectedAgent;
if (agents.length === 0) {
consoleManager.error("No agents found in the current directory");
process.exit(1);
}
else if (agents.length === 1 || agentPathArg) {
selectedAgent =
(agentPathArg &&
agents.find((a) => a.relativePath === agentPathArg)) ||
agents[0];
}
else {
selectedAgent = await consoleManager.withAllowedOutput(async () => {
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)) {
process.exit(0);
}
return choice;
});
}
client.setSelectedAgent(selectedAgent);
await client.startChat();
await consoleManager.withAllowedOutput(async () => {
p.outro("Chat ended");
});
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
consoleManager.error(`Error: ${errorMessage}`);
process.exit(1);
}
finally {
// Ensure cleanup happens
consoleManager.restore();
}
}
parseServer() {
return true;
}
parseHost(val) {
return val;
}
parseVerbose() {
return true;
}
parseHot() {
return true;
}
parseWatch(val) {
return (val || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
}
};
exports.RunCommand = RunCommand;
__decorate([
(0, nest_commander_1.Option)({
flags: "-s, --server",
description: "Start ADK-TS server only (without chat interface)",
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Boolean)
], RunCommand.prototype, "parseServer", null);
__decorate([
(0, nest_commander_1.Option)({
flags: "-h, --host <host>",
description: "Host for server (when using --server) or API URL target",
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", String)
], RunCommand.prototype, "parseHost", null);
__decorate([
(0, nest_commander_1.Option)({
flags: "--verbose",
description: "Enable verbose logs",
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Boolean)
], RunCommand.prototype, "parseVerbose", null);
__decorate([
(0, nest_commander_1.Option)({
flags: "--hot",
description: "Enable hot reloading (watches agents and optional paths)",
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Boolean)
], RunCommand.prototype, "parseHot", null);
__decorate([
(0, nest_commander_1.Option)({
flags: "--watch <paths>",
description: "Comma-separated list of additional paths to watch for reloads",
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Array)
], RunCommand.prototype, "parseWatch", null);
exports.RunCommand = RunCommand = __decorate([
(0, nest_commander_1.Command)({
name: "run",
description: "Start an interactive chat with an agent",
arguments: "[agent-path]",
})
], RunCommand);
//# sourceMappingURL=run.command.js.map