Skip to main content

harn_cli/
lib.rs

1#![recursion_limit = "256"]
2
3pub mod acp;
4mod bootstrap;
5pub mod cli;
6mod cli_bytecode;
7pub mod commands;
8mod compiler_context;
9#[doc(hidden)]
10pub mod dispatch;
11mod entrypoint;
12pub mod env_guard;
13mod eval_cli;
14mod exit;
15pub mod format;
16pub mod json_envelope;
17pub use commands::check::{
18    decode_lint_envelope, decode_lint_json, lint_json_schema, DecodedLintEnvelope, LintDecodeError,
19    LintDecodeOptions, LintReportWire,
20};
21mod net;
22pub mod package;
23mod path_policy;
24mod provider_bootstrap;
25mod provider_info;
26mod run_records;
27mod runtime;
28pub mod skill_loader;
29pub mod skill_provenance;
30mod source_exec;
31pub mod test_report;
32pub mod test_runner;
33pub mod test_timing {
34    pub use harn_test_runner::DurationSummary;
35}
36#[doc(hidden)]
37pub mod tests;
38mod typecheck_imports;
39pub use commands::dispatch_explain::DISPATCH_AUDIT_SCHEMA_VERSION;
40pub(crate) use compiler_context::{
41    compiler_for_source, compiler_with_imported_enum_candidates,
42    ensure_builtin_signatures_installed,
43};
44pub use harn_skills::{get_embedded_skill, list_embedded_skills, EmbeddedSkill, SkillFrontmatter};
45// Items that used to live directly in this file. A bare item at the crate
46// root is visible crate-wide, so re-exporting the modules' `pub(crate)` items
47// here keeps every existing `crate::<item>` path resolving unchanged.
48pub(crate) use self::entrypoint::*;
49pub(crate) use self::eval_cli::*;
50pub(crate) use self::exit::*;
51pub(crate) use self::provider_info::*;
52pub(crate) use self::run_records::*;
53pub(crate) use self::source_exec::*;
54
55use clap::{error::ErrorKind, CommandFactory, Parser as ClapParser};
56use std::path::{Path, PathBuf};
57use std::sync::{Arc, Once};
58use std::{env, fs, panic, process, thread};
59
60use cli::{
61    Cli, Command, CompletionShell, EvalCommand, GuardCommand, MergeCaptainCommand,
62    MergeCaptainMockCommand, ModelInfoArgs, PackageArtifactsCommand, PackageCacheCommand,
63    PackageCommand, PackageScaffoldCommand, PgCommand, ProviderCommand, SkillCommand,
64    SkillKeyCommand, SkillTrustCommand, TimeCommand, ToolCommand,
65};
66use harn_lexer::Lexer;
67use harn_modules::project_config;
68use harn_parser::{DiagnosticSeverity, Parser, TypeChecker};
69use runtime::{build_cli_runtime, cli_runtime_mode, CliRuntimeMode};
70/// The CLI's name for the shared runtime stack contract. Kept as a re-export
71/// rather than its own number so the CLI and the serve hosts cannot drift.
72pub const CLI_RUNTIME_STACK_SIZE: usize = harn_vm::RUNTIME_STACK_SIZE;
73static BROKEN_PIPE_PANIC_HOOK: Once = Once::new();
74
75#[cfg(feature = "hostlib")]
76pub(crate) fn install_default_hostlib(vm: &mut harn_vm::Vm) {
77    let _ = harn_hostlib::install_default(vm);
78    // The `rules` capability lives in its own crate (it depends on
79    // `harn-rules`, which depends on `harn-hostlib`, so it can't ship inside
80    // `install_default`). Wire it in alongside the defaults.
81    harn_rules_hostlib::install(vm);
82}
83
84#[cfg(not(feature = "hostlib"))]
85pub(crate) fn install_default_hostlib(_vm: &mut harn_vm::Vm) {}
86
87/// Entry point used by `src/main.rs`. Hosts the CLI runtime thread and
88/// drives the async dispatcher in `async_main`.
89pub fn run() {
90    install_broken_pipe_panic_hook();
91    harn_vm::initialize_runtime_assets();
92    let raw_args = normalize_serve_args(bootstrap::args_after_pre_runtime_command());
93    // Defeat rlib dead-code stripping of `#[harn_builtin]`-emitted statics
94    // (linkme issue #36). Without this touch the linker can drop every
95    // builtin's distributed-slice entry, leaving `ALL_BUILTIN_DEFS` empty
96    // and surfacing as a swarm of `HARN-NAM-002` errors at first call.
97    harn_vm::stdlib::force_link();
98
99    ensure_builtin_signatures_installed();
100
101    let runtime_mode = cli_runtime_mode(&raw_args);
102
103    let handle = thread::Builder::new()
104        .name("harn-cli".to_string())
105        .stack_size(CLI_RUNTIME_STACK_SIZE)
106        .spawn(move || {
107            let runtime = build_cli_runtime(runtime_mode);
108            runtime.block_on(async_main(raw_args, runtime_mode));
109            // Drain any queued OTLP exports while the tokio runtime
110            // is still alive. The auto-registered `OtelSink` uses a
111            // batch processor with `runtime::Tokio`; if we let the
112            // runtime drop before this call, in-flight spans never
113            // reach the configured collector. No-op when OTel is not
114            // configured.
115            if let Err(error) = harn_vm::events::shutdown_otel_sink() {
116                eprintln!("[harn] OTel exporter shutdown failed: {error}");
117            }
118        })
119        .unwrap_or_else(|error| {
120            eprintln!("failed to start CLI runtime thread: {error}");
121            process::exit(1);
122        });
123
124    if let Err(payload) = handle.join() {
125        if runtime::is_broken_pipe_panic_payload(payload.as_ref()) {
126            process::exit(0);
127        }
128        std::panic::resume_unwind(payload);
129    }
130}
131
132fn install_broken_pipe_panic_hook() {
133    BROKEN_PIPE_PANIC_HOOK.call_once(|| {
134        let previous = panic::take_hook();
135        panic::set_hook(Box::new(move |info| {
136            if runtime::is_broken_pipe_panic_payload(info.payload()) {
137                return;
138            }
139            previous(info);
140        }));
141    });
142}