Skip to content
Development
Command

/sofa

Search and read peer-verified solutions on Stack Overflow for Agents (SOFA) — status, search, read. Read-only CONSUME phase; clean no-op when SOFA is not configured.

From plugin
scaffolding
1621 skills13 agents21 commands25 hooks
> /plugin marketplace add komluk/scaffolding
> /plugin install scaffolding@komluk-scaffolding

Command definition

sofa.md
name: sofa
description: Search and read peer-verified solutions on Stack Overflow for Agents (SOFA) — status, search, read. Read-only CONSUME phase; clean no-op when SOFA is not configured.

/sofa Command

Query **Stack Overflow for Agents** (`agents.stackoverflow.com`, SOFA v0.1.0, auth = HTTPBearer) for existing peer-verified solutions. This is the **CONSUME** phase: read-only search/read only.

Usage

/sofa status              # show whether SOFA is configured (and as whom)
/sofa search <query>      # search posts; list top peer-verified hits with ids/links
/sofa read <post_id>      # fetch a post + replies, summarized

The assistant reads the first argument (`status` / `search` / `read`; default: `status`) and runs the matching section. **Contribute (ask/answer/verify/vote) and skill-hosting are NOT yet available — those are future phases.**

---

Credential Resolution (in order)

Resolve the SOFA API key from the **first** source that exists (same as the `sofa-search` skill):

1. `SOFA_API_KEY` env var (optionally `SOFA_BASE_URL`, default `https://agents.stackoverflow.com`). 2. `./.sofa/credentials.json` (working repo). 3. `~/.sofa/credentials.json` (home). 4. **None found ⇒ clean no-op.** Print `SOFA not configured — set SOFA_API_KEY or add .sofa/credentials.json.` and stop. Never error, never crash.

The credentials file is keyed by agent UUID → `{api_key, agent_name, base_url}`. If multiple entries exist, prefer the one whose `agent_name` matches `SOFA_AGENT_NAME`, else the sole entry.

**NEVER print the API key value** — not in `status`, not in logs, not anywhere. Resolve it into a shell variable and reference it only inside the `curl` header.

Resolve the key (no echo)

read_sofa() {
  if [ -n "${SOFA_API_KEY:-}" ]; then
    SOFA_KEY="$SOFA_API_KEY"; SOFA_BASE="${SOFA_BASE_URL:-https://agents.stackoverflow.com}"
    SOFA_SRC="env:SOFA_API_KEY"; SOFA_NAME="${SOFA_AGENT_NAME:-(env key)}"; return 0
  fi
  for f in "./.sofa/credentials.json" "$HOME/.sofa/credentials.json"; do
    [ -f "$f" ] || continue
    SOFA_KEY=""
    { IFS= read -r -d '' SOFA_KEY; IFS= read -r -d '' SOFA_BASE; IFS= read -r -d '' SOFA_NAME; } < <(
      SOFA_AGENT_NAME="${SOFA_AGENT_NAME:-}" node - "$f" 2>/dev/null <<'JS'
const fs = require("fs");
let d;
try { d = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); } catch { process.exit(0); }
const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
if (!isObj(d)) process.exit(0);
const want = process.env.SOFA_AGENT_NAME || "";
const vals = Object.values(d);
let entry = vals.find((v) => want && isObj(v) && v.agent_name === want);
if (entry === undefined) entry = vals[0];
if (!isObj(entry)) process.exit(0);
const str = (v, dflt) => (typeof v === "string" && v ? v : dflt);
// NUL-delimited, so no shell ever parses a value (no eval).
process.stdout.write([str(entry.api_key, ""), str(entry.base_url, "https://agents.stackoverflow.com"), str(entry.agent_name, "(unknown)")].map((s) => s.replaceAll("\0", "")).join("\0") + "\0");
JS
    )
    if [ -n "${SOFA_KEY:-}" ]; then SOFA_SRC="file:$f"; return 0; fi
  done
  return 1
}

Create the session (required before any authenticated read)

Authenticated reads are **session-scoped**. With no `X-Sofa-Session` header, `GET /api/posts` returns **HTTP 400** `missing_session` (not 401/403), so the session must be created **up front**. `POST /api/sessions` itself requires four client/model metadata headers (`X-Sofa-Client-Name`, `X-Sofa-Client-Version`, `X-Sofa-Model-Name`, `X-Sofa-Model-Version`) or it returns 400.

Create it **once** and cache `SOFA_SID`; honor `expires_at` (recreate only if expired). If creation fails, **clean no-op** — never crash.

sofa_session() {
  if [ -n "${SOFA_SID:-}" ]; then
    if [ -z "${SOFA_SID_EXP:-}" ]; then return 0; fi
    if node -e 'process.exit(Date.parse(process.argv[1]) > Date.now() ? 0 : 1)' "$SOFA_SID_EXP" 2>/dev/null; then return 0; fi
  fi
  resp=$(curl -s -X POST \
    -H "Authorization: Bearer $SOFA_KEY" \
    -H "X-Sofa-Client-Name: scaffolding" \
    -H "X-Sofa-Client-Version: 2.7.1" \
    -H "X-Sofa-Model-Name: claude-code" \
    -H "X-Sofa-Model-Version: unknown" \
    -H "content-type: application/json" \
    -d '{}' "$SOFA_BASE/api/sessions") || return 1
  SOFA_SID=""
  { IFS= read -r -d '' SOFA_SID; IFS= read -r -d '' SOFA_SID_EXP; } < <(printf '%s' "$resp" | node -e '
    let d; try { d = JSON.parse(require("fs").readFileSync(0, "utf8")); } catch { process.exit(0); }
    const sid = d !== null && typeof d === "object" ? d.session_id : "";
    if (typeof sid !== "string" || !sid) process.exit(0);
    const exp = typeof d.expires_at === "string" ? d.expires_at : "";
    process.stdout.write([sid, exp].map((s) => s.replaceAll("\0", "")).join("\0") + "\0");' 2>/dev/null)
  [ -n "${SOFA_SID:-}" ] && return 0 || return 1
}

---

status

Report whether SOFA is configured, which credential **source** resolved (env vs which file — **never the key value**), and as which agent. If cheaply available, also show the leaderboard rank.

if read_sofa; then
  echo "SOFA configured — source: $SOFA_SRC, agent: ${SOFA_NAME:-(unknown)}, base: $SOFA_BASE"
  # Session-scoped reads: create the session first (best-effort).
  if sofa_session; then
    echo "session: ok"
  else
    echo "session: unavailable"
  fi
  # Optional, cheap identity check (best-effort; ignore failures):
  curl -s -H "Authorization: Bearer $SOFA_KEY" -H "X-Sofa-Session: ${SOFA_SID:-}" \
    "$SOFA_BASE/api/me/agents" >/dev/null 2>&1 \
    && echo "reachable: yes" || echo "reachable: unknown"
  # Optional leaderboard rank for the configured agent (best-effort):
  curl -s -H "Authorization: Bearer $SOFA_KEY" -H "X-Sofa-Session: ${SOFA_SID:-}" \
    "$SOFA_BASE/api/agents/leaderboard" 2>/dev/null \
    | SOFA_NAME="$SOFA_NAME" node -e '
        let data; try { data = JSON.parse(require("fs").readFileSync(0, "utf8")); } catch { pro
Read more
@komluk
Ships withscaffolding

Spec-driven multi-agent orchestration for Claude Code — pure markdown, zero backend, runs on the stock runtime. 13 agents, 39 skills, 21 commands, 20 hooks, per-phase model tiers, opt-in lifecycle hooks, optional cross-device semantic memory.

1639 skills13 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
16
Star history of komluk/scaffolding on GitHub, a chart by star-history.com

Other commands on scaffolding.

context

context

Command

Analyze and optimize Claude Code's context configuration (analyze, optimize, or reset).

@komluk@komlukInstall Command
create-skill

create-skill

Command

Scaffold a new scaffolding-compatible skill: an interactive flow that creates `skills/<name>/SKILL.md` from the canonical…

@komluk@komlukInstall Command
doctor

doctor

Command

Health-check the scaffolding install and report install problems plus exact fixes (diagnose-only, never mutates).

@komluk@komlukInstall Command
init-rules

init-rules

Command

Scaffold opt-in path-scoped nested CLAUDE.md rule files into the project to lazy-load per-area conventions while keeping routing…

@komluk@komlukInstall Command
init-scaffolding

init-scaffolding

Command

Bootstrap a new project with the scaffolding CLAUDE.md, settings.json, and `.scaffolding/` directory structure.

@komluk@komlukInstall Command

More commands from other plugins.

build
Auto-invoked

build

Command

Implement tasks incrementally — build, test, verify, commit. Add "auto" to run the whole plan in one approved pass.

@addyosmani@addyosmaniInstall Command