- 重构消息列表、思考块与工具调用块样式 - ChatContext 支持 agent 分段与 markdown 渲染优化 - 后端增加 PI 命令配置与 Web 终端改进 - 新增 run-backend.sh 启动脚本,忽略 backend/dist 构建产物
150 lines
4.4 KiB
Go
150 lines
4.4 KiB
Go
package config
|
||
|
||
import (
|
||
"flag"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
)
|
||
|
||
const ConfigDirName = ".sproutclaw"
|
||
|
||
type Config struct {
|
||
Port int
|
||
AgentDir string
|
||
RepoRoot string // SproutClaw 仓库根目录(CLI 子进程的工作目录)
|
||
DataDir string // sproutclaw-web 自己的数据目录(SQLite 等)
|
||
PiCmd string // SproutClaw CLI 启动命令(完整路径)
|
||
PiArgs []string
|
||
|
||
// 派生路径(从 AgentDir 推导)
|
||
SessionsDir string
|
||
SystemPromptFile string
|
||
ModelsConfigFile string
|
||
McpConfigFile string
|
||
McpCacheFile string
|
||
SettingsFile string
|
||
SkillsDir string
|
||
SkillsDisabledDir string
|
||
ExtensionsDir string
|
||
PromptsDir string
|
||
FrontendDist string // ../frontend/dist 相对于可执行文件
|
||
}
|
||
|
||
func New() *Config {
|
||
port := flag.Int("port", 19133, "HTTP port")
|
||
agentDir := flag.String("agent-dir", "", "SproutClaw agent directory (e.g. /path/to/.sproutclaw/agent)")
|
||
repoRoot := flag.String("repo-root", "", "SproutClaw repo root (CLI cwd); defaults to two levels above agent-dir")
|
||
dataDir := flag.String("data-dir", "", "sproutclaw-web data dir for SQLite (defaults to ./data)")
|
||
sproutclawCmd := flag.String("sproutclaw-cmd", "", "SproutClaw CLI command (alias for --pi-cmd)")
|
||
piCmd := flag.String("pi-cmd", "", "SproutClaw CLI command (may include args, e.g. \"node /path/tsx.mjs /path/cli.ts\")")
|
||
frontendDist := flag.String("frontend-dist", "", "path to frontend/dist (defaults to ../frontend/dist relative to binary)")
|
||
flag.Parse()
|
||
|
||
if *agentDir == "" {
|
||
*agentDir = resolveDefaultAgentDir()
|
||
}
|
||
if abs, err := filepath.Abs(*agentDir); err == nil {
|
||
*agentDir = abs
|
||
}
|
||
|
||
cfg := &Config{
|
||
Port: *port,
|
||
AgentDir: *agentDir,
|
||
RepoRoot: *repoRoot,
|
||
DataDir: *dataDir,
|
||
}
|
||
if cfg.RepoRoot == "" {
|
||
cfg.RepoRoot = inferRepoRoot(*agentDir)
|
||
}
|
||
if abs, err := filepath.Abs(cfg.RepoRoot); err == nil {
|
||
cfg.RepoRoot = abs
|
||
}
|
||
// dataDir defaults to ./data relative to cwd (run-backend.bat cd's into backend/)
|
||
if cfg.DataDir == "" {
|
||
cwd, _ := os.Getwd()
|
||
cfg.DataDir = filepath.Join(cwd, "data")
|
||
}
|
||
if abs, err := filepath.Abs(cfg.DataDir); err == nil {
|
||
cfg.DataDir = abs
|
||
}
|
||
// --pi-cmd / --sproutclaw-cmd may include arguments separated by spaces, e.g.
|
||
// "node /path/to/tsx/dist/cli.mjs /path/to/cli.ts"
|
||
// Split into executable + args so exec.Command receives them correctly.
|
||
cmdLine := strings.TrimSpace(*sproutclawCmd)
|
||
if cmdLine == "" {
|
||
cmdLine = strings.TrimSpace(*piCmd)
|
||
}
|
||
if parts := strings.Fields(cmdLine); len(parts) > 0 {
|
||
cfg.PiCmd = parts[0]
|
||
cfg.PiArgs = parts[1:]
|
||
}
|
||
cfg.derivePaths()
|
||
|
||
// Override frontend dist if explicitly specified
|
||
if *frontendDist != "" {
|
||
cfg.FrontendDist = *frontendDist
|
||
}
|
||
return cfg
|
||
}
|
||
|
||
func (c *Config) derivePaths() {
|
||
a := c.AgentDir
|
||
c.SessionsDir = filepath.Join(a, "sessions")
|
||
c.SystemPromptFile = filepath.Join(a, "AGENTS.md")
|
||
c.ModelsConfigFile = filepath.Join(a, "models.json")
|
||
c.McpConfigFile = filepath.Join(a, "mcp.json")
|
||
c.McpCacheFile = filepath.Join(a, "mcp-cache.json")
|
||
c.SettingsFile = filepath.Join(a, "settings.json")
|
||
c.SkillsDir = filepath.Join(a, "skills")
|
||
c.SkillsDisabledDir = filepath.Join(a, "skills-disabled")
|
||
c.ExtensionsDir = filepath.Join(a, "extensions")
|
||
c.PromptsDir = filepath.Join(a, "prompts")
|
||
|
||
// frontend/dist 相对于二进制所在目录的上级
|
||
exe, err := os.Executable()
|
||
if err != nil {
|
||
exe, _ = os.Getwd()
|
||
}
|
||
exeDir := filepath.Dir(exe)
|
||
c.FrontendDist = filepath.Join(exeDir, "..", "frontend", "dist")
|
||
}
|
||
|
||
func Platform() string {
|
||
return runtime.GOOS
|
||
}
|
||
|
||
func Arch() string {
|
||
return runtime.GOARCH
|
||
}
|
||
|
||
func resolveDefaultAgentDir() string {
|
||
if env := strings.TrimSpace(os.Getenv("SPROUTCLAW_AGENT_DIR")); env != "" {
|
||
return env
|
||
}
|
||
cwd, _ := os.Getwd()
|
||
candidates := []string{
|
||
filepath.Join(cwd, ConfigDirName, "agent"),
|
||
filepath.Join(cwd, "..", "sproutclaw", ConfigDirName, "agent"),
|
||
filepath.Join(cwd, "..", "sproutclaw", ".pi", "agent"),
|
||
}
|
||
for _, candidate := range candidates {
|
||
if st, err := os.Stat(candidate); err == nil && st.IsDir() {
|
||
return candidate
|
||
}
|
||
}
|
||
return filepath.Join(cwd, ConfigDirName, "agent")
|
||
}
|
||
|
||
func inferRepoRoot(agentDir string) string {
|
||
agentDir = filepath.Clean(agentDir)
|
||
if filepath.Base(agentDir) == "agent" {
|
||
configDir := filepath.Base(filepath.Dir(agentDir))
|
||
if configDir == ConfigDirName || configDir == ".pi" {
|
||
return filepath.Dir(filepath.Dir(agentDir))
|
||
}
|
||
}
|
||
return filepath.Dir(filepath.Dir(agentDir))
|
||
}
|