From 99f663404706e21aa93481310030e18d54344a49 Mon Sep 17 00:00:00 2001 From: Patrick Singletary Date: Mon, 10 Aug 2026 15:54:16 -0400 Subject: [PATCH] Add hermes-macos-project-setup skill (v1.1.0) to shared repo --- skills/hermes-macos-project-setup/SKILL.md | 226 +++++++++++++ .../references/scripts/hermes-porkbun-mcp.sh | 65 ++++ .../references/scripts/project-init.sh | 301 ++++++++++++++++++ .../references/templates/AGENTS.md.tmpl | 46 +++ .../references/templates/README.md.tmpl | 53 +++ .../references/templates/gitignore.tmpl | 41 +++ .../references/templates/hermes.md.tmpl | 31 ++ .../templates/zed-settings.json.tmpl | 8 + .../references/templates/zed-tasks.json.tmpl | 47 +++ 9 files changed, 818 insertions(+) create mode 100644 skills/hermes-macos-project-setup/SKILL.md create mode 100644 skills/hermes-macos-project-setup/references/scripts/hermes-porkbun-mcp.sh create mode 100755 skills/hermes-macos-project-setup/references/scripts/project-init.sh create mode 100644 skills/hermes-macos-project-setup/references/templates/AGENTS.md.tmpl create mode 100644 skills/hermes-macos-project-setup/references/templates/README.md.tmpl create mode 100644 skills/hermes-macos-project-setup/references/templates/gitignore.tmpl create mode 100644 skills/hermes-macos-project-setup/references/templates/hermes.md.tmpl create mode 100644 skills/hermes-macos-project-setup/references/templates/zed-settings.json.tmpl create mode 100644 skills/hermes-macos-project-setup/references/templates/zed-tasks.json.tmpl diff --git a/skills/hermes-macos-project-setup/SKILL.md b/skills/hermes-macos-project-setup/SKILL.md new file mode 100644 index 0000000..e9bb53a --- /dev/null +++ b/skills/hermes-macos-project-setup/SKILL.md @@ -0,0 +1,226 @@ +--- +name: hermes-macos-project-setup +description: "Setup macOS: Tangled SSH, Porkbun MCP Bitwarden, Zed tasks." +version: 1.1.0 +author: Hermes Agent +license: MIT +platforms: [macos] +metadata: + hermes: + tags: [setup, macos, tangled, porkbun, mcp, bitwarden, keychain, bootstrap, zed, security] +--- + +# Hermes macOS Project Setup + +Complete development environment setup for new macOS machines. Covers Tangled SSH git hosting, Porkbun MCP DNS management (credentials in Bitwarden + macOS Keychain), project scaffolding (AGENTS.md, .hermes.md, Zed tasks), and security hardening from red-team audit. + +## Prerequisites + +- macOS with Homebrew +- Node.js 18+ (for Porkbun MCP server) +- Python 3.10+ (for Hermes MCP client) +- Bitwarden account with Personal API Key (https://vault.bitwarden.com → Settings → Security → Keys → View API key) +- Porkbun account with API access +- Tangled.org account with ATProto identity + +## Installation + +### 1. Core tools + +```bash +# Bitwarden CLI +brew install bitwarden-cli +bw --version + +# Hermes MCP Python SDK (use Homebrew Python, not system) +/opt/homebrew/bin/python3 -m pip install --break-system-packages mcp +``` + +### 2. Tangled SSH + +```bash +ssh-keygen -t ed25519 -C "you@email.com" -f ~/.ssh/id_ed25519_tangled +# Register at https://tangled.org/settings/keys (paste the .pub) + +cat >> ~/.ssh/config << 'SSH' +Host tangled.org + HostName tangled.org + User git + IdentityFile ~/.ssh/id_ed25519_tangled + IdentitiesOnly yes +SSH + +ssh -T git@tangled.org +# Expected: "Hi @your-handle! You're authenticated to knot..." +``` + +### 3. Bitwarden credential store + +```bash +mkdir -p ~/.config/bw +touch ~/.config/bw/env +chmod 600 ~/.config/bw/env +``` + +Edit `~/.config/bw/env`: +``` +export BW_CLIENTID=user.xxxxx +export BW_CLIENTSECRET=xxxxx +``` + +Get these from: https://vault.bitwarden.com → Settings → Security → Keys → View API key. + +```bash +source ~/.config/bw/env +bw login --apikey +bw unlock +``` + +### 4. Porkbun API keys (PKCE flow) + +Follow https://porkbun.com/llms/agent-setup: + +```bash +codeVerifier=$(openssl rand -base64 60 | tr '+/' '-_' | tr -d '=\n') +codeChallenge=$(printf '%s' "$codeVerifier" | openssl dgst -binary -sha256 | openssl base64 | tr '+/' '-_' | tr -d '=\n') + +curl -s -X POST https://api.porkbun.com/api/json/v3/apikey/request \ + -H 'Content-Type: application/json' \ + -d "{\"name\":\"Hermes Agent — $(hostname -s)\",\"codeChallenge\":\"$codeChallenge\"}" + +# → Open the returned authUrl in browser, approve (must be logged into Porkbun) + +curl -s -X POST https://api.porkbun.com/api/json/v3/apikey/retrieve \ + -H 'Content-Type: application/json' \ + -d "{\"requestToken\":\"\",\"codeVerifier\":\"$codeVerifier\"}" +# → Returns apikey (pk1_...) and secretapikey (sk1_...) — secret appears ONCE +``` + +Store in Bitwarden (after unlocking vault): +```bash +echo '{"type":1,"name":"Porkbun API","notes":"Domains: ","login":{"username":"pk1_...","password":"sk1_...","uris":[],"totp":null,"fido2Credentials":[]}}' | base64 | bw create item --session "$BW_SESSION" +``` + +Scope the key at https://porkbun.com/account/api — restrict to project domains and set a spend cap. + +### 5. Store master password in Keychain + +```bash +security add-generic-password -s bw-master -a "$USER" -w +# Prompts twice — type your Bitwarden master password +``` + +### 6. Pin Porkbun MCP server + +```bash +mkdir -p ~/scripts/porkbun-mcp +cd ~/scripts/porkbun-mcp +npm init -y --silent +npm install @porkbunllc/mcp-server@ --save-exact +``` + +To upgrade: `cd ~/scripts/porkbun-mcp && npm install @porkbunllc/mcp-server@ --save-exact`, review diff, restart Hermes. + +### 7. MCP wrapper script + +Create `~/scripts/hermes-porkbun-mcp.sh` (see this skill's references for the full script). Key points: +- Sources `~/.config/bw/env` for BW_CLIENTID/BW_CLIENTSECRET +- Fetches BW_PASSWORD from Keychain: `security find-generic-password -s bw-master -w` +- Exports BW_PASSWORD so `bw unlock --passwordenv` finds it +- Fetches Porkbun keys from Bitwarden item "Porkbun API" +- Launches pinned install: `exec node ~/scripts/porkbun-mcp/node_modules/@porkbunllc/mcp-server/dist/index.js` + +```bash +chmod 711 ~/scripts/hermes-porkbun-mcp.sh +``` + +### 8. Register MCP server in Hermes + +```bash +hermes config set mcp_servers.porkbun.command "$HOME/scripts/hermes-porkbun-mcp.sh" +hermes config set mcp_servers.porkbun.args '[]' +``` + +### 9. Project templates + +Create `~/templates/` with these files (see skill references for full content): +- `AGENTS.md.tmpl` — Multi-agent project rules (Tangled, wisp, deploy) +- `hermes.md.tmpl` — Hermes-specific instructions (skills, conventions, credentials) +- `gitignore.tmpl` — Comprehensive ignore (Node, Python, secrets, macOS, IDE) +- `zed-tasks.json.tmpl` — Zed tasks (Build, Test, Deploy, Push) +- `zed-settings.json.tmpl` — Zed settings (prettier, format_on_save) +- `README.md.tmpl` — Project skeleton README + +### 10. Bootstrap script + +Create `~/scripts/project-init.sh` (see skill references for full script). Prompts for project metadata, renders templates, git init with Tangled remote. + +```bash +chmod 711 ~/scripts/project-init.sh +``` + +### 11. Project workspace layout and shared conventions (~/dev + _shared) + +```bash +mkdir -p ~/dev && cd ~/dev +# Shared conventions repo — clone by DID (stable across handle changes) +git clone git@tangled.org:did:plc:gbmu2edwp7u7dva6x62gpgre _shared +``` + +Rules: +- **Working trees live in ~/dev only** — never inside OneDrive/CloudStorage paths (Files On-Demand breaks npm/git with ETIMEDOUT on dataless files). +- Tangled is the sync mechanism between machines (push/pull). OneDrive holds cold-storage repo bundles only, written by `_shared/bin/backup-bundles.zsh`. +- `~/dev/_shared` is the shared source of truth: AGENTS.md conventions, `bin/`, `scripts/`, `config/model_config.yaml`, `docs/`, and the full setup plan (`_shared/plan.md`). + +AGENTS.md generation (`_shared/bin/gen-agents.zsh`, idempotent): +- `_shared/AGENTS.common.md` — shared template; `{{REPO}}` is replaced with the repo dir name. +- `_shared/AGENTS..md` — optional per-repo extra blocks, appended verbatim. +- `~/dev/_shared/bin/gen-agents.zsh` # regenerate all repos +- `~/dev/_shared/bin/gen-agents.zsh zodiac` # one repo only +- Run it after cloning or creating any repo; it only rewrites files whose content differs. + +## Security Architecture + +### Credential hierarchy (least → most sensitive) + +| Credential | Storage | Access pattern | +|-----------|---------|---------------| +| BW_CLIENTID, BW_CLIENTSECRET | `~/.config/bw/env` (0600) | Sourced at runtime | +| BW_PASSWORD (master) | macOS Keychain `bw-master` | Fetched at runtime, exported, never on disk | +| Porkbun API keys | Bitwarden "Porkbun API" item | Fetched per-use via `bw get item` | +| Bluesky app passwords | Bitwarden per-project | Fetched per-use — never exported globally | + +### Supply chain + +Porkbun MCP server pinned via local install + `package-lock.json`. Upgrades are explicit. + +### Anti-patterns + +- Never `export` secrets in `.zshrc` (world-readable, inherited by all processes) +- Never store master passwords in files (Keychain only) +- Never use unpinned `npx -y` for credential-bearing servers +- Never leave old passwords in history or session databases + +## Verification + +```bash +ssh -T git@tangled.org # → authenticated +security find-generic-password -s bw-master -w >/dev/null && echo OK # → OK +bash -n ~/scripts/hermes-porkbun-mcp.sh # → no output +ls ~/scripts/porkbun-mcp/package-lock.json # → exists +grep -A3 mcp_servers ~/.hermes/config.yaml # → porkbun entry +~/scripts/project-init.sh # → prompts and scaffolds +~/dev/_shared/bin/gen-agents.zsh # → "gen-agents: 0 written, N unchanged, 0 skipped" +# After Hermes restart: mcp_porkbun_ping → credentialsValid: true +``` + +## Red-Team Hardening Checklist + +- [ ] `grep 'export.*PASSWORD' ~/.zshrc` → 0 +- [ ] `grep 'PASSWORD' ~/.zsh_history` → 0 +- [ ] `grep 'BW_PASSWORD' ~/.config/bw/env` → 0 +- [ ] `grep 'npx -y' ~/scripts/hermes-porkbun-mcp.sh` → 0 +- [ ] `ls -la ~/.zshrc | awk '{print $1}'` → `-rw-------` +- [ ] `bash -n ~/scripts/hermes-porkbun-mcp.sh` → PASS +- [ ] Porkbun dashboard: key scoped + spend cap set +- [ ] Bluesky app passwords: in Bitwarden, not in any file \ No newline at end of file diff --git a/skills/hermes-macos-project-setup/references/scripts/hermes-porkbun-mcp.sh b/skills/hermes-macos-project-setup/references/scripts/hermes-porkbun-mcp.sh new file mode 100644 index 0000000..424ad7b --- /dev/null +++ b/skills/hermes-macos-project-setup/references/scripts/hermes-porkbun-mcp.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# hermes-porkbun-mcp.sh — Fetch Porkbun API keys from Bitwarden, launch MCP server +# Called by Hermes as an MCP server entry point (stdio transport). +# +# Credentials: +# BW_CLIENTID / BW_CLIENTSECRET — in ~/.config/bw/env (0600) +# BW_PASSWORD — macOS Keychain item "bw-master" (or env override) +# Set via: security add-generic-password -s bw-master -a "$USER" -w +# Porkbun keys — Bitwarden item "Porkbun API" (login type) +# +# Pinning: @porkbunllc/mcp-server is installed locally at ~/scripts/porkbun-mcp/ +# (version pinned in package.json + package-lock.json). To upgrade: +# cd ~/scripts/porkbun-mcp && npm install @porkbunllc/mcp-server@ --save-exact +# Review the diff before restarting Hermes. + +set -euo pipefail + +# ── Authenticate to Bitwarden ────────────────────────────────────────────── +# Source API credentials from secure file if env vars aren't already set +if [ -z "${BW_CLIENTID:-}" ] && [ -f "$HOME/.config/bw/env" ]; then + source "$HOME/.config/bw/env" +fi + +# Resolve BW_PASSWORD: env overrides Keychain +if [ -z "${BW_PASSWORD:-}" ]; then + BW_PASSWORD=$(security find-generic-password -s bw-master -w 2>/dev/null || true) +fi +export BW_PASSWORD + +if [ -n "${BW_SESSION:-}" ]; then + # Session already unlocked — reuse it + : +elif [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ] && [ -n "${BW_PASSWORD:-}" ]; then + # Ensure logged in (may already be), then unlock to get session key + bw login --apikey 2>/dev/null || true + BW_SESSION=$(bw unlock --passwordenv BW_PASSWORD --raw 2>/dev/null) + if [ -z "$BW_SESSION" ]; then + echo '{"error": "Bitwarden: unlock failed. Check Keychain item bw-master (security find-generic-password -s bw-master)."}' >&2 + exit 1 + fi +else + echo '{"error": "Bitwarden: set BW_CLIENTID+BW_CLIENTSECRET in ~/.config/bw/env, add bw-master to Keychain, or set BW_SESSION."}' >&2 + exit 1 +fi + +# ── Fetch Porkbun keys from Bitwarden ────────────────────────────────────── + +PORKBUN_ITEM=$(bw get item "Porkbun API" --session "$BW_SESSION" 2>/dev/null) +if [ -z "$PORKBUN_ITEM" ]; then + echo '{"error": "Bitwarden: item \"Porkbun API\" not found. Create it first with bw create item."}' >&2 + exit 1 +fi + +export PORKBUN_API_KEY=$(echo "$PORKBUN_ITEM" | jq -r '.login.username') +export PORKBUN_SECRET_API_KEY=$(echo "$PORKBUN_ITEM" | jq -r '.login.password') + +if [ -z "$PORKBUN_API_KEY" ] || [ "$PORKBUN_API_KEY" = "null" ] || \ + [ -z "$PORKBUN_SECRET_API_KEY" ] || [ "$PORKBUN_SECRET_API_KEY" = "null" ]; then + echo '{"error": "Bitwarden: Porkbun API item missing username (api key) or password (secret key)."}' >&2 + exit 1 +fi + +# ── Launch Porkbun MCP server ────────────────────────────────────────────── + +exec node "$HOME/scripts/porkbun-mcp/node_modules/@porkbunllc/mcp-server/dist/index.js" \ No newline at end of file diff --git a/skills/hermes-macos-project-setup/references/scripts/project-init.sh b/skills/hermes-macos-project-setup/references/scripts/project-init.sh new file mode 100755 index 0000000..5a63900 --- /dev/null +++ b/skills/hermes-macos-project-setup/references/scripts/project-init.sh @@ -0,0 +1,301 @@ +#!/bin/bash +# project-init.sh — Bootstrap a new project with Tangled, Zed, and Hermes config +# Usage: ./project-init.sh +# Prompts for project metadata, generates all scaffolding. + +set -euo pipefail + +# ── Paths ────────────────────────────────────────────────────────────────── + +HERMES_ROOT="$HOME/Library/CloudStorage/OneDrive-Personal/hermes" +TEMPLATES="$HOME/templates" +SCRIPTS="$HOME/scripts" + +# Color helpers (safe for non-TTY) +if [ -t 1 ]; then + GREEN='\033[0;32m' + CYAN='\033[0;36m' + YELLOW='\033[1;33m' + NC='\033[0m' +else + GREEN='' CYAN='' YELLOW='' NC='' +fi + +echo -e "${CYAN}╔══════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ Project Bootstrap — Tangled ║${NC}" +echo -e "${CYAN}╚══════════════════════════════════════╝${NC}" +echo "" + +# ── Prerequisite checks ─────────────────────────────────────────────────── + +check_cmd() { + if ! command -v "$1" &>/dev/null; then + echo -e "${YELLOW}⚠ Missing: $1${NC}" + return 1 + fi +} + +MISSING=0 +check_cmd git || MISSING=1 +check_cmd jq || MISSING=1 +if [ $MISSING -eq 1 ]; then + echo "Install missing tools and retry." + exit 1 +fi + +if [ ! -f ~/.ssh/id_ed25519_tangled ]; then + echo -e "${YELLOW}⚠ Tangled SSH key not found at ~/.ssh/id_ed25519_tangled${NC}" + echo " Generate: ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_tangled" + echo " Register: https://tangled.org/settings/keys" + exit 1 +fi + +# ── Project name ────────────────────────────────────────────────────────── + +while true; do + read -r -p "Project name (kebab-case): " PROJECT_NAME + if [ -z "$PROJECT_NAME" ]; then + echo "Project name is required." + continue + fi + if [[ ! "$PROJECT_NAME" =~ ^[a-z0-9][a-z0-9-]{0,62}$ ]]; then + echo "Project name must be kebab-case (lowercase letters, digits, hyphens only)." + continue + fi + PROJECT_DIR="$HERMES_ROOT/$PROJECT_NAME" + if [ -d "$PROJECT_DIR" ]; then + echo -e "${YELLOW}⚠ Directory already exists: $PROJECT_DIR${NC}" + read -r -p "Overwrite? [y/N] " yn + case $yn in + [Yy]*) rm -rf "$PROJECT_DIR"; break ;; + *) echo "Aborting."; exit 0 ;; + esac + else + break + fi +done + +# ── Project type ────────────────────────────────────────────────────────── + +echo "" +echo "Project type:" +echo " 1) static-site — HTML/CSS/JS, framework build → Tangled Sites" +echo " 2) atproto-app — AT Protocol app (OAuth, DID:WEB, wisp)" +echo " 3) python-tool — Python script/tool (no web deploy)" +echo " 4) other — Minimal scaffold" +read -r -p "Choose [1-4]: " TYPE_NUM + +case $TYPE_NUM in + 1) PROJECT_TYPE="static-site" ;; + 2) PROJECT_TYPE="atproto-app" ;; + 3) PROJECT_TYPE="python-tool" ;; + 4) PROJECT_TYPE="other" ;; + *) echo "Invalid choice."; exit 1 ;; +esac + +# ── Description ──────────────────────────────────────────────────────────── + +echo "" +read -r -p "Short description: " PROJECT_DESC +PROJECT_DESC="${PROJECT_DESC:-A new project.}" + +# ── Type-specific prompts ────────────────────────────────────────────────── + +WISP_SITE="" +PRIMARY_DOMAIN="" +HANDLE="psingletary.com" +PDS_DOMAIN="" +DID_WEB_DOMAIN="" +IS_STATIC_SITE="" +IS_WISP="" +IS_ATPROTO_APP="" +IS_PYTHON_TOOL="" + +case $PROJECT_TYPE in + static-site) + IS_STATIC_SITE=1 + read -r -p "wisp.place site name (rkey, e.g. '$PROJECT_NAME'): " WISP_SITE_IN + WISP_SITE="${WISP_SITE_IN:-$PROJECT_NAME}" + IS_WISP=1 + read -r -p "Primary domain (e.g. '$PROJECT_NAME.psingletary.com'): " DOMAIN_IN + PRIMARY_DOMAIN="${DOMAIN_IN:-$PROJECT_NAME.psingletary.com}" + ;; + atproto-app) + IS_ATPROTO_APP=1 + IS_STATIC_SITE=1 + read -r -p "wisp.place site name (rkey, e.g. '$PROJECT_NAME'): " WISP_SITE_IN + WISP_SITE="${WISP_SITE_IN:-$PROJECT_NAME}" + IS_WISP=1 + read -r -p "ATProto handle [psingletary.com]: " HANDLE_IN + HANDLE="${HANDLE_IN:-psingletary.com}" + read -r -p "PDS domain (leave blank if none): " PDS_DOMAIN + read -r -p "DID:WEB domain (leave blank if none): " DID_WEB_DOMAIN + read -r -p "Primary domain (e.g. '$PROJECT_NAME.psingletary.com'): " DOMAIN_IN + PRIMARY_DOMAIN="${DOMAIN_IN:-$PROJECT_NAME.psingletary.com}" + ;; + python-tool) + IS_PYTHON_TOOL=1 + ;; + other) + ;; +esac + +# ── Git remote ───────────────────────────────────────────────────────────── + +TANGLED_REMOTE="git@tangled.org:psingletary.com/$PROJECT_NAME" + +# ── Confirm ──────────────────────────────────────────────────────────────── + +echo "" +echo -e "${CYAN}─── Configuration Preview ───${NC}" +echo " Name: $PROJECT_NAME" +echo " Type: $PROJECT_TYPE" +echo " Description: $PROJECT_DESC" +[ -n "$WISP_SITE" ] && echo " wisp site: $WISP_SITE" +[ -n "$PRIMARY_DOMAIN" ] && echo " Domain: $PRIMARY_DOMAIN" +[ -n "$PDS_DOMAIN" ] && echo " PDS domain: $PDS_DOMAIN" +[ -n "$DID_WEB_DOMAIN" ] && echo " DID:WEB: $DID_WEB_DOMAIN" +echo " Tangled: $TANGLED_REMOTE" +echo " Directory: $PROJECT_DIR" +echo "" + +read -r -p "Create project? [Y/n] " yn +case $yn in + [Nn]*) echo "Aborting."; exit 0 ;; +esac + +# ── Create directory structure ───────────────────────────────────────────── + +mkdir -p "$PROJECT_DIR/.hermes/plans" +mkdir -p "$PROJECT_DIR/.zed" + +# ── Render templates (sed substitution) ──────────────────────────────────── + +render() { + local tmpl="$1" out="$2" + if [ ! -f "$tmpl" ]; then + echo -e "${YELLOW}⚠ Template not found: $tmpl — skipping${NC}" + return + fi + # Escape sed-special chars (delimiter |, &, \) in all replacement values + local esc + esc() { printf '%s' "$1" | sed 's/[&|\\]/\\&/g'; } + sed \ + -e "s|{{PROJECT_NAME}}|$(esc "$PROJECT_NAME")|g" \ + -e "s|{{PROJECT_DESCRIPTION}}|$(esc "$PROJECT_DESC")|g" \ + -e "s|{{TANGLED_REMOTE}}|$(esc "$TANGLED_REMOTE")|g" \ + -e "s|{{WISP_SITE}}|$(esc "$WISP_SITE")|g" \ + -e "s|{{PRIMARY_DOMAIN}}|$(esc "$PRIMARY_DOMAIN")|g" \ + -e "s|{{HANDLE}}|$(esc "$HANDLE")|g" \ + -e "s|{{PDS_DOMAIN}}|$(esc "$PDS_DOMAIN")|g" \ + -e "s|{{DID_WEB_DOMAIN}}|$(esc "$DID_WEB_DOMAIN")|g" \ + "$tmpl" > "$out" +} + +# Render main files +render "$TEMPLATES/AGENTS.md.tmpl" "$PROJECT_DIR/AGENTS.md" +render "$TEMPLATES/hermes.md.tmpl" "$PROJECT_DIR/.hermes.md" +render "$TEMPLATES/README.md.tmpl" "$PROJECT_DIR/README.md" +cp "$TEMPLATES/gitignore.tmpl" "$PROJECT_DIR/.gitignore" + +# Render Zed configs (conditional sections handled by removing non-applicable lines) +if [ -n "$IS_WISP" ]; then + render "$TEMPLATES/zed-tasks.json.tmpl" "$PROJECT_DIR/.zed/tasks.json" + # Strip template markers after render + sed -i '' '/^{{#IS_WISP}}/d; /^{{\/IS_WISP}}/d' "$PROJECT_DIR/.zed/tasks.json" +else + # Strip wisp-specific block + sed '/{{#IS_WISP}}/,/{{ \/IS_WISP}}/d' "$TEMPLATES/zed-tasks.json.tmpl" | \ + sed \ + -e "s|{{PROJECT_NAME}}|$(printf '%s' "$PROJECT_NAME" | sed 's/[&|\\]/\\&/g')|g" \ + -e "s|{{WISP_SITE}}|$(printf '%s' "$WISP_SITE" | sed 's/[&|\\]/\\&/g')|g" \ + > "$PROJECT_DIR/.zed/tasks.json" +fi +cp "$TEMPLATES/zed-settings.json.tmpl" "$PROJECT_DIR/.zed/settings.json" + +# Remove conditional markers from AGENTS.md and .hermes.md +for file in "$PROJECT_DIR/AGENTS.md" "$PROJECT_DIR/.hermes.md"; do + if [ -f "$file" ]; then + # Remove blocks for inactive conditionals + [ -z "$IS_STATIC_SITE" ] && sed -i '' '/^{{#IS_STATIC_SITE}}/,/^{{\/IS_STATIC_SITE}}/d' "$file" 2>/dev/null || true + [ -z "$IS_ATPROTO_APP" ] && sed -i '' '/^{{#IS_ATPROTO_APP}}/,/^{{\/IS_ATPROTO_APP}}/d' "$file" 2>/dev/null || true + [ -z "$IS_WISP" ] && sed -i '' '/^{{#IS_WISP}}/,/^{{\/IS_WISP}}/d' "$file" 2>/dev/null || true + [ -z "$WISP_SITE" ] && sed -i '' '/^{{#WISP_SITE}}/,/^{{\/WISP_SITE}}/d' "$file" 2>/dev/null || true + [ -z "$PRIMARY_DOMAIN" ] && sed -i '' '/^{{#PRIMARY_DOMAIN}}/,/^{{\/PRIMARY_DOMAIN}}/d' "$file" 2>/dev/null || true + [ -z "$DID_WEB_DOMAIN" ] && sed -i '' '/^{{#DID_WEB_DOMAIN}}/,/^{{\/DID_WEB_DOMAIN}}/d' "$file" 2>/dev/null || true + [ -z "$PDS_DOMAIN" ] && sed -i '' '/^{{#PDS_DOMAIN}}/,/^{{\/PDS_DOMAIN}}/d' "$file" 2>/dev/null || true + + # Strip remaining standalone markers (active conditionals — remove the marker lines, keep content) + sed -i '' '/^{{#[A-Z_]*}}$/d' "$file" 2>/dev/null || true + sed -i '' '/^{{\/[A-Z_]*}}$/d' "$file" 2>/dev/null || true + + # Strip inline markers at end of lines (e.g., "|{{/WISP_SITE}}") + sed -i '' 's/{{#[A-Z_]*}}//g; s/{{\/[A-Z_]*}}//g' "$file" 2>/dev/null || true + fi +done + +# ── Generate project.yaml ────────────────────────────────────────────────── + +cat > "$PROJECT_DIR/.hermes/project.yaml" <> "$PROJECT_DIR/.hermes/project.yaml" <> "$PROJECT_DIR/.hermes/project.yaml" <> "$PROJECT_DIR/.hermes/project.yaml" < Detailed Hermes-specific instructions. AGENTS.md has the portable reference. + +## Skills to Load + +{{#IS_ATPROTO_APP}} +- `atproto-development` — AT Protocol patterns, OAuth, wisp deploys +- `small-business-atproto-migration` — if migrating a business site +{{/IS_ATPROTO_APP}} +- `plan` — before implementing features +- `adversarial-red-team-review` — red-team audit of plans and code + +## Conventions + +- **Plans:** `.hermes/plans/YYYY-MM-DD_HHMMSS-slug.md` +- **Deploy:** Push to Tangled (auto-deploy via Sites if configured) +- **Git remote:** `{{TANGLED_REMOTE}}` + +{{#IS_ATPROTO_APP}} +## AT Protocol + +- `wispctl` OAuth session: `~/.config/wispctl/state.sqlite` +- Tangled SSH: `~/.ssh/id_ed25519_tangled` +- DID:WEB private key: `~/.config/{{PROJECT_NAME}}/did-web-private-key.hex` (0o600) +{{/IS_ATPROTO_APP}} + +## Credentials + +- **Porkbun API keys:** Bitwarden → "Porkbun API" item +- **Other secrets:** Store in Bitwarden → create a folder named `{{PROJECT_NAME}}` \ No newline at end of file diff --git a/skills/hermes-macos-project-setup/references/templates/zed-settings.json.tmpl b/skills/hermes-macos-project-setup/references/templates/zed-settings.json.tmpl new file mode 100644 index 0000000..7441f38 --- /dev/null +++ b/skills/hermes-macos-project-setup/references/templates/zed-settings.json.tmpl @@ -0,0 +1,8 @@ +{ + "formatter": "prettier", + "format_on_save": "on", + "tab_size": 2, + "ensure_final_newline": true, + "remove_trailing_whitespace_on_save": true, + "soft_wrap": "preferred_line_length" +} \ No newline at end of file diff --git a/skills/hermes-macos-project-setup/references/templates/zed-tasks.json.tmpl b/skills/hermes-macos-project-setup/references/templates/zed-tasks.json.tmpl new file mode 100644 index 0000000..2fe84c6 --- /dev/null +++ b/skills/hermes-macos-project-setup/references/templates/zed-tasks.json.tmpl @@ -0,0 +1,47 @@ +{ + "tasks": [ + { + "label": "Build", + "command": "npm run build", + "cwd": "$ZED_WORKTREE_ROOT", + "use_new_terminal": true, + "allow_concurrent_runs": false + }, + { + "label": "Test", + "command": "npm test", + "cwd": "$ZED_WORKTREE_ROOT", + "use_new_terminal": true, + "allow_concurrent_runs": false + }, +{{#IS_WISP}} + { + "label": "Deploy to wisp.place", + "command": "wispctl deploy --path ./build --site {{WISP_SITE}} --spa --yes --db ~/.config/wispctl/state.sqlite psingletary.com", + "cwd": "$ZED_WORKTREE_ROOT", + "use_new_terminal": true, + "allow_concurrent_runs": false + }, +{{/IS_WISP}} + { + "label": "Push to Tangled (deploy)", + "command": "git push origin main", + "cwd": "$ZED_WORKTREE_ROOT", + "use_new_terminal": true, + "allow_concurrent_runs": false + }, + { + "label": "Git status", + "command": "git status", + "cwd": "$ZED_WORKTREE_ROOT", + "use_new_terminal": true, + "allow_concurrent_runs": true + }, + { + "label": "Open Tangled repo", + "command": "open https://tangled.org/psingletary.com/{{PROJECT_NAME}}", + "use_new_terminal": false, + "allow_concurrent_runs": true + } + ] +} \ No newline at end of file -- 2.51.2