feat: implement full CLI command tree
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>
This commit is contained in:
parent
25601ba758
commit
3eabe3c780
@ -1,507 +1,16 @@
|
||||
// molecule-cli — Molecule AI platform CLI
|
||||
//
|
||||
// Entry point. Wires cobra root command and runs it.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbletea"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/Molecule-AI/molecule-cli/internal/cmd"
|
||||
)
|
||||
|
||||
// Global flags
|
||||
var (
|
||||
flagOutput string
|
||||
flagVerbose bool
|
||||
flagConfig string
|
||||
)
|
||||
|
||||
// Output formats
|
||||
type OutputFormat int
|
||||
|
||||
const (
|
||||
FormatText OutputFormat = iota
|
||||
FormatJSON
|
||||
FormatYAML
|
||||
)
|
||||
|
||||
var formatMap = map[string]OutputFormat{
|
||||
"text": FormatText,
|
||||
"json": FormatJSON,
|
||||
"yaml": FormatYAML,
|
||||
}
|
||||
|
||||
// writeOutput writes the result in the requested format
|
||||
func writeOutput(w io.Writer, data any, format string) {
|
||||
switch formatMap[format] {
|
||||
case FormatJSON:
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
enc.Encode(data)
|
||||
case FormatYAML:
|
||||
// Simple YAML-like output for maps
|
||||
if m, ok := data.(map[string]any); ok {
|
||||
for k, v := range m {
|
||||
fmt.Fprintf(w, "%s: %v\n", k, v)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(w, "%v\n", data)
|
||||
}
|
||||
default:
|
||||
dumpText(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
func dumpText(w io.Writer, data any) {
|
||||
switch v := data.(type) {
|
||||
case string:
|
||||
fmt.Fprintln(w, v)
|
||||
case []string:
|
||||
for _, s := range v {
|
||||
fmt.Fprintln(w, s)
|
||||
}
|
||||
case map[string]any:
|
||||
for k, val := range v {
|
||||
fmt.Fprintf(w, "%-20s %v\n", k+":", val)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(w, "%v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
func errorExit(format string, args ...any) {
|
||||
fmt.Fprintf(os.Stderr, format, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// PlatformClient is a minimal client for the Molecule AI platform API
|
||||
type PlatformClient struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
}
|
||||
|
||||
func newPlatformClient() *PlatformClient {
|
||||
baseURL := os.Getenv("MOLECULE_API_URL")
|
||||
if baseURL == "" {
|
||||
baseURL = "https://api.moleculesai.app"
|
||||
}
|
||||
return &PlatformClient{BaseURL: baseURL, Token: os.Getenv("MOLECULE_API_TOKEN")}
|
||||
}
|
||||
|
||||
// Workspace represents a Molecule AI workspace
|
||||
type Workspace struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Tier int `json:"tier"`
|
||||
ParentID string `json:"parent_id,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
}
|
||||
|
||||
// Agent represents a Molecule AI agent
|
||||
type Agent struct {
|
||||
ID string `json:"id"`
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// ─── Workspace Commands ───────────────────────────────────────────────────────
|
||||
|
||||
var workspaceCmd = &cobra.Command{
|
||||
Use: "workspace",
|
||||
Short: "Manage workspaces",
|
||||
}
|
||||
|
||||
var workspaceCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new workspace",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
tier, _ := cmd.Flags().GetInt("tier")
|
||||
template, _ := cmd.Flags().GetString("template")
|
||||
|
||||
if name == "" {
|
||||
errorExit("workspace create: --name is required")
|
||||
}
|
||||
|
||||
// Placeholder — actual POST to platform API
|
||||
ws := Workspace{
|
||||
ID: uuid.New().String(),
|
||||
Name: name,
|
||||
Status: "provisioning",
|
||||
Tier: tier,
|
||||
Created: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
writeOutput(os.Stdout, ws, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
var workspaceListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all workspaces",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Placeholder — actual GET /workspaces
|
||||
workspaces := []Workspace{
|
||||
{ID: "placeholder-workspace-id", Name: "example-workspace", Status: "online", Tier: 2},
|
||||
}
|
||||
writeOutput(os.Stdout, workspaces, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
var workspaceInspectCmd = &cobra.Command{
|
||||
Use: "inspect",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Inspect a workspace by ID",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := args[0]
|
||||
ws := Workspace{ID: id, Name: "placeholder", Status: "online", Tier: 2}
|
||||
writeOutput(os.Stdout, ws, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
var workspaceDeleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Delete a workspace",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := args[0]
|
||||
fmt.Fprintf(os.Stderr, "workspace delete: deleted %s\n", id)
|
||||
},
|
||||
}
|
||||
|
||||
var workspaceRestartCmd = &cobra.Command{
|
||||
Use: "restart",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Restart a workspace",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := args[0]
|
||||
fmt.Fprintf(os.Stderr, "workspace restart: restarting %s\n", id)
|
||||
},
|
||||
}
|
||||
|
||||
var workspaceDelegateCmd = &cobra.Command{
|
||||
Use: "delegate",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Delegate a task to a workspace",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := args[0]
|
||||
task, _ := cmd.Flags().GetString("task")
|
||||
async, _ := cmd.Flags().GetBool("async")
|
||||
|
||||
if task == "" {
|
||||
errorExit("workspace delegate: --task is required")
|
||||
}
|
||||
|
||||
if async {
|
||||
taskID := uuid.New().String()
|
||||
fmt.Fprintf(os.Stderr, "workspace delegate: async task %s dispatched to %s\n", taskID, id)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "workspace delegate: sync task dispatched to %s\n", id)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var workspaceAuditCmd = &cobra.Command{
|
||||
Use: "audit",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Audit a workspace's configuration",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := args[0]
|
||||
report := map[string]any{
|
||||
"workspace_id": id,
|
||||
"audit_passed": true,
|
||||
"issues_found": 0,
|
||||
"recommendations": []string{},
|
||||
}
|
||||
writeOutput(os.Stdout, report, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
// ─── Agent Commands ─────────────────────────────────────────────────────────
|
||||
|
||||
var agentCmd = &cobra.Command{
|
||||
Use: "agent",
|
||||
Short: "Manage agents",
|
||||
}
|
||||
|
||||
var agentListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "List agents in a workspace",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
workspaceID := args[0]
|
||||
agents := []Agent{
|
||||
{ID: "placeholder-agent-id", WorkspaceID: workspaceID, Name: "example-agent", Status: "online", Role: "assistant"},
|
||||
}
|
||||
writeOutput(os.Stdout, agents, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
var agentInspectCmd = &cobra.Command{
|
||||
Use: "inspect",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Inspect an agent by ID",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := args[0]
|
||||
agent := Agent{ID: id, Status: "online", Role: "assistant"}
|
||||
writeOutput(os.Stdout, agent, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
var agentSendCmd = &cobra.Command{
|
||||
Use: "send",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Send a message to an agent",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := args[0]
|
||||
msg, _ := cmd.Flags().GetString("message")
|
||||
if msg == "" {
|
||||
errorExit("agent send: --message is required")
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "agent send: message sent to %s\n", id)
|
||||
},
|
||||
}
|
||||
|
||||
var agentPeersCmd = &cobra.Command{
|
||||
Use: "peers",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "List peers for an agent",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := args[0]
|
||||
peers := []string{}
|
||||
writeOutput(os.Stdout, peers, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
// ─── Platform Commands ───────────────────────────────────────────────────────
|
||||
|
||||
var platformCmd = &cobra.Command{
|
||||
Use: "platform",
|
||||
Short: "Interact with the platform",
|
||||
}
|
||||
|
||||
var platformAuditCmd = &cobra.Command{
|
||||
Use: "audit",
|
||||
Short: "Audit platform configuration",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
url, _ := cmd.Flags().GetString("url")
|
||||
token, _ := cmd.Flags().GetString("token")
|
||||
if url == "" {
|
||||
url = os.Getenv("MOLECULE_API_URL")
|
||||
}
|
||||
|
||||
report := map[string]any{
|
||||
"platform_url": url,
|
||||
"api_key_configured": token != "" || os.Getenv("MOLECULE_API_TOKEN") != "",
|
||||
"audit_passed": true,
|
||||
}
|
||||
writeOutput(os.Stdout, report, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
var platformHealthCmd = &cobra.Command{
|
||||
Use: "health",
|
||||
Short: "Check platform health",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
health := map[string]any{
|
||||
"status": "ok",
|
||||
"api_url": os.Getenv("MOLECULE_API_URL"),
|
||||
"checked_at": time.Now().Format(time.RFC3339),
|
||||
}
|
||||
writeOutput(os.Stdout, health, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
// ─── Config Commands ─────────────────────────────────────────────────────────
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Manage CLI configuration",
|
||||
}
|
||||
|
||||
var configListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Show current configuration",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cfg := map[string]string{
|
||||
"MOLECULE_API_URL": os.Getenv("MOLECULE_API_URL"),
|
||||
"MOLECULE_RUNTIME_URL": os.Getenv("MOLECULE_RUNTIME_URL"),
|
||||
"MOLECULE_API_TOKEN": maskToken(os.Getenv("MOLECULE_API_TOKEN")),
|
||||
"config_file": viper.ConfigFileUsed(),
|
||||
}
|
||||
writeOutput(os.Stdout, cfg, flagOutput)
|
||||
},
|
||||
}
|
||||
|
||||
var configGetCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Get a specific config value",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
key := args[0]
|
||||
val := viper.GetString(key)
|
||||
fmt.Fprintln(os.Stdout, val)
|
||||
},
|
||||
}
|
||||
|
||||
var configSetCmd = &cobra.Command{
|
||||
Use: "set",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Short: "Set a config value",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
key, val := args[0], args[1]
|
||||
viper.Set(key, val)
|
||||
fmt.Fprintf(os.Stderr, "config set: %s = %s\n", key, val)
|
||||
},
|
||||
}
|
||||
|
||||
var configInitCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Bootstrap ~/.config/molecule/cli.yaml",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
errorExit("config init: cannot determine home directory")
|
||||
}
|
||||
dir := home + "/.config/molecule"
|
||||
os.MkdirAll(dir, 0755)
|
||||
path := dir + "/cli.yaml"
|
||||
content := `# Molecule CLI configuration
|
||||
# Copy this file to ~/.config/molecule/cli.yaml
|
||||
|
||||
api_url: "https://api.moleculesai.app"
|
||||
runtime_url: ""
|
||||
|
||||
# Token is read from MOLECULE_API_TOKEN env var — do not store here.
|
||||
`
|
||||
os.WriteFile(path, []byte(content), 0644)
|
||||
fmt.Fprintf(os.Stderr, "config init: created %s\n", path)
|
||||
},
|
||||
}
|
||||
|
||||
var configViewCmd = &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "Print config file path and current values",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Fprintf(os.Stderr, "config file: %s\n", viper.ConfigFileUsed())
|
||||
fmt.Fprintf(os.Stderr, "effective config:\n")
|
||||
for _, key := range viper.AllKeys() {
|
||||
fmt.Fprintf(os.Stderr, " %s = %v\n", key, viper.Get(key))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// ─── Root Command ────────────────────────────────────────────────────────────
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "molecule",
|
||||
Short: "Molecule AI CLI — agent platform management tool",
|
||||
Long: `Molecule AI CLI for managing agents, workspaces, and deployments.
|
||||
|
||||
Environment variables:
|
||||
MOLECULE_API_URL Control plane API base URL (default: https://api.moleculesai.app)
|
||||
MOLECULE_RUNTIME_URL Workspace runtime URL
|
||||
MOLECULE_API_TOKEN API authentication token
|
||||
|
||||
Examples:
|
||||
molecule workspace list
|
||||
molecule agent inspect <agent-id>
|
||||
molecule config init
|
||||
`,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&flagOutput, "output", "o", "text", "Output format: text, json, yaml")
|
||||
rootCmd.PersistentFlags().BoolVarP(&flagVerbose, "verbose", "v", false, "Enable verbose output")
|
||||
rootCmd.PersistentFlags().StringVar(&flagConfig, "config", "", "Path to config file")
|
||||
|
||||
// Workspace subcommands
|
||||
workspaceCreateCmd.Flags().String("name", "", "Workspace name")
|
||||
workspaceCreateCmd.Flags().Int("tier", 2, "Workspace tier (1-4)")
|
||||
workspaceCreateCmd.Flags().String("template", "default", "Template ID")
|
||||
workspaceCreateCmd.MarkFlagRequired("name")
|
||||
|
||||
workspaceDelegateCmd.Flags().String("task", "", "Task prompt")
|
||||
workspaceDelegateCmd.Flags().Bool("async", false, "Fire and forget (async)")
|
||||
|
||||
// Agent subcommands
|
||||
agentSendCmd.Flags().String("message", "", "Message to send")
|
||||
agentSendCmd.MarkFlagRequired("message")
|
||||
|
||||
// Platform subcommands
|
||||
platformAuditCmd.Flags().String("url", "", "Platform URL override")
|
||||
platformAuditCmd.Flags().String("token", "", "API token override")
|
||||
|
||||
// Wire up workspace tree
|
||||
workspaceCmd.AddCommand(workspaceCreateCmd, workspaceListCmd, workspaceInspectCmd,
|
||||
workspaceDeleteCmd, workspaceRestartCmd, workspaceDelegateCmd, workspaceAuditCmd)
|
||||
rootCmd.AddCommand(workspaceCmd)
|
||||
|
||||
// Wire up agent tree
|
||||
agentCmd.AddCommand(agentListCmd, agentInspectCmd, agentSendCmd, agentPeersCmd)
|
||||
rootCmd.AddCommand(agentCmd)
|
||||
|
||||
// Wire up platform tree
|
||||
platformCmd.AddCommand(platformAuditCmd, platformHealthCmd)
|
||||
rootCmd.AddCommand(platformCmd)
|
||||
|
||||
// Wire up config tree
|
||||
configCmd.AddCommand(configListCmd, configGetCmd, configSetCmd, configInitCmd, configViewCmd)
|
||||
rootCmd.AddCommand(configCmd)
|
||||
}
|
||||
|
||||
func maskToken(token string) string {
|
||||
if len(token) <= 8 {
|
||||
return "***"
|
||||
}
|
||||
return token[:4] + strings.Repeat("*", len(token)-8) + token[len(token)-4:]
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Configure viper for config file support
|
||||
viper.SetConfigName("cli")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("$HOME/.config/molecule")
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// Bind CLI flags to viper
|
||||
viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output"))
|
||||
viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
|
||||
|
||||
// Override flag values from config file
|
||||
if viper.IsSet("output") {
|
||||
flagOutput = viper.GetString("output")
|
||||
}
|
||||
if viper.IsSet("verbose") {
|
||||
flagVerbose = viper.GetBool("verbose")
|
||||
}
|
||||
|
||||
// Set Gin mode based on verbosity
|
||||
if flagVerbose {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
// Execute
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
if strings.Contains(err.Error(), "unknown subcommand") ||
|
||||
strings.Contains(err.Error(), "missing required") ||
|
||||
strings.Contains(err.Error(), "flag") {
|
||||
os.Exit(2)
|
||||
}
|
||||
if err := cmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
95
go.mod
95
go.mod
@ -3,88 +3,25 @@ module github.com/Molecule-AI/molecule-cli
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/gin-contrib/cors v1.7.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
|
||||
github.com/alicebob/miniredis/v2 v2.37.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v28.2.2+incompatible // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/spf13/cobra v1.10.2 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
|
||||
go.opentelemetry.io/otel v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
||||
263
go.sum
263
go.sum
@ -1,252 +1,57 @@
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/alicebob/miniredis/v2 v2.37.0 h1:RheObYW32G1aiJIj81XVt78ZHJpHonHLHW7OLIshq68=
|
||||
github.com/alicebob/miniredis/v2 v2.37.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
||||
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
|
||||
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
276
internal/client/platform.go
Normal file
276
internal/client/platform.go
Normal file
@ -0,0 +1,276 @@
|
||||
// Package client provides a thin HTTP client for the Molecule AI platform API.
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Platform is the root API client.
|
||||
type Platform struct {
|
||||
BaseURL string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// New returns a Platform client configured with baseURL.
|
||||
func New(baseURL string) *Platform {
|
||||
return &Platform{
|
||||
BaseURL: baseURL,
|
||||
client: &http.Client{Timeout: 30 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
// Workspace represents a Molecule AI workspace.
|
||||
type Workspace struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Role string `json:"role,omitempty"`
|
||||
ParentID string `json:"parent_id,omitempty"`
|
||||
Runtime string `json:"runtime,omitempty"`
|
||||
WorkspaceDir string `json:"workspace_dir,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
Tier int `json:"tier,omitempty"`
|
||||
Canvas *Canvas `json:"canvas,omitempty"`
|
||||
}
|
||||
|
||||
// Canvas holds the workspace's position on the canvas.
|
||||
type Canvas struct {
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
|
||||
// Agent represents an agent running in a workspace.
|
||||
type Agent struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
WorkspaceID string `json:"workspace_id,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Runtime string `json:"runtime,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
}
|
||||
|
||||
// CreateWorkspaceRequest mirrors the platform's POST /workspaces body.
|
||||
type CreateWorkspaceRequest struct {
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role,omitempty"`
|
||||
Template string `json:"template,omitempty"`
|
||||
Tier int `json:"tier,omitempty"`
|
||||
ParentID string `json:"parent_id,omitempty"`
|
||||
Runtime string `json:"runtime,omitempty"`
|
||||
WorkspaceDir string `json:"workspace_dir,omitempty"`
|
||||
}
|
||||
|
||||
// PlatformHealth holds the /health endpoint response.
|
||||
type PlatformHealth struct {
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Uptime string `json:"uptime,omitempty"`
|
||||
Database string `json:"database,omitempty"`
|
||||
}
|
||||
|
||||
// ConfigEntry represents a config key-value pair.
|
||||
type ConfigEntry struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// ListWorkspaces returns all workspaces in the org.
|
||||
func (p *Platform) ListWorkspaces() ([]Workspace, error) {
|
||||
var out []Workspace
|
||||
if err := p.getInto("/workspaces", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GetWorkspace returns a single workspace by ID.
|
||||
func (p *Platform) GetWorkspace(id string) (*Workspace, error) {
|
||||
var out Workspace
|
||||
if err := p.getInto(fmt.Sprintf("/workspaces/%s", id), &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// CreateWorkspace creates a new workspace.
|
||||
func (p *Platform) CreateWorkspace(req CreateWorkspaceRequest) (*Workspace, error) {
|
||||
var out Workspace
|
||||
if err := p.postInto("/workspaces", req, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteWorkspace deletes a workspace by ID.
|
||||
func (p *Platform) DeleteWorkspace(id string) error {
|
||||
_, err := p.delete(fmt.Sprintf("/workspaces/%s?confirm=true", id))
|
||||
return err
|
||||
}
|
||||
|
||||
// RestartWorkspace triggers a restart for a workspace.
|
||||
func (p *Platform) RestartWorkspace(id string) error {
|
||||
_, err := p.postEmpty(fmt.Sprintf("/workspaces/%s/restart", id))
|
||||
return err
|
||||
}
|
||||
|
||||
// ListAgents returns all agents across the org.
|
||||
func (p *Platform) ListAgents() ([]Agent, error) {
|
||||
var out []Agent
|
||||
if err := p.getInto("/agents", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ListWorkspaceAgents returns agents for a given workspace.
|
||||
func (p *Platform) ListWorkspaceAgents(workspaceID string) ([]Agent, error) {
|
||||
var out []Agent
|
||||
if err := p.getInto(fmt.Sprintf("/workspaces/%s/agents", workspaceID), &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GetAgent returns a single agent by ID.
|
||||
func (p *Platform) GetAgent(id string) (*Agent, error) {
|
||||
var out Agent
|
||||
if err := p.getInto(fmt.Sprintf("/agents/%s", id), &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// Health returns the platform's /health status.
|
||||
func (p *Platform) Health() (*PlatformHealth, error) {
|
||||
var out PlatformHealth
|
||||
if err := p.getInto("/health", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// AuditWorkspaces returns all workspaces and agents.
|
||||
func (p *Platform) AuditWorkspaces() ([]Workspace, []Agent, error) {
|
||||
ws, err := p.ListWorkspaces()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("audit workspaces: %w", err)
|
||||
}
|
||||
agents, err := p.ListAgents()
|
||||
if err != nil {
|
||||
return ws, nil, fmt.Errorf("audit agents: %w", err)
|
||||
}
|
||||
return ws, agents, nil
|
||||
}
|
||||
|
||||
// GetPeers returns peer workspaces reachable from a workspace.
|
||||
func (p *Platform) GetPeers(workspaceID string) ([]Agent, error) {
|
||||
var out []Agent
|
||||
if err := p.getInto(fmt.Sprintf("/registry/%s/peers", workspaceID), &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GetDelegations returns delegation status for a workspace.
|
||||
func (p *Platform) GetDelegations(workspaceID string) ([]map[string]interface{}, error) {
|
||||
var out []map[string]interface{}
|
||||
if err := p.getInto(fmt.Sprintf("/workspaces/%s/delegations", workspaceID), &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private HTTP helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (p *Platform) getInto(path string, out interface{}) error {
|
||||
url := p.BaseURL + path
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new GET request: %w", err)
|
||||
}
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GET %s: %w", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("GET %s: HTTP %d — %s", url, resp.StatusCode, string(body))
|
||||
}
|
||||
if err := json.Unmarshal(body, out); err != nil {
|
||||
return fmt.Errorf("decode GET %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Platform) postInto(path string, body interface{}, out interface{}) error {
|
||||
encoded, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal POST body: %w", err)
|
||||
}
|
||||
url := p.BaseURL + path
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(encoded))
|
||||
if err != nil {
|
||||
return fmt.Errorf("new POST request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("POST %s: %w", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("POST %s: HTTP %d — %s", url, resp.StatusCode, string(respBody))
|
||||
}
|
||||
if out != nil {
|
||||
if err := json.Unmarshal(respBody, out); err != nil {
|
||||
return fmt.Errorf("decode POST %s response: %w", path, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Platform) delete(path string) ([]byte, error) {
|
||||
url := p.BaseURL + path
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new DELETE request: %w", err)
|
||||
}
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DELETE %s: %w", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("DELETE %s: HTTP %d — %s", url, resp.StatusCode, string(body))
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (p *Platform) postEmpty(path string) ([]byte, error) {
|
||||
url := p.BaseURL + path
|
||||
req, err := http.NewRequest("POST", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new POST request: %w", err)
|
||||
}
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("POST %s: %w", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("POST %s: HTTP %d — %s", url, resp.StatusCode, string(body))
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
175
internal/cmd/agent.go
Normal file
175
internal/cmd/agent.go
Normal file
@ -0,0 +1,175 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agent command group
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
var agentCmd = &cobra.Command{
|
||||
Use: "agent",
|
||||
Short: "Inspect and interact with agents",
|
||||
Long: `List agents, inspect individual agents, send messages, and discover peers.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
agentCmd.AddCommand(
|
||||
agentListCmd, agentInspectCmd, agentSendCmd, agentPeersCmd,
|
||||
)
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol agent list
|
||||
// ===========================================================================
|
||||
var agentListCmd = &cobra.Command{
|
||||
Use: "list [workspace-id]",
|
||||
Short: "List all agents (optionally filtered to one workspace)",
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
RunE: runAgentList,
|
||||
}
|
||||
|
||||
func runAgentList(cmd *cobra.Command, args []string) error {
|
||||
cl := client.New(apiURL)
|
||||
var agents []client.Agent
|
||||
var err error
|
||||
if len(args) == 0 {
|
||||
agents, err = cl.ListAgents()
|
||||
} else {
|
||||
agents, err = cl.ListWorkspaceAgents(args[0])
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("agent list: %w", err)
|
||||
}
|
||||
if outputFormat == "json" || outputFormat == "yaml" {
|
||||
return printJSON(agents)
|
||||
}
|
||||
if len(agents) == 0 {
|
||||
fmt.Println("No agents found.")
|
||||
return nil
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tNAME\tWORKSPACE\tSTATUS\tMODEL\tRUNTIME")
|
||||
for _, a := range agents {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
a.ID, a.Name, a.WorkspaceID, a.Status, a.Model, a.Runtime)
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol agent inspect
|
||||
// ===========================================================================
|
||||
var agentInspectCmd = &cobra.Command{
|
||||
Use: "inspect <agent-id>",
|
||||
Short: "Show full details for an agent",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAgentInspect,
|
||||
}
|
||||
|
||||
func runAgentInspect(cmd *cobra.Command, args []string) error {
|
||||
cl := client.New(apiURL)
|
||||
a, err := cl.GetAgent(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("agent inspect: %w", err)
|
||||
}
|
||||
if outputFormat == "json" || outputFormat == "yaml" {
|
||||
return printJSON(a)
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
||||
kv(w, "ID", a.ID)
|
||||
kv(w, "Name", a.Name)
|
||||
kv(w, "WorkspaceID", a.WorkspaceID)
|
||||
kv(w, "Status", a.Status)
|
||||
kv(w, "Model", a.Model)
|
||||
kv(w, "Runtime", a.Runtime)
|
||||
kv(w, "CreatedAt", a.CreatedAt)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol agent send
|
||||
// ===========================================================================
|
||||
var agentSendCmd = &cobra.Command{
|
||||
Use: "send <agent-id> <message>",
|
||||
Short: "Send a one-shot message to an agent via A2A",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: runAgentSend,
|
||||
}
|
||||
|
||||
func runAgentSend(cmd *cobra.Command, args []string) error {
|
||||
agentID, message := args[0], args[1]
|
||||
cl := client.New(apiURL)
|
||||
|
||||
a, err := cl.GetAgent(agentID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("agent send: %w", err)
|
||||
}
|
||||
wsID := a.WorkspaceID
|
||||
if wsID == "" {
|
||||
return fmt.Errorf("agent send: workspace_id unknown for agent %q", agentID)
|
||||
}
|
||||
|
||||
type a2aReq struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
type a2aResp struct {
|
||||
Result string `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
encoded, _ := json.Marshal(a2aReq{AgentID: agentID, Message: message})
|
||||
body, err := runHTTP("POST", cl.BaseURL+"/workspaces/"+wsID+"/a2a", encoded)
|
||||
if err != nil {
|
||||
return fmt.Errorf("agent send: %w", err)
|
||||
}
|
||||
var resp a2aResp
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return fmt.Errorf("agent send: parse response: %w", err)
|
||||
}
|
||||
if resp.Error != "" {
|
||||
return fmt.Errorf("agent send: platform error: %s", resp.Error)
|
||||
}
|
||||
fmt.Println(resp.Result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol agent peers
|
||||
// ===========================================================================
|
||||
var agentPeersCmd = &cobra.Command{
|
||||
Use: "peers <workspace-id>",
|
||||
Short: "List peer workspaces reachable from a workspace",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAgentPeers,
|
||||
}
|
||||
|
||||
func runAgentPeers(cmd *cobra.Command, args []string) error {
|
||||
cl := client.New(apiURL)
|
||||
peers, err := cl.GetPeers(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("agent peers: %w", err)
|
||||
}
|
||||
if outputFormat == "json" || outputFormat == "yaml" {
|
||||
return printJSON(peers)
|
||||
}
|
||||
if len(peers) == 0 {
|
||||
fmt.Println("No peers found.")
|
||||
return nil
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tNAME\tWORKSPACE\tSTATUS\tMODEL")
|
||||
for _, p := range peers {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
|
||||
p.ID, p.Name, p.WorkspaceID, p.Status, p.Model)
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
175
internal/cmd/config.go
Normal file
175
internal/cmd/config.go
Normal file
@ -0,0 +1,175 @@
|
||||
// Package cmd implements the CLI command tree.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config command group
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "View and manage CLI and workspace configuration",
|
||||
Long: `mol config list — list all config keys (from file + env)
|
||||
mol config get <key> — print a single config value
|
||||
mol config set <key> <value> — write a key to the config file
|
||||
mol config init — scaffold a default mol.yaml in the current directory
|
||||
mol config view — print the current config file with sources annotated`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(
|
||||
configListCmd, configGetCmd, configSetCmd, configInitCmd, configViewCmd,
|
||||
)
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol config list
|
||||
// ===========================================================================
|
||||
var configListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all known config keys and their effective values",
|
||||
RunE: runConfigList,
|
||||
}
|
||||
|
||||
func runConfigList(cmd *cobra.Command, _ []string) error {
|
||||
settings := viper.AllSettings()
|
||||
if len(settings) == 0 {
|
||||
fmt.Println("No config keys set. Use `mol config set <key> <value>` or set env vars.")
|
||||
return nil
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "KEY\tVALUE\tSOURCE")
|
||||
for k, v := range settings {
|
||||
source := "default"
|
||||
if viper.InConfig(k) {
|
||||
source = "file"
|
||||
}
|
||||
if strings.HasPrefix(k, "MOLECULE_") || strings.HasPrefix(k, "MOL_") {
|
||||
source = "env"
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%v\t%s\n", k, v, source)
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol config get
|
||||
// ===========================================================================
|
||||
var configGetCmd = &cobra.Command{
|
||||
Use: "get <key>",
|
||||
Short: "Print the effective value of a config key",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runConfigGet,
|
||||
}
|
||||
|
||||
func runConfigGet(cmd *cobra.Command, args []string) error {
|
||||
if !viper.IsSet(args[0]) {
|
||||
return fmt.Errorf("config get: key %q not set (check env var MOLECULE_%s)", args[0], args[0])
|
||||
}
|
||||
fmt.Println(viper.GetString(args[0]))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol config set
|
||||
// ===========================================================================
|
||||
var configSetCmd = &cobra.Command{
|
||||
Use: "set <key> <value>",
|
||||
Short: "Write a config key to the config file (~/.config/mol.yaml)",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: runConfigSet,
|
||||
}
|
||||
|
||||
func runConfigSet(cmd *cobra.Command, args []string) error {
|
||||
key, value := args[0], args[1]
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
configDir = "."
|
||||
}
|
||||
configFile := filepath.Join(configDir, "mol.yaml")
|
||||
|
||||
v := viper.New()
|
||||
v.SetConfigFile(configFile)
|
||||
_ = v.ReadInConfig() // ignore not-found
|
||||
v.Set(key, value)
|
||||
if err := v.WriteConfig(); err != nil {
|
||||
if err2 := v.SafeWriteConfig(); err2 != nil {
|
||||
return fmt.Errorf("config set: write %s: %w (tried WriteConfig then SafeWriteConfig)", configFile, err)
|
||||
}
|
||||
}
|
||||
fmt.Printf("Set %s=%q in %s\n", key, value, v.ConfigFileUsed())
|
||||
return nil
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol config init
|
||||
// ===========================================================================
|
||||
var configInitCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Scaffold a default mol.yaml in the current directory",
|
||||
RunE: runConfigInit,
|
||||
}
|
||||
|
||||
func runConfigInit(cmd *cobra.Command, _ []string) error {
|
||||
const defaultConfig = `# mol CLI config — https://github.com/Molecule-AI/molecule-cli
|
||||
#
|
||||
# All values can be overridden by environment variables:
|
||||
# MOLECULE_API_URL, MOLECULE_RUNTIME_URL, MOL_OUTPUT, MOL_VERBOSE, etc.
|
||||
|
||||
# Platform API base URL (env: MOLECULE_API_URL)
|
||||
# api_url: http://localhost:8080
|
||||
|
||||
# Output format: table | json | yaml (env: MOL_OUTPUT)
|
||||
# output: table
|
||||
|
||||
# Verbose logging: true | false (env: MOL_VERBOSE)
|
||||
# verbose: false
|
||||
`
|
||||
if _, err := os.Stat("mol.yaml"); err == nil {
|
||||
return fmt.Errorf("config init: mol.yaml already exists (not overwriting)")
|
||||
}
|
||||
if err := os.WriteFile("mol.yaml", []byte(defaultConfig), 0o644); err != nil {
|
||||
return fmt.Errorf("config init: write mol.yaml: %w", err)
|
||||
}
|
||||
fmt.Println("Scaffolded mol.yaml — edit it and run mol --config mol.yaml, or move it to ~/.config/mol.yaml")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol config view
|
||||
// ===========================================================================
|
||||
var configViewCmd = &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "Print the current config file with sources annotated",
|
||||
RunE: runConfigView,
|
||||
}
|
||||
|
||||
func runConfigView(cmd *cobra.Command, _ []string) error {
|
||||
if viper.ConfigFileUsed() == "" {
|
||||
fmt.Println("No config file in use. Set one with --config or mol config init.")
|
||||
fmt.Println("\nActive env vars starting with MOLECULE_ or MOL_:")
|
||||
for _, env := range os.Environ() {
|
||||
if strings.HasPrefix(env, "MOLECULE_") || strings.HasPrefix(env, "MOL_") {
|
||||
fmt.Println(" ", env)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
data, err := os.ReadFile(viper.ConfigFileUsed())
|
||||
if err != nil {
|
||||
return fmt.Errorf("config view: read %s: %w", viper.ConfigFileUsed(), err)
|
||||
}
|
||||
fmt.Printf("# Config file: %s\n\n", viper.ConfigFileUsed())
|
||||
fmt.Print(string(data))
|
||||
return nil
|
||||
}
|
||||
34
internal/cmd/http.go
Normal file
34
internal/cmd/http.go
Normal file
@ -0,0 +1,34 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// runHTTP does a raw HTTP call.
|
||||
func runHTTP(method, url string, body []byte) ([]byte, error) {
|
||||
req, err := http.NewRequest(method, url, strings.NewReader(string(body)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
resp, err := httpClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("HTTP %d — %s", resp.StatusCode, string(b))
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func httpClient() *http.Client {
|
||||
return &http.Client{Timeout: 30 * time.Second}
|
||||
}
|
||||
145
internal/cmd/platform.go
Normal file
145
internal/cmd/platform.go
Normal file
@ -0,0 +1,145 @@
|
||||
// Package cmd implements the CLI command tree.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/Molecule-AI/molecule-cli/internal/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform command group
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
var platformCmd = &cobra.Command{
|
||||
Use: "platform",
|
||||
Short: "Platform-level operations",
|
||||
Long: `Audit the platform, check health, and inspect raw API responses.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
platformCmd.AddCommand(platformAuditCmd, platformHealthCmd)
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol platform audit
|
||||
// ===========================================================================
|
||||
var platformAuditCmd = &cobra.Command{
|
||||
Use: "audit",
|
||||
Short: "Full platform audit: workspaces, agents, delegation summary",
|
||||
RunE: runPlatformAudit,
|
||||
}
|
||||
|
||||
func runPlatformAudit(cmd *cobra.Command, _ []string) error {
|
||||
cl := client.New(apiURL)
|
||||
workspaces, agents, err := cl.AuditWorkspaces()
|
||||
if err != nil {
|
||||
return fmt.Errorf("platform audit: %w", err)
|
||||
}
|
||||
|
||||
delegationsByWS := map[string]int{}
|
||||
for _, ws := range workspaces {
|
||||
dels, err := cl.GetDelegations(ws.ID)
|
||||
if err == nil {
|
||||
delegationsByWS[ws.ID] = len(dels)
|
||||
}
|
||||
}
|
||||
|
||||
type wsRow struct {
|
||||
ID, Name, Status, Role string
|
||||
AgentCount, DelegationCount int
|
||||
}
|
||||
rows := make([]wsRow, 0, len(workspaces))
|
||||
for _, ws := range workspaces {
|
||||
ac := 0
|
||||
for _, a := range agents {
|
||||
if a.WorkspaceID == ws.ID {
|
||||
ac++
|
||||
}
|
||||
}
|
||||
rows = append(rows, wsRow{
|
||||
ID: ws.ID, Name: ws.Name, Status: ws.Status, Role: ws.Role,
|
||||
AgentCount: ac, DelegationCount: delegationsByWS[ws.ID],
|
||||
})
|
||||
}
|
||||
|
||||
if outputFormat == "json" || outputFormat == "yaml" {
|
||||
type audit struct {
|
||||
WorkspaceCount int `json:"workspace_count"`
|
||||
AgentCount int `json:"agent_count"`
|
||||
ByStatus map[string]int `json:"by_status"`
|
||||
DelegationMap map[string]int `json:"delegations_by_workspace"`
|
||||
Rows []wsRow `json:"workspaces"`
|
||||
Agents []client.Agent `json:"agents"`
|
||||
}
|
||||
byStatus := map[string]int{}
|
||||
for _, ws := range workspaces {
|
||||
byStatus[ws.Status]++
|
||||
}
|
||||
return printJSON(audit{
|
||||
WorkspaceCount: len(workspaces),
|
||||
AgentCount: len(agents),
|
||||
ByStatus: byStatus,
|
||||
DelegationMap: delegationsByWS,
|
||||
Rows: rows,
|
||||
Agents: agents,
|
||||
})
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
||||
fmt.Fprintf(w, "=== Platform Audit (%d workspaces, %d agents) ===\n\n",
|
||||
len(workspaces), len(agents))
|
||||
fmt.Fprintln(w, "WORKSPACE\tSTATUS\tROLE\tAGENTS\tDELEGATIONS")
|
||||
for _, r := range rows {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%d\n",
|
||||
r.Name, r.Status, r.Role, r.AgentCount, r.DelegationCount)
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// mol platform health
|
||||
// ===========================================================================
|
||||
var platformHealthCmd = &cobra.Command{
|
||||
Use: "health",
|
||||
Short: "Check platform health and version",
|
||||
RunE: runPlatformHealth,
|
||||
}
|
||||
|
||||
func runPlatformHealth(cmd *cobra.Command, _ []string) error {
|
||||
cl := client.New(apiURL)
|
||||
h, err := cl.Health()
|
||||
if err != nil {
|
||||
// Fall back to raw check if /health 404s on older platforms.
|
||||
body, hErr := platformRawHealth(cl.BaseURL)
|
||||
if hErr != nil {
|
||||
return fmt.Errorf("platform health: %w (and /health fallback also failed: %v)", err, hErr)
|
||||
}
|
||||
fmt.Printf("Platform reachable at %s — raw status: %s\n", cl.BaseURL, string(body))
|
||||
return nil
|
||||
}
|
||||
if outputFormat == "json" || outputFormat == "yaml" {
|
||||
return printJSON(h)
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
||||
kv(w, "Status", h.Status)
|
||||
kv(w, "Version", h.Version)
|
||||
kv(w, "Uptime", h.Uptime)
|
||||
kv(w, "Database", h.Database)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func platformRawHealth(baseURL string) ([]byte, error) {
|
||||
req, _ := http.NewRequest("GET", baseURL+"/health", nil)
|
||||
resp, err := httpClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
126
internal/cmd/root.go
Normal file
126
internal/cmd/root.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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())
|
||||
}
|
||||
289
internal/cmd/workspace.go
Normal file
289
internal/cmd/workspace.go
Normal file
@ -0,0 +1,289 @@
|
||||
// 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" || outputFormat == "yaml" {
|
||||
return printJSON(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" || outputFormat == "yaml" {
|
||||
return printJSON(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" || outputFormat == "yaml" {
|
||||
return printJSON(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" || outputFormat == "yaml" {
|
||||
return printJSON(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)
|
||||
}
|
||||
_ = workspaceID
|
||||
return nil
|
||||
}
|
||||
@ -27,14 +27,15 @@ Format per entry:
|
||||
|
||||
## KI-001 — No entry point yet (`cmd/molecule/main.go` does not exist)
|
||||
|
||||
**File:** `cmd/molecule/main.go`
|
||||
**Status:** Not yet implemented
|
||||
**Severity:** Critical
|
||||
**File:** `cmd/molecule/main.go`
|
||||
**Status:** ✅ Resolved
|
||||
**Resolved in:** `feat/cli-full-command-tree` branch, commit "feat: implement full CLI command tree"
|
||||
|
||||
### Symptom
|
||||
The repo is initialized as a Go module but has no `cmd/molecule/main.go`. Running
|
||||
`go build ./cmd/molecule` or `go run ./cmd/molecule` fails with
|
||||
"package cmd/molecule: cannot find module" or "build failed".
|
||||
`cmd/molecule/main.go` exists and calls `cmd.Execute()`. Root command is wired
|
||||
with global flags (`--verbose`, `--output`, `--config`, `--api-url`). All
|
||||
subcommand groups registered: workspace (7 commands), agent (4 commands),
|
||||
platform (2 commands), config (5 commands). Binary builds to `bin/mol`.
|
||||
|
||||
### Impact
|
||||
The CLI is not runnable. No workspace management, agent inspection, or any other
|
||||
@ -50,9 +51,11 @@ See the stub checklist in `CLAUDE.md` Section 8.
|
||||
|
||||
## KI-002 — No API client; all commands will make raw HTTP calls
|
||||
|
||||
**File:** `cmd/molecule/` (no API client package yet)
|
||||
**Status:** Not yet implemented
|
||||
**Severity:** High
|
||||
**File:** `cmd/molecule/` (no API client package yet)
|
||||
**Status:** ✅ Partially resolved
|
||||
**Resolved in:** `internal/client/platform.go` exists with workspace and agent
|
||||
operations; `runHTTP` helper in `internal/cmd/http.go` used by `agent send` and
|
||||
`workspace delegate`. Remaining: workspace runtime client (dev/proxy mode).
|
||||
|
||||
### Symptom
|
||||
There is no `internal/client/` or `pkg/api/` package. Any subcommand
|
||||
@ -76,9 +79,10 @@ are implemented.
|
||||
|
||||
## KI-003 — `go.sum` may contain entries from non-release toolchains
|
||||
|
||||
**File:** `go.sum`
|
||||
**Status:** Identified
|
||||
**Severity:** Low
|
||||
**File:** `go.sum`
|
||||
**Status:** ✅ Resolved
|
||||
**Resolved in:** `go mod tidy` run on `feat/cli-full-command-tree`; `go.sum` regenerated
|
||||
clean. Dependencies: cobra v1.10.2, viper v1.21.0, their transitive deps.
|
||||
|
||||
### Symptom
|
||||
The `go.sum` file was generated during initial module setup. It may contain
|
||||
@ -102,8 +106,8 @@ resulting `go.sum`. Add `go mod verify` to CI as a lint step. Ensure
|
||||
|
||||
## KI-004 — GoReleaser config may not be aligned with go.mod module path
|
||||
|
||||
**File:** `.github/workflows/release.yml`
|
||||
**Status:** Not verified
|
||||
**File:** `.github/workflows/release.yml`
|
||||
**Status:** ⚠️ Unverified — needs real tag to confirm
|
||||
**Severity:** Medium
|
||||
|
||||
### Symptom
|
||||
|
||||
Loading…
Reference in New Issue
Block a user