Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions cmd/picoclaw/internal/configcmd/agent_add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package configcmd

import (
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
"github.com/sipeed/picoclaw/pkg/config"
)

func newAgentAddCommand() *cobra.Command {
var (
name string
model string
workspace string
defaultAgent bool
)

cmd := &cobra.Command{
Use: "add <id>",
Short: "Add an agent to agents.list",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
return runAgentAdd(args[0], name, model, workspace, defaultAgent)
},
}

cmd.Flags().StringVar(&name, "name", "", "Display name")
cmd.Flags().StringVar(&model, "model", "", "Model name (from model_list)")
cmd.Flags().StringVar(&workspace, "workspace", "", "Workspace path")
cmd.Flags().BoolVar(&defaultAgent, "default", false, "Set as default agent")

return cmd
}

func runAgentAdd(id, name, model, workspace string, defaultAgent bool) error {
cfg, err := internal.LoadConfig()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}

configPath := internal.GetConfigPath()
if _, err := os.Stat(configPath); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("config not found; run: picoclaw onboard")
}
return err
}

// Interactive prompt when TTY and missing fields
if IsTTY() {
if name == "" {
name, _ = Prompt("Name (display): ")
}
if model == "" {
model, _ = Prompt("Model (model_name from model_list): ")
}
if workspace == "" {
workspace, _ = Prompt("Workspace: ")
}
}

entry := config.AgentConfig{
ID: id,
Default: defaultAgent,
Name: name,
Workspace: workspace,
}
if model != "" {
entry.Model = &config.AgentModelConfig{Primary: model}
}

cfg.Agents.List = append(cfg.Agents.List, entry)

if err := config.SaveConfig(configPath, cfg); err != nil {
return fmt.Errorf("saving config: %w", err)
}

fmt.Printf("Added agent %q to agents.list.\n", id)
return nil
}
18 changes: 18 additions & 0 deletions cmd/picoclaw/internal/configcmd/agent_defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package configcmd

import (
"github.com/spf13/cobra"
)

func newAgentDefaultsCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "defaults",
Short: "Get or set agents.defaults",
RunE: func(cmd *cobra.Command, _ []string) error {
return cmd.Help()
},
}
cmd.AddCommand(newAgentDefaultsGetCommand())
cmd.AddCommand(newAgentDefaultsSetCommand())
return cmd
}
104 changes: 104 additions & 0 deletions cmd/picoclaw/internal/configcmd/agent_defaults_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package configcmd

import (
"fmt"
"os"
"strings"

"github.com/spf13/cobra"

"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
"github.com/sipeed/picoclaw/pkg/config"
)

// agentDefaultsKeys in display order for get (all).
var agentDefaultsKeys = []string{
"workspace", "restrict_to_workspace", "provider", "model_name", "model",
"model_fallbacks", "image_model", "image_model_fallbacks",
"max_tokens", "temperature", "max_tool_iterations",
}

var agentDefaultsKeySet map[string]bool

func init() {
agentDefaultsKeySet = make(map[string]bool, len(agentDefaultsKeys))
for _, k := range agentDefaultsKeys {
agentDefaultsKeySet[k] = true
}
}

func newAgentDefaultsGetCommand() *cobra.Command {
return &cobra.Command{
Use: "get [key]",
Short: "Get agents.defaults (all fields or one key)",
Args: cobra.MaximumNArgs(1),
RunE: runAgentDefaultsGet,
}
}

func runAgentDefaultsGet(_ *cobra.Command, args []string) error {
cfg, err := internal.LoadConfig()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}

configPath := internal.GetConfigPath()
if _, err := os.Stat(configPath); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("config not found; run: picoclaw onboard")
}
return err
}

d := &cfg.Agents.Defaults

if len(args) == 0 {
for _, key := range agentDefaultsKeys {
value := agentDefaultsGetValue(d, key)
fmt.Printf("%s: %s\n", key, value)
}
return nil
}

key := args[0]
if !agentDefaultsKeySet[key] {
return fmt.Errorf("invalid key %q; allowed: %s", key, strings.Join(agentDefaultsKeys, ", "))
}
fmt.Println(agentDefaultsGetValue(d, key))
return nil
}

func agentDefaultsGetValue(d *config.AgentDefaults, key string) string {
switch key {
case "workspace":
return d.Workspace
case "restrict_to_workspace":
if d.RestrictToWorkspace {
return "true"
}
return "false"
case "provider":
return d.Provider
case "model_name":
return d.ModelName
case "model":
return d.Model
case "model_fallbacks":
return strings.Join(d.ModelFallbacks, ",")
case "image_model":
return d.ImageModel
case "image_model_fallbacks":
return strings.Join(d.ImageModelFallbacks, ",")
case "max_tokens":
return fmt.Sprintf("%d", d.MaxTokens)
case "temperature":
if d.Temperature == nil {
return ""
}
return fmt.Sprintf("%g", *d.Temperature)
case "max_tool_iterations":
return fmt.Sprintf("%d", d.MaxToolIterations)
default:
return ""
}
}
120 changes: 120 additions & 0 deletions cmd/picoclaw/internal/configcmd/agent_defaults_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package configcmd

import (
"fmt"
"os"
"strconv"
"strings"

"github.com/spf13/cobra"

"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
"github.com/sipeed/picoclaw/pkg/config"
)

func newAgentDefaultsSetCommand() *cobra.Command {
return &cobra.Command{
Use: "set <key> <value>",
Short: "Set a single field in agents.defaults",
Args: cobra.ExactArgs(2),
RunE: runAgentDefaultsSet,
}
}

func runAgentDefaultsSet(_ *cobra.Command, args []string) error {
cfg, err := internal.LoadConfig()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}

configPath := internal.GetConfigPath()
if _, err := os.Stat(configPath); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("config not found; run: picoclaw onboard")
}
return err
}

key, value := args[0], args[1]
if !agentDefaultsKeySet[key] {
return fmt.Errorf("invalid key %q; allowed: %s", key, strings.Join(agentDefaultsKeys, ", "))
}

d := &cfg.Agents.Defaults

switch key {
case "workspace":
d.Workspace = value
case "restrict_to_workspace":
b, err := parseBool(value)
if err != nil {
return fmt.Errorf("restrict_to_workspace: %w", err)
}
d.RestrictToWorkspace = b
case "provider":
d.Provider = value
case "model_name":
d.ModelName = value
case "model":
d.Model = value
case "model_fallbacks":
if value == "" {
d.ModelFallbacks = nil
} else {
d.ModelFallbacks = strings.Split(value, ",")
for i := range d.ModelFallbacks {
d.ModelFallbacks[i] = strings.TrimSpace(d.ModelFallbacks[i])
}
}
case "image_model":
d.ImageModel = value
case "image_model_fallbacks":
if value == "" {
d.ImageModelFallbacks = nil
} else {
d.ImageModelFallbacks = strings.Split(value, ",")
for i := range d.ImageModelFallbacks {
d.ImageModelFallbacks[i] = strings.TrimSpace(d.ImageModelFallbacks[i])
}
}
case "max_tokens":
n, err := strconv.Atoi(value)
if err != nil {
return fmt.Errorf("max_tokens: %w", err)
}
d.MaxTokens = n
case "temperature":
if value == "" {
d.Temperature = nil
} else {
f, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("temperature: %w", err)
}
d.Temperature = &f
}
case "max_tool_iterations":
n, err := strconv.Atoi(value)
if err != nil {
return fmt.Errorf("max_tool_iterations: %w", err)
}
d.MaxToolIterations = n
}

if err := config.SaveConfig(configPath, cfg); err != nil {
return fmt.Errorf("saving config: %w", err)
}
fmt.Printf("Set agents.defaults %s.\n", key)
return nil
}

func parseBool(s string) (bool, error) {
switch strings.ToLower(s) {
case "true", "1", "yes":
return true, nil
case "false", "0", "no":
return false, nil
default:
return false, fmt.Errorf("expected true/false, got %q", s)
}
}
69 changes: 69 additions & 0 deletions cmd/picoclaw/internal/configcmd/agent_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package configcmd

import (
"fmt"
"os"
"strings"

"github.com/spf13/cobra"

"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
)

func newAgentListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all agents in agents.list",
Args: cobra.NoArgs,
RunE: runAgentList,
}
}

func runAgentList(_ *cobra.Command, _ []string) error {
cfg, err := internal.LoadConfig()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}

configPath := internal.GetConfigPath()
if _, err := os.Stat(configPath); err != nil {
if os.IsNotExist(err) {
fmt.Println("No config file found. Run: picoclaw onboard")
return nil
}
return err
}

if len(cfg.Agents.List) == 0 {
fmt.Println("agents.list is empty.")
return nil
}

fmt.Printf("%-20s %-25s %-20s %s\n", "ID", "NAME", "MODEL", "WORKSPACE")
fmt.Println(strings.Repeat("-", 85))

for _, a := range cfg.Agents.List {
model := ""
if a.Model != nil && a.Model.Primary != "" {
model = a.Model.Primary
}
if len(model) > 18 {
model = model[:15] + "..."
}
name := a.Name
if len(name) > 23 {
name = name[:20] + "..."
}
id := a.ID
if len(id) > 18 {
id = id[:15] + "..."
}
ws := a.Workspace
if len(ws) > 35 {
ws = ws[:32] + "..."
}
fmt.Printf("%-20s %-25s %-20s %s\n", id, name, model, ws)
}

return nil
}
Loading