Commit Graph
14 Commits
Author SHA1 Message Date
noconnorandClaude Opus 5.5 9cb18a795d Coexist with a desktop that already manages its tools (Omarchy)
On an Omarchy laptop bootstrap left two of almost everything: VS Code from
both pacman and the tarball (two menu entries, one shared ~/.config/Code
profile), and kubectl/helm/k9s/kubectx/yq/jq/node/ansible in ~/.local/bin
behind mise and pacman copies that always won on PATH. The zsh helpers
(op*, grmb, secrets.env) also never reached the bash login shell.

- lib/fetch.sh: add `shadowed`, true when another copy of a tool comes
  earlier on PATH than ~/.local/bin. Always false where zshrc puts
  ~/.local/bin first, so VM installs are unchanged.
- bin/, node/, ansible/: skip tools that are shadowed.
- vscode/: defer to any system `code`, and remove a tarball copy this
  module installed earlier.
- bash/: new module; bash port of the zshrc helpers, sourced from
  ~/.bashrc via a marked block. No-ops without ~/.bashrc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:44:18 -04:00
noconnorandClaude Opus 5.5 41a90a10fb zsh: verify opc/opd against the vault and explain failures
op item edit can exit non-zero with "(404) Not Found" after an edit it
did save (seen with op 2.34.1 and 2.39.0 on an existing item), which
made opc report failure and skip the ops refresh. opc now reads the
field back and opd re-checks the item's labels, then prints either the
op:// reference it changed or what failed, op's message without its
timestamp prefix, and where to look next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:49:10 +00:00
noconnorandClaude Opus 5.5 5f0dec5270 zsh: let opc take NAME=VALUE and prompt when the value is missing
`opc NAME=VALUE` sets a field directly; `opc NAME` or `opc NAME=` prompts
with hidden input, and an empty answer changes nothing. The old
`opc NAME VALUE` form still works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:43:27 +00:00
noconnorandClaude Opus 5 86b8e51646 Remove the docker module: dotfiles no longer installs Docker
Installing Docker Engine is a host/image decision, not a per-user
environment one. In Coder workspaces (unprivileged pods, no systemd) the
module ran get.docker.com on every start for a daemon that can never
run there. Where Docker is wanted, install it on the host or bake it
into the image.

Also drops the zshrc newgrp-docker hook that existed only for this
module's group change, and the docs/skills references to docker/.
docker-rootless-cgroup/ (configures Kasm's existing rootless daemon)
and bin/'s kind gate (detects a daemon) stay.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 13:39:59 -04:00
noconnorandClaude Opus 5 1159197e4b secrets: rename the vault helpers from env* to op*
They act on 1Password, not on the environment, so the prefix now says so:
opr (read), opc (create/update), opd (delete), opl (list), ops (refresh).
No behavior change. Checked the new names don't collide with any command
or oh-my-zsh alias, and verified opr/opl/ops against the real vault
(values compared, not printed).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 16:36:20 +00:00
noconnorandClaude Opus 5 c6e5f22acb secrets: read the 1Password vault once into a 0600 env file
Secrets were pulled in each box's hand-written ~/.zshrc.local with one
`op read` per field on EVERY new shell: every terminal waited on
1Password, every shell broke when it was unreachable, and the loop lived
outside the repo.

New secrets/ module (after bin/, which provides op and jq): one vault per
user and one item, both defaulting to the login name, whose field labels
are the variable names. It asks for the service-account token once
(hidden), fetches the item with a single `op item get`, and writes every
valid-named field to ~/.config/dotfiles/secrets.env (0600), replaced
atomically so a failed fetch keeps the previous copy. Values go through
jq @sh, so quotes, $() and newlines in a secret can't break or inject.
The token is kept in op.env (0600), which shells never source.

bootstrap loads secrets.env right after the module, so mcp-config takes
LITELLM_KEY from the env instead of prompting. A LITELLM_KEY rotated in
the vault also updates ~/.config/mcp-sync/key, since sync.sh re-exports
from that file after secrets.env loads and would otherwise win.

The managed zshrc sources secrets.env (no op call at startup) and adds
envr / envc / envd / envl / envs to read, upsert, delete, list and
refresh; they load the token only in a subshell.

Existing boxes still need the old op loop removed from ~/.zshrc.local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:30:24 +00:00
noconnorandClaude Opus 5 135112eee5 mcp-config/hermes: make the gateway's context limits actually reach the clients
Folds in the in-flight local-checkout work and fixes the context accounting
on top of it. Both halves are the same bug seen from two ends: clients were
sending requests larger than the backend's window and getting hard
ContextWindowExceededError 400s, intermittently, in a way that looked like
the model or the GPU misbehaving.

Already in flight here (unchanged in intent):
  - sync.sh prefers the LOCAL CHECKOUT over re-downloading from Gitea, and
    install.sh records the checkout path so it can be found. That recording
    is load-bearing: without it sync.sh cannot find the repo, silently falls
    back to Gitea, and every local edit to opencode.gateway.json is ignored.
    Observed on this box — ~/.config/mcp-sync/source did not exist, the
    default ~/.dotfiles is not where the checkout lives, and the Gitea base
    still served models:{}.
  - the hermes custom-provider wiring.

Fixed on top:

opencode had NO context limit on ANY gateway model, not a wrong one. Two
faults compounded. /model/info 403s for a day-to-day virtual key (only
management-scoped keys may read it), which sync.sh treats as best-effort and
falls back to names-only — as designed and as commented. But the merge then
did `prov["models"] = models`, replacing the map wholesale, so limits
declared in the base config were discarded too and there was no floor left.
Base specs are now a floor that live /model/info overrides field by field:
discovery still wins where present, so this adds no drift, and a 403 no
longer strips the limits. Verified: ornith 195584/32768, the qwens
261120/32768.

hermes' model map named `qwen3-vl-30b-a3b` at 262144 — a model the gateway
stopped serving when Ornith replaced it — while ornith-1.0-35b, the model
actually on cg-1 AND the only one with a window smaller than its neighbours
(196608), had no entry at all and inherited Hermes' default. The one model
needing a lower ceiling was the one not declaring one, which is exactly the
shape that fails only once a session grows.

NOTE for other boxes: hermes reads ~/.hermes/config.yaml, which install.sh
produces by MERGING gateway.yaml. Editing gateway.yaml alone changes nothing
until install.sh re-runs — this machine's live config was patched by hand.

Not addressed here, deliberately: the gateway-side context_window_fallback
(ai-k8s-iac). It is wired but inert while cg-2 is scaled down, so preventing
the oversized request client-side is what actually works today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FoDK4wwB1Twty5gwiriYLf
2026-09-11 16:38:03 -04:00
noconnorandClaude Opus 5 098b1e377f git+zsh: stop tab-completion producing branch deletes that fail
`git push origin --delete <TAB>` completes to `origin/foo`, but `origin/foo` is
a LOCAL remote-tracking name — the ref on the server is just `foo`. So the
command zsh completes for you is the one git rejects, and it rejects it with
"remote ref does not exist", which reads as "that branch is gone" rather than
"you named it wrong".

The second half is worse, because it makes the first half look intermittent:
without pruning, `git branch -r` and the completion keep offering branches that
were deleted on the server weeks ago. Then some completions fail because the
branch is genuinely gone and others fail because of the prefix, with identical
error text.

Two fixes, one per cause:

- `fetch.prune = true` globally, so stale remote-tracking refs are dropped on
  every fetch/pull and dead branches stop being offered at all.
- `grmb`, which takes either form, prunes first, and reports per-branch rather
  than aborting the batch. `grmb origin/foo` and `grmb foo` both work.

Named grmb rather than the more obvious gbd because oh-my-zsh's git plugin
already binds gbd to `git branch --delete`. Aliases resolve before functions,
so the collision would not have errored — it would have silently tried to
delete a LOCAL branch when the whole point is the remote one. Caught by
`type gbd` after installing; check any new g* helper the same way.

grmb deliberately does not wrap `git push` itself — intercepting a git
subcommand to rewrite its arguments is the kind of thing that works until it
silently does the wrong thing to a refspec someone meant literally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 14:58:22 -04:00
noconnorandClaude Opus 4.8 e42807c054 hermes: wire into dotfiles; fix opencode's generated context limits
Two client-side fixes against the covert-gpt LiteLLM gateway, both traced
from opencode throwing intermittent ContextWindowExceededError 400s.

opencode (mcp-config/sync.sh):
  spec_for() mapped LiteLLM's /model/info onto opencode's schema 1:1, but the
  two mean different things by "output". LiteLLM's max_output_tokens is the
  whole window (vLLM caps TOTAL context, not output separately, which is
  correct for LiteLLM); opencode's limit.output is a RESERVE it subtracts from
  context to pick a compaction point. Copying one to the other told opencode to
  reserve the entire 262144 window, so it targeted an input of context-output
  and the server counted one token more than opencode did -- landing on
  context+1 and a hard 400, every time (measured: 196609 in + 65536 out =
  262145 vs a 262144 window; the retries shrinking max_tokens by 65 never
  helped because the input grew to compensate).

  Capping the reserve alone would NOT have fixed it -- opencode would still sit
  exactly on the boundary, just at a different input size. So: cap the reserve
  (OUTPUT_RESERVE_CAP) and hold a margin back from context
  (CONTEXT_SAFETY_MARGIN). Generated at sync time, so it fixes every gateway
  model at once, including third-party ones.

hermes (hermes/gateway.yaml, hermes/install.sh):
  Hermes reached the gateway by pretending it WAS OpenRouter -- the LiteLLM key
  in OPENROUTER_API_KEY, the gateway in OPENROUTER_BASE_URL. Requests routed
  fine, but fetch_openrouter_models hardcodes openrouter.ai/api/v1/models and
  ignores OPENROUTER_BASE_URL, so the picker listed OpenRouter's ~342-model
  catalog -- models this gateway cannot serve -- while the gateway's own models,
  including cg-1's, never appeared at all.

  Replaced with a named custom provider. install.sh now merges gateway.yaml into
  ~/.hermes/config.yaml, so a fresh box gets a Hermes that can actually talk to
  the gateway instead of one pointed at nothing. The merge is idempotent and
  narrow: only the covert-gpt entry is touched, model.default/provider are
  seeded only when unset, and a box still on the masquerade is migrated off it
  (fingerprint: provider openrouter + base_url pointing at this gateway; a
  genuine OpenRouter user never has that, and is left alone).

  No secrets: gateway.yaml names the key via key_env, and mcp-config/sync.sh is
  what exports it. Hermes needs no context safety margin -- it omits max_tokens
  entirely, so vLLM bounds output by whatever context is left.

Also folds in the in-flight hermes bootstrap wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:38:12 -04:00
noconnorandClaude Opus 4.8 c0d67f023a mcp-config: sync shared opencode skills from the repo
Models and MCP servers are discovered from the LiteLLM gateway at sync time
(/v1/models, and the aggregate /mcp/ endpoint). Skills have no gateway side to
query: LiteLLM's /v1/skills is a passthrough to Anthropic's Skills API, which
lists skills on an Anthropic account rather than serving skill files, and
/public/skill_hub is a marketplace catalog. So the repo is the source of truth
and the tree is fetched whole, from the repo tarball.

Deploy to ~/.config/mcp-sync/skills rather than ~/.config/opencode/skills so
the atomic replace can never delete a skill the user wrote by hand; opencode
picks the tree up via skills.paths, which expands {env:HOME} — verified — so
the shared config stays identical for every user.

Same throttle and non-fatal discipline as the config sync: a fetch that yields
no skills/ leaves the last-good tree in place instead of emptying it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:26:17 -04:00
noconnorandClaude Opus 4.8 efca892109 zsh/: source ~/.zshrc.local for per-machine secrets
~/.zshrc is managed and overwritten on every bootstrap, so there was
nowhere to put a machine-local key. The managed rc now sources
~/.zshrc.local last (so it can override anything) and bootstrap never
touches that file. zshrc.local.example documents the variables modules
look for: GITEA_KEY, LITELLM_KEY, OP_SERVICE_ACCOUNT_TOKEN.

Secrets stay out of the repo — zsh/zshrc is shared config applied to
every box.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 04:40:14 -04:00
noconnorandClaude Opus 4.8 3751836c5c docker/: install rootful Docker; gate kind on it
kind never worked reliably against rootless Docker on Kasm, even with the
cgroup delegation from docker-rootless-cgroup/. So install full Docker
where we can, and flip bin/'s kind gate from "rootless is available" to
"a rootful daemon answers" — kind is simply skipped on Kasm now.

docker/install.sh no-ops when a rootless daemon is already running so it
never fights Kasm's setup. It runs before bin/ in bootstrap so the kind
gate can see the daemon it just started. zsh/zshrc re-execs via newgrp
when the account gained the docker group but the live shell predates it.

docker-rootless-cgroup/ keeps its delegation (still useful for
per-container resource limits); only its kind-centric comments change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 04:39:58 -04:00
noconnorandClaude Opus 4.8 fd88d8bba2 Add bin/ module: userland CLI tools into ~/.local/bin
New bin/install.sh downloads self-contained release binaries (ripgrep, fd,
bat, fzf, jq) into ~/.local/bin — no sudo, nothing outside $HOME, so they
persist across Kasm redeploys with no reinstall. Statically linked where
available, so they keep working even after the root fs is wiped. Idempotent:
pinned versions already present are skipped.

lib/fetch.sh adds arch detection + tarball/raw binary download helpers.
zsh/zshrc puts ~/.local/bin on PATH; bootstrap.sh runs the module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 10:25:18 -04:00
noconnorandClaude Sonnet 5 d4e3121d68 Add bootstrap.sh with zsh and git modules
Cross-distro (RHEL/Debian) VM setup: zsh + oh-my-zsh with a self-healing
bash fallback for environments that reset the root filesystem or hardcode
bash for new terminals (e.g. Kasm Workspaces), plus git identity and
HTTPS credential-store configuration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 09:36:52 -04:00