Skip to main content

llm_wiki/
cli.rs

1use clap::{Parser, Subcommand};
2
3/// Root CLI entry point — parses subcommands and global flags.
4#[derive(Parser)]
5#[command(
6    name = "llm-wiki",
7    version,
8    about = "Git-backed wiki engine with MCP server"
9)]
10pub struct Cli {
11    /// The subcommand to execute.
12    #[command(subcommand)]
13    pub command: Commands,
14
15    /// Target a specific wiki
16    #[arg(long, global = true)]
17    pub wiki: Option<String>,
18
19    /// Path to global config file (default: ~/.llm-wiki/config.toml).
20    /// Overrides the LLM_WIKI_CONFIG environment variable.
21    #[arg(long, global = true)]
22    pub config: Option<std::path::PathBuf>,
23}
24
25/// Top-level subcommands available from the `llm-wiki` CLI.
26#[derive(Subcommand)]
27pub enum Commands {
28    /// Manage wiki spaces
29    Spaces {
30        /// The spaces subcommand.
31        #[command(subcommand)]
32        action: SpacesAction,
33    },
34    /// Read and write configuration
35    Config {
36        /// The config subcommand.
37        #[command(subcommand)]
38        action: ConfigAction,
39    },
40    /// Content operations (read, write, new, commit)
41    Content {
42        /// The content subcommand.
43        #[command(subcommand)]
44        action: ContentAction,
45    },
46    /// Full-text BM25 search
47    Search {
48        /// Search query
49        query: String,
50        /// Filter by frontmatter type
51        #[arg(long, name = "type")]
52        r#type: Option<String>,
53        /// Omit excerpts — refs only
54        #[arg(long)]
55        no_excerpt: bool,
56        /// Max results (default: from config)
57        #[arg(long)]
58        top_k: Option<usize>,
59        /// Include section index pages in results
60        #[arg(long)]
61        include_sections: bool,
62        /// Search across all registered wikis
63        #[arg(long)]
64        cross_wiki: bool,
65        /// Output format: text | json
66        #[arg(long)]
67        format: Option<String>,
68    },
69    /// Paginated enumeration of wiki pages
70    List {
71        /// Filter by frontmatter type
72        #[arg(long, name = "type")]
73        r#type: Option<String>,
74        /// Filter by frontmatter status
75        #[arg(long)]
76        status: Option<String>,
77        /// Page number, 1-based
78        #[arg(long, default_value = "1")]
79        page: usize,
80        /// Results per page
81        #[arg(long)]
82        page_size: Option<usize>,
83        /// Output format: text | json
84        #[arg(long)]
85        format: Option<String>,
86    },
87    /// Validate and index files in the wiki tree
88    Ingest {
89        /// Slug, URI, or path relative to wiki root
90        path: String,
91        /// Validate only, no commit
92        #[arg(long)]
93        dry_run: bool,
94        /// Redact secrets from file bodies before validation (opt-in; lossy)
95        #[arg(long)]
96        redact: bool,
97        /// Output format: text | json
98        #[arg(long)]
99        format: Option<String>,
100    },
101    /// Generate a concept graph
102    Graph {
103        /// Output format: mermaid | dot | llms
104        #[arg(long)]
105        format: Option<String>,
106        /// Subgraph from this node (slug)
107        #[arg(long)]
108        root: Option<String>,
109        /// Hop limit from root
110        #[arg(long)]
111        depth: Option<usize>,
112        /// Comma-separated page types to include
113        #[arg(long, name = "type")]
114        r#type: Option<String>,
115        /// Filter edges by relation label
116        #[arg(long)]
117        relation: Option<String>,
118        /// File path for output (default: stdout)
119        #[arg(long)]
120        output: Option<String>,
121        /// Merge all mounted wikis into a unified graph
122        #[arg(long)]
123        cross_wiki: bool,
124    },
125    /// Manage the tantivy search index
126    Index {
127        /// The index subcommand.
128        #[command(subcommand)]
129        action: IndexAction,
130    },
131    /// Git commit history for a page
132    History {
133        /// Slug or wiki:// URI
134        slug: String,
135        /// Max entries to return
136        #[arg(long, short = 'n')]
137        limit: Option<usize>,
138        /// Disable rename tracking
139        #[arg(long)]
140        no_follow: bool,
141        /// Output format: text | json
142        #[arg(long)]
143        format: Option<String>,
144    },
145    /// Wiki health dashboard
146    Stats {
147        /// Output format: text | json
148        #[arg(long)]
149        format: Option<String>,
150    },
151    /// Run deterministic lint rules on the wiki index
152    Lint {
153        /// Comma-separated rule names: orphan, broken-link, missing-fields, stale, unknown-type
154        #[arg(long)]
155        rules: Option<String>,
156        /// Filter output by severity: error | warning
157        #[arg(long)]
158        severity: Option<String>,
159        /// Output format: text | json
160        #[arg(long)]
161        format: Option<String>,
162    },
163    /// Suggest related pages to link
164    Suggest {
165        /// Slug or wiki:// URI
166        slug: String,
167        /// Max suggestions
168        #[arg(long, short = 'n')]
169        limit: Option<usize>,
170        /// Output format: text | json
171        #[arg(long)]
172        format: Option<String>,
173    },
174    /// Inspect and manage type schemas
175    Schema {
176        /// The schema subcommand.
177        #[command(subcommand)]
178        action: SchemaAction,
179    },
180    /// Export the full wiki to a file (llms.txt, llms-full, or json)
181    Export {
182        /// Output path (relative to wiki root or absolute; default: llms.txt)
183        #[arg(long)]
184        path: Option<String>,
185        /// Export format: llms-txt | llms-full | json
186        #[arg(long)]
187        format: Option<String>,
188        /// Page status filter: active | all (default: active, excludes archived)
189        #[arg(long)]
190        status: Option<String>,
191    },
192    /// Start the wiki MCP/ACP server
193    Serve {
194        /// Enable HTTP transport (optional port, e.g. :8080)
195        #[arg(long, value_name = "PORT")]
196        http: Option<Option<String>>,
197        /// Enable ACP transport
198        #[arg(long)]
199        acp: bool,
200        /// Enable filesystem watcher
201        #[arg(long)]
202        watch: bool,
203        /// Print what would be started, no server
204        #[arg(long)]
205        dry_run: bool,
206    },
207    /// Auto-ingest on file save (standalone watcher)
208    Watch {
209        /// Target wiki name
210        #[arg(long)]
211        wiki: Option<String>,
212    },
213    /// Inspect and manage server logs
214    Logs {
215        /// The logs subcommand.
216        #[command(subcommand)]
217        action: LogsAction,
218    },
219}
220
221/// Subcommands for `llm-wiki logs`.
222#[derive(Subcommand)]
223pub enum LogsAction {
224    /// Show recent log entries
225    Tail {
226        /// Number of lines to show (default: 50)
227        #[arg(long, default_value = "50")]
228        lines: usize,
229    },
230    /// List log files
231    List,
232    /// Delete all log files
233    Clear,
234}
235
236/// Subcommands for `llm-wiki spaces`.
237#[derive(Subcommand)]
238pub enum SpacesAction {
239    /// Create a new wiki repository
240    Create {
241        /// Path to create the wiki at
242        path: String,
243        /// Wiki name — used in wiki:// URIs
244        #[arg(long)]
245        name: String,
246        /// Optional one-line description
247        #[arg(long)]
248        description: Option<String>,
249        /// Update space entry if name differs from existing
250        #[arg(long)]
251        force: bool,
252        /// Set as default wiki
253        #[arg(long)]
254        set_default: bool,
255        /// Content directory relative to repo root (default: "wiki")
256        #[arg(long)]
257        wiki_root: Option<String>,
258    },
259    /// Register an existing wiki repository without creating files
260    Register {
261        /// Absolute path to the existing wiki repository
262        path: String,
263        /// Wiki name — used in wiki:// URIs
264        #[arg(long)]
265        name: String,
266        /// Optional one-line description
267        #[arg(long)]
268        description: Option<String>,
269        /// Content directory relative to repo root (overrides wiki.toml)
270        #[arg(long)]
271        wiki_root: Option<String>,
272    },
273    /// List all registered wikis
274    List {
275        /// Wiki name (omit for all)
276        name: Option<String>,
277        /// Output format: text | json
278        #[arg(long)]
279        format: Option<String>,
280    },
281    /// Remove a wiki from the registry
282    Remove {
283        /// Wiki name to remove
284        name: String,
285        /// Also delete the wiki directory from disk
286        #[arg(long)]
287        delete: bool,
288    },
289    /// Set the default wiki
290    SetDefault {
291        /// Wiki name to set as default
292        name: String,
293    },
294}
295
296/// Subcommands for `llm-wiki config`.
297#[derive(Subcommand)]
298pub enum ConfigAction {
299    /// Print a config value
300    Get {
301        /// Config key (e.g. defaults.search_top_k)
302        key: String,
303    },
304    /// Set a config value
305    Set {
306        /// Config key
307        key: String,
308        /// Config value
309        value: String,
310        /// Write to global config
311        #[arg(long)]
312        global: bool,
313        /// Write to per-wiki config
314        #[arg(long)]
315        wiki: Option<String>,
316    },
317    /// Print all resolved config
318    List {
319        /// Global config only
320        #[arg(long)]
321        global: bool,
322        /// Per-wiki config only
323        #[arg(long)]
324        wiki: Option<String>,
325        /// Output format: text | json
326        #[arg(long)]
327        format: Option<String>,
328    },
329}
330
331/// Subcommands for `llm-wiki content`.
332#[derive(Subcommand)]
333pub enum ContentAction {
334    /// Read a page or asset by slug or wiki:// URI
335    Read {
336        /// Slug or wiki:// URI
337        uri: String,
338        /// Strip frontmatter from output
339        #[arg(long)]
340        no_frontmatter: bool,
341        /// List co-located assets instead of content
342        #[arg(long)]
343        list_assets: bool,
344    },
345    /// Write a file into the wiki tree
346    Write {
347        /// Slug or wiki:// URI
348        uri: String,
349        /// Read content from a file instead of stdin
350        #[arg(long)]
351        file: Option<String>,
352    },
353    /// Create a page or section with scaffolded frontmatter
354    New {
355        /// Slug or wiki:// URI
356        uri: String,
357        /// Create a section instead of a page
358        #[arg(long)]
359        section: bool,
360        /// Create as bundle (folder + index.md)
361        #[arg(long)]
362        bundle: bool,
363        /// Page title (default: derived from slug)
364        #[arg(long)]
365        name: Option<String>,
366        /// Page type (default: page)
367        #[arg(long, name = "type")]
368        r#type: Option<String>,
369        /// Show what would be created without creating
370        #[arg(long)]
371        dry_run: bool,
372    },
373    /// Commit pending changes to git
374    Commit {
375        /// Page slugs to commit (omit for --all)
376        slugs: Vec<String>,
377        /// Commit all pending changes
378        #[arg(long)]
379        all: bool,
380        /// Commit message
381        #[arg(long, short)]
382        message: Option<String>,
383    },
384}
385
386/// Subcommands for `llm-wiki index`.
387#[derive(Subcommand)]
388pub enum IndexAction {
389    /// Rebuild the search index from committed Markdown
390    Rebuild {
391        /// Walk and count pages, no write
392        #[arg(long)]
393        dry_run: bool,
394        /// Output format: text | json
395        #[arg(long)]
396        format: Option<String>,
397    },
398    /// Inspect index health
399    Status {
400        /// Output format: text | json
401        #[arg(long)]
402        format: Option<String>,
403    },
404}
405
406/// Subcommands for `llm-wiki schema`.
407#[derive(Subcommand)]
408pub enum SchemaAction {
409    /// List all registered types
410    List {
411        /// Output format: text | json
412        #[arg(long)]
413        format: Option<String>,
414    },
415    /// Show JSON Schema or frontmatter template for a type
416    Show {
417        /// Type name
418        name: String,
419        /// Print frontmatter template instead of schema
420        #[arg(long)]
421        template: bool,
422        /// Output format: text | json
423        #[arg(long)]
424        format: Option<String>,
425    },
426    /// Register a custom type
427    Add {
428        /// Type name
429        name: String,
430        /// Path to JSON Schema file
431        schema_path: String,
432    },
433    /// Unregister a type and remove its pages from the index
434    Remove {
435        /// Type name
436        name: String,
437        /// Also delete/modify the schema file
438        #[arg(long)]
439        delete: bool,
440        /// Also delete page .md files from disk
441        #[arg(long)]
442        delete_pages: bool,
443        /// Show what would be done without doing it
444        #[arg(long)]
445        dry_run: bool,
446    },
447    /// Validate schema files and index resolution
448    Validate {
449        /// Validate a specific type only (omit for all)
450        name: Option<String>,
451    },
452}