@opensearch-project/agent-health 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // cli/index.ts
4
- import { Command as Command10 } from "commander";
5
- import chalk10 from "chalk";
4
+ import { Command as Command11 } from "commander";
5
+ import chalk11 from "chalk";
6
6
  import { fileURLToPath as fileURLToPath3 } from "url";
7
7
  import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
8
8
  import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
@@ -28,6 +28,8 @@ function findPackageRoot() {
28
28
  }
29
29
  async function startServer(options) {
30
30
  process.env.VITE_BACKEND_PORT = String(options.port);
31
+ if (options.headless) process.env.AGENT_HEALTH_HEADLESS = "1";
32
+ if (options.apiKey) process.env.AGENT_HEALTH_API_KEY = options.apiKey;
31
33
  const packageRoot = findPackageRoot();
32
34
  const serverPath = join(packageRoot, "server", "dist", "app.js");
33
35
  const { createApp } = await import(serverPath);
@@ -4898,6 +4900,122 @@ function createCompareServicesCommand() {
4898
4900
  return cmd;
4899
4901
  }
4900
4902
 
4903
+ // cli/commands/remote.ts
4904
+ import { Command as Command10 } from "commander";
4905
+ import chalk10 from "chalk";
4906
+ import fs2 from "fs";
4907
+ import path2 from "path";
4908
+ var CONFIG_FILENAME2 = "agent-health.config.json";
4909
+ function getConfigPath() {
4910
+ return path2.join(process.cwd(), CONFIG_FILENAME2);
4911
+ }
4912
+ function readConfig() {
4913
+ const filePath = getConfigPath();
4914
+ if (!fs2.existsSync(filePath)) return {};
4915
+ try {
4916
+ return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
4917
+ } catch {
4918
+ return {};
4919
+ }
4920
+ }
4921
+ function writeConfig(config) {
4922
+ fs2.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n", "utf-8");
4923
+ }
4924
+ function getRemoteServers(config) {
4925
+ return Array.isArray(config.remoteServers) ? config.remoteServers : [];
4926
+ }
4927
+ function createRemoteCommand() {
4928
+ const remote = new Command10("remote").description("Manage remote agent-health server connections");
4929
+ remote.command("add").description("Add a remote server").requiredOption("--name <name>", "Display name for the server").requiredOption("--url <url>", "Server URL (e.g. http://10.0.1.50:4001)").option("--api-key <key>", "API key for authentication").action((options) => {
4930
+ const config = readConfig();
4931
+ const servers = getRemoteServers(config);
4932
+ if (servers.some((s) => s.name === options.name)) {
4933
+ console.error(chalk10.red(`
4934
+ Error: Server "${options.name}" already exists. Use 'remote remove' first.
4935
+ `));
4936
+ process.exit(1);
4937
+ }
4938
+ const server = { name: options.name, url: options.url.replace(/\/$/, "") };
4939
+ if (options.apiKey) server.apiKey = options.apiKey;
4940
+ servers.push(server);
4941
+ config.remoteServers = servers;
4942
+ writeConfig(config);
4943
+ console.log(chalk10.green(`
4944
+ Added remote server: ${options.name} (${options.url})
4945
+ `));
4946
+ });
4947
+ remote.command("remove").description("Remove a remote server").argument("<name>", "Server name to remove").action((name) => {
4948
+ const config = readConfig();
4949
+ const servers = getRemoteServers(config);
4950
+ const idx = servers.findIndex((s) => s.name === name);
4951
+ if (idx === -1) {
4952
+ console.error(chalk10.red(`
4953
+ Error: Server "${name}" not found.
4954
+ `));
4955
+ process.exit(1);
4956
+ }
4957
+ servers.splice(idx, 1);
4958
+ config.remoteServers = servers;
4959
+ writeConfig(config);
4960
+ console.log(chalk10.green(`
4961
+ Removed remote server: ${name}
4962
+ `));
4963
+ });
4964
+ remote.command("list").description("List configured remote servers").action(() => {
4965
+ const config = readConfig();
4966
+ const servers = getRemoteServers(config);
4967
+ if (servers.length === 0) {
4968
+ console.log(chalk10.gray("\n No remote servers configured.\n"));
4969
+ console.log(chalk10.gray(" Add one with: agent-health remote add --name <name> --url <url>\n"));
4970
+ return;
4971
+ }
4972
+ console.log(chalk10.cyan(`
4973
+ Remote Servers (${servers.length}):
4974
+ `));
4975
+ for (const s of servers) {
4976
+ const auth = s.apiKey ? chalk10.green(" [auth]") : chalk10.gray(" [no auth]");
4977
+ console.log(` ${chalk10.bold(s.name)} ${s.url}${auth}`);
4978
+ }
4979
+ console.log("");
4980
+ });
4981
+ remote.command("test").description("Test connectivity to all remote servers").action(async () => {
4982
+ const config = readConfig();
4983
+ const servers = getRemoteServers(config);
4984
+ if (servers.length === 0) {
4985
+ console.log(chalk10.gray("\n No remote servers configured.\n"));
4986
+ return;
4987
+ }
4988
+ console.log(chalk10.cyan(`
4989
+ Testing ${servers.length} remote server(s)...
4990
+ `));
4991
+ for (const s of servers) {
4992
+ try {
4993
+ const headers = {};
4994
+ if (s.apiKey) headers["Authorization"] = `Bearer ${s.apiKey}`;
4995
+ const controller = new AbortController();
4996
+ const timer = setTimeout(() => controller.abort(), 5e3);
4997
+ const response = await fetch(`${s.url}/api/coding-agents/available`, {
4998
+ headers,
4999
+ signal: controller.signal
5000
+ });
5001
+ clearTimeout(timer);
5002
+ if (response.ok) {
5003
+ const data = await response.json();
5004
+ const agentCount = data.agents?.length ?? 0;
5005
+ console.log(chalk10.green(` \u2713 ${s.name} \u2014 OK (${agentCount} agents detected)`));
5006
+ } else {
5007
+ console.log(chalk10.red(` \u2717 ${s.name} \u2014 HTTP ${response.status} ${response.statusText}`));
5008
+ }
5009
+ } catch (error) {
5010
+ const msg = error instanceof Error ? error.message : String(error);
5011
+ console.log(chalk10.red(` \u2717 ${s.name} \u2014 ${msg}`));
5012
+ }
5013
+ }
5014
+ console.log("");
5015
+ });
5016
+ return remote;
5017
+ }
5018
+
4901
5019
  // cli/index.ts
4902
5020
  var __filename3 = fileURLToPath3(import.meta.url);
4903
5021
  var __dirname3 = dirname3(__filename3);
@@ -4911,25 +5029,25 @@ try {
4911
5029
  function loadEnvFile(envPath) {
4912
5030
  const absolutePath = resolve4(process.cwd(), envPath);
4913
5031
  if (!existsSync5(absolutePath)) {
4914
- console.error(chalk10.red(`
5032
+ console.error(chalk11.red(`
4915
5033
  Error: Environment file not found: ${absolutePath}
4916
5034
  `));
4917
5035
  process.exit(1);
4918
5036
  }
4919
5037
  const result = loadDotenv({ path: absolutePath });
4920
5038
  if (result.error) {
4921
- console.error(chalk10.red(`
5039
+ console.error(chalk11.red(`
4922
5040
  Error loading environment file: ${result.error.message}
4923
5041
  `));
4924
5042
  process.exit(1);
4925
5043
  }
4926
- console.log(chalk10.gray(` Loaded environment from: ${envPath}`));
5044
+ console.log(chalk11.gray(` Loaded environment from: ${envPath}`));
4927
5045
  }
4928
5046
  var defaultEnvPath = resolve4(process.cwd(), ".env");
4929
5047
  if (existsSync5(defaultEnvPath)) {
4930
5048
  loadDotenv({ path: defaultEnvPath });
4931
5049
  }
4932
- var program = new Command10();
5050
+ var program = new Command11();
4933
5051
  program.name("agent-health").description("Agent Health Evaluation Framework - Evaluate and monitor AI agent performance").version(version).enablePositionalOptions().passThroughOptions().configureHelp({
4934
5052
  sortSubcommands: false,
4935
5053
  // Hide default command list — replaced by grouped custom help below
@@ -4942,81 +5060,95 @@ program.name("agent-health").description("Agent Health Evaluation Framework - Ev
4942
5060
  if (desc) {
4943
5061
  output.push(desc, "");
4944
5062
  }
4945
- output.push(`${chalk10.cyan.bold("Usage:")} ${helper.commandUsage(cmd)}`, "");
5063
+ output.push(`${chalk11.cyan.bold("Usage:")} ${helper.commandUsage(cmd)}`, "");
4946
5064
  const optionList = helper.visibleOptions(cmd).map((opt) => {
4947
5065
  const term = helper.optionTerm(opt);
4948
5066
  const desc2 = helper.optionDescription(opt);
4949
5067
  return ` ${term.padEnd(termWidth)} ${desc2}`;
4950
5068
  }).join("\n");
4951
5069
  if (optionList) {
4952
- output.push(`${chalk10.cyan.bold("Options:")}`, optionList, "");
5070
+ output.push(`${chalk11.cyan.bold("Options:")}`, optionList, "");
4953
5071
  }
4954
5072
  return output.join("\n");
4955
5073
  }
4956
5074
  });
4957
5075
  program.addHelpText("after", `
4958
- ${chalk10.cyan.bold("Getting Started:")}
4959
- ${chalk10.yellow("agent-health")} Launch the web UI and evaluation server
4960
- ${chalk10.yellow("agent-health init")} Generate an agent-health.config.ts file
4961
- ${chalk10.yellow("agent-health doctor")} Verify your setup (AWS creds, OpenSearch, agents)
5076
+ ${chalk11.cyan.bold("Getting Started:")}
5077
+ ${chalk11.yellow("agent-health")} Launch the web UI and evaluation server
5078
+ ${chalk11.yellow("agent-health init")} Generate an agent-health.config.ts file
5079
+ ${chalk11.yellow("agent-health doctor")} Verify your setup (AWS creds, OpenSearch, agents)
5080
+
5081
+ ${chalk11.cyan.bold("Running Evaluations:")}
5082
+ ${chalk11.yellow("agent-health run")} ${chalk11.gray("-t <case> -a <agent>")} Run a single test case against an agent
5083
+ ${chalk11.yellow("agent-health benchmark")} ${chalk11.gray("-f <file>")} Run a full benchmark from a test cases JSON file
5084
+ ${chalk11.yellow("agent-health benchmark")} ${chalk11.gray("-b <id>")} Re-run an existing benchmark
4962
5085
 
4963
- ${chalk10.cyan.bold("Running Evaluations:")}
4964
- ${chalk10.yellow("agent-health run")} ${chalk10.gray("-t <case> -a <agent>")} Run a single test case against an agent
4965
- ${chalk10.yellow("agent-health benchmark")} ${chalk10.gray("-f <file>")} Run a full benchmark from a test cases JSON file
4966
- ${chalk10.yellow("agent-health benchmark")} ${chalk10.gray("-b <id>")} Re-run an existing benchmark
5086
+ ${chalk11.cyan.bold("Viewing Results:")}
5087
+ ${chalk11.yellow("agent-health list")} ${chalk11.gray("agents|benchmarks|...")} List agents, connectors, test cases, or benchmarks
5088
+ ${chalk11.yellow("agent-health report")} ${chalk11.gray("-b <benchmark>")} Generate an HTML/PDF/JSON report
5089
+ ${chalk11.yellow("agent-health export")} ${chalk11.gray("-b <benchmark>")} Export test cases as re-importable JSON
5090
+ ${chalk11.yellow("agent-health compare-services")} ${chalk11.gray("-s A B")} Compare error patterns between services
4967
5091
 
4968
- ${chalk10.cyan.bold("Viewing Results:")}
4969
- ${chalk10.yellow("agent-health list")} ${chalk10.gray("agents|benchmarks|...")} List agents, connectors, test cases, or benchmarks
4970
- ${chalk10.yellow("agent-health report")} ${chalk10.gray("-b <benchmark>")} Generate an HTML/PDF/JSON report
4971
- ${chalk10.yellow("agent-health export")} ${chalk10.gray("-b <benchmark>")} Export test cases as re-importable JSON
4972
- ${chalk10.yellow("agent-health compare-services")} ${chalk10.gray("-s A B")} Compare error patterns between services
5092
+ ${chalk11.cyan.bold("Remote Servers:")}
5093
+ ${chalk11.yellow("agent-health remote add")} ${chalk11.gray("--name <n> --url <u>")} Add a remote server
5094
+ ${chalk11.yellow("agent-health remote list")} List configured remote servers
5095
+ ${chalk11.yellow("agent-health remote test")} Test connectivity to all remotes
4973
5096
 
4974
- ${chalk10.cyan.bold("Maintenance:")}
4975
- ${chalk10.yellow("agent-health migrate")} Migrate legacy benchmark data to current format
4976
- ${chalk10.yellow("agent-health serve")} Start the server (same as default, explicit command)
5097
+ ${chalk11.cyan.bold("Maintenance:")}
5098
+ ${chalk11.yellow("agent-health migrate")} Migrate legacy benchmark data to current format
5099
+ ${chalk11.yellow("agent-health serve")} Start the server (same as default, explicit command)
4977
5100
 
4978
- ${chalk10.cyan.bold("Examples:")}
4979
- ${chalk10.gray("$")} npx @opensearch-project/agent-health
4980
- ${chalk10.gray("$")} npx @opensearch-project/agent-health --port 8080 --no-browser
4981
- ${chalk10.gray("$")} npx @opensearch-project/agent-health run -t "RCA for 500 errors" -a langgraph
4982
- ${chalk10.gray("$")} npx @opensearch-project/agent-health benchmark -f ./test-cases.json -a my-agent
4983
- ${chalk10.gray("$")} npx @opensearch-project/agent-health list agents
4984
- ${chalk10.gray("$")} npx @opensearch-project/agent-health report -b bench-123 -f pdf -o report.pdf
5101
+ ${chalk11.cyan.bold("Examples:")}
5102
+ ${chalk11.gray("$")} npx @opensearch-project/agent-health
5103
+ ${chalk11.gray("$")} npx @opensearch-project/agent-health --port 8080 --no-browser
5104
+ ${chalk11.gray("$")} npx @opensearch-project/agent-health run -t "RCA for 500 errors" -a langgraph
5105
+ ${chalk11.gray("$")} npx @opensearch-project/agent-health benchmark -f ./test-cases.json -a my-agent
5106
+ ${chalk11.gray("$")} npx @opensearch-project/agent-health list agents
5107
+ ${chalk11.gray("$")} npx @opensearch-project/agent-health report -b bench-123 -f pdf -o report.pdf
5108
+ ${chalk11.gray("$")} npx @opensearch-project/agent-health serve --headless --api-key sk-secret
4985
5109
  `);
4986
- program.option("-p, --port <number>", "Server port", "4001").option("-e, --env-file <path>", "Load environment variables from file (e.g., .env)").option("--no-browser", "Do not open browser automatically");
5110
+ program.option("-p, --port <number>", "Server port", "4001").option("-e, --env-file <path>", "Load environment variables from file (e.g., .env)").option("--no-browser", "Do not open browser automatically").option("--headless", "Run API server only (no frontend, no browser)").option("--api-key <key>", "Require API key for coding-agents endpoints");
4987
5111
  program.action(async (options) => {
4988
- console.log(chalk10.cyan.bold(`
5112
+ console.log(chalk11.cyan.bold(`
4989
5113
  Agent Health v${version} - AI Agent Evaluation Framework
4990
5114
  `));
4991
- console.log(chalk10.gray(` Working directory: ${process.cwd()}`));
4992
- console.log(chalk10.gray(` Package directory: ${__dirname3}`));
5115
+ console.log(chalk11.gray(` Working directory: ${process.cwd()}`));
5116
+ console.log(chalk11.gray(` Package directory: ${__dirname3}`));
4993
5117
  if (options.envFile) {
4994
5118
  loadEnvFile(options.envFile);
4995
5119
  } else if (existsSync5(defaultEnvPath)) {
4996
- console.log(chalk10.gray(" Auto-loaded .env from current directory"));
5120
+ console.log(chalk11.gray(" Auto-loaded .env from current directory"));
4997
5121
  }
4998
5122
  const port = parseInt(options.port, 10);
4999
- const spinner = ora5("Starting server...").start();
5123
+ const headless = options.headless || false;
5124
+ const spinner = ora5(headless ? "Starting headless API server..." : "Starting server...").start();
5000
5125
  try {
5001
- await startServer({ port });
5002
- spinner.succeed("Server started");
5003
- console.log(chalk10.gray("\n Configuration:"));
5004
- console.log(chalk10.gray(` Storage: Sample data (configure OpenSearch for persistence)`));
5005
- console.log(chalk10.gray(` Agent: Select in UI (Demo Agent for mock, real agents require endpoints)`));
5006
- console.log(chalk10.gray(` Judge: Select in UI (Demo Judge for mock, Bedrock requires AWS creds)
5126
+ await startServer({ port, headless, apiKey: options.apiKey });
5127
+ spinner.succeed(headless ? "Headless API server started" : "Server started");
5128
+ if (headless) {
5129
+ console.log(chalk11.green(`
5130
+ API server running on http://0.0.0.0:${port}`));
5131
+ if (options.apiKey) console.log(chalk11.gray(" API key authentication enabled"));
5132
+ console.log(chalk11.gray(" Mode: headless (API only, no frontend)\n"));
5133
+ } else {
5134
+ console.log(chalk11.gray("\n Configuration:"));
5135
+ console.log(chalk11.gray(` Storage: Sample data (configure OpenSearch for persistence)`));
5136
+ console.log(chalk11.gray(` Agent: Select in UI (Demo Agent for mock, real agents require endpoints)`));
5137
+ console.log(chalk11.gray(` Judge: Select in UI (Demo Judge for mock, Bedrock requires AWS creds)
5007
5138
  `));
5008
- const url = `http://localhost:${port}`;
5009
- console.log(chalk10.green(` Server running at ${chalk10.bold(url)}
5139
+ const url = `http://localhost:${port}`;
5140
+ console.log(chalk11.green(` Server running at ${chalk11.bold(url)}
5010
5141
  `));
5011
- console.log(chalk10.green(` Demo data loaded`));
5012
- if (options.browser !== false) {
5013
- console.log(chalk10.gray(" Opening browser..."));
5014
- await open(url);
5142
+ console.log(chalk11.green(` Demo data loaded`));
5143
+ if (options.browser !== false) {
5144
+ console.log(chalk11.gray(" Opening browser..."));
5145
+ await open(url);
5146
+ }
5015
5147
  }
5016
- console.log(chalk10.gray(" Press Ctrl+C to stop\n"));
5148
+ console.log(chalk11.gray(" Press Ctrl+C to stop\n"));
5017
5149
  } catch (error) {
5018
5150
  spinner.fail("Failed to start server");
5019
- console.error(chalk10.red(`
5151
+ console.error(chalk11.red(`
5020
5152
  Error: ${error instanceof Error ? error.message : error}
5021
5153
  `));
5022
5154
  process.exit(1);
@@ -5031,26 +5163,34 @@ program.addCommand(createDoctorCommand());
5031
5163
  program.addCommand(createInitCommand());
5032
5164
  program.addCommand(createMigrateCommand());
5033
5165
  program.addCommand(createCompareServicesCommand());
5034
- program.command("serve").description("Start the Agent Health server (same as default action)").option("-p, --port <number>", "Server port", "4001").option("--no-browser", "Do not open browser automatically").action(async (options) => {
5035
- console.log(chalk10.cyan.bold(`
5166
+ program.addCommand(createRemoteCommand());
5167
+ program.command("serve").description("Start the Agent Health server (same as default action)").option("-p, --port <number>", "Server port", "4001").option("--no-browser", "Do not open browser automatically").option("--headless", "Run API server only (no frontend, no browser)").option("--api-key <key>", "Require API key for coding-agents endpoints").action(async (options) => {
5168
+ console.log(chalk11.cyan.bold(`
5036
5169
  Agent Health v${version} - AI Agent Evaluation Framework
5037
5170
  `));
5038
5171
  const port = parseInt(options.port, 10);
5039
- const spinner = ora5("Starting server...").start();
5172
+ const headless = options.headless || false;
5173
+ const spinner = ora5(headless ? "Starting headless API server..." : "Starting server...").start();
5040
5174
  try {
5041
- await startServer({ port });
5042
- spinner.succeed("Server started");
5175
+ await startServer({ port, headless, apiKey: options.apiKey });
5176
+ spinner.succeed(headless ? "Headless API server started" : "Server started");
5043
5177
  const url = `http://localhost:${port}`;
5044
- console.log(chalk10.green(` Server running at ${chalk10.bold(url)}
5178
+ if (headless) {
5179
+ console.log(chalk11.green(` API server running on http://0.0.0.0:${port}`));
5180
+ if (options.apiKey) console.log(chalk11.gray(" API key authentication enabled"));
5181
+ console.log(chalk11.gray(" Mode: headless (API only, no frontend)\n"));
5182
+ } else {
5183
+ console.log(chalk11.green(` Server running at ${chalk11.bold(url)}
5045
5184
  `));
5046
- if (options.browser !== false) {
5047
- console.log(chalk10.gray(" Opening browser..."));
5048
- await open(url);
5185
+ if (options.browser !== false) {
5186
+ console.log(chalk11.gray(" Opening browser..."));
5187
+ await open(url);
5188
+ }
5049
5189
  }
5050
- console.log(chalk10.gray(" Press Ctrl+C to stop\n"));
5190
+ console.log(chalk11.gray(" Press Ctrl+C to stop\n"));
5051
5191
  } catch (error) {
5052
5192
  spinner.fail("Failed to start server");
5053
- console.error(chalk10.red(`
5193
+ console.error(chalk11.red(`
5054
5194
  Error: ${error instanceof Error ? error.message : error}
5055
5195
  `));
5056
5196
  process.exit(1);
@@ -5059,15 +5199,15 @@ program.command("serve").description("Start the Agent Health server (same as def
5059
5199
  program.on("command:*", (operands) => {
5060
5200
  const unknownCommand = operands[0];
5061
5201
  const availableCommands = program.commands.map((cmd) => cmd.name());
5062
- console.error(chalk10.red(`
5202
+ console.error(chalk11.red(`
5063
5203
  Error: Unknown command '${unknownCommand}'`));
5064
5204
  console.log("");
5065
- console.log(chalk10.cyan(" Available commands:"));
5205
+ console.log(chalk11.cyan(" Available commands:"));
5066
5206
  for (const cmd of availableCommands) {
5067
- console.log(chalk10.gray(` - ${cmd}`));
5207
+ console.log(chalk11.gray(` - ${cmd}`));
5068
5208
  }
5069
5209
  console.log("");
5070
- console.log(chalk10.gray(` Run ${chalk10.cyan("agent-health --help")} for usage information.
5210
+ console.log(chalk11.gray(` Run ${chalk11.cyan("agent-health --help")} for usage information.
5071
5211
  `));
5072
5212
  process.exitCode = 1;
5073
5213
  });