diff --git a/.chezmoitemplates/grok.toml b/.chezmoitemplates/grok.toml index 2bf681c..a5b255a 100644 --- a/.chezmoitemplates/grok.toml +++ b/.chezmoitemplates/grok.toml @@ -6,71 +6,5 @@ compact_mode = false permission_mode = "always-approve" vim_mode = true -# Ox Alpha provider catalog. Keys remain machine-local environment state. -[model_providers.ox-opencode] -base_url = "https://opencode.ai/zen/v1" -api_backend = "chat_completions" -env_key = "OPENCODE_ZEN_API_KEY" -context_window = 1048576 - -[model_providers.ox-openrouter] -base_url = "https://openrouter.ai/api/v1" -api_backend = "chat_completions" -env_key = "OPENROUTER_API_KEY" -context_window = 1048576 - -[model_providers.ox-command] -base_url = "https://api.commandcode.ai/provider/v1" -api_backend = "chat_completions" -env_key = "COMMAND_CODE_API_KEY" -context_window = 1048576 - -[model_providers.ox-nous] -base_url = "http://127.0.0.1:8645/v1" -api_backend = "chat_completions" -env_key = "NOUS_API_KEY" -context_window = 1048576 - -[model_providers.ox-venice] -base_url = "https://api.venice.ai/api/v1" -api_backend = "chat_completions" -env_key = "VENICE_API_KEY" -context_window = 1048576 - -[model.ox-opencode] -model = "x-preview-f-free" -name = "Ox Alpha (OpenCode Zen; ZDR)" -description = "Limited-time free stealth preview with zero retention" -model_provider = "ox-opencode" -max_completion_tokens = 131072 - -[model.ox-openrouter] -model = "stealth/ox-alpha" -name = "Ox Alpha (OpenRouter)" -description = "Free stealth preview; upstream retains prompts and completions" -model_provider = "ox-openrouter" -max_completion_tokens = 131072 - -[model.ox-command] -model = "stealth/ox-alpha" -name = "Ox Alpha (Command Code)" -description = "Limited-time free stealth preview; not ZDR" -model_provider = "ox-command" -max_completion_tokens = 131072 - -[model.ox-nous] -model = "stealth/ox-alpha" -name = "Ox Alpha (Nous Portal)" -description = "Free-tier route; enable Nous Privacy Mode separately" -model_provider = "ox-nous" -max_completion_tokens = 131072 - -[model.ox-venice] -model = "stealth-ox-alpha" -name = "Ox Alpha (Venice)" -description = "Free beta route; anonymized third-party inference" -model_provider = "ox-venice" -max_completion_tokens = 131072 - [models] default = "grok-4.6" diff --git a/AGENTS.md b/AGENTS.md index 245f58d..17f1039 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -206,7 +206,7 @@ These are codified in `docs/PLAYBOOKS.md`. Cheat sheet: | Add a brew package | Edit `provision/Brewfile` (host/tap tools) or `provision/Brewfile.desktop` (GUI apps). Mise invokes real Homebrew. | | Onboard another machine | `bash ~/dotfiles/scripts/onboard.sh` (standalone download also documented in `docs/SELF-SAVING-DOTFILES.md`); assumes base tools and GitHub login exist. | | Provision a workstation | `mise bootstrap`; inventories and the isolated test rig are documented in `docs/BOOTSTRAP.md`. | -| Add a CLAUDE.md hook / MCP server / skill | Edit `dot_claude/settings.json` for hooks/MCP. Drop a `dot_claude/skills//SKILL.md` for a skill. Run `/discover` after to confirm pickup. | +| Add a CLAUDE.md hook / MCP server / skill | Add hooks/MCP to the `managed` block in `dot_claude/modify_settings.json`. Put a shared skill in `dot_agents/skills//SKILL.md` with a `symlink_SKILL.md` adapter per harness. Run `/discover` after to confirm pickup. | | Add a chezmoi template variable | Add to `~/.config/chezmoi/chezmoi.toml` under `[data]`. Reference as `{{ .key }}` in a `.tmpl` file. | | Add per-machine override | Three options in increasing specificity: chezmoi.toml per machine → hostname branch in `dot_gitconfig.tmpl` → `~/.gitconfig-work` via `includeIf`. See docs/setup.md. | | Set up the alt git identity on a new machine | `~/dotfiles/scripts/setup-alt-identity.sh` — interactive; writes `~/.gitconfig-alt`, generates `~/.ssh/id_ed25519_alt`, runs `gh auth login` into `~/.config/gh-alt/`. Untracked outputs are per-machine. | diff --git a/CLAUDE.md b/CLAUDE.md index abd2705..276ae40 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,9 +10,9 @@ covers the universal substrate. This file is the Claude-flavored supplement. | Surface | Where | What it does | |---|---|---| | `/discover` skill | `dot_claude/skills/discover/SKILL.md` + `dot_local/bin/executable_claude-discover` | Run when the user asks about enabled hooks, MCP servers, experimental flags, or "what's new." Snapshots the current Claude Code surface and diffs against the last snapshot at `$XDG_STATE_HOME/dotfiles/claude/known-features.json`. | -| `repo-onboarding` skill | `dot_claude/skills/repo-onboarding/SKILL.md` | Read yourself in at session start. Detects stack, conventions, in-flight work. Read-only. | +| Shared skills | `dot_agents/skills//SKILL.md`, adapted per harness by `dot_claude/skills//symlink_SKILL.md` | One copy in `~/.agents/skills/`, symlinked into Claude, OpenCode and Hermes. `repo-onboarding`, `blackbird`, `web-research`, `cyclomatic-complexity`, `i-have-adhd`. | | Custom agents | `dot_claude/agents/*.md` | `terminal-executor` is the only one currently tracked. Add more here, not as untracked files in `~/.claude/agents/`. | -| User settings | `dot_claude/settings.json` | Plugin enables, `effortLevel`, dangerous-mode setting. Tracked. | +| User settings | `dot_claude/modify_settings.json` | A chezmoi `modify_` script, **not** a copied file. It merges the portable managed keys (plugin enables, `effortLevel`, dangerous-mode) into whatever is already on disk, so `herdr`'s live hooks survive every apply. | | Project-local permissions | `.claude/settings.local.json` (gitignored) | Per-machine, never tracked. The global `~/.config/git/ignore` enforces. | --- @@ -23,7 +23,7 @@ All live at `~/.local/bin/` post-`chezmoi apply`. All print to stdout in a form you can read and present to the user. ```sh -dot-doctor # health check — 27 checks, exit 0/1/2 +dot-doctor # health check — one file per check in checks/, exit 0/1/2 dot-doctor --list # list all checks discovered DOT_SKIP=zsh,claude dot-doctor # skip specific checks dot-bench # perf vs PERF.md baselines (zsh-bench) @@ -60,24 +60,35 @@ file that still says `python3` or `pip`, convert it as part of the change. When the user asks to add a hook, MCP server, skill, or agent: 1. **Identify the right home:** - - **Hook**: `dot_claude/settings.json` → `hooks` array. Hooks are - **harness-executed**, not Claude-executed — memory and preferences can't - fulfill "automatically do X" requests. Use the `update-config` skill if - available. - - **MCP server**: `dot_claude/settings.json` → `mcpServers` object. - - **Skill**: `dot_claude/skills//SKILL.md` with frontmatter - (`name`, `description`). Skill body is plain markdown. + - **Hook**: the `managed` block in `dot_claude/modify_settings.json` → + `hooks`. Hooks are **harness-executed**, not Claude-executed — memory and + preferences can't fulfill "automatically do X" requests. Use the + `update-config` skill if available. + - **MCP server**: the `managed` block → `mcpServers`. + - **Skill**: `dot_agents/skills//SKILL.md`, plus a + `dot_claude/skills//symlink_SKILL.md` adapter (and the equivalent + under `dot_config/opencode/skill/` and `dot_hermes/skills/` for harnesses + that should also see it). Claude-only skills may live at + `dot_claude/skills//SKILL.md` — `discover` is the one example. - **Agent**: `dot_claude/agents/.md` with frontmatter. - **Slash command**: `dot_claude/commands/.md`. 2. **Edit in the chezmoi source (not in `~/.claude/`)**. Editing `~/.claude/` directly will be overwritten on next `chezmoi apply`. + `settings.json` is reconciled by a `modify_` script, so only the keys in its + `managed` block are enforced; everything else on disk (herdr's hooks, status + line, project state) is preserved. Shared skills are the mirror image: + `~/.agents/skills/` is a live preference owned by native mise history, and + `dot_agents/skills/` only *seeds* files that are missing — an existing live + file is never overwritten. + 3. **Apply + verify:** ```sh chezmoi diff # confirm scope chezmoi apply - dot-doctor # claude.sh check verifies settings.json parses + matches source + dot-doctor # claude.sh proves the managed keys took and that + # the merge preserved everything it does not own ``` 4. **Confirm discoverability** — run `claude-discover`. New hooks/skills/MCP @@ -97,9 +108,9 @@ When the user says "from now on when X, do Y" or "whenever X" or "before/after X that's a **hook** request. The harness executes hooks; memory and preferences do not. If you save it as a memory, the rule will be ignored. -Use the `update-config` skill if available; otherwise edit -`dot_claude/settings.json` directly under the `hooks` field, then -`chezmoi apply`, then run `claude-discover` to confirm pickup. +Use the `update-config` skill if available; otherwise add the hook to the +`managed` block in `dot_claude/modify_settings.json`, then `chezmoi apply`, +then run `claude-discover` to confirm pickup. --- @@ -109,12 +120,13 @@ Each project (this repo included) can have a `.claude/settings.local.json` with **host-specific permission allowlists**. By Claude Code convention, those files are gitignored via the global `~/.config/git/ignore`. They are NOT tracked, NOT templated, and **must not contain anything that should -follow the user across machines** — that belongs in `dot_claude/settings.json`. +follow the user across machines** — that belongs in +`dot_claude/modify_settings.json`. If the user wants to broaden permissions for *this* repo, edit `~/dotfiles/.claude/settings.local.json` (gitignored, per-machine). -If they want it across all projects, edit `dot_claude/settings.json` and -`chezmoi apply`. +If they want it across all projects, add them to the `managed` block in +`dot_claude/modify_settings.json` and `chezmoi apply`. --- @@ -143,10 +155,11 @@ The user has an active auto-memory at --- -## Ultrareview / unattended runs +## Deep review / unattended runs -When the user invokes `/ultrareview`, it's a cloud-billed multi-agent review. -You cannot launch it on their behalf. They invoke it; you don't. +`/code-review ultra` is a cloud-billed multi-agent review (`/ultrareview` is a +deprecated alias for the same thing). You cannot launch it on their behalf. +They invoke it; you don't. For long-running unattended work (`/loop`, `/schedule`), the user has the `loop` and `schedule` skills available. Suggest them when appropriate. @@ -159,9 +172,9 @@ For long-running unattended work (`/loop`, `/schedule`), the user has the `dot-doctor` / `dot-bench` (where they survive only as terminal output). - **Skill descriptions are imperatives** in the frontmatter (`description: Surface the current...`), not third-person. -- **Hooks are documented in their definition** — settings.json comments are - not supported in strict JSON, but you can leave a sibling `.md` under - `dot_claude/` if a hook needs explaining. +- **Hooks are documented in their definition.** `dot_claude/modify_settings.json` + is a shell script wrapping a JSON literal, so a `#` comment above the entry is + the right place to explain one — no sibling `.md` needed. - **The `/discover` skill is the canonical answer to "what's enabled?"** Use it; don't reinvent it. diff --git a/checks/blackbird.sh b/checks/blackbird.sh index 6ad5eee..4e9d89d 100644 --- a/checks/blackbird.sh +++ b/checks/blackbird.sh @@ -1,14 +1,68 @@ +# Blackbird durable coordination. +# +# Two levels of evidence, because they are not always both available. Where the +# service lifecycle is enabled, ask the runtime what it actually resolved -- +# OpenCode accepts MCP servers at both .mcp. and .mcp.servers., and +# a check that hardcodes one spelling reports a healthy daemon as broken. Where +# it is not (a container, a profile that opts out), no daemon is running to ask, +# so assert the configuration declares the integration correctly instead. hdr "blackbird" if ! command -v blackbird >/dev/null; then fail "Blackbird missing — run mise bootstrap" return 0 fi -config="$HOME/.config/opencode/opencode.jsonc" -if jq -e '.mcp.servers.blackbird.url == "http://127.0.0.1:8081" and any(.plugins[]; type == "object" and .package == "blackbird-opencode@0.1.3" and .options.baseUrl == "http://127.0.0.1:8080" and (.options | has("token") | not))' "$config" >/dev/null; then - ok "MCP and push-delivery connections configured without literal credentials" + +bb_configs=() +for config in "$HOME/.config/opencode/opencode.jsonc" "$HOME/.config/opencode/opencode.json"; do + [[ -f "$config" ]] && bb_configs+=("$config") +done + +# Accept either MCP placement, in any of the loaded documents. +bb_declared=1 +for config in "${bb_configs[@]}"; do + jq -e '((.mcp.blackbird // .mcp.servers.blackbird) | .url) == "http://127.0.0.1:8081"' "$config" >/dev/null 2>&1 && bb_declared=0 +done + +bb_plugin_declared=1 +for config in "${bb_configs[@]}"; do + jq -e 'any((.plugins // [])[]; type == "object" and (.package // "" | startswith("blackbird-opencode")))' "$config" >/dev/null 2>&1 && bb_plugin_declared=0 +done + +if ! command -v opencode2 >/dev/null 2>&1; then + warn "OpenCode absent — Blackbird delivery integration unverified" +elif services_enabled; then + # A daemon is running, so resolved state is the stronger claim. + if ! mcp_list="$(opencode2 mcp list 2>/dev/null)"; then + fail "OpenCode could not enumerate MCP servers" + elif grep -qE '^\s*✓\s+blackbird\b' <<< "$mcp_list"; then + ok "Blackbird MCP endpoint connected" + elif grep -qE '^\s*[⚠○]\s+blackbird\b' <<< "$mcp_list"; then + fail "Blackbird MCP server is registered but not connected — blackbird doctor" + else + fail "Blackbird MCP server is not registered with OpenCode" + fi + if opencode2 plugin list 2>/dev/null | grep -qE '^\S*blackbird\s'; then + ok "Blackbird push-delivery plugin loaded" + else + fail "Blackbird OpenCode plugin is not loaded — opencode2 plugin list" + fi else - fail "Blackbird V2 integration invalid" + (( bb_declared )) && fail "Blackbird MCP endpoint is not declared in OpenCode config" \ + || ok "Blackbird MCP endpoint declared" + (( bb_plugin_declared )) && fail "Blackbird push-delivery plugin is not declared in OpenCode config" \ + || ok "Blackbird push-delivery plugin declared" fi + +# A credential belongs in the store, never in a tracked or synced config. +bb_credential_leak=0 +for config in "${bb_configs[@]}"; do + if ! jq -e '[(.plugins // [])[] | select(type == "object" and (.package // "" | startswith("blackbird-opencode"))) | .options // {}] | all(has("token") | not)' "$config" >/dev/null 2>&1; then + fail "Blackbird plugin options carry a literal token in ${config/#$HOME/\~} — use the credential store" + bb_credential_leak=1 + fi +done +(( bb_credential_leak )) || ok "no literal Blackbird credentials in OpenCode config" + if services_enabled; then if blackbird doctor >/dev/null 2>&1; then ok "native doctor healthy" else fail "Blackbird service needs attention — blackbird doctor"; fi diff --git a/checks/claude.sh b/checks/claude.sh index 9740db5..0e6e235 100644 --- a/checks/claude.sh +++ b/checks/claude.sh @@ -1,26 +1,60 @@ -# claude checks. chezmoi-aware: files are real copies, not symlinks. +# shellcheck shell=bash +# Claude Code. Settings are reconciled by a modify_ script, not copied: herdr +# writes its own hooks into ~/.claude/settings.json live, so chezmoi merges the +# portable managed keys and leaves everything else alone. A plain source-vs- +# target diff is therefore meaningless here -- assert that the managed keys +# actually took, and nothing about the keys we deliberately do not own. harness_enabled claude || return 0 hdr "claude" -if [[ -f "$HOME/.claude/settings.json" ]]; then - if jq empty "$HOME/.claude/settings.json" 2>/dev/null; then - ok "settings.json parses" +settings="$HOME/.claude/settings.json" +modify="$DOTFILES/dot_claude/modify_settings.json" + +if [[ ! -f "$settings" ]]; then + warn "~/.claude/settings.json missing — run 'chezmoi apply'" +elif ! jq -e 'type == "object"' "$settings" >/dev/null 2>&1; then + fail "settings.json does not parse as JSON" +else + ok "settings.json parses" + # The managed block is the contract. Re-derive it from the modify_ script so + # this check cannot drift from what chezmoi actually enforces. + if [[ -x "$modify" ]] && managed="$(sed -n "/^managed='/,/^}'$/p" "$modify" | sed "s/^managed='//; s/^}'$/}/")" \ + && jq -e 'type == "object"' <<< "$managed" >/dev/null 2>&1; then + if jq -e --argjson managed "$managed" '. as $live | $managed | [paths(scalars) as $p | ($live | getpath($p)) == ($managed | getpath($p))] | all' "$settings" >/dev/null 2>&1; then + ok "managed settings keys are applied" + else + warn "managed settings keys diverge — run 'chezmoi apply'" + fi else - fail "settings.json does not parse as JSON" + fail "dot_claude/modify_settings.json is missing, not executable, or its managed block is unreadable" fi +fi - # Drift detection: does the applied copy match the chezmoi source? - src="$DOTFILES/dot_claude/settings.json" - if [[ -f "$src" ]] && ! diff -q "$src" "$HOME/.claude/settings.json" >/dev/null 2>&1; then - warn "settings.json diverges from source — run 'chezmoi apply' (or 'chezmoi diff' to inspect)" +# The modify_ script must merge, never replace: a replacing script would erase +# herdr's live hooks on every apply. +if [[ -x "$modify" ]]; then + probe='{"hooks":{"Stop":[{"keep":true}]},"theme":"light"}' + merged="$(printf '%s' "$probe" | "$modify" 2>/dev/null)" + if jq -e '.hooks.Stop[0].keep == true and .theme == "dark"' <<< "$merged" >/dev/null 2>&1; then + ok "modify_settings merges: unmanaged keys survive, managed keys win" else - ok "settings.json matches source" + fail "modify_settings does not preserve unmanaged keys — it would erase herdr's hooks" fi -else - warn "~/.claude/settings.json missing — run 'chezmoi apply'" fi +# Skills reach Claude as symlink adapters into the shared ~/.agents tree. +for adapter in "$DOTFILES"/dot_claude/skills/*/symlink_SKILL.md; do + [[ -e "$adapter" ]] || continue + skill="$(basename "$(dirname "$adapter")")" + live="$HOME/.claude/skills/$skill/SKILL.md" + if [[ -e "$live" ]]; then + ok "claude skill adapter: $skill" + else + warn "claude skill adapter missing: $skill — run 'chezmoi apply'" + fi +done + # Custom agents — tracked count vs applied count. if [[ -d "$DOTFILES/dot_claude/agents" ]]; then tracked=$(find "$DOTFILES/dot_claude/agents" -maxdepth 1 -name '*.md' -type f 2>/dev/null | wc -l | tr -d ' ') @@ -31,10 +65,3 @@ if [[ -d "$DOTFILES/dot_claude/agents" ]]; then warn "agent count mismatch — tracked=$tracked applied=$applied. Move untracked agents into \$DOTFILES/dot_claude/agents/ and 'chezmoi apply'" fi fi - -# settings.local.json (project-local Claude permissions). Gitignored via global. -if [[ -f "$DOTFILES/.claude/settings.local.json" ]] \ - && grep -q "/Users/phall/" "$DOTFILES/.claude/settings.local.json" 2>/dev/null; then - # Project-local file with hardcoded path is fine — it's host-specific anyway. - : -fi diff --git a/checks/opencode.sh b/checks/opencode.sh index 012dc2e..592bda4 100644 --- a/checks/opencode.sh +++ b/checks/opencode.sh @@ -1,25 +1,47 @@ -#!/usr/bin/env bash -# OpenCode V2-only configuration and direct OpenAI model exposure. +# shellcheck shell=bash +# OpenCode V2 configuration. +# +# OpenCode migrates legacy key names when it loads a config (permission -> +# permissions, provider -> providers, attachment -> media), so asserting key +# names against the raw file on disk reports a working config as broken. Ask +# the runtime what it resolved instead: `opencode2 debug config` is the only +# authority on the effective shape. hdr "opencode" -config="$HOME/.config/opencode/opencode.jsonc" - -if [[ -f "$config" ]] && jq -e ' - .permissions - and .mcp.servers - and (.update == "auto") - and .plugins - and (has("permission") | not) - and (has("provider") | not) - and (has("plugin") | not) -' "$config" >/dev/null 2>&1; then - ok "native V2 config with automatic updates" +if ! command -v opencode2 >/dev/null 2>&1; then + warn "opencode2 missing — run mise bootstrap" + return 0 +fi + +for config in "$HOME/.config/opencode/opencode.jsonc" "$HOME/.config/opencode/opencode.json"; do + [[ -f "$config" ]] || continue + jq -e 'type == "object"' "$config" >/dev/null 2>&1 \ + || fail "${config/#$HOME/\~} does not parse as JSON" +done + +if ! resolved="$(opencode2 debug config 2>/dev/null)"; then + fail "OpenCode could not resolve its configuration — opencode2 debug config" + return 0 +fi + +# Multiple config documents load in order and shadow each other silently. One +# document is the intended shape; a second is usually a leftover. +documents="$(jq -r '[.[] | select(.type == "document")] | length' <<< "$resolved")" +if [[ "$documents" -le 1 ]]; then + ok "single OpenCode config document" +else + warn "$documents OpenCode config documents load and shadow each other: $(jq -r '[.[] | select(.type == "document") | .path | sub("^" + env.HOME; "~")] | join(", ")' <<< "$resolved")" +fi + +if jq -e '[.[] | select(.type == "document") | .info] | add + | (.permissions != null) and (.plugins != null) and (.mcp != null)' <<< "$resolved" >/dev/null 2>&1; then + ok "resolved config carries native V2 permissions, plugins and MCP" else - fail "OpenCode config is missing native V2 shape or automatic updates" + fail "resolved OpenCode config is missing native V2 permissions/plugins/MCP" fi -if services_enabled && command -v opencode2 >/dev/null 2>&1; then +if services_enabled; then if ! opencode_models="$(opencode2 models 2>/dev/null)"; then fail "OpenCode could not enumerate its model catalog" elif grep -qx 'openai/gpt-6-astra' <<< "$opencode_models"; then @@ -37,7 +59,7 @@ for path in \ "$HOME/.local/bin/opencode-ohmy" \ "$HOME/.local/bin/opencode-safe" do - [[ -e "$path" || -L "$path" ]] && obsolete+=" ${path/#$HOME/~}" + [[ -e "$path" || -L "$path" ]] && obsolete+=" ${path/#$HOME/\~}" done if [[ -z "$obsolete" ]]; then diff --git a/checks/ox-alpha.sh b/checks/ox-alpha.sh deleted file mode 100644 index 89c2da5..0000000 --- a/checks/ox-alpha.sh +++ /dev/null @@ -1,40 +0,0 @@ -# Only selected harnesses participate in workstation health. The isolated -# fixture suite validates every optional source, even when it is not applied. -hdr "ox alpha routes" -if "$HOME/.local/bin/ox" status >/dev/null 2>&1; then ok "Ox launcher healthy" -else fail "Ox launcher missing or broken"; fi -if jq -e '.providers.opencode.models["x-preview-f-free"] and .providers.openrouter.models["stealth/ox-alpha"] and .providers["ox-command"].models["stealth/ox-alpha"] and .providers["ox-nous"].models["stealth/ox-alpha"] and .providers["ox-venice"].models["stealth-ox-alpha"]' "$HOME/.config/opencode/opencode.jsonc" >/dev/null 2>&1; then - ok "OpenCode provider fleet configured" -else - fail "OpenCode provider fleet invalid" -fi -if services_enabled; then - if opencode2 debug config >/dev/null 2>&1; then ok "OpenCode accepts the managed V2 config" - else fail "OpenCode rejected the managed config"; fi -fi -if harness_enabled pi; then - if jq -e --argjson expected '["ox-opencode","ox-openrouter","ox-command","ox-nous","ox-venice"]' ' - (.providers | keys) as $keys | ($expected - $keys | length) == 0 - and ([.providers[$expected[]].models[0].contextWindow] | all(. == 1048576)) - and ([.providers[$expected[]].models[0].maxTokens] | all(. == 131072)) - ' "$HOME/.pi/agent/models.json" >/dev/null 2>&1; then ok "Pi Ox provider catalog valid" - else fail "Pi Ox catalog invalid"; fi -fi -if harness_enabled hermes; then - if yq -e '[.custom_providers[] | select(.name == "ox-command" or .name == "ox-venice")] | length == 2' "$HOME/.hermes/config.yaml" >/dev/null 2>&1; then ok "Hermes Ox custom routes valid" - else fail "Hermes Ox custom routes missing"; fi -fi -if harness_enabled goose; then - for provider in opencode openrouter command nous venice; do - if jq -e '.engine == "openai" and .requires_auth and .models[0].context_limit == 1048576' "$HOME/.config/goose/custom_providers/ox_$provider.json" >/dev/null 2>&1; then ok "Goose Ox $provider valid" - else fail "Goose Ox $provider missing or invalid"; fi - done -fi -if harness_enabled grok; then - if yq -p=toml -o=json -e '.model."ox-opencode" and .model."ox-venice"' "$HOME/.grok/config.toml" >/dev/null 2>&1; then ok "Grok Ox catalog valid" - else fail "Grok Ox catalog invalid"; fi -fi -for harness in pi claude hermes goose grok; do - harness_enabled "$harness" || continue - command -v "$harness" >/dev/null || fail "selected harness $harness is not installed" -done diff --git a/checks/pi.sh b/checks/pi.sh index 02b0ff0..18282e4 100644 --- a/checks/pi.sh +++ b/checks/pi.sh @@ -10,6 +10,19 @@ goal_config="$HOME/.pi/agent/pi-goal.json" module_bridge="$HOME/.pi/agent/node_modules" mcp_config="$HOME/.config/mcp/mcp.json" +# The managed package pins are declared in modify_settings.json. Restating them +# here made this check a lockstep-edit detector: it could only ever catch "you +# edited one file and not the other", never real drift. Derive them instead -- +# and the retired-package assertion below derives its own name the same way. +managed_packages="$(sed -n "/^managed_packages='/,/^]'$/p" "$pi_modify" | sed "s/^managed_packages='//; s/^]'$/]/")" +if ! jq -e 'type == "array"' <<< "$managed_packages" >/dev/null 2>&1; then + fail "cannot read managed_packages from dot_pi/private_agent/modify_settings.json" + return 0 +fi +npm_pins="$(jq -r '[.[] | select(startswith("npm:"))]' <<< "$managed_packages")" +retired_packages="$(sed -n "/^retired_packages='/,/^]'$/p" "$pi_modify" | sed "s/^retired_packages='//; s/^]'$/]/")" +jq -e 'type == "array"' <<< "$retired_packages" >/dev/null 2>&1 || retired_packages='[]' + if command -v pi >/dev/null 2>&1; then version="$(pi --version 2>/dev/null | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -1)" pi_pin="$(yq -p=toml -o=json -r '.tools."npm:@earendil-works/pi-coding-agent"' "$DOTFILES/mise.pi.toml")" @@ -29,21 +42,38 @@ fi if [[ -f "$pi_settings" ]] && jq -e 'type == "object"' "$pi_settings" >/dev/null 2>&1; then ok "settings.json parses" [[ "$(jq -r '.defaultProjectTrust // empty' "$pi_settings")" == always ]] && ok "project trust defaults to always" || fail "defaultProjectTrust is not always" - missing="$(jq -r '["npm:pi-subagents@0.47.1","npm:@narumitw/pi-goal@0.51.0","npm:@ff-labs/pi-fff@0.10.3","npm:pi-mcp-adapter@2.23.0","npm:pi-web-access@0.22.0","npm:@osolmaz/pi-workflows@0.13.4"] - (.packages // []) | .[]' "$pi_settings")" + missing="$(jq -r --argjson pins "$npm_pins" '$pins - (.packages // []) | .[]' "$pi_settings")" [[ -z "$missing" ]] && ok "portable Pi package pins present" || fail "missing managed Pi package pin(s): ${missing//$'\n'/, }" - if jq -e '[(.packages // [])[] | if type == "object" then .source else . end | select(type == "string" and test("rpiv-ask-user-question"))] | length == 0' "$pi_settings" >/dev/null 2>&1; then - ok "user-question package absent" + still_present="$(jq -r --argjson retired "$retired_packages" ' + [(.packages // [])[] | if type == "object" then .source else . end | select(type == "string")] as $configured + | [$retired[] | select(. as $name | $configured | map(contains($name)) | any)] | .[]' "$pi_settings")" + if [[ -z "$still_present" ]]; then + ok "retired packages absent" else - fail "retired user-question package is still configured" + fail "retired package(s) still configured: ${still_present//$'\n'/, }" fi else fail "~/.pi/agent/settings.json missing or invalid" fi if [[ -x "$pi_modify" ]]; then - synthetic='{"defaultProvider":"local","defaultModel":"keep-me","defaultThinkingLevel":"low","runtime":{"token":"keep"},"packages":["npm:pi-subagents@old","npm:@juicesharp/rpiv-ask-user-question@2.4.0","git:example/tool"]}' + # Feed it a stale pin, a retired package and an unmanaged entry, then assert + # the contract: runtime keys survive, every managed pin lands, retired names go. + synthetic="$(jq -nc --argjson retired "$retired_packages" '{ + defaultProvider: "local", defaultModel: "keep-me", defaultThinkingLevel: "low", + runtime: {token: "keep"}, + packages: (["npm:pi-subagents@old", "git:example/tool"] + [$retired[] | "npm:" + . + "@1.0.0"]) + }')" roundtrip="$(printf '%s' "$synthetic" | "$pi_modify" 2>/dev/null)" - if jq -e '.defaultProvider=="local" and .defaultModel=="keep-me" and .defaultThinkingLevel=="low" and .runtime.token=="keep" and (.packages|index("git:example/tool")) and (.packages|index("npm:pi-subagents@0.47.1")) and (.packages|index("npm:@osolmaz/pi-workflows@0.13.4")) and ((.packages|map(tostring)|map(contains("rpiv-ask-user-question"))|any) | not) and .subagents.watchdog.enabled==true and .subagents.watchdog.main.enabled==true and .defaultProjectTrust=="always"' <<<"$roundtrip" >/dev/null 2>&1; then + if jq -e --argjson pins "$npm_pins" --argjson retired "$retired_packages" ' + [(.packages // [])[] | if type == "object" then .source else . end | select(type == "string")] as $out + | .defaultProvider == "local" and .defaultModel == "keep-me" + and .defaultThinkingLevel == "low" and .runtime.token == "keep" + and ($out | index("git:example/tool")) + and (($pins - $out) | length == 0) + and ([$retired[] | select(. as $n | $out | map(contains($n)) | any)] | length == 0) + and .subagents.watchdog.enabled == true and .subagents.watchdog.main.enabled == true + and .defaultProjectTrust == "always"' <<<"$roundtrip" >/dev/null 2>&1; then ok "modify_settings preserves runtime keys and enforces managed values" else fail "modify_settings semantic roundtrip failed" @@ -82,33 +112,12 @@ else fail "shared Blackbird MCP config missing or inconsistent" fi -for skill in blackbird web-research cyclomatic-complexity; do - [[ -f "$HOME/.agents/skills/$skill/SKILL.md" ]] && ok "shared $skill skill" || fail "shared $skill skill missing" -done - -complexity_skill="$HOME/.agents/skills/cyclomatic-complexity/SKILL.md" -for adapter in \ - "$HOME/.claude/skills/cyclomatic-complexity/SKILL.md" \ - "$HOME/.config/opencode/skill/cyclomatic-complexity/SKILL.md" \ - "$HOME/.hermes/skills/cyclomatic-complexity/SKILL.md" -do - case "$adapter" in - "$HOME/.claude/"*) harness_enabled claude || continue ;; - "$HOME/.hermes/"*) harness_enabled hermes || continue ;; - esac - if [[ -L "$adapter" && "$adapter" -ef "$complexity_skill" ]]; then - ok "cyclomatic-complexity adapter: ${adapter#"$HOME"/}" - else - fail "cyclomatic-complexity adapter missing or stale: ${adapter#"$HOME"/}" - fi -done - if [[ -d "$HOME/.pi/agent/npm/node_modules" ]]; then - for spec in 'pi-subagents:0.47.1' '@narumitw/pi-goal:0.51.0' '@ff-labs/pi-fff:0.10.3' 'pi-mcp-adapter:2.23.0' 'pi-web-access:0.22.0' '@osolmaz/pi-workflows:0.13.4'; do - pkg="${spec%:*}"; expected="${spec##*:}"; manifest="$HOME/.pi/agent/npm/node_modules/$pkg/package.json" + while IFS= read -r spec; do + pkg="${spec%@*}"; expected="${spec##*@}"; manifest="$HOME/.pi/agent/npm/node_modules/$pkg/package.json" if [[ ! -f "$manifest" ]]; then warn "$pkg not installed yet" elif [[ "$(jq -r .version "$manifest")" == "$expected" ]]; then ok "$pkg@$expected installed" else warn "$pkg installed version differs from $expected" fi - done + done < <(jq -r '.[] | sub("^npm:"; "")' <<< "$npm_pins") fi diff --git a/checks/skills.sh b/checks/skills.sh new file mode 100644 index 0000000..9c9c5d0 --- /dev/null +++ b/checks/skills.sh @@ -0,0 +1,40 @@ +# shellcheck shell=bash +# Shared skills and their per-harness adapters. +# +# Harnesses that do not read ~/.agents/skills natively get chezmoi symlink +# adapters. A symlink into a missing file is silent at every layer above the +# filesystem -- the harness simply never loads the skill -- so the adapters are +# resolved here rather than merely counted. +hdr "shared skills" + +live="$HOME/.agents/skills" +seeds="$DOTFILES/dot_agents/skills" + +missing='' +while IFS= read -r -d '' source; do + relative="${source#"$seeds"/}" + [[ -e "$live/$relative" ]] || missing+=" $relative" +done < <(find "$seeds" -type f -print0 2>/dev/null) + +if [[ -z "$missing" ]]; then + ok "every tracked shared skill file is present in ~/.agents/skills" +else + fail "shared skill files absent from the live tree:$missing — run 'mise bootstrap' (scripts/bootstrap/skills.sh)" +fi + +dangling='' +for adapter in \ + "$HOME"/.claude/skills/*/SKILL.md \ + "$HOME"/.config/opencode/skill/*/SKILL.md \ + "$HOME"/.hermes/skills/*/SKILL.md +do + [[ -L "$adapter" ]] || continue + [[ -e "$adapter" ]] && continue + dangling+=" ${adapter#"$HOME"/}" +done + +if [[ -z "$dangling" ]]; then + ok "all harness skill adapters resolve" +else + fail "dangling skill adapters:$dangling" +fi diff --git a/docs/BOOTSTRAP.md b/docs/BOOTSTRAP.md index 9501db8..cb06084 100644 --- a/docs/BOOTSTRAP.md +++ b/docs/BOOTSTRAP.md @@ -28,6 +28,7 @@ definitions. Bootstrap never copies these between hosts. | Other selected harnesses | `scripts/bootstrap/harnesses.sh` | Native installer; existing installations are retained | | Rust | rustup | Native toolchains and project `rust-toolchain.toml` | | Editable preferences | Native mise tracking, initially selected by `provision/dotfiles-history.json` | Edit live → autosave → private two-way sync | +| Shared skills | Live `~/.agents/skills/` (an editable preference); `dot_agents/skills/` seeds it via `scripts/bootstrap/skills.sh` | Missing files are seeded; an existing live file is never overwritten | | Templates and integrations | Remaining chezmoi `dot_*` sources | Preview → apply → verify | | Phux/Blackbird services | Native product installers | Healthy services are retained; Phux adoption preserves live panes | diff --git a/docs/OX-ALPHA.md b/docs/OX-ALPHA.md deleted file mode 100644 index dec8431..0000000 --- a/docs/OX-ALPHA.md +++ /dev/null @@ -1,99 +0,0 @@ -# Ox Alpha preview routes - -Ox Alpha is an anonymous 1,048,576-token-context reasoning model currently -available at zero token price through five gateways. It is **not unlimited**: -accounts, API keys, provider rate limits, and preview availability still apply. -OpenCode and Command Code explicitly call the offer temporary; Venice calls it -beta and removable without notice. Keep normal paid/subscription models as the -defaults. - -## Fast path - -The tracked `ox` launcher chooses the first authenticated route in a -known-working order and reuses local OpenCode API credentials when possible. - -```sh -ox status --live -ox run "implement the next scoped task and verify it" -ox ask "review this diff for correctness" # read-only Pi posture -ox each "compare three designs for this API" # read-only provider fanout -ox run --provider venice --harness grok "fix the test" -ox run --provider opencode --harness goose --dir ~/src/app "run the tests" -``` - -`ox run` is mutation-capable because it uses each harness's normal posture. -`ox ask` and `ox each` use `pi-inspect`; fanout outputs are saved below -`$XDG_STATE_HOME/ox/runs/` (default `~/.local/state/ox/runs/`). This preserves -the one-writer rule while making cheap parallel review/research easy. Automatic -selection does not retry through a different gateway after dispatch: silently -crossing from a ZDR route to a retaining route would violate the privacy labels. - -## Authentication - -Credentials remain local. Never add them to this repository. - -| Route | Environment variable | Account/key page | -|---|---|---| -| OpenCode Zen | `OPENCODE_ZEN_API_KEY` | | -| OpenRouter | `OPENROUTER_API_KEY` | | -| Command Code | `COMMAND_CODE_API_KEY` | | -| Nous Portal | Hermes OAuth | run `hermes portal` | -| Venice | `VENICE_API_KEY` | | - -On this Mac, OpenRouter and Venice keys are stored in the untracked -`~/.zsh_secrets`, and Nous is connected through Hermes OAuth with Portal -Privacy Mode enabled. Venice still refuses inference at a zero account balance -even for its $0 model, so it is locally disabled and `ox` skips it. Run -`ox enable venice` after adding an eligible free balance or intentionally buying -credits. No purchase or paid subscription was made. - -Run `opencode2 auth login` to store a key locally under the matching -configured provider. `ox` can reuse API-key entries from OpenCode or Pi without -copying the secret. `ox auth [provider]` prints the exact setup reminder. - -## Endpoint and privacy matrix - -| Route | Wire model ID | API root | Current privacy contract | -|---|---|---|---| -| OpenCode Zen | `x-preview-f-free` | `https://opencode.ai/zen/v1` | zero retention; no training | -| OpenRouter | `stealth/ox-alpha` | `https://openrouter.ai/api/v1` | prompts/completions retained; no training | -| Command Code | `stealth/ox-alpha` | `https://api.commandcode.ai/provider/v1` | retained; no training; Ox refuses ZDR mode | -| Nous Portal | `stealth/ox-alpha` | official: `https://inference-api.nousresearch.com/v1` | enable account Privacy Mode; metadata/exceptions remain | -| Venice | `stealth-ox-alpha` | `https://api.venice.ai/api/v1` | Venice does not log content, but upstream sees anonymized content | - -Do not send private repositories or secrets through a retaining/anonymized -route merely because the model name is the same. OpenCode Zen is the preferred -route for sensitive-but-approved workloads. - -Primary references: - -- OpenCode Zen model, pricing, retention, and endpoint: -- OpenRouter model and API: -- Command Code model/API/pricing: , -- Nous provider catalog and privacy: , -- Venice catalog, pricing, privacy, and beta policy: , - -## Harness coverage - -| Harness | Direct routes | -|---|---| -| Pi | all five through `~/.pi/agent/models.json` | -| OpenCode | all five; Zen/OpenRouter native plus three compatible providers | -| Hermes | Zen/OpenRouter/Nous native; Command/Venice named custom providers | -| Goose | all five declarative OpenAI-compatible providers | -| Grok Build | all five tracked custom models | -| Claude Code | none directly | - -Claude Code requires Anthropic Messages semantics and Anthropic does not support -non-Claude models behind gateways. A translating proxy would be an unsupported, -extra dependency, so the `ox` launcher refuses that combination rather than -pretending it is reliable. - -Tracked provider catalogs are secret-free and preserve the normal default model -in every harness. Pi and Hermes use `modify_` merges so runtime-owned providers, -OAuth state, and selections survive `chezmoi apply`. - -For non-Hermes harnesses, `ox` exposes the Nous OAuth session through Hermes's -loopback-only OpenAI-compatible proxy at `127.0.0.1:8645`; it starts the proxy -on demand and supplies only a dummy local bearer token. The real Nous credential -never leaves Hermes's auth store. diff --git a/docs/PLAYBOOKS.md b/docs/PLAYBOOKS.md index 0b1326d..b3be494 100644 --- a/docs/PLAYBOOKS.md +++ b/docs/PLAYBOOKS.md @@ -266,8 +266,8 @@ See **CLAUDE.md** for the full picture. Cheat sheet: |---|---|---| | Skill | `dot_claude/skills//SKILL.md` | Auto-loaded on next session by Claude Code's skill discovery | | Agent | `dot_claude/agents/.md` | Tab to switch, or `@` mention | -| Hook | Edit `dot_claude/settings.json` → `hooks` | Harness-executed on configured events | -| MCP server | Edit `dot_claude/settings.json` → `mcpServers` | Auto-connected on next session | +| Hook | Add to the `managed` block in `dot_claude/modify_settings.json` → `hooks` | Harness-executed on configured events | +| MCP server | Add to the `managed` block in `dot_claude/modify_settings.json` → `mcpServers` | Auto-connected on next session | | Slash command | `dot_claude/commands/.md` | `/` in-session | ```sh diff --git a/dot_config/goose/custom_providers/ox_command.json b/dot_config/goose/custom_providers/ox_command.json deleted file mode 100644 index 095637f..0000000 --- a/dot_config/goose/custom_providers/ox_command.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "ox_command", - "engine": "openai", - "display_name": "Ox Alpha — Command Code", - "description": "Limited-time free Ox Alpha via Command Code; not ZDR", - "api_key_env": "COMMAND_CODE_API_KEY", - "base_url": "https://api.commandcode.ai/provider/v1/chat/completions", - "models": [ - { - "name": "stealth/ox-alpha", - "context_limit": 1048576, - "input_token_cost": 0, - "output_token_cost": 0, - "reasoning": true - } - ], - "supports_streaming": true, - "requires_auth": true, - "preserves_thinking": true -} diff --git a/dot_config/goose/custom_providers/ox_nous.json b/dot_config/goose/custom_providers/ox_nous.json deleted file mode 100644 index d1436e6..0000000 --- a/dot_config/goose/custom_providers/ox_nous.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "ox_nous", - "engine": "openai", - "display_name": "Ox Alpha — Nous Portal", - "description": "Free-tier Ox Alpha through the local Hermes OAuth proxy", - "api_key_env": "NOUS_API_KEY", - "base_url": "http://127.0.0.1:8645/v1/chat/completions", - "models": [ - { - "name": "stealth/ox-alpha", - "context_limit": 1048576, - "input_token_cost": 0, - "output_token_cost": 0, - "reasoning": true - } - ], - "supports_streaming": true, - "requires_auth": true, - "preserves_thinking": true -} diff --git a/dot_config/goose/custom_providers/ox_opencode.json b/dot_config/goose/custom_providers/ox_opencode.json deleted file mode 100644 index e6319d3..0000000 --- a/dot_config/goose/custom_providers/ox_opencode.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "ox_opencode", - "engine": "openai", - "display_name": "Ox Alpha — OpenCode Zen (ZDR)", - "description": "Limited-time free Ox Alpha via OpenCode Zen", - "api_key_env": "OPENCODE_ZEN_API_KEY", - "base_url": "https://opencode.ai/zen/v1/chat/completions", - "models": [ - { - "name": "x-preview-f-free", - "context_limit": 1048576, - "input_token_cost": 0, - "output_token_cost": 0, - "reasoning": true - } - ], - "supports_streaming": true, - "requires_auth": true, - "preserves_thinking": true -} diff --git a/dot_config/goose/custom_providers/ox_openrouter.json b/dot_config/goose/custom_providers/ox_openrouter.json deleted file mode 100644 index a79b0d7..0000000 --- a/dot_config/goose/custom_providers/ox_openrouter.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "ox_openrouter", - "engine": "openai", - "display_name": "Ox Alpha — OpenRouter", - "description": "Free preview Ox Alpha via OpenRouter; prompts and completions retained upstream", - "api_key_env": "OPENROUTER_API_KEY", - "base_url": "https://openrouter.ai/api/v1/chat/completions", - "models": [ - { - "name": "stealth/ox-alpha", - "context_limit": 1048576, - "input_token_cost": 0, - "output_token_cost": 0, - "reasoning": true - } - ], - "supports_streaming": true, - "requires_auth": true, - "preserves_thinking": true -} diff --git a/dot_config/goose/custom_providers/ox_venice.json b/dot_config/goose/custom_providers/ox_venice.json deleted file mode 100644 index e3a1d2d..0000000 --- a/dot_config/goose/custom_providers/ox_venice.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "ox_venice", - "engine": "openai", - "display_name": "Ox Alpha — Venice", - "description": "Free beta Ox Alpha via Venice; anonymized third-party route", - "api_key_env": "VENICE_API_KEY", - "base_url": "https://api.venice.ai/api/v1/chat/completions", - "models": [ - { - "name": "stealth-ox-alpha", - "context_limit": 1048576, - "input_token_cost": 0, - "output_token_cost": 0, - "reasoning": true - } - ], - "supports_streaming": true, - "requires_auth": true, - "preserves_thinking": true -} diff --git a/dot_config/opencode/modify_private_cli.json b/dot_config/opencode/modify_private_cli.json index d742cb3..c6a96d3 100644 --- a/dot_config/opencode/modify_private_cli.json +++ b/dot_config/opencode/modify_private_cli.json @@ -8,6 +8,6 @@ printf '%s' "$current" | jq '. * { "diffs": {"wrap": "word"}, "session": {"sidebar": "auto", "scrollbar": false, "thinking": "hide", "grouping": "auto"}, "animations": true, - "tabs": {"layout": "horizontal", "enabled": true, "scope": "cwd"}, + "tabs": {"layout": "vertical", "enabled": true, "scope": "cwd"}, "debug": {"devtools": true} }' diff --git a/dot_config/opencode/opencode.jsonc b/dot_config/opencode/opencode.jsonc index b8fc676..6afd7c9 100644 --- a/dot_config/opencode/opencode.jsonc +++ b/dot_config/opencode/opencode.jsonc @@ -53,91 +53,6 @@ "watcher": { "ignore": ["**/.git/**", "**/node_modules/**"] }, - "providers": { - "opencode": { - "models": { - "x-preview-f-free": { - "name": "Ox Alpha Free (ZDR)", - "capabilities": { - "tools": true, - "input": ["text", "image", "video"], - "output": ["text"] - }, - "limit": { "context": 1048576, "output": 131072 } - } - } - }, - "openrouter": { - "models": { - "stealth/ox-alpha": { - "name": "Ox Alpha (retained upstream)", - "capabilities": { - "tools": true, - "input": ["text", "image", "video"], - "output": ["text"] - }, - "limit": { "context": 1048576, "output": 131072 } - } - } - }, - "ox-command": { - "package": "@opencode-ai/ai/providers/openai-compatible", - "name": "Command Code — Ox Alpha", - "settings": { - "baseURL": "https://api.commandcode.ai/provider/v1", - "apiKey": "{env:COMMAND_CODE_API_KEY}" - }, - "models": { - "stealth/ox-alpha": { - "name": "Ox Alpha (not ZDR)", - "capabilities": { - "tools": true, - "input": ["text", "image", "video"], - "output": ["text"] - }, - "limit": { "context": 1048576, "output": 131072 } - } - } - }, - "ox-nous": { - "package": "@opencode-ai/ai/providers/openai-compatible", - "name": "Nous Portal — Ox Alpha", - "settings": { - "baseURL": "http://127.0.0.1:8645/v1", - "apiKey": "{env:NOUS_API_KEY}" - }, - "models": { - "stealth/ox-alpha": { - "name": "Ox Alpha (enable Privacy Mode)", - "capabilities": { - "tools": true, - "input": ["text", "image", "video"], - "output": ["text"] - }, - "limit": { "context": 1048576, "output": 131072 } - } - } - }, - "ox-venice": { - "package": "@opencode-ai/ai/providers/openai-compatible", - "name": "Venice — Ox Alpha", - "settings": { - "baseURL": "https://api.venice.ai/api/v1", - "apiKey": "{env:VENICE_API_KEY}" - }, - "models": { - "stealth-ox-alpha": { - "name": "Ox Alpha (anonymized)", - "capabilities": { - "tools": true, - "input": ["text", "image", "video"], - "output": ["text"] - }, - "limit": { "context": 1048576, "output": 131072 } - } - } - } - }, "plugins": [ { "package": "blackbird-opencode@0.1.3", diff --git a/dot_hermes/modify_private_config.yaml b/dot_hermes/modify_private_config.yaml deleted file mode 100755 index c4a3d3c..0000000 --- a/dot_hermes/modify_private_config.yaml +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh -# chezmoi modify_ script -> ~/.hermes/config.yaml -# -# Hermes owns its selected model, OAuth state, and the rest of this file. -# Chezmoi adds only named, secret-free Ox endpoints that Hermes does not -# already provide natively. API keys remain environment/runtime state. - -set -eu - -current="$(cat)" -[ -n "$current" ] || current='{}' -if ! current_json="$(printf '%s' "$current" | yq -o=json '.' 2>/dev/null)"; then - # Preserve malformed runtime state for explicit repair instead of erasing it. - printf '%s\n' "$current" - exit 0 -fi - -managed='[ - { - "name": "ox-command", - "base_url": "https://api.commandcode.ai/provider/v1", - "key_env": "COMMAND_CODE_API_KEY", - "api_mode": "chat_completions", - "model": "stealth/ox-alpha", - "models": { - "stealth/ox-alpha": {"context_length": 1048576, "max_output_tokens": 131072} - } - }, - { - "name": "ox-venice", - "base_url": "https://api.venice.ai/api/v1", - "key_env": "VENICE_API_KEY", - "api_mode": "chat_completions", - "model": "stealth-ox-alpha", - "models": { - "stealth-ox-alpha": {"context_length": 1048576, "max_output_tokens": 131072} - } - } -]' - -printf '%s' "$current_json" | jq --argjson managed "$managed" ' - ($managed | map(.name)) as $names - | [(.custom_providers // [])[] | select(.name as $name | $names | index($name) | not)] as $unmanaged - | .custom_providers = ($unmanaged + $managed) -' | yq -p=json -o=yaml -P '.' diff --git a/dot_local/bin/executable_ox b/dot_local/bin/executable_ox deleted file mode 100755 index aa7a0d9..0000000 --- a/dot_local/bin/executable_ox +++ /dev/null @@ -1,513 +0,0 @@ -#!/usr/bin/env bash -# Run the free Ox Alpha preview consistently across installed agent harnesses. -set -euo pipefail -umask 077 - -providers=(opencode openrouter nous venice command) - -usage() { - cat <<'EOF' -Usage: - ox status [--live] - ox auth [provider] - ox enable PROVIDER - ox disable PROVIDER [REASON...] - ox run [--provider NAME] [--harness NAME] [--dir DIR] [--prompt-file FILE] [PROMPT...] - ox ask [--provider NAME] [--dir DIR] [--prompt-file FILE] [PROMPT...] - ox each [--dir DIR] [--prompt-file FILE] [PROMPT...] - -Providers: auto, opencode, openrouter, command, nous, venice -Harnesses: pi (default), opencode, hermes, goose, grok - -`run` is a normal mutation-capable agent run. `ask` is one read-only Pi run. -`each` fans the prompt out read-only to every authenticated provider and saves -results under ${XDG_STATE_HOME:-~/.local/state}/ox/runs/. -EOF -} - -env_name() { - case "$1" in - opencode) printf '%s\n' OPENCODE_ZEN_API_KEY ;; - openrouter) printf '%s\n' OPENROUTER_API_KEY ;; - command) printf '%s\n' COMMAND_CODE_API_KEY ;; - nous) printf '%s\n' NOUS_API_KEY ;; - venice) printf '%s\n' VENICE_API_KEY ;; - *) return 1 ;; - esac -} - -opencode_auth_id() { - case "$1" in - opencode) printf '%s\n' opencode ;; - openrouter) printf '%s\n' openrouter ;; - command) printf '%s\n' ox-command ;; - nous) printf '%s\n' ox-nous ;; - venice) printf '%s\n' ox-venice ;; - *) return 1 ;; - esac -} - -pi_auth_id() { - printf 'ox-%s\n' "$1" -} - -credential() { - local provider=$1 variable auth_file auth_id value - if [[ "$provider" == nous ]]; then - if command -v hermes >/dev/null 2>&1 && hermes auth status nous 2>/dev/null | grep -q 'logged in'; then - printf '%s' ox-local-proxy - return 0 - fi - return 1 - fi - variable=$(env_name "$provider") || return 1 - value=${!variable:-} - if [[ -n "$value" ]]; then - printf '%s' "$value" - return 0 - fi - - if [[ -r "$HOME/.zsh_secrets" ]] && command -v zsh >/dev/null 2>&1; then - value=$(zsh -dfc 'source "$HOME/.zsh_secrets"; print -rn -- "${(P)1}"' ox-key "$variable" 2>/dev/null || true) - if [[ -n "$value" ]]; then - printf '%s' "$value" - return 0 - fi - fi - - auth_file=${XDG_DATA_HOME:-"$HOME/.local/share"}/opencode/auth.json - auth_id=$(opencode_auth_id "$provider") - if [[ -f "$auth_file" ]]; then - value=$(jq -er --arg id "$auth_id" '.[$id] | select(.type == "api") | .key' "$auth_file" 2>/dev/null || true) - if [[ -n "$value" ]]; then - printf '%s' "$value" - return 0 - fi - fi - - auth_file=$HOME/.pi/agent/auth.json - auth_id=$(pi_auth_id "$provider") - if [[ -f "$auth_file" ]]; then - value=$(jq -er --arg id "$auth_id" '.[$id] | select(.type == "api_key" or .type == "api") | (.key // .apiKey)' "$auth_file" 2>/dev/null || true) - if [[ -n "$value" ]]; then - printf '%s' "$value" - return 0 - fi - fi - return 1 -} - -credential_source() { - local provider=$1 variable auth_file auth_id - if [[ "$provider" == nous ]]; then - if command -v hermes >/dev/null 2>&1 && hermes auth status nous 2>/dev/null | grep -q 'logged in'; then - printf 'Hermes OAuth proxy' - return 0 - fi - printf 'missing:Hermes-OAuth' - return 1 - fi - variable=$(env_name "$provider") || return 1 - if [[ -n ${!variable:-} ]]; then - printf 'env:%s' "$variable" - return 0 - fi - if [[ -r "$HOME/.zsh_secrets" ]] && command -v zsh >/dev/null 2>&1 && zsh -dfc 'source "$HOME/.zsh_secrets"; [[ -n "${(P)1}" ]]' ox-key "$variable" 2>/dev/null; then - printf 'file:~/.zsh_secrets' - return 0 - fi - auth_file=${XDG_DATA_HOME:-"$HOME/.local/share"}/opencode/auth.json - auth_id=$(opencode_auth_id "$provider") - if [[ -f "$auth_file" ]] && jq -e --arg id "$auth_id" '.[$id] | .type == "api" and ((.key // "") | length > 0)' "$auth_file" >/dev/null 2>&1; then - printf 'OpenCode auth:%s' "$auth_id" - return 0 - fi - auth_file=$HOME/.pi/agent/auth.json - auth_id=$(pi_auth_id "$provider") - if [[ -f "$auth_file" ]] && jq -e --arg id "$auth_id" '.[$id] | (.type == "api_key" or .type == "api")' "$auth_file" >/dev/null 2>&1; then - printf 'Pi auth:%s' "$auth_id" - return 0 - fi - printf 'missing:%s' "$variable" - return 1 -} - -model_id() { - case "$1" in - opencode) printf '%s\n' x-preview-f-free ;; - venice) printf '%s\n' stealth-ox-alpha ;; - openrouter|command|nous) printf '%s\n' stealth/ox-alpha ;; - *) return 1 ;; - esac -} - -catalog_url() { - case "$1" in - opencode) printf '%s\n' https://opencode.ai/zen/v1/models ;; - openrouter) printf '%s\n' https://openrouter.ai/api/v1/models ;; - command) printf '%s\n' https://api.commandcode.ai/provider/v1/models ;; - nous) printf '%s\n' https://inference-api.nousresearch.com/v1/models ;; - venice) printf '%s\n' https://api.venice.ai/api/v1/models ;; - *) return 1 ;; - esac -} - -privacy_label() { - case "$1" in - opencode) printf '%s' 'ZDR / no training' ;; - venice) printf '%s' 'anonymized upstream' ;; - nous) printf '%s' 'enable Portal Privacy Mode' ;; - openrouter|command) printf '%s' 'retained / no training' ;; - esac -} - -disabled_file() { - printf '%s/ox/disabled/%s\n' "${XDG_STATE_HOME:-$HOME/.local/state}" "$1" -} - -disabled_reason() { - local file - file=$(disabled_file "$1") - [[ -f "$file" ]] || return 1 - head -1 "$file" -} - -set_disabled() { - local provider=$1 reason=${2:-manually-disabled} file - model_id "$provider" >/dev/null || { printf 'ox: unknown provider: %s\n' "$provider" >&2; return 2; } - file=$(disabled_file "$provider") - mkdir -p "${file%/*}" - printf '%s\n' "$reason" > "$file" - printf 'ox: disabled %s (%s)\n' "$provider" "$reason" -} - -set_enabled() { - local provider=$1 file - model_id "$provider" >/dev/null || { printf 'ox: unknown provider: %s\n' "$provider" >&2; return 2; } - file=$(disabled_file "$provider") - rm -f "$file" - printf 'ox: enabled %s\n' "$provider" -} - -supports_route() { - local harness=$1 provider=$2 - case "$harness" in - claude) return 1 ;; - pi|opencode|hermes|goose|grok) return 0 ;; - *) return 1 ;; - esac -} - -has_route_credential() { - local provider=$1 harness=$2 - credential "$provider" >/dev/null 2>&1 -} - -nous_proxy_ready() { - curl -fsS --max-time 1 http://127.0.0.1:8645/v1/models -H 'Authorization: Bearer ox-local-proxy' 2>/dev/null \ - | jq -e '[.data[]? | select(.id == "stealth/ox-alpha")] | length > 0' >/dev/null 2>&1 -} - -ensure_nous_proxy() { - local state_dir pid_file log_file - nous_proxy_ready && return 0 - command -v hermes >/dev/null 2>&1 || { printf '%s\n' 'ox: Hermes is required for the Nous OAuth proxy' >&2; return 1; } - hermes auth status nous 2>/dev/null | grep -q 'logged in' || { printf '%s\n' 'ox: run `hermes portal` before using Nous' >&2; return 1; } - state_dir=${XDG_STATE_HOME:-"$HOME/.local/state"}/ox - pid_file=$state_dir/nous-proxy.pid - log_file=$state_dir/nous-proxy.log - mkdir -p "$state_dir" - (PYTHONUNBUFFERED=1 nohup hermes proxy start --provider nous --host 127.0.0.1 --port 8645 >"$log_file" 2>&1 & echo $! > "$pid_file") - for _ in {1..20}; do - nous_proxy_ready && return 0 - sleep 0.25 - done - printf 'ox: Nous proxy failed to start; see %s\n' "$log_file" >&2 - return 1 -} - -select_provider() { - local requested=$1 harness=$2 provider reason - if [[ "$requested" != auto ]]; then - if reason=$(disabled_reason "$requested" 2>/dev/null); then - printf 'ox: %s is disabled (%s); run `ox enable %s` to retry\n' "$requested" "$reason" "$requested" >&2 - return 1 - fi - supports_route "$harness" "$requested" || { - printf 'ox: %s does not directly support %s (protocol mismatch)\n' "$harness" "$requested" >&2 - return 1 - } - has_route_credential "$requested" "$harness" || { - printf 'ox: no credential for %s; run `ox auth %s`\n' "$requested" "$requested" >&2 - return 1 - } - printf '%s\n' "$requested" - return 0 - fi - for provider in "${providers[@]}"; do - disabled_reason "$provider" >/dev/null 2>&1 && continue - supports_route "$harness" "$provider" || continue - if has_route_credential "$provider" "$harness"; then - printf '%s\n' "$provider" - return 0 - fi - done - printf 'ox: no authenticated Ox provider supports %s; run `ox auth`\n' "$harness" >&2 - return 1 -} - -inject_credential() { - local provider=$1 variable key - variable=$(env_name "$provider") - key=$(credential "$provider") - export "$variable=$key" -} - -prepare_harness_route() { - local provider=$1 harness=$2 - if [[ "$provider" == nous && "$harness" != hermes ]]; then - ensure_nous_proxy - fi - if [[ "$harness" != hermes || "$provider" != nous ]]; then - inject_credential "$provider" - fi -} - -run_pi_harness() { - local provider=$1 model=$2 prompt=$3 - local pi_provider=ox-$provider - if command -v pi-commander >/dev/null 2>&1; then - pi-commander -p --provider "$pi_provider" --model "$model" --thinking high "$prompt" - else - pi -p --provider "$pi_provider" --model "$model" --thinking high "$prompt" - fi -} - -run_opencode_harness() { - local provider=$1 model=$2 prompt=$3 oc_model - case "$provider" in - opencode) oc_model=opencode/$model ;; - openrouter) oc_model=openrouter/$model ;; - *) oc_model=ox-$provider/$model ;; - esac - opencode2 run --model "$oc_model" "$prompt" -} - -run_hermes_harness() { - local provider=$1 model=$2 prompt=$3 hermes_provider - case "$provider" in - opencode) hermes_provider=opencode-zen ;; - openrouter|nous) hermes_provider=$provider ;; - *) hermes_provider=custom:ox-$provider ;; - esac - hermes --provider "$hermes_provider" --model "$model" --oneshot "$prompt" -} - -run_goose_harness() { - local provider=$1 model=$2 prompt=$3 - goose run --no-session --provider "ox_$provider" --model "$model" --text "$prompt" -} - -run_grok_harness() { - local provider=$1 prompt=$3 - grok --single "$prompt" --model "ox-$provider" -} - -run_harness() { - local harness=$1 provider=$2 prompt=$3 model - model=$(model_id "$provider") - prepare_harness_route "$provider" "$harness" - case "$harness" in - pi) run_pi_harness "$provider" "$model" "$prompt" ;; - opencode) run_opencode_harness "$provider" "$model" "$prompt" ;; - hermes) run_hermes_harness "$provider" "$model" "$prompt" ;; - goose) run_goose_harness "$provider" "$model" "$prompt" ;; - grok) run_grok_harness "$provider" "$model" "$prompt" ;; - claude) - printf '%s\n' 'ox: Claude Code requires Anthropic Messages and does not support Ox directly.' >&2 - return 2 - ;; - *) - printf 'ox: unknown harness: %s\n' "$harness" >&2 - return 2 - ;; - esac -} - -status() { - local live=${1:-false} provider source state model url body reason - printf '%-12s %-28s %-28s %s\n' PROVIDER AUTH PRIVACY MODEL - for provider in "${providers[@]}"; do - if reason=$(disabled_reason "$provider" 2>/dev/null); then - state="disabled:$reason" - elif source=$(credential_source "$provider" 2>/dev/null); then - state=ready - else - state=$source - fi - model=$(model_id "$provider") - printf '%-12s %-28s %-28s %s\n' "$provider" "$state" "$(privacy_label "$provider")" "$model" - done - if [[ "$live" == true ]]; then - printf '\nLive catalog:\n' - for provider in "${providers[@]}"; do - model=$(model_id "$provider") - url=$(catalog_url "$provider") - body=$(curl -LfsS --max-time 12 "$url" 2>/dev/null || true) - if jq -e --arg id "$model" '[.data[]? | select(.id == $id)] | length > 0' <<<"$body" >/dev/null 2>&1; then - printf ' %-10s present (re-check provider pricing/terms)\n' "$provider" - else - printf ' %-10s unavailable-or-catalog-changed\n' "$provider" - fi - done - fi -} - -auth_help() { - local provider=${1:-all} - case "$provider" in - all) - cat <<'EOF' -Authenticate only the routes you want; keys remain local: - opencode https://opencode.ai/auth OPENCODE_ZEN_API_KEY - openrouter https://openrouter.ai/keys OPENROUTER_API_KEY - command https://commandcode.ai/studio/api-keys COMMAND_CODE_API_KEY - nous `hermes portal` NOUS_API_KEY or Hermes OAuth - venice https://venice.ai/settings/api VENICE_API_KEY - -For OpenCode-backed reuse, run `opencode2 auth login`, select the matching -configured provider, and paste the key. `ox` can reuse those local API entries. -EOF - ;; - opencode) printf '%s\n' 'Create/copy a key at https://opencode.ai/auth, then run `opencode2 auth login` and select OpenCode Zen.' ;; - openrouter) printf '%s\n' 'Create a key at https://openrouter.ai/keys, then export OPENROUTER_API_KEY or log in through OpenCode.' ;; - command) printf '%s\n' 'Create a key at https://commandcode.ai/studio/api-keys, then export COMMAND_CODE_API_KEY or log in to ox-command through OpenCode.' ;; - nous) printf '%s\n' 'Run `hermes portal`; ox reuses Hermes OAuth through a loopback-only proxy. Enable Portal Privacy Mode separately.' ;; - venice) printf '%s\n' 'Create a key at https://venice.ai/settings/api, then export VENICE_API_KEY or log in to ox-venice through OpenCode.' ;; - *) printf 'ox: unknown provider: %s\n' "$provider" >&2; return 2 ;; - esac -} - -fanout() { - local prompt=$1 run_root run_dir provider key variable model pid rc any=false - run_root=${XDG_STATE_HOME:-"$HOME/.local/state"}/ox/runs - mkdir -p "$run_root" - run_dir=$(mktemp -d "$run_root/$(date -u +%Y%m%dT%H%M%SZ)-XXXXXX") - printf '%s\n' "$prompt" > "$run_dir/prompt.txt" - declare -a pids=() - for provider in "${providers[@]}"; do - disabled_reason "$provider" >/dev/null 2>&1 && continue - if ! key=$(credential "$provider" 2>/dev/null); then continue; fi - if [[ "$provider" == nous ]]; then ensure_nous_proxy || continue; fi - any=true - variable=$(env_name "$provider") - model=$(model_id "$provider") - ( - export "$variable=$key" - if command -v pi-inspect >/dev/null 2>&1; then - pi-inspect -p --provider "ox-$provider" --model "$model" --thinking high "$prompt" - else - pi -p --provider "ox-$provider" --model "$model" --thinking high --no-tools "$prompt" - fi - ) >"$run_dir/$provider.out" 2>"$run_dir/$provider.err" & - pids+=("$!:$provider") - done - if [[ "$any" != true ]]; then - printf '%s\n' 'ox: no authenticated providers; run `ox auth`' >&2 - return 1 - fi - for pid in "${pids[@]}"; do - provider=${pid#*:}; pid=${pid%%:*}; rc=0 - wait "$pid" || rc=$? - printf '%s\n' "$rc" > "$run_dir/$provider.rc" - done - printf 'Ox read-only fanout: %s\n' "$run_dir" - for provider in "${providers[@]}"; do - [[ -f "$run_dir/$provider.rc" ]] || continue - printf '\n===== %s (exit %s) =====\n' "$provider" "$(cat "$run_dir/$provider.rc")" - cat "$run_dir/$provider.out" - if [[ -s "$run_dir/$provider.err" ]]; then - printf '\n[stderr saved: %s]\n' "$run_dir/$provider.err" - fi - done -} - -mode=run -if (($#)); then - case "$1" in - status|auth|enable|disable|run|ask|each|help|-h|--help) mode=$1; shift ;; - esac -fi -case "$mode" in - help|-h|--help) usage; exit 0 ;; - status) - live=false - [[ ${1:-} == --live ]] && live=true - status "$live" - exit 0 - ;; - auth) - auth_help "${1:-all}" - exit 0 - ;; - enable) - [[ -n ${1:-} ]] || { printf '%s\n' 'ox: provider required' >&2; exit 2; } - set_enabled "$1" - exit 0 - ;; - disable) - [[ -n ${1:-} ]] || { printf '%s\n' 'ox: provider required' >&2; exit 2; } - disabled_provider=$1 - shift - set_disabled "$disabled_provider" "${*:-manually-disabled}" - exit 0 - ;; -esac - -provider=auto -harness=pi -workdir=$PWD -prompt_file= -while (($#)); do - case "$1" in - --provider) provider=${2:?missing provider}; shift 2 ;; - --harness) harness=${2:?missing harness}; shift 2 ;; - --dir) workdir=${2:?missing directory}; shift 2 ;; - --prompt-file) prompt_file=${2:?missing prompt file}; shift 2 ;; - --) shift; break ;; - -h|--help) usage; exit 0 ;; - -*) printf 'ox: unknown option: %s\n' "$1" >&2; exit 2 ;; - *) break ;; - esac -done - -if [[ -n "$prompt_file" ]]; then - prompt=$(<"$prompt_file") -else - prompt="$*" -fi -[[ -n "$prompt" ]] || { printf '%s\n' 'ox: a prompt or --prompt-file is required' >&2; exit 2; } -[[ -d "$workdir" ]] || { printf 'ox: directory not found: %s\n' "$workdir" >&2; exit 2; } -cd "$workdir" - -case "$mode" in - each) fanout "$prompt" ;; - ask) - provider=$(select_provider "$provider" pi) - if [[ "$provider" == nous ]]; then ensure_nous_proxy; fi - key=$(credential "$provider") - variable=$(env_name "$provider") - export "$variable=$key" - model=$(model_id "$provider") - if command -v pi-inspect >/dev/null 2>&1; then - pi-inspect -p --provider "ox-$provider" --model "$model" --thinking high "$prompt" - else - pi -p --provider "ox-$provider" --model "$model" --thinking high --no-tools "$prompt" - fi - ;; - run) - provider=$(select_provider "$provider" "$harness") - run_harness "$harness" "$provider" "$prompt" - ;; - *) usage; exit 2 ;; -esac diff --git a/dot_pi/private_agent/modify_private_models.json b/dot_pi/private_agent/modify_private_models.json deleted file mode 100755 index 19ad3d4..0000000 --- a/dot_pi/private_agent/modify_private_models.json +++ /dev/null @@ -1,122 +0,0 @@ -#!/bin/sh -# chezmoi modify_ script -> ~/.pi/agent/models.json -# -# Keep Ox Alpha's secret-free provider catalog portable while preserving every -# runtime-owned provider. Credentials stay in environment variables or local -# auth stores and are never written by chezmoi. - -set -eu - -current="$(cat)" -if [ -z "$current" ]; then - current='{}' -elif ! printf '%s' "$current" | jq -e 'type == "object"' >/dev/null 2>&1; then - # A malformed runtime file needs repair, not silent replacement. - printf '%s\n' "$current" - exit 0 -fi - -managed='{ - "ox-opencode": { - "baseUrl": "https://opencode.ai/zen/v1", - "api": "openai-completions", - "apiKey": "$OPENCODE_ZEN_API_KEY", - "compat": { - "supportsDeveloperRole": false, - "supportsReasoningEffort": false, - "maxTokensField": "max_tokens" - }, - "models": [{ - "id": "x-preview-f-free", - "name": "Ox Alpha (OpenCode Zen; ZDR)", - "reasoning": true, - "input": ["text", "image"], - "contextWindow": 1048576, - "maxTokens": 131072, - "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0} - }] - }, - "ox-openrouter": { - "baseUrl": "https://openrouter.ai/api/v1", - "api": "openai-completions", - "apiKey": "$OPENROUTER_API_KEY", - "compat": { - "supportsDeveloperRole": false, - "supportsReasoningEffort": false, - "maxTokensField": "max_tokens", - "thinkingFormat": "openrouter", - "sessionAffinityFormat": "openrouter" - }, - "models": [{ - "id": "stealth/ox-alpha", - "name": "Ox Alpha (OpenRouter; retained)", - "reasoning": true, - "input": ["text", "image"], - "contextWindow": 1048576, - "maxTokens": 131072, - "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0} - }] - }, - "ox-command": { - "baseUrl": "https://api.commandcode.ai/provider/v1", - "api": "openai-completions", - "apiKey": "$COMMAND_CODE_API_KEY", - "compat": { - "supportsDeveloperRole": false, - "supportsReasoningEffort": false, - "maxTokensField": "max_tokens" - }, - "models": [{ - "id": "stealth/ox-alpha", - "name": "Ox Alpha (Command Code; retained)", - "reasoning": true, - "input": ["text", "image"], - "contextWindow": 1048576, - "maxTokens": 131072, - "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0} - }] - }, - "ox-nous": { - "baseUrl": "http://127.0.0.1:8645/v1", - "api": "openai-completions", - "apiKey": "$NOUS_API_KEY", - "compat": { - "supportsDeveloperRole": false, - "supportsReasoningEffort": false, - "maxTokensField": "max_tokens", - "thinkingFormat": "openrouter" - }, - "models": [{ - "id": "stealth/ox-alpha", - "name": "Ox Alpha (Nous Portal)", - "reasoning": true, - "input": ["text", "image"], - "contextWindow": 1048576, - "maxTokens": 131072, - "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0} - }] - }, - "ox-venice": { - "baseUrl": "https://api.venice.ai/api/v1", - "api": "openai-completions", - "apiKey": "$VENICE_API_KEY", - "compat": { - "supportsDeveloperRole": false, - "supportsReasoningEffort": true, - "maxTokensField": "max_tokens" - }, - "models": [{ - "id": "stealth-ox-alpha", - "name": "Ox Alpha (Venice; anonymized)", - "reasoning": true, - "input": ["text", "image"], - "contextWindow": 1048576, - "maxTokens": 131072, - "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0} - }] - } -}' - -printf '%s' "$current" | jq --argjson managed "$managed" ' - .providers = ((.providers // {}) * $managed) -' diff --git a/provision/retired-files.sha256 b/provision/retired-files.sha256 index 74e0d08..cd3ed27 100644 --- a/provision/retired-files.sha256 +++ b/provision/retired-files.sha256 @@ -7,3 +7,11 @@ c594c224b7df2a41703f79989047548989e302a2ee24a03924e28bdcbd379758 .config/opencode/subtask2.jsonc 37431148208dcf5df395127c462fc98feabc90af0e374b429ad8c193c282b23a .config/opencode/worktree.conf cbbb9abf9c46438584bbe13a31f763a6f43b31fd096d4eaaf33d159fb6f2262b tests/gha-local-smoke.sh +cfcc9b62886cf4c69f4972e5a865832a1660a7d70300f84cd629e9a7f9506a33 .local/bin/ox +d0c8a44d34c2af909139afff714febdfb0031fd605c1821280ad87b4d093f84e .config/goose/custom_providers/ox_command.json +f6560c9fa1214c008ef608fcb53f47bb8628561b104e7f1a5190aa93c142c2be .config/goose/custom_providers/ox_nous.json +a722b81001c5198d94bab0910cfd7c8dd2b7780ac8d7875d2040f6ae25886581 .config/goose/custom_providers/ox_opencode.json +5704f5bed3d47a48ee2777a37d1999218e4a90854c4e270c510dde4fb0052961 .config/goose/custom_providers/ox_openrouter.json +abf532e487576ecca794ab0e5e30ebf6f020fb5e87d453c0d9bb5fd6abb202c3 .config/goose/custom_providers/ox_venice.json +b312dd78435c2e04b06af249dad7ad978d417c3cd521c53a552c5675c1d82fcb .pi/agent/models.json +c27c3ad1d7add01086fecafd4ccd730c667acc4acaf32daf4fadf2e6a79449fe CLAUDE.md diff --git a/scripts/bootstrap/converge.sh b/scripts/bootstrap/converge.sh index ae14dd4..448cfca 100755 --- a/scripts/bootstrap/converge.sh +++ b/scripts/bootstrap/converge.sh @@ -11,6 +11,7 @@ chezmoi apply --source "$root" mise trust "${XDG_CONFIG_HOME:-$HOME/.config}/mise/conf.d/dotfiles.toml" # Install the selected optional inventories from the rendered global config. mise -C "$HOME" install --yes +bash "$root/scripts/bootstrap/skills.sh" bash "$root/scripts/bootstrap/refresh-shell.sh" bash "$root/scripts/bootstrap/integrations.sh" # Optional native installers can seed config. Reconcile managed keys while diff --git a/scripts/bootstrap/skills.sh b/scripts/bootstrap/skills.sh new file mode 100755 index 0000000..06ac269 --- /dev/null +++ b/scripts/bootstrap/skills.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Seed the shared skill tree. +# +# ~/.agents/skills is enrolled in native live preferences, so .chezmoiignore +# excludes it and `chezmoi apply` never writes there. dot_agents/skills is +# therefore a seed, not a copy -- but nothing was seeding it. A skill tracked +# here but absent from the live tree left every harness adapter dangling, and +# because the adapters are symlinks the failure is silent at the filesystem +# level and only shows up as a skill that never loads. +# +# Seed missing files only. The live tree stays authoritative: an existing file +# is never overwritten, so local edits and history restores always win. +set -euo pipefail +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +seeds="$root/dot_agents/skills" +live="$HOME/.agents/skills" + +[[ -d "$seeds" ]] || exit 0 + +seeded=0 +while IFS= read -r -d '' source; do + relative="${source#"$seeds"/}" + target="$live/$relative" + [[ -e "$target" ]] && continue + mkdir -p "$(dirname "$target")" + cp "$source" "$target" + seeded=$((seeded + 1)) + echo "Seeded shared skill file: ~/.agents/skills/$relative" +done < <(find "$seeds" -type f -print0) + +[[ "$seeded" -eq 0 ]] || echo "Seeded $seeded shared skill file(s) into the live tree." diff --git a/tests/bootstrap/fixtures.sh b/tests/bootstrap/fixtures.sh index 16ca193..10b1c1c 100755 --- a/tests/bootstrap/fixtures.sh +++ b/tests/bootstrap/fixtures.sh @@ -125,6 +125,4 @@ fi echo 'PASS: optional harness selection' [[ -f "$home/.config/opencode/skill/repo-onboarding/SKILL.md" ]] [[ "$(printf '%s' 'malformed-json' | "$root/dot_pi/private_agent/modify_settings.json")" == malformed-json ]] -[[ "$(printf '%s' 'malformed-json' | "$root/dot_pi/private_agent/modify_private_models.json")" == malformed-json ]] -[[ "$(printf '%s' 'not: [valid' | "$root/dot_hermes/modify_private_config.yaml")" == 'not: [valid' ]] echo 'PASS: shared skill resolves and malformed Pi state survives'