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, broken-cross-wiki-link,
154        /// missing-fields, stale, unknown-type, articulation-point, bridge, periphery
155        #[arg(long)]
156        rules: Option<String>,
157        /// Filter output by severity: error | warning
158        #[arg(long)]
159        severity: Option<String>,
160        /// Output format: text | json
161        #[arg(long)]
162        format: Option<String>,
163    },
164    /// Suggest related pages to link
165    Suggest {
166        /// Slug or wiki:// URI
167        slug: String,
168        /// Max suggestions
169        #[arg(long, short = 'n')]
170        limit: Option<usize>,
171        /// Output format: text | json
172        #[arg(long)]
173        format: Option<String>,
174    },
175    /// Inspect and manage type schemas
176    Schema {
177        /// The schema subcommand.
178        #[command(subcommand)]
179        action: SchemaAction,
180    },
181    /// Export the full wiki to a file (llms.txt, llms-full, or json)
182    Export {
183        /// Output path (relative to wiki root or absolute; default: llms.txt)
184        #[arg(long)]
185        path: Option<String>,
186        /// Export format: llms-txt | llms-full | json
187        #[arg(long)]
188        format: Option<String>,
189        /// Page status filter: active | all (default: active, excludes archived)
190        #[arg(long)]
191        status: Option<String>,
192    },
193    /// Start the wiki MCP/ACP server
194    Serve {
195        /// Enable HTTP transport (optional port, e.g. :8080)
196        #[arg(long, value_name = "PORT")]
197        http: Option<Option<String>>,
198        /// Enable ACP transport
199        #[arg(long)]
200        acp: bool,
201        /// Enable filesystem watcher
202        #[arg(long)]
203        watch: bool,
204        /// Print what would be started, no server
205        #[arg(long)]
206        dry_run: bool,
207    },
208    /// Auto-ingest on file save (standalone watcher)
209    Watch {
210        /// Target wiki name
211        #[arg(long)]
212        wiki: Option<String>,
213    },
214    /// Inspect and manage server logs
215    Logs {
216        /// The logs subcommand.
217        #[command(subcommand)]
218        action: LogsAction,
219    },
220}
221
222/// Subcommands for `llm-wiki logs`.
223#[derive(Subcommand)]
224pub enum LogsAction {
225    /// Show recent log entries
226    Tail {
227        /// Number of lines to show (default: 50)
228        #[arg(long, default_value = "50")]
229        lines: usize,
230    },
231    /// List log files
232    List,
233    /// Delete all log files
234    Clear,
235}
236
237/// Subcommands for `llm-wiki spaces`.
238#[derive(Subcommand)]
239pub enum SpacesAction {
240    /// Create a new wiki repository
241    Create {
242        /// Path to create the wiki at
243        path: String,
244        /// Wiki name — used in wiki:// URIs
245        #[arg(long)]
246        name: String,
247        /// Optional one-line description
248        #[arg(long)]
249        description: Option<String>,
250        /// Update space entry if name differs from existing
251        #[arg(long)]
252        force: bool,
253        /// Set as default wiki
254        #[arg(long)]
255        set_default: bool,
256        /// Content directory relative to repo root (default: "wiki")
257        #[arg(long)]
258        wiki_root: Option<String>,
259    },
260    /// Register an existing wiki repository without creating files
261    Register {
262        /// Absolute path to the existing wiki repository
263        path: String,
264        /// Wiki name — used in wiki:// URIs
265        #[arg(long)]
266        name: String,
267        /// Optional one-line description
268        #[arg(long)]
269        description: Option<String>,
270        /// Content directory relative to repo root (overrides wiki.toml)
271        #[arg(long)]
272        wiki_root: Option<String>,
273    },
274    /// List all registered wikis
275    List {
276        /// Wiki name (omit for all)
277        name: Option<String>,
278        /// Output format: text | json
279        #[arg(long)]
280        format: Option<String>,
281    },
282    /// Remove a wiki from the registry
283    Remove {
284        /// Wiki name to remove
285        name: String,
286        /// Also delete the wiki directory from disk
287        #[arg(long)]
288        delete: bool,
289    },
290    /// Set the default wiki
291    SetDefault {
292        /// Wiki name to set as default
293        name: String,
294    },
295}
296
297/// Subcommands for `llm-wiki config`.
298#[derive(Subcommand)]
299pub enum ConfigAction {
300    /// Print a config value
301    Get {
302        /// Config key (e.g. defaults.search_top_k)
303        key: String,
304    },
305    /// Set a config value
306    Set {
307        /// Config key
308        key: String,
309        /// Config value
310        value: String,
311        /// Write to global config
312        #[arg(long)]
313        global: bool,
314        /// Write to per-wiki config
315        #[arg(long)]
316        wiki: Option<String>,
317    },
318    /// Print all resolved config
319    List {
320        /// Global config only
321        #[arg(long)]
322        global: bool,
323        /// Per-wiki config only
324        #[arg(long)]
325        wiki: Option<String>,
326        /// Output format: text | json
327        #[arg(long)]
328        format: Option<String>,
329    },
330}
331
332/// Subcommands for `llm-wiki content`.
333#[derive(Subcommand)]
334pub enum ContentAction {
335    /// Read a page or asset by slug or wiki:// URI
336    Read {
337        /// Slug or wiki:// URI
338        uri: String,
339        /// Strip frontmatter from output
340        #[arg(long)]
341        no_frontmatter: bool,
342        /// List co-located assets instead of content
343        #[arg(long)]
344        list_assets: bool,
345    },
346    /// Write a file into the wiki tree
347    Write {
348        /// Slug or wiki:// URI
349        uri: String,
350        /// Read content from a file instead of stdin
351        #[arg(long)]
352        file: Option<String>,
353    },
354    /// Create a page or section with scaffolded frontmatter
355    New {
356        /// Slug or wiki:// URI
357        uri: String,
358        /// Create a section instead of a page
359        #[arg(long)]
360        section: bool,
361        /// Create as bundle (folder + index.md)
362        #[arg(long)]
363        bundle: bool,
364        /// Page title (default: derived from slug)
365        #[arg(long)]
366        name: Option<String>,
367        /// Page type (default: page)
368        #[arg(long, name = "type")]
369        r#type: Option<String>,
370        /// Show what would be created without creating
371        #[arg(long)]
372        dry_run: bool,
373    },
374    /// Commit pending changes to git
375    Commit {
376        /// Page slugs to commit (omit for --all)
377        slugs: Vec<String>,
378        /// Commit all pending changes
379        #[arg(long)]
380        all: bool,
381        /// Commit message
382        #[arg(long, short)]
383        message: Option<String>,
384    },
385}
386
387/// Subcommands for `llm-wiki index`.
388#[derive(Subcommand)]
389pub enum IndexAction {
390    /// Rebuild the search index from committed Markdown
391    Rebuild {
392        /// Walk and count pages, no write
393        #[arg(long)]
394        dry_run: bool,
395        /// Output format: text | json
396        #[arg(long)]
397        format: Option<String>,
398    },
399    /// Inspect index health
400    Status {
401        /// Output format: text | json
402        #[arg(long)]
403        format: Option<String>,
404    },
405}
406
407/// Subcommands for `llm-wiki schema`.
408#[derive(Subcommand)]
409pub enum SchemaAction {
410    /// List all registered types
411    List {
412        /// Output format: text | json
413        #[arg(long)]
414        format: Option<String>,
415    },
416    /// Show JSON Schema or frontmatter template for a type
417    Show {
418        /// Type name
419        name: String,
420        /// Print frontmatter template instead of schema
421        #[arg(long)]
422        template: bool,
423        /// Output format: text | json
424        #[arg(long)]
425        format: Option<String>,
426    },
427    /// Register a custom type
428    Add {
429        /// Type name
430        name: String,
431        /// Path to JSON Schema file
432        schema_path: String,
433    },
434    /// Unregister a type and remove its pages from the index
435    Remove {
436        /// Type name
437        name: String,
438        /// Also delete/modify the schema file
439        #[arg(long)]
440        delete: bool,
441        /// Also delete page .md files from disk
442        #[arg(long)]
443        delete_pages: bool,
444        /// Show what would be done without doing it
445        #[arg(long)]
446        dry_run: bool,
447    },
448    /// Validate schema files and index resolution
449    Validate {
450        /// Validate a specific type only (omit for all)
451        name: Option<String>,
452    },
453}