diff options
| author | Paul Buetow <paul@buetow.org> | 2025-07-19 16:45:29 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-07-19 16:45:29 +0300 |
| commit | 5ca3c39da7854a753d8535465ec42bebfa3fcf8e (patch) | |
| tree | f0ad03216a47e997eaa9833ca65140d5918eefdd | |
| parent | 2ca1d94d1c6785a40b722a581a842be6a8741cc6 (diff) | |
feat: add aichat support for showcase project descriptionsv0.8.1
- Add --ai-tool flag to showcase command (default: claude)
- Support aichat as alternative to claude for generating project summaries
- When using aichat, read README.md and pipe it as input
- Update documentation and examples
- Bump version to 0.8.1
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
| -rw-r--r-- | CLAUDE.md | 3 | ||||
| -rw-r--r-- | internal/cli/showcase_handler.go | 5 | ||||
| -rw-r--r-- | internal/cli/showcase_only_handler.go | 5 | ||||
| -rw-r--r-- | internal/cmd/showcase.go | 12 | ||||
| -rw-r--r-- | internal/showcase/showcase.go | 67 | ||||
| -rw-r--r-- | internal/version/version.go | 2 | ||||
| -rw-r--r-- | test-config.json | 16 |
7 files changed, 94 insertions, 16 deletions
@@ -49,6 +49,9 @@ gitsyncer release create --auto --no-ai-notes # Use aichat instead of claude for AI release notes gitsyncer release create --auto --ai-tool aichat + +# Generate showcase using aichat for project descriptions +gitsyncer showcase --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/internal/cli/showcase_handler.go b/internal/cli/showcase_handler.go index a3dfd26..929ce95 100644 --- a/internal/cli/showcase_handler.go +++ b/internal/cli/showcase_handler.go @@ -24,6 +24,11 @@ func HandleShowcase(cfg *config.Config, flags *Flags) int { // Create showcase generator generator := showcase.New(cfg, flags.WorkDir) + // Set AI tool if specified + if flags.AITool != "" { + generator.SetAITool(flags.AITool) + } + // Generate showcase with optional filter if err := generator.GenerateShowcase(repoFilter, flags.Force); err != nil { log.Printf("ERROR: Failed to generate showcase: %v\n", err) diff --git a/internal/cli/showcase_only_handler.go b/internal/cli/showcase_only_handler.go index ff2a82a..f777729 100644 --- a/internal/cli/showcase_only_handler.go +++ b/internal/cli/showcase_only_handler.go @@ -45,6 +45,11 @@ func HandleShowcaseOnly(cfg *config.Config, flags *Flags) int { fmt.Println("\nGenerating showcase for all repositories...") generator := showcase.New(cfg, flags.WorkDir) + // Set AI tool if specified + if flags.AITool != "" { + generator.SetAITool(flags.AITool) + } + // Pass empty filter to process all repos if err := generator.GenerateShowcase(nil, flags.Force); err != nil { log.Printf("ERROR: Failed to generate showcase: %v\n", err) diff --git a/internal/cmd/showcase.go b/internal/cmd/showcase.go index e184700..802cf10 100644 --- a/internal/cmd/showcase.go +++ b/internal/cmd/showcase.go @@ -13,14 +13,15 @@ var ( outputPath string outputFormat string excludePattern string + showcaseAITool string ) var showcaseCmd = &cobra.Command{ Use: "showcase", Short: "Generate AI-powered project showcase", - Long: `Generate a comprehensive showcase of all your projects using Claude AI. + Long: `Generate a comprehensive showcase of all your projects using AI. This feature creates a formatted document with project summaries, statistics, -and code snippets.`, +and code snippets. By default uses Claude, but can also use aichat.`, Example: ` # Generate showcase with cached summaries gitsyncer showcase @@ -34,11 +35,15 @@ and code snippets.`, gitsyncer showcase --format markdown # Exclude certain repositories - gitsyncer showcase --exclude "test-.*"`, + gitsyncer showcase --exclude "test-.*" + + # Use aichat instead of claude for AI summaries + gitsyncer showcase --ai-tool aichat`, Run: func(cmd *cobra.Command, args []string) { flags := buildFlags() flags.Showcase = true flags.Force = forceRegenerate + flags.AITool = showcaseAITool fmt.Println("Running showcase generation for all repositories...") exitCode := cli.HandleShowcaseOnly(cfg, flags) @@ -54,4 +59,5 @@ func init() { showcaseCmd.Flags().StringVarP(&outputPath, "output", "o", "", "custom output path (default: ~/git/foo.zone-content/gemtext/about/showcase.gmi.tpl)") showcaseCmd.Flags().StringVar(&outputFormat, "format", "gemtext", "output format: gemtext, markdown, html") showcaseCmd.Flags().StringVar(&excludePattern, "exclude", "", "exclude repos matching pattern") + showcaseCmd.Flags().StringVar(&showcaseAITool, "ai-tool", "claude", "AI tool to use for project summaries (claude or aichat)") }
\ No newline at end of file diff --git a/internal/showcase/showcase.go b/internal/showcase/showcase.go index 8c49b54..815614c 100644 --- a/internal/showcase/showcase.go +++ b/internal/showcase/showcase.go @@ -17,6 +17,7 @@ import ( type Generator struct { config *config.Config workDir string + aiTool string } // ProjectSummary holds the summary information for a project @@ -49,9 +50,15 @@ func New(cfg *config.Config, workDir string) *Generator { return &Generator{ config: cfg, workDir: workDir, + aiTool: "claude", // default to claude } } +// SetAITool sets the AI tool to use for generating summaries +func (g *Generator) SetAITool(tool string) { + g.aiTool = tool +} + // GenerateShowcase generates a showcase for repositories // If repoFilter is provided, only those repositories are processed // If repoFilter is empty/nil, all repositories in work directory are processed @@ -184,16 +191,16 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool var haveCachedSummary bool if !forceRegenerate { if cached, err := g.loadFromCache(cacheFile); err == nil { - fmt.Printf("Using cached Claude summary (cache file: %s)\n", cacheFile) + fmt.Printf("Using cached AI summary (cache file: %s)\n", cacheFile) cachedSummary = cached.Summary haveCachedSummary = true } } - // Check if claude command exists (only if we need to run it) + // Check if AI tool command exists (only if we need to run it) if !haveCachedSummary { - if _, err := exec.LookPath("claude"); err != nil { - return nil, fmt.Errorf("claude command not found. Please install Claude CLI") + if _, err := exec.LookPath(g.aiTool); err != nil { + return nil, fmt.Errorf("%s command not found. Please install %s CLI", g.aiTool, g.aiTool) } } @@ -216,27 +223,63 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool // Continue anyway with partial metadata } - // Get the summary - either from cache or by running Claude + // Get the summary - either from cache or by running AI tool var summary string if haveCachedSummary { summary = cachedSummary - fmt.Printf("Using cached Claude summary\n") + fmt.Printf("Using cached AI summary\n") } else { - // Run claude command prompt := "Please provide a 1-2 paragraph summary of this project, explaining what it does, why it's useful, and how it's implemented. Focus on the key features and architecture. Be concise but informative." - fmt.Printf("Running Claude command:\n") - fmt.Printf(" claude --model sonnet \"%s\"\n", prompt) + var cmd *exec.Cmd + + switch g.aiTool { + case "claude": + fmt.Printf("Running Claude command:\n") + fmt.Printf(" claude --model sonnet \"%s\"\n", prompt) + cmd = exec.Command("claude", "--model", "sonnet", prompt) + case "aichat": + // For aichat, we need to read README.md and pipe it to aichat + fmt.Printf("Running aichat command:\n") + + // Find README file + readmeFiles := []string{ + "README.md", "readme.md", "Readme.md", + "README.MD", "README.txt", "readme.txt", + "README", "readme", + } + + var readmeContent []byte + var readmeFound bool + for _, readmeFile := range readmeFiles { + content, err := os.ReadFile(readmeFile) + if err == nil { + readmeContent = content + readmeFound = true + fmt.Printf(" Using %s as input\n", readmeFile) + break + } + } + + if !readmeFound { + return nil, fmt.Errorf("no README file found for aichat input") + } + + fmt.Printf(" echo <README content> | aichat \"%s\"\n", prompt) + cmd = exec.Command("aichat", prompt) + cmd.Stdin = strings.NewReader(string(readmeContent)) + default: + return nil, fmt.Errorf("unsupported AI tool: %s", g.aiTool) + } - cmd := exec.Command("claude", "--model", "sonnet", prompt) output, err := cmd.Output() if err != nil { - return nil, fmt.Errorf("failed to run claude: %w", err) + return nil, fmt.Errorf("failed to run %s: %w", g.aiTool, err) } summary = strings.TrimSpace(string(output)) if summary == "" { - return nil, fmt.Errorf("received empty summary from claude") + return nil, fmt.Errorf("received empty summary from %s", g.aiTool) } } diff --git a/internal/version/version.go b/internal/version/version.go index b45fdc1..b31f9ec 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -7,7 +7,7 @@ import ( var ( // Version is the current version of gitsyncer - Version = "0.8.0" + Version = "0.8.1" // GitCommit is the git commit hash at build time GitCommit = "unknown" diff --git a/test-config.json b/test-config.json new file mode 100644 index 0000000..28dbfb9 --- /dev/null +++ b/test-config.json @@ -0,0 +1,16 @@ +{ + "organizations": [ + { + "host": "github.com", + "name": "test-org", + "github_token": "test-token" + }, + { + "host": "codeberg.org", + "name": "test-org", + "codeberg_token": "test-token" + } + ], + "repositories": ["test-repo"], + "work_dir": "/tmp/gitsyncer-test" +}
\ No newline at end of file |
