fix: unify runtime path resolution for procd services
Centralize config/auth/global skills path derivation with PICOCLAW_CONFIG and PICOCLAW_HOME precedence so non-interactive OpenWrt services load the intended files. Always apply env overrides even when the config file is missing, and add tests for global --config parsing plus resolver precedence. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
57dac394c5
commit
c34ce9d128
10 changed files with 357 additions and 22 deletions
|
|
@ -120,6 +120,20 @@ func copyDirectory(src, dst string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
parsedArgs, configOverride, err := parseGlobalFlags(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOverride != "" {
|
||||||
|
if err := os.Setenv(config.EnvPicoClawConfig, configOverride); err != nil {
|
||||||
|
fmt.Printf("Error setting %s: %v\n", config.EnvPicoClawConfig, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Args = parsedArgs
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
printHelp()
|
printHelp()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
@ -158,10 +172,9 @@ func main() {
|
||||||
|
|
||||||
workspace := cfg.WorkspacePath()
|
workspace := cfg.WorkspacePath()
|
||||||
installer := skills.NewSkillInstaller(workspace)
|
installer := skills.NewSkillInstaller(workspace)
|
||||||
// 获取全局配置目录和内置 skills 目录
|
paths := config.ResolveRuntimePaths()
|
||||||
globalDir := filepath.Dir(getConfigPath())
|
globalSkillsDir := paths.GlobalSkillsDir
|
||||||
globalSkillsDir := filepath.Join(globalDir, "skills")
|
builtinSkillsDir := filepath.Join(paths.HomeDir, "picoclaw", "skills")
|
||||||
builtinSkillsDir := filepath.Join(globalDir, "picoclaw", "skills")
|
|
||||||
skillsLoader := skills.NewSkillsLoader(workspace, globalSkillsDir, builtinSkillsDir)
|
skillsLoader := skills.NewSkillsLoader(workspace, globalSkillsDir, builtinSkillsDir)
|
||||||
|
|
||||||
switch subcommand {
|
switch subcommand {
|
||||||
|
|
@ -200,9 +213,45 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseGlobalFlags(args []string) ([]string, string, error) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return args, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := make([]string, 0, len(args))
|
||||||
|
filtered = append(filtered, args[0])
|
||||||
|
var configOverride string
|
||||||
|
|
||||||
|
for i := 1; i < len(args); i++ {
|
||||||
|
arg := args[i]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case arg == "--config":
|
||||||
|
if i+1 >= len(args) || strings.TrimSpace(args[i+1]) == "" {
|
||||||
|
return nil, "", fmt.Errorf("--config requires a path")
|
||||||
|
}
|
||||||
|
configOverride = args[i+1]
|
||||||
|
i++
|
||||||
|
case strings.HasPrefix(arg, "--config="):
|
||||||
|
value := strings.TrimSpace(strings.TrimPrefix(arg, "--config="))
|
||||||
|
if value == "" {
|
||||||
|
return nil, "", fmt.Errorf("--config requires a path")
|
||||||
|
}
|
||||||
|
configOverride = value
|
||||||
|
default:
|
||||||
|
filtered = append(filtered, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered, configOverride, nil
|
||||||
|
}
|
||||||
|
|
||||||
func printHelp() {
|
func printHelp() {
|
||||||
fmt.Printf("%s picoclaw - Personal AI Assistant v%s\n\n", logo, version)
|
fmt.Printf("%s picoclaw - Personal AI Assistant v%s\n\n", logo, version)
|
||||||
fmt.Println("Usage: picoclaw <command>")
|
fmt.Println("Usage: picoclaw [--config <path>] <command>")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Global options:")
|
||||||
|
fmt.Println(" --config <path> Use a custom config file path")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" onboard Initialize picoclaw configuration and workspace")
|
fmt.Println(" onboard Initialize picoclaw configuration and workspace")
|
||||||
|
|
@ -983,8 +1032,7 @@ func authStatusCmd() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfigPath() string {
|
func getConfigPath() string {
|
||||||
home, _ := os.UserHomeDir()
|
return config.ResolveRuntimePaths().ConfigPath
|
||||||
return filepath.Join(home, ".picoclaw", "config.json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace string, restrict bool) *cron.CronService {
|
func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace string, restrict bool) *cron.CronService {
|
||||||
|
|
|
||||||
70
cmd/picoclaw/main_test.go
Normal file
70
cmd/picoclaw/main_test.go
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseGlobalFlags_ConfigPair(t *testing.T) {
|
||||||
|
args := []string{"picoclaw", "--config", "/tmp/config.json", "gateway", "--debug"}
|
||||||
|
|
||||||
|
filtered, override, err := parseGlobalFlags(args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseGlobalFlags() error: %v", err)
|
||||||
|
}
|
||||||
|
if override != "/tmp/config.json" {
|
||||||
|
t.Errorf("override = %q, want %q", override, "/tmp/config.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []string{"picoclaw", "gateway", "--debug"}
|
||||||
|
if !reflect.DeepEqual(filtered, want) {
|
||||||
|
t.Errorf("filtered args = %#v, want %#v", filtered, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGlobalFlags_ConfigEqualsSyntax(t *testing.T) {
|
||||||
|
args := []string{"picoclaw", "--config=/tmp/config.json", "status"}
|
||||||
|
|
||||||
|
filtered, override, err := parseGlobalFlags(args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseGlobalFlags() error: %v", err)
|
||||||
|
}
|
||||||
|
if override != "/tmp/config.json" {
|
||||||
|
t.Errorf("override = %q, want %q", override, "/tmp/config.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []string{"picoclaw", "status"}
|
||||||
|
if !reflect.DeepEqual(filtered, want) {
|
||||||
|
t.Errorf("filtered args = %#v, want %#v", filtered, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGlobalFlags_MissingValue(t *testing.T) {
|
||||||
|
tests := [][]string{
|
||||||
|
{"picoclaw", "--config"},
|
||||||
|
{"picoclaw", "--config", ""},
|
||||||
|
{"picoclaw", "--config= "},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
_, _, err := parseGlobalFlags(tt)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("parseGlobalFlags(%#v) expected error, got nil", tt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGlobalFlags_DoesNotConsumeCronShortFlag(t *testing.T) {
|
||||||
|
args := []string{"picoclaw", "cron", "add", "-c", "* * * * *"}
|
||||||
|
|
||||||
|
filtered, override, err := parseGlobalFlags(args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseGlobalFlags() error: %v", err)
|
||||||
|
}
|
||||||
|
if override != "" {
|
||||||
|
t.Errorf("override = %q, want empty", override)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(filtered, args) {
|
||||||
|
t.Errorf("filtered args = %#v, want %#v", filtered, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
"github.com/sipeed/picoclaw/pkg/providers"
|
"github.com/sipeed/picoclaw/pkg/providers"
|
||||||
"github.com/sipeed/picoclaw/pkg/skills"
|
"github.com/sipeed/picoclaw/pkg/skills"
|
||||||
|
|
@ -22,11 +23,7 @@ type ContextBuilder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGlobalConfigDir() string {
|
func getGlobalConfigDir() string {
|
||||||
home, err := os.UserHomeDir()
|
return config.ResolveRuntimePaths().HomeDir
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return filepath.Join(home, ".picoclaw")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContextBuilder(workspace string) *ContextBuilder {
|
func NewContextBuilder(workspace string) *ContextBuilder {
|
||||||
|
|
|
||||||
31
pkg/agent/context_test.go
Normal file
31
pkg/agent/context_test.go
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetGlobalConfigDir_UsesPicoClawHomeOverride(t *testing.T) {
|
||||||
|
homeOverride := filepath.Join(t.TempDir(), "pico-home")
|
||||||
|
t.Setenv(config.EnvPicoClawConfig, "")
|
||||||
|
t.Setenv(config.EnvPicoClawHome, homeOverride)
|
||||||
|
|
||||||
|
if got := getGlobalConfigDir(); got != homeOverride {
|
||||||
|
t.Errorf("getGlobalConfigDir() = %q, want %q", got, homeOverride)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGlobalConfigDir_ConfigOverrideTakesPrecedence(t *testing.T) {
|
||||||
|
homeOverride := filepath.Join(t.TempDir(), "pico-home")
|
||||||
|
configDir := filepath.Join(t.TempDir(), "custom-config-dir")
|
||||||
|
configPath := filepath.Join(configDir, "config.json")
|
||||||
|
|
||||||
|
t.Setenv(config.EnvPicoClawHome, homeOverride)
|
||||||
|
t.Setenv(config.EnvPicoClawConfig, configPath)
|
||||||
|
|
||||||
|
if got := getGlobalConfigDir(); got != configDir {
|
||||||
|
t.Errorf("getGlobalConfigDir() = %q, want %q", got, configDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthCredential struct {
|
type AuthCredential struct {
|
||||||
|
|
@ -35,8 +37,7 @@ func (c *AuthCredential) NeedsRefresh() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func authFilePath() string {
|
func authFilePath() string {
|
||||||
home, _ := os.UserHomeDir()
|
return config.ResolveRuntimePaths().AuthPath
|
||||||
return filepath.Join(home, ".picoclaw", "auth.json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadStore() (*AuthStore, error) {
|
func LoadStore() (*AuthStore, error) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuthCredentialIsExpired(t *testing.T) {
|
func TestAuthCredentialIsExpired(t *testing.T) {
|
||||||
|
|
@ -102,7 +104,7 @@ func TestStoreFilePermissions(t *testing.T) {
|
||||||
t.Fatalf("SetCredential() error: %v", err)
|
t.Fatalf("SetCredential() error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
path := filepath.Join(tmpDir, ".picoclaw", "auth.json")
|
path := config.ResolveRuntimePaths().AuthPath
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Stat() error: %v", err)
|
t.Fatalf("Stat() error: %v", err)
|
||||||
|
|
@ -187,3 +189,53 @@ func TestLoadStoreEmpty(t *testing.T) {
|
||||||
t.Errorf("expected empty credentials, got %d", len(store.Credentials))
|
t.Errorf("expected empty credentials, got %d", len(store.Credentials))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStoreUsesPicoClawHomeOverride(t *testing.T) {
|
||||||
|
baseDir := t.TempDir()
|
||||||
|
home := filepath.Join(baseDir, "home")
|
||||||
|
override := filepath.Join(baseDir, "pico-home")
|
||||||
|
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
t.Setenv(config.EnvPicoClawConfig, "")
|
||||||
|
t.Setenv(config.EnvPicoClawHome, override)
|
||||||
|
|
||||||
|
cred := &AuthCredential{
|
||||||
|
AccessToken: "override-token",
|
||||||
|
Provider: "openai",
|
||||||
|
AuthMethod: "oauth",
|
||||||
|
}
|
||||||
|
if err := SetCredential("openai", cred); err != nil {
|
||||||
|
t.Fatalf("SetCredential() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPath := filepath.Join(override, "auth.json")
|
||||||
|
if _, err := os.Stat(expectedPath); err != nil {
|
||||||
|
t.Fatalf("expected auth store at %s, got stat error: %v", expectedPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreUsesConfigDirectoryWhenConfigOverrideSet(t *testing.T) {
|
||||||
|
baseDir := t.TempDir()
|
||||||
|
home := filepath.Join(baseDir, "home")
|
||||||
|
homeOverride := filepath.Join(baseDir, "pico-home")
|
||||||
|
configDir := filepath.Join(baseDir, "custom-config-dir")
|
||||||
|
configPath := filepath.Join(configDir, "config.json")
|
||||||
|
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
t.Setenv(config.EnvPicoClawHome, homeOverride)
|
||||||
|
t.Setenv(config.EnvPicoClawConfig, configPath)
|
||||||
|
|
||||||
|
cred := &AuthCredential{
|
||||||
|
AccessToken: "config-override-token",
|
||||||
|
Provider: "openai",
|
||||||
|
AuthMethod: "oauth",
|
||||||
|
}
|
||||||
|
if err := SetCredential("openai", cred); err != nil {
|
||||||
|
t.Fatalf("SetCredential() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPath := filepath.Join(configDir, "auth.json")
|
||||||
|
if _, err := os.Stat(expectedPath); err != nil {
|
||||||
|
t.Fatalf("expected auth store at %s, got stat error: %v", expectedPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -339,15 +339,14 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
|
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if err := json.Unmarshal(data, cfg); err != nil {
|
if err := json.Unmarshal(data, cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := env.Parse(cfg); err != nil {
|
if err := env.Parse(cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -204,3 +204,17 @@ func TestConfig_Complete(t *testing.T) {
|
||||||
t.Error("Heartbeat should be enabled by default")
|
t.Error("Heartbeat should be enabled by default")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadConfig_AppliesEnvWithoutConfigFile(t *testing.T) {
|
||||||
|
t.Setenv("PICOCLAW_AGENTS_DEFAULTS_MODEL", "env-only-model")
|
||||||
|
|
||||||
|
missingPath := filepath.Join(t.TempDir(), "missing-config.json")
|
||||||
|
cfg, err := LoadConfig(missingPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Agents.Defaults.Model != "env-only-model" {
|
||||||
|
t.Errorf("Model = %q, want %q", cfg.Agents.Defaults.Model, "env-only-model")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
49
pkg/config/paths.go
Normal file
49
pkg/config/paths.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnvPicoClawConfig = "PICOCLAW_CONFIG"
|
||||||
|
EnvPicoClawHome = "PICOCLAW_HOME"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RuntimePaths struct {
|
||||||
|
HomeDir string
|
||||||
|
ConfigPath string
|
||||||
|
AuthPath string
|
||||||
|
GlobalSkillsDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveRuntimePaths() RuntimePaths {
|
||||||
|
if configPath := expandHome(strings.TrimSpace(os.Getenv(EnvPicoClawConfig))); configPath != "" {
|
||||||
|
return buildRuntimePaths(filepath.Dir(configPath), configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
homeDir := expandHome(strings.TrimSpace(os.Getenv(EnvPicoClawHome)))
|
||||||
|
if homeDir == "" {
|
||||||
|
homeDir = defaultPicoClawHome()
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildRuntimePaths(homeDir, filepath.Join(homeDir, "config.json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultPicoClawHome() string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil || home == "" {
|
||||||
|
return ".picoclaw"
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".picoclaw")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRuntimePaths(homeDir, configPath string) RuntimePaths {
|
||||||
|
return RuntimePaths{
|
||||||
|
HomeDir: homeDir,
|
||||||
|
ConfigPath: configPath,
|
||||||
|
AuthPath: filepath.Join(homeDir, "auth.json"),
|
||||||
|
GlobalSkillsDir: filepath.Join(homeDir, "skills"),
|
||||||
|
}
|
||||||
|
}
|
||||||
74
pkg/config/paths_test.go
Normal file
74
pkg/config/paths_test.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolveRuntimePaths_Default(t *testing.T) {
|
||||||
|
home := t.TempDir()
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
t.Setenv(EnvPicoClawConfig, "")
|
||||||
|
t.Setenv(EnvPicoClawHome, "")
|
||||||
|
|
||||||
|
paths := ResolveRuntimePaths()
|
||||||
|
wantHome := filepath.Join(home, ".picoclaw")
|
||||||
|
|
||||||
|
if paths.HomeDir != wantHome {
|
||||||
|
t.Errorf("HomeDir = %q, want %q", paths.HomeDir, wantHome)
|
||||||
|
}
|
||||||
|
if paths.ConfigPath != filepath.Join(wantHome, "config.json") {
|
||||||
|
t.Errorf("ConfigPath = %q, want %q", paths.ConfigPath, filepath.Join(wantHome, "config.json"))
|
||||||
|
}
|
||||||
|
if paths.AuthPath != filepath.Join(wantHome, "auth.json") {
|
||||||
|
t.Errorf("AuthPath = %q, want %q", paths.AuthPath, filepath.Join(wantHome, "auth.json"))
|
||||||
|
}
|
||||||
|
if paths.GlobalSkillsDir != filepath.Join(wantHome, "skills") {
|
||||||
|
t.Errorf("GlobalSkillsDir = %q, want %q", paths.GlobalSkillsDir, filepath.Join(wantHome, "skills"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveRuntimePaths_UsesPicoClawHomeOverride(t *testing.T) {
|
||||||
|
homeOverride := filepath.Join(t.TempDir(), "pico-home")
|
||||||
|
t.Setenv(EnvPicoClawConfig, "")
|
||||||
|
t.Setenv(EnvPicoClawHome, homeOverride)
|
||||||
|
|
||||||
|
paths := ResolveRuntimePaths()
|
||||||
|
|
||||||
|
if paths.HomeDir != homeOverride {
|
||||||
|
t.Errorf("HomeDir = %q, want %q", paths.HomeDir, homeOverride)
|
||||||
|
}
|
||||||
|
if paths.ConfigPath != filepath.Join(homeOverride, "config.json") {
|
||||||
|
t.Errorf("ConfigPath = %q, want %q", paths.ConfigPath, filepath.Join(homeOverride, "config.json"))
|
||||||
|
}
|
||||||
|
if paths.AuthPath != filepath.Join(homeOverride, "auth.json") {
|
||||||
|
t.Errorf("AuthPath = %q, want %q", paths.AuthPath, filepath.Join(homeOverride, "auth.json"))
|
||||||
|
}
|
||||||
|
if paths.GlobalSkillsDir != filepath.Join(homeOverride, "skills") {
|
||||||
|
t.Errorf("GlobalSkillsDir = %q, want %q", paths.GlobalSkillsDir, filepath.Join(homeOverride, "skills"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveRuntimePaths_ConfigOverrideTakesPrecedence(t *testing.T) {
|
||||||
|
homeOverride := filepath.Join(t.TempDir(), "pico-home")
|
||||||
|
configDir := filepath.Join(t.TempDir(), "custom-config-dir")
|
||||||
|
configPath := filepath.Join(configDir, "config.json")
|
||||||
|
|
||||||
|
t.Setenv(EnvPicoClawHome, homeOverride)
|
||||||
|
t.Setenv(EnvPicoClawConfig, configPath)
|
||||||
|
|
||||||
|
paths := ResolveRuntimePaths()
|
||||||
|
|
||||||
|
if paths.ConfigPath != configPath {
|
||||||
|
t.Errorf("ConfigPath = %q, want %q", paths.ConfigPath, configPath)
|
||||||
|
}
|
||||||
|
if paths.HomeDir != configDir {
|
||||||
|
t.Errorf("HomeDir = %q, want %q", paths.HomeDir, configDir)
|
||||||
|
}
|
||||||
|
if paths.AuthPath != filepath.Join(configDir, "auth.json") {
|
||||||
|
t.Errorf("AuthPath = %q, want %q", paths.AuthPath, filepath.Join(configDir, "auth.json"))
|
||||||
|
}
|
||||||
|
if paths.GlobalSkillsDir != filepath.Join(configDir, "skills") {
|
||||||
|
t.Errorf("GlobalSkillsDir = %q, want %q", paths.GlobalSkillsDir, filepath.Join(configDir, "skills"))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue