diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..974b0c9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,22 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.26.x" + check-latest: true + - name: Build + run: go build ./... + - name: Vet + run: go vet ./... + - name: Test + run: go test ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f0203ae --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,103 @@ +name: Release + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: "Tag to (re)build and attach assets to (e.g. v2.1.0)" + required: true + +permissions: + contents: write + +jobs: + build: + name: Build ${{ matrix.goos }}/${{ matrix.goarch }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - goos: linux + goarch: amd64 + ext: "" + - goos: linux + goarch: arm64 + ext: "" + - goos: darwin + goarch: amd64 + ext: "" + - goos: darwin + goarch: arm64 + ext: "" + - goos: windows + goarch: amd64 + ext: ".exe" + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.26.x" + check-latest: true + + - name: Resolve version + id: ver + run: | + TAG="${{ github.event.inputs.tag || github.ref_name }}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" + echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" + + - name: Build + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: 0 + run: | + OUT="cue-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }}" + go build -trimpath \ + -ldflags "-s -w -X main.Version=${{ steps.ver.outputs.version }} -X main.BuildDate=${{ steps.ver.outputs.build_date }}" \ + -o "dist/${OUT}" . + (cd dist && sha256sum "${OUT}" > "${OUT}.sha256") + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: cue-${{ matrix.goos }}-${{ matrix.goarch }} + path: dist/* + if-no-files-found: error + + publish: + name: Publish release assets + needs: build + runs-on: ubuntu-latest + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Resolve tag + id: ver + run: | + TAG="${{ github.event.inputs.tag || github.ref_name }}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + + - name: Upload to GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.ver.outputs.tag }}" + if ! gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TAG" --generate-notes + fi + gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber dist/* diff --git a/README.md b/README.md index e8de0c4..d933c5e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@
-

πŸ“Ί Welcome to v2.0

+

πŸ“Ί Welcome to v2.2

Onboarding Demo

πŸͺ Environment Store Management

@@ -56,14 +56,23 @@ Modern application development requires immense cognitive overhead. Should you u The CLI distributes as a self-contained, statically linked fat binary. No complex Node/Python/Ruby dependencies are strictly required to run it!
-🐧 Linux / macOS +🐧 Linux / macOS (incl. Arch Linux support)
+**One-liner install (Ubuntu / Debian / Arch / macOS):** ```bash curl -fsSL https://raw.githubusercontent.com/GyaneshSamanta/cue/main/scripts/install.sh | bash ``` -> **Note for Arch Linux Users:** The `install.sh` script is fully compatible with Arch Linux. For optimal compatibility with future AUR packaging and dependencies, ensure you have the base development tools installed via `sudo pacman -S base-devel`. +To pin a specific version: +```bash +curl -fsSL https://raw.githubusercontent.com/GyaneshSamanta/cue/main/scripts/install.sh | CUE_VERSION=v2.1.0 bash +``` + +**Arch Linux specifics:** The `install.sh` script works directly on Arch. We recommend having `base-devel`, `curl`, and `sudo` ready (`sudo pacman -S --needed base-devel curl`). + +> [!TIP] +> **AUR packaging:** AUR support (`cue-bin`) is currently in development. For now, the direct install is the recommended path.
@@ -75,6 +84,25 @@ curl -fsSL https://raw.githubusercontent.com/GyaneshSamanta/cue/main/scripts/ins iwr https://raw.githubusercontent.com/GyaneshSamanta/cue/main/scripts/install.ps1 -useb | iex ``` +To pin a specific version: +```powershell +$env:CUE_VERSION = 'v2.1.0'; iwr https://raw.githubusercontent.com/GyaneshSamanta/cue/main/scripts/install.ps1 -useb | iex +``` + + + +
+πŸ›  Build from source +
+ +Requires Go 1.26+. + +```bash +git clone https://github.com/GyaneshSamanta/cue && cd cue +go build -o cue . +./cue --version +``` +

@@ -118,6 +146,33 @@ During installation, it offers multiple execution engines: --- +## 🧠 Local Models (Ollama, Gemma & friends) + +Run open-weight models on your own machine, then point Claude Code at them. Cue takes care of pulling, switching, and benchmarking. + +```bash +cue model recommend # hardware-aware suggestions for your laptop +cue model gemma # one-shot install of Google Gemma 3 (4B default) +cue model gemma 12b # bigger Gemma for stronger quality +cue model pull qwen2.5-coder:7b +cue model use gemma3:4b # set as the default for Claude Code +cue model list # see what's installed +cue model benchmark gemma3:4b # quick speed test +``` + +**Gemma sizes** at a glance: + +| Tag | Size | Best for | +| :--- | :--- | :--- | +| `gemma3:1b` | ~0.8 GB | Tiny laptops, instant load | +| `gemma3:4b` | ~3.3 GB | Default β€” strong general use on 8 GB+ RAM | +| `gemma3:12b` | ~8.1 GB | High quality on 16 GB RAM / 12 GB VRAM | +| `gemma3:27b` | ~17 GB | Best local quality on 32 GB RAM / 24 GB VRAM | + +> Cue requires [Ollama](https://ollama.com/download) for local models. Run `cue model gemma` and Cue will tell you exactly how to install it if it's missing. + +--- + ## ⚑ The Ultimate Macro Glossary Macros encapsulate best-practices and safety constraints into readable verbs. Forget raw terminal commandsβ€”use our semantic shortcuts. @@ -167,6 +222,27 @@ ui.HandleError(se) --- +## πŸ“‹ Changelog + +### v2.2.0 β€” May 2026 + +**New** +- **`cue model` command** β€” the `internal/model` package was wired up and is now a first-class CLI feature. Run `cue model --help` to see all subcommands. +- **Google Gemma 3 support** β€” `cue model gemma [1b|4b|12b|27b]` installs Google Gemma via Ollama in one command. Hardware-aware `cue model recommend` now includes all four Gemma sizes. +- **`cue toolkit` command** β€” install, upgrade, remove, and inspect developer tools with automatic version-manager bootstrapping (e.g. `cue toolkit install node`). Closes the loop from `cue doctor fix --all`, which had been suggesting `cue toolkit install ` when the command didn't yet exist. +- **Release pipeline** β€” added `.github/workflows/release.yml` that cross-compiles for linux/darwin/windows Γ— amd64/arm64 and uploads binaries to GitHub Releases on every version tag. **This is what was causing the broken `install.sh` / `install.ps1`** β€” prior releases had no binary assets. +- **CI pipeline** β€” added `.github/workflows/ci.yml` for build / vet / test checks on every PR. + +**Fixed** +- `install.sh` and `install.ps1` now fail with a clear actionable error if no release is found, instead of silently downloading nothing. +- Both scripts accept a `CUE_VERSION=vX.Y.Z` / `$env:CUE_VERSION` env var to pin to a specific release. +- `cue model list/pull/gemma` now prints a friendly install hint (brew / curl / winget) if Ollama is missing, instead of an opaque exec error. + +**Improved** +- README install section: explicit Arch Linux guidance, versioned-install snippets, build-from-source instructions. + +--- +

Built with ❀️ by Gyanesh

diff --git a/cmd/model.go b/cmd/model.go new file mode 100644 index 0000000..7e5a932 --- /dev/null +++ b/cmd/model.go @@ -0,0 +1,119 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/GyaneshSamanta/cue/internal/model" + "github.com/GyaneshSamanta/cue/internal/ui" +) + +var modelCmd = &cobra.Command{ + Use: "model", + Short: "Manage local AI models (Ollama, Gemma, etc.)", + Long: `Manage local AI models served by Ollama, including Google Gemma. + +Cue treats Ollama as the canonical local-model runtime. Use this command +to pull models, set the active model for Claude Code, benchmark inference +speed, or get hardware-aware recommendations (including Gemma sizes).`, +} + +var modelListCmd = &cobra.Command{ + Use: "list", + Short: "List installed local models", + RunE: func(cmd *cobra.Command, args []string) error { + return model.ListModels() + }, +} + +var modelPullCmd = &cobra.Command{ + Use: "pull ", + Short: "Download a model (e.g. gemma3:4b, llama3.1:8b)", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return model.PullModel(args[0]) + }, +} + +var modelRemoveCmd = &cobra.Command{ + Use: "remove ", + Short: "Remove a downloaded model", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return model.RemoveModel(args[0]) + }, +} + +var modelUseCmd = &cobra.Command{ + Use: "use ", + Short: "Set the default model used by Claude Code", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return model.UseModel(args[0]) + }, +} + +var modelRecommendCmd = &cobra.Command{ + Use: "recommend", + Short: "Suggest models based on detected hardware", + RunE: func(cmd *cobra.Command, args []string) error { + return model.Recommend() + }, +} + +var modelBenchmarkCmd = &cobra.Command{ + Use: "benchmark [name]", + Short: "Run a quick inference speed test", + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + name := "" + if len(args) == 1 { + name = args[0] + } + return model.Benchmark(name) + }, +} + +var modelSearchCmd = &cobra.Command{ + Use: "search ", + Short: "Search the Ollama registry", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return model.Search(args[0]) + }, +} + +var modelGemmaCmd = &cobra.Command{ + Use: "gemma [size]", + Short: "Quick-install Google Gemma (sizes: 1b, 4b, 12b, 27b β€” default 4b)", + Long: `Quick-install Google Gemma 3 via Ollama. + +Examples: + cue model gemma # installs gemma3:4b (good default for 16GB RAM) + cue model gemma 1b # tiny β€” fits low-RAM laptops + cue model gemma 12b # quality β€” needs ~16GB RAM or 12GB VRAM + cue model gemma 27b # best β€” needs ~32GB RAM or 24GB VRAM + +After install, run 'cue model use gemma3:' to make it the default +for Claude Code.`, + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + size := "4b" + if len(args) == 1 { + size = args[0] + } + valid := map[string]bool{"1b": true, "4b": true, "12b": true, "27b": true} + if !valid[size] { + ui.PrintWarning("Unknown Gemma size '" + size + "'. Supported: 1b, 4b, 12b, 27b. Proceeding anyway.") + } + tag := "gemma3:" + size + if err := model.EnsureOllama(); err != nil { + return err + } + return model.PullModel(tag) + }, +} + +func init() { + modelCmd.AddCommand(modelListCmd, modelPullCmd, modelRemoveCmd, modelUseCmd, + modelRecommendCmd, modelBenchmarkCmd, modelSearchCmd, modelGemmaCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 471be3a..23112e5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -120,6 +120,7 @@ func init() { rootCmd.AddCommand(updateCmd) rootCmd.AddCommand(versionMgrCmd) rootCmd.AddCommand(doctorCmd) + rootCmd.AddCommand(modelCmd) } // Execute runs the root command. diff --git a/internal/model/manager.go b/internal/model/manager.go index 513884b..f196eca 100644 --- a/internal/model/manager.go +++ b/internal/model/manager.go @@ -12,8 +12,25 @@ import ( "github.com/GyaneshSamanta/cue/internal/ui" ) +// EnsureOllama verifies that the Ollama runtime is installed and reachable. +// It returns a helpful, actionable error if not β€” rather than letting an +// opaque exec failure bubble up. +func EnsureOllama() error { + if _, err := exec.LookPath("ollama"); err != nil { + ui.PrintWarning("Ollama is not installed. Install it from https://ollama.com/download") + ui.PrintDim(" macOS: brew install ollama") + ui.PrintDim(" Linux: curl -fsSL https://ollama.com/install.sh | sh") + ui.PrintDim(" Windows: winget install Ollama.Ollama") + return fmt.Errorf("ollama not found in PATH") + } + return nil +} + // ListModels shows all Ollama models. func ListModels() error { + if err := EnsureOllama(); err != nil { + return err + } out, err := exec.Command("ollama", "list").CombinedOutput() if err != nil { return fmt.Errorf("ollama not running or not installed: %w", err) @@ -82,6 +99,10 @@ func Recommend() error { {"Reasoning", "deepseek-r1:8b", "5.2 GB", "Strong multi-step reasoning", ramGB >= 12}, {"Reasoning (large)", "deepseek-r1:14b", "9.1 GB", "Best reasoning under 15B", ramGB >= 24}, {"General", "llama3.1:8b", "4.7 GB", "Versatile general model", ramGB >= 12}, + {"Gemma (tiny)", "gemma3:1b", "0.8 GB", "Google Gemma 3 β€” runs anywhere", true}, + {"Gemma (default)", "gemma3:4b", "3.3 GB", "Google Gemma 3 β€” strong general use", ramGB >= 8}, + {"Gemma (quality)", "gemma3:12b", "8.1 GB", "Google Gemma 3 β€” high quality", ramGB >= 16 || vramGB >= 12}, + {"Gemma (large)", "gemma3:27b", "17 GB", "Google Gemma 3 β€” best quality", ramGB >= 32 || vramGB >= 24}, } fmt.Println(" Recommendations:") diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 9d2904d..cb76825 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -27,14 +27,19 @@ $Arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" } Write-Ok "Detected: windows/$Arch" # Get latest version -Write-Step "Fetching latest version..." -try { - $Release = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest" -UseBasicParsing - $Version = $Release.tag_name -replace '^v', '' - Write-Ok "Latest version: v$Version" -} catch { - $Version = "1.0.0" - Write-Warn "Could not fetch latest version, using v$Version" +if ($env:CUE_VERSION) { + $Version = $env:CUE_VERSION -replace '^v', '' + Write-Ok "Using requested version: v$Version" +} else { + Write-Step "Fetching latest version..." + try { + $Release = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest" -UseBasicParsing + $Version = $Release.tag_name -replace '^v', '' + Write-Ok "Latest version: v$Version" + } catch { + Write-Err "Could not fetch latest release. Set `$env:CUE_VERSION = 'vX.Y.Z' to install a specific version, or download from https://github.com/$Repo/releases" + exit 1 + } } # Download diff --git a/scripts/install.sh b/scripts/install.sh index 3a45427..49c69cb 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -43,10 +43,15 @@ detect_platform() { # Get latest release version get_latest_version() { - VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/') + if [ -n "${CUE_VERSION:-}" ]; then + VERSION="${CUE_VERSION#v}" + print_ok "Using requested version: v${VERSION}" + return + fi + VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name"' | head -n1 | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/') if [ -z "$VERSION" ]; then - VERSION="1.0.0" - print_warn "Could not fetch latest version, using v${VERSION}" + print_err "Could not fetch latest release. Set CUE_VERSION=vX.Y.Z to install a specific version, or download from https://github.com/${REPO}/releases" + exit 1 fi print_ok "Latest version: v${VERSION}" }