feat: add Jules API integration as a CLI command\n\n- Added JulesConfig to pkg/config/tools.go to store API Key configuration.\n- Created a new CLI command package cmd/picoclaw/internal/jules to interact with Jules API (manage sessions, list activities, approve plans, etc.).\n- Registered the command into the main.go entrypoint.\n- Wrote comprehensive unit tests mocking HTTP requests to verify command functionality.
Co-authored-by: hobbyistlabs-coder <267281733+hobbyistlabs-coder@users.noreply.github.com>
This commit is contained in:
parent
a41d5d1bcc
commit
f61a148e7c
9 changed files with 1379 additions and 0 deletions
214
cmd/picoclaw/internal/jules/jules.go
Normal file
214
cmd/picoclaw/internal/jules/jules.go
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
package jules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"jane/cmd/picoclaw/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var julesBaseURL = "https://jules.googleapis.com/v1alpha"
|
||||||
|
|
||||||
|
func getAPIKey() (string, error) {
|
||||||
|
if key := os.Getenv("JULES_API_KEY"); key != "" {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
cfg, err := internal.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to load config: %w", err)
|
||||||
|
}
|
||||||
|
if cfg.Tools.Jules.APIKey != "" {
|
||||||
|
return cfg.Tools.Jules.APIKey, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("JULES_API_KEY environment variable or config value not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRequest(method, url string, body []byte) error {
|
||||||
|
apiKey, err := getAPIKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("x-goog-api-key", apiKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error making request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return fmt.Errorf("API error (%d): %s", resp.StatusCode, string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(respBody) > 0 {
|
||||||
|
var prettyJSON bytes.Buffer
|
||||||
|
if err := json.Indent(&prettyJSON, respBody, "", " "); err == nil {
|
||||||
|
fmt.Println(prettyJSON.String())
|
||||||
|
} else {
|
||||||
|
fmt.Println(string(respBody))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Success")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJulesCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "jules",
|
||||||
|
Short: "Manage Jules sessions and activities",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(newSessionCmd())
|
||||||
|
cmd.AddCommand(newActivityCmd())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSessionCmd() *cobra.Command {
|
||||||
|
sessionCmd := &cobra.Command{
|
||||||
|
Use: "session",
|
||||||
|
Short: "Manage Jules sessions",
|
||||||
|
}
|
||||||
|
|
||||||
|
var prompt string
|
||||||
|
var title string
|
||||||
|
var source string
|
||||||
|
var branch string
|
||||||
|
|
||||||
|
createCmd := &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Short: "Create a new session",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if prompt == "" || source == "" {
|
||||||
|
return fmt.Errorf("--prompt and --source are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"prompt": prompt,
|
||||||
|
"sourceContext": map[string]interface{}{
|
||||||
|
"source": source,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if title != "" {
|
||||||
|
payload["title"] = title
|
||||||
|
}
|
||||||
|
if branch != "" {
|
||||||
|
payload["sourceContext"].(map[string]interface{})["githubRepoContext"] = map[string]interface{}{
|
||||||
|
"startingBranch": branch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
return doRequest("POST", julesBaseURL+"/sessions", body)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createCmd.Flags().StringVar(&prompt, "prompt", "", "The prompt for the session")
|
||||||
|
createCmd.Flags().StringVar(&title, "title", "", "The title of the session")
|
||||||
|
createCmd.Flags().StringVar(&source, "source", "", "The source repository (e.g., sources/github-owner-repo)")
|
||||||
|
createCmd.Flags().StringVar(&branch, "branch", "", "The starting branch")
|
||||||
|
|
||||||
|
listCmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List sessions",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("GET", julesBaseURL+"/sessions", nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
getCmd := &cobra.Command{
|
||||||
|
Use: "get [sessionId]",
|
||||||
|
Short: "Get a session by ID",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("GET", julesBaseURL+"/sessions/"+args[0], nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCmd := &cobra.Command{
|
||||||
|
Use: "delete [sessionId]",
|
||||||
|
Short: "Delete a session by ID",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("DELETE", julesBaseURL+"/sessions/"+args[0], nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var message string
|
||||||
|
messageCmd := &cobra.Command{
|
||||||
|
Use: "message [sessionId]",
|
||||||
|
Short: "Send a message to a session",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if message == "" {
|
||||||
|
return fmt.Errorf("--message is required")
|
||||||
|
}
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"prompt": message,
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
return doRequest("POST", julesBaseURL+"/sessions/"+args[0]+":sendMessage", body)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
messageCmd.Flags().StringVar(&message, "message", "", "The message to send")
|
||||||
|
|
||||||
|
approveCmd := &cobra.Command{
|
||||||
|
Use: "approve [sessionId]",
|
||||||
|
Short: "Approve a session plan",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("POST", julesBaseURL+"/sessions/"+args[0]+":approvePlan", []byte("{}"))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCmd.AddCommand(createCmd, listCmd, getCmd, deleteCmd, messageCmd, approveCmd)
|
||||||
|
return sessionCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func newActivityCmd() *cobra.Command {
|
||||||
|
activityCmd := &cobra.Command{
|
||||||
|
Use: "activity",
|
||||||
|
Short: "Manage Jules activities",
|
||||||
|
}
|
||||||
|
|
||||||
|
listCmd := &cobra.Command{
|
||||||
|
Use: "list [sessionId]",
|
||||||
|
Short: "List activities for a session",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("GET", julesBaseURL+"/sessions/"+args[0]+"/activities", nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
getCmd := &cobra.Command{
|
||||||
|
Use: "get [sessionId] [activityId]",
|
||||||
|
Short: "Get an activity by ID",
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("GET", julesBaseURL+"/sessions/"+args[0]+"/activities/"+args[1], nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
activityCmd.AddCommand(listCmd, getCmd)
|
||||||
|
return activityCmd
|
||||||
|
}
|
||||||
214
cmd/picoclaw/internal/jules/jules.go.orig
Normal file
214
cmd/picoclaw/internal/jules/jules.go.orig
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
package jules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"jane/cmd/picoclaw/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const julesBaseURL = "https://jules.googleapis.com/v1alpha"
|
||||||
|
|
||||||
|
func getAPIKey() (string, error) {
|
||||||
|
if key := os.Getenv("JULES_API_KEY"); key != "" {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
cfg, err := internal.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to load config: %w", err)
|
||||||
|
}
|
||||||
|
if cfg.Tools.Jules.APIKey != "" {
|
||||||
|
return cfg.Tools.Jules.APIKey, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("JULES_API_KEY environment variable or config value not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRequest(method, url string, body []byte) error {
|
||||||
|
apiKey, err := getAPIKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("x-goog-api-key", apiKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error making request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return fmt.Errorf("API error (%d): %s", resp.StatusCode, string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(respBody) > 0 {
|
||||||
|
var prettyJSON bytes.Buffer
|
||||||
|
if err := json.Indent(&prettyJSON, respBody, "", " "); err == nil {
|
||||||
|
fmt.Println(prettyJSON.String())
|
||||||
|
} else {
|
||||||
|
fmt.Println(string(respBody))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Success")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJulesCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "jules",
|
||||||
|
Short: "Manage Jules sessions and activities",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(newSessionCmd())
|
||||||
|
cmd.AddCommand(newActivityCmd())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSessionCmd() *cobra.Command {
|
||||||
|
sessionCmd := &cobra.Command{
|
||||||
|
Use: "session",
|
||||||
|
Short: "Manage Jules sessions",
|
||||||
|
}
|
||||||
|
|
||||||
|
var prompt string
|
||||||
|
var title string
|
||||||
|
var source string
|
||||||
|
var branch string
|
||||||
|
|
||||||
|
createCmd := &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Short: "Create a new session",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if prompt == "" || source == "" {
|
||||||
|
return fmt.Errorf("--prompt and --source are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"prompt": prompt,
|
||||||
|
"sourceContext": map[string]interface{}{
|
||||||
|
"source": source,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if title != "" {
|
||||||
|
payload["title"] = title
|
||||||
|
}
|
||||||
|
if branch != "" {
|
||||||
|
payload["sourceContext"].(map[string]interface{})["githubRepoContext"] = map[string]interface{}{
|
||||||
|
"startingBranch": branch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
return doRequest("POST", julesBaseURL+"/sessions", body)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createCmd.Flags().StringVar(&prompt, "prompt", "", "The prompt for the session")
|
||||||
|
createCmd.Flags().StringVar(&title, "title", "", "The title of the session")
|
||||||
|
createCmd.Flags().StringVar(&source, "source", "", "The source repository (e.g., sources/github-owner-repo)")
|
||||||
|
createCmd.Flags().StringVar(&branch, "branch", "", "The starting branch")
|
||||||
|
|
||||||
|
listCmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List sessions",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("GET", julesBaseURL+"/sessions", nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
getCmd := &cobra.Command{
|
||||||
|
Use: "get [sessionId]",
|
||||||
|
Short: "Get a session by ID",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("GET", julesBaseURL+"/sessions/"+args[0], nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCmd := &cobra.Command{
|
||||||
|
Use: "delete [sessionId]",
|
||||||
|
Short: "Delete a session by ID",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("DELETE", julesBaseURL+"/sessions/"+args[0], nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var message string
|
||||||
|
messageCmd := &cobra.Command{
|
||||||
|
Use: "message [sessionId]",
|
||||||
|
Short: "Send a message to a session",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if message == "" {
|
||||||
|
return fmt.Errorf("--message is required")
|
||||||
|
}
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"prompt": message,
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
return doRequest("POST", julesBaseURL+"/sessions/"+args[0]+":sendMessage", body)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
messageCmd.Flags().StringVar(&message, "message", "", "The message to send")
|
||||||
|
|
||||||
|
approveCmd := &cobra.Command{
|
||||||
|
Use: "approve [sessionId]",
|
||||||
|
Short: "Approve a session plan",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("POST", julesBaseURL+"/sessions/"+args[0]+":approvePlan", []byte("{}"))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCmd.AddCommand(createCmd, listCmd, getCmd, deleteCmd, messageCmd, approveCmd)
|
||||||
|
return sessionCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func newActivityCmd() *cobra.Command {
|
||||||
|
activityCmd := &cobra.Command{
|
||||||
|
Use: "activity",
|
||||||
|
Short: "Manage Jules activities",
|
||||||
|
}
|
||||||
|
|
||||||
|
listCmd := &cobra.Command{
|
||||||
|
Use: "list [sessionId]",
|
||||||
|
Short: "List activities for a session",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("GET", julesBaseURL+"/sessions/"+args[0]+"/activities", nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
getCmd := &cobra.Command{
|
||||||
|
Use: "get [sessionId] [activityId]",
|
||||||
|
Short: "Get an activity by ID",
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return doRequest("GET", julesBaseURL+"/sessions/"+args[0]+"/activities/"+args[1], nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
activityCmd.AddCommand(listCmd, getCmd)
|
||||||
|
return activityCmd
|
||||||
|
}
|
||||||
192
cmd/picoclaw/internal/jules/jules_test.go
Normal file
192
cmd/picoclaw/internal/jules/jules_test.go
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
package jules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJulesCommands(t *testing.T) {
|
||||||
|
os.Setenv("JULES_API_KEY", "test-api-key")
|
||||||
|
defer os.Unsetenv("JULES_API_KEY")
|
||||||
|
|
||||||
|
var lastReq *http.Request
|
||||||
|
var lastBody []byte
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
lastReq = r
|
||||||
|
var err error
|
||||||
|
lastBody, err = io.ReadAll(r.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"status": "ok"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
originalBaseURL := julesBaseURL
|
||||||
|
julesBaseURL = ts.URL
|
||||||
|
defer func() { julesBaseURL = originalBaseURL }()
|
||||||
|
|
||||||
|
t.Run("session create", func(t *testing.T) {
|
||||||
|
cmd := NewJulesCommand()
|
||||||
|
cmd.SetArgs([]string{"session", "create", "--prompt", "test prompt", "--source", "sources/test", "--title", "test title", "--branch", "main"})
|
||||||
|
|
||||||
|
// Capture stdout
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
r, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
err := cmd.Execute()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, r)
|
||||||
|
|
||||||
|
assert.Equal(t, "POST", lastReq.Method)
|
||||||
|
assert.Equal(t, "/sessions", lastReq.URL.Path)
|
||||||
|
assert.Equal(t, "test-api-key", lastReq.Header.Get("x-goog-api-key"))
|
||||||
|
|
||||||
|
expectedBody := `{"prompt":"test prompt","sourceContext":{"githubRepoContext":{"startingBranch":"main"},"source":"sources/test"},"title":"test title"}`
|
||||||
|
// unmarshal and marshal again to compare json ignoring key order
|
||||||
|
assert.JSONEq(t, expectedBody, string(lastBody))
|
||||||
|
assert.Contains(t, buf.String(), `"status": "ok"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("session list", func(t *testing.T) {
|
||||||
|
cmd := NewJulesCommand()
|
||||||
|
cmd.SetArgs([]string{"session", "list"})
|
||||||
|
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
_, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
err := cmd.Execute()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
|
||||||
|
assert.Equal(t, "GET", lastReq.Method)
|
||||||
|
assert.Equal(t, "/sessions", lastReq.URL.Path)
|
||||||
|
assert.Equal(t, "test-api-key", lastReq.Header.Get("x-goog-api-key"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("session get", func(t *testing.T) {
|
||||||
|
cmd := NewJulesCommand()
|
||||||
|
cmd.SetArgs([]string{"session", "get", "123"})
|
||||||
|
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
_, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
err := cmd.Execute()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
|
||||||
|
assert.Equal(t, "GET", lastReq.Method)
|
||||||
|
assert.Equal(t, "/sessions/123", lastReq.URL.Path)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("session delete", func(t *testing.T) {
|
||||||
|
cmd := NewJulesCommand()
|
||||||
|
cmd.SetArgs([]string{"session", "delete", "123"})
|
||||||
|
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
_, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
err := cmd.Execute()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
|
||||||
|
assert.Equal(t, "DELETE", lastReq.Method)
|
||||||
|
assert.Equal(t, "/sessions/123", lastReq.URL.Path)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("session message", func(t *testing.T) {
|
||||||
|
cmd := NewJulesCommand()
|
||||||
|
cmd.SetArgs([]string{"session", "message", "123", "--message", "hello jules"})
|
||||||
|
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
_, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
err := cmd.Execute()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
|
||||||
|
assert.Equal(t, "POST", lastReq.Method)
|
||||||
|
assert.Equal(t, "/sessions/123:sendMessage", lastReq.URL.Path)
|
||||||
|
assert.JSONEq(t, `{"prompt":"hello jules"}`, string(lastBody))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("session approve", func(t *testing.T) {
|
||||||
|
cmd := NewJulesCommand()
|
||||||
|
cmd.SetArgs([]string{"session", "approve", "123"})
|
||||||
|
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
_, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
err := cmd.Execute()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
|
||||||
|
assert.Equal(t, "POST", lastReq.Method)
|
||||||
|
assert.Equal(t, "/sessions/123:approvePlan", lastReq.URL.Path)
|
||||||
|
assert.JSONEq(t, `{}`, string(lastBody))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("activity list", func(t *testing.T) {
|
||||||
|
cmd := NewJulesCommand()
|
||||||
|
cmd.SetArgs([]string{"activity", "list", "123"})
|
||||||
|
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
_, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
err := cmd.Execute()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
|
||||||
|
assert.Equal(t, "GET", lastReq.Method)
|
||||||
|
assert.Equal(t, "/sessions/123/activities", lastReq.URL.Path)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("activity get", func(t *testing.T) {
|
||||||
|
cmd := NewJulesCommand()
|
||||||
|
cmd.SetArgs([]string{"activity", "get", "123", "act1"})
|
||||||
|
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
_, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
err := cmd.Execute()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
|
||||||
|
assert.Equal(t, "GET", lastReq.Method)
|
||||||
|
assert.Equal(t, "/sessions/123/activities/act1", lastReq.URL.Path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"jane/cmd/picoclaw/internal/auth"
|
"jane/cmd/picoclaw/internal/auth"
|
||||||
"jane/cmd/picoclaw/internal/cron"
|
"jane/cmd/picoclaw/internal/cron"
|
||||||
"jane/cmd/picoclaw/internal/gateway"
|
"jane/cmd/picoclaw/internal/gateway"
|
||||||
|
"jane/cmd/picoclaw/internal/jules"
|
||||||
"jane/cmd/picoclaw/internal/migrate"
|
"jane/cmd/picoclaw/internal/migrate"
|
||||||
"jane/cmd/picoclaw/internal/onboard"
|
"jane/cmd/picoclaw/internal/onboard"
|
||||||
"jane/cmd/picoclaw/internal/skills"
|
"jane/cmd/picoclaw/internal/skills"
|
||||||
|
|
@ -41,6 +42,7 @@ func NewPicoclawCommand() *cobra.Command {
|
||||||
gateway.NewGatewayCommand(),
|
gateway.NewGatewayCommand(),
|
||||||
status.NewStatusCommand(),
|
status.NewStatusCommand(),
|
||||||
cron.NewCronCommand(),
|
cron.NewCronCommand(),
|
||||||
|
jules.NewJulesCommand(),
|
||||||
migrate.NewMigrateCommand(),
|
migrate.NewMigrateCommand(),
|
||||||
skills.NewSkillsCommand(),
|
skills.NewSkillsCommand(),
|
||||||
version.NewVersionCommand(),
|
version.NewVersionCommand(),
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ func TestNewPicoclawCommand(t *testing.T) {
|
||||||
"auth",
|
"auth",
|
||||||
"cron",
|
"cron",
|
||||||
"gateway",
|
"gateway",
|
||||||
|
"jules",
|
||||||
"migrate",
|
"migrate",
|
||||||
"onboard",
|
"onboard",
|
||||||
"skills",
|
"skills",
|
||||||
|
|
|
||||||
|
|
@ -404,6 +404,12 @@ func DefaultConfig() *Config {
|
||||||
},
|
},
|
||||||
ExecTimeoutMinutes: 5,
|
ExecTimeoutMinutes: 5,
|
||||||
},
|
},
|
||||||
|
Jules: JulesConfig{
|
||||||
|
ToolConfig: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
Exec: ExecConfig{
|
Exec: ExecConfig{
|
||||||
ToolConfig: ToolConfig{
|
ToolConfig: ToolConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
|
|
||||||
506
pkg/config/defaults.go.orig
Normal file
506
pkg/config/defaults.go.orig
Normal file
|
|
@ -0,0 +1,506 @@
|
||||||
|
// PicoClaw - Ultra-lightweight personal AI agent
|
||||||
|
// License: MIT
|
||||||
|
//
|
||||||
|
// Copyright (c) 2026 PicoClaw contributors
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultConfig returns the default configuration for PicoClaw.
|
||||||
|
func DefaultConfig() *Config {
|
||||||
|
// Determine the base path for the workspace.
|
||||||
|
// Priority: $PICOCLAW_HOME > ~/.picoclaw
|
||||||
|
var homePath string
|
||||||
|
if picoclawHome := os.Getenv("PICOCLAW_HOME"); picoclawHome != "" {
|
||||||
|
homePath = picoclawHome
|
||||||
|
} else {
|
||||||
|
userHome, _ := os.UserHomeDir()
|
||||||
|
homePath = filepath.Join(userHome, ".picoclaw")
|
||||||
|
}
|
||||||
|
workspacePath := filepath.Join(homePath, "workspace")
|
||||||
|
|
||||||
|
return &Config{
|
||||||
|
Agents: AgentsConfig{
|
||||||
|
Defaults: AgentDefaults{
|
||||||
|
Workspace: workspacePath,
|
||||||
|
RestrictToWorkspace: true,
|
||||||
|
Provider: "",
|
||||||
|
Model: "",
|
||||||
|
MaxTokens: 32768,
|
||||||
|
Temperature: nil, // nil means use provider default
|
||||||
|
MaxToolIterations: 50,
|
||||||
|
SummarizeMessageThreshold: 20,
|
||||||
|
SummarizeTokenPercent: 75,
|
||||||
|
},
|
||||||
|
List: []AgentConfig{
|
||||||
|
{
|
||||||
|
ID: "the-clinician",
|
||||||
|
Name: "Medical Persona",
|
||||||
|
Workspace: filepath.Join(homePath, "Obsidian_Vault", "Patients"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bindings: []AgentBinding{},
|
||||||
|
Session: SessionConfig{
|
||||||
|
DMScope: "per-channel-peer",
|
||||||
|
},
|
||||||
|
Channels: ChannelsConfig{
|
||||||
|
WhatsApp: WhatsAppConfig{
|
||||||
|
Enabled: false,
|
||||||
|
BridgeURL: "ws://localhost:3001",
|
||||||
|
UseNative: false,
|
||||||
|
SessionStorePath: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
Telegram: TelegramConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Token: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
Typing: TypingConfig{Enabled: true},
|
||||||
|
Placeholder: PlaceholderConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Text: "Thinking... 💭",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Discord: DiscordConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Token: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
MentionOnly: false,
|
||||||
|
},
|
||||||
|
MaixCam: MaixCamConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Host: "0.0.0.0",
|
||||||
|
Port: 18790,
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
QQ: QQConfig{
|
||||||
|
Enabled: false,
|
||||||
|
AppID: "",
|
||||||
|
AppSecret: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
MaxMessageLength: 2000,
|
||||||
|
},
|
||||||
|
DingTalk: DingTalkConfig{
|
||||||
|
Enabled: false,
|
||||||
|
ClientID: "",
|
||||||
|
ClientSecret: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
Slack: SlackConfig{
|
||||||
|
Enabled: false,
|
||||||
|
BotToken: "",
|
||||||
|
AppToken: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
Matrix: MatrixConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Homeserver: "https://matrix.org",
|
||||||
|
UserID: "",
|
||||||
|
AccessToken: "",
|
||||||
|
DeviceID: "",
|
||||||
|
JoinOnInvite: true,
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
GroupTrigger: GroupTriggerConfig{
|
||||||
|
MentionOnly: true,
|
||||||
|
},
|
||||||
|
Placeholder: PlaceholderConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Text: "Thinking... 💭",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LINE: LINEConfig{
|
||||||
|
Enabled: false,
|
||||||
|
ChannelSecret: "",
|
||||||
|
ChannelAccessToken: "",
|
||||||
|
WebhookHost: "0.0.0.0",
|
||||||
|
WebhookPort: 18791,
|
||||||
|
WebhookPath: "/webhook/line",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
GroupTrigger: GroupTriggerConfig{MentionOnly: true},
|
||||||
|
},
|
||||||
|
OneBot: OneBotConfig{
|
||||||
|
Enabled: false,
|
||||||
|
WSUrl: "ws://127.0.0.1:3001",
|
||||||
|
AccessToken: "",
|
||||||
|
ReconnectInterval: 5,
|
||||||
|
GroupTriggerPrefix: []string{},
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
Pico: PicoConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Token: "",
|
||||||
|
PingInterval: 30,
|
||||||
|
ReadTimeout: 60,
|
||||||
|
WriteTimeout: 10,
|
||||||
|
MaxConnections: 100,
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Providers: ProvidersConfig{
|
||||||
|
OpenAI: OpenAIProviderConfig{WebSearch: true},
|
||||||
|
},
|
||||||
|
ModelList: []ModelConfig{
|
||||||
|
// ============================================
|
||||||
|
// Add your API key to the model you want to use
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Zhipu AI (智谱) - https://open.bigmodel.cn/usercenter/apikeys
|
||||||
|
{
|
||||||
|
ModelName: "glm-4.7",
|
||||||
|
Model: "zhipu/glm-4.7",
|
||||||
|
APIBase: "https://open.bigmodel.cn/api/paas/v4",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// OpenAI - https://platform.openai.com/api-keys
|
||||||
|
{
|
||||||
|
ModelName: "gpt-5.4",
|
||||||
|
Model: "openai/gpt-5.4",
|
||||||
|
APIBase: "https://api.openai.com/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Anthropic Claude - https://console.anthropic.com/settings/keys
|
||||||
|
{
|
||||||
|
ModelName: "claude-sonnet-4.6",
|
||||||
|
Model: "anthropic/claude-sonnet-4.6",
|
||||||
|
APIBase: "https://api.anthropic.com/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// DeepSeek - https://platform.deepseek.com/
|
||||||
|
{
|
||||||
|
ModelName: "deepseek-chat",
|
||||||
|
Model: "deepseek/deepseek-chat",
|
||||||
|
APIBase: "https://api.deepseek.com/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Google Gemini - https://ai.google.dev/
|
||||||
|
{
|
||||||
|
ModelName: "gemini-2.0-flash",
|
||||||
|
Model: "gemini/gemini-2.0-flash-exp",
|
||||||
|
APIBase: "https://generativelanguage.googleapis.com/v1beta",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Qwen (通义千问) - https://dashscope.console.aliyun.com/apiKey
|
||||||
|
{
|
||||||
|
ModelName: "qwen-plus",
|
||||||
|
Model: "qwen/qwen-plus",
|
||||||
|
APIBase: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Moonshot (月之暗面) - https://platform.moonshot.cn/console/api-keys
|
||||||
|
{
|
||||||
|
ModelName: "moonshot-v1-8k",
|
||||||
|
Model: "moonshot/moonshot-v1-8k",
|
||||||
|
APIBase: "https://api.moonshot.cn/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Groq - https://console.groq.com/keys
|
||||||
|
{
|
||||||
|
ModelName: "llama-3.3-70b",
|
||||||
|
Model: "groq/llama-3.3-70b-versatile",
|
||||||
|
APIBase: "https://api.groq.com/openai/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// OpenRouter (100+ models) - https://openrouter.ai/keys
|
||||||
|
{
|
||||||
|
ModelName: "openrouter-auto",
|
||||||
|
Model: "openrouter/auto",
|
||||||
|
APIBase: "https://openrouter.ai/api/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ModelName: "openrouter-gpt-5.4",
|
||||||
|
Model: "openrouter/openai/gpt-5.4",
|
||||||
|
APIBase: "https://openrouter.ai/api/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// NVIDIA - https://build.nvidia.com/
|
||||||
|
{
|
||||||
|
ModelName: "nemotron-4-340b",
|
||||||
|
Model: "nvidia/nemotron-4-340b-instruct",
|
||||||
|
APIBase: "https://integrate.api.nvidia.com/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Cerebras - https://inference.cerebras.ai/
|
||||||
|
{
|
||||||
|
ModelName: "cerebras-llama-3.3-70b",
|
||||||
|
Model: "cerebras/llama-3.3-70b",
|
||||||
|
APIBase: "https://api.cerebras.ai/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Vivgrid - https://vivgrid.com
|
||||||
|
{
|
||||||
|
ModelName: "vivgrid-auto",
|
||||||
|
Model: "vivgrid/auto",
|
||||||
|
APIBase: "https://api.vivgrid.com/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Volcengine (火山引擎) - https://console.volcengine.com/ark
|
||||||
|
{
|
||||||
|
ModelName: "ark-code-latest",
|
||||||
|
Model: "volcengine/ark-code-latest",
|
||||||
|
APIBase: "https://ark.cn-beijing.volces.com/api/v3",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ModelName: "doubao-pro",
|
||||||
|
Model: "volcengine/doubao-pro-32k",
|
||||||
|
APIBase: "https://ark.cn-beijing.volces.com/api/v3",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// ShengsuanYun (神算云)
|
||||||
|
{
|
||||||
|
ModelName: "deepseek-v3",
|
||||||
|
Model: "shengsuanyun/deepseek-v3",
|
||||||
|
APIBase: "https://api.shengsuanyun.com/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Antigravity (Google Cloud Code Assist) - OAuth only
|
||||||
|
{
|
||||||
|
ModelName: "gemini-flash",
|
||||||
|
Model: "antigravity/gemini-3-flash",
|
||||||
|
AuthMethod: "oauth",
|
||||||
|
},
|
||||||
|
|
||||||
|
// GitHub Copilot - https://github.com/settings/tokens
|
||||||
|
{
|
||||||
|
ModelName: "copilot-gpt-5.4",
|
||||||
|
Model: "github-copilot/gpt-5.4",
|
||||||
|
APIBase: "http://localhost:4321",
|
||||||
|
AuthMethod: "oauth",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Ollama (local) - https://ollama.com
|
||||||
|
{
|
||||||
|
ModelName: "llama3",
|
||||||
|
Model: "ollama/llama3",
|
||||||
|
APIBase: "http://localhost:11434/v1",
|
||||||
|
APIKey: "ollama",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mistral AI - https://console.mistral.ai/api-keys
|
||||||
|
{
|
||||||
|
ModelName: "mistral-small",
|
||||||
|
Model: "mistral/mistral-small-latest",
|
||||||
|
APIBase: "https://api.mistral.ai/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Avian - https://avian.io
|
||||||
|
{
|
||||||
|
ModelName: "deepseek-v3.2",
|
||||||
|
Model: "avian/deepseek/deepseek-v3.2",
|
||||||
|
APIBase: "https://api.avian.io/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ModelName: "kimi-k2.5",
|
||||||
|
Model: "avian/moonshotai/kimi-k2.5",
|
||||||
|
APIBase: "https://api.avian.io/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Minimax - https://api.minimaxi.com/
|
||||||
|
{
|
||||||
|
ModelName: "MiniMax-M2.5",
|
||||||
|
Model: "minimax/MiniMax-M2.5",
|
||||||
|
APIBase: "https://api.minimaxi.com/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// LongCat - https://longcat.chat/platform
|
||||||
|
{
|
||||||
|
ModelName: "LongCat-Flash-Thinking",
|
||||||
|
Model: "longcat/LongCat-Flash-Thinking",
|
||||||
|
APIBase: "https://api.longcat.chat/openai",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// VLLM (local) - http://localhost:8000
|
||||||
|
{
|
||||||
|
ModelName: "local-model",
|
||||||
|
Model: "vllm/custom-model",
|
||||||
|
APIBase: "http://localhost:8000/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Logger: LoggerConfig{
|
||||||
|
TimeFormat: "15:04:05",
|
||||||
|
},
|
||||||
|
Gateway: GatewayConfig{
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 18790,
|
||||||
|
},
|
||||||
|
Tools: ToolsConfig{
|
||||||
|
MediaCleanup: MediaCleanupConfig{
|
||||||
|
ToolConfig: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
MaxAge: 30,
|
||||||
|
Interval: 5,
|
||||||
|
},
|
||||||
|
Web: WebToolsConfig{
|
||||||
|
ToolConfig: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Proxy: "",
|
||||||
|
FetchLimitBytes: 10 * 1024 * 1024, // 10MB by default
|
||||||
|
Brave: BraveConfig{
|
||||||
|
Enabled: false,
|
||||||
|
APIKey: "",
|
||||||
|
APIKeys: nil,
|
||||||
|
MaxResults: 5,
|
||||||
|
},
|
||||||
|
Tavily: TavilyConfig{
|
||||||
|
Enabled: false,
|
||||||
|
APIKey: "",
|
||||||
|
APIKeys: nil,
|
||||||
|
MaxResults: 5,
|
||||||
|
},
|
||||||
|
DuckDuckGo: DuckDuckGoConfig{
|
||||||
|
Enabled: true,
|
||||||
|
MaxResults: 5,
|
||||||
|
},
|
||||||
|
Perplexity: PerplexityConfig{
|
||||||
|
Enabled: false,
|
||||||
|
APIKey: "",
|
||||||
|
APIKeys: nil,
|
||||||
|
MaxResults: 5,
|
||||||
|
},
|
||||||
|
SearXNG: SearXNGConfig{
|
||||||
|
Enabled: false,
|
||||||
|
BaseURL: "",
|
||||||
|
MaxResults: 5,
|
||||||
|
},
|
||||||
|
GLMSearch: GLMSearchConfig{
|
||||||
|
Enabled: false,
|
||||||
|
APIKey: "",
|
||||||
|
BaseURL: "https://open.bigmodel.cn/api/paas/v4/web_search",
|
||||||
|
SearchEngine: "search_std",
|
||||||
|
MaxResults: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cron: CronToolsConfig{
|
||||||
|
ToolConfig: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
ExecTimeoutMinutes: 5,
|
||||||
|
},
|
||||||
|
Exec: ExecConfig{
|
||||||
|
ToolConfig: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
EnableDenyPatterns: true,
|
||||||
|
AllowRemote: true,
|
||||||
|
TimeoutSeconds: 60,
|
||||||
|
},
|
||||||
|
Skills: SkillsToolsConfig{
|
||||||
|
ToolConfig: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Registries: SkillsRegistriesConfig{
|
||||||
|
ClawHub: ClawHubRegistryConfig{
|
||||||
|
Enabled: true,
|
||||||
|
BaseURL: "https://clawhub.ai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxConcurrentSearches: 2,
|
||||||
|
SearchCache: SearchCacheConfig{
|
||||||
|
MaxSize: 50,
|
||||||
|
TTLSeconds: 300,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SendFile: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
MCP: MCPConfig{
|
||||||
|
ToolConfig: ToolConfig{
|
||||||
|
Enabled: false,
|
||||||
|
},
|
||||||
|
Discovery: ToolDiscoveryConfig{
|
||||||
|
Enabled: false,
|
||||||
|
TTL: 5,
|
||||||
|
MaxSearchResults: 5,
|
||||||
|
UseBM25: true,
|
||||||
|
UseRegex: false,
|
||||||
|
},
|
||||||
|
Servers: map[string]MCPServerConfig{},
|
||||||
|
},
|
||||||
|
AppendFile: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
EditFile: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
FindSkills: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
I2C: ToolConfig{
|
||||||
|
Enabled: false, // Hardware tool - Linux only
|
||||||
|
},
|
||||||
|
InstallSkill: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
ListDir: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Message: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
ReadFile: ReadFileToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
MaxReadFileSize: 64 * 1024, // 64KB
|
||||||
|
},
|
||||||
|
Spawn: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
SPI: ToolConfig{
|
||||||
|
Enabled: false, // Hardware tool - Linux only
|
||||||
|
},
|
||||||
|
Subagent: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
WebFetch: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
WriteFile: ToolConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Heartbeat: HeartbeatConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Interval: 30,
|
||||||
|
},
|
||||||
|
Devices: DevicesConfig{
|
||||||
|
Enabled: false,
|
||||||
|
MonitorUSB: true,
|
||||||
|
},
|
||||||
|
Voice: VoiceConfig{
|
||||||
|
EchoTranscription: false,
|
||||||
|
},
|
||||||
|
BuildInfo: BuildInfo{
|
||||||
|
Version: Version,
|
||||||
|
GitCommit: GitCommit,
|
||||||
|
BuildTime: BuildTime,
|
||||||
|
GoVersion: GoVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -96,6 +96,11 @@ type MediaCleanupConfig struct {
|
||||||
Interval int ` env:"PICOCLAW_MEDIA_CLEANUP_INTERVAL" json:"interval_minutes"`
|
Interval int ` env:"PICOCLAW_MEDIA_CLEANUP_INTERVAL" json:"interval_minutes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JulesConfig struct {
|
||||||
|
ToolConfig `envPrefix:"PICOCLAW_TOOLS_JULES_"`
|
||||||
|
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_JULES_API_KEY"`
|
||||||
|
}
|
||||||
|
|
||||||
type ReadFileToolConfig struct {
|
type ReadFileToolConfig struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
MaxReadFileSize int `json:"max_read_file_size"`
|
MaxReadFileSize int `json:"max_read_file_size"`
|
||||||
|
|
@ -106,6 +111,7 @@ type ToolsConfig struct {
|
||||||
AllowReadPaths []string `json:"allow_read_paths" env:"PICOCLAW_TOOLS_ALLOW_READ_PATHS"`
|
AllowReadPaths []string `json:"allow_read_paths" env:"PICOCLAW_TOOLS_ALLOW_READ_PATHS"`
|
||||||
AllowWritePaths []string `json:"allow_write_paths" env:"PICOCLAW_TOOLS_ALLOW_WRITE_PATHS"`
|
AllowWritePaths []string `json:"allow_write_paths" env:"PICOCLAW_TOOLS_ALLOW_WRITE_PATHS"`
|
||||||
Web WebToolsConfig `json:"web"`
|
Web WebToolsConfig `json:"web"`
|
||||||
|
Jules JulesConfig `json:"jules"`
|
||||||
Cron CronToolsConfig `json:"cron"`
|
Cron CronToolsConfig `json:"cron"`
|
||||||
Exec ExecConfig `json:"exec"`
|
Exec ExecConfig `json:"exec"`
|
||||||
Skills SkillsToolsConfig `json:"skills"`
|
Skills SkillsToolsConfig `json:"skills"`
|
||||||
|
|
@ -182,6 +188,8 @@ func (t *ToolsConfig) IsToolEnabled(name string) bool {
|
||||||
switch name {
|
switch name {
|
||||||
case "web":
|
case "web":
|
||||||
return t.Web.Enabled
|
return t.Web.Enabled
|
||||||
|
case "jules":
|
||||||
|
return t.Jules.Enabled
|
||||||
case "cron":
|
case "cron":
|
||||||
return t.Cron.Enabled
|
return t.Cron.Enabled
|
||||||
case "exec":
|
case "exec":
|
||||||
|
|
|
||||||
236
pkg/config/tools.go.orig
Normal file
236
pkg/config/tools.go.orig
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
type ToolDiscoveryConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_DISCOVERY_ENABLED"`
|
||||||
|
TTL int `json:"ttl" env:"PICOCLAW_TOOLS_DISCOVERY_TTL"`
|
||||||
|
MaxSearchResults int `json:"max_search_results" env:"PICOCLAW_MAX_SEARCH_RESULTS"`
|
||||||
|
UseBM25 bool `json:"use_bm25" env:"PICOCLAW_TOOLS_DISCOVERY_USE_BM25"`
|
||||||
|
UseRegex bool `json:"use_regex" env:"PICOCLAW_TOOLS_DISCOVERY_USE_REGEX"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"ENABLED"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BraveConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_BRAVE_ENABLED"`
|
||||||
|
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_BRAVE_API_KEY"`
|
||||||
|
APIKeys []string `json:"api_keys" env:"PICOCLAW_TOOLS_WEB_BRAVE_API_KEYS"`
|
||||||
|
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_BRAVE_MAX_RESULTS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TavilyConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_TAVILY_ENABLED"`
|
||||||
|
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_TAVILY_API_KEY"`
|
||||||
|
APIKeys []string `json:"api_keys" env:"PICOCLAW_TOOLS_WEB_TAVILY_API_KEYS"`
|
||||||
|
BaseURL string `json:"base_url" env:"PICOCLAW_TOOLS_WEB_TAVILY_BASE_URL"`
|
||||||
|
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_TAVILY_MAX_RESULTS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DuckDuckGoConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_DUCKDUCKGO_ENABLED"`
|
||||||
|
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_DUCKDUCKGO_MAX_RESULTS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PerplexityConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_ENABLED"`
|
||||||
|
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_API_KEY"`
|
||||||
|
APIKeys []string `json:"api_keys" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_API_KEYS"`
|
||||||
|
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_MAX_RESULTS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearXNGConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_SEARXNG_ENABLED"`
|
||||||
|
BaseURL string `json:"base_url" env:"PICOCLAW_TOOLS_WEB_SEARXNG_BASE_URL"`
|
||||||
|
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_SEARXNG_MAX_RESULTS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GLMSearchConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_GLM_ENABLED"`
|
||||||
|
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_GLM_API_KEY"`
|
||||||
|
BaseURL string `json:"base_url" env:"PICOCLAW_TOOLS_WEB_GLM_BASE_URL"`
|
||||||
|
// SearchEngine specifies the search backend: "search_std" (default),
|
||||||
|
// "search_pro", "search_pro_sogou", or "search_pro_quark".
|
||||||
|
SearchEngine string `json:"search_engine" env:"PICOCLAW_TOOLS_WEB_GLM_SEARCH_ENGINE"`
|
||||||
|
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_GLM_MAX_RESULTS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebToolsConfig struct {
|
||||||
|
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_WEB_"`
|
||||||
|
Brave BraveConfig ` json:"brave"`
|
||||||
|
Tavily TavilyConfig ` json:"tavily"`
|
||||||
|
DuckDuckGo DuckDuckGoConfig ` json:"duckduckgo"`
|
||||||
|
Perplexity PerplexityConfig ` json:"perplexity"`
|
||||||
|
SearXNG SearXNGConfig ` json:"searxng"`
|
||||||
|
GLMSearch GLMSearchConfig ` json:"glm_search"`
|
||||||
|
// Proxy is an optional proxy URL for web tools (http/https/socks5/socks5h).
|
||||||
|
// For authenticated proxies, prefer HTTP_PROXY/HTTPS_PROXY env vars instead of embedding credentials in config.
|
||||||
|
Proxy string `json:"proxy,omitempty" env:"PICOCLAW_TOOLS_WEB_PROXY"`
|
||||||
|
FetchLimitBytes int64 `json:"fetch_limit_bytes,omitempty" env:"PICOCLAW_TOOLS_WEB_FETCH_LIMIT_BYTES"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CronToolsConfig struct {
|
||||||
|
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_CRON_"`
|
||||||
|
ExecTimeoutMinutes int ` env:"PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES" json:"exec_timeout_minutes"` // 0 means no timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecConfig struct {
|
||||||
|
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_EXEC_"`
|
||||||
|
EnableDenyPatterns bool ` env:"PICOCLAW_TOOLS_EXEC_ENABLE_DENY_PATTERNS" json:"enable_deny_patterns"`
|
||||||
|
AllowRemote bool ` env:"PICOCLAW_TOOLS_EXEC_ALLOW_REMOTE" json:"allow_remote"`
|
||||||
|
CustomDenyPatterns []string ` env:"PICOCLAW_TOOLS_EXEC_CUSTOM_DENY_PATTERNS" json:"custom_deny_patterns"`
|
||||||
|
CustomAllowPatterns []string ` env:"PICOCLAW_TOOLS_EXEC_CUSTOM_ALLOW_PATTERNS" json:"custom_allow_patterns"`
|
||||||
|
TimeoutSeconds int ` env:"PICOCLAW_TOOLS_EXEC_TIMEOUT_SECONDS" json:"timeout_seconds"` // 0 means use default (60s)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SkillsToolsConfig struct {
|
||||||
|
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_SKILLS_"`
|
||||||
|
Registries SkillsRegistriesConfig ` json:"registries"`
|
||||||
|
MaxConcurrentSearches int ` json:"max_concurrent_searches" env:"PICOCLAW_TOOLS_SKILLS_MAX_CONCURRENT_SEARCHES"`
|
||||||
|
SearchCache SearchCacheConfig ` json:"search_cache"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MediaCleanupConfig struct {
|
||||||
|
ToolConfig ` envPrefix:"PICOCLAW_MEDIA_CLEANUP_"`
|
||||||
|
MaxAge int ` env:"PICOCLAW_MEDIA_CLEANUP_MAX_AGE" json:"max_age_minutes"`
|
||||||
|
Interval int ` env:"PICOCLAW_MEDIA_CLEANUP_INTERVAL" json:"interval_minutes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReadFileToolConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
MaxReadFileSize int `json:"max_read_file_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolsConfig struct {
|
||||||
|
Alpaca AlpacaConfig `json:"alpaca,omitempty"`
|
||||||
|
AllowReadPaths []string `json:"allow_read_paths" env:"PICOCLAW_TOOLS_ALLOW_READ_PATHS"`
|
||||||
|
AllowWritePaths []string `json:"allow_write_paths" env:"PICOCLAW_TOOLS_ALLOW_WRITE_PATHS"`
|
||||||
|
Web WebToolsConfig `json:"web"`
|
||||||
|
Cron CronToolsConfig `json:"cron"`
|
||||||
|
Exec ExecConfig `json:"exec"`
|
||||||
|
Skills SkillsToolsConfig `json:"skills"`
|
||||||
|
MediaCleanup MediaCleanupConfig `json:"media_cleanup"`
|
||||||
|
MCP MCPConfig `json:"mcp"`
|
||||||
|
AppendFile ToolConfig `json:"append_file" envPrefix:"PICOCLAW_TOOLS_APPEND_FILE_"`
|
||||||
|
EditFile ToolConfig `json:"edit_file" envPrefix:"PICOCLAW_TOOLS_EDIT_FILE_"`
|
||||||
|
FindSkills ToolConfig `json:"find_skills" envPrefix:"PICOCLAW_TOOLS_FIND_SKILLS_"`
|
||||||
|
I2C ToolConfig `json:"i2c" envPrefix:"PICOCLAW_TOOLS_I2C_"`
|
||||||
|
InstallSkill ToolConfig `json:"install_skill" envPrefix:"PICOCLAW_TOOLS_INSTALL_SKILL_"`
|
||||||
|
ListDir ToolConfig `json:"list_dir" envPrefix:"PICOCLAW_TOOLS_LIST_DIR_"`
|
||||||
|
Message ToolConfig `json:"message" envPrefix:"PICOCLAW_TOOLS_MESSAGE_"`
|
||||||
|
ReadFile ReadFileToolConfig `json:"read_file" envPrefix:"PICOCLAW_TOOLS_READ_FILE_"`
|
||||||
|
SendFile ToolConfig `json:"send_file" envPrefix:"PICOCLAW_TOOLS_SEND_FILE_"`
|
||||||
|
Spawn ToolConfig `json:"spawn" envPrefix:"PICOCLAW_TOOLS_SPAWN_"`
|
||||||
|
SPI ToolConfig `json:"spi" envPrefix:"PICOCLAW_TOOLS_SPI_"`
|
||||||
|
Subagent ToolConfig `json:"subagent" envPrefix:"PICOCLAW_TOOLS_SUBAGENT_"`
|
||||||
|
WebFetch ToolConfig `json:"web_fetch" envPrefix:"PICOCLAW_TOOLS_WEB_FETCH_"`
|
||||||
|
WriteFile ToolConfig `json:"write_file" envPrefix:"PICOCLAW_TOOLS_WRITE_FILE_"`
|
||||||
|
BrowserAction ToolConfig `json:"browser_action" envPrefix:"PICOCLAW_TOOLS_BROWSER_ACTION_"`
|
||||||
|
GoEval ToolConfig `json:"go_eval" envPrefix:"PICOCLAW_TOOLS_GO_EVAL_"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchCacheConfig struct {
|
||||||
|
MaxSize int `json:"max_size" env:"PICOCLAW_SKILLS_SEARCH_CACHE_MAX_SIZE"`
|
||||||
|
TTLSeconds int `json:"ttl_seconds" env:"PICOCLAW_SKILLS_SEARCH_CACHE_TTL_SECONDS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SkillsRegistriesConfig struct {
|
||||||
|
ClawHub ClawHubRegistryConfig `json:"clawhub"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClawHubRegistryConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_ENABLED"`
|
||||||
|
BaseURL string `json:"base_url" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_BASE_URL"`
|
||||||
|
AuthToken string `json:"auth_token" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_AUTH_TOKEN"`
|
||||||
|
SearchPath string `json:"search_path" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_SEARCH_PATH"`
|
||||||
|
SkillsPath string `json:"skills_path" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_SKILLS_PATH"`
|
||||||
|
DownloadPath string `json:"download_path" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_DOWNLOAD_PATH"`
|
||||||
|
Timeout int `json:"timeout" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_TIMEOUT"`
|
||||||
|
MaxZipSize int `json:"max_zip_size" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_MAX_ZIP_SIZE"`
|
||||||
|
MaxResponseSize int `json:"max_response_size" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_MAX_RESPONSE_SIZE"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MCPServerConfig defines configuration for a single MCP server
|
||||||
|
type MCPServerConfig struct {
|
||||||
|
// Enabled indicates whether this MCP server is active
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
// Command is the executable to run (e.g., "npx", "python", "/path/to/server")
|
||||||
|
Command string `json:"command"`
|
||||||
|
// Args are the arguments to pass to the command
|
||||||
|
Args []string `json:"args,omitempty"`
|
||||||
|
// Env are environment variables to set for the server process (stdio only)
|
||||||
|
Env map[string]string `json:"env,omitempty"`
|
||||||
|
// EnvFile is the path to a file containing environment variables (stdio only)
|
||||||
|
EnvFile string `json:"env_file,omitempty"`
|
||||||
|
// Type is "stdio", "sse", or "http" (default: stdio if command is set, sse if url is set)
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
// URL is used for SSE/HTTP transport
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
// Headers are HTTP headers to send with requests (sse/http only)
|
||||||
|
Headers map[string]string `json:"headers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MCPConfig defines configuration for all MCP servers
|
||||||
|
type MCPConfig struct {
|
||||||
|
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_MCP_"`
|
||||||
|
Discovery ToolDiscoveryConfig ` json:"discovery"`
|
||||||
|
// Servers is a map of server name to server configuration
|
||||||
|
Servers map[string]MCPServerConfig `json:"servers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToolsConfig) IsToolEnabled(name string) bool {
|
||||||
|
switch name {
|
||||||
|
case "web":
|
||||||
|
return t.Web.Enabled
|
||||||
|
case "cron":
|
||||||
|
return t.Cron.Enabled
|
||||||
|
case "exec":
|
||||||
|
return t.Exec.Enabled
|
||||||
|
case "skills":
|
||||||
|
return t.Skills.Enabled
|
||||||
|
case "media_cleanup":
|
||||||
|
return t.MediaCleanup.Enabled
|
||||||
|
case "append_file":
|
||||||
|
return t.AppendFile.Enabled
|
||||||
|
case "edit_file":
|
||||||
|
return t.EditFile.Enabled
|
||||||
|
case "find_skills":
|
||||||
|
return t.FindSkills.Enabled
|
||||||
|
case "i2c":
|
||||||
|
return t.I2C.Enabled
|
||||||
|
case "install_skill":
|
||||||
|
return t.InstallSkill.Enabled
|
||||||
|
case "list_dir":
|
||||||
|
return t.ListDir.Enabled
|
||||||
|
case "message":
|
||||||
|
return t.Message.Enabled
|
||||||
|
case "read_file":
|
||||||
|
return t.ReadFile.Enabled
|
||||||
|
case "spawn":
|
||||||
|
return t.Spawn.Enabled
|
||||||
|
case "spi":
|
||||||
|
return t.SPI.Enabled
|
||||||
|
case "subagent":
|
||||||
|
return t.Subagent.Enabled
|
||||||
|
case "web_fetch":
|
||||||
|
return t.WebFetch.Enabled
|
||||||
|
case "browser_action":
|
||||||
|
return t.BrowserAction.Enabled
|
||||||
|
case "go_eval":
|
||||||
|
return t.GoEval.Enabled
|
||||||
|
case "send_file":
|
||||||
|
return t.SendFile.Enabled
|
||||||
|
case "write_file":
|
||||||
|
return t.WriteFile.Enabled
|
||||||
|
case "mcp":
|
||||||
|
return t.MCP.Enabled
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlpacaConfig struct {
|
||||||
|
KeyID string `json:"key_id,omitempty"`
|
||||||
|
SecretKey string `json:"secret_key,omitempty"`
|
||||||
|
BaseURL string `json:"base_url,omitempty"`
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue