Compare commits
2
Commits
f4fca1797f
...
ffad7e56ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffad7e56ea | ||
|
|
34bc1663ed |
No files matched your search
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env zsh
|
||||
# The Mac mini's shell. macOS stays on zsh: its bash is 3.2, and
|
||||
# config/bash/shell leans on Linux-only tools (stat -c, ksshaskpass, the nvm
|
||||
# profile script), so this is a small zsh counterpart rather than a port.
|
||||
# Linked to ~/.zshrc. Homebrew's PATH comes from ~/.zprofile (brew shellenv).
|
||||
export PANAMA_PATH="$HOME/.local/share/Panama"
|
||||
|
||||
export EDITOR="nvim"
|
||||
export SUDO_EDITOR="$EDITOR"
|
||||
|
||||
export BUN_INSTALL="$HOME/.bun"
|
||||
export PATH="$HOME/.local/bin:$BUN_INSTALL/bin:$PANAMA_PATH/bin:$PATH"
|
||||
|
||||
# History shared across sessions, so tmux panes see each other's commands.
|
||||
HISTFILE="$HOME/.zsh_history"
|
||||
HISTSIZE=50000
|
||||
SAVEHIST=50000
|
||||
setopt SHARE_HISTORY HIST_IGNORE_DUPS HIST_IGNORE_SPACE
|
||||
|
||||
# Emacs keys, like bash's readline: ctrl-a/e/r work as they do on Linux.
|
||||
bindkey -e
|
||||
|
||||
autoload -Uz compinit && compinit
|
||||
[ -s "$BUN_INSTALL/_bun" ] && source "$BUN_INSTALL/_bun"
|
||||
|
||||
# Same SSH behaviour as config/bash/shell: an SSH login lands in a tmux
|
||||
# session that survives a dropped connection. PANAMA_SSH_TMUX=off opts out.
|
||||
if [[ -n "$SSH_CONNECTION" && -z "$TMUX" && -o interactive &&
|
||||
"${PANAMA_SSH_TMUX:-on}" != "off" ]] && command -v tmux >/dev/null 2>&1; then
|
||||
exec tmux new-session -A -s main
|
||||
fi
|
||||
|
||||
alias c="clear"
|
||||
alias n="nvim"
|
||||
alias n.="nvim ."
|
||||
alias ..="cd .."
|
||||
alias ...="cd ../.."
|
||||
alias ....="cd ../../.."
|
||||
alias letscode="ranger ~/Documents/Documents/Code"
|
||||
alias commit-repo='git add -A && git commit -m "Update stuff" && git push'
|
||||
alias ls='eza -lh --group-directories-first --icons=auto'
|
||||
alias lsa='ls -a'
|
||||
alias lt='eza --tree --level=2 --long --icons=auto --git'
|
||||
alias ff="fzf --preview 'bat --style=numbers --color=always {}'"
|
||||
compress() { tar -czf "${1%/}.tar.gz" "${1%/}"; }
|
||||
alias decompress="tar -xzf"
|
||||
|
||||
# Xcode cannot sign from an SSH session while the login keychain is locked
|
||||
# (errSecInternalComponent). Run this once per session before a build.
|
||||
alias unlock-keychain='security unlock-keychain ~/Library/Keychains/login.keychain-db'
|
||||
|
||||
eval "$(zoxide init zsh)"
|
||||
source <(fzf --zsh)
|
||||
eval "$(oh-my-posh init zsh --config "$PANAMA_PATH/config/dot/ohmyposh/gib.omp.json")"
|
||||
+62
-7
@@ -18,9 +18,15 @@
|
||||
# This is personal content, so it obeys the same interview decision link-user
|
||||
# does. A machine that never said yes registers nothing.
|
||||
#
|
||||
# Only Claude Code is handled. Codex stores its servers inside config.toml, and
|
||||
# rewriting a section of somebody's live TOML is a worse failure mode than
|
||||
# leaving two lines for them to paste once.
|
||||
# Claude Code and opencode are handled. Codex stores its servers inside
|
||||
# config.toml, and rewriting a section of somebody's live TOML is a worse
|
||||
# failure mode than leaving two lines for them to paste once.
|
||||
#
|
||||
# opencode has no CLI for this either, but its config is plain JSON, so this
|
||||
# stage merges two keys into it with jq: one mcp entry per server row, and the
|
||||
# rules directory under instructions, because opencode reads ~/.claude/CLAUDE.md
|
||||
# and ~/.claude/skills on its own but not ~/.claude/rules. Every other key in
|
||||
# that file is left as it was.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -44,8 +50,12 @@ if [[ "$decision" != "yes" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! command -v claude >/dev/null 2>&1; then
|
||||
log "Claude Code is not installed; nothing to register."
|
||||
has_claude=false
|
||||
has_opencode=false
|
||||
command -v claude >/dev/null 2>&1 && has_claude=true
|
||||
command -v opencode >/dev/null 2>&1 && has_opencode=true
|
||||
if ! $has_claude && ! $has_opencode; then
|
||||
log "Neither Claude Code nor opencode is installed; nothing to register."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -62,6 +72,8 @@ fi
|
||||
|
||||
registered=0
|
||||
skipped=0
|
||||
# The opencode mcp object, built up one server at a time.
|
||||
opencode_mcp='{}'
|
||||
|
||||
while read -r name transport target token_var _rest; do
|
||||
case "${name:-}" in ''|'#'*) continue ;; esac
|
||||
@@ -103,6 +115,20 @@ while read -r name transport target token_var _rest; do
|
||||
fi
|
||||
fi
|
||||
|
||||
auth=""
|
||||
[[ "${args[0]:-}" == -H ]] && auth="${args[1]#Authorization: }"
|
||||
|
||||
if [[ "$transport" == stdio ]]; then
|
||||
entry="$(jq -cn --arg c "$launcher" --arg t "$token" \
|
||||
'{type: "local", command: [$c]} + (if $t == "" then {} else {environment: {MCP_TOKEN: $t}} end)')"
|
||||
else
|
||||
entry="$(jq -cn --arg u "$url" --arg a "$auth" \
|
||||
'{type: "remote", url: $u} + (if $a == "" then {} else {headers: {Authorization: $a}} end)')"
|
||||
fi
|
||||
opencode_mcp="$(jq -c --arg n "$name" --argjson e "$entry" '.[$n] = $e' <<<"$opencode_mcp")"
|
||||
|
||||
$has_claude || continue
|
||||
|
||||
# A server already registered exactly as this row would register it is left
|
||||
# alone. Removing and re-adding it would throw away the OAuth session of a
|
||||
# server that signs in on its own (bill-tracker, atlassian), so every
|
||||
@@ -114,8 +140,6 @@ while read -r name transport target token_var _rest; do
|
||||
'.mcpServers[$n] // {} | {command, env: (if (.env // {}) == {} then null else .env end)}' \
|
||||
"$HOME/.claude.json" 2>/dev/null || true)"
|
||||
else
|
||||
auth=""
|
||||
[[ "${args[0]:-}" == -H ]] && auth="${args[1]#Authorization: }"
|
||||
want="$(jq -cn --arg u "$url" --arg a "$auth" \
|
||||
'{url: $u, auth: (if $a == "" then null else $a end)}')"
|
||||
have="$(jq -c --arg n "$name" \
|
||||
@@ -146,5 +170,36 @@ while read -r name transport target token_var _rest; do
|
||||
fi
|
||||
done <"$SERVERS"
|
||||
|
||||
if $has_claude; then
|
||||
log "MCP servers: $registered registered, $skipped skipped."
|
||||
log "Claude Code loads them at start, so restart a running session to pick them up."
|
||||
fi
|
||||
|
||||
$has_opencode || exit 0
|
||||
|
||||
# opencode reads opencode.jsonc or opencode.json, whichever this machine has.
|
||||
# The file holds tokens once this runs, so it is written private and swapped in
|
||||
# whole, the way ~/.claude.json already holds them for Claude Code.
|
||||
OPENCODE_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
|
||||
opencode_config="$OPENCODE_DIR/opencode.jsonc"
|
||||
[[ -e "$opencode_config" ]] || opencode_config="$OPENCODE_DIR/opencode.json"
|
||||
rules="$HOME/.claude/rules/*.md"
|
||||
|
||||
mkdir -p "$OPENCODE_DIR"
|
||||
current='{"$schema": "https://opencode.ai/config.json"}'
|
||||
[[ -e "$opencode_config" ]] && current="$(cat "$opencode_config")"
|
||||
|
||||
# jq cannot read JSONC comments. A file that has them is left alone rather
|
||||
# than rewritten without them.
|
||||
if ! merged="$(jq --argjson mcp "$opencode_mcp" --arg rules "$rules" \
|
||||
'.mcp = ((.mcp // {}) + $mcp) | .instructions = (((.instructions // []) - [$rules]) + [$rules])' \
|
||||
<<<"$current" 2>/dev/null)"; then
|
||||
warn "Could not parse $opencode_config as JSON; opencode MCP servers not written."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp="$(mktemp "$OPENCODE_DIR/.opencode-config.XXXXXX")"
|
||||
chmod 600 "$tmp"
|
||||
printf '%s\n' "$merged" >"$tmp"
|
||||
mv "$tmp" "$opencode_config"
|
||||
log "opencode: $(jq -r 'length' <<<"$opencode_mcp") MCP servers and the rules written to $opencode_config."
|
||||
+3
-3
@@ -12,9 +12,9 @@ and one file says where each piece goes.
|
||||
|
||||
| Path | Goes to | Why |
|
||||
| --- | --- | --- |
|
||||
| `agents/AGENTS.md` | `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md` | Two tools, two names, one file. These were byte-identical copies before this, waiting to disagree. |
|
||||
| `agents/skills/` | `~/.agents/skills`, `~/.claude/skills` | Personal skills live once in the checkout. Both destinations use `linkdir` so Panama's shipped skills can live beside them. |
|
||||
| `agents/rules/` | `~/.claude/rules` | |
|
||||
| `agents/AGENTS.md` | `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.config/opencode/AGENTS.md` | Three tools, one file. These were byte-identical copies before this, waiting to disagree. |
|
||||
| `agents/skills/` | `~/.agents/skills`, `~/.claude/skills` | Personal skills live once in the checkout. Both destinations use `linkdir` so Panama's shipped skills can live beside them. opencode reads both directories itself. |
|
||||
| `agents/rules/` | `~/.claude/rules` | opencode loads these through `instructions` in its config, which `setup/scripts/link-mcp` writes. |
|
||||
| `ssh/config` | `~/.ssh/config` | Host aliases only. Keys are per-machine and are never tracked. |
|
||||
| `espanso/identity.yml` | `~/.config/espanso/match/identity.yml` | Copied, not linked, because a machine may add its own triggers. |
|
||||
|
||||
|
||||
+3
-2
@@ -23,10 +23,11 @@
|
||||
# Gabriel's agent instructions on their machine.
|
||||
|
||||
# ── Agents ───────────────────────────────────────────────────────────────────
|
||||
# One file, two names. Claude Code reads CLAUDE.md and Codex reads AGENTS.md,
|
||||
# and before this they were two byte-identical copies waiting to disagree.
|
||||
# One file, three names. Claude Code reads CLAUDE.md, Codex and opencode read
|
||||
# AGENTS.md, and before this they were byte-identical copies waiting to disagree.
|
||||
link agents/AGENTS.md ~/.claude/CLAUDE.md
|
||||
link agents/AGENTS.md ~/.codex/AGENTS.md
|
||||
link agents/AGENTS.md ~/.config/opencode/AGENTS.md
|
||||
|
||||
# Skills, at both paths that look for them. Each tracked skill is linked from
|
||||
# this checkout so edits stay shared and `panama update` offers to commit them.
|
||||
|
||||
Reference in new issue
Block a user