summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-07-19 16:45:29 +0300
committerPaul Buetow <paul@buetow.org>2025-07-19 16:45:29 +0300
commit5ca3c39da7854a753d8535465ec42bebfa3fcf8e (patch)
treef0ad03216a47e997eaa9833ca65140d5918eefdd
parent2ca1d94d1c6785a40b722a581a842be6a8741cc6 (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.md3
-rw-r--r--internal/cli/showcase_handler.go5
-rw-r--r--internal/cli/showcase_only_handler.go5
-rw-r--r--internal/cmd/showcase.go12
-rw-r--r--internal/showcase/showcase.go67
-rw-r--r--internal/version/version.go2
-rw-r--r--test-config.json16
7 files changed, 94 insertions, 16 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index c9ab699..9bcd678 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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