diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4410207 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - uses: DeterminateSystems/nix-installer-action@main + + - name: Check flake + run: nix flake check --accept-flake-config + + - name: Build package + run: nix build . --accept-flake-config diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml new file mode 100644 index 0000000..832def3 --- /dev/null +++ b/.github/workflows/update-flake.yml @@ -0,0 +1,38 @@ +name: Update Flake + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + update: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - uses: DeterminateSystems/nix-installer-action@main + + - uses: DeterminateSystems/update-flake-lock@main + id: update + with: + pr-title: "flake: update inputs" + pr-labels: dependencies + pr-assignees: ${{ github.repository_owner }} + + - name: Enable auto-merge + if: steps.update.outputs.pull-request-number != '' + run: | + gh pr merge --auto --merge "${{ steps.update.outputs.pull-request-number }}" + env: + GH_TOKEN: ${{ github.token }} + + - name: Wait for CI + if: steps.update.outputs.pull-request-number != '' + run: | + gh pr checks "${{ steps.update.outputs.pull-request-number }}" --watch --fail-fast + env: + GH_TOKEN: ${{ github.token }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..74d139e --- /dev/null +++ b/README.md @@ -0,0 +1,173 @@ +# OpenCode Nix + +> **Status: Work In Progress** - This project is under active development. + +A Nix flake for running OpenCode with company-specific LiteLLM configuration. + +## Quick Start (Milestone 1 - Planned) + +```bash +nix run github:juspay/oc +``` + +## Project Overview + +This project provides a `nix run` way to quickly spin up [OpenCode](https://opencode.ai) using company-specific LiteLLM configuration. The implementation references [srid/nixos-config PR #103](https://github.com/srid/nixos-config/pull/103) (home-manager module). + +## Milestones + +### Milestone 1: Basic `nix run` Launch + +**Goal:** `nix run` launches opencode using [numtide/llm-agents.nix](https://github.com/numtide/llm-agents.nix). + +**Tasks:** + +1. **Create `flake.nix` with llm-agents.nix input** + - Add `llm-agents.nix` as flake input + - Configure binary cache (required for pre-built packages): + ```nix + nixConfig = { + extra-substituters = [ "https://numtide.cachix.org" ]; + extra-trusted-public-keys = [ "numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE=" ]; + }; + ``` + - Export opencode package as default app + +2. **GitHub Workflow for Daily Flake Updates** + - Create `.github/workflows/update-flake.yml` + - Use `DeterminateSystems/update-flake-lock` action + - Configure auto-merge when CI passes + - **Edge Cases:** + - Concurrent updates: Use rebase strategy to avoid conflicts + - CI failures: Do NOT auto-merge; create PR for manual review + - Token permissions: Need `contents: write` and `pull-requests: write` + - Branch protection: Auto-merge requires bypass or admin token + - **Workflow Schedule:** Daily at midnight UTC (`cron: '0 0 * * *'`) + +3. **Platform Support** + - Supported: `x86_64-linux`, `aarch64-linux`, `aarch64-darwin` + - Runtime dependencies (wrapped by llm-agents.nix): `fzf`, `ripgrep` + +**Verification:** +```bash +nix run . -- --help +``` + +--- + +### Milestone 2: Company Configuration + +**Goal:** Add Juspay-specific LiteLLM configuration from [PR #103](https://github.com/srid/nixos-config/pull/103). + +**Tasks:** + +1. **Create home-manager module for opencode** + - The `programs.opencode` module is provided by [home-manager upstream](https://github.com/nix-community/home-manager/blob/master/modules/programs/opencode.nix) + - Available options: + - `programs.opencode.settings` - JSON config for opencode.json + - `programs.opencode.commands` - Custom slash commands + - `programs.opencode.agents` - Custom agent definitions + - Custom agent definitions + - `Custom agent definitions + - `programs = location gettingettings` + - `programs opencode + - Listrollup +żroll + - `Addition +- `agents` grams` commands + + - `programs.opencode.skills` - Custom skills + - `programs.opencode.themes` - Custom themes + - `programs.opencode.tools` - Custom tools + +2. **Define Juspay Provider Configuration** + - Create `modules/juspay.nix` with provider settings: + ```nix + { + npm = "@ai-sdk/openai-compatible"; + name = "Juspay"; + options = { + baseURL = "https://grid.ai.juspay.net"; + apiKey = "{env:JUSPAY_API_KEY}"; # Environment variable + timeout = 600000; + }; + models = { ... }; # Import from models.nix + } + ``` + - **Edge Case:** The reference implementation uses `{file:...}` syntax with agenix secrets. For `nix run` standalone, use `{env:JUSPAY_API_KEY}` and require user to set the environment variable. + +3. **Define Available Models** + - Create `modules/models.nix` with model definitions: + - `open-large`, `open-fast`, `open-vision` + - `claude-opus-4-5`, `claude-opus-4-6`, `claude-sonnet-4-5`, `claude-sonnet-4-6` + - `glm-latest`, `glm-flash-experimental` + - `gemini-3-pro-preview`, `gemini-3-flash-preview` + - `minimax-m2`, `kimi-latest` + - Each model needs: `name`, `modalities`, `limit.context`, `limit.output` + +4. **Configure Default Settings** + - Default model: `litellm/glm-latest` + - Explore agent with `litellm/open-fast` for fast codebase search + - MCP: Enable deepwiki remote server + - **Edge Case:** The `agent.explore` configuration uses subagent mode for parallel search tasks. + +5. **Secret Handling** + - **Option A (Recommended for `nix run`):** Environment variable + - User sets: `export JUSPAY_API_KEY="..."` + - Config uses: `apiKey = "{env:JUSPAY_API_KEY}"` + - **Option B (For home-manager integration):** Agenix secrets + - Use `{file:/run/user/1000/agenix/juspay-anthropic-api-key}` + - Requires agenix setup in host config + +6. **Create Wrapper Script** + - Wrap opencode with environment setup + - Check for `JUSPAY_API_KEY` and warn if not set + - **Edge Case:** Some users may have multiple API keys; consider supporting `OPENCODE_API_KEY` as fallback. + +**Verification:** +```bash +export JUSPAY_API_KEY="your-key" +nix run . -- --help +# Verify config is generated at ~/.config/opencode/opencode.json +``` + +--- + +## Architecture + +``` +oc/ +├── flake.nix # Main flake with inputs and outputs +├── flake.lock # Locked dependencies +├── modules/ +│ ├── default.nix # Default opencode configuration +│ ├── juspay.nix # Juspay provider + models +│ └── models.nix # Model definitions +├── .github/ +│ └── workflows/ +│ └── update-flake.yml # Daily flake updates +└── README.md +``` + +## Dependencies + +- **Runtime:** `opencode` (from llm-agents.nix), `fzf`, `ripgrep` + +## Configuration Reference + +The opencode configuration is written to `~/.config/opencode/opencode.json`. Key settings: + +| Setting | Description | Default | +|---------|-------------|---------| +| `model` | Default model to use | `litellm/glm-latest` | +| `autoupdate` | Auto-update opencode | `true` | +| `provider.litellm` | Juspay LiteLLM provider | See modules/juspay.nix | +| `mcp.deepwiki` | DeepWiki MCP server | enabled | +| `agent.explore` | Fast explore subagent | `litellm/open-fast` | + +## Related + +- [OpenCode Documentation](https://opencode.ai/docs/) +- [home-manager opencode module](https://github.com/nix-community/home-manager/blob/master/modules/programs/opencode.nix) +- [llm-agents.nix](https://github.com/numtide/llm-agents.nix) +- [Reference Implementation PR #103](https://github.com/srid/nixos-config/pull/103) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c00f6d4 --- /dev/null +++ b/flake.lock @@ -0,0 +1,215 @@ +{ + "nodes": { + "blueprint": { + "inputs": { + "nixpkgs": [ + "llm-agents", + "nixpkgs" + ], + "systems": [ + "llm-agents", + "systems" + ] + }, + "locked": { + "lastModified": 1771437256, + "narHash": "sha256-bLqwib+rtyBRRVBWhMuBXPCL/OThfokA+j6+uH7jDGU=", + "owner": "numtide", + "repo": "blueprint", + "rev": "06ee7190dc2620ea98af9eb225aa9627b68b0e33", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "blueprint", + "type": "github" + } + }, + "bun2nix": { + "inputs": { + "flake-parts": [ + "llm-agents", + "flake-parts" + ], + "import-tree": "import-tree", + "nixpkgs": [ + "llm-agents", + "nixpkgs" + ], + "systems": [ + "llm-agents", + "systems" + ], + "treefmt-nix": [ + "llm-agents", + "treefmt-nix" + ] + }, + "locked": { + "lastModified": 1770895533, + "narHash": "sha256-v3QaK9ugy9bN9RXDnjw0i2OifKmz2NnKM82agtqm/UY=", + "owner": "nix-community", + "repo": "bun2nix", + "rev": "c843f477b15f51151f8c6bcc886954699440a6e1", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "bun2nix", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1772408722, + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "llm-agents", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1772408722, + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "import-tree": { + "locked": { + "lastModified": 1763762820, + "narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=", + "owner": "vic", + "repo": "import-tree", + "rev": "3c23749d8013ec6daa1d7255057590e9ca726646", + "type": "github" + }, + "original": { + "owner": "vic", + "repo": "import-tree", + "type": "github" + } + }, + "llm-agents": { + "inputs": { + "blueprint": "blueprint", + "bun2nix": "bun2nix", + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs", + "systems": "systems", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1773325712, + "narHash": "sha256-s83aVJRJmR2sa9otxXnefohasKuRJACZv7zDh0FHzdg=", + "owner": "numtide", + "repo": "llm-agents.nix", + "rev": "dfe7089b3cb1766cf40dccd4378f240356bcf2b2", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "llm-agents.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1773201692, + "narHash": "sha256-NXrKzNMniu4Oam2kAFvqJ3GB2kAvlAFIriTAheaY8hw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b6067cc0127d4db9c26c79e4de0513e58d0c40c9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1772328832, + "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "llm-agents": "llm-agents" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "llm-agents", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1773297127, + "narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "71b125cd05fbfd78cab3e070b73544abe24c5016", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d70915b --- /dev/null +++ b/flake.nix @@ -0,0 +1,27 @@ +{ + description = "OpenCode Nix - Company-specific LiteLLM configuration"; + + nixConfig = { + extra-substituters = [ "https://numtide.cachix.org" ]; + extra-trusted-public-keys = [ "numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE=" ]; + }; + + inputs = { + flake-parts.url = "github:hercules-ci/flake-parts"; + llm-agents.url = "github:numtide/llm-agents.nix"; + }; + + outputs = inputs@{ self, flake-parts, llm-agents, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" ]; + + perSystem = { system, ... }: { + packages.default = llm-agents.packages.${system}.opencode; + + apps.default = { + type = "app"; + program = "${self.packages.${system}.default}/bin/opencode"; + }; + }; + }; +}