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}