summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/cli/flags.go4
-rw-r--r--internal/cmd/showcase.go6
-rw-r--r--internal/cmd/sync.go6
-rw-r--r--internal/release/release.go43
-rw-r--r--internal/showcase/showcase.go30
-rw-r--r--internal/version/version.go2
6 files changed, 69 insertions, 22 deletions
diff --git a/internal/cli/flags.go b/internal/cli/flags.go
index 8c69797..4e5e617 100644
--- a/internal/cli/flags.go
+++ b/internal/cli/flags.go
@@ -66,13 +66,13 @@ func ParseFlags() *Flags {
flag.BoolVar(&f.Clean, "clean", false, "delete all repositories in work directory (with confirmation)")
flag.StringVar(&f.DeleteRepo, "delete-repo", "", "delete specified repository from all configured organizations (with confirmation)")
flag.BoolVar(&f.Backup, "backup", false, "enable syncing to backup locations")
- flag.BoolVar(&f.Showcase, "showcase", false, "generate project showcase using AI (amp by default) after syncing")
+ flag.BoolVar(&f.Showcase, "showcase", false, "generate project showcase using AI (opencode by default) after syncing")
flag.BoolVar(&f.Force, "force", false, "force operations even when cache or sync interval checks would skip work")
flag.BoolVar(&f.BatchRun, "batch-run", false, "enable --full and --showcase (runs only once per week)")
flag.BoolVar(&f.CheckReleases, "check-releases", false, "manually check for version tags without releases and create them (with confirmation)")
flag.BoolVar(&f.NoCheckReleases, "no-check-releases", false, "disable automatic release checking after sync operations")
flag.BoolVar(&f.AutoCreateReleases, "auto-create-releases", false, "automatically create releases without confirmation prompts")
- flag.BoolVar(&f.AIReleaseNotes, "ai-release-notes", false, "generate release notes using AI (amp by default) based on git diff")
+ flag.BoolVar(&f.AIReleaseNotes, "ai-release-notes", false, "generate release notes using AI (opencode by default) based on git diff")
flag.BoolVar(&f.UpdateReleases, "update-releases", false, "update existing releases with new AI-generated notes")
flag.BoolVar(&f.Throttle, "throttle", false, "enable throttled syncing based on local activity")
diff --git a/internal/cmd/showcase.go b/internal/cmd/showcase.go
index a590756..c48dee1 100644
--- a/internal/cmd/showcase.go
+++ b/internal/cmd/showcase.go
@@ -22,7 +22,7 @@ var showcaseCmd = &cobra.Command{
Short: "Generate AI-powered project showcase",
Long: `Generate a comprehensive showcase of all your projects using AI.
This feature creates a formatted document with project summaries, statistics,
-and code snippets. By default uses amp, with fallback to hexai, claude, and aichat.`,
+and code snippets. By default uses opencode (local Ollama), with fallback to amp, hexai, claude, and aichat.`,
Example: ` # Generate showcase with cached summaries
gitsyncer showcase
@@ -39,7 +39,7 @@ and code snippets. By default uses amp, with fallback to hexai, claude, and aich
gitsyncer showcase --exclude "test-.*"
# Use a specific AI tool
- gitsyncer showcase --ai-tool amp`,
+ gitsyncer showcase --ai-tool opencode`,
Run: func(cmd *cobra.Command, args []string) {
flags := buildFlags()
flags.Showcase = true
@@ -63,6 +63,6 @@ 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", "amp", "AI tool for summaries: amp, hexai, claude, claude-code, or aichat (default tries amp→hexai→claude→aichat)")
+ showcaseCmd.Flags().StringVar(&showcaseAITool, "ai-tool", "opencode", "AI tool for summaries: opencode, amp, hexai, claude, claude-code, or aichat (default tries opencode→amp→hexai→claude→aichat)")
showcaseCmd.Flags().StringVar(&showcaseRepo, "repo", "", "only generate showcase for a single repository")
}
diff --git a/internal/cmd/sync.go b/internal/cmd/sync.go
index 5681ccc..75d8c88 100644
--- a/internal/cmd/sync.go
+++ b/internal/cmd/sync.go
@@ -47,8 +47,8 @@ var syncRepoCmd = &cobra.Command{
# Sync without AI-generated release notes
gitsyncer sync repo myproject --no-ai-release-notes
- # Auto-create releases using amp for AI notes
-gitsyncer sync repo myproject --auto-create-releases --ai-tool amp`,
+ # Auto-create releases using opencode for AI notes (default)
+gitsyncer sync repo myproject --auto-create-releases --ai-tool opencode`,
Run: func(cmd *cobra.Command, args []string) {
flags := buildFlags()
flags.SyncRepo = args[0]
@@ -194,7 +194,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", "amp", "AI tool to use for release notes when auto-creating (amp, claude, aichat, or hexai; amp is tried first if available)")
+ syncCmd.PersistentFlags().StringVar(&syncAITool, "ai-tool", "opencode", "AI tool to use for release notes when auto-creating (opencode, amp, claude, aichat, or hexai; opencode is tried first if available)")
syncCmd.PersistentFlags().BoolVarP(&syncForce, "force", "f", false, "force sync even if normal sync interval checks would skip a repository")
syncCmd.PersistentFlags().BoolVar(&throttle, "throttle", false, "throttle syncing based on local repo activity")
}
diff --git a/internal/release/release.go b/internal/release/release.go
index 6d6091f..4ac134a 100644
--- a/internal/release/release.go
+++ b/internal/release/release.go
@@ -420,10 +420,10 @@ func (m *Manager) GenerateAIReleaseNotes(repoPath, repoName, tag string, allTags
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(instr.String())+len(input.String()))
- // Determine which AI tool to use (default to amp if not set)
+ // Determine which AI tool to use (default to opencode if not set)
aiTool := m.aiTool
if aiTool == "" {
- aiTool = "amp"
+ aiTool = "opencode"
}
// Build a full prompt string for tools that read a single argument
@@ -431,26 +431,47 @@ func (m *Manager) GenerateAIReleaseNotes(repoPath, repoName, tag string, allTags
var releaseNotes string
- // 1) Try amp first: echo input to stdin and pass instructions as argument
- // Note: print stderr to console, but only use stdout for notes
- if _, err := exec.LookPath("amp"); err == nil {
- fmt.Println(" Running amp CLI command (stdin payload)...")
- cmd := exec.Command("amp", "--execute", instr.String())
+ // 0) Try opencode first (local Ollama with gpt-oss:120b)
+ if _, err := exec.LookPath("opencode"); err == nil {
+ fmt.Println(" Running opencode CLI command (stdin payload)...")
+ cmd := exec.Command("opencode", "run", "--model", "ollama/gpt-oss:120b", instr.String())
cmd.Stdin = strings.NewReader(input.String())
cmd.Stderr = os.Stderr
out, err := cmd.Output()
if err != nil {
- fmt.Printf(" amp CLI failed: %v\n", err)
+ fmt.Printf(" opencode CLI failed: %v\n", err)
} else {
notes := strings.TrimSpace(string(out))
if notes == "" {
- fmt.Println(" amp returned empty output; will try fallbacks...")
+ fmt.Println(" opencode returned empty output; will try fallbacks...")
} else {
releaseNotes = notes
}
}
}
+ // 1) Try amp as fallback: echo input to stdin and pass instructions as argument
+ // Note: print stderr to console, but only use stdout for notes
+ if releaseNotes == "" {
+ if _, err := exec.LookPath("amp"); err == nil {
+ fmt.Println(" Running amp CLI command (stdin payload)...")
+ cmd := exec.Command("amp", "--execute", instr.String())
+ cmd.Stdin = strings.NewReader(input.String())
+ cmd.Stderr = os.Stderr
+ out, err := cmd.Output()
+ if err != nil {
+ fmt.Printf(" amp CLI failed: %v\n", err)
+ } else {
+ notes := strings.TrimSpace(string(out))
+ if notes == "" {
+ fmt.Println(" amp returned empty output; will try fallbacks...")
+ } else {
+ releaseNotes = notes
+ }
+ }
+ }
+ }
+
// 2) Try hexai as fallback
if releaseNotes == "" {
if _, err := exec.LookPath("hexai"); err == nil {
@@ -506,8 +527,8 @@ func (m *Manager) GenerateAIReleaseNotes(repoPath, repoName, tag string, allTags
releaseNotes = notes
}
- if releaseNotes == "" && aiTool == "amp" {
- return "", fmt.Errorf("amp CLI not found in PATH and fallbacks failed")
+ if releaseNotes == "" && (aiTool == "opencode" || aiTool == "amp") {
+ return "", fmt.Errorf("opencode/amp CLI not found in PATH and fallbacks failed")
}
if releaseNotes == "" {
diff --git a/internal/showcase/showcase.go b/internal/showcase/showcase.go
index dd5fc9c..24662e9 100644
--- a/internal/showcase/showcase.go
+++ b/internal/showcase/showcase.go
@@ -50,7 +50,7 @@ func New(cfg *config.Config, workDir string) *Generator {
return &Generator{
config: cfg,
workDir: workDir,
- aiTool: "amp", // default to amp
+ aiTool: "opencode", // default to opencode (local Ollama with gpt-oss:120b)
}
}
@@ -215,7 +215,25 @@ func findReadmeContent(repoPath string) ([]byte, string, bool) {
func selectSummaryTool(aiTool string) string {
switch aiTool {
- case "amp", "":
+ case "opencode", "":
+ // Default chain: opencode → amp → hexai → claude → aichat
+ if _, err := exec.LookPath("opencode"); err == nil {
+ return "opencode"
+ }
+ if _, err := exec.LookPath("amp"); err == nil {
+ return "amp"
+ }
+ if _, err := exec.LookPath("hexai"); err == nil {
+ return "hexai"
+ }
+ if _, err := exec.LookPath("claude"); err == nil {
+ return "claude"
+ }
+ if _, err := exec.LookPath("aichat"); err == nil {
+ return "aichat"
+ }
+ case "amp":
+ // Explicit amp: amp → hexai → claude → aichat
if _, err := exec.LookPath("amp"); err == nil {
return "amp"
}
@@ -251,6 +269,14 @@ func runSummaryTool(selectedTool, prompt, repoPath, readmeFile string, readmeCon
var cmd *exec.Cmd
switch selectedTool {
+ case "opencode":
+ fmt.Printf("Running opencode command (stdin payload)\n")
+ if readmeFound {
+ fmt.Printf(" echo <README content> | opencode run --model ollama/gpt-oss:120b \"%s\"\n", prompt)
+ fmt.Printf(" Using %s as input\n", readmeFile)
+ cmd = exec.Command("opencode", "run", "--model", "ollama/gpt-oss:120b", prompt)
+ cmd.Stdin = strings.NewReader(string(readmeContent))
+ }
case "amp":
fmt.Printf("Running amp command (stdin payload)\n")
if readmeFound {
diff --git a/internal/version/version.go b/internal/version/version.go
index c4f6082..15929ce 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.15.5"
+ Version = "0.15.6"
// GitCommit is the git commit hash at build time
GitCommit = "unknown"