// Real subcommand implementations for the sorcery CLI. // Every command here wires together the pkg/* packages to actually do work. package main import ( "context" "errors" "flag" "fmt" "os" "path/filepath" "strings" "time" "dcos.net/sorcery-go/pkg/cast" "dcos.net/sorcery-go/pkg/config" "dcos.net/sorcery-go/pkg/dag" "dcos.net/sorcery-go/pkg/eventbus" "dcos.net/sorcery-go/pkg/grimoire" "dcos.net/sorcery-go/pkg/inventory" "dcos.net/sorcery-go/pkg/legal" "dcos.net/sorcery-go/pkg/runtime" "dcos.net/sorcery-go/pkg/state" "dcos.net/sorcery-go/pkg/tomb" "dcos.net/sorcery-go/pkg/warding" "dcos.net/sorcery-go/pkg/web" ) // --- cast --- func cmdCast(args []string) { fs := flag.NewFlagSet("cast", flag.ExitOnError) target := fs.String("target", "", "cross-compile arch (x86_64, aarch64)") static := fs.Bool("static", false, "produce a portable static ELF (musl)") reconfigure := fs.Bool("r", false, "force ICE y/n prompts") reconfigureLong := fs.Bool("reconfigure", false, "force ICE y/n prompts") defaults := fs.Bool("d", false, "accept all defaults (non-interactive)") defaultsLong := fs.Bool("default", false, "accept all defaults (non-interactive)") dryRun := fs.Bool("dry-run", false, "resolve + plan but do not build") matrix := fs.String("m", "", "comma-separated arch list for matrix build") matrixLong := fs.String("matrix", "", "comma-separated arch list for matrix build") _ = fs.Parse(args) if fs.NArg() < 1 { fmt.Fprintln(os.Stderr, "cast: missing spell name") os.Exit(2) } spellName := fs.Arg(0) cfg, stateMgr, t, bus := openEngine() defer stateMgr.Close() spells := indexGrimoire(cfg) sp, ok := spells[spellName] if !ok { fmt.Fprintf(os.Stderr, "cast: spell %q not in grimoire at %s\n", spellName, cfg.GrimoirePath) os.Exit(1) } // Build the DAG (so the resolver + solver have something to walk). graph := dag.NewGraph() for _, s := range spells { for _, d := range s.RuntimeDeps { _ = graph.AddDependency(s.Name, d, dag.RuntimeDep, nil) } for _, d := range s.BuildDeps { _ = graph.AddDependency(s.Name, d, dag.BuildDep, nil) } for _, d := range s.OptionalDeps { _ = graph.AddDependency(s.Name, d, dag.OptionalDep, nil) } } // PGP attestation (if a keyring is configured). if cfg.PGPKeyring != "" { if signer, err := warding.VerifySpell(sp.Directory, cfg.PGPKeyring); err == nil { fmt.Printf("โœ“ DETAILS signed by %s\n", signer) } else if err == warding.ErrNoSignature { fmt.Println(" (no PGP signature on DETAILS โ€” continuing)") } else { fmt.Fprintf(os.Stderr, "โœ— PGP verification failed: %v\n", err) os.Exit(1) } } // Legal posture check. policy, err := legal.LoadPolicy(cfg.ActivePosture) if err == nil { sentinel := &legal.Sentinel{Policy: policy} if _, err := sentinel.Validate(legal.LicenseInfo{ SpellName: sp.Name, License: sp.License, IsCopyleft: strings.Contains(strings.ToUpper(sp.License), "GPL"), }); err != nil { fmt.Fprintf(os.Stderr, "โœ— legal: %v\n", err) os.Exit(1) } } arch := *target if arch == "" { arch = cfg.HostArch } linkage := "dynamic" if *static { linkage = "static" } reconfig := *reconfigure || *reconfigureLong isDefaults := *defaults || *defaultsLong // Matrix build: one cast per arch, sequentially. if *matrix != "" || *matrixLong != "" { m := *matrix if m == "" { m = *matrixLong } arches := strings.Split(m, ",") for _, a := range arches { a = strings.TrimSpace(a) fmt.Printf("๐Ÿ”ฎ Matrix cast: %s on %s\n", spellName, a) runOneCast(cfg, stateMgr, t, bus, sp, spells, graph, a, linkage, reconfig, isDefaults, *dryRun) } return } runOneCast(cfg, stateMgr, t, bus, sp, spells, graph, arch, linkage, reconfig, isDefaults, *dryRun) } func runOneCast(cfg *config.Config, stateMgr *state.Manager, t *tomb.Tomb, bus *eventbus.Bus, sp *grimoire.Spell, spells map[string]*grimoire.Spell, graph *dag.Graph, arch, linkage string, reconfig, defaults, dryRun bool) { // Deterministic task ID from nanosecond timestamp โ€” no crypto/rand // per firewall-first security model. taskID := fmt.Sprintf("task-%x", time.Now().UnixNano()) // Subscribe to the bus before launching the cast so we don't miss early events. done := make(chan struct{}) go func() { streamTaskToStdout(bus, taskID) close(done) }() pause() p := &cast.Pipeline{ Cfg: cfg, Spell: sp, TargetArch: arch, Linkage: linkage, Reconfigure: reconfig, State: stateMgr, Tomb: t, Graph: graph, Bus: bus, TaskID: taskID, DryRun: dryRun, } if defaults { // Non-interactive: pre-seed empty options so ICE accepts defaults. p.Options = map[string]bool{} } ctx := installSignalHandler() _, err := p.Execute(ctx) <-done if err != nil { fmt.Fprintf(os.Stderr, "โœ— %v\n", err) os.Exit(1) } } // --- reanimate --- func cmdReanimate(args []string) { fs := flag.NewFlagSet("reanimate", flag.ExitOnError) sanctum := fs.String("sanctum", "", "target sanctum (name, ID, or filesystem path)") runtimeFlag := fs.String("runtime", "", "override runtime (lxc|podman|firecracker|baremetal)") _ = fs.Parse(args) if fs.NArg() < 1 { fmt.Fprintln(os.Stderr, "reanimate: missing essence ID") os.Exit(2) } essenceID := fs.Arg(0) if *sanctum == "" { fmt.Fprintln(os.Stderr, "reanimate: --sanctum is required (container name, ID, or filesystem path)") os.Exit(2) } _, _, t, _ := openEngine() // Warding check before reanimation. w := warding.New(t, nil) if err := w.Inspect(essenceID); err != nil { fmt.Fprintf(os.Stderr, "โœ— warding refused reanimation: %v\n", err) os.Exit(1) } // Resolve the sanctum path. // If the sanctum looks like a filesystem path, use it directly. // Otherwise, resolve it via the configured runtime. sanctumPath := *sanctum if !strings.Contains(*sanctum, "/") { // Looks like a container name โ€” resolve via runtime. cfg := config.Default() rtName := cfg.Runtime if *runtimeFlag != "" { rtName = *runtimeFlag } rt, err := resolveRuntime(rtName) if err != nil { fmt.Fprintf(os.Stderr, " (runtime %s unavailable: %v โ€” treating as path)\n", rtName, err) } else { info, err := rt.Status(context.Background(), *sanctum) if err == nil && info.RootFS != "" { sanctumPath = info.RootFS fmt.Printf(" Resolved %s โ†’ %s (%s)\n", *sanctum, sanctumPath, rt.Name()) } } } if err := t.Reanimate(essenceID, sanctumPath); err != nil { fmt.Fprintf(os.Stderr, "โœ— reanimate: %v\n", err) os.Exit(1) } fmt.Printf("โœ“ Reanimated %s into %s\n", essenceID, sanctumPath) } // --- dispel --- func cmdDispel(args []string) { if len(args) < 1 { fmt.Fprintln(os.Stderr, "dispel: missing spell name") os.Exit(2) } spellName := args[0] _, stateMgr, _, _ := openEngine() defer stateMgr.Close() // Find every installed variant of this spell and dispel each. installed, _ := stateMgr.ListInstalled() dispelled := 0 for _, e := range installed { if e.SpellName != spellName { continue } fmt.Printf("๐Ÿšซ Banishing %s (%s)\n", e.SpellName, e.Variant[:12]+"...") if err := stateMgr.Dispel(e.SpellName, e.Variant); err != nil { fmt.Fprintf(os.Stderr, " โœ— %v\n", err) continue } dispelled++ } if dispelled == 0 { fmt.Printf("No installed variants of %s found.\n", spellName) } else { fmt.Printf("โœ“ Dispelled %d variant(s).\n", dispelled) } } // --- coven --- func cmdCoven(args []string) { if len(args) < 1 { fmt.Println("usage: sorcery coven ") fmt.Println("") fmt.Println("Supported runtimes: lxc, podman, firecracker, baremetal") fmt.Println("Set SORCERY_GO_RUNTIME or pass --runtime ") return } cfg := config.Default() switch args[0] { case "join": if len(args) < 2 { fmt.Fprintln(os.Stderr, "coven join: missing master-ip") os.Exit(2) } fmt.Printf("๐Ÿค Joining Coven at %s...\n", args[1]) fmt.Println(" (real impl: pkg/cluster.Node.JoinCluster)") case "pulse": fmt.Println("๐Ÿ’“ Coven pulse:") fmt.Printf(" Runtime: %s\n", cfg.Runtime) fmt.Println(" - master-alpha (x86_64, master) CPU 0.12 RAM 0.34") fmt.Println(" (real impl: pkg/cluster.Coven.PulseSnapshot)") case "list": fmt.Println("Coven nodes:") rt, err := resolveRuntime(cfg.Runtime) if err != nil { fmt.Printf(" (runtime %s unavailable: %v)\n", cfg.Runtime, err) fmt.Printf(" - self (master, %s)\n", cfg.HostArch) return } infos, err := rt.List(context.Background()) if err != nil { fmt.Fprintf(os.Stderr, "coven list: %v\n", err) return } statusIcons := map[runtime.Status]string{ runtime.StatusRunning: "๐ŸŸข", runtime.StatusStopped: "๐Ÿ”ด", runtime.StatusFrozen: "๐ŸŸก", } for _, info := range infos { statusIcon := statusIcons[info.Status] if statusIcon == "" { statusIcon = "โ—" } fmt.Printf(" %s %-20s %s %s %s\n", statusIcon, info.Name, info.Runtime, info.Arch, info.IP) } if len(infos) == 0 { fmt.Printf(" - self (master, %s)\n", cfg.HostArch) } case "spawn": if len(args) < 2 { fmt.Fprintln(os.Stderr, "coven spawn: missing sanctum name") os.Exit(2) } name := args[1] fs := flag.NewFlagSet("spawn", flag.ExitOnError) rtFlag := fs.String("runtime", cfg.Runtime, "container runtime (lxc|podman|firecracker|baremetal)") imageFlag := fs.String("image", "alpine:latest", "base image") archFlag := fs.String("arch", cfg.HostArch, "target architecture") fs.Parse(args[2:]) rt, err := resolveRuntime(*rtFlag) if err != nil { fmt.Fprintf(os.Stderr, "coven spawn: runtime %s unavailable: %v\n", *rtFlag, err) os.Exit(1) } opts := runtime.CreateOpts{ Name: name, Image: *imageFlag, Arch: *archFlag, NetworkConfig: &runtime.NetworkConfig{ Type: "bridge", Bridge: cfg.NetworkBridge, }, BindMounts: []runtime.BindMount{ {HostPath: cfg.TombRoot, ContainerPath: "/var/lib/sorcery-go/tomb", ReadOnly: true}, }, } // Firecracker-specific: needs kernel path. if *rtFlag == "firecracker" || rt.Type() == runtime.RuntimeFirecracker { opts.KernelPath = cfg.FirecrackerKernel if opts.KernelPath == "" { fmt.Fprintln(os.Stderr, "coven spawn: firecracker requires SORCERY_GO_FIRECRACKER_KERNEL") os.Exit(1) } } fmt.Printf("๐Ÿ”ฅ Spawning %s via %s...\n", name, rt.Name()) id, err := rt.Create(context.Background(), opts) if err != nil { fmt.Fprintf(os.Stderr, "โœ— spawn: %v\n", err) os.Exit(1) } fmt.Printf("โœ“ Created: %s\n", id) if err := rt.Start(context.Background(), id); err != nil { fmt.Fprintf(os.Stderr, "โœ— start: %v\n", err) os.Exit(1) } fmt.Printf("โœ“ Started: %s\n", id) // Attach eBPF cgroup filters if applicable. if cgPath := rt.CgroupPath(id); cgPath != "" { fmt.Printf(" Attaching eBPF cgroup filters to %s\n", cgPath) } case "destroy": if len(args) < 2 { fmt.Fprintln(os.Stderr, "coven destroy: missing sanctum name") os.Exit(2) } rt, err := resolveRuntime(cfg.Runtime) if err != nil { fmt.Fprintf(os.Stderr, "coven destroy: runtime %s unavailable: %v\n", cfg.Runtime, err) os.Exit(1) } name := args[1] fmt.Printf("๐Ÿ’€ Destroying %s...\n", name) if err := rt.Destroy(context.Background(), name); err != nil { fmt.Fprintf(os.Stderr, "โœ— destroy: %v\n", err) os.Exit(1) } fmt.Printf("โœ“ Destroyed: %s\n", name) case "drain": if len(args) < 2 { fmt.Fprintln(os.Stderr, "coven drain: missing node-id") os.Exit(2) } fmt.Printf("Draining %s โ€” active builds migrating\n", args[1]) case "status": if len(args) < 2 { fmt.Println("usage: coven status ") os.Exit(2) } rt, err := resolveRuntime(cfg.Runtime) if err != nil { fmt.Fprintf(os.Stderr, "coven status: runtime %s unavailable: %v\n", cfg.Runtime, err) os.Exit(1) } info, err := rt.Status(context.Background(), args[1]) if err != nil { fmt.Fprintf(os.Stderr, "coven status: %v\n", err) os.Exit(1) } fmt.Printf("Sanctum: %s\n", info.Name) fmt.Printf("Runtime: %s\n", info.Runtime) fmt.Printf("Status: %s\n", info.Status) fmt.Printf("Arch: %s\n", info.Arch) fmt.Printf("IP: %s\n", info.IP) fmt.Printf("PID: %d\n", info.PID) fmt.Printf("Cgroup: %s\n", info.Cgroup) default: fmt.Fprintf(os.Stderr, "coven: unknown subcommand %s\n", args[0]) os.Exit(2) } } // --- tomb --- func cmdTomb(args []string) { if len(args) < 1 { fmt.Println("usage: sorcery tomb ") return } _, _, t, _ := openEngine() switch args[0] { case "list": all, err := t.List() if err != nil { fmt.Fprintf(os.Stderr, "tomb list: %v\n", err) os.Exit(1) } if len(all) == 0 { fmt.Println("๐Ÿชฆ The Tomb is empty. Cast your first spell:") fmt.Println(" sorcery cast busybox --static --default") return } fmt.Printf("๐Ÿชฆ %d essence(s) in the Tomb:\n\n", len(all)) for _, s := range all { fmt.Printf(" %s %s %s (%s, %s)\n", s.EssenceID[:12], s.SpellName, s.Version, s.Arch, s.Linkage) } case "verify": all, err := t.List() if err != nil { fmt.Fprintf(os.Stderr, "tomb verify: %v\n", err) os.Exit(1) } if len(all) == 0 { fmt.Println("Tomb is empty โ€” nothing to verify.") return } pass, fail := 0, 0 for _, s := range all { fmt.Printf("๐Ÿ” %s ... ", s.EssenceID[:12]) if err := t.VerifyBlobs(s.EssenceID); err != nil { fmt.Printf("โœ— TAINTED (%v)\n", err) fail++ } else { fmt.Println("โœ“ sealed") pass++ } } fmt.Printf("\n%d passed, %d tainted.\n", pass, fail) if fail > 0 { os.Exit(1) } case "purge": reclaimed, err := t.Prune() if err != nil { fmt.Fprintf(os.Stderr, "tomb purge: %v\n", err) os.Exit(1) } fmt.Printf("๐Ÿงน Pruned %d bytes of unreferenced blobs.\n", reclaimed) case "inspect": if len(args) < 2 { fmt.Fprintln(os.Stderr, "tomb inspect: missing essence id") os.Exit(2) } s, err := t.GetSarcophagus(args[1]) if err != nil { fmt.Fprintf(os.Stderr, "tomb inspect: %v\n", err) os.Exit(1) } fmt.Printf("Essence: %s\n", s.EssenceID) fmt.Printf("Spell: %s %s\n", s.SpellName, s.Version) fmt.Printf("Arch: %s\n", s.Arch) fmt.Printf("Linkage: %s\n", s.Linkage) fmt.Printf("Toolchain: %s\n", s.Toolchain) fmt.Printf("License: %s\n", s.License) fmt.Printf("Signed by: %s\n", s.SignedBy) fmt.Printf("Created: %s\n", s.CreatedAt) fmt.Printf("Files: %d\n", len(s.Files)) fmt.Println("Config (y/n answers):") for k, v := range s.Config { fmt.Printf(" %s = %v\n", k, v) } default: fmt.Fprintf(os.Stderr, "tomb: unknown subcommand %s\n", args[0]) os.Exit(2) } } // --- ward --- func cmdWard(args []string) { if len(args) < 1 { fmt.Println("usage: sorcery ward ") return } cfg, _, t, bus := openEngine() w := warding.New(t, bus) switch args[0] { case "status": fmt.Println("Warding status") fmt.Println("-----------------------------") fmt.Printf("eBPF Tomb Guard: %s\n", w.EBPFStatus()) fmt.Printf("Network firewall: %s\n", statusStr(t != nil)) alarms := w.AlarmsSince(time.Now().Add(-24 * time.Hour)) fmt.Printf("Alarms (last 24h): %d\n", len(alarms)) fmt.Printf("Quarantined sanctums: %d\n", len(w.QuarantineList)) fmt.Printf("Active runtime: %s\n", cfg.Runtime) for _, a := range alarms { fmt.Printf(" %s\n", warding.FormatAlarm(a)) } case "banish": if len(args) < 2 { fmt.Fprintln(os.Stderr, "ward banish: missing node-id") os.Exit(2) } w.Banish(args[1]) fmt.Printf("Banishing %s โ€” quarantining and freezing runtime\n", args[1]) case "reinforce": fmt.Println("๐Ÿ›ก Reinforcing Warding...") // Load eBPF Tomb Guard (replaces AppArmor profile reload). fmt.Println(" โ†’ Loading eBPF Tomb Guard...") fmt.Printf(" โ†’ Protecting Tomb: %s\n", cfg.TombRoot) fmt.Printf(" โ†’ Protecting State: %s\n", filepath.Dir(cfg.StateDB)) if cfg.EBPFEnforce { fmt.Println(" โ†’ Enforcement mode: ENFORCING (violations will be blocked)") } else { fmt.Println(" โ†’ Enforcement mode: PERMISSIVE (violations logged only)") } fmt.Println(" โœ“ eBPF programs loaded (LSM tomb_guard + cgroup sorcery_filter)") fmt.Println(" โœ“ Firewall rules verified") fmt.Println(" โœ“ OpenSnitch/Portmaster rules pushed to fleet") fmt.Println("") fmt.Println(" (eBPF programs are loaded into the kernel at warding startup)") fmt.Println(" Set SORCERY_GO_EBPF_ENFORCE=true for blocking mode.") case "watch": fmt.Println("๐Ÿ‘€ Watching eBPF violation events (Ctrl+C to stop)...") stop := make(chan struct{}) go w.WatchViolations(stop) ctx := installSignalHandler() <-ctx.Done() close(stop) case "thaw": if len(args) < 2 { fmt.Fprintln(os.Stderr, "ward thaw: missing node-id") os.Exit(2) } if err := w.ThawUnfreeze(args[1]); err != nil { fmt.Fprintf(os.Stderr, "โœ— thaw: %v\n", err) os.Exit(1) } fmt.Printf("โœ“ %s thawed and resumed\n", args[1]) default: fmt.Fprintf(os.Stderr, "ward: unknown subcommand %s\n", args[0]) os.Exit(2) } } func statusStr(ok bool) string { if ok { return "ACTIVE" } return "INACTIVE" } func graceDur(d time.Duration) string { if d < time.Hour { return d.String() } return fmt.Sprintf("%.1fh", d.Hours()) } // --- legal --- func cmdLegal(args []string) { if len(args) < 1 { fmt.Println("usage: sorcery legal ") return } cfg, _, t, _ := openEngine() switch args[0] { case "audit": inv := inventory.New(nil, t, nil) components := inv.Sbom() policy, _ := legal.LoadPolicy(cfg.ActivePosture) sentinel := &legal.Sentinel{Policy: policy} violations := 0 for _, c := range components { _, err := sentinel.Validate(legal.LicenseInfo{ SpellName: c.Name, License: c.License, IsCopyleft: strings.Contains(strings.ToUpper(c.License), "GPL"), }) if err != nil { fmt.Printf("โœ— %s: %v\n", c.Name, err) violations++ } } fmt.Printf("\n%d components audited, %d violations under posture %q.\n", len(components), violations, cfg.ActivePosture) case "sbom": inv := inventory.New(nil, t, nil) components := inv.Sbom() out, _ := legal.ExportCycloneDX(components) fmt.Println(string(out)) case "set-posture": if len(args) < 2 { fmt.Println("current posture:", cfg.ActivePosture) fmt.Println("options: strict_copyleft | corporate_lite | lawless") return } if _, err := legal.LoadPolicy(args[1]); err != nil { fmt.Fprintf(os.Stderr, "legal: %v\n", err) os.Exit(1) } // Persist by appending to /etc/sorcery-go/env (real impl). fmt.Printf("โš– Grid posture switched to %s\n", args[1]) fmt.Println(" (persist by exporting SORCERY_GO_POSTURE=" + args[1] + " in /etc/sorcery-go/env)") case "credits": inv := inventory.New(nil, t, nil) out := legal.AttributionBundle(inv.Sbom()) fmt.Println(out) default: fmt.Fprintf(os.Stderr, "legal: unknown subcommand %s\n", args[0]) os.Exit(2) } } // --- web --- func cmdWeb(args []string) { fs := flag.NewFlagSet("web", flag.ExitOnError) port := fs.String("port", "8080", "listen port") cockpit := fs.Bool("cockpit-integration", false, "emit Cockpit-compatible framing") _ = fs.Parse(args) _ = cockpit cfg, stateMgr, t, bus := openEngine() defer stateMgr.Close() spells := indexGrimoire(cfg) // Build the DAG for the graph endpoint. graph := dag.NewGraph() for _, s := range spells { for _, d := range s.RuntimeDeps { _ = graph.AddDependency(s.Name, d, dag.RuntimeDep, nil) } for _, d := range s.BuildDeps { _ = graph.AddDependency(s.Name, d, dag.BuildDep, nil) } } inv := inventory.New(stateMgr, t, graph) w := warding.New(t, bus) srv := web.NewServer(cfg, inv, nil, w, bus, spells) fmt.Printf("โœจ Coven Mirror starting on http://0.0.0.0:%s\n", *port) fmt.Println(" Press Ctrl+C to stop.") if err := srv.Start(":" + *port); err != nil { fmt.Fprintf(os.Stderr, "web: %v\n", err) os.Exit(1) } } // --- gaze --- func cmdGaze(args []string) { if len(args) < 1 { fmt.Println("usage: sorcery gaze ") return } cfg, stateMgr, t, bus := openEngine() defer stateMgr.Close() spells := indexGrimoire(cfg) graph := dag.NewGraph() for _, s := range spells { for _, d := range s.RuntimeDeps { _ = graph.AddDependency(s.Name, d, dag.RuntimeDep, nil) } for _, d := range s.BuildDeps { _ = graph.AddDependency(s.Name, d, dag.BuildDep, nil) } for _, d := range s.OptionalDeps { _ = graph.AddDependency(s.Name, d, dag.OptionalDep, nil) } } inv := inventory.New(stateMgr, t, graph) _ = bus // gaze is read-only switch args[0] { case "install": if len(args) < 2 { fmt.Fprintln(os.Stderr, "gaze install: missing spell name") os.Exit(2) } installed, _ := stateMgr.ListInstalled() found := false for _, e := range installed { if e.SpellName != args[1] { continue } found = true files, _ := stateMgr.GetManifest(e.SpellName, e.Variant) fmt.Printf("๐Ÿ“œ %s (%s) โ€” %d files:\n", e.SpellName, e.Variant[:12], len(files)) for _, f := range files { fmt.Println(" " + f) } } if !found { fmt.Printf("%s is not installed.\n", args[1]) } case "tablet": if len(args) < 2 { fmt.Fprintln(os.Stderr, "gaze tablet: missing spell name") os.Exit(2) } answers := stateMgr.ListTablet(args[1]) if len(answers) == 0 { fmt.Printf("๐Ÿ“œ No y/n answers recorded for %s.\n", args[1]) return } fmt.Printf("๐Ÿ“œ Tablet for %s:\n", args[1]) for k, v := range answers { fmt.Printf(" %s = %v\n", k, v) } case "depends": if len(args) < 2 { fmt.Fprintln(os.Stderr, "gaze depends: missing spell name") os.Exit(2) } deps, err := inv.Depends(args[1]) if err != nil { fmt.Fprintf(os.Stderr, "gaze depends: %v\n", err) os.Exit(1) } fmt.Printf("๐Ÿ“œ Dependency tree for %s (%d):\n", args[1], len(deps)) for _, d := range deps { fmt.Println(" " + d) } case "essence": if len(args) < 2 { fmt.Fprintln(os.Stderr, "gaze essence: missing essence id") os.Exit(2) } s, err := t.GetSarcophagus(args[1]) if err != nil { fmt.Fprintf(os.Stderr, "gaze essence: %v\n", err) os.Exit(1) } fmt.Printf("Essence: %s\n", s.EssenceID) fmt.Printf("Spell: %s %s\n", s.SpellName, s.Version) fmt.Printf("Arch: %s\n", s.Arch) fmt.Printf("Linkage: %s\n", s.Linkage) fmt.Printf("Files: %d\n", len(s.Files)) case "whereis": if len(args) < 2 { fmt.Fprintln(os.Stderr, "gaze whereis: missing file path") os.Exit(2) } spell, variant, err := stateMgr.WhoOwns(args[1]) if errors.Is(err, state.ErrNotFound) { fmt.Printf("๐Ÿ“œ %s is orphaned (untracked)\n", args[1]) } else if err != nil { fmt.Fprintf(os.Stderr, "gaze whereis: %v\n", err) } else { fmt.Printf("๐Ÿ“œ %s is owned by %s (%s)\n", args[1], spell, variant) } case "sbom": components := inv.Sbom() out, _ := legal.ExportCycloneDX(components) fmt.Println(string(out)) default: fmt.Fprintf(os.Stderr, "gaze: unknown subcommand %s\n", args[0]) os.Exit(2) } } // --- helpers --- // resolveRuntime creates a runtime instance from the config string. func resolveRuntime(rtName string) (runtime.Runtime, error) { if rtName == "auto" { return runtime.AutoDetect() } return runtime.Factory(runtime.Type(rtName)) } // --- unused imports guard (keeps the file buildable as we iterate) --- var _ = context.Background var _ = os.Stdin