#[cfg(not(unix))]
compile_error!(
"thoughts only supports Unix-like platforms (Linux/macOS). Windows is not supported."
);
use anyhow::Result;
use clap::Parser;
use clap::Subcommand;
use colored::Colorize;
use tracing::info;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
mod commands;
pub use thoughts_tool::config;
pub use thoughts_tool::error;
pub use thoughts_tool::git;
pub use thoughts_tool::mount;
pub use thoughts_tool::platform;
pub use thoughts_tool::utils;
use crate::config::SyncStrategy;
#[derive(Parser)]
#[command(name = "thoughts")]
#[command(about = "A flexible thought management tool with filesystem merging")]
#[command(version)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
#[arg(short, long)]
quiet: bool,
}
#[derive(Subcommand)]
enum Commands {
Init {
#[arg(short, long)]
force: bool,
},
Sync {
mount: Option<String>,
#[arg(short, long)]
message: Option<String>,
#[arg(short, long)]
all: bool,
},
Status {
#[arg(short, long)]
detailed: bool,
},
Mount {
#[command(subcommand)]
command: MountCommands,
},
Config {
#[command(subcommand)]
command: ConfigCommands,
},
References {
#[command(subcommand)]
command: ReferenceCommands,
},
Work {
#[command(subcommand)]
command: WorkCommands,
},
}
#[derive(Subcommand)]
enum MountCommands {
Add {
path: std::path::PathBuf,
mount_path: Option<String>,
#[arg(long, value_parser = clap::value_parser!(SyncStrategy), default_value = "auto")]
sync: SyncStrategy,
#[arg(short, long)]
description: Option<String>,
},
Remove {
mount_name: String,
},
List {
#[arg(short, long)]
verbose: bool,
},
Update,
Clone {
url: String,
path: Option<std::path::PathBuf>,
},
Debug {
#[command(subcommand)]
command: MountDebugCommands,
},
Status,
}
#[derive(Subcommand)]
enum MountDebugCommands {
Info {
target: String,
},
Command {
mount_name: String,
},
Remount {
mount_name: String,
},
}
#[derive(Subcommand)]
enum ConfigCommands {
Create,
Show {
#[arg(short, long)]
json: bool,
},
Edit {},
Validate,
}
#[derive(Subcommand)]
enum ReferenceCommands {
Add {
url: String,
},
Remove {
url: String,
},
List,
Sync {
#[arg(long, short)]
verbose: bool,
},
Doctor {
#[arg(long)]
fix: bool,
},
}
#[derive(Subcommand)]
enum WorkCommands {
Init,
Complete,
List {
#[arg(short, long)]
recent: Option<usize>,
},
Open {
#[arg(long, value_parser = ["research", "plans", "artifacts"])]
subdir: Option<String>,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let log_level = match (cli.quiet, cli.verbose) {
(true, _) => "error",
(false, 0) => "info",
(false, 1) => "debug",
(false, _) => "trace",
};
let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(log_level));
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(false)
.with_thread_ids(false)
.with_thread_names(false);
tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer)
.init();
info!("Starting thoughts v{}", env!("CARGO_PKG_VERSION"));
if let Ok(personal_config_path) = crate::utils::paths::get_personal_config_path()
&& personal_config_path.exists()
{
eprintln!(
"{}: Detected personal config at {}. Personal mounts are deprecated and ignored. To migrate, re-add any needed repos as context mounts (thoughts mount add) or references (thoughts references add).",
"Warning".yellow(),
personal_config_path.display()
);
}
match cli.command {
Commands::Init { force } => commands::init::execute(force).await,
Commands::Sync {
mount,
message: _,
all,
} => commands::sync::execute(mount, all).await,
Commands::Status { detailed } => commands::status::execute(detailed).await,
Commands::Mount { command } => match command {
MountCommands::Add {
path,
mount_path,
sync,
description,
} => commands::mount::add::execute(path, mount_path, sync, description).await,
MountCommands::Remove { mount_name } => {
commands::mount::remove::execute(mount_name).await
}
MountCommands::List { verbose } => commands::mount::list::execute(verbose).await,
MountCommands::Update => commands::mount::update::execute().await,
MountCommands::Clone { url, path } => commands::mount::clone::execute(url, path).await,
MountCommands::Debug { command } => match command {
MountDebugCommands::Info { target } => {
commands::mount::debug::info::execute(target).await
}
MountDebugCommands::Command { mount_name } => {
commands::mount::debug::command::execute(mount_name).await
}
MountDebugCommands::Remount { mount_name } => {
commands::mount::debug::remount::execute(mount_name).await
}
},
MountCommands::Status => commands::mount::status::execute().await,
},
Commands::Config { command } => match command {
ConfigCommands::Create => commands::config::create::execute().await,
ConfigCommands::Show { json } => commands::config::show::execute(json).await,
ConfigCommands::Edit {} => commands::config::edit::execute().await,
ConfigCommands::Validate => commands::config::validate::execute().await,
},
Commands::References { command } => match command {
ReferenceCommands::Add { url } => commands::references::add::execute(url).await,
ReferenceCommands::Remove { url } => commands::references::remove::execute(url).await,
ReferenceCommands::List => commands::references::list::execute().await,
ReferenceCommands::Sync { verbose } => {
commands::references::sync::execute(verbose).await
}
ReferenceCommands::Doctor { fix } => commands::references::doctor::execute(fix).await,
},
Commands::Work { command } => match command {
WorkCommands::Init => commands::work::init::execute().await,
WorkCommands::Complete => commands::work::complete::execute().await,
WorkCommands::List { recent } => commands::work::list::execute(recent).await,
WorkCommands::Open { subdir } => {
use commands::work::open::OpenSubdir;
use commands::work::open::execute;
let which = match subdir.as_deref() {
Some("research") => OpenSubdir::Research,
Some("plans") => OpenSubdir::Plans,
Some("artifacts") => OpenSubdir::Artifacts,
_ => OpenSubdir::Base,
};
execute(which).await
}
},
}
}