zernel/commands/
onboard.rs

1// Copyright (C) 2026 Dyber, Inc. — Proprietary
2
3//! zernel onboard — One-command developer onboarding
4//!
5//! Gets a new team member from "laptop" to "training a model" in minutes.
6
7use anyhow::Result;
8use clap::Subcommand;
9use std::process::Command;
10
11#[derive(Subcommand)]
12pub enum OnboardCommands {
13    /// Full onboarding (check environment, install stack, create project)
14    Setup {
15        /// Project name
16        #[arg(default_value = "my-first-project")]
17        name: String,
18    },
19    /// Sync environment from another machine's snapshot
20    Sync {
21        /// Path to environment snapshot file
22        file: String,
23    },
24    /// Share current environment with a teammate
25    Share {
26        /// Output file
27        #[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            // Step 1: Environment check
40            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            // Step 2: Install ML stack
71            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            // Step 3: Create project
92            println!();
93            println!("[3/5] Creating project: {name}...");
94            let _ = crate::commands::init::run(&name).await;
95
96            // Step 4: Environment snapshot
97            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            // Step 5: Summary
106            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}