zernel/commands/
onboard.rs1use anyhow::Result;
8use clap::Subcommand;
9use std::process::Command;
10
11#[derive(Subcommand)]
12pub enum OnboardCommands {
13 Setup {
15 #[arg(default_value = "my-first-project")]
17 name: String,
18 },
19 Sync {
21 file: String,
23 },
24 Share {
26 #[arg(long, default_value = "zernel-env-share.yml")]
28 output: String,
29 },
30}
31
32pub async fn run(cmd: OnboardCommands) -> Result<()> {
33 match cmd {
34 OnboardCommands::Setup { name } => {
35 println!("Zernel Onboarding");
36 println!("{}", "=".repeat(60));
37 println!();
38
39 println!("[1/5] Checking environment...");
41 let checks = [
42 ("python3", "--version"),
43 ("git", "--version"),
44 ("nvidia-smi", "--query-gpu=name --format=csv,noheader"),
45 ];
46
47 let mut all_ok = true;
48 for (cmd_name, args) in &checks {
49 let ok = Command::new(cmd_name)
50 .args(args.split_whitespace())
51 .output()
52 .map(|o| o.status.success())
53 .unwrap_or(false);
54
55 let status = if ok {
56 "OK"
57 } else {
58 all_ok = false;
59 "MISSING"
60 };
61 println!(" {cmd_name:<15} {status}");
62 }
63
64 if !all_ok {
65 println!();
66 println!("Some dependencies are missing. Run: zernel doctor");
67 println!("Continue anyway? (y/n)");
68 }
69
70 println!();
72 println!("[2/5] Checking ML stack...");
73 let torch_ok = Command::new("python3")
74 .args(["-c", "import torch; print(torch.__version__)"])
75 .output()
76 .map(|o| o.status.success())
77 .unwrap_or(false);
78
79 if torch_ok {
80 let version = Command::new("python3")
81 .args(["-c", "import torch; print(torch.__version__)"])
82 .output()
83 .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
84 .unwrap_or_default();
85 println!(" PyTorch {version} — already installed");
86 } else {
87 println!(" PyTorch not found.");
88 println!(" Install with: zernel install pytorch");
89 }
90
91 println!();
93 println!("[3/5] Creating project: {name}...");
94 let _ = crate::commands::init::run(&name).await;
95
96 println!();
98 println!("[4/5] Saving environment snapshot...");
99 let snapshot_path = format!("{name}/zernel-env.yml");
100 let _ = Command::new("zernel")
101 .args(["env", "snapshot", "--output", &snapshot_path])
102 .status();
103 println!(" Saved to: {snapshot_path}");
104
105 println!();
107 println!("[5/5] Onboarding complete!");
108 println!();
109 println!(" Next steps:");
110 println!(" cd {name}");
111 println!(" zernel run train.py # Start training");
112 println!(" zernel watch # Monitor GPU dashboard");
113 println!(" zernel gpu status # Check GPU health");
114 println!(" zernel bench quick # Run performance benchmark");
115 println!();
116 println!(" Share this environment with teammates:");
117 println!(" zernel onboard share --output env.yml");
118 println!(" # Teammate runs: zernel onboard sync env.yml");
119 }
120
121 OnboardCommands::Sync { file } => {
122 println!("Syncing environment from: {file}");
123 println!();
124
125 if !std::path::Path::new(&file).exists() {
126 anyhow::bail!("file not found: {file}");
127 }
128
129 println!(" Installing packages from snapshot...");
130 let status = Command::new("zernel")
131 .args(["env", "reproduce", &file])
132 .status();
133
134 match status {
135 Ok(s) if s.success() => {
136 println!(" Environment synced successfully.");
137 }
138 _ => {
139 println!(" Some packages may have failed. Check output above.");
140 }
141 }
142 }
143
144 OnboardCommands::Share { output } => {
145 println!("Generating shareable environment snapshot...");
146 let status = Command::new("zernel")
147 .args(["env", "snapshot", "--output", &output])
148 .status();
149
150 match status {
151 Ok(s) if s.success() => {
152 println!(" Saved to: {output}");
153 println!();
154 println!(" Share with teammates. They can sync with:");
155 println!(" zernel onboard sync {output}");
156 }
157 _ => {
158 println!(" Failed to generate snapshot.");
159 }
160 }
161 }
162 }
163 Ok(())
164}