summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-07-19 16:29:06 +0300
committerPaul Buetow <paul@buetow.org>2025-07-19 16:29:06 +0300
commit2ca1d94d1c6785a40b722a581a842be6a8741cc6 (patch)
tree4f5ee4a0a3f6f320b2f6b2ea08792f8fafece482
parente23fc252fbac2aba69f1f1268af9425af4d43d19 (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.md3
-rw-r--r--README.md3
-rw-r--r--internal/cli/flags.go1
-rw-r--r--internal/cli/release.go1
-rw-r--r--internal/cmd/release.go8
-rw-r--r--internal/cmd/sync.go8
-rw-r--r--internal/release/release.go85
7 files changed, 85 insertions, 24 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index c628c20..c9ab699 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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.
diff --git a/README.md b/README.md
index 88b6f89..f56554b 100644
--- a/README.md
+++ b/README.md
@@ -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