use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand, ValueEnum};
use llmwiki_tooling::cmd::lint::SeverityFilter;
use llmwiki_tooling::config::WikiConfig;
use llmwiki_tooling::error::WikiError;
use llmwiki_tooling::wiki::{Wiki, WikiRoot};
#[derive(Parser)]
#[command(name = "llmwiki-tool", about = "Manage LLM wiki knowledge bases")]
struct Cli {
#[arg(long, global = true)]
root: Option<PathBuf>,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Links {
#[command(subcommand)]
action: LinksAction,
},
Lint {
#[arg(long, value_enum, default_value_t = SeverityArg::All)]
severity: SeverityArg,
},
Rename {
old: String,
new: String,
#[arg(long)]
write: bool,
},
Refs {
#[command(subcommand)]
action: RefsAction,
},
Sections {
#[command(subcommand)]
action: SectionsAction,
},
Frontmatter {
#[command(subcommand)]
action: FrontmatterAction,
},
Scan,
Setup {
#[command(subcommand)]
action: SetupAction,
},
}
#[derive(Subcommand)]
enum SetupAction {
Prompt,
ExampleConfig,
Init {
#[arg(long)]
show: bool,
#[arg(long, short)]
force: bool,
},
}
#[derive(Clone, Copy, ValueEnum)]
enum SeverityArg {
All,
Error,
Warn,
}
impl From<SeverityArg> for SeverityFilter {
fn from(arg: SeverityArg) -> Self {
match arg {
SeverityArg::All => Self::All,
SeverityArg::Error => Self::ErrorOnly,
SeverityArg::Warn => Self::WarnOnly,
}
}
}
#[derive(Subcommand)]
enum LinksAction {
Check,
Fix {
#[arg(long)]
write: bool,
},
Broken,
Orphans,
}
#[derive(Subcommand)]
enum RefsAction {
To { page: String },
From { page: String },
Graph,
}
#[derive(Subcommand)]
enum FrontmatterAction {
Get {
file: PathBuf,
field: Option<String>,
},
Set {
file: PathBuf,
field: String,
value: String,
},
}
#[derive(Subcommand)]
enum SectionsAction {
Rename {
old: String,
new: String,
#[arg(long)]
dirs: Option<Vec<String>>,
#[arg(long)]
write: bool,
},
}
fn resolve_root(cli_root: Option<PathBuf>) -> Result<WikiRoot, WikiError> {
match cli_root {
Some(path) => WikiRoot::from_path(path),
None => {
let cwd = std::env::current_dir().map_err(|_| WikiError::RootNotFound {
start: PathBuf::from("."),
})?;
WikiRoot::discover(&cwd)
}
}
}
fn run() -> Result<ExitCode, anyhow::Error> {
let cli = Cli::parse();
let root = resolve_root(cli.root)?;
match &cli.command {
Command::Scan => {
llmwiki_tooling::cmd::agent::scan(&root)?;
return Ok(ExitCode::SUCCESS);
}
Command::Setup { action } => {
match action {
SetupAction::Prompt => llmwiki_tooling::cmd::agent::setup(&root)?,
SetupAction::ExampleConfig => llmwiki_tooling::cmd::agent::example_config(),
SetupAction::Init { force, show } => llmwiki_tooling::cmd::init::init(&root, *force, *show)?,
}
return Ok(ExitCode::SUCCESS);
}
_ => {}
}
let config = WikiConfig::load_or_detect(root.path())?;
let mut wiki = Wiki::build(root, config)?;
match cli.command {
Command::Links { action } => match action {
LinksAction::Check => {
let count = llmwiki_tooling::cmd::links::check(&wiki)?;
if count > 0 {
eprintln!("{count} bare mention(s) found");
}
}
LinksAction::Fix { write } => {
let count = llmwiki_tooling::cmd::links::fix(&mut wiki, write)?;
if count > 0 && !write {
eprintln!("{count} bare mention(s) to fix. Use --write to apply.");
} else if count == 0 {
eprintln!("no bare mentions found");
}
}
LinksAction::Broken => {
let count = llmwiki_tooling::cmd::links::broken(&wiki)?;
if count > 0 {
eprintln!("{count} broken link(s) found");
return Ok(ExitCode::from(1));
}
}
LinksAction::Orphans => {
let count = llmwiki_tooling::cmd::links::orphans(&wiki)?;
if count > 0 {
eprintln!("{count} orphan page(s) found");
}
}
},
Command::Lint { severity } => {
let errors = llmwiki_tooling::cmd::lint::lint(&wiki, severity.into())?;
if errors > 0 {
return Ok(ExitCode::from(2));
}
}
Command::Rename { old, new, write } => {
llmwiki_tooling::cmd::rename::rename(&mut wiki, &old, &new, write)?;
}
Command::Refs { action } => match action {
RefsAction::To { page } => {
llmwiki_tooling::cmd::refs::refs_to(&wiki, &page)?;
}
RefsAction::From { page } => {
llmwiki_tooling::cmd::refs::refs_from(&wiki, &page)?;
}
RefsAction::Graph => {
llmwiki_tooling::cmd::refs::refs_graph(&wiki)?;
}
},
Command::Sections { action } => match action {
SectionsAction::Rename {
old,
new,
dirs,
write,
} => {
let count = llmwiki_tooling::cmd::sections::rename(&mut wiki, &old, &new, &dirs, write)?;
if count > 0 && !write {
eprintln!("{count} occurrence(s) to rename. Use --write to apply.");
} else if count == 0 {
eprintln!("no occurrences of '{}' found", old);
}
}
},
Command::Frontmatter { action } => match action {
FrontmatterAction::Get { file, field } => {
llmwiki_tooling::cmd::frontmatter_cmd::get(&wiki, &file, field.as_deref())?;
}
FrontmatterAction::Set { file, field, value } => {
llmwiki_tooling::cmd::frontmatter_cmd::set(&mut wiki, &file, &field, &value)?;
}
},
Command::Scan | Command::Setup { .. } => unreachable!(),
}
Ok(ExitCode::SUCCESS)
}
fn main() -> ExitCode {
match run() {
Ok(code) => code,
Err(e) => {
eprintln!("error: {e:#}");
ExitCode::FAILURE
}
}
}