use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
#[derive(Debug, Parser)]
#[command(
name = "harn",
about = "The agent harness language",
version,
disable_help_subcommand = false,
arg_required_else_help = true
)]
pub(crate) struct Cli {
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(Debug, Subcommand)]
pub(crate) enum Command {
#[command(long_about = "\
Execute a .harn file or an inline expression.
USAGE
harn run script.harn
harn run -e 'println(\"hello\")'
harn run script.harn -- arg1 arg2 (script reads `argv` as list<string>)
CONCURRENCY
Harn supports first-class concurrency primitives:
- spawn { ... } — launch a task, return a handle
- parallel each LIST — concurrent map
- parallel settle LIST — concurrent map, collect Ok/Err
- parallel N — N-way fan-out
- with { max_concurrent: N } — cap in-flight workers
- channels, retry, select
https://harn.burincode.com/concurrency.html
LLM THROTTLING
Providers can be rate-limited via `rpm:` in harn.toml / providers.toml
or via `HARN_RATE_LIMIT_<PROVIDER>=N`. Rate limits control throughput
(RPM); `max_concurrent` on `parallel` caps simultaneous in-flight jobs.
SCRIPTING
LLM-readable one-pager: https://harn.burincode.com/docs/llm/harn-quickref.md
Human cheatsheet: https://harn.burincode.com/scripting-cheatsheet.html
Full docs: https://harn.burincode.com/
")]
Run(RunArgs),
Check(CheckArgs),
Contracts(ContractsArgs),
Lint(PathTargetsArgs),
Fmt(FmtArgs),
Test(TestArgs),
Init(InitArgs),
New(InitArgs),
Doctor(DoctorArgs),
Serve(ServeArgs),
Acp(AcpArgs),
McpServe(McpServeArgs),
Mcp(McpArgs),
Watch(WatchArgs),
Portal(PortalArgs),
Runs(RunsArgs),
Replay(ReplayArgs),
Eval(EvalArgs),
Repl,
Bench(BenchArgs),
Viz(VizArgs),
Install,
Add(AddArgs),
ModelInfo(ModelInfoArgs),
Version,
#[command(hide = true, name = "dump-highlight-keywords")]
DumpHighlightKeywords(DumpHighlightKeywordsArgs),
}
#[derive(Debug, Args)]
pub(crate) struct RunArgs {
#[arg(long)]
pub trace: bool,
#[arg(long, conflicts_with = "allow")]
pub deny: Option<String>,
#[arg(long, conflicts_with = "deny")]
pub allow: Option<String>,
#[arg(short = 'e')]
pub eval: Option<String>,
pub file: Option<String>,
#[arg(last = true)]
pub argv: Vec<String>,
}
#[derive(Debug, Args)]
pub(crate) struct CheckArgs {
#[arg(long = "host-capabilities")]
pub host_capabilities: Option<String>,
#[arg(long = "bundle-root")]
pub bundle_root: Option<String>,
#[arg(long = "strict-types")]
pub strict_types: bool,
#[arg(required = true)]
pub targets: Vec<String>,
}
#[derive(Debug, Args)]
pub(crate) struct ContractsArgs {
#[command(subcommand)]
pub command: ContractsCommand,
}
#[derive(Debug, Subcommand)]
pub(crate) enum ContractsCommand {
Builtins(ContractsOutputArgs),
HostCapabilities(ContractsHostCapabilitiesArgs),
Bundle(ContractsBundleArgs),
}
#[derive(Debug, Args)]
pub(crate) struct ContractsOutputArgs {
#[arg(long, default_value_t = true, action = ArgAction::Set)]
pub pretty: bool,
}
#[derive(Debug, Args)]
pub(crate) struct ContractsHostCapabilitiesArgs {
#[arg(long = "host-capabilities")]
pub host_capabilities: Option<String>,
#[arg(long, default_value_t = true, action = ArgAction::Set)]
pub pretty: bool,
}
#[derive(Debug, Args)]
pub(crate) struct ContractsBundleArgs {
#[arg(long = "host-capabilities")]
pub host_capabilities: Option<String>,
#[arg(long = "bundle-root")]
pub bundle_root: Option<String>,
#[arg(long)]
pub verify: bool,
#[arg(long, default_value_t = true, action = ArgAction::Set)]
pub pretty: bool,
#[arg(required = true)]
pub targets: Vec<String>,
}
#[derive(Debug, Args)]
pub(crate) struct PathTargetsArgs {
#[arg(long)]
pub fix: bool,
#[arg(required = true)]
pub targets: Vec<String>,
}
#[derive(Debug, Args)]
pub(crate) struct FmtArgs {
#[arg(long)]
pub check: bool,
#[arg(long = "line-width", default_value_t = 100)]
pub line_width: usize,
#[arg(required = true)]
pub targets: Vec<String>,
}
#[derive(Debug, Args)]
pub(crate) struct TestArgs {
#[arg(long)]
pub filter: Option<String>,
#[arg(long)]
pub junit: Option<String>,
#[arg(long, default_value_t = 30_000)]
pub timeout: u64,
#[arg(long)]
pub parallel: bool,
#[arg(long)]
pub watch: bool,
#[arg(short = 'v', long = "verbose", action = ArgAction::SetTrue)]
pub verbose: bool,
#[arg(long, action = ArgAction::SetTrue)]
pub timing: bool,
#[arg(long)]
pub record: bool,
#[arg(long)]
pub replay: bool,
pub target: Option<String>,
pub selection: Option<String>,
}
#[derive(Debug, Args)]
pub(crate) struct InitArgs {
pub name: Option<String>,
#[arg(long, value_enum, default_value_t = ProjectTemplate::Basic)]
pub template: ProjectTemplate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub(crate) enum ProjectTemplate {
Basic,
Agent,
#[value(name = "mcp-server")]
McpServer,
Eval,
}
#[derive(Debug, Args)]
pub(crate) struct DoctorArgs {
#[arg(long)]
pub no_network: bool,
}
#[derive(Debug, Args)]
pub(crate) struct VizArgs {
pub file: String,
#[arg(short, long)]
pub output: Option<String>,
}
#[derive(Debug, Args)]
pub(crate) struct BenchArgs {
pub file: String,
#[arg(short = 'n', long, default_value_t = 10)]
pub iterations: usize,
}
#[derive(Debug, Args)]
pub(crate) struct ServeArgs {
#[arg(long, default_value_t = 8080)]
pub port: u16,
pub file: String,
}
#[derive(Debug, Args)]
pub(crate) struct AcpArgs {
pub pipeline: Option<String>,
}
#[derive(Debug, Args)]
pub(crate) struct McpServeArgs {
pub file: String,
}
#[derive(Debug, Args)]
pub(crate) struct McpArgs {
#[command(subcommand)]
pub command: McpCommand,
}
#[derive(Debug, Subcommand)]
pub(crate) enum McpCommand {
Login(McpLoginArgs),
Logout(McpServerRefArgs),
Status(McpServerRefArgs),
RedirectUri,
}
#[derive(Debug, Args)]
pub(crate) struct McpLoginArgs {
pub target: Option<String>,
#[arg(long)]
pub url: Option<String>,
#[arg(long = "client-id")]
pub client_id: Option<String>,
#[arg(long = "client-secret")]
pub client_secret: Option<String>,
#[arg(long = "scope")]
pub scope: Option<String>,
#[arg(
long = "redirect-uri",
default_value = "http://127.0.0.1:9783/oauth/callback"
)]
pub redirect_uri: String,
}
#[derive(Debug, Args)]
pub(crate) struct McpServerRefArgs {
pub target: Option<String>,
#[arg(long)]
pub url: Option<String>,
}
#[derive(Debug, Args)]
pub(crate) struct WatchArgs {
#[arg(long, conflicts_with = "allow")]
pub deny: Option<String>,
#[arg(long, conflicts_with = "deny")]
pub allow: Option<String>,
pub file: String,
}
#[derive(Debug, Args)]
pub(crate) struct PortalArgs {
#[arg(long, default_value = ".harn-runs")]
pub dir: String,
#[arg(long, default_value = "127.0.0.1")]
pub host: String,
#[arg(long, default_value_t = 4721)]
pub port: u16,
#[arg(long, default_value_t = true, action = ArgAction::Set)]
pub open: bool,
}
#[derive(Debug, Args)]
pub(crate) struct RunsArgs {
#[command(subcommand)]
pub command: RunsCommand,
}
#[derive(Debug, Subcommand)]
pub(crate) enum RunsCommand {
Inspect(RunsInspectArgs),
}
#[derive(Debug, Args)]
pub(crate) struct RunsInspectArgs {
pub path: String,
#[arg(long)]
pub compare: Option<String>,
}
#[derive(Debug, Args)]
pub(crate) struct ReplayArgs {
pub path: String,
}
#[derive(Debug, Args)]
pub(crate) struct EvalArgs {
pub path: String,
#[arg(long)]
pub compare: Option<String>,
}
#[derive(Debug, Args)]
pub(crate) struct DumpHighlightKeywordsArgs {
#[arg(long, default_value = "docs/theme/harn-keywords.js")]
pub output: String,
#[arg(long)]
pub check: bool,
}
#[derive(Debug, Args)]
pub(crate) struct AddArgs {
pub name: String,
#[arg(long, conflicts_with = "path")]
pub git: Option<String>,
#[arg(long)]
pub tag: Option<String>,
#[arg(long, conflicts_with = "git")]
pub path: Option<String>,
}
#[derive(Debug, Args)]
pub(crate) struct ModelInfoArgs {
pub model: String,
}
#[cfg(test)]
mod tests {
use super::{Cli, Command, McpCommand, ProjectTemplate, RunsCommand};
use clap::Parser;
#[test]
fn test_parses_conformance_target_selection() {
let cli = Cli::parse_from([
"harn",
"test",
"conformance",
"tests/worktree_runtime.harn",
"--verbose",
]);
let Command::Test(args) = cli.command.unwrap() else {
panic!("expected test command");
};
assert_eq!(args.target.as_deref(), Some("conformance"));
assert_eq!(
args.selection.as_deref(),
Some("tests/worktree_runtime.harn")
);
assert!(args.verbose);
}
#[test]
fn test_run_rejects_deny_allow_conflict() {
let err = Cli::try_parse_from([
"harn",
"run",
"--deny",
"read_file",
"--allow",
"exec",
"main.harn",
])
.unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
}
#[test]
fn test_parses_mcp_login_flags() {
let cli = Cli::parse_from([
"harn",
"mcp",
"login",
"notion",
"--url",
"https://example.com/mcp",
"--client-id",
"abc",
]);
let Command::Mcp(args) = cli.command.unwrap() else {
panic!("expected mcp command");
};
let McpCommand::Login(login) = args.command else {
panic!("expected mcp login");
};
assert_eq!(login.target.as_deref(), Some("notion"));
assert_eq!(login.url.as_deref(), Some("https://example.com/mcp"));
assert_eq!(login.client_id.as_deref(), Some("abc"));
}
#[test]
fn test_parses_runs_inspect_compare() {
let cli = Cli::parse_from([
"harn",
"runs",
"inspect",
"run.json",
"--compare",
"baseline.json",
]);
let Command::Runs(args) = cli.command.unwrap() else {
panic!("expected runs command");
};
let RunsCommand::Inspect(inspect) = args.command;
assert_eq!(inspect.path, "run.json");
assert_eq!(inspect.compare.as_deref(), Some("baseline.json"));
}
#[test]
fn test_parses_portal_flags() {
let cli = Cli::parse_from([
"harn", "portal", "--dir", "runs", "--host", "0.0.0.0", "--port", "4900", "--open",
"false",
]);
let Command::Portal(args) = cli.command.unwrap() else {
panic!("expected portal command");
};
assert_eq!(args.dir, "runs");
assert_eq!(args.host, "0.0.0.0");
assert_eq!(args.port, 4900);
assert!(!args.open);
}
#[test]
fn test_parses_new_template() {
let cli = Cli::parse_from(["harn", "new", "review-bot", "--template", "agent"]);
let Command::New(args) = cli.command.unwrap() else {
panic!("expected new command");
};
assert_eq!(args.name.as_deref(), Some("review-bot"));
assert_eq!(args.template, ProjectTemplate::Agent);
}
#[test]
fn test_parses_doctor_flags() {
let cli = Cli::parse_from(["harn", "doctor", "--no-network"]);
let Command::Doctor(args) = cli.command.unwrap() else {
panic!("expected doctor command");
};
assert!(args.no_network);
}
#[test]
fn test_parses_viz_args() {
let cli = Cli::parse_from(["harn", "viz", "main.harn", "--output", "graph.mmd"]);
let Command::Viz(args) = cli.command.unwrap() else {
panic!("expected viz command");
};
assert_eq!(args.file, "main.harn");
assert_eq!(args.output.as_deref(), Some("graph.mmd"));
}
#[test]
fn test_parses_bench_args() {
let cli = Cli::parse_from(["harn", "bench", "main.harn", "--iterations", "25"]);
let Command::Bench(args) = cli.command.unwrap() else {
panic!("expected bench command");
};
assert_eq!(args.file, "main.harn");
assert_eq!(args.iterations, 25);
}
#[test]
fn test_parses_model_info_args() {
let cli = Cli::parse_from(["harn", "model-info", "tog-gemma4-31b"]);
let Command::ModelInfo(args) = cli.command.unwrap() else {
panic!("expected model-info command");
};
assert_eq!(args.model, "tog-gemma4-31b");
}
}