Implement the core CLI for molecule-cli: - cmd/molecule/main.go: entry point calling cmd.Execute() - internal/cmd/root.go: cobra root with global flags (--api-url, --verbose, --output, --config), registers all 4 command groups - internal/cmd/workspace.go: 7 subcommands (list, create, inspect, delete, restart, audit, delegate) - internal/cmd/agent.go: 4 subcommands (list, inspect, send, peers) - internal/cmd/platform.go: 2 subcommands (audit, health) - internal/cmd/config.go: 5 subcommands (list, get, set, init, view) - internal/cmd/http.go: runHTTP helper shared by agent send and workspace delegate - internal/client/platform.go: control plane HTTP client with workspace/agent/health/audit operations All 18 subcommands wire to platform API via MOLECULE_API_URL. Binary builds to ./bin/mol. Resolves KI-001, KI-002 (partial), KI-003. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
126 lines
3.0 KiB
Go
126 lines
3.0 KiB
Go
// Package cmd implements the CLI command tree.
|
|
package cmd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"text/tabwriter"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// Version is set at build time via -ldflags.
|
|
var Version = "dev"
|
|
|
|
// Global flags.
|
|
var (
|
|
verbose bool
|
|
outputFormat string
|
|
configPath string
|
|
apiURL string
|
|
)
|
|
|
|
// rootCmd is the top-level molecule command.
|
|
var rootCmd = &cobra.Command{
|
|
Use: "mol",
|
|
Version: Version,
|
|
Short: "mol — Molecule AI platform CLI",
|
|
Long: `mol is the CLI for the Molecule AI agent platform.
|
|
|
|
Manage workspaces, inspect agents, audit the platform, and configure
|
|
agent behaviour from the terminal.
|
|
|
|
Quick start:
|
|
mol workspace list
|
|
mol agent list
|
|
mol platform health`,
|
|
SilenceUsage: true,
|
|
SilenceErrors: true,
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.PersistentFlags().StringVar(&apiURL, "api-url",
|
|
envOr("MOLECULE_API_URL", "http://localhost:8080"),
|
|
"Platform API base URL (env: MOLECULE_API_URL)")
|
|
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false,
|
|
"Enable verbose (DEBUG-level) output to stderr")
|
|
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "table",
|
|
"Output format: table | json | yaml")
|
|
rootCmd.PersistentFlags().StringVar(&configPath, "config", "",
|
|
"Path to config file (default ~/.config/mol.yaml or ./mol.yaml)")
|
|
rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
|
|
return &exitError{code: 2, msg: err.Error()}
|
|
})
|
|
rootCmd.SetErr(os.Stderr)
|
|
}
|
|
|
|
// Execute runs the CLI.
|
|
func Execute() error {
|
|
// Load config file.
|
|
if configPath != "" {
|
|
viper.SetConfigFile(configPath)
|
|
} else {
|
|
viper.SetConfigName("mol")
|
|
viper.AddConfigPath("$HOME/.config")
|
|
viper.AddConfigPath(".")
|
|
}
|
|
viper.AutomaticEnv()
|
|
_ = viper.ReadInConfig() // ignore not-found; env vars win
|
|
|
|
return rootCmd.Execute()
|
|
}
|
|
|
|
// envOr returns the value of env var key, or fallback if unset/empty.
|
|
func envOr(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
// init registers all subcommand trees.
|
|
func init() {
|
|
rootCmd.AddCommand(workspaceCmd)
|
|
rootCmd.AddCommand(agentCmd)
|
|
rootCmd.AddCommand(platformCmd)
|
|
rootCmd.AddCommand(configCmd)
|
|
}
|
|
|
|
// exitError wraps a user-facing error with a specific exit code.
|
|
type exitError struct{ code int; msg string }
|
|
|
|
func (e *exitError) Error() string { return e.msg }
|
|
|
|
// handleErr converts an error to the right exit code.
|
|
func handleErr(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if ee, ok := err.(*exitError); ok {
|
|
fmt.Fprintf(os.Stderr, "%s\n", ee.msg)
|
|
os.Exit(ee.code)
|
|
}
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
return nil
|
|
}
|
|
|
|
// printJSON writes v as JSON to stdout.
|
|
func printJSON(v interface{}) error {
|
|
return json.NewEncoder(os.Stdout).Encode(v)
|
|
}
|
|
|
|
// kv writes a key-value pair to the tabwriter (only if v is non-empty).
|
|
func kv(w *tabwriter.Writer, k, v string) {
|
|
if v == "" {
|
|
return
|
|
}
|
|
fmt.Fprintf(w, "%s:\t%s\n", k, v)
|
|
}
|
|
|
|
func versionInfo() string {
|
|
return fmt.Sprintf("mol %s (go %s)", Version, runtime.Version())
|
|
} |