Skip to main content

st/
cli.rs

1// -----------------------------------------------------------------------------
2// CLI Definitions for Smart Tree
3// All command-line argument parsing happens here using clap.
4// Extracted from main.rs to keep things organized!
5// -----------------------------------------------------------------------------
6
7use anyhow::{Context, Result};
8use chrono::NaiveDate;
9use clap::{Parser, Subcommand, ValueEnum};
10use std::path::PathBuf;
11use std::time::SystemTime;
12
13/// Smart Tree CLI - intelligent directory visualization
14#[derive(Parser, Debug)]
15#[command(
16    name = "st",
17    about = "Smart Tree - An intelligent directory visualization tool. Not just a tree, it's a smart-tree!",
18    author
19)]
20pub struct Cli {
21    // =========================================================================
22    // GETTING STARTED
23    // =========================================================================
24    /// Show the cheatsheet - quick reference for all commands
25    #[arg(long, exclusive = true, help_heading = "Getting Started")]
26    pub cheet: bool,
27
28    /// Show version information and check for updates
29    #[arg(short = 'V', long, exclusive = true, help_heading = "Getting Started")]
30    pub version: bool,
31
32    /// Generate shell completion scripts (bash, zsh, fish, powershell)
33    #[arg(
34        long,
35        exclusive = true,
36        value_name = "SHELL",
37        help_heading = "Getting Started"
38    )]
39    pub completions: Option<clap_complete::Shell>,
40
41    /// Generate the man page
42    #[arg(long, exclusive = true, help_heading = "Getting Started")]
43    pub man: bool,
44
45    /// Check for updates and install the latest version
46    #[arg(long, exclusive = true, help_heading = "Getting Started")]
47    pub update: bool,
48
49    /// Skip the automatic update check on startup
50    #[arg(long, help_heading = "Getting Started")]
51    pub no_update_check: bool,
52
53    // =========================================================================
54    // INTERACTIVE MODES
55    // =========================================================================
56    /// Launch Spicy TUI - interactive file browser with fuzzy search!
57    #[arg(long, help_heading = "Interactive Modes")]
58    pub spicy: bool,
59
60    /// Launch Smart Tree Terminal Interface (STTI)
61    #[arg(long, exclusive = true, help_heading = "Interactive Modes")]
62    pub terminal: bool,
63
64    /// Launch web dashboard (browser-based terminal + file browser)
65    #[arg(long, exclusive = true, help_heading = "Interactive Modes")]
66    pub dashboard: bool,
67
68    /// Open browser automatically when starting dashboard
69    #[arg(long, requires = "dashboard", help_heading = "Interactive Modes")]
70    pub open_browser: bool,
71
72    /// Network CIDR allow-list for dashboard (e.g., 192.168.1.0/24)
73    #[arg(long, value_name = "CIDR", requires = "dashboard", help_heading = "Interactive Modes")]
74    pub allow: Vec<String>,
75
76    /// Start HTTP daemon (MCP over HTTP, LLM proxy, The Custodian)
77    #[arg(long, alias = "daemon", help_heading = "Interactive Modes")]
78    pub http_daemon: bool,
79
80    // =========================================================================
81    // MCP SERVER (via Daemon)
82    // =========================================================================
83    /// Run as MCP server for AI assistants (auto-starts daemon)
84    #[arg(long, exclusive = true, help_heading = "MCP Server")]
85    pub mcp: bool,
86
87    /// Install Smart Tree as MCP server in Claude Desktop
88    #[arg(long, exclusive = true, help_heading = "MCP Server")]
89    pub mcp_install: bool,
90
91    /// Uninstall Smart Tree MCP server from Claude Desktop
92    #[arg(long, exclusive = true, help_heading = "MCP Server")]
93    pub mcp_uninstall: bool,
94
95    /// Check MCP installation status in Claude Desktop
96    #[arg(long, exclusive = true, help_heading = "MCP Server")]
97    pub mcp_status: bool,
98
99    // =========================================================================
100    // DAEMON CONTROL
101    // =========================================================================
102    /// Set the log level
103    #[arg(long, value_enum, help_heading = "Daemon Control")]
104    pub log_level: Option<LogLevel>,
105
106    /// Start the Smart Tree daemon
107    #[arg(long, exclusive = true, help_heading = "Daemon Control")]
108    pub daemon_start: bool,
109
110    /// Stop the Smart Tree daemon
111    #[arg(long, exclusive = true, help_heading = "Daemon Control")]
112    pub daemon_stop: bool,
113
114    /// Show Smart Tree daemon status
115    #[arg(long, exclusive = true, help_heading = "Daemon Control")]
116    pub daemon_status: bool,
117
118    /// Get context from the daemon
119    #[arg(long, exclusive = true, help_heading = "Daemon Control")]
120    pub daemon_context: bool,
121
122    /// List projects tracked by the daemon
123    #[arg(long, exclusive = true, help_heading = "Daemon Control")]
124    pub daemon_projects: bool,
125
126    /// Show Foken credits from daemon
127    #[arg(long, exclusive = true, help_heading = "Daemon Control")]
128    pub daemon_credits: bool,
129
130    /// [DEPRECATED: use `st service install`] Install daemon as a system service
131    #[arg(long, exclusive = true, help_heading = "Daemon Control", hide = true)]
132    pub daemon_install: bool,
133
134    // =========================================================================
135    // CONSCIOUSNESS & MEMORY
136    // =========================================================================
137    /// Save agent consciousness state to .aye_consciousness.m8
138    #[arg(long, exclusive = true, help_heading = "Consciousness & Memory")]
139    pub agent_save: bool,
140
141    /// Restore agent consciousness from .aye_consciousness.m8
142    #[arg(long, exclusive = true, help_heading = "Consciousness & Memory")]
143    pub agent_restore: bool,
144
145    /// Show agent consciousness status and summary
146    #[arg(long, exclusive = true, help_heading = "Consciousness & Memory")]
147    pub agent_context: bool,
148
149    /// Ultra-compressed consciousness restoration format
150    #[arg(long, exclusive = true, help_heading = "Consciousness & Memory")]
151    pub agent_kickstart: bool,
152
153    /// Dump raw consciousness file content for debugging
154    #[arg(long, exclusive = true, help_heading = "Consciousness & Memory")]
155    pub agent_dump: bool,
156
157    /// Anchor a memory: --memory-anchor <TYPE> <KEYWORDS> <CONTEXT>
158    /// Types: insight, decision, pattern, gotcha, todo
159    #[arg(long, num_args = 3, value_names = ["TYPE", "KEYWORDS", "CONTEXT"], help_heading = "Consciousness & Memory")]
160    pub memory_anchor: Option<Vec<String>>,
161
162    /// Find memories by keywords (comma-separated)
163    #[arg(long, value_name = "KEYWORDS", help_heading = "Consciousness & Memory")]
164    pub memory_find: Option<String>,
165
166    /// Show memory statistics
167    #[arg(long, exclusive = true, help_heading = "Consciousness & Memory")]
168    pub memory_stats: bool,
169
170    /// Update .m8 consciousness files for a directory
171    #[arg(long, value_name = "PATH", help_heading = "Consciousness & Memory")]
172    pub update_consciousness: Option<String>,
173
174    // =========================================================================
175    // SECURITY
176    // =========================================================================
177    /// Scan codebase for supply chain attack patterns (default: current dir)
178    #[arg(long, value_name = "PATH", default_missing_value = ".", num_args = 0..=1, help_heading = "Security")]
179    pub security_scan: Option<String>,
180
181    /// Scan a file for prompt injection patterns
182    #[arg(long, value_name = "FILE", help_heading = "Security")]
183    pub guardian_scan: Option<String>,
184
185    /// Run Guardian daemon for system-wide AI protection
186    #[arg(long, exclusive = true, help_heading = "Security")]
187    pub guardian_daemon: bool,
188
189    /// Security cleanup - detect and remove malicious MCP entries
190    #[arg(long, exclusive = true, help_heading = "Security")]
191    pub cleanup: bool,
192
193    // =========================================================================
194    // HOOKS
195    // =========================================================================
196    /// Install Smart Tree hooks to AI Agent settings
197    #[arg(long, exclusive = true, help_heading = "Hooks")]
198    pub hooks_install: bool,
199
200    /// Manage hooks: enable, disable, status
201    #[arg(long, value_name = "ACTION", value_parser = ["enable", "disable", "status"], help_heading = "Hooks")]
202    pub hooks_config: Option<String>,
203
204    // =========================================================================
205    // MEGA SESSIONS
206    // =========================================================================
207    /// Start a mega session (persistent cross-context conversation)
208    #[arg(long, value_name = "NAME", default_missing_value = "", num_args = 0..=1, help_heading = "Mega Sessions")]
209    pub mega_start: Option<String>,
210
211    /// Save current mega session snapshot
212    #[arg(long, exclusive = true, help_heading = "Mega Sessions")]
213    pub mega_save: bool,
214
215    /// List all mega sessions
216    #[arg(long, exclusive = true, help_heading = "Mega Sessions")]
217    pub mega_list: bool,
218
219    /// Show mega session statistics
220    #[arg(long, exclusive = true, help_heading = "Mega Sessions")]
221    pub mega_stats: bool,
222
223    // =========================================================================
224    // ANALYSIS
225    // =========================================================================
226    /// Show tokenization statistics for a path
227    #[arg(long, value_name = "PATH", help_heading = "Analysis")]
228    pub token_stats: Option<String>,
229
230    /// Get wave frequency for a directory
231    #[arg(long, value_name = "PATH", help_heading = "Analysis")]
232    pub get_frequency: Option<String>,
233
234    // =========================================================================
235    // LOGGING & TRANSPARENCY
236    // =========================================================================
237    /// Enable activity logging to JSONL file
238    #[arg(long, value_name = "PATH", help_heading = "Logging & Transparency")]
239    pub log: Option<Option<String>>,
240
241    /// Control smart tips (on/off)
242    #[arg(long, value_name = "STATE", value_parser = ["on", "off"], help_heading = "Logging & Transparency")]
243    pub tips: Option<String>,
244
245    // =========================================================================
246    // TOP-LEVEL COMMANDS
247    // =========================================================================
248    #[command(subcommand)]
249    pub cmd: Option<Cmd>,
250
251    // =========================================================================
252    // SCAN OPTIONS
253    // =========================================================================
254    /// Path to analyze (directory, file, URL, or stream)
255    pub path: Option<String>,
256
257    /// Specify input type explicitly (filesystem, qcp, sse, openapi, mem8)
258    #[arg(long, value_name = "TYPE")]
259    pub input: Option<String>,
260
261    #[command(flatten)]
262    pub scan_opts: ScanArgs,
263}
264
265#[derive(Parser, Debug)]
266pub struct ScanArgs {
267    // =========================================================================
268    // OUTPUT FORMAT
269    // =========================================================================
270    /// Output format (classic, ai, quantum, json, etc.)
271    #[arg(
272        short,
273        long,
274        value_enum,
275        default_value = "auto",
276        help_heading = "Output Format"
277    )]
278    pub mode: OutputMode,
279
280    // =========================================================================
281    // FILTERING - What to include/exclude
282    // =========================================================================
283    /// Find files matching regex pattern (e.g., --find "README\.md")
284    #[arg(long, help_heading = "Filtering")]
285    pub find: Option<String>,
286
287    /// Filter by file extension (e.g., --type rs)
288    #[arg(long = "type", help_heading = "Filtering")]
289    pub filter_type: Option<String>,
290
291    /// Filter by entry type: f (files) or d (directories)
292    #[arg(long = "entry-type", value_parser = ["f", "d"], help_heading = "Filtering")]
293    pub entry_type: Option<String>,
294
295    /// Only files larger than size (e.g., --min-size 1M)
296    #[arg(long, help_heading = "Filtering")]
297    pub min_size: Option<String>,
298
299    /// Only files smaller than size (e.g., --max-size 100K)
300    #[arg(long, help_heading = "Filtering")]
301    pub max_size: Option<String>,
302
303    /// Files newer than date (YYYY-MM-DD)
304    #[arg(long, help_heading = "Filtering")]
305    pub newer_than: Option<String>,
306
307    /// Files older than date (YYYY-MM-DD)
308    #[arg(long, help_heading = "Filtering")]
309    pub older_than: Option<String>,
310
311    // =========================================================================
312    // TRAVERSAL - How to scan
313    // =========================================================================
314    /// Traversal depth (0 = auto, 1 = shallow, 10 = deep)
315    #[arg(short, long, default_value = "0", help_heading = "Traversal")]
316    pub depth: usize,
317
318    /// Ignore .gitignore files
319    #[arg(long, help_heading = "Traversal")]
320    pub no_ignore: bool,
321
322    /// Ignore default patterns (node_modules, __pycache__, etc.)
323    #[arg(long, help_heading = "Traversal")]
324    pub no_default_ignore: bool,
325
326    /// Show hidden files (starting with .)
327    #[arg(long, short = 'a', help_heading = "Traversal")]
328    pub all: bool,
329
330    /// Show ignored directories in brackets
331    #[arg(long, help_heading = "Traversal")]
332    pub show_ignored: bool,
333
334    /// Show EVERYTHING (--all + --no-ignore + --no-default-ignore)
335    #[arg(long, help_heading = "Traversal")]
336    pub everything: bool,
337
338    // =========================================================================
339    // SMART SCANNING - Intelligent context-aware output
340    // =========================================================================
341    /// Enable smart mode - surface what matters, not everything
342    /// Groups output by interest: security, changes, important, background
343    #[arg(long, help_heading = "Smart Scanning")]
344    pub smart: bool,
345
346    /// Only show changes since last scan
347    #[arg(long, help_heading = "Smart Scanning")]
348    pub changes_only: bool,
349
350    /// Minimum interest level (0.0-1.0) to display
351    #[arg(long, default_value = "0.0", help_heading = "Smart Scanning")]
352    pub min_interest: f32,
353
354    /// Disable security scanning during traversal
355    #[arg(long, help_heading = "Smart Scanning")]
356    pub no_security: bool,
357
358    // =========================================================================
359    // DISPLAY - How output looks
360    // =========================================================================
361    /// Show filesystem type indicators (X=XFS, 4=ext4, B=Btrfs)
362    #[arg(long, help_heading = "Display")]
363    pub show_filesystems: bool,
364
365    /// Disable emojis (Trish will miss them!)
366    #[arg(long, help_heading = "Display")]
367    pub no_emoji: bool,
368
369    /// Compress output with zlib (base64 encoded)
370    #[arg(short = 'z', long, help_heading = "Display")]
371    pub compress: bool,
372
373    /// Optimize for MCP/API (compression + no colors/emoji)
374    #[arg(long, help_heading = "Display")]
375    pub mcp_optimize: bool,
376
377    /// Compact JSON (single line)
378    #[arg(long, help_heading = "Display")]
379    pub compact: bool,
380
381    /// Path display: off, relative, or full
382    #[arg(
383        long = "path-mode",
384        value_enum,
385        default_value = "off",
386        help_heading = "Display"
387    )]
388    pub path_mode: PathMode,
389
390    /// Color output: always, never, or auto
391    #[arg(long, value_enum, default_value = "auto", help_heading = "Display")]
392    pub color: ColorMode,
393
394    /// Wrap AI output in JSON structure
395    #[arg(long, help_heading = "Display")]
396    pub ai_json: bool,
397
398    // =========================================================================
399    // STREAMING - Real-time output
400    // =========================================================================
401    /// Stream output as files are scanned
402    #[arg(long, help_heading = "Streaming")]
403    pub stream: bool,
404
405    /// Start SSE server for real-time monitoring
406    #[arg(long, help_heading = "Streaming")]
407    pub sse_server: bool,
408
409    /// SSE server port (also used as daemon port)
410    #[arg(long, alias = "daemon-port", default_value = "28428", help_heading = "Streaming")]
411    pub sse_port: u16,
412
413    // =========================================================================
414    // SEARCH & ANALYSIS
415    // =========================================================================
416    /// Search file contents (e.g., --search "TODO")
417    #[arg(long, help_heading = "Search & Analysis")]
418    pub search: Option<String>,
419
420    /// Group by semantic similarity
421    #[arg(long, help_heading = "Search & Analysis")]
422    pub semantic: bool,
423
424    /// Focus analysis on specific file (relations mode)
425    #[arg(long, value_name = "FILE", help_heading = "Search & Analysis")]
426    pub focus: Option<PathBuf>,
427
428    /// Filter relationships: imports, calls, types, tests, coupled
429    #[arg(long, value_name = "TYPE", help_heading = "Search & Analysis")]
430    pub relations_filter: Option<String>,
431
432    // =========================================================================
433    // SORTING
434    // =========================================================================
435    /// Sort by: a-to-z, z-to-a, largest, smallest, newest, oldest, type
436    #[arg(long, value_enum, help_heading = "Sorting")]
437    pub sort: Option<SortField>,
438
439    /// Show only top N results (use with --sort)
440    #[arg(long, value_name = "N", help_heading = "Sorting")]
441    pub top: Option<usize>,
442
443    // =========================================================================
444    // MERMAID & MARKDOWN OPTIONS
445    // =========================================================================
446    /// Mermaid style: flowchart, mindmap, gitgraph, treemap
447    #[arg(
448        long,
449        value_enum,
450        default_value = "flowchart",
451        help_heading = "Mermaid & Markdown"
452    )]
453    pub mermaid_style: MermaidStyleArg,
454
455    /// Exclude mermaid diagrams from markdown
456    #[arg(long, help_heading = "Mermaid & Markdown")]
457    pub no_markdown_mermaid: bool,
458
459    /// Exclude tables from markdown
460    #[arg(long, help_heading = "Mermaid & Markdown")]
461    pub no_markdown_tables: bool,
462
463    /// Exclude pie charts from markdown
464    #[arg(long, help_heading = "Mermaid & Markdown")]
465    pub no_markdown_pie_charts: bool,
466
467    // =========================================================================
468    // ADVANCED
469    // =========================================================================
470    /// Index code to SmartPastCode registry
471    #[arg(long, value_name = "URL", help_heading = "Advanced")]
472    pub index_registry: Option<String>,
473
474    /// Show private functions in docs (function-markdown mode)
475    #[arg(long, help_heading = "Advanced")]
476    pub show_private: bool,
477
478    /// View Smart Edit diffs from .st folder
479    #[arg(long, help_heading = "Advanced")]
480    pub view_diffs: bool,
481
482    /// Clean up old diffs, keep last N per file
483    #[arg(long, value_name = "N", help_heading = "Advanced")]
484    pub cleanup_diffs: Option<usize>,
485}
486
487#[derive(Debug, Subcommand)]
488pub enum Cmd {
489    /// Manage the smart-tree daemon (Linux: systemd, macOS: launchctl, Windows: Task Scheduler)
490    #[command(subcommand)]
491    Service(Service),
492
493    /// Manage project tags
494    #[command(subcommand, name = "project-tags")]
495    ProjectTags(ProjectTags),
496}
497
498#[derive(Debug, Subcommand)]
499pub enum Service {
500    /// Install the smart-tree daemon as a system service
501    Install,
502    /// Uninstall the service
503    Uninstall,
504    /// Start the service for the current project
505    Start,
506    /// Stop the service
507    Stop,
508    /// Show service status
509    Status,
510    /// Show service logs
511    Logs,
512}
513
514#[derive(Debug, Subcommand)]
515pub enum ProjectTags {
516    /// Add a tag to the project
517    Add {
518        /// The tag to add
519        #[arg(required = true)]
520        tag: String,
521    },
522    /// Remove a tag from the project
523    Remove {
524        /// The tag to remove
525        #[arg(required = true)]
526        tag: String,
527    },
528}
529
530/// Sort field options with intuitive names
531#[derive(Debug, Clone, Copy, ValueEnum)]
532pub enum SortField {
533    /// Sort alphabetically A to Z
534    #[value(name = "a-to-z")]
535    AToZ,
536    /// Sort alphabetically Z to A
537    #[value(name = "z-to-a")]
538    ZToA,
539    /// Sort by size, largest files first
540    #[value(name = "largest")]
541    Largest,
542    /// Sort by size, smallest files first
543    #[value(name = "smallest")]
544    Smallest,
545    /// Sort by modification date, newest first
546    #[value(name = "newest")]
547    Newest,
548    /// Sort by modification date, oldest first
549    #[value(name = "oldest")]
550    Oldest,
551    /// Sort by file type/extension
552    #[value(name = "type")]
553    Type,
554    /// Legacy aliases for backward compatibility
555    #[value(name = "name", alias = "alpha")]
556    Name,
557    #[value(name = "size")]
558    Size,
559    #[value(name = "date", alias = "modified")]
560    Date,
561}
562
563/// Enum for mermaid style argument
564#[derive(Debug, Clone, Copy, ValueEnum)]
565pub enum MermaidStyleArg {
566    /// Traditional flowchart (default)
567    Flowchart,
568    /// Mind map style
569    Mindmap,
570    /// Git graph style
571    Gitgraph,
572    /// Treemap style (shows file sizes visually)
573    Treemap,
574}
575
576/// Color mode for output
577#[derive(Debug, Clone, Copy, ValueEnum)]
578pub enum ColorMode {
579    /// Always use colors
580    Always,
581    /// Never use colors
582    Never,
583    /// Auto-detect (colors if terminal)
584    Auto,
585}
586
587/// Path display mode
588#[derive(Debug, Clone, Copy, ValueEnum)]
589pub enum PathMode {
590    /// Show only filenames (default)
591    Off,
592    /// Show paths relative to scan root
593    Relative,
594    /// Show full absolute paths
595    Full,
596}
597
598/// Output format mode
599#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
600pub enum OutputMode {
601    /// Auto mode - smart default selection based on context
602    Auto,
603    /// Classic tree format with metadata and emojis
604    Classic,
605    /// Hexadecimal format with fixed-width fields
606    Hex,
607    /// HexTree - readable quantum compression with tree structure
608    HexTree,
609    /// JSON output for programmatic use
610    Json,
611    /// Unix ls -Alh format
612    Ls,
613    /// AI-optimized format for LLMs
614    Ai,
615    /// Directory statistics only
616    Stats,
617    /// CSV format
618    Csv,
619    /// TSV format
620    Tsv,
621    /// Super compact digest format
622    Digest,
623    /// Emotional tree - files with feelings!
624    Emotional,
625    /// MEM|8 Quantum format - ultimate compression
626    Quantum,
627    /// Semantic grouping format
628    Semantic,
629    /// Projects discovery mode
630    Projects,
631    /// Mermaid diagram format
632    Mermaid,
633    /// Markdown report format
634    Markdown,
635    /// Interactive summary mode
636    Summary,
637    /// AI-optimized summary mode
638    SummaryAi,
639    /// Context mode for AI conversations
640    Context,
641    /// Code relationship analysis
642    Relations,
643    /// Quantum compression with semantic understanding
644    QuantumSemantic,
645    /// Waste detection and optimization analysis
646    Waste,
647    /// Marqant - Quantum-compressed markdown format
648    Marqant,
649    /// SSE - Server-Sent Events streaming format
650    Sse,
651    /// Function documentation in markdown format
652    FunctionMarkdown,
653}
654
655/// Get the ideal depth for each output mode
656pub fn get_ideal_depth_for_mode(mode: &OutputMode) -> usize {
657    match mode {
658        OutputMode::Auto => 3,
659        OutputMode::Ls => 1,
660        OutputMode::Classic => 3,
661        OutputMode::Ai | OutputMode::Hex => 5,
662        OutputMode::Stats => 10,
663        OutputMode::Digest => 10,
664        OutputMode::Emotional => 5,
665        OutputMode::Quantum | OutputMode::QuantumSemantic | OutputMode::HexTree => 5,
666        OutputMode::Summary | OutputMode::SummaryAi | OutputMode::Context => 4,
667        OutputMode::Waste => 10,
668        OutputMode::Relations => 10,
669        OutputMode::Projects => 5,
670        _ => 4,
671    }
672}
673
674/// Parse a date string (YYYY-MM-DD) into SystemTime
675pub fn parse_date(date_str: &str) -> Result<SystemTime> {
676    let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d")?;
677    let datetime = date.and_hms_opt(0, 0, 0).context("Invalid time")?;
678    Ok(SystemTime::from(
679        datetime
680            .and_local_timezone(chrono::Local)
681            .single()
682            .context("Invalid timezone")?,
683    ))
684}
685
686#[derive(Debug, Clone, Copy, ValueEnum)]
687pub enum LogLevel {
688    Error,
689    Warn,
690    Info,
691    Debug,
692    Trace,
693}