diff options
| author | Paul Buetow <paul@buetow.org> | 2025-07-19 16:29:06 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-07-19 16:29:06 +0300 |
| commit | 2ca1d94d1c6785a40b722a581a842be6a8741cc6 (patch) | |
| tree | 4f5ee4a0a3f6f320b2f6b2ea08792f8fafece482 | |
| parent | e23fc252fbac2aba69f1f1268af9425af4d43d19 (diff) | |
feat: add support for aichat as AI tool for release notesv0.8.0
- Add --ai-tool flag to release and sync commands
- Support both 'claude' and 'aichat' options (default: claude)
- Update GenerateAIReleaseNotes to handle both tools
- Add tool-specific error messages and hints
- Update documentation with usage examples
This allows users to choose between Claude CLI and aichat for
generating AI-powered release notes.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
| -rw-r--r-- | CLAUDE.md | 3 | ||||
| -rw-r--r-- | README.md | 3 | ||||
| -rw-r--r-- | internal/cli/flags.go | 1 | ||||
| -rw-r--r-- | internal/cli/release.go | 1 | ||||
| -rw-r--r-- | internal/cmd/release.go | 8 | ||||
| -rw-r--r-- | internal/cmd/sync.go | 8 | ||||
| -rw-r--r-- | internal/release/release.go | 85 |
7 files changed, 85 insertions, 24 deletions
@@ -46,6 +46,9 @@ gitsyncer release create --auto # Create releases without AI notes gitsyncer release create --auto --no-ai-notes + +# Use aichat instead of claude for AI release notes +gitsyncer release create --auto --ai-tool aichat ``` Note: Release checking is enabled by default after sync operations. It will check for version tags (formats: vX.Y.Z, vX.Y, vX, X.Y.Z, X.Y, X) that don't have corresponding releases on GitHub/Codeberg and prompt for confirmation before creating them. @@ -172,6 +172,9 @@ gitsyncer release create --update-existing # Create for specific repository without AI gitsyncer release create myproject --no-ai-notes + +# Use aichat instead of claude for AI release notes +gitsyncer release create --ai-tool aichat ``` ### Project Showcase diff --git a/internal/cli/flags.go b/internal/cli/flags.go index fb367cb..43d9fa3 100644 --- a/internal/cli/flags.go +++ b/internal/cli/flags.go @@ -35,6 +35,7 @@ type Flags struct { AutoCreateReleases bool AIReleaseNotes bool UpdateReleases bool + AITool string // Internal fields for batch run state management (not set by flags) BatchRunStateManager *state.Manager diff --git a/internal/cli/release.go b/internal/cli/release.go index d7601f4..e05cbdd 100644 --- a/internal/cli/release.go +++ b/internal/cli/release.go @@ -59,6 +59,7 @@ func HandleCheckReleasesForRepo(cfg *config.Config, flags *Flags, repoName strin // HandleCheckReleasesForRepos checks for version tags without releases and creates them with confirmation func HandleCheckReleasesForRepos(cfg *config.Config, flags *Flags, repositories []string) int { releaseManager := release.NewManager(flags.WorkDir) + releaseManager.SetAITool(flags.AITool) // Load persistent AI release notes cache cacheFile := filepath.Join(flags.WorkDir, ".gitsyncer-ai-release-notes-cache.json") diff --git a/internal/cmd/release.go b/internal/cmd/release.go index f05005a..f9fa04f 100644 --- a/internal/cmd/release.go +++ b/internal/cmd/release.go @@ -12,6 +12,7 @@ var ( noAINotes bool updateExisting bool templatePath string + aiTool string ) var releaseCmd = &cobra.Command{ @@ -70,13 +71,17 @@ If no repository is specified, processes all configured repositories.`, gitsyncer release create --update-existing # Create for specific repository without AI - gitsyncer release create myproject --no-ai-notes`, + gitsyncer release create myproject --no-ai-notes + + # Use aichat instead of claude for AI release notes + gitsyncer release create --ai-tool aichat`, Run: func(cmd *cobra.Command, args []string) { flags := buildFlags() flags.CheckReleases = true flags.AutoCreateReleases = autoRelease flags.AIReleaseNotes = !noAINotes flags.UpdateReleases = updateExisting + flags.AITool = aiTool if len(args) > 0 { // Create releases for specific repo @@ -103,4 +108,5 @@ func init() { releaseCreateCmd.Flags().BoolVar(&noAINotes, "no-ai-notes", false, "disable AI-generated release notes (AI notes are enabled by default)") releaseCreateCmd.Flags().BoolVar(&updateExisting, "update-existing", false, "update existing releases with new AI-generated notes") releaseCreateCmd.Flags().StringVar(&templatePath, "template", "", "custom template for release notes") + releaseCreateCmd.Flags().StringVar(&aiTool, "ai-tool", "claude", "AI tool to use for release notes (claude or aichat)") }
\ No newline at end of file diff --git a/internal/cmd/sync.go b/internal/cmd/sync.go index abedb7e..9fcf0fd 100644 --- a/internal/cmd/sync.go +++ b/internal/cmd/sync.go @@ -14,6 +14,7 @@ var ( noReleases bool autoCreate bool noAIReleaseNotes bool + syncAITool string ) var syncCmd = &cobra.Command{ @@ -39,7 +40,10 @@ var syncRepoCmd = &cobra.Command{ gitsyncer sync repo myproject --dry-run # Sync without AI-generated release notes - gitsyncer sync repo myproject --no-ai-release-notes`, + gitsyncer sync repo myproject --no-ai-release-notes + + # Auto-create releases using aichat for AI notes + gitsyncer sync repo myproject --auto-create-releases --ai-tool aichat`, Run: func(cmd *cobra.Command, args []string) { flags := buildFlags() flags.SyncRepo = args[0] @@ -185,6 +189,7 @@ func init() { syncCmd.PersistentFlags().BoolVar(&noReleases, "no-releases", false, "skip release checking after sync") syncCmd.PersistentFlags().BoolVar(&autoCreate, "auto-create-releases", false, "automatically create releases without confirmation") syncCmd.PersistentFlags().BoolVar(&noAIReleaseNotes, "no-ai-release-notes", false, "disable AI-generated release notes (AI notes are enabled by default)") + syncCmd.PersistentFlags().StringVar(&syncAITool, "ai-tool", "claude", "AI tool to use for release notes when auto-creating (claude or aichat)") } func buildFlags() *cli.Flags { @@ -196,6 +201,7 @@ func buildFlags() *cli.Flags { NoCheckReleases: noReleases, AutoCreateReleases: autoCreate, AIReleaseNotes: !noAIReleaseNotes, + AITool: syncAITool, CreateGitHubRepos: createRepos, CreateCodebergRepos: createRepos, } diff --git a/internal/release/release.go b/internal/release/release.go index ff84f4c..9d82900 100644 --- a/internal/release/release.go +++ b/internal/release/release.go @@ -30,6 +30,7 @@ type Manager struct { workDir string githubToken string codebergToken string + aiTool string } // NewManager creates a new release manager @@ -49,6 +50,11 @@ func (m *Manager) SetCodebergToken(token string) { m.codebergToken = token } +// SetAITool sets the AI tool to use for release notes generation +func (m *Manager) SetAITool(tool string) { + m.aiTool = tool +} + // isVersionTag checks if a tag name is a version tag // Supports formats: vX.Y.Z, vX.Y, vX, X.Y.Z, X.Y, X func isVersionTag(tag string) bool { @@ -327,56 +333,91 @@ func (m *Manager) GenerateAIReleaseNotes(repoPath, repoName, tag string, allTags prompt.WriteString("7. Format using Markdown\n") prompt.WriteString("\nDo not include the version number in the title as it will be added automatically.") - // Run Claude CLI - fmt.Println(" Running Claude CLI command:") - fmt.Println(" claude --model sonnet \"...\"") + // Run AI CLI + fmt.Printf(" Running %s CLI command:\n", m.aiTool) + if m.aiTool == "claude" || m.aiTool == "" { + fmt.Println(" claude --model sonnet \"...\"") + } else if m.aiTool == "aichat" { + fmt.Println(" aichat \"...\"") + } fmt.Printf(" Prompt: Generate release notes for %s %s\n", repoName, tag) fmt.Printf(" Prompt includes: %d commits, %.1fKB of code changes\n", len(commits), float64(len(diff))/1024) fmt.Printf(" Total prompt length: %d characters\n", len(prompt.String())) - // Check if claude CLI is available - if _, err := exec.LookPath("claude"); err != nil { - return "", fmt.Errorf("claude CLI not found in PATH. Please ensure claude CLI is installed and available") + // Determine which AI tool to use (default to claude if not set) + aiTool := m.aiTool + if aiTool == "" { + aiTool = "claude" } - // Skip auth check - it may hang or cause issues - // Users can manually run 'claude auth status' if needed + var cmd *exec.Cmd - cmd := exec.Command("claude", "--model", "sonnet", prompt.String()) - cmd.Env = append(os.Environ(), "CLAUDE_DEBUG=1") // Enable debug mode if supported + switch aiTool { + case "claude": + // Check if claude CLI is available + if _, err := exec.LookPath("claude"); err != nil { + return "", fmt.Errorf("claude CLI not found in PATH. Please ensure claude CLI is installed and available") + } + + // Skip auth check - it may hang or cause issues + // Users can manually run 'claude auth status' if needed + + cmd = exec.Command("claude", "--model", "sonnet", prompt.String()) + cmd.Env = append(os.Environ(), "CLAUDE_DEBUG=1") // Enable debug mode if supported + + case "aichat": + // Check if aichat CLI is available + if _, err := exec.LookPath("aichat"); err != nil { + return "", fmt.Errorf("aichat CLI not found in PATH. Please ensure aichat CLI is installed and available") + } + + // For aichat, we need to pipe the prompt through stdin + cmd = exec.Command("aichat", prompt.String()) + + default: + return "", fmt.Errorf("unsupported AI tool: %s (supported: claude, aichat)", aiTool) + } output, err := cmd.CombinedOutput() // Use CombinedOutput to capture stderr if err != nil { // Check if it's an exit error and print the output if exitErr, ok := err.(*exec.ExitError); ok { - fmt.Printf(" Claude CLI failed with exit code %d\n", exitErr.ExitCode()) + fmt.Printf(" %s CLI failed with exit code %d\n", aiTool, exitErr.ExitCode()) fmt.Printf(" Error output: %s\n", string(output)) // Provide more helpful error messages based on common issues errorMsg := string(output) fmt.Printf(" Raw error output: %q\n", errorMsg) // Show with quotes to see whitespace - if strings.Contains(errorMsg, "Execution error") && len(errorMsg) < 50 { - fmt.Println(" Hint: This generic error often indicates:") - fmt.Println(" - Authentication issues (try: claude auth login)") - fmt.Println(" - Network connectivity problems") - fmt.Println(" - Rate limiting") - fmt.Println(" - Invalid model name (valid: opus, sonnet, haiku)") - fmt.Println(" - Try running manually: claude --model sonnet \"Hello\"") + + if aiTool == "claude" { + if strings.Contains(errorMsg, "Execution error") && len(errorMsg) < 50 { + fmt.Println(" Hint: This generic error often indicates:") + fmt.Println(" - Authentication issues (try: claude auth login)") + fmt.Println(" - Network connectivity problems") + fmt.Println(" - Rate limiting") + fmt.Println(" - Invalid model name (valid: opus, sonnet, haiku)") + fmt.Println(" - Try running manually: claude --model sonnet \"Hello\"") + } + } else if aiTool == "aichat" { + fmt.Println(" Hint: Common aichat issues:") + fmt.Println(" - Check configuration file (usually ~/.config/aichat/config.yaml)") + fmt.Println(" - Ensure API keys are set correctly") + fmt.Println(" - Try running manually: echo \"Hello\" | aichat \"Say hello back\"") } - return "", fmt.Errorf("claude CLI failed: %s", errorMsg) + return "", fmt.Errorf("%s CLI failed: %s", aiTool, errorMsg) } - return "", fmt.Errorf("failed to run claude: %w", err) + return "", fmt.Errorf("failed to run %s: %w", aiTool, err) } releaseNotes := strings.TrimSpace(string(output)) if releaseNotes == "" { - return "", fmt.Errorf("received empty release notes from claude") + return "", fmt.Errorf("received empty release notes from %s", aiTool) } // Check for known error messages in the output if releaseNotes == "Execution error" || strings.HasPrefix(releaseNotes, "Error:") { - return "", fmt.Errorf("claude CLI returned an error: %s", releaseNotes) + return "", fmt.Errorf("%s CLI returned an error: %s", aiTool, releaseNotes) } // Add header |
