2026-01-30 00:22:47 +00:00
|
|
|
package dev
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/host-uk/core/pkg/devops"
|
2026-01-30 00:47:54 +00:00
|
|
|
"github.com/spf13/cobra"
|
2026-01-30 00:22:47 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// addVMCommands adds the dev environment VM commands to the dev parent command.
|
|
|
|
|
// These are added as direct subcommands: core dev install, core dev boot, etc.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMCommands(parent *cobra.Command) {
|
2026-01-30 00:22:47 +00:00
|
|
|
addVMInstallCommand(parent)
|
|
|
|
|
addVMBootCommand(parent)
|
|
|
|
|
addVMStopCommand(parent)
|
|
|
|
|
addVMStatusCommand(parent)
|
|
|
|
|
addVMShellCommand(parent)
|
|
|
|
|
addVMServeCommand(parent)
|
|
|
|
|
addVMTestCommand(parent)
|
|
|
|
|
addVMClaudeCommand(parent)
|
|
|
|
|
addVMUpdateCommand(parent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// addVMInstallCommand adds the 'dev install' command.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMInstallCommand(parent *cobra.Command) {
|
|
|
|
|
installCmd := &cobra.Command{
|
|
|
|
|
Use: "install",
|
|
|
|
|
Short: "Download and install the dev environment image",
|
|
|
|
|
Long: `Downloads the platform-specific dev environment image.
|
|
|
|
|
|
|
|
|
|
The image includes Go, PHP, Node.js, Python, Docker, and Claude CLI.
|
|
|
|
|
Downloads are cached at ~/.core/images/
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
core dev install`,
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return runVMInstall()
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parent.AddCommand(installCmd)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runVMInstall() error {
|
|
|
|
|
d, err := devops.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if d.IsInstalled() {
|
|
|
|
|
fmt.Println(successStyle.Render("Dev environment already installed"))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Printf("Use %s to check for updates\n", dimStyle.Render("core dev update"))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Image:"), devops.ImageName())
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Println("Downloading dev environment...")
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
start := time.Now()
|
|
|
|
|
var lastProgress int64
|
|
|
|
|
|
|
|
|
|
err = d.Install(ctx, func(downloaded, total int64) {
|
|
|
|
|
if total > 0 {
|
|
|
|
|
pct := int(float64(downloaded) / float64(total) * 100)
|
|
|
|
|
if pct != int(float64(lastProgress)/float64(total)*100) {
|
|
|
|
|
fmt.Printf("\r%s %d%%", dimStyle.Render("Progress:"), pct)
|
|
|
|
|
lastProgress = downloaded
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
fmt.Println() // Clear progress line
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("install failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elapsed := time.Since(start).Round(time.Second)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Printf("%s in %s\n", successStyle.Render("Installed"), elapsed)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Printf("Start with: %s\n", dimStyle.Render("core dev boot"))
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 00:47:54 +00:00
|
|
|
// VM boot command flags
|
|
|
|
|
var (
|
|
|
|
|
vmBootMemory int
|
|
|
|
|
vmBootCPUs int
|
|
|
|
|
vmBootFresh bool
|
|
|
|
|
)
|
|
|
|
|
|
2026-01-30 00:22:47 +00:00
|
|
|
// addVMBootCommand adds the 'devops boot' command.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMBootCommand(parent *cobra.Command) {
|
|
|
|
|
bootCmd := &cobra.Command{
|
|
|
|
|
Use: "boot",
|
|
|
|
|
Short: "Start the dev environment",
|
|
|
|
|
Long: `Boots the dev environment VM.
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
core dev boot
|
|
|
|
|
core dev boot --memory 8192 --cpus 4
|
|
|
|
|
core dev boot --fresh`,
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return runVMBoot(vmBootMemory, vmBootCPUs, vmBootFresh)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bootCmd.Flags().IntVar(&vmBootMemory, "memory", 0, "Memory in MB (default: 4096)")
|
|
|
|
|
bootCmd.Flags().IntVar(&vmBootCPUs, "cpus", 0, "Number of CPUs (default: 2)")
|
|
|
|
|
bootCmd.Flags().BoolVar(&vmBootFresh, "fresh", false, "Stop existing and start fresh")
|
|
|
|
|
|
|
|
|
|
parent.AddCommand(bootCmd)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runVMBoot(memory, cpus int, fresh bool) error {
|
|
|
|
|
d, err := devops.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !d.IsInstalled() {
|
|
|
|
|
return fmt.Errorf("dev environment not installed (run 'core dev install' first)")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
opts := devops.DefaultBootOptions()
|
|
|
|
|
if memory > 0 {
|
|
|
|
|
opts.Memory = memory
|
|
|
|
|
}
|
|
|
|
|
if cpus > 0 {
|
|
|
|
|
opts.CPUs = cpus
|
|
|
|
|
}
|
|
|
|
|
opts.Fresh = fresh
|
|
|
|
|
|
|
|
|
|
fmt.Printf("%s %dMB, %d CPUs\n", dimStyle.Render("Config:"), opts.Memory, opts.CPUs)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Println("Booting dev environment...")
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
if err := d.Boot(ctx, opts); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Println(successStyle.Render("Dev environment running"))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Printf("Connect with: %s\n", dimStyle.Render("core dev shell"))
|
|
|
|
|
fmt.Printf("SSH port: %s\n", dimStyle.Render("2222"))
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// addVMStopCommand adds the 'devops stop' command.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMStopCommand(parent *cobra.Command) {
|
|
|
|
|
stopCmd := &cobra.Command{
|
|
|
|
|
Use: "stop",
|
|
|
|
|
Short: "Stop the dev environment",
|
|
|
|
|
Long: `Stops the running dev environment VM.
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
core dev stop`,
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return runVMStop()
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parent.AddCommand(stopCmd)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runVMStop() error {
|
|
|
|
|
d, err := devops.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
running, err := d.IsRunning(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !running {
|
|
|
|
|
fmt.Println(dimStyle.Render("Dev environment is not running"))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println("Stopping dev environment...")
|
|
|
|
|
|
|
|
|
|
if err := d.Stop(ctx); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println(successStyle.Render("Stopped"))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// addVMStatusCommand adds the 'devops status' command.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMStatusCommand(parent *cobra.Command) {
|
|
|
|
|
statusCmd := &cobra.Command{
|
|
|
|
|
Use: "vm-status",
|
|
|
|
|
Short: "Show dev environment status",
|
|
|
|
|
Long: `Shows the current status of the dev environment.
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
core dev vm-status`,
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return runVMStatus()
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parent.AddCommand(statusCmd)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runVMStatus() error {
|
|
|
|
|
d, err := devops.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
status, err := d.Status(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println(headerStyle.Render("Dev Environment Status"))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// Installation status
|
|
|
|
|
if status.Installed {
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Installed:"), successStyle.Render("Yes"))
|
|
|
|
|
if status.ImageVersion != "" {
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Version:"), status.ImageVersion)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Installed:"), errorStyle.Render("No"))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Printf("Install with: %s\n", dimStyle.Render("core dev install"))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// Running status
|
|
|
|
|
if status.Running {
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Status:"), successStyle.Render("Running"))
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Container:"), status.ContainerID[:8])
|
|
|
|
|
fmt.Printf("%s %dMB\n", dimStyle.Render("Memory:"), status.Memory)
|
|
|
|
|
fmt.Printf("%s %d\n", dimStyle.Render("CPUs:"), status.CPUs)
|
|
|
|
|
fmt.Printf("%s %d\n", dimStyle.Render("SSH Port:"), status.SSHPort)
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Uptime:"), formatVMUptime(status.Uptime))
|
|
|
|
|
} else {
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Status:"), dimStyle.Render("Stopped"))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Printf("Start with: %s\n", dimStyle.Render("core dev boot"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatVMUptime(d time.Duration) string {
|
|
|
|
|
if d < time.Minute {
|
|
|
|
|
return fmt.Sprintf("%ds", int(d.Seconds()))
|
|
|
|
|
}
|
|
|
|
|
if d < time.Hour {
|
|
|
|
|
return fmt.Sprintf("%dm", int(d.Minutes()))
|
|
|
|
|
}
|
|
|
|
|
if d < 24*time.Hour {
|
|
|
|
|
return fmt.Sprintf("%dh %dm", int(d.Hours()), int(d.Minutes())%60)
|
|
|
|
|
}
|
|
|
|
|
return fmt.Sprintf("%dd %dh", int(d.Hours()/24), int(d.Hours())%24)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 00:47:54 +00:00
|
|
|
// VM shell command flags
|
|
|
|
|
var vmShellConsole bool
|
|
|
|
|
|
2026-01-30 00:22:47 +00:00
|
|
|
// addVMShellCommand adds the 'devops shell' command.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMShellCommand(parent *cobra.Command) {
|
|
|
|
|
shellCmd := &cobra.Command{
|
|
|
|
|
Use: "shell [-- command...]",
|
|
|
|
|
Short: "Connect to the dev environment",
|
|
|
|
|
Long: `Opens an interactive shell in the dev environment.
|
|
|
|
|
|
|
|
|
|
Uses SSH by default, or serial console with --console.
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
core dev shell
|
|
|
|
|
core dev shell --console
|
|
|
|
|
core dev shell -- ls -la`,
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return runVMShell(vmShellConsole, args)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shellCmd.Flags().BoolVar(&vmShellConsole, "console", false, "Use serial console instead of SSH")
|
|
|
|
|
|
|
|
|
|
parent.AddCommand(shellCmd)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runVMShell(console bool, command []string) error {
|
|
|
|
|
d, err := devops.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
opts := devops.ShellOptions{
|
|
|
|
|
Console: console,
|
|
|
|
|
Command: command,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
return d.Shell(ctx, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 00:47:54 +00:00
|
|
|
// VM serve command flags
|
|
|
|
|
var (
|
|
|
|
|
vmServePort int
|
|
|
|
|
vmServePath string
|
|
|
|
|
)
|
|
|
|
|
|
2026-01-30 00:22:47 +00:00
|
|
|
// addVMServeCommand adds the 'devops serve' command.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMServeCommand(parent *cobra.Command) {
|
|
|
|
|
serveCmd := &cobra.Command{
|
|
|
|
|
Use: "serve",
|
|
|
|
|
Short: "Mount project and start dev server",
|
|
|
|
|
Long: `Mounts the current project into the dev environment and starts a dev server.
|
|
|
|
|
|
|
|
|
|
Auto-detects the appropriate serve command based on project files.
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
core dev serve
|
|
|
|
|
core dev serve --port 3000
|
|
|
|
|
core dev serve --path public`,
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return runVMServe(vmServePort, vmServePath)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
serveCmd.Flags().IntVarP(&vmServePort, "port", "p", 0, "Port to serve on (default: 8000)")
|
|
|
|
|
serveCmd.Flags().StringVar(&vmServePath, "path", "", "Subdirectory to serve")
|
|
|
|
|
|
|
|
|
|
parent.AddCommand(serveCmd)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runVMServe(port int, path string) error {
|
|
|
|
|
d, err := devops.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
projectDir, err := os.Getwd()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
opts := devops.ServeOptions{
|
|
|
|
|
Port: port,
|
|
|
|
|
Path: path,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
return d.Serve(ctx, projectDir, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 00:47:54 +00:00
|
|
|
// VM test command flags
|
|
|
|
|
var vmTestName string
|
|
|
|
|
|
2026-01-30 00:22:47 +00:00
|
|
|
// addVMTestCommand adds the 'devops test' command.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMTestCommand(parent *cobra.Command) {
|
|
|
|
|
testCmd := &cobra.Command{
|
|
|
|
|
Use: "test [-- command...]",
|
|
|
|
|
Short: "Run tests in the dev environment",
|
|
|
|
|
Long: `Runs tests in the dev environment.
|
|
|
|
|
|
|
|
|
|
Auto-detects the test command based on project files, or uses .core/test.yaml.
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
core dev test
|
|
|
|
|
core dev test --name integration
|
|
|
|
|
core dev test -- go test -v ./...`,
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return runVMTest(vmTestName, args)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
testCmd.Flags().StringVarP(&vmTestName, "name", "n", "", "Run named test command from .core/test.yaml")
|
|
|
|
|
|
|
|
|
|
parent.AddCommand(testCmd)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runVMTest(name string, command []string) error {
|
|
|
|
|
d, err := devops.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
projectDir, err := os.Getwd()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
opts := devops.TestOptions{
|
|
|
|
|
Name: name,
|
|
|
|
|
Command: command,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
return d.Test(ctx, projectDir, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 00:47:54 +00:00
|
|
|
// VM claude command flags
|
|
|
|
|
var (
|
|
|
|
|
vmClaudeNoAuth bool
|
|
|
|
|
vmClaudeModel string
|
|
|
|
|
vmClaudeAuthFlags []string
|
|
|
|
|
)
|
|
|
|
|
|
2026-01-30 00:22:47 +00:00
|
|
|
// addVMClaudeCommand adds the 'devops claude' command.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMClaudeCommand(parent *cobra.Command) {
|
|
|
|
|
claudeCmd := &cobra.Command{
|
|
|
|
|
Use: "claude",
|
|
|
|
|
Short: "Start sandboxed Claude session",
|
|
|
|
|
Long: `Starts a Claude Code session inside the dev environment sandbox.
|
|
|
|
|
|
|
|
|
|
Provides isolation while forwarding selected credentials.
|
|
|
|
|
Auto-boots the dev environment if not running.
|
|
|
|
|
|
|
|
|
|
Auth options (default: all):
|
|
|
|
|
gh - GitHub CLI auth
|
|
|
|
|
anthropic - Anthropic API key
|
|
|
|
|
ssh - SSH agent forwarding
|
|
|
|
|
git - Git config (name, email)
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
core dev claude
|
|
|
|
|
core dev claude --model opus
|
|
|
|
|
core dev claude --auth gh,anthropic
|
|
|
|
|
core dev claude --no-auth`,
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return runVMClaude(vmClaudeNoAuth, vmClaudeModel, vmClaudeAuthFlags)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
claudeCmd.Flags().BoolVar(&vmClaudeNoAuth, "no-auth", false, "Don't forward any auth credentials")
|
|
|
|
|
claudeCmd.Flags().StringVarP(&vmClaudeModel, "model", "m", "", "Model to use (opus, sonnet)")
|
|
|
|
|
claudeCmd.Flags().StringSliceVar(&vmClaudeAuthFlags, "auth", nil, "Selective auth forwarding (gh,anthropic,ssh,git)")
|
|
|
|
|
|
|
|
|
|
parent.AddCommand(claudeCmd)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runVMClaude(noAuth bool, model string, authFlags []string) error {
|
|
|
|
|
d, err := devops.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
projectDir, err := os.Getwd()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
opts := devops.ClaudeOptions{
|
|
|
|
|
NoAuth: noAuth,
|
|
|
|
|
Model: model,
|
|
|
|
|
Auth: authFlags,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
return d.Claude(ctx, projectDir, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 00:47:54 +00:00
|
|
|
// VM update command flags
|
|
|
|
|
var vmUpdateApply bool
|
|
|
|
|
|
2026-01-30 00:22:47 +00:00
|
|
|
// addVMUpdateCommand adds the 'devops update' command.
|
2026-01-30 00:47:54 +00:00
|
|
|
func addVMUpdateCommand(parent *cobra.Command) {
|
|
|
|
|
updateCmd := &cobra.Command{
|
|
|
|
|
Use: "update",
|
|
|
|
|
Short: "Check for and apply updates",
|
|
|
|
|
Long: `Checks for dev environment updates and optionally applies them.
|
2026-01-30 00:22:47 +00:00
|
|
|
|
2026-01-30 00:47:54 +00:00
|
|
|
Examples:
|
|
|
|
|
core dev update
|
|
|
|
|
core dev update --apply`,
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return runVMUpdate(vmUpdateApply)
|
|
|
|
|
},
|
|
|
|
|
}
|
2026-01-30 00:22:47 +00:00
|
|
|
|
2026-01-30 00:47:54 +00:00
|
|
|
updateCmd.Flags().BoolVar(&vmUpdateApply, "apply", false, "Download and apply the update")
|
2026-01-30 00:22:47 +00:00
|
|
|
|
2026-01-30 00:47:54 +00:00
|
|
|
parent.AddCommand(updateCmd)
|
2026-01-30 00:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runVMUpdate(apply bool) error {
|
|
|
|
|
d, err := devops.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
fmt.Println("Checking for updates...")
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
current, latest, hasUpdate, err := d.CheckUpdate(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to check for updates: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Current:"), valueStyle.Render(current))
|
|
|
|
|
fmt.Printf("%s %s\n", dimStyle.Render("Latest:"), valueStyle.Render(latest))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
if !hasUpdate {
|
|
|
|
|
fmt.Println(successStyle.Render("Already up to date"))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println(warningStyle.Render("Update available"))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
if !apply {
|
|
|
|
|
fmt.Printf("Run %s to update\n", dimStyle.Render("core dev update --apply"))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop if running
|
|
|
|
|
running, _ := d.IsRunning(ctx)
|
|
|
|
|
if running {
|
|
|
|
|
fmt.Println("Stopping current instance...")
|
|
|
|
|
_ = d.Stop(ctx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println("Downloading update...")
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
start := time.Now()
|
|
|
|
|
err = d.Install(ctx, func(downloaded, total int64) {
|
|
|
|
|
if total > 0 {
|
|
|
|
|
pct := int(float64(downloaded) / float64(total) * 100)
|
|
|
|
|
fmt.Printf("\r%s %d%%", dimStyle.Render("Progress:"), pct)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("update failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elapsed := time.Since(start).Round(time.Second)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Printf("%s in %s\n", successStyle.Render("Updated"), elapsed)
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|