#!/usr/bin/env bash
# 137-memories installer — wire team memories + design canon into Claude Code.
#
# Installs (all user-scope, no per-project setup):
#   - openviking MCP server (team memory backend)
#   - /remember /recall /forget slash commands
#   - 137-design skill (auto-loads design canon when working in 137 repos)
#   - SessionStart hook that surfaces the skill + memory in 137 repos
#   - Bearer file at ~/.137-memories/bearer (chmod 600) for non-MCP tools
#   - User-scope CLAUDE.md fragment routing auto-memory writes to the team backend
#
# Usage:
#   curl -fsSL https://137-memories.fly.dev/install.sh | bash
#   curl -fsSL https://137-memories.fly.dev/install.sh | OPENVIKING_ROOT_API_KEY=xxx bash
#   curl -fsSL https://137-memories.fly.dev/install.sh -o install.sh && less install.sh
#
# Prerequisites: claude CLI on PATH, jq on PATH. Bearer from 1Password.

set -euo pipefail

SERVER_URL="https://137-memories.fly.dev"
MCP_URL="$SERVER_URL/mcp"

if ! command -v claude >/dev/null 2>&1; then
  echo "ERROR: 'claude' CLI not found on PATH." >&2
  echo "Install Claude Code first: https://docs.claude.com/en/docs/claude-code" >&2
  exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
  echo "ERROR: 'jq' not found on PATH (required for hook + settings merge)." >&2
  exit 1
fi

KEY="${OPENVIKING_ROOT_API_KEY:-}"
if [ -z "$KEY" ]; then
  if [ ! -t 0 ]; then
    echo "ERROR: OPENVIKING_ROOT_API_KEY not set, and no TTY for interactive prompt." >&2
    echo "Either:" >&2
    echo "  curl ... | OPENVIKING_ROOT_API_KEY=<key> bash" >&2
    echo "  curl ... -o install.sh && bash install.sh" >&2
    exit 1
  fi
  printf "Paste OPENVIKING_ROOT_API_KEY (input hidden): " >&2
  read -rs KEY
  echo >&2
fi
if [ -z "$KEY" ]; then
  echo "ERROR: empty key." >&2
  exit 1
fi

echo "Validating key against $SERVER_URL ..."
HTTP=$(curl -sS -o /dev/null -w "%{http_code}" -X POST "$MCP_URL" \
  -H "Authorization: Bearer $KEY" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"installer","version":"1.0"}}}' || true)
if [ "$HTTP" != "200" ]; then
  echo "ERROR: bearer did not authenticate (HTTP $HTTP). Check the key value." >&2
  exit 1
fi

echo "Registering MCP server..."
claude mcp remove openviking -s user >/dev/null 2>&1 || true
# Order matters: --header is variadic in claude CLI 2.1.123+ and slurps subsequent
# tokens. Positional name+URL must come BEFORE --header. See `claude mcp add --help`.
if ! claude mcp add --transport http --scope user \
  openviking "$MCP_URL" \
  --header "Authorization: Bearer $KEY" >/dev/null 2>&1
then
  echo "ERROR: claude mcp add failed" >&2
  exit 1
fi

COMMANDS_DIR="${HOME}/.claude/commands"
mkdir -p "$COMMANDS_DIR"
echo "Installing slash commands to $COMMANDS_DIR ..."

cat > "$COMMANDS_DIR/remember.md" <<'COMMAND_EOF'
---
description: Save a memory to team memories (auto-tagged with project)
---

Save a memory to team memories. Writes directly via OpenViking's REST `POST /api/v1/content/write` endpoint — bypasses the unreliable LLM-driven memory extractor so the write is deterministic and always succeeds.

## Step 1 — determine the project tag

- **Explicit override.** If `$ARGUMENTS` starts with `[<name>]`, use it verbatim.
- **Auto-detect.** Otherwise run `(git rev-parse --show-toplevel 2>/dev/null || pwd) | xargs basename` via the Bash tool. Map to flat tag: `137-os`, `memories`, `izza-os`, `izza-welcome`, `jung137`, `fellowship-ops` → as-is. Anything else → `general`.

## Step 2 — synthesise the memory

If `$ARGUMENTS` (after stripping any `[tag]` prefix) has text, use it. Otherwise infer from recent conversation — decision, gotcha, fact, or "why" worth persisting.

Compose:
- A topic line (e.g. "Why does X?", "Decision: Y")
- The actual content with enough specificity that a future dev can act on it

## Step 3 — write directly via HTTP

Pick a 2–4 word lowercase_with_underscores slug describing the subject. Then run via Bash (do NOT echo `$KEY` or the body):

    KEY=$(jq -r '.mcpServers.openviking.headers.Authorization' ~/.claude.json | sed 's/^Bearer //')
    AUTHOR=$(git config user.email 2>/dev/null || git config --global user.email 2>/dev/null || echo "$(id -un)@local")
    SUFFIX=$(openssl rand -hex 2)
    URI="viking://user/default/memories/events/$(date -u +%Y/%m/%d)/<tag>_<slug>_${SUFFIX}.md"
    DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    # Two-line frontmatter so /console/api/catalog can index by author. The
    # body keeps the legacy [user]/[assistant] layout so existing search
    # behaviour is unchanged.
    CONTENT=$(printf -- '---\nauthor: %s\ntime: %s\nproject: %s\n---\n\n[user]: [%s] %s\n[assistant]: %s\n' "$AUTHOR" "$DATE" "<tag>" "<tag>" "<topic>" "<content>")
    curl -sS -X POST https://137-memories.fly.dev/api/v1/content/write \
      -H "Authorization: Bearer $KEY" \
      -H "X-OpenViking-Account: default" -H "X-OpenViking-User: default" \
      -H "Content-Type: application/json" \
      -d "$(jq -n --arg uri "$URI" --arg content "$CONTENT" '{uri:$uri, content:$content, mode:"create", wait:true}')"
    unset KEY

Substitute `<tag>`, `<slug>`, `<topic>`, `<content>` with actual values.

Response should include `"status":"ok"` and `"semantic_status":"complete"`. Surface any error.

## Step 4 — confirm

One sentence: what was stored, what tag, the filename (not full URI, not the bearer).

## Skip

- Ephemeral state, in-flight branches, file paths in flight
- Anything obvious from the code
- Sensitive values (keys, tokens, customer data)

User input: $ARGUMENTS
COMMAND_EOF

cat > "$COMMANDS_DIR/recall.md" <<'COMMAND_EOF'
---
description: Search team memories (current project by default; `all` for cross-project)
---

Search team memories via the `openviking` MCP server's `search` tool. Defaults to the current project so you don't get cross-project noise.

## Step 1 — determine scope

Detect the current project with the Bash tool:

    (git rev-parse --show-toplevel 2>/dev/null || pwd) | xargs basename

Map the basename to a project tag using the same rule as `/remember`:
- known projects (`137-os`, `memories`, `izza-os`, `izza-welcome`, `jung137`, `fellowship-ops`) → use as-is
- otherwise → `general`

Then look at `$ARGUMENTS`:
- If it ends with the literal token `all` (e.g. `recall stripe webhook all`), drop the trailing `all` — search **cross-project** with the rest of the text as the query.
- If it starts with `[<name>]`, use that tag and the rest as the query — search a **specific other project**.
- Otherwise, scope to the current project.

## Step 2 — search

Call `openviking.search` with:

- `query`: when scoped, prefix the user's text with `[<tag>]` so semantic ranking biases toward project-tagged memories. When `all`, pass the user's text as-is.
- `limit`: 8

Note: this is bias, not strict filtering — cross-project results may still surface if they're highly relevant. Don't try to post-filter; surface what comes back.

## Step 3 — present

For each result, show:
- URI
- Match score
- One-line abstract

Mention the scope at the top: e.g. *"searching `[137-os]` — append `all` to broaden"*.

If the user wants more detail on a specific result, call `openviking.read` on its URI.

If nothing scores above ~50%, say so plainly. Suggest:
- broadening with `all` (if scoped)
- or rephrasing the query

If `$ARGUMENTS` is empty, ask: *"What are you looking for? Append `all` for cross-project."*

User input: $ARGUMENTS
COMMAND_EOF

cat > "$COMMANDS_DIR/forget.md" <<'COMMAND_EOF'
---
description: Delete a team memory (irreversible)
---

The user wants to delete one or more memories from team memories.

Behaviour:

1. If `$ARGUMENTS` is a `viking://` URI, treat it as the target. Show the URI and ask the user to confirm with a yes/no. Only call `openviking.forget` after they explicitly confirm.

2. If `$ARGUMENTS` is a search query (or empty — in which case ask the user what they want to forget), call `openviking.search` with the query, show the top 5 results with their URIs, scores, and a one-line abstract each, and ask the user which URI(s) to forget. Then for each chosen URI, confirm once more before calling `forget`.

Rules:

- Never call `openviking.forget` without an explicit per-URI yes from the user. The action is irreversible.
- Never batch-forget multiple URIs in one call without naming each one in the confirmation.
- If the user replies ambiguously, ask again rather than guessing.

User input: $ARGUMENTS
COMMAND_EOF

# ============================================================================
# Bearer file — non-MCP tools (hooks, scripts, future Cursor/Codex setups) read
# this. Same trust level as ~/.claude.json (which already stores the bearer
# under mcpServers.openviking.headers.Authorization).
# ============================================================================

BEARER_DIR="${HOME}/.137-memories"
mkdir -p "$BEARER_DIR"
chmod 700 "$BEARER_DIR"
printf '%s' "$KEY" > "$BEARER_DIR/bearer"
chmod 600 "$BEARER_DIR/bearer"
echo "Wrote bearer to $BEARER_DIR/bearer (chmod 600)"

# ============================================================================
# 137-design skill — installed at ~/.claude/skills/137-design/SKILL.md.
# A thin reference doc with critical inline reminders; full canon lives in
# OpenViking and is fetched on demand via /api/v1/content/read so the skill
# can never go stale.
# ============================================================================

SKILLS_DIR="${HOME}/.claude/skills/137-design"
mkdir -p "$SKILLS_DIR"
echo "Installing 137-design skill to $SKILLS_DIR ..."

cat > "$SKILLS_DIR/SKILL.md" <<'SKILL_EOF'
---
name: 137-design
description: Use when designing or styling UI for any 137 surface — 137 OS, IZZA, Fellowship, or Rare TV. Loads tokens, voice rules, and component recipes from team memories so output matches the canon without per-repo setup.
---

# 137 design — activate before writing UI

The full design canon lives in team memories (OpenViking). This file gives you the always-true rules inline, plus pointers to fetch the rest on demand.

## Critical rules (do not violate)

- **Dark mode is default.** `data-theme="dark"`. Surface 0 is `oklch(0.145 0 0)`. Never `bg-black`.
- **Pure white text is forbidden.** Top is `oklch(0.985 0 0)` (`#FAFAFA`).
- **Sentence case** for headings, buttons, labels. UPPERCASE only for micro-labels (11px, 0.05em letter-spacing).
- **Color is semantic only** — status, accent, charts, company colors. No decorative color.
- **Borders, not shadows** in dark mode. Shadows only on floating modal/palette.
- **4px grid.** Every space, every line-height. Buttons are 32px tall.
- **Inter** for everything except numerals, which use **Geist Mono** with `font-variant-numeric: tabular-nums`.
- **Animate `transform` and `opacity` only.** Never width or height.
- **Voice: operator, not marketer.** No exclamation points, no hype. "None today." not "You've got nothing!".
- **Company accents:** 137=indigo (`oklch(0.623 0.214 259)`), IZZA=amber, Fellowship=rose, Rare TV=teal.

## Load the full canon when needed

The full SKILL.md, README, tokens, preview HTML, and UI kits live in team memories. Fetch via the `openviking` MCP server's `read` tool, or the REST endpoint:

```bash
BEARER=$(cat ~/.137-memories/bearer)
curl -sS -G https://137-memories.fly.dev/api/v1/content/read \
  --data-urlencode "uri=viking://user/default/memories/137_design/SKILL.md" \
  -H "Authorization: Bearer $BEARER" \
  -H "X-OpenViking-Account: default" -H "X-OpenViking-User: default" \
  | jq -r '.result'
```

Available canon URIs (under `viking://user/default/memories/137_design/`):

- `README.md` — full philosophy (companies, voice, visual foundations, iconography)
- `SKILL.md` — agent-facing skill manifest
- `colors_and_type.css.md` — all design tokens (color, type, spacing, radius, motion, layout)
- `preview/<page>.html.md` — visual previews (colors, type, spacing, components, brand)
- `ui_kits/<company>/<page>.html.md` — example surfaces per company

## When working in a 137 repo

1. Read these critical rules.
2. If the task touches non-trivial UI (more than a copy tweak), fetch `SKILL.md` and the relevant `ui_kits/` example.
3. If unsure of token values or recipe shapes, fetch `colors_and_type.css.md`.
4. Mention briefly that you're following the 137 canon so the user knows.

## When NOT in a 137 repo

This skill should not activate. Only use it when working in `137-xyz/*` repos or when the user explicitly references 137/IZZA/Fellowship/Rare TV branding.
SKILL_EOF

# ============================================================================
# SessionStart hook — when the cwd is a 137 repo, attach a brief reminder
# so the agent knows the skill + memory backend are available without the
# user having to ask. Cheap (~50 tokens once per session).
# ============================================================================

HOOKS_DIR="${HOME}/.claude/hooks"
mkdir -p "$HOOKS_DIR"
echo "Installing SessionStart hook to $HOOKS_DIR/137-session-start.sh ..."

cat > "$HOOKS_DIR/137-session-start.sh" <<'HOOK_EOF'
#!/usr/bin/env bash
# 137 SessionStart hook
# Detects 137 repos and attaches a brief context reminder pointing at the
# 137-design skill and the openviking MCP server. Outputs the JSON shape
# Claude Code expects for SessionStart hooks; otherwise exits silent.

set -e

cwd_input=""
if [ ! -t 0 ]; then
  cwd_input="$(cat 2>/dev/null || true)"
fi
cwd="$(printf '%s' "$cwd_input" | jq -r '.cwd // ""' 2>/dev/null || true)"
[ -z "$cwd" ] && cwd="${PWD:-$(pwd)}"

remote=""
if command -v git >/dev/null 2>&1; then
  remote="$(cd "$cwd" 2>/dev/null && git remote get-url origin 2>/dev/null || true)"
fi

is_137=false
case "$remote" in
  *137-xyz/*|*137-xyz.git*) is_137=true ;;
esac
if [ "$is_137" = false ]; then
  case "$cwd" in
    */137/*|*/137-os|*/137-os/*|*/izza-os|*/izza-os/*|*/izza-welcome|*/izza-welcome/*|*/jung137|*/jung137/*|*/fellowship-os|*/fellowship-os/*|*/fellowship-ops|*/fellowship-ops/*|*/raretv|*/raretv/*|*/RareTV|*/RareTV/*) is_137=true ;;
  esac
fi

[ "$is_137" = true ] || exit 0

jq -n '{
  hookSpecificOutput: {
    hookEventName: "SessionStart",
    additionalContext: "Working in a 137 repo. Two team services are wired automatically:\n\n1. **137-design skill** (~/.claude/skills/137-design/SKILL.md) — invoke via the Skill tool before designing UI; gives you tokens, voice rules, and recipe pointers. Critical rules are inline; full canon is in team memories under viking://user/default/memories/137_design/.\n\n2. **openviking MCP server** — team-shared memory across all 137 repos. Use /recall to surface prior decisions, /remember to save new ones. The MCP `search`, `read`, `forget` tools are also directly available.\n\nWhen you save auto-memories, route them to OpenViking REST instead of local files (see ~/.claude/CLAUDE.md). Bearer is at ~/.137-memories/bearer."
  }
}'
HOOK_EOF
chmod +x "$HOOKS_DIR/137-session-start.sh"

# Register the hook in user-scope settings.json. Idempotent: replaces any
# prior hook with command containing "137-session-start.sh".
SETTINGS="${HOME}/.claude/settings.json"
mkdir -p "$(dirname "$SETTINGS")"
[ -f "$SETTINGS" ] || echo '{}' > "$SETTINGS"

tmp_settings="$(mktemp)"
jq --arg cmd "$HOOKS_DIR/137-session-start.sh" '
  .hooks //= {} |
  .hooks.SessionStart //= [] |
  .hooks.SessionStart |= (
    map(
      if (.matcher? == "*" or .matcher? == null) and .hooks
      then .hooks |= map(select((.command // "") | contains("137-session-start.sh") | not))
      else . end
    )
    | map(select(.hooks | length > 0))
  ) |
  .hooks.SessionStart += [{
    matcher: "*",
    hooks: [{ type: "command", command: $cmd, timeout: 5 }]
  }]
' "$SETTINGS" > "$tmp_settings" && mv "$tmp_settings" "$SETTINGS"
echo "Registered SessionStart hook in $SETTINGS"

# ============================================================================
# Auto-memory routing — user-scope CLAUDE.md fragment.
# Tells Claude Code (and other AGENTS.md-respecting agents) that auto-memory
# writes should go to OpenViking via REST instead of local files. Same
# triggers as the harness's built-in auto-memory; different storage.
# ============================================================================

USER_CLAUDE_MD="${HOME}/.claude/CLAUDE.md"
MARKER_BEGIN="<!-- 137-memories:auto-memory-routing:BEGIN -->"
MARKER_END="<!-- 137-memories:auto-memory-routing:END -->"

ROUTING_BLOCK=$(cat <<'CLAUDE_MD_EOF'
<!-- 137-memories:auto-memory-routing:BEGIN -->
## 137 team auto-memory routing

When you would auto-save a memory file (per the auto-memory rules in your system prompt — user/feedback/project/reference types), **route it to the team backend instead of writing a local file**, so every teammate's next session benefits.

How to write:

```bash
BEARER=$(cat ~/.137-memories/bearer 2>/dev/null) || { echo "no bearer; falling back to local file"; exit 0; }
AUTHOR=$(git config user.email 2>/dev/null || git config --global user.email 2>/dev/null || echo "$(id -un)@local")
SUFFIX=$(openssl rand -hex 2)
TAG=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" 2>/dev/null)
SLUG="<short_slug>"   # 2-4 lowercase words, underscores, describing the subject
TYPE="<user|feedback|project|reference>"
URI="viking://user/default/memories/auto/${TYPE}/$(date -u +%Y/%m/%d)/${TAG}_${SLUG}_${SUFFIX}.md"
# Body uses the same frontmatter the local auto-memory would have produced,
# plus `author:` so the /console UI can index memories by who wrote them.
BODY=$(cat <<EOF
---
name: <memory name>
description: <one-line description>
type: ${TYPE}
project: ${TAG}
author: ${AUTHOR}
---

<memory content>
EOF
)
curl -sS -X POST https://137-memories.fly.dev/api/v1/content/write \
  -H "Authorization: Bearer $BEARER" \
  -H "X-OpenViking-Account: default" -H "X-OpenViking-User: default" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg uri "$URI" --arg content "$BODY" '{uri:$uri, content:$content, mode:"create", wait:false}')" \
  >/dev/null
unset BEARER
```

The `mode:"create"` ensures we don't accidentally clobber a same-named memory; the random hex4 suffix prevents collisions on identical slugs.

Same write rules as the local auto-memory:
- Save: decisions, gotchas, user corrections, validated approaches, references to external systems, project context.
- Skip: ephemeral state, anything obvious from the code, secrets, customer data.
- Dedup: before writing, recall (`openviking.search`) to check if a similar memory already exists. If yes, update via `mode:"replace"` on the existing URI rather than creating a new one.

If `~/.137-memories/bearer` is missing, fall back to the default local-file auto-memory.
<!-- 137-memories:auto-memory-routing:END -->
CLAUDE_MD_EOF
)

if [ ! -f "$USER_CLAUDE_MD" ]; then
  echo "$ROUTING_BLOCK" > "$USER_CLAUDE_MD"
  echo "Created $USER_CLAUDE_MD with auto-memory routing"
elif grep -q "$MARKER_BEGIN" "$USER_CLAUDE_MD"; then
  # Replace existing block.
  awk -v begin="$MARKER_BEGIN" -v end="$MARKER_END" -v block="$ROUTING_BLOCK" '
    BEGIN { skip = 0 }
    index($0, begin) { print block; skip = 1; next }
    index($0, end)   { skip = 0; next }
    !skip            { print }
  ' "$USER_CLAUDE_MD" > "$USER_CLAUDE_MD.tmp" && mv "$USER_CLAUDE_MD.tmp" "$USER_CLAUDE_MD"
  echo "Updated auto-memory routing block in $USER_CLAUDE_MD"
else
  printf '\n\n%s\n' "$ROUTING_BLOCK" >> "$USER_CLAUDE_MD"
  echo "Appended auto-memory routing block to $USER_CLAUDE_MD"
fi

unset KEY

echo
echo "Done."
echo "  - openviking MCP server registered (user scope)"
echo "  - 3 slash commands installed: /remember, /recall, /forget"
echo "  - 137-design skill installed at ~/.claude/skills/137-design/"
echo "  - SessionStart hook registered (auto-loads canon in 137 repos)"
echo "  - Bearer at ~/.137-memories/bearer (chmod 600)"
echo "  - Auto-memory routing block written to ~/.claude/CLAUDE.md"
echo
echo "Restart any open Claude Code session. Try in a 137 repo:"
echo "  /recall what is the IZZA accent color"
echo "Then ask Claude to design something — it will follow the canon automatically."
