diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..86c879f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.{json,jsonc}] +indent_style = space +indent_size = 2 diff --git a/flake.lock b/flake.lock index d6262da..9a3239b 100644 --- a/flake.lock +++ b/flake.lock @@ -45,6 +45,28 @@ "type": "github" } }, + "blueprint": { + "inputs": { + "nixpkgs": [ + "llm-agents", + "nixpkgs" + ], + "systems": "systems_2" + }, + "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" + } + }, "devshell": { "inputs": { "nixpkgs": [ @@ -216,7 +238,7 @@ }, "flake-utils": { "inputs": { - "systems": "systems_5" + "systems": "systems_6" }, "locked": { "lastModified": 1731533236, @@ -234,7 +256,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_7" + "systems": "systems_8" }, "locked": { "lastModified": 1681202837, @@ -340,11 +362,33 @@ "type": "github" } }, + "llm-agents": { + "inputs": { + "blueprint": "blueprint", + "nixpkgs": [ + "nixpkgs" + ], + "treefmt-nix": "treefmt-nix_2" + }, + "locked": { + "lastModified": 1772634885, + "narHash": "sha256-sizdR/xi8eq12PMPJ3BlGeLZ0lZ38NOra5zgNZVAG1g=", + "owner": "numtide", + "repo": "llm-agents.nix", + "rev": "40dc5f264af66355fb609e3b444dcb8e57f6239f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "llm-agents.nix", + "type": "github" + } + }, "minecraft-plymouth-theme": { "inputs": { "flake-parts": "flake-parts_2", "nixpkgs": "nixpkgs_2", - "systems": "systems_2" + "systems": "systems_3" }, "locked": { "lastModified": 1762860197, @@ -464,7 +508,7 @@ "nixpkgs": [ "nixpkgs" ], - "systems": "systems_3" + "systems": "systems_4" }, "locked": { "lastModified": 1772017571, @@ -486,7 +530,7 @@ "nixpkgs": [ "nixpkgs" ], - "systems": "systems_4" + "systems": "systems_5" }, "locked": { "lastModified": 1771987719, @@ -623,7 +667,7 @@ "nixpkgs": [ "nixpkgs" ], - "systems": "systems_6" + "systems": "systems_7" }, "locked": { "lastModified": 1771135771, @@ -687,6 +731,7 @@ "firefox-addons": "firefox-addons", "hammerspoon-nix": "hammerspoon-nix", "home-manager": "home-manager", + "llm-agents": "llm-agents", "minecraft-plymouth-theme": "minecraft-plymouth-theme", "minegrub-world-sel-theme": "minegrub-world-sel-theme", "niri": "niri", @@ -732,6 +777,21 @@ } }, "systems_3": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + }, + "systems_4": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -746,7 +806,7 @@ "type": "github" } }, - "systems_4": { + "systems_5": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -761,7 +821,7 @@ "type": "github" } }, - "systems_5": { + "systems_6": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -776,7 +836,7 @@ "type": "github" } }, - "systems_6": { + "systems_7": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -791,7 +851,7 @@ "type": "github" } }, - "systems_7": { + "systems_8": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -827,6 +887,27 @@ "type": "github" } }, + "treefmt-nix_2": { + "inputs": { + "nixpkgs": [ + "llm-agents", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1770228511, + "narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "337a4fe074be1042a35086f15481d763b8ddc0e7", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, "vscode-server": { "inputs": { "flake-utils": "flake-utils_2", diff --git a/flake.nix b/flake.nix index e0f2b1b..3e67636 100644 --- a/flake.nix +++ b/flake.nix @@ -3,6 +3,8 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + llm-agents.url = "github:numtide/llm-agents.nix"; + llm-agents.inputs.nixpkgs.follows = "nixpkgs"; minegrub-world-sel-theme.url = "github:Lxtharia/minegrub-world-sel-theme"; minegrub-world-sel-theme.inputs.nixpkgs.follows = "nixpkgs"; diff --git a/home/alebat01/home.nix b/home/alebat01/home.nix index d28d46a..c076acf 100644 --- a/home/alebat01/home.nix +++ b/home/alebat01/home.nix @@ -1,4 +1,8 @@ -{ config, pkgs, ... }: +{ + config, + pkgs, + ... +}: { imports = [ ./ssh.nix @@ -14,7 +18,7 @@ templates.commit_trailers = ''if(!trailers.contains_key("Change-Id"), format_gerrit_change_id_trailer(self))''; }; - ai-agent.agent = "codex"; + ai-agent.agent = "opencode"; home.stateVersion = "24.05"; diff --git a/modules/home/ai-agent/default.nix b/modules/home/ai-agent/default.nix index f912d20..005cd0c 100644 --- a/modules/home/ai-agent/default.nix +++ b/modules/home/ai-agent/default.nix @@ -1,14 +1,30 @@ { lib, pkgs, + inputs, config, ... }: let + llmAgentsPkgs = inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}; + mergeJsonc = + name: base: override: + pkgs.runCommand name { } '' + ${ + pkgs.python3.withPackages (ps: [ ps.json5 ]) + }/bin/python ${./merge-jsonc.py} "${base}" "${override}" "$out" + ''; + mergedArmOpencodeConfig = + mergeJsonc "opencode.jsonc" ./opencode/config.jsonc + ./opencode/config.arm.jsonc; + mergedArmOhMyOpencodeConfig = + mergeJsonc "oh-my-opencode.jsonc" ./opencode/oh-my-opencode.jsonc + ./opencode/oh-my-opencode.arm.jsonc; skillsDir = { "claude-code" = ".claude/skills"; "codex" = ".codex/skills"; + "opencode" = ".opencode/skills"; } .${config.ai-agent.agent}; @@ -33,6 +49,7 @@ in type = lib.types.enum [ "claude-code" "codex" + "opencode" ]; default = "claude-code"; description = "Which AI coding agent to install."; @@ -41,8 +58,7 @@ in config = { home.packages = [ - pkgs.${config.ai-agent.agent} - pkgs.beads + llmAgentsPkgs.${config.ai-agent.agent} ]; home.file.${skillsDir} = { @@ -53,5 +69,24 @@ in home.file.".claude/settings.json" = lib.mkIf (config.ai-agent.agent == "claude-code") { text = builtins.toJSON claudeSettings; }; + + home.file.".config/opencode/opencode.jsonc" = lib.mkIf (config.ai-agent.agent == "opencode") { + source = lib.mkDefault ( + if config.home.username == "alebat01" then mergedArmOpencodeConfig else ./opencode/config.jsonc + ); + }; + + home.file.".config/opencode/tui.jsonc" = lib.mkIf (config.ai-agent.agent == "opencode") { + source = lib.mkDefault ./opencode/tui.jsonc; + }; + + home.file.".config/opencode/oh-my-opencode.jsonc" = lib.mkIf (config.ai-agent.agent == "opencode") { + source = lib.mkDefault ( + if config.home.username == "alebat01" then + mergedArmOhMyOpencodeConfig + else + ./opencode/oh-my-opencode.jsonc + ); + }; }; } diff --git a/modules/home/ai-agent/merge-jsonc.py b/modules/home/ai-agent/merge-jsonc.py new file mode 100644 index 0000000..28aa54b --- /dev/null +++ b/modules/home/ai-agent/merge-jsonc.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import json +import sys + +import json5 + + +def merge(base_value, override_value): + if isinstance(base_value, dict) and isinstance(override_value, dict): + merged = dict(base_value) + for key, value in override_value.items(): + if key in merged: + merged[key] = merge(merged[key], value) + else: + merged[key] = value + return merged + + return override_value + + +def main() -> int: + if len(sys.argv) != 4: + print("Usage: merge-jsonc.py ", file=sys.stderr) + return 2 + + base_path, override_path, out_path = sys.argv[1:4] + + with open(base_path, "r", encoding="utf-8") as handle: + base = json5.load(handle) + + with open(override_path, "r", encoding="utf-8") as handle: + override = json5.load(handle) + + merged = merge(base, override) + + with open(out_path, "w", encoding="utf-8") as handle: + json.dump(merged, handle, indent=2) + handle.write("\n") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/modules/home/ai-agent/opencode/config.arm.jsonc b/modules/home/ai-agent/opencode/config.arm.jsonc new file mode 100644 index 0000000..216f6ba --- /dev/null +++ b/modules/home/ai-agent/opencode/config.arm.jsonc @@ -0,0 +1,32 @@ +{ + "$schema": "https://opencode.ai/config.json", + "share": "disabled", + "disabled_providers": ["zen", "opencode", "openai"], + "provider": { + "arm": { + "npm": "@ai-sdk/openai", + "name": "Arm", + "options": { + "baseURL": "https://openai-api-proxy.geo.arm.com/api/providers/openai/v1", + }, + // Any needed OpenAI model can be added here + "models": { + "gpt-5-mini": { + "id": "gpt-5-mini", + }, + "gpt-5.2": { + "id": "gpt-5.2", + }, + "gpt-5.2-pro": { + "id": "gpt-5.2-pro", + }, + "gpt-5.3-codex": { + "id": "gpt-5.3-codex", + }, + "gpt-5.3-codex-spark": { + "id": "gpt-5.3-codex-spark", + }, + }, + }, + }, +} diff --git a/modules/home/ai-agent/opencode/config.jsonc b/modules/home/ai-agent/opencode/config.jsonc new file mode 100644 index 0000000..8e6e80e --- /dev/null +++ b/modules/home/ai-agent/opencode/config.jsonc @@ -0,0 +1,6 @@ +{ + "$schema": "https://opencode.ai/config.json", + "plugin": [ + "oh-my-opencode", // FIXME: does not install + ], +} diff --git a/modules/home/ai-agent/opencode/oh-my-opencode.arm.jsonc b/modules/home/ai-agent/opencode/oh-my-opencode.arm.jsonc new file mode 100644 index 0000000..98fccf2 --- /dev/null +++ b/modules/home/ai-agent/opencode/oh-my-opencode.arm.jsonc @@ -0,0 +1,43 @@ +{ + "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json", + + // See https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/refs/heads/dev/docs/guide/agent-model-matching.md + // Work machines use the Arm OpenAI proxy only + // Cost is not a factor, but speed still matters for utility paths + "agents": { + // Sisyphus is Claude-first in upstream docs; use strongest OpenAI general model. + "sisyphus": { + "model": "arm/gpt-5.2-pro", + "ultrawork": { "model": "arm/gpt-5.3-codex", "variant": "xhigh" }, + }, + + // Utility/search agents: use faster model for snappier turnaround. + "librarian": { "model": "arm/gpt-5-mini" }, + "explore": { "model": "arm/gpt-5-mini" }, + + // Deep/specialist agents. + "hephaestus": { "model": "arm/gpt-5.3-codex", "variant": "xhigh" }, + "oracle": { "model": "arm/gpt-5.2-pro", "variant": "xhigh" }, + "momus": { "model": "arm/gpt-5.2-pro", "variant": "xhigh" }, + "metis": { "model": "arm/gpt-5.2-pro", "variant": "xhigh" }, + "atlas": { "model": "arm/gpt-5.2" }, + + // Keep the same orchestration bias tweak. + "prometheus": { + "prompt_append": "Leverage deep & quick agents heavily, always in parallel.", + }, + }, + + "categories": { + "quick": { "model": "arm/gpt-5-mini" }, + "unspecified-low": { "model": "arm/gpt-5-mini" }, + "unspecified-high": { "model": "arm/gpt-5.2-pro", "variant": "xhigh" }, + "writing": { "model": "arm/gpt-5.2-pro" }, + "visual-engineering": { "model": "arm/gpt-5.2-pro", "variant": "xhigh" }, + + // Additional upstream-style categories mapped to OpenAI-only models. + "deep": { "model": "arm/gpt-5.3-codex", "variant": "xhigh" }, + "ultrabrain": { "model": "arm/gpt-5.3-codex", "variant": "xhigh" }, + "artistry": { "model": "arm/gpt-5.2-pro", "variant": "xhigh" }, + }, +} diff --git a/modules/home/ai-agent/opencode/oh-my-opencode.jsonc b/modules/home/ai-agent/opencode/oh-my-opencode.jsonc new file mode 100644 index 0000000..94d51ad --- /dev/null +++ b/modules/home/ai-agent/opencode/oh-my-opencode.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json", + "experimental": { + "aggressive_truncation": true, + "task_system": true, + "dynamic_context_pruning": { + "enabled": true, + "notification": "minimal", + }, + }, + "tmux": { "enabled": false }, + "hashline_edit": true, +} diff --git a/modules/home/ai-agent/opencode/tui.jsonc b/modules/home/ai-agent/opencode/tui.jsonc new file mode 100644 index 0000000..1640ba5 --- /dev/null +++ b/modules/home/ai-agent/opencode/tui.jsonc @@ -0,0 +1,16 @@ +{ + "$schema": "https://opencode.ai/tui.json", + + "theme": "system", + + //"scroll_speed": 1, // Anything higher is impossible to use with a touchpad + "scroll_acceleration": { + "enabled": true, + }, + + "keybinds": { + "agent_cycle_reverse": "none", + "model_cycle_favorite": "shift+tab", + "tool_details": "none", + }, +}