Files
sproutclaw-web/backend/internal/config/config.go
shumengya 33717d742b feat: 增强聊天 UI 与终端配置
- 重构消息列表、思考块与工具调用块样式
- ChatContext 支持 agent 分段与 markdown 渲染优化
- 后端增加 PI 命令配置与 Web 终端改进
- 新增 run-backend.sh 启动脚本,忽略 backend/dist 构建产物
2026-06-26 13:21:15 +08:00

150 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))
}