Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ This is NOT an "AI agent installer." Most agents are already easy to install (`n
| Linux - Ubuntu/Debian | apt + Homebrew | P0 |
| Linux - Arch | pacman | P0 |
| Linux - Fedora/RHEL | dnf | P1 |
| Linux - openSUSE | zypper | P1 |
| WSL 2 (Windows) | apt + Homebrew | P1 |
| Windows (native) | winget / scoop / choco | P2 |
| Termux (Android) | pkg | P2 |
Expand Down Expand Up @@ -130,7 +131,7 @@ These are the base tools the installer itself and the ecosystem need.
| Dependency | Min Version | Why | Install Method |
|-----------|-------------|-----|----------------|
| `bash` | 3.2+ | GGA, install scripts, Engram plugin hooks | Pre-installed on all targets |
| `git` | 2.x | GGA (diff/staging), Engram (git sync), skills clone, agent integrations | `brew`/`apt`/`pacman`/`dnf`/`pkg` |
| `git` | 2.x | GGA (diff/staging), Engram (git sync), skills clone, agent integrations | `brew`/`apt`/`pacman`/`dnf`/`zypper`/`pkg` |
| `curl` | Any | Binary downloads, GGA providers (lmstudio, github), installer script | Pre-installed on most systems |

#### Conditionally Required (based on user's selections)
Expand All @@ -140,7 +141,7 @@ These are the base tools the installer itself and the ecosystem need.
| **Homebrew** | Any | macOS (primary pkg manager), Linux (recommended for Engram, agents) | Official install script |
| **Node.js** | 20+ | Claude Code (needs 18+), Gemini CLI (needs 20+) β€” installer picks the highest required version | `brew install node` / `nvm` / `fnm` / distro package |
| **npm** | Comes with Node.js | Installing Claude Code, Gemini CLI, Codex | Bundled with Node.js |
| **Go** | 1.25+ | ONLY if building Engram from source (NOT needed for binary/Homebrew install) | `brew install go` / distro package |
| **Go** | 1.25+ | ONLY for local development/source builds (NOT needed for release-binary/Homebrew installs) | `brew install go` / distro package |
| **python3** | 3.x | GGA with Ollama API mode or LM Studio provider (has fallback without it) | Pre-installed on macOS, `apt`/`pacman`/`dnf` on Linux |
| **gh** (GitHub CLI) | Any | GGA with `github:<model>` provider | `brew install gh` / distro package |

Expand All @@ -152,6 +153,7 @@ These are the base tools the installer itself and the ecosystem need.
| **Ubuntu/Debian** | bash, curl, git, sha256sum | Homebrew (optional), Node.js (apt version is often outdated β†’ use NodeSource or fnm) | Node.js from apt is often v12/v16 β€” MUST use NodeSource repo or version manager for v20+ |
| **Arch** | bash, curl, git, python3, sha256sum | Node.js (`pacman -S nodejs npm`) | Arch packages are usually current β€” `pacman` versions are fine |
| **Fedora/RHEL** | bash, curl, git, sha256sum | Node.js (`dnf install nodejs`) | May need `dnf module enable nodejs:20` for correct version |
| **openSUSE** | bash, curl, git, sha256sum | Node.js (`zypper install nodejs npm`) | Tumbleweed packages are usually current; Leap may require a newer Node.js source if distro packages lag |
| **WSL 2** | Same as host Linux distro | Same as Linux + note about Windows-side agents (Cursor, VSCode) | Windows-side agents use Windows paths; WSL agents use Linux paths |
| **Windows native** | None guaranteed | Everything: git (Git for Windows), Node.js (winget/scoop), bash (Git Bash) | GGA needs bash β€” Git for Windows includes Git Bash |
| **Termux** | bash, curl, git | Node.js (`pkg install nodejs`), python (`pkg install python`) | No sudo, no Homebrew. Commands run directly, not via `sh -c`. Go cross-compile has limitations on Android. |
Expand All @@ -174,7 +176,7 @@ Node.js is the most critical dependency β€” multiple agents depend on it, and di
- R-DEP-02: The installer MUST show the complete dependency tree to the user and get confirmation before installing anything
- R-DEP-03: The installer MUST install missing dependencies automatically (with user consent) using the platform's preferred package manager
- R-DEP-04: The installer MUST handle Node.js version requirements intelligently β€” Claude Code needs 18+, Gemini CLI needs 20+, so install 20+ to satisfy both
- R-DEP-05: The installer MUST NOT install Go unless the user explicitly chooses to build Engram from source (pre-compiled binaries are the default)
- R-DEP-05: The installer MUST NOT install Go unless the user explicitly chooses a source-build/development workflow (pre-compiled binaries are the default)
- R-DEP-06: On Linux, the installer MUST NOT use distro-default Node.js if it's below v20 β€” use NodeSource, fnm, or Homebrew instead
- R-DEP-07: The installer MUST handle platform-specific differences transparently (BSD sed vs GNU sed, sha256sum vs shasum, Xcode CLT on macOS)
- R-DEP-08: The installer MUST detect existing version managers (fnm, nvm, n) and use them instead of installing Node.js system-wide
Expand Down Expand Up @@ -252,7 +254,7 @@ The installer supports configuring the Gentleman ecosystem into ANY AI coding ag

| Component | Method | Notes |
|-----------|--------|-------|
| Engram binary | Go install / Homebrew / direct download | Single binary, no deps |
| Engram binary | Homebrew / direct download | Single binary, no deps |
| Engram plugin for Claude Code | `claude plugin marketplace add` | Automatic |
| Engram plugin for OpenCode | Copy `engram.ts` to plugins dir | Automatic |
| Engram config for Gemini CLI | Write `~/.gemini/settings.json` + `system.md` | Automatic |
Expand Down
8 changes: 4 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal/
system/ OS/distro detection, dependency checks, platform guards
cli/ Install flags, validation, orchestration, dry-run
planner/ Dependency graph, resolution, ordering, review payloads
installcmd/ Profile-aware command resolver (brew/apt/pacman/dnf/winget/go install)
installcmd/ Profile-aware command resolver (brew/apt/pacman/dnf/zypper/winget/go install)
pipeline/ Staged execution + rollback orchestration
backup/ Config snapshot + restore
assets/ Embedded skill files + persona templates
Expand All @@ -31,7 +31,7 @@ internal/
tui/ Bubbletea TUI (Rose Pine theme)
styles/ screens/
scripts/ Installer scripts (bash + PowerShell)
e2e/ Docker-based E2E tests (Ubuntu + Arch)
e2e/ Docker-based E2E tests (Ubuntu + Arch + Fedora + openSUSE)
testdata/ Golden test fixtures
```

Expand All @@ -43,7 +43,7 @@ testdata/ Golden test fixtures
# Unit tests
go test ./...

# Docker E2E (Ubuntu + Arch, requires Docker)
# Docker E2E (Ubuntu + Arch + Fedora + openSUSE, requires Docker)
RUN_FULL_E2E=1 RUN_BACKUP_TESTS=1 ./e2e/docker-test.sh

# Dry-run smoke test (macOS/Linux)
Expand All @@ -57,7 +57,7 @@ Test coverage:

- **26 test packages** across the codebase
- **260+ test functions** covering all agent adapters, components, and system detection
- **78 E2E test functions** running in Docker containers (Ubuntu + Arch)
- **78 E2E test functions** running in Docker containers (Ubuntu + Arch + Fedora + openSUSE)
- **17 golden files** for snapshot testing component output
- Full pipeline tested: detection, planning, execution, backup, restore, verification
- All 8 agent adapters have unit tests with cross-platform path validation
Expand Down
8 changes: 8 additions & 0 deletions docs/docker-e2e-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ e2e/
e2e_test.sh # All test cases, tiered by env vars
Dockerfile.ubuntu # Ubuntu 22.04 test image
Dockerfile.arch # Arch Linux test image
Dockerfile.fedora # Fedora test image
Dockerfile.opensuse # openSUSE Tumbleweed test image
docker-test.sh # Orchestrator: build + run all platforms
```

Expand Down Expand Up @@ -37,6 +39,8 @@ RUN_FULL_E2E=1 RUN_BACKUP_TESTS=1 ./e2e/docker-test.sh
|----------|-----------|-----------------|
| Ubuntu 22.04 | `Dockerfile.ubuntu` | apt |
| Arch Linux | `Dockerfile.arch` | pacman |
| Fedora | `Dockerfile.fedora` | dnf |
| openSUSE Tumbleweed | `Dockerfile.opensuse` | zypper |

## How it works

Expand All @@ -60,6 +64,10 @@ docker run --rm gentle-ai-e2e-ubuntu
docker build -f e2e/Dockerfile.arch -t gentle-ai-e2e-arch .
docker run --rm -e RUN_FULL_E2E=1 gentle-ai-e2e-arch

# Run openSUSE only
docker build -f e2e/Dockerfile.opensuse -t gentle-ai-e2e-opensuse .
docker run --rm gentle-ai-e2e-opensuse

# Interactive debugging
docker run --rm -it gentle-ai-e2e-ubuntu /bin/bash
```
Expand Down
1 change: 1 addition & 0 deletions docs/non-interactive.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The installer detects the platform automatically at runtime β€” there is no flag
| Ubuntu/Debian | `apt` | `sudo npm install -g opencode-ai` |
| Arch | `pacman` | `sudo npm install -g opencode-ai` |
| Fedora/RHEL family | `dnf` | `sudo npm install -g opencode-ai` |
| openSUSE family | `zypper` | `sudo npm install -g opencode-ai` |

The `--dry-run` output includes a `Platform decision` line showing `os`, `distro`, `package-manager`, and `status`.

Expand Down
3 changes: 2 additions & 1 deletion docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
| Linux (Ubuntu/Debian) | apt | Supported |
| Linux (Arch) | pacman | Supported |
| Linux (Fedora/RHEL family) | dnf | Supported |
| Linux (openSUSE family) | zypper | Supported |
| Windows 10/11 | Scoop | Supported |

Derivatives are detected via `ID_LIKE` in `/etc/os-release` (Linux Mint, Pop!_OS, Manjaro, EndeavourOS, CentOS Stream, Rocky Linux, AlmaLinux, etc.).
Derivatives are detected via `ID_LIKE` in `/etc/os-release` (Linux Mint, Pop!_OS, Manjaro, EndeavourOS, CentOS Stream, Rocky Linux, AlmaLinux, openSUSE Tumbleweed, openSUSE Leap, etc.).

Release artifacts are produced by CI, but Windows users should install through Scoop so upgrades stay consistent.

Expand Down
13 changes: 10 additions & 3 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@
- `git` available.
- Node.js installs use NodeSource LTS setup + `dnf install -y nodejs` during dependency remediation.

### openSUSE family (Tumbleweed, Leap)

- `zypper` available (standard on these distros).
- `sudo` access for package installs.
- `git` available.
- Node.js installs use distro packages via `zypper install nodejs npm` during dependency remediation.

### All platforms

- Go 1.24+ (for building from source).
- Go 1.24+ only if you are building Gentle AI from source; it is not required for normal release-binary installs.
- Node.js / npm if installing Claude Code (agent is installed via `npm install -g`).
- Pi installed and available as `pi` on `PATH` if you select the Pi agent.

Expand All @@ -50,7 +57,7 @@ Use `--dry-run` first to validate selections and execution plan without applying
go run ./cmd/gentle-ai install
```

The installer detects your platform automatically β€” no flags needed to select macOS vs Linux. Install commands are resolved through the appropriate package manager (brew, apt, pacman, or dnf) based on detection.
The installer detects your platform automatically β€” no flags needed to select macOS vs Linux. Install commands are resolved through the appropriate package manager (brew, apt, pacman, dnf, or zypper) based on detection.

After completion, verify that agent configs and selected components were installed to their expected paths.

Expand Down Expand Up @@ -96,4 +103,4 @@ Optional wrapper tools for extra defense:
If you run the installer on an unsupported OS or Linux distro, it exits immediately with an error:

- `unsupported operating system: only macOS, Linux, and Windows are supported (detected <os>)`
- `unsupported linux distro: Linux support is limited to Ubuntu/Debian, Arch, and Fedora/RHEL family (detected <distro>)`
- `unsupported linux distro: Linux support is limited to Ubuntu/Debian, Arch, Fedora/RHEL family, and openSUSE family (detected <distro>)`
4 changes: 2 additions & 2 deletions docs/rollback.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@ Pinned backups are never automatically deleted, even when the retention limit is

## What rollback does NOT cover

- Packages installed via `brew install`, `apt-get install`, or `pacman -S` are not uninstalled during rollback. The snapshot system handles configuration files only.
- If you need to undo a package install, use your platform's package manager directly (e.g., `brew uninstall`, `sudo apt-get remove`, `sudo pacman -R`).
- Packages installed via `brew install`, `apt-get install`, `pacman -S`, `dnf install`, or `zypper install` are not uninstalled during rollback. The snapshot system handles configuration files only.
- If you need to undo a package install, use your platform's package manager directly (e.g., `brew uninstall`, `sudo apt-get remove`, `sudo pacman -R`, `sudo dnf remove`, `sudo zypper remove`).
3 changes: 2 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ gentle-ai install --agent windsurf --preset full-gentleman

- **Detected tools**: git, curl, node, npm, brew, go
- **Version checks**: validates minimum versions where applicable
- **Platform-aware hints**: suggests `brew install`, `apt install`, `pacman -S`, `dnf install`, or `winget install` depending on your OS
- **Platform-aware hints**: suggests `brew install`, `apt install`, `pacman -S`, `dnf install`, `zypper install`, or `winget install` depending on your OS
- **Node LTS alignment**: on apt/dnf systems, Node.js hints use NodeSource LTS bootstrap before package install
- **Optional Go**: Go is only needed for local development/source builds, not normal release-binary installs
- **Dependency-first approach**: detects what's installed, calculates what's needed, shows the full dependency tree before installing anything, then verifies each dependency after installation
54 changes: 54 additions & 0 deletions e2e/Dockerfile.opensuse
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Dockerfile.opensuse β€” E2E test image for gentle-ai on openSUSE Tumbleweed
# Builds the binary from source and runs the E2E test suite as a non-root user.
FROM opensuse/tumbleweed:latest

# ---------------------------------------------------------------------------
# System dependencies (includes npm/node for claude-code tests)
# ---------------------------------------------------------------------------
RUN zypper --non-interactive refresh && \
zypper --non-interactive install --no-recommends \
git \
curl \
sudo \
ca-certificates \
go \
nodejs \
npm \
&& zypper clean --all

# ---------------------------------------------------------------------------
# Create non-root test user with passwordless sudo
# ---------------------------------------------------------------------------
RUN useradd -m -s /bin/bash testuser && \
echo "testuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# ---------------------------------------------------------------------------
# Copy project source and build
# ---------------------------------------------------------------------------
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -o /usr/local/bin/gentle-ai ./cmd/gentle-ai

# ---------------------------------------------------------------------------
# Prepare test environment
# ---------------------------------------------------------------------------
COPY e2e/lib.sh /home/testuser/e2e/lib.sh
COPY e2e/e2e_test.sh /home/testuser/e2e/e2e_test.sh
RUN chmod +x /home/testuser/e2e/e2e_test.sh /home/testuser/e2e/lib.sh && \
chown -R testuser:testuser /home/testuser

USER testuser

# Ensure go install binaries are on PATH for testuser
ENV GOPATH="/home/testuser/go"
ENV PATH="${GOPATH}/bin:${PATH}"

WORKDIR /home/testuser

# ---------------------------------------------------------------------------
# Default: run Tier 1 tests only
# ---------------------------------------------------------------------------
CMD ["./e2e/e2e_test.sh"]
1 change: 1 addition & 0 deletions e2e/docker-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ PLATFORMS=(
"ubuntu:Dockerfile.ubuntu"
"arch:Dockerfile.arch"
"fedora:Dockerfile.fedora"
"opensuse:Dockerfile.opensuse"
)

# Environment variables to forward into containers
Expand Down
7 changes: 6 additions & 1 deletion internal/agents/opencode/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,14 @@ func TestInstallCommand(t *testing.T) {
profile: system.PlatformProfile{OS: "linux", LinuxDistro: system.LinuxDistroFedora, PackageManager: "dnf", NpmWritable: true},
want: [][]string{{"npm", "install", "-g", "--ignore-scripts", "opencode-ai@" + versions.OpenCode}},
},
{
name: "opensuse resolves npm install",
profile: system.PlatformProfile{OS: "linux", LinuxDistro: system.LinuxDistroOpenSUSE, PackageManager: "zypper"},
want: [][]string{{"sudo", "npm", "install", "-g", "--ignore-scripts", "opencode-ai@" + versions.OpenCode}},
},
{
name: "unsupported package manager returns error",
profile: system.PlatformProfile{OS: "linux", LinuxDistro: system.LinuxDistroUbuntu, PackageManager: "zypper"},
profile: system.PlatformProfile{OS: "linux", LinuxDistro: system.LinuxDistroUbuntu, PackageManager: "unknownpm"},
wantErr: true,
},
}
Expand Down
14 changes: 14 additions & 0 deletions internal/app/parity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ func TestGuardAcceptsFedoraProfile(t *testing.T) {
}
}

func TestGuardAcceptsOpenSUSEProfile(t *testing.T) {
profile := system.PlatformProfile{OS: "linux", LinuxDistro: system.LinuxDistroOpenSUSE, PackageManager: "zypper", Supported: true}
if err := system.EnsureSupportedPlatform(profile); err != nil {
t.Fatalf("expected opensuse profile to be accepted, got %v", err)
}
}

func TestGuardFlowLinuxDryRunPropagatesDecision(t *testing.T) {
detection := system.DetectionResult{
System: system.SystemInfo{
Expand Down Expand Up @@ -339,6 +346,13 @@ func TestGuardFlowLinuxArchProfileExplicitlyPasses(t *testing.T) {
}
}

func TestGuardFlowLinuxOpenSUSEProfileExplicitlyPasses(t *testing.T) {
profile := system.PlatformProfile{OS: "linux", LinuxDistro: system.LinuxDistroOpenSUSE, PackageManager: "zypper", Supported: true}
if err := system.EnsureSupportedPlatform(profile); err != nil {
t.Fatalf("openSUSE profile should pass guard, got %v", err)
}
}

func TestInstallPlannerParityLinuxPreservesComponentOrder(t *testing.T) {
linuxDetection := system.DetectionResult{
System: system.SystemInfo{
Expand Down
4 changes: 2 additions & 2 deletions internal/components/engram/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func TestInstallCommandByProfile(t *testing.T) {
wantErr: true,
},
{
name: "unsupported package manager returns error",
profile: system.PlatformProfile{OS: "linux", PackageManager: "zypper"},
name: "opensuse returns error (uses DownloadLatestBinary instead of go install)",
profile: system.PlatformProfile{OS: "linux", LinuxDistro: system.LinuxDistroOpenSUSE, PackageManager: "zypper"},
wantErr: true,
},
}
Expand Down
11 changes: 10 additions & 1 deletion internal/components/gga/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,20 @@ func TestInstallCommandByProfile(t *testing.T) {
{"bash", "/tmp/gentleman-guardian-angel/install.sh"},
},
},
{
name: "opensuse uses git clone and install.sh",
profile: system.PlatformProfile{OS: "linux", LinuxDistro: system.LinuxDistroOpenSUSE, PackageManager: "zypper"},
want: [][]string{
{"rm", "-rf", "/tmp/gentleman-guardian-angel"},
{"git", "clone", "https://github.com/Gentleman-Programming/gentleman-guardian-angel.git", "/tmp/gentleman-guardian-angel"},
{"bash", "/tmp/gentleman-guardian-angel/install.sh"},
},
},
{
name: "unsupported package manager returns error",
profile: system.PlatformProfile{
OS: "linux",
PackageManager: "zypper",
PackageManager: "unknownpm",
},
wantErr: true,
},
Expand Down
Loading