Allow running multiple daemon instances against different servers (e.g. production and local dev) simultaneously. Each profile gets isolated config, PID file, log file, health port, and workspaces root. Usage: multica login --profile dev --server-url http://localhost:8080 multica daemon start --profile dev Default profile (no --profile flag) behavior is unchanged. Closes MUL-42
136 lines
3.2 KiB
Go
136 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/multica-ai/multica/server/internal/cli"
|
|
"github.com/multica-ai/multica/server/internal/daemon"
|
|
)
|
|
|
|
var agentCmd = &cobra.Command{
|
|
Use: "agent",
|
|
Short: "Manage agents",
|
|
}
|
|
|
|
var agentListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List agents in the workspace",
|
|
RunE: runAgentList,
|
|
}
|
|
|
|
func init() {
|
|
agentCmd.AddCommand(agentListCmd)
|
|
|
|
agentListCmd.Flags().String("output", "table", "Output format: table or json")
|
|
}
|
|
|
|
// resolveProfile returns the --profile flag value (empty string means default profile).
|
|
func resolveProfile(cmd *cobra.Command) string {
|
|
val, _ := cmd.Flags().GetString("profile")
|
|
return val
|
|
}
|
|
|
|
func newAPIClient(cmd *cobra.Command) (*cli.APIClient, error) {
|
|
serverURL := resolveServerURL(cmd)
|
|
workspaceID := resolveWorkspaceID(cmd)
|
|
token := resolveToken(cmd)
|
|
|
|
if serverURL == "" {
|
|
return nil, fmt.Errorf("server URL not set: use --server-url flag, MULTICA_SERVER_URL env, or 'multica config set server_url <url>'")
|
|
}
|
|
|
|
client := cli.NewAPIClient(serverURL, workspaceID, token)
|
|
// When running inside a daemon task, attribute actions to the agent.
|
|
if agentID := os.Getenv("MULTICA_AGENT_ID"); agentID != "" {
|
|
client.AgentID = agentID
|
|
}
|
|
if taskID := os.Getenv("MULTICA_TASK_ID"); taskID != "" {
|
|
client.TaskID = taskID
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
func resolveServerURL(cmd *cobra.Command) string {
|
|
val := cli.FlagOrEnv(cmd, "server-url", "MULTICA_SERVER_URL", "")
|
|
if val != "" {
|
|
return normalizeAPIBaseURL(val)
|
|
}
|
|
profile := resolveProfile(cmd)
|
|
cfg, err := cli.LoadCLIConfigForProfile(profile)
|
|
if err != nil {
|
|
return "http://localhost:8080"
|
|
}
|
|
if cfg.ServerURL != "" {
|
|
return normalizeAPIBaseURL(cfg.ServerURL)
|
|
}
|
|
return "http://localhost:8080"
|
|
}
|
|
|
|
func normalizeAPIBaseURL(raw string) string {
|
|
normalized, err := daemon.NormalizeServerBaseURL(raw)
|
|
if err == nil {
|
|
return normalized
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func resolveWorkspaceID(cmd *cobra.Command) string {
|
|
val := cli.FlagOrEnv(cmd, "workspace-id", "MULTICA_WORKSPACE_ID", "")
|
|
if val != "" {
|
|
return val
|
|
}
|
|
profile := resolveProfile(cmd)
|
|
cfg, _ := cli.LoadCLIConfigForProfile(profile)
|
|
return cfg.WorkspaceID
|
|
}
|
|
|
|
func runAgentList(cmd *cobra.Command, _ []string) error {
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
var agents []map[string]any
|
|
path := "/api/agents"
|
|
if client.WorkspaceID != "" {
|
|
path += "?" + url.Values{"workspace_id": {client.WorkspaceID}}.Encode()
|
|
}
|
|
if err := client.GetJSON(ctx, path, &agents); err != nil {
|
|
return fmt.Errorf("list agents: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, agents)
|
|
}
|
|
|
|
headers := []string{"ID", "NAME", "STATUS", "RUNTIME"}
|
|
rows := make([][]string, 0, len(agents))
|
|
for _, a := range agents {
|
|
rows = append(rows, []string{
|
|
strVal(a, "id"),
|
|
strVal(a, "name"),
|
|
strVal(a, "status"),
|
|
strVal(a, "runtime_mode"),
|
|
})
|
|
}
|
|
cli.PrintTable(os.Stdout, headers, rows)
|
|
return nil
|
|
}
|
|
|
|
func strVal(m map[string]any, key string) string {
|
|
v, ok := m[key]
|
|
if !ok || v == nil {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("%v", v)
|
|
}
|