summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-21 11:39:27 +0300
committerPaul Buetow <paul@buetow.org>2026-05-21 11:39:27 +0300
commite0648fdc2ed75a09f16e08626f122a5ed4582ad1 (patch)
treefabcceb0d9cc5aeb098eb18062b0cc20423323f5
parent73c6a37ecf0aac04711e5624455743b3493a7ef5 (diff)
feat(ai): update AI tool order and switch to glm-5.1:cloud via ollama launchv0.17.1
- Default AI tool fallback order: opencode -> hexai -> claude -> amp - Switch opencode invocation to: ollama launch opencode --model glm-5.1:cloud -y -- run - Update helptext and comments across all commands
-rw-r--r--internal/cmd/release.go10
-rw-r--r--internal/cmd/showcase.go4
-rw-r--r--internal/cmd/sync.go2
-rw-r--r--internal/release/release.go69
-rw-r--r--internal/showcase/showcase.go49
-rw-r--r--internal/version/version.go2
6 files changed, 65 insertions, 71 deletions
diff --git a/internal/cmd/release.go b/internal/cmd/release.go
index 88ce86b..5216b17 100644
--- a/internal/cmd/release.go
+++ b/internal/cmd/release.go
@@ -19,8 +19,8 @@ var releaseCmd = &cobra.Command{
Use: "release",
Short: "Manage releases across platforms",
Long: `Check for version tags without releases and create them across
-GitHub and Codeberg. Supports AI-generated release notes via amp (stdin pipeline),
-with fallback to hexai or Claude.`,
+GitHub and Codeberg. Supports AI-generated release notes via opencode (launch run with glm-5.1:cloud),
+with fallback to hexai, claude, or amp.`,
}
var releaseCheckCmd = &cobra.Command{
@@ -74,8 +74,8 @@ If no repository is specified, processes all configured repositories.`,
# Create for specific repository without AI
gitsyncer release create myproject --no-ai-notes
- # Use amp for AI release notes
-gitsyncer release create --ai-tool amp`,
+ # Use opencode for AI release notes
+ gitsyncer release create --ai-tool opencode`,
Run: func(cmd *cobra.Command, args []string) {
flags := buildFlags()
flags.CheckReleases = true
@@ -109,5 +109,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", "amp", "AI tool to use for release notes (amp, claude, or hexai; amp is tried first if available)")
+ releaseCreateCmd.Flags().StringVar(&aiTool, "ai-tool", "opencode", "AI tool to use for release notes (opencode, hexai, claude, or amp; opencode is tried first if available)")
}
diff --git a/internal/cmd/showcase.go b/internal/cmd/showcase.go
index 3217318..d6d8faf 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 opencode (local Ollama), with fallback to amp, hexai, and claude.`,
+and code snippets. By default uses opencode (via ollama launch with glm-5.1:cloud), with fallback to hexai, claude, and amp.`,
Example: ` # Generate showcase with cached summaries
gitsyncer showcase
@@ -63,6 +63,6 @@ func init() {
showcaseCmd.Flags().StringVarP(&outputPath, "output", "o", "", "custom output eath (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", "opencode", "AI tool for summaries: opencode, amp, hexai, claude, or claude-code (default tries opencode→amp→hexai→claude)")
+ showcaseCmd.Flags().StringVar(&showcaseAITool, "ai-tool", "opencode", "AI tool for summaries: opencode, hexai, claude, amp, or claude-code (default tries opencode→hexai→claude→amp)")
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 a4fff84..ea4cb8a 100644
--- a/internal/cmd/sync.go
+++ b/internal/cmd/sync.go
@@ -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", "opencode", "AI tool to use for release notes when auto-creating (opencode, amp, claude, or hexai; opencode is tried first if available)")
+ syncCmd.PersistentFlags().StringVar(&syncAITool, "ai-tool", "opencode", "AI tool to use for release notes when auto-creating (opencode, hexai, claude, or amp; 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 0b0bcdf..9ddf563 100644
--- a/internal/release/release.go
+++ b/internal/release/release.go
@@ -431,48 +431,25 @@ func (m *Manager) GenerateAIReleaseNotes(repoPath, repoName, tag string, allTags
var releaseNotes 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())
+ // 0) Try opencode first (glm-5.1:cloud via ollama launch)
+ if _, err := exec.LookPath("ollama"); err == nil {
+ fmt.Println(" Running ollama launch opencode ...")
+ cmd := exec.Command("ollama", "launch", "opencode", "--model", "glm-5.1:cloud", "-y", "--", "run", fullPrompt)
cmd.Stderr = os.Stderr
out, err := cmd.Output()
if err != nil {
- fmt.Printf(" opencode CLI failed: %v\n", err)
+ fmt.Printf("opencode ollama failed: %v\n", err)
} else {
notes := strings.TrimSpace(string(out))
if notes == "" {
- fmt.Println(" opencode returned empty output; will try fallbacks...")
+ fmt.Println(" ollama 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
+ // 1) Try hexai as fallback
if releaseNotes == "" {
if _, err := exec.LookPath("hexai"); err == nil {
fmt.Println(" Running hexai CLI command (stdin payload)...")
@@ -493,14 +470,12 @@ func (m *Manager) GenerateAIReleaseNotes(repoPath, repoName, tag string, allTags
}
}
- if releaseNotes == "" && aiTool == "claude" {
- fmt.Println(" Running claude CLI command...")
- if _, err := exec.LookPath("claude"); err != nil {
- fmt.Println(" claude CLI not found, all fallbacks exhausted")
- } else {
+ // 2) Try claude as fallback
+ if releaseNotes == "" {
+ if _, err := exec.LookPath("claude"); err == nil {
+ fmt.Println(" Running claude CLI command...")
cmd := exec.Command("claude", "--model", "sonnet", fullPrompt)
cmd.Env = append(os.Environ(), "CLAUDE_DEBUG=1")
-
notes, err := m.executeAICommand(cmd, "claude")
if err != nil {
fmt.Printf(" Claude CLI failed: %v\n", err)
@@ -510,8 +485,26 @@ func (m *Manager) GenerateAIReleaseNotes(repoPath, repoName, tag string, allTags
}
}
- if releaseNotes == "" && (aiTool == "opencode" || aiTool == "amp") {
- return "", fmt.Errorf("opencode/amp CLI not found in PATH and fallbacks failed")
+ // 3) 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
+ }
+ }
+ }
}
if releaseNotes == "" {
diff --git a/internal/showcase/showcase.go b/internal/showcase/showcase.go
index c6154f9..ebd48a7 100644
--- a/internal/showcase/showcase.go
+++ b/internal/showcase/showcase.go
@@ -51,7 +51,7 @@ func New(cfg *config.Config, workDir string) *Generator {
return &Generator{
config: cfg,
workDir: workDir,
- aiTool: "opencode", // default to opencode (local Ollama with gpt-oss:120b)
+ aiTool: "opencode", // default to opencode (via ollama launch with glm-5.1:cloud)
}
}
@@ -221,38 +221,39 @@ func findReadmeContent(repoPath string) ([]byte, string, bool) {
func selectSummaryTool(aiTool string) string {
switch aiTool {
case "opencode", "":
- // Default chain: opencode → amp → hexai → claude
- if _, err := exec.LookPath("opencode"); err == nil {
+ // Default chain: opencode (via ollama launch) → hexai → claude → amp
+ if _, err := exec.LookPath("ollama"); 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"
}
- case "amp":
- // Explicit amp: amp → hexai → claude
if _, err := exec.LookPath("amp"); err == nil {
return "amp"
}
+ case "hexai":
+ // Explicit hexai: hexai → claude → amp
if _, err := exec.LookPath("hexai"); err == nil {
return "hexai"
}
if _, err := exec.LookPath("claude"); err == nil {
return "claude"
}
+ if _, err := exec.LookPath("amp"); err == nil {
+ return "amp"
+ }
case "claude", "claude-code":
+ // Explicit claude: claude → amp
if _, err := exec.LookPath("claude"); err == nil {
return "claude"
}
- if _, err := exec.LookPath("hexai"); err == nil {
- return "hexai"
+ if _, err := exec.LookPath("amp"); err == nil {
+ return "amp"
}
- case "hexai":
+ case "amp":
if _, err := exec.LookPath(aiTool); err == nil {
return aiTool
}
@@ -266,31 +267,31 @@ func runSummaryTool(selectedTool, prompt, repoPath, readmeFile string, readmeCon
switch selectedTool {
case "opencode":
- fmt.Printf("Running opencode command (stdin payload)\n")
+ fmt.Printf("Running ollama launch opencode command\n")
if readmeFound {
- fmt.Printf(" echo <README content> | opencode run --model ollama/gpt-oss:120b \"%s\"\n", prompt)
+ fullPrompt := prompt + "\n\nREADME content:\n" + string(readmeContent)
+ fmt.Printf(" ollama launch opencode --model glm-5.1:cloud -y -- run \"...\"\n")
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))
+ cmd = exec.Command("ollama", "launch", "opencode", "--model", "glm-5.1:cloud", "-y", "--", "run", fullPrompt)
}
- case "amp":
- fmt.Printf("Running amp command (stdin payload)\n")
+ case "hexai":
+ fmt.Printf("Running hexai command (stdin payload)\n")
if readmeFound {
- fmt.Printf(" echo <README content> | amp --execute \"%s\"\n", prompt)
+ fmt.Printf(" echo <README content> | hexai \"%s\"\n", prompt)
fmt.Printf(" Using %s as input\n", readmeFile)
- cmd = exec.Command("amp", "--execute", prompt)
+ cmd = exec.Command("hexai", prompt)
cmd.Stdin = strings.NewReader(string(readmeContent))
}
case "claude":
fmt.Printf("Running Claude command:\n")
fmt.Printf(" claude --model sonnet \"%s\"\n", prompt)
cmd = exec.Command("claude", "--model", "sonnet", prompt)
- case "hexai":
- fmt.Printf("Running hexai command (stdin payload)\n")
+ case "amp":
+ fmt.Printf("Running amp command (stdin payload)\n")
if readmeFound {
- fmt.Printf(" echo <README content> | hexai \"%s\"\n", prompt)
+ fmt.Printf(" echo <README content> | amp --execute \"%s\"\n", prompt)
fmt.Printf(" Using %s as input\n", readmeFile)
- cmd = exec.Command("hexai", prompt)
+ cmd = exec.Command("amp", "--execute", prompt)
cmd.Stdin = strings.NewReader(string(readmeContent))
}
}
@@ -788,7 +789,7 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool
}
// Determine which AI tool to use (only if we need to run it)
- // Prefer amp if available when default tool is "" (aligns with release flow)
+ // Prefer opencode if available when default tool is "" (aligns with release flow)
selectedTool := g.aiTool
if !haveCachedSummary {
selectedTool = selectSummaryTool(g.aiTool)
diff --git a/internal/version/version.go b/internal/version/version.go
index 189f928..2516192 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.17.0"
+ Version = "0.17.1"
// GitCommit is the git commit hash at build time
GitCommit = "unknown"