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    },
256    /// List all registered wikis
257    List {
258        /// Wiki name (omit for all)
259        name: Option<String>,
260        /// Output format: text | json
261        #[arg(long)]
262        format: Option<String>,
263    },
264    /// Remove a wiki from the registry
265    Remove {
266        /// Wiki name to remove
267        name: String,
268        /// Also delete the wiki directory from disk
269        #[arg(long)]
270        delete: bool,
271    },
272    /// Set the default wiki
273    SetDefault {
274        /// Wiki name to set as default
275        name: String,
276    },
277}
278
279/// Subcommands for `llm-wiki config`.
280#[derive(Subcommand)]
281pub enum ConfigAction {
282    /// Print a config value
283    Get {
284        /// Config key (e.g. defaults.search_top_k)
285        key: String,
286    },
287    /// Set a config value
288    Set {
289        /// Config key
290        key: String,
291        /// Config value
292        value: String,
293        /// Write to global config
294        #[arg(long)]
295        global: bool,
296        /// Write to per-wiki config
297        #[arg(long)]
298        wiki: Option<String>,
299    },
300    /// Print all resolved config
301    List {
302        /// Global config only
303        #[arg(long)]
304        global: bool,
305        /// Per-wiki config only
306        #[arg(long)]
307        wiki: Option<String>,
308        /// Output format: text | json
309        #[arg(long)]
310        format: Option<String>,
311    },
312}
313
314/// Subcommands for `llm-wiki content`.
315#[derive(Subcommand)]
316pub enum ContentAction {
317    /// Read a page or asset by slug or wiki:// URI
318    Read {
319        /// Slug or wiki:// URI
320        uri: String,
321        /// Strip frontmatter from output
322        #[arg(long)]
323        no_frontmatter: bool,
324        /// List co-located assets instead of content
325        #[arg(long)]
326        list_assets: bool,
327    },
328    /// Write a file into the wiki tree
329    Write {
330        /// Slug or wiki:// URI
331        uri: String,
332        /// Read content from a file instead of stdin
333        #[arg(long)]
334        file: Option<String>,
335    },
336    /// Create a page or section with scaffolded frontmatter
337    New {
338        /// Slug or wiki:// URI
339        uri: String,
340        /// Create a section instead of a page
341        #[arg(long)]
342        section: bool,
343        /// Create as bundle (folder + index.md)
344        #[arg(long)]
345        bundle: bool,
346        /// Page title (default: derived from slug)
347        #[arg(long)]
348        name: Option<String>,
349        /// Page type (default: page)
350        #[arg(long, name = "type")]
351        r#type: Option<String>,
352        /// Show what would be created without creating
353        #[arg(long)]
354        dry_run: bool,
355    },
356    /// Commit pending changes to git
357    Commit {
358        /// Page slugs to commit (omit for --all)
359        slugs: Vec<String>,
360        /// Commit all pending changes
361        #[arg(long)]
362        all: bool,
363        /// Commit message
364        #[arg(long, short)]
365        message: Option<String>,
366    },
367}
368
369/// Subcommands for `llm-wiki index`.
370#[derive(Subcommand)]
371pub enum IndexAction {
372    /// Rebuild the search index from committed Markdown
373    Rebuild {
374        /// Walk and count pages, no write
375        #[arg(long)]
376        dry_run: bool,
377        /// Output format: text | json
378        #[arg(long)]
379        format: Option<String>,
380    },
381    /// Inspect index health
382    Status {
383        /// Output format: text | json
384        #[arg(long)]
385        format: Option<String>,
386    },
387}
388
389/// Subcommands for `llm-wiki schema`.
390#[derive(Subcommand)]
391pub enum SchemaAction {
392    /// List all registered types
393    List {
394        /// Output format: text | json
395        #[arg(long)]
396        format: Option<String>,
397    },
398    /// Show JSON Schema or frontmatter template for a type
399    Show {
400        /// Type name
401        name: String,
402        /// Print frontmatter template instead of schema
403        #[arg(long)]
404        template: bool,
405        /// Output format: text | json
406        #[arg(long)]
407        format: Option<String>,
408    },
409    /// Register a custom type
410    Add {
411        /// Type name
412        name: String,
413        /// Path to JSON Schema file
414        schema_path: String,
415    },
416    /// Unregister a type and remove its pages from the index
417    Remove {
418        /// Type name
419        name: String,
420        /// Also delete/modify the schema file
421        #[arg(long)]
422        delete: bool,
423        /// Also delete page .md files from disk
424        #[arg(long)]
425        delete_pages: bool,
426        /// Show what would be done without doing it
427        #[arg(long)]
428        dry_run: bool,
429    },
430    /// Validate schema files and index resolution
431    Validate {
432        /// Validate a specific type only (omit for all)
433        name: Option<String>,
434    },
435}