From 4a62b98c9a6130297842e643da0ae13fea0eb1fb Mon Sep 17 00:00:00 2001 From: yushen Date: Fri, 27 Mar 2026 13:54:40 +0800 Subject: [PATCH] feat(cli): add workspace get and members commands Expose workspace details (including context field) and member listing via the CLI so agents can dynamically query workspace context instead of relying on static snapshots. Co-Authored-By: Claude Opus 4.6 (1M context) --- server/cmd/multica/cmd_workspace.go | 115 ++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/server/cmd/multica/cmd_workspace.go b/server/cmd/multica/cmd_workspace.go index 9e74d61b..dd921852 100644 --- a/server/cmd/multica/cmd_workspace.go +++ b/server/cmd/multica/cmd_workspace.go @@ -6,6 +6,7 @@ import ( "os" "text/tabwriter" "time" + "unicode/utf8" "github.com/spf13/cobra" @@ -23,6 +24,20 @@ var workspaceListCmd = &cobra.Command{ RunE: runWorkspaceList, } +var workspaceGetCmd = &cobra.Command{ + Use: "get [workspace-id]", + Short: "Get workspace details", + Args: cobra.MaximumNArgs(1), + RunE: runWorkspaceGet, +} + +var workspaceMembersCmd = &cobra.Command{ + Use: "members [workspace-id]", + Short: "List workspace members", + Args: cobra.MaximumNArgs(1), + RunE: runWorkspaceMembers, +} + var workspaceWatchCmd = &cobra.Command{ Use: "watch ", Short: "Add a workspace to the daemon watch list", @@ -39,8 +54,13 @@ var workspaceUnwatchCmd = &cobra.Command{ func init() { workspaceCmd.AddCommand(workspaceListCmd) + workspaceCmd.AddCommand(workspaceGetCmd) + workspaceCmd.AddCommand(workspaceMembersCmd) workspaceCmd.AddCommand(workspaceWatchCmd) workspaceCmd.AddCommand(workspaceUnwatchCmd) + + workspaceGetCmd.Flags().String("output", "json", "Output format: table or json") + workspaceMembersCmd.Flags().String("output", "table", "Output format: table or json") } func runWorkspaceList(cmd *cobra.Command, _ []string) error { @@ -86,6 +106,101 @@ func runWorkspaceList(cmd *cobra.Command, _ []string) error { return w.Flush() } +func workspaceIDFromArgs(cmd *cobra.Command, args []string) string { + if len(args) > 0 { + return args[0] + } + return resolveWorkspaceID(cmd) +} + +func runWorkspaceGet(cmd *cobra.Command, args []string) error { + wsID := workspaceIDFromArgs(cmd, args) + if wsID == "" { + return fmt.Errorf("workspace ID is required: pass as argument or set MULTICA_WORKSPACE_ID") + } + + serverURL := resolveServerURL(cmd) + token := resolveToken() + if token == "" { + return fmt.Errorf("not authenticated: run 'multica auth login' first") + } + + client := cli.NewAPIClient(serverURL, "", token) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + var ws map[string]any + if err := client.GetJSON(ctx, "/api/workspaces/"+wsID, &ws); err != nil { + return fmt.Errorf("get workspace: %w", err) + } + + output, _ := cmd.Flags().GetString("output") + if output == "table" { + desc := strVal(ws, "description") + if utf8.RuneCountInString(desc) > 60 { + runes := []rune(desc) + desc = string(runes[:57]) + "..." + } + wsContext := strVal(ws, "context") + if utf8.RuneCountInString(wsContext) > 60 { + runes := []rune(wsContext) + wsContext = string(runes[:57]) + "..." + } + headers := []string{"ID", "NAME", "SLUG", "DESCRIPTION", "CONTEXT"} + rows := [][]string{{ + strVal(ws, "id"), + strVal(ws, "name"), + strVal(ws, "slug"), + desc, + wsContext, + }} + cli.PrintTable(os.Stdout, headers, rows) + return nil + } + + return cli.PrintJSON(os.Stdout, ws) +} + +func runWorkspaceMembers(cmd *cobra.Command, args []string) error { + wsID := workspaceIDFromArgs(cmd, args) + if wsID == "" { + return fmt.Errorf("workspace ID is required: pass as argument or set MULTICA_WORKSPACE_ID") + } + + serverURL := resolveServerURL(cmd) + token := resolveToken() + if token == "" { + return fmt.Errorf("not authenticated: run 'multica auth login' first") + } + + client := cli.NewAPIClient(serverURL, "", token) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + var members []map[string]any + if err := client.GetJSON(ctx, "/api/workspaces/"+wsID+"/members", &members); err != nil { + return fmt.Errorf("list members: %w", err) + } + + output, _ := cmd.Flags().GetString("output") + if output == "json" { + return cli.PrintJSON(os.Stdout, members) + } + + headers := []string{"USER ID", "NAME", "EMAIL", "ROLE"} + rows := make([][]string, 0, len(members)) + for _, m := range members { + rows = append(rows, []string{ + strVal(m, "user_id"), + strVal(m, "name"), + strVal(m, "email"), + strVal(m, "role"), + }) + } + cli.PrintTable(os.Stdout, headers, rows) + return nil +} + func runWatch(cmd *cobra.Command, args []string) error { workspaceID := args[0]