1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
mod commands;
mod config;
mod git;
mod paths;
mod registry;
mod render;
mod runner;
mod scanner;
mod state;
mod template;
use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "devist")]
#[command(version, about = "Project bootstrap CLI for AI-assisted development")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Check environment dependencies
Doctor,
/// Show devist info
About,
/// Initialize ~/.devist workspace (run once)
Setup,
/// Manage templates
Template {
#[command(subcommand)]
cmd: commands::template::TemplateCmd,
},
/// Manage registered projects
Project {
#[command(subcommand)]
cmd: commands::project::ProjectCmd,
},
/// Create a new project from a template
Init {
/// Project name
name: String,
/// Template to use
#[arg(short, long)]
template: String,
/// Target directory (defaults to ./<name>)
#[arg(long)]
path: Option<PathBuf>,
/// Override variables: --var key=value
#[arg(long = "var", value_name = "KEY=VALUE")]
vars: Vec<String>,
},
/// Start a project's backend (stops any other active backend first)
Start {
/// Project name
name: String,
/// Also run the dev server in foreground after starting backend
#[arg(long)]
dev: bool,
},
/// Stop the active project's backend (or a specific one)
Stop {
/// Optional project name (defaults to currently active)
name: Option<String>,
},
/// Print a project brief (git state + scan + recent activity)
Brief {
/// Project name
name: String,
/// Output as JSON instead of Markdown
#[arg(long)]
json: bool,
},
/// Scan a project's codebase metadata
Scan {
/// Project name
name: String,
/// Output as JSON
#[arg(long)]
json: bool,
/// Print directory tree instead of stats
#[arg(long)]
tree: bool,
/// Max depth for tree output
#[arg(long, default_value_t = 4)]
max_depth: usize,
},
/// Explain a specific file or directory inside a project
Explain {
/// Project name
name: String,
/// Path or filename inside the project
target: String,
/// Max lines of file content to display
#[arg(long, default_value_t = 200)]
max_lines: usize,
},
/// Watch a project for changes and print activity
Watch {
/// Project name
name: String,
/// Debounce in milliseconds
#[arg(long, default_value_t = 500)]
debounce_ms: u64,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Doctor => commands::doctor::run(),
Commands::About => commands::about::run(),
Commands::Setup => commands::init_workspace::run(),
Commands::Template { cmd } => commands::template::run(cmd),
Commands::Project { cmd } => commands::project::run(cmd),
Commands::Init {
name,
template,
path,
vars,
} => commands::init::run(name, template, path, vars),
Commands::Start { name, dev } => commands::start::run(name, dev),
Commands::Stop { name } => commands::stop::run(name),
Commands::Brief { name, json } => commands::brief::run(name, json),
Commands::Scan {
name,
json,
tree,
max_depth,
} => commands::scan::run(name, json, tree, max_depth),
Commands::Explain {
name,
target,
max_lines,
} => commands::explain::run(name, target, max_lines),
Commands::Watch { name, debounce_ms } => commands::watch::run(name, debounce_ms),
}
}