Skip to main content

obsidian_cli_inspector/
cli.rs

1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4const LONG_ABOUT: &str = r#"
5Obsidian CLI Inspector - A local-first, read-only CLI/TUI for Obsidian vaults
6
7ABOUT:
8    This tool helps you inspect, search, and navigate your Obsidian vault from the terminal.
9  All data is stored locally in a SQLite database for fast, offline access.
10
11USE CASES:
12  • Search: Find notes quickly with full-text search across your entire vault
13  • Link Analysis: Discover backlinks, forward links, and unresolved references
14  • Tag Management: Filter and organize notes by tags with AND/OR logic
15  • Graph Exploration: Visualize note relationships and connection depths
16  • Content Quality: Identify bloated notes that may need refactoring
17  • Related Notes: Get AI-style suggestions for notes you might want to link
18  • Scripting: Integrate with shell scripts and automation workflows
19  • Interactive TUI: Browse your vault in a terminal user interface
20
21WORKFLOW:
22  1. Run 'init init' to set up the database
23  2. Run 'index index' to scan and parse your vault
24  3. Use search commands (notes, backlinks, tags, etc.)
25  4. Re-run 'index index' periodically to catch changes
26
27CLI STRUCTURE:
28  obsidian-cli-inspector <group> <function> [args...]
29
30  Groups:
31    init     - Database initialization
32    index    - Vault indexing (scan, status)
33    search   - Search and retrieval (notes, backlinks, links, tags, unresolved)
34    graph    - Graph operations (neighbors, paths, centrality, components)
35    analyze  - Content analysis (bloat, related, similar, quality)
36    diagnose - Diagnostics (orphans, broken-links, conflicts)
37    view     - Display commands (stats, describe, health)
38
39EXAMPLES:
40  # Initialize and index your vault
41  obsidian-cli-inspector init init
42  obsidian-cli-inspector index index
43
44  # Search for notes containing 'rust'
45  obsidian-cli-inspector search notes rust --limit 10
46
47  # Find all notes linking to 'Project Ideas'
48  obsidian-cli-inspector search backlinks "Project Ideas"
49
50  # List all notes tagged with 'work'
51  obsidian-cli-inspector search tags work
52
53  # Find large notes that might need splitting
54  obsidian-cli-inspector analyze bloat --threshold 100000
55
56  # Launch interactive mode
57  obsidian-cli-inspector tui
58
59CONFIG:
60  Place config at ~/.config/obsidian-cli-inspector/config.toml
61  Specify vault path and database location there.
62"#;
63
64const HELP_TEMPLATE: &str =
65    "{about-with-newline}Version: {version}\n\nUsage: {usage}\n\n{all-args}{after-help}";
66
67#[derive(Parser)]
68#[command(name = "obsidian-cli-inspector")]
69#[command(author, version)]
70#[command(help_template = HELP_TEMPLATE)]
71#[command(about = "Local-first CLI/TUI for indexing and gain insight in Obsidian vaults")]
72#[command(long_about = LONG_ABOUT)]
73pub struct Cli {
74    /// Path to config file
75    #[arg(short, long, value_name = "FILE")]
76    pub config: Option<PathBuf>,
77
78    /// Output in JSON format (for machine integration)
79    #[arg(short = 'o', long = "output", value_name = "FORMAT", global = true)]
80    pub output: Option<String>,
81
82    #[command(subcommand)]
83    pub command: Commands,
84}
85
86#[derive(Subcommand)]
87#[command(arg_required_else_help(true))]
88pub enum Commands {
89    /// Database initialization commands
90    #[command(subcommand)]
91    Init(InitCommands),
92
93    /// Vault indexing commands
94    #[command(subcommand)]
95    Index(IndexCommands),
96
97    /// Search and retrieval commands
98    #[command(subcommand)]
99    Search(SearchCommands),
100
101    // /// Graph operations commands
102    // #[command(subcommand)]
103    // Graph(GraphCommands),
104    /// Content analysis commands
105    #[command(subcommand)]
106    Analyze(AnalyzeCommands),
107
108    /// Diagnostic commands
109    #[command(subcommand)]
110    Diagnose(DiagnoseCommands),
111
112    /// Display commands
113    #[command(subcommand)]
114    View(ViewCommands),
115
116    /// Launch interactive TUI
117    Tui,
118}
119
120// ============================================================================
121// INIT Commands
122// ============================================================================
123#[derive(Subcommand)]
124pub enum InitCommands {
125    /// Initialize or reinitialize the database
126    Init {
127        /// Force reinitialization (drops existing data)
128        #[arg(short, long)]
129        force: bool,
130    },
131}
132
133// ============================================================================
134// INDEX Commands
135// ============================================================================
136#[derive(Subcommand)]
137pub enum IndexCommands {
138    /// Index the vault (scan and parse all files)
139    Index {
140        /// Perform a dry run without writing to database
141        #[arg(short = 'n', long)]
142        dry_run: bool,
143
144        /// Force full re-index (ignores change detection)
145        #[arg(short, long)]
146        force: bool,
147
148        /// Show verbose output
149        #[arg(short, long)]
150        verbose: bool,
151    },
152    // /// Show indexing status
153    // Status,
154
155    // /// Scan vault for changes (without indexing)
156    // Scan,
157}
158
159// ============================================================================
160// SEARCH Commands
161// ============================================================================
162#[derive(Subcommand)]
163pub enum SearchCommands {
164    /// Search notes using full-text search
165    Notes {
166        /// Search query
167        query: String,
168
169        /// Maximum number of results
170        #[arg(short, long, default_value = "20")]
171        limit: usize,
172    },
173
174    /// List backlinks to a note
175    Backlinks {
176        /// Note path or title
177        note: String,
178    },
179
180    /// List forward links from a note
181    Links {
182        /// Note path or title
183        note: String,
184    },
185
186    /// List all unresolved links in the vault
187    Unresolved,
188
189    /// List notes by tag
190    Tags {
191        /// Tag name (without #)
192        tag: Option<String>,
193
194        /// List all tags if no tag specified
195        #[arg(short, long)]
196        list: bool,
197    },
198}
199
200// ============================================================================
201// GRAPH Commands
202// ============================================================================
203// #[derive(Subcommand)]
204// pub enum GraphCommands {
205//     /// Find neighboring notes (BFS traversal)
206//     Neighbors {
207//         /// Note path or title
208//         note: String,
209//         /// Maximum traversal depth
210//         #[arg(short, long, default_value = "2")]
211//         depth: usize,
212//     },
213//     /// Find shortest paths between notes
214//     Paths {
215//         /// Source note path or title
216//         source: String,
217//         /// Target note path or title
218//         target: String,
219//     },
220//     /// Calculate centrality metrics
221//     Centrality,
222//     /// Find connected components
223//     Components,
224// }
225
226// ============================================================================
227// ANALYZE Commands
228// ============================================================================
229#[derive(Subcommand)]
230pub enum AnalyzeCommands {
231    /// Detect bloated notes and suggest refactoring
232    Bloat {
233        /// Minimum size threshold in bytes
234        #[arg(short, long, default_value = "50000")]
235        threshold: usize,
236
237        /// Maximum number of notes to analyze
238        #[arg(short, long, default_value = "10")]
239        limit: usize,
240    },
241
242    /// Suggest related notes not directly linked
243    Related {
244        /// Note path or title
245        note: String,
246
247        /// Maximum number of suggestions
248        #[arg(short, long, default_value = "10")]
249        limit: usize,
250    },
251    // /// Find similar notes based on content
252    // Similar {
253    //     /// Note path or title
254    //     note: String,
255    //     /// Maximum number of similar notes
256    //     #[arg(short, long, default_value = "10")]
257    //     limit: usize,
258    // },
259    // /// Analyze note quality metrics
260    // Quality {
261    //     /// Note path or title
262    //     note: String,
263    // },
264}
265
266// ============================================================================
267// DIAGNOSE Commands
268// ============================================================================
269#[derive(Subcommand)]
270pub enum DiagnoseCommands {
271    /// Diagnose orphan notes (no incoming + no outgoing links)
272    Orphans {
273        /// Exclude template notes
274        #[arg(long)]
275        exclude_templates: bool,
276
277        /// Exclude daily notes
278        #[arg(long)]
279        exclude_daily: bool,
280    },
281
282    /// Diagnose broken links (unresolved and ambiguous)
283    BrokenLinks,
284    // /// Diagnose note conflicts
285    // Conflicts,
286}
287
288// ============================================================================
289// VIEW Commands
290// ============================================================================
291#[derive(Subcommand)]
292pub enum ViewCommands {
293    /// Show statistics about the vault
294    Stats,
295
296    /// Describe file metadata (without displaying paragraphs)
297    Describe {
298        /// File path or title to describe
299        filename: String,
300    },
301    // /// Show health metrics
302    // Health,
303}