opencode: two profiles, per-profile permissions, shared agent prompts

oc-work: local models only on the local key, edits and most commands ask,
loads instructions/work.md. oc-local (also oc, opencode): every model,
defaults to qwen3.8-flash-next-iq3s-med, edits and commands run except
destructive and outward-facing ones. On sctfw004 oc-local presents the work
key. personal.json and work-strict.json removed.

Agents: drop variant, which has no effect on LiteLLM models. dev takes its
permissions from the profile. The read-only agents deny redirects and the
write/exec flags of allowed commands, swap find for fd, and only allow
listing forms of git branch. review defaults to the uncommitted diff.

CLAUDE.md: add the comment, process and shell-tool rules from the laptop's
AGENTS.md, and note that OpenCode reads this file.
This commit is contained in:
connor committed 2026-11-02 10:34:38 -05:00
1 parent 7ad155c4b2
commit 275a5bfb2b
16 files changed
+439 -680

No files matched your search

+20 -18
View File
@@ -1,14 +1,18 @@
# ---------------------------------------------------------------------------
# OpenCode profile launchers (Phase 5)
#
# Four profiles, one LiteLLM proxy. The profile decides WHICH KEY is used, and
# the key decides which models are reachable -- enforcement lives in LiteLLM
# (Phase 4 access groups), not here. This file only picks a key and a config.
# Two profiles, one LiteLLM proxy. The profile picks a config (models,
# permissions) and a key; the key decides which models are reachable --
# enforcement lives in LiteLLM access groups, not here.
#
# opencode / oc personal anthropic + openai + local
# oc-work work openai + local NO anthropic
# oc-work-strict work-strict openai + local NO anthropic, PRC never
# oc-local local local only, no egress
# oc-work work.json local models only, tight permissions
# oc-local / oc local.json every model, defaults to local Qwen3.8,
# looser permissions
#
# oc-work uses the LiteLLM `local` key, so nothing leaves the machine.
# oc-local uses the `personal` key, except on sctfw004, where it uses the
# `work` key (local + OpenAI): that laptop must never reach Anthropic, and the
# Anthropic entries in local.json 403 there.
#
# NO SECRET IS STORED HERE. Keys are read from rbw at launch and passed to the
# process as LITELLM_KEY, which the config files reference as {env:LITELLM_KEY}.
@@ -74,9 +78,7 @@ _oc_launch() {
# that survives for the rest of the shell if the profile is exported.
local colour
case "$profile" in
personal) colour=$'\e[1;36m' ;; # cyan
work) colour=$'\e[1;33m' ;; # yellow
work-strict) colour=$'\e[1;31m' ;; # red
local) colour=$'\e[1;32m' ;; # green
esac
print -u2 "${colour}┌─ opencode profile: ${(U)profile} ─┐\e[0m"
@@ -95,8 +97,8 @@ _oc_launch() {
# ~/.config/opencode/opencode.json is a GLOBAL BASE that opencode merges into
# every other config -- OPENCODE_CONFIG adds to it, it does not replace it, and
# neither does OPENCODE_CONFIG_DIR. Verified: with the personal profile parked
# at that path, `oc-local models` listed the Anthropic models too. So personal
# lives at personal.json like every other profile, and the file at the default
# at that path, `oc-local models` listed the Anthropic models too. So every
# profile lives under its own name, and the file at the default
# path is a two-line guard holding only `enabled_providers: ["litellm"]`.
#
# That guard is a safety net, not decoration: without it a bare `opencode` run
@@ -119,10 +121,10 @@ _oc_launch() {
#
# The `plan` agent is native to opencode, not part of a plugin.
#
# Personal is still wrapped under the bare name so `opencode` does the
# expected thing and gets a key.
opencode() { _oc_launch personal litellm-key-personal "$HOME/.config/opencode/personal.json" "$@" }
oc() { _oc_launch personal litellm-key-personal "$HOME/.config/opencode/personal.json" "$@" }
oc-work() { _oc_launch work litellm-key-work "$HOME/.config/opencode/work.json" "$@" }
oc-work-strict() { _oc_launch work-strict litellm-key-work-strict "$HOME/.config/opencode/work-strict.json" "$@" }
oc-local() { _oc_launch local litellm-key-local "$HOME/.config/opencode/local.json" "$@" }
# The bare names go to oc-local so `opencode` does the expected thing and gets
# a key.
_oc_local_key() { [[ ${(L)HOST} == sctfw004 ]] && print litellm-key-work || print litellm-key-personal }
oc-work() { _oc_launch work litellm-key-local "$HOME/.config/opencode/work.json" "$@" }
oc-local() { _oc_launch local "$(_oc_local_key)" "$HOME/.config/opencode/local.json" "$@" }
oc() { oc-local "$@" }
opencode() { oc-local "$@" }