forked from bots-garden/ori
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 1 | // Package config parses the command-line configuration of the ori server. |
| 2 | // | |
| 3 | // It is deliberately tiny: three flags, validated once at startup, then passed | |
| 4 | // around as an immutable value. | |
| 5 | package config | |
| 6 | ||
| 7 | import ( | |
| 8 | "flag" | |
| 9 | "fmt" | |
| 10 | "os" | |
| 11 | "path/filepath" | |
| 12 | "strings" | |
| 13 | ) | |
| 14 | ||
| 15 | // DefaultAgentCommand is the ACP agent spawned when --agent-cmd is not given. | |
| 16 | // It is the official Claude Code ACP adapter, the same one Zed uses. | |
| 17 | const DefaultAgentCommand = "npx -y @zed-industries/claude-code-acp" | |
| 18 | ||
| 19 | // Config holds the runtime configuration of the ori server. | |
| 20 | type Config struct { | |
| 21 | // Addr is the TCP address the HTTP server listens on, e.g. ":8888". | |
| 22 | Addr string | |
| 23 | // Cwd is the absolute working directory handed to the agent session. | |
| 24 | Cwd string | |
| 25 | // AgentCommand is the command line used to spawn the ACP agent, | |
| 26 | // split on whitespace (the first field is the executable). | |
| 27 | AgentCommand []string | |
| 28 | } | |
| 29 | ||
| 30 | // FromArgs parses command-line arguments (without the program name) into a | |
| 31 | // Config. It returns an error for unknown flags, an empty agent command, or a | |
| 32 | // working directory that does not exist. | |
| 33 | // | |
| 34 | // Example: | |
| 35 | // | |
| 36 | // cfg, err := config.FromArgs([]string{"--addr", ":9000", "--cwd", "/tmp"}) | |
| 37 | // if err != nil { | |
| 38 | // log.Fatal(err) | |
| 39 | // } | |
| 40 | // fmt.Println(cfg.Addr) // ":9000" | |
| 41 | func FromArgs(args []string) (Config, error) { | |
| 42 | fs := flag.NewFlagSet("ori", flag.ContinueOnError) | |
| 43 | addr := fs.String("addr", ":8888", "TCP address the HTTP server listens on") | |
| 44 | cwd := fs.String("cwd", "", "working directory for the agent session (default: current directory)") | |
| 45 | agentCmd := fs.String("agent-cmd", DefaultAgentCommand, "command spawning the ACP agent, split on whitespace") | |
| 46 | ||
| 47 | if err := fs.Parse(args); err != nil { | |
| 48 | return Config{}, err | |
| 49 | } | |
| 50 | ||
| 51 | command := strings.Fields(*agentCmd) | |
| 52 | if len(command) == 0 { | |
| 53 | return Config{}, fmt.Errorf("--agent-cmd must not be empty") | |
| 54 | } | |
| 55 | ||
| 56 | dir := *cwd | |
| 57 | if dir == "" { | |
| 58 | wd, err := os.Getwd() | |
| 59 | if err != nil { | |
| 60 | return Config{}, fmt.Errorf("resolve current directory: %w", err) | |
| 61 | } | |
| 62 | dir = wd | |
| 63 | } | |
| 64 | absDir, err := filepath.Abs(dir) | |
| 65 | if err != nil { | |
| 66 | return Config{}, fmt.Errorf("resolve --cwd: %w", err) | |
| 67 | } | |
| 68 | info, err := os.Stat(absDir) | |
| 69 | if err != nil { | |
| 70 | return Config{}, fmt.Errorf("--cwd: %w", err) | |
| 71 | } | |
| 72 | if !info.IsDir() { | |
| 73 | return Config{}, fmt.Errorf("--cwd: %s is not a directory", absDir) | |
| 74 | } | |
| 75 | ||
| 76 | return Config{Addr: *addr, Cwd: absDir, AgentCommand: command}, nil | |
| 77 | } |