use clap::Parser;
use std::path::PathBuf;
#[derive(Parser)]
#[command(
name = "ix",
version = env!("CARGO_PKG_VERSION"),
about = "High-performance, safety-aware code search engine for humans and agents.",
after_help = r#"USAGE:
Existence check: ix -c "pattern" → Single integer (count)
Location: ix -l "pattern" → Unique file paths
Contextual: ix -C 3 "pattern" → ±3 lines around match
Structured: ix --json "pattern" → JSON Lines output
Deterministic: ix --fresh "pattern" → Force rebuild + search
SEARCH MODES (mutually exclusive):
1. Literal (default): ix "timeout" → exact substring match
2. Word-boundary: ix -w "timeout" → whole-word match (finds "timeout" but not "timeoutExceeded")
3. Regex: ix --regex "err(or|no).*timeout" → full regex pattern
EXAMPLES:
Index the current directory:
ix --build
Search for a literal string:
ix "ConnectionTimeout"
Search for whole word "timeout":
ix -w timeout
Search using a Regular Expression:
ix --regex "err(or|no).*timeout"
Search in a specific directory without using the index:
ix --no-index "TODO" ./src
NOTES:
- Default is unlimited results (use -n N to cap at N results).
- Index stored in .ix/shard.ix relative to search path.
- Uses LLMOSafe for resource monitoring and back-pressure.
- Word-boundary (-w) uses regex internally but enforces whole-word semantics."#
)]
pub(crate) struct Cli {
#[arg(value_name = "PATTERN")]
pub(crate) pattern: Option<String>,
#[arg(value_name = "PATH", num_args = 0..)]
pub(crate) path: Vec<PathBuf>,
#[arg(
long,
value_name = "PATH",
num_args = 0..=1,
default_missing_value = ".",
help_heading = "Actions"
)]
pub(crate) build: Option<PathBuf>,
#[arg(short, long)]
pub(crate) regex: bool,
#[arg(short, long)]
pub(crate) ignore_case: bool,
#[arg(short = 'w', long)]
pub(crate) word: bool,
#[arg(long)]
pub(crate) json: bool,
#[arg(long)]
pub(crate) stats: bool,
#[arg(short, long)]
pub(crate) count: bool,
#[arg(short = 'l', long)]
pub(crate) files_only: bool,
#[arg(short = 'C', long, default_value = "0")]
pub(crate) context: usize,
#[arg(short = 'n', long, default_value = "0")]
pub(crate) max_results: usize,
#[arg(short = 't', long = "type")]
pub(crate) file_types: Vec<String>,
#[arg(short = 'z', long)]
pub(crate) decompress: bool,
#[arg(short = 'j', long, default_value = "0")]
pub(crate) threads: usize,
#[arg(short = 'U', long)]
pub(crate) multiline: bool,
#[arg(long)]
pub(crate) archive: bool,
#[arg(long)]
pub(crate) binary: bool,
#[arg(long, default_value = "100")]
pub(crate) max_file_size: u64,
#[arg(long)]
pub(crate) no_index: bool,
#[arg(long)]
pub(crate) fresh: bool,
#[arg(long)]
pub(crate) force: bool,
#[arg(long, conflicts_with = "build")]
pub(crate) stdin: bool,
#[arg(long, hide = true)]
pub(crate) daemon: bool,
#[arg(long, default_value = "0")]
pub(crate) chunk_size: usize,
#[arg(long, default_value = "0")]
pub(crate) chunk_overlap: usize,
#[command(subcommand)]
pub(crate) command: Option<Command>,
}
#[derive(clap::Subcommand)]
pub(crate) enum Command {
#[command(name = "service")]
Service {
#[command(subcommand)]
action: ServiceAction,
},
#[command(name = "stats")]
Stats {
#[arg(short = 'p', long = "path", value_name = "PATH", default_value = ".")]
path: PathBuf,
#[arg(long)]
json: bool,
},
}
#[derive(clap::Subcommand)]
pub(crate) enum ServiceAction {
Install {
#[arg(value_name = "PATH")]
path: Option<PathBuf>,
},
Start,
Stop,
Restart,
Status {
#[arg(value_name = "PATH")]
path: Option<PathBuf>,
#[arg(long)]
json: bool,
},
}
#[derive(Clone, Copy)]
pub(crate) struct SearchFlags {
pub(crate) is_regex: bool,
pub(crate) ignore_case: bool,
pub(crate) word_boundary: bool,
pub(crate) no_index: bool,
pub(crate) fresh: bool,
pub(crate) force: bool,
pub(crate) json: bool,
pub(crate) stats: bool,
pub(crate) count: bool,
pub(crate) files_only: bool,
pub(crate) decompress: bool,
pub(crate) multiline: bool,
pub(crate) archive: bool,
pub(crate) binary: bool,
}
pub(crate) struct SearchParams<'a> {
pub(crate) pattern: &'a str,
pub(crate) path: &'a std::path::Path,
pub(crate) flags: SearchFlags,
pub(crate) context: usize,
pub(crate) max_results: usize,
pub(crate) file_types: &'a [String],
pub(crate) max_file_size: u64,
pub(crate) chunk_size: usize,
pub(crate) chunk_overlap: usize,
}
pub(crate) struct CwdGuard {
original: PathBuf,
}
impl CwdGuard {
pub(crate) fn new(target: &std::path::Path) -> Result<Self, std::io::Error> {
let original = std::env::current_dir()?;
std::env::set_current_dir(target)?;
Ok(Self { original })
}
}
impl Drop for CwdGuard {
fn drop(&mut self) {
if let Err(e) = std::env::set_current_dir(&self.original) {
eprintln!("ix: warning: failed to restore working directory: {e}");
}
}
}
#[derive(serde::Serialize)]
pub(crate) struct BeaconStatusJson {
pub(crate) status: String,
pub(crate) pid: i32,
pub(crate) uptime_secs: Option<u64>,
pub(crate) daemon_status: String,
pub(crate) root: String,
pub(crate) socket: Option<String>,
pub(crate) instance_id: u64,
}
#[derive(serde::Serialize)]
pub(crate) struct SimpleStatusJson {
pub(crate) status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) stale_pid: Option<i32>,
}