Skip to main content

kimun_notes/cli/
mod.rs

1// tui/src/cli/mod.rs
2pub mod commands;
3pub mod helpers;
4pub mod json_output;
5pub mod metadata_extractor;
6pub mod output;
7
8use clap::Subcommand;
9use color_eyre::eyre::{Result, eyre};
10use commands::JournalArgs;
11use commands::note_ops::NoteSubcommand;
12use commands::workspace::WorkspaceSubcommand;
13use helpers::{
14    create_and_init_vault, load_and_resolve_workspace, load_settings, resolve_inbox_path,
15    resolve_quick_note_path,
16};
17use kimun_core::{NoteVault, VaultConfig};
18use output::OutputFormat;
19
20#[derive(Subcommand)]
21pub enum CliCommand {
22    /// Search notes by query
23    Search {
24        query: String,
25        #[arg(long, value_enum, default_value = "text")]
26        format: OutputFormat,
27    },
28    /// List all notes
29    Notes {
30        #[arg(long, help = "Filter notes by path prefix")]
31        path: Option<String>,
32        #[arg(long, value_enum, default_value = "text")]
33        format: OutputFormat,
34    },
35    /// Manage workspaces
36    Workspace {
37        #[command(subcommand)]
38        subcommand: WorkspaceSubcommand,
39    },
40    /// Note operations (create, append, show)
41    Note {
42        #[command(subcommand)]
43        subcommand: NoteSubcommand,
44    },
45    /// Append to or show journal entries
46    Journal(JournalArgs),
47    /// Start the MCP server (stdio transport)
48    Mcp,
49    /// List all hashtag labels in the vault with note counts
50    Labels {
51        #[arg(long, value_enum, default_value = "text")]
52        format: OutputFormat,
53    },
54    /// Check for a newer release and, where possible, self-update
55    Update {
56        /// Only check and report; do not download or install
57        #[arg(long)]
58        check: bool,
59    },
60}
61
62pub async fn run_cli(command: CliCommand, config_path: Option<std::path::PathBuf>) -> Result<()> {
63    match command {
64        CliCommand::Workspace { subcommand } => {
65            let mut settings = load_settings(config_path)?;
66            commands::workspace::run(subcommand, &mut settings).await
67        }
68        CliCommand::Note { subcommand } => {
69            let (settings, workspace_path, workspace_name) =
70                load_and_resolve_workspace(config_path)?;
71            let quick_note_path = resolve_quick_note_path(&settings);
72            let inbox_path = resolve_inbox_path(&settings);
73            let cache_path = settings.index_for(&workspace_name);
74            let mut vault = NoteVault::new(
75                VaultConfig::new(workspace_path)
76                    .with_index(cache_path)
77                    .with_backup(true),
78            )
79            .await?;
80            vault.set_inbox_path(kimun_core::nfs::VaultPath::new(&inbox_path));
81            // Every one-shot command closes its vault before returning: the
82            // command owns that vault for its whole life, and dropping a pool
83            // only schedules the close, so anything the same process does next
84            // (a `workspace rename`/`remove`, a test driving several commands
85            // in a row) would find the cache file still open — fatal on
86            // Windows, which will not move or delete an open file.
87            let result = if vault.index_ready() {
88                commands::note_ops::run(subcommand, &vault, &quick_note_path, &workspace_name).await
89            } else {
90                Err(eyre!(
91                    "Workspace index is not ready.\nRun `kimun workspace reindex` to initialise it."
92                ))
93            };
94            vault.close().await;
95            result
96        }
97        CliCommand::Search { query, format } => {
98            let (vault, workspace_name) = create_and_init_vault(config_path).await?;
99            let result =
100                commands::search::run(&vault, &query, format, &workspace_name, false).await;
101            vault.close().await;
102            result
103        }
104        CliCommand::Notes { path, format } => {
105            let (vault, workspace_name) = create_and_init_vault(config_path).await?;
106            let result =
107                commands::notes::run(&vault, path.as_deref(), format, &workspace_name, false).await;
108            vault.close().await;
109            result
110        }
111        CliCommand::Journal(args) => {
112            let (vault, workspace_name) = create_and_init_vault(config_path).await?;
113            let result = commands::journal::run(args, &vault, &workspace_name).await;
114            vault.close().await;
115            result
116        }
117        CliCommand::Mcp => commands::mcp::run(config_path).await,
118        CliCommand::Labels { format } => {
119            let (vault, workspace_name) = create_and_init_vault(config_path).await?;
120            let result = commands::labels::run(&vault, format, &workspace_name).await;
121            vault.close().await;
122            result
123        }
124        // Update is vault-independent: it talks to GitHub and the app config
125        // dir, not a workspace.
126        CliCommand::Update { check } => commands::update::run(check).await,
127    }
128}