748048be50
Sessions run claude with --dangerously-skip-permissions and a prompt built from issue/PR/comment text, so only trusted authors may supply that text. teabot now dispatches a job only when the triggering event's author login is on an allowlist; an empty allowlist dispatches nothing (fail-closed). Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"git.unkin.net/unkin/teabot/internal/config"
|
|
)
|
|
|
|
func newConfigCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "config",
|
|
Short: "Inspect and scaffold teabot configuration",
|
|
}
|
|
cmd.AddCommand(newConfigInitCmd())
|
|
cmd.AddCommand(newConfigShowCmd())
|
|
return cmd
|
|
}
|
|
|
|
func newConfigInitCmd() *cobra.Command {
|
|
var force bool
|
|
cmd := &cobra.Command{
|
|
Use: "init",
|
|
Short: "Write an example config file",
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
path := configPath
|
|
if _, err := os.Stat(path); err == nil && !force {
|
|
return fmt.Errorf("config %s already exists (use --force to overwrite)", path)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(path, []byte(config.Example), 0o644); err != nil {
|
|
return err
|
|
}
|
|
_, err := fmt.Fprintf(cmd.OutOrStdout(),
|
|
"wrote example config to %s\nedit it, then create the referenced tea config files with `tea logins add`.\n",
|
|
path)
|
|
return err
|
|
},
|
|
}
|
|
cmd.Flags().BoolVar(&force, "force", false, "overwrite an existing config file")
|
|
return cmd
|
|
}
|
|
|
|
func newConfigShowCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "show",
|
|
Short: "Validate and print the effective configuration",
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
cfg, err := config.Load(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lines := []string{
|
|
fmt.Sprintf("config: %s", configPath),
|
|
fmt.Sprintf("gitea_url: %s", cfg.GiteaURL),
|
|
fmt.Sprintf("state_dir: %s", cfg.StateDirOrDefault()),
|
|
fmt.Sprintf("poll_interval: %s", cfg.PollInterval),
|
|
fmt.Sprintf("job_timeout: %s", cfg.JobTimeout),
|
|
fmt.Sprintf("max_concurrent: %d", cfg.MaxConcurrent),
|
|
fmt.Sprintf("job_image: %s", cfg.JobImage),
|
|
fmt.Sprintf("claude_config: %s", cfg.ClaudeConfigDir),
|
|
}
|
|
if cfg.AnthropicBaseURL != "" {
|
|
lines = append(lines, "anthropic_base_url: "+cfg.AnthropicBaseURL)
|
|
}
|
|
if cfg.AnthropicAPIKey != "" {
|
|
lines = append(lines, "anthropic_api_key: (set)")
|
|
}
|
|
if !cfg.HasAnyAllowlist() {
|
|
lines = append(lines, "allowed_authors: (none — FAIL-CLOSED, dispatches nothing)")
|
|
} else {
|
|
lines = append(lines, "allowed_authors: "+strings.Join(cfg.AllowedAuthors, ", "))
|
|
for repo, authors := range cfg.RepoAllowedAuthors {
|
|
lines = append(lines, fmt.Sprintf(" override %s: %s", repo, strings.Join(authors, ", ")))
|
|
}
|
|
}
|
|
lines = append(lines, "repos:")
|
|
for _, r := range cfg.Repos {
|
|
lines = append(lines, " - "+r)
|
|
}
|
|
lines = append(lines, "personalities:")
|
|
for _, p := range cfg.Personalities {
|
|
lines = append(lines, fmt.Sprintf(" - %s (role=%s, login=%s)", p.Name, p.Role, p.Login))
|
|
}
|
|
_, err = fmt.Fprintln(cmd.OutOrStdout(), strings.Join(lines, "\n"))
|
|
return err
|
|
},
|
|
}
|
|
}
|