molecule-cli/internal/cmd/workspace.go
Molecule AI SDK-Dev ab91b652ef fix(cli): implement true YAML output support
The `--output yaml` flag was accepted but aliased to JSON since
printYAML() was never defined. Add printYAML() using gopkg.in/yaml.v3
(already an indirect dep via viper) and split all output format
branches into explicit json/yaml/table paths.

Affected commands: workspace list/create/inspect/audit,
agent list/inspect/peers, platform audit/health.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 03:47:00 +00:00

300 lines
8.9 KiB
Go

// Package cmd implements the CLI command tree.
package cmd
import (
"encoding/json"
"fmt"
"os"
"text/tabwriter"
"github.com/Molecule-AI/molecule-cli/internal/client"
"github.com/spf13/cobra"
)
// ---------------------------------------------------------------------------
// Workspace command group
// ---------------------------------------------------------------------------
var workspaceCmd = &cobra.Command{
Use: "workspace",
Short: "Manage Molecule AI workspaces",
Long: `List, inspect, create, delete, restart, audit, and delegate to workspaces.`,
}
func init() {
workspaceCmd.AddCommand(
workspaceListCmd, workspaceCreateCmd, workspaceInspectCmd,
workspaceDeleteCmd, workspaceRestartCmd, workspaceAuditCmd, workspaceDelegateCmd,
)
}
// ===========================================================================
// mol workspace list
// ===========================================================================
var workspaceListCmd = &cobra.Command{
Use: "list",
Short: "List all workspaces",
RunE: runWorkspaceList,
}
func runWorkspaceList(cmd *cobra.Command, _ []string) error {
cl := client.New(apiURL)
ws, err := cl.ListWorkspaces()
if err != nil {
return fmt.Errorf("workspace list: %w", err)
}
if outputFormat == "json" {
return printJSON(ws)
}
if outputFormat == "yaml" {
return printYAML(ws)
}
if len(ws) == 0 {
fmt.Println("No workspaces found.")
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
fmt.Fprintln(w, "ID\tNAME\tSTATUS\tROLE\tRUNTIME\tCREATED AT")
for _, s := range ws {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
s.ID, s.Name, s.Status, s.Role, s.Runtime, s.CreatedAt)
}
return w.Flush()
}
// ===========================================================================
// mol workspace create
// ===========================================================================
var createFlags struct {
name string
role string
runtime string
template string
parentID string
workspaceDir string
tier int
}
var workspaceCreateCmd = &cobra.Command{
Use: "create --name <name> [flags]",
Short: "Create a new workspace",
RunE: runWorkspaceCreate,
}
func init() {
f := workspaceCreateCmd.Flags()
f.StringVarP(&createFlags.name, "name", "n", "", "Workspace name (required)")
f.StringVar(&createFlags.role, "role", "", "Role (e.g. pm, report, researcher)")
f.StringVar(&createFlags.runtime, "runtime", "", "Runtime (e.g. claude-code, deepagents)")
f.StringVar(&createFlags.template, "template", "", "Template name or ID")
f.StringVar(&createFlags.parentID, "parent-id", "", "Parent workspace ID")
f.StringVar(&createFlags.workspaceDir, "workspace-dir", "", "Workspace directory path")
f.IntVar(&createFlags.tier, "tier", 0, "Tier value")
workspaceCreateCmd.MarkFlagRequired("name")
}
func runWorkspaceCreate(cmd *cobra.Command, _ []string) error {
cl := client.New(apiURL)
req := client.CreateWorkspaceRequest{Name: createFlags.name}
if createFlags.role != "" {
req.Role = createFlags.role
}
if createFlags.runtime != "" {
req.Runtime = createFlags.runtime
}
if createFlags.template != "" {
req.Template = createFlags.template
}
if createFlags.parentID != "" {
req.ParentID = createFlags.parentID
}
if createFlags.workspaceDir != "" {
req.WorkspaceDir = createFlags.workspaceDir
}
if createFlags.tier > 0 {
req.Tier = createFlags.tier
}
ws, err := cl.CreateWorkspace(req)
if err != nil {
return fmt.Errorf("workspace create: %w", err)
}
if outputFormat == "json" {
return printJSON(ws)
}
if outputFormat == "yaml" {
return printYAML(ws)
}
fmt.Printf("Workspace created: %s (%s)\n", ws.Name, ws.ID)
return nil
}
// ===========================================================================
// mol workspace inspect
// ===========================================================================
var workspaceInspectCmd = &cobra.Command{
Use: "inspect <workspace-id>",
Short: "Show full details for a workspace",
Args: cobra.ExactArgs(1),
RunE: runWorkspaceInspect,
}
func runWorkspaceInspect(cmd *cobra.Command, args []string) error {
cl := client.New(apiURL)
ws, err := cl.GetWorkspace(args[0])
if err != nil {
return fmt.Errorf("workspace inspect: %w", err)
}
if outputFormat == "json" {
return printJSON(ws)
}
if outputFormat == "yaml" {
return printYAML(ws)
}
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
kv(w, "ID", ws.ID)
kv(w, "Name", ws.Name)
kv(w, "Status", ws.Status)
kv(w, "Role", ws.Role)
kv(w, "Runtime", ws.Runtime)
kv(w, "Tier", fmt.Sprintf("%d", ws.Tier))
kv(w, "ParentID", ws.ParentID)
kv(w, "WorkspaceDir", ws.WorkspaceDir)
kv(w, "CreatedAt", ws.CreatedAt)
if ws.Canvas != nil {
kv(w, "Canvas", fmt.Sprintf("(%.0f, %.0f)", ws.Canvas.X, ws.Canvas.Y))
}
return w.Flush()
}
// ===========================================================================
// mol workspace delete
// ===========================================================================
var workspaceDeleteCmd = &cobra.Command{
Use: "delete <workspace-id>",
Short: "Delete a workspace (irreversible)",
Args: cobra.ExactArgs(1),
RunE: runWorkspaceDelete,
}
func runWorkspaceDelete(cmd *cobra.Command, args []string) error {
cl := client.New(apiURL)
if err := cl.DeleteWorkspace(args[0]); err != nil {
return fmt.Errorf("workspace delete: %w", err)
}
fmt.Printf("Workspace %q deleted.\n", args[0])
return nil
}
// ===========================================================================
// mol workspace restart
// ===========================================================================
var workspaceRestartCmd = &cobra.Command{
Use: "restart <workspace-id>",
Short: "Restart a workspace",
Args: cobra.ExactArgs(1),
RunE: runWorkspaceRestart,
}
func runWorkspaceRestart(cmd *cobra.Command, args []string) error {
cl := client.New(apiURL)
if err := cl.RestartWorkspace(args[0]); err != nil {
return fmt.Errorf("workspace restart: %w", err)
}
fmt.Printf("Restart triggered for workspace %q.\n", args[0])
return nil
}
// ===========================================================================
// mol workspace audit
// ===========================================================================
var workspaceAuditCmd = &cobra.Command{
Use: "audit",
Short: "Full workspace + agent audit report",
RunE: runWorkspaceAudit,
}
func runWorkspaceAudit(cmd *cobra.Command, _ []string) error {
cl := client.New(apiURL)
workspaces, agents, err := cl.AuditWorkspaces()
if err != nil {
return fmt.Errorf("workspace audit: %w", err)
}
type auditReport struct {
Workspaces int `json:"workspaces"`
Agents int `json:"agents"`
ByStatus map[string]int `json:"by_status"`
Items []client.Workspace `json:"workspaces_list"`
AgentList []client.Agent `json:"agents_list"`
}
byStatus := map[string]int{}
for _, ws := range workspaces {
byStatus[ws.Status]++
}
report := auditReport{
Workspaces: len(workspaces),
Agents: len(agents),
ByStatus: byStatus,
Items: workspaces,
AgentList: agents,
}
if outputFormat == "json" {
return printJSON(report)
}
if outputFormat == "yaml" {
return printYAML(report)
}
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
fmt.Fprintln(w, "WORKSPACES\t")
fmt.Fprintln(w, "ID\tNAME\tSTATUS\tROLE\tRUNTIME")
for _, ws := range workspaces {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
ws.ID, ws.Name, ws.Status, ws.Role, ws.Runtime)
}
fmt.Fprintln(w)
fmt.Fprintln(w, "AGENTS\t")
fmt.Fprintln(w, "ID\tNAME\tWORKSPACE\tSTATUS\tMODEL")
for _, a := range agents {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
a.ID, a.Name, a.WorkspaceID, a.Status, a.Model)
}
return w.Flush()
}
// ===========================================================================
// mol workspace delegate
// ===========================================================================
var workspaceDelegateCmd = &cobra.Command{
Use: "delegate <workspace-id> <target-workspace-id> <task>",
Short: "Delegate a task to another workspace (non-blocking)",
Args: cobra.ExactArgs(3),
RunE: runWorkspaceDelegate,
}
func runWorkspaceDelegate(cmd *cobra.Command, args []string) error {
workspaceID, targetID, task := args[0], args[1], args[2]
cl := client.New(apiURL)
type delReq struct {
TargetID string `json:"target_id"`
Task string `json:"task"`
}
type delResp struct {
DelegationID string `json:"delegation_id,omitempty"`
Status string `json:"status,omitempty"`
}
encoded, _ := json.Marshal(delReq{TargetID: targetID, Task: task})
body, err := runHTTP("POST", cl.BaseURL+"/workspaces/"+workspaceID+"/delegate", encoded)
if err != nil {
return fmt.Errorf("workspace delegate: %w", err)
}
var resp delResp
if err := json.Unmarshal(body, &resp); err != nil {
return fmt.Errorf("workspace delegate: parse response: %w", err)
}
if resp.DelegationID != "" {
fmt.Printf("Delegation queued: %s (status: %s)\n", resp.DelegationID, resp.Status)
} else {
fmt.Printf("Delegation sent to %q.\n", targetID)
}
return nil
}